Bike Sharing Analysis(一)- 探索數據


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. 理解數據

我們首先加載數據並做初始的分析。這里的目標主要是:

  1. 對數據有基本的了解
  2. 各個特征是如何分布
  3. 是否有缺失值
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 里對數據的說明,我們可以了解以下幾點:

  1. instant 為 id 索引,對預測無幫助
  2. 離散型特征有:season,yr,mnth,hr,holiday,weekday,workingday,weathersit
  3. 數值型特征有:temp,atemp,hum,windspeed,casual,registered,cnt
  4. 數值型特征中temp、atemp,hum,windspeed 均已做標准化處理,casual,registered,cnt未做標准化處理
  5. cnt 是 casual 與 registered 相加之和,可以由這兩個屬性計算出
  6. dteday為時間特征

按照特征描述分類,可以分為3大類:

  1. 時間相關,包含條目注冊時的時間:dteday,season,yr,mnth,hr,holiday,weekday,workingday
  2. 天氣相關,包含天氣條件:weathersit,temp,atemp,hun以及windspeed
  3. 條目本身相關,包含指定小時內,總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')

 

從分布圖我們可以了解到:

  1. 兩者的分布均為正傾斜
  2. 騎行的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')

從這個圖可以看到:

  1. registered 用戶的騎行次數,基本每天都是要明顯超出casual 用戶非常多
  2. 冬季騎行數會少下降

但是這個圖中,兩個時間之間的差比非常大,所以有很高的的抖動(毛刺)。有一個平滑毛刺的方法是:使用滾動平均值與滾動標准差來替換所需要可視化的值,以及它們的期望標准差情況。

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')

 

從這個圖我們可以了解到:

  1. 在工作日,用戶騎行時間主要分布在早上8點到下午6點之間,符合我們的預期;
  2. Registered 用戶為共享單車的主要使用者
  3. Casual 用戶在工作日使用共享單車有限
  4. 在休息日,可以明顯看到對於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用戶主要在周末偶爾使用共享單車。

 

當然,這個假設結論不能僅基於可視化圖像觀察,還需要有背后的統計測試進行支持。也就是我們下一節要討論的問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM