最近,我偶然發現了一個有趣的分享,有人利用政府提供的 YouBike 開放數據來解決自己在使用 YouBike 時遇到找不到車可以借的麻煩。這讓我靈光一閃:為什麼不利用這些數據做一點有趣的數據分析,來看看不同時間點的 YouBike 使用率呢?
接下來,我決定展開一個小小的實驗,使用 Google Apps Script 來分析這些公開的資料,從而深入了解城市中的共享單車系統。或許,我們可以發現一些出乎意料的使用趨勢,甚至預測何時能輕鬆找到一輛空閒的 YouBike。讓我們一起挖掘這些數據背後的秘密吧!
1. Google Sheet
2. Google Apps Script
3. Javascript
// 抓取 YouBike 數據並存儲
function fetchAndStoreYouBikeData() {
try {
var response = UrlFetchApp.fetch(YOUBIKE_API_URL);
var data = JSON.parse(response.getContentText());
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
if (!sheet) {
sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('Raw Data');
sheet.appendRow(['Timestamp', 'Station ID', 'Station Name', 'Total Bikes', 'Available Bikes', 'Available Spaces']);
}
var timestamp = new Date();
var rowsAdded = 0;
data.forEach(function(station) {
var stationId = String(station.sno); // 將站點 ID 轉換為字串
if (STATION_IDS.includes(stationId)) {
sheet.appendRow([
timestamp,
stationId,
station.sna,
station.total,
station.available_rent_bikes,
station.available_return_bikes
]);
rowsAdded++;
} else {
Logger.log('站點 ID 不匹配: ' + stationId);
}
});
Logger.log('成功添加了 ' + rowsAdded + ' 行數據');
// 如果沒有添加任何數據,記錄所有接收到的站點 ID
if (rowsAdded === 0) {
Logger.log('警告:沒有匹配的站點。接收到的站點 ID:' + data.map(station => station.sno).join(', '));
}
} catch (error) {
Logger.log('錯誤:獲取或存儲 YouBike 數據時出錯 - ' + error.toString());
}
}
使用簡單的「線性回歸」來預測未來供需量
function analyzeDemandAndSupply() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
var data = sheet.getDataRange().getValues();
var analysisSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Analysis');
if (!analysisSheet) {
analysisSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('Analysis');
analysisSheet.appendRow(['Station ID', 'Station Name', 'Avg Available Bikes', 'Demand Prediction', 'Supply Recommendation']);
}
// 清空分析表格(保留表頭)
analysisSheet.getRange(2, 1, analysisSheet.getLastRow(), 5).clear();
var stationData = {};
// 處理原始數據
for (var i = 1; i < data.length; i++) {
var stationId = String(data[i][1]); // 將站點 ID 轉換為字串
if (STATION_IDS.includes(stationId)) {
if (!stationData[stationId]) {
stationData[stationId] = {
name: data[i][2],
availableBikes: [],
totalBikes: data[i][3]
};
}
stationData[stationId].availableBikes.push(data[i][4]);
}
}
// 檢查是否有數據
if (Object.keys(stationData).length === 0) {
Logger.log('警告:沒有找到匹配的站點數據');
return;
}
// 分析每個站點
STATION_IDS.forEach(function(stationId) {
var station = stationData[stationId];
if (station && station.availableBikes.length > 0) {
var avgAvailableBikes = average(station.availableBikes);
var demandPrediction = predictDemand(station.availableBikes);
var supplyRecommendation = recommendSupply(avgAvailableBikes, station.totalBikes)
analysisSheet.appendRow([
stationId,
station.name,
avgAvailableBikes,
demandPrediction,
supplyRecommendation
]);
} else {
Logger.log('警告:站點 ' + stationId + ' 沒有數據或可用自行車數據為空');
}
});
}
// 需要一個函數來獲取站點名稱
function getStationNames() {
var rawDataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
var rawData = rawDataSheet.getDataRange().getValues();
var stationNames = {};
rawData.forEach(function(row) {
if (row[1] && row[2]) { // 確保站點 ID 和名稱都存在
stationNames[String(row[1])] = row[2];
}
});
return stationNames;
}
// 準備圖表資料
function prepareChartData() {
var rawDataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw Data');
var rawData = rawDataSheet.getDataRange().getValues();
var stationNames = getStationNames();
STATION_IDS.forEach(function(stationId) {
var stationName = stationNames[stationId] || stationId; // 如果找不到名稱,就使用 ID
var chartDataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Chart Data ' + stationName);
if (!chartDataSheet) {
chartDataSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('Chart Data ' + stationName);
}
// 清空圖表數據表格
chartDataSheet.clear();
// 添加表頭
chartDataSheet.appendRow(['Timestamp', '使用率', '可租借的數量', '可還車的數量']);
// 處理原始數據
var stationData = rawData.filter(function(row) {
return String(row[1]) === stationId;
});
// 將數據轉換為圖表格式
stationData.forEach(function(row) {
var timestamp = row[0];
var availableBikes = row[4];
var usingBike = row[5]; // 空車柱數量
var usePercent = Math.round(usingBike / row[3] * 100) // 可用車數 / 總車數 = 使用率
chartDataSheet.appendRow([new Date(timestamp), usePercent, availableBikes, usingBike]);
});
// 創建圖表
createChart(chartDataSheet, stationId, stationName);
});
}
// createChart 函數
function createChart(sheet, stationId, stationName) {
var charts = sheet.getCharts();
// 如果已存在圖表,則刪除
charts.forEach(function(chart) {
sheet.removeChart(chart);
});
// 創建新圖表
var chartBuilder = sheet.newChart();
// 設置圖表類型為線圖
chartBuilder.setChartType(Charts.ChartType.LINE);
// 設置圖表標題,使用站點名稱
chartBuilder.setOption('title', 'YouBike 站點 ' + stationName + ' 使用率分析');
// 設置 X 軸
chartBuilder.setOption('hAxis', {title: '時間'});
// 設置 Y 軸
chartBuilder.setOption('vAxis', {title: '使用率'});
// 添加數據範圍
chartBuilder.addRange(sheet.getRange("A2:B"));
// 所有範圍:
// chartBuilder.addRange(sheet.getDataRange());
// 設置圖表位置
chartBuilder.setPosition(1, 6, 0, 0);
// 設置圖例
chartBuilder.setOption('legend', {position: 'bottom'});
// 設置線條顏色
chartBuilder.setOption('series', {
// 0: {color: 'blue', labelInLegend: '可租數量'}, // 可用車輛線條顏色
// 1: {color: 'red', labelInLegend: '可還數量'}, // 空車率線條顏色
0: {color: 'green', labelInLegend: '使用率'} // 使用率線條顏色
});
// 添加圖表到工作表
sheet.insertChart(chartBuilder.build());
}
此次針對公館捷運站4個不同出口的youbike站點來觀察不同時段的使用率