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用户主要在周末偶尔使用共享单车。

当然,这个假设结论不能仅基于可视化图像观察,还需要有背后的统计测试进行支持。也就是我们下一节要讨论的问题。

原文地址:https://www.cnblogs.com/zackstang/p/13963876.html