1. Bike Sharing Analysis
在這章主要介紹如何分析共享單車服務數據,以及如何基於時間、天氣狀態特征來識別單車的使用模式。除此之外,我們還會引入可視化分析,假設檢驗、以及時間序列分析的概念與方法。
共享單車是城市里較為快速的通勤方式,了解用戶使用共享單車所考慮的因素,對於公司和用戶來說都是必須的。
從公司的角度來看,了解某一個時間段某一區域里,用戶對共享單車的需求,可以顯著地提升業績以及用戶滿意度。同時也可以優化未來的運營成本。從用戶的角度來看,可能最重要的因素是:在最短的時間內滿足對單車的需求。這點與公司的利益是一致的。
在這篇文章中,我們會分析來自於華盛頓Capital Bikeshare 的單車共享數據,時間跨度從2011年1月1日,到2012年12月31日,數據以小時級別進行了聚合。也就是說, 數據中不包含單次騎行的起始與終止的位置,而是僅僅每小時的騎行次數。除此之外,數據集中還有額外的天氣信息,可作為一個影響因素,影響在某個特定時間點對騎行的需求總數(天氣比較差的時候可能會對騎行需求有較大的影響)。
1.1. Note
源數據獲取地址:https://archive.ics.uci.edu/ml/datasets/Bike+Sharing+Dataset#
若是對此topic 比較感興趣,可以進一步閱讀論文:Fanaee-T, Hadi, and Gama, Joao, 'Event labeling combining ensemble detectors and background knowledge', Progress in Artificial Intelligence (2013): pp. 1-15, Springer Berlin Heidelberg.
雖然這個主題僅是對共享單車進行分析,但是提供的技術可以很容易應用到其他不同的共享商業模型,例如共享汽車或是共享摩托車等。
2. 理解數據
我們首先加載數據並做初始的分析。這里的目標主要是:
- 對數據有基本的了解
- 各個特征是如何分布
- 是否有缺失值
import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import numpy as np import s3fs %matplotlib inline # load hourly data hourly_data = pd.read_csv('s3://tang-sagemaker/workshop/bike_sharing/hour.csv') print(f"Shape of data: {hourly_data.shape}") print(f"Number of missing values in the data: {hourly_data.isnull().sum().sum()}") Res: Shape of data: (17379, 17) Number of missing values in the data: 0
查看一下統計數據:
結合Readme.txt 里對數據的說明,我們可以了解以下幾點:
- instant 為 id 索引,對預測無幫助
- 離散型特征有:season,yr,mnth,hr,holiday,weekday,workingday,weathersit
- 數值型特征有:temp,atemp,hum,windspeed,casual,registered,cnt
- 數值型特征中temp、atemp,hum,windspeed 均已做標准化處理,casual,registered,cnt未做標准化處理
- cnt 是 casual 與 registered 相加之和,可以由這兩個屬性計算出
- dteday為時間特征
按照特征描述分類,可以分為3大類:
- 時間相關,包含條目注冊時的時間:dteday,season,yr,mnth,hr,holiday,weekday,workingday
- 天氣相關,包含天氣條件:weathersit,temp,atemp,hun以及windspeed
- 條目本身相關,包含指定小時內,總records數:casual,registered以及cnt
3. 數據預處理
為了適應機器學習算法的需求,使預測結果更為准去,我們需要對數據做預處理。偶爾也會為了可視化的目的進行預處理展示。
3.1. 處理時間與天氣特征
這里對時間與天氣特征進行處理,主要不是為了方便機器學習訓練,而是為了方便人可讀。在數據集中,有部分特征已經被編碼過,我們再次將這些特征進行編碼,方便人可讀:
- season 特征,它的值為1到4,分別對應的是 Spring、Summer、Fall和 Winter;
- yr特征,它的值為 0和1,分別對應2011 和 2012;
- weekday特征,值為0到6,分別對應一周的每天(0: Sunday,6: Saturday)
- weathersit特征,值為1到4,分別對應的是clear, cloudy, light_rain_snow, heavy_rain_snow
- hum特征,被縮放到了0到1區間內,原始應為0到100區間內
- windspeed特征,被縮放到了0到1區間內,原始應為0到67區間內
首先處理 season、yr以及weekday 特征:
preprocessed_data = hourly_data.copy() # temperal features seasons_map = {1:"Spring", 2:"Summer", 3:"Fall", 4:"Winter"} yr_map = {0:2011, 1:2012} weekday_mapping = {0:'Sunday', 1:'Monday', 2:'Tuesday', 3:'Wednesday', 4:'Thursday', 5:'Friday', 6:'Saturday'} preprocessed_data['season'] = preprocessed_data['season'].apply(lambda x: seasons_map[x]) preprocessed_data['yr'] = preprocessed_data['yr'].apply(lambda x: yr_map[x]) preprocessed_data['weekday'] = preprocessed_data['weekday'].apply(lambda x: weekday_mapping[x])
繼續處理weathersit、hum以及windspeed特征。其中hum以及wind的原始范圍分別為 [0, 100] 以及[0, 67],已經被縮放為[0, 1]:
# weather features weather_mapping = {1: 'clear', 2: 'cloudy', \ 3: 'light_rain_snow', 4: 'heavy_rain_snow'} preprocessed_data['weathersit'] = preprocessed_data['weathersit'].apply(lambda x: weekday_mapping[x]) preprocessed_data['hum'] = preprocessed_data['hum'] * 100 preprocessed_data['windspeed'] = preprocessed_data['windspeed'] * 67
驗證轉換效果:
# validate cols = ['season', 'yr', 'weekday', 'weathersit', 'hum', 'windspeed'] preprocessed_data[cols].sample(10, random_state=42)
3.2. Registered versus Casual分析
根據數據說明,registered + casual = cnt,我們可以驗證一下:
assert (preprocessed_data['registered'] + preprocessed_data['casual'] == preprocessed_data['cnt']).all(), 'not all are equal'
首先對這2個特征進行分析的話,可以看一下它們的分布,這里會使用到seaborn,它是基於標准matplotlib構建的可視化庫,為不同的統計圖提供了更高級的接口。下面我們看一下registered 與 casual 騎行的分布:
# plot distribution of registered and casual sns.distplot(preprocessed_data['registered'], label='registered') sns.distplot(preprocessed_data['casual'], label='casual') plt.legend() plt.xlabel('rides') plt.title("Rides Distribution") plt.savefig('figs/rides_distributions.png', format='png')
從分布圖我們可以了解到:
- 兩者的分布均為正傾斜
- 騎行的registered 用戶遠多於casual 用戶
下面我們探索一下隨時間變化的騎行數,以天為單位:
# plot evolotion of ride over time plot_data = preprocessed_data[['registered', 'casual', 'dteday']] ax = plot_data.groupby('dteday').sum().plot(figsize=(10, 6)) ax.set_xlabel("time") ax.set_ylabel("number of rides per day") plt.savefig('figs/rides_daily.png', format='png')
從這個圖可以看到:
- registered 用戶的騎行次數,基本每天都是要明顯超出casual 用戶非常多
- 冬季騎行數會少下降
但是這個圖中,兩個時間之間的差比非常大,所以有很高的的抖動(毛刺)。有一個平滑毛刺的方法是:使用滾動平均值與滾動標准差來替換所需要可視化的值,以及它們的期望標准差情況。
plot_data = preprocessed_data[['registered', 'casual', 'dteday']] plot_data = plot_data.groupby('dteday').sum() # define window for computing the rolling mean and standard deviation window = 7 rolling_means = plot_data.rolling(window).mean() rolling_deviation = plot_data.rolling(window).std() ax = rolling_means.plot(figsize=(10, 6)) ax.fill_between(rolling_means.index, rolling_means['registered'] + 2*rolling_deviation['registered'], rolling_means['registered'] - 2*rolling_deviation['registered'], alpha=0.2) ax.fill_between(rolling_means.index, rolling_means['casual'] + 2*rolling_deviation['casual'], rolling_means['casual'] - 2*rolling_deviation['casual'], alpha=0.2) ax.set_xlabel("time") ax.set_ylabel("number of rides per day") plt.savefig('figs/rides_aggregated.png', format='png')
下面我們繼續關注一下騎行請求隨一天中不同小時、以及一周中不同天的分布情況。我們預期是會有隨時間變化的騎行請求數,因為直覺來看,騎行的請求數應該在一天中某幾個特定小時,以及一周中的特定天是有關的。
# select relevant columns plot_data = preprocessed_data[['hr', 'weekday', 'registered', 'casual']] plot_data = plot_data.melt(id_vars=['hr', 'weekday'], var_name='type', value_name='count') grid = sns.FacetGrid(plot_data, row='weekday', col='type', height=2.5, aspect=2.5, row_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']) grid.map(sns.barplot, 'hr', 'count', alpha=0.5) grid.savefig('figs/weekday_hour_distributions.png', format='png')
從這個圖我們可以了解到:
- 在工作日,用戶騎行時間主要分布在早上8點到下午6點之間,符合我們的預期;
- Registered 用戶為共享單車的主要使用者
- Casual 用戶在工作日使用共享單車有限
- 在休息日,可以明顯看到對於registered 與 casual 用戶騎行的分布有變化,但registered 用戶仍占主要使用者大部分;兩者的分布基本一致,在早上11點AM 到 6點PM的分布類似於均勻分布
總的來說,我們可以得出結論:大部分的單車使用在工作日,一般為工作時間內(如早9晚5)。
3. 天氣對騎行影響分析
下面我們繼續探索天氣對騎行的影響。
plot_data = plot_data.melt(id_vars=['hr', 'season'], var_name='type', value_name='count') grid = sns.FacetGrid(plot_data, row='season', col='type', height=2.5, aspect=2.5, row_order = ['Spring', 'Summer', 'Fall', 'Winter']) grid.map(sns.barplot, 'hr', 'count', alpha=0.5)
從四個季度來看,分布基本一致,其中春季的騎行需求稍低。
再從weekday方面進一步探索:
plot_data = preprocessed_data[['weekday', 'season', 'registered', 'casual']] plot_data = plot_data.melt(id_vars=['weekday', 'season'], var_name='type', value_name='count') grid = sns.FacetGrid(plot_data, row='season', col='type', height=2.5, aspect=2.5, row_order = ['Spring', 'Summer', 'Fall', 'Winter']) grid.map(sns.barplot, 'weekday', 'count', alpha=0.5, order=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])
從這個圖我們可以看到:對於registered 用戶來說,工作日使用量高於休息日使用量;對於casual 用戶來說,休息日使用量高於工作日使用量。
據此,我們可能會提出初始的假設:registered 用戶用共享單車主要是為了通勤,而casual用戶主要在周末偶爾使用共享單車。
當然,這個假設結論不能僅基於可視化圖像觀察,還需要有背后的統計測試進行支持。也就是我們下一節要討論的問題。