新闻中心
Kaggle共享单车数据分析(kaggle共享单车项目)
共享单车在2015年起开始在国内掀起热潮,大量资本涌入,一时间摩拜,ofo,HelloBike,小鸣单车,永安行等公司开启了中国互联网历史上的又一场市场大战,各色Logo的共享单车遍布街头,甚至堆积如山。这一盛况没有持续太久,烧钱之后,补贴过后,繁华褪去,大批共享单车公司倒下,如今共享单车的战局跟当年网约车大战类似,仅剩摩拜一家独大,不胜唏嘘。
共享单车早年已经出现在美国,本文数据来自Kaggle的Bike Sharing Demand预测项目,由共享单车公司Capital Bikeshare提供。数据统计了华盛顿特区2011年1月1日至2012年12月31日共享单车租赁数据。数据集分为train.csv和test.csv,train.csv包含了这两年在每月1日至19日的租车数据,test.csv包含了每月20日至当月最后一天的天气和时间数据,租车数据需要项目参与者预测。由于本文不涉及机器学习部分,因此仅对train.csv数据进行统计分析。
1. 提出问题
数据中各列标题如下:
datetime时间 - 格式:年月日小时 season季节 - 1 = spring春天, 2 = summer夏天, 3 = fall秋天, 4 = winter冬天 holiday节假日 - 0:否,1:是 workingday工作日 - 该天既不是周末也不是假日(0:否,1:是) weather天气 - 1:晴天,2:阴天 ,3:小雨或小雪 ,4:恶劣天气(大雨、冰雹、暴风雨或者大雪) temp实际温度 - 摄氏度 atemp体感温度 - 摄氏度 humidity湿度 - 相对湿度 windspeed风速 - 风速 casual - 未注册用户租车数量 registered - 注册用户租车数量 count - 总租车数量因此可以提出如下问题:
2011年至2012年的共享单车的总体租赁情况如何?租车量是否受季节因素影响?工作日,节假日的租车量有何不同?每天哪个时段的租车量较高?天气情况对租车量的影响如何?注册用户和非注册用户的租车量对比,用户习惯是否不同?2. 理解数据
下面开始读取数据并尝试理解数据。
# 导入模块包 import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.ticker import FuncFormatter import seaborn as sns %matplotlib inline %config InlineBackend.figure_format = retina # 读取csv文件并查看前5行数据 df = pd.read_csv(train.csv, parse_dates=[datetime]) df.head()

数据共有10886行,所有列都没有缺失值,因此可以略过缺失值填充过程。
# 查看数据集描述性统计 df.describe()
各列的描述性统计数据相对合理,没有明显的异常值,因此可以跳到数据清洗过程。
3. 数据清洗
由于时间数据仅存在datetime一列中,为了分析各个时段的租车数据,例如每个时间段,每天或者每月的数据,就需要将年月日时刻信息从datetime数据中提取出来,因此需要增加时间数据列。
3.1 增加时间数据列
# 保留原始数据df,新建df的副本进行数据清洗 data = df.copy() # 导入dateutil模块帮助提取时间数据 from dateutil.parser import * # 提取datetime数据,并将数据类型转换成str date_time = data[datetime].astype(str) # 利用parse()解析时间数据 parsed = date_time.apply(lambda x: parse(x)) # 提取年、月、日、小时、星期日数,并以列的形式添加到data数据中 data[year] = parsed.apply(lambda x: x.year) data[month] = parsed.apply(lambda x: x.month) data[day] = parsed.apply(lambda x: x.day) data[hour] = parsed.apply(lambda x: x.hour) data[weekday] = parsed.apply(lambda x: x.strftime("%a")) # 查看列标题 data.columns Index([datetime, season, holiday, workingday, weather, temp, atemp, humidity, windspeed, casual, registered, count, year, month, day, hour, weekday], dtype=object) # 调整列标题顺序,方便理解数据 columns = [datetime, year, month, day, hour, weekday, season, holiday, workingday, weather, temp, atemp, humidity, windspeed, casual, registered, count,] data = data[columns] # 查看列顺序调整后的数据 data.head()
数据符合预期,下面使用map()函数做进一步的数据清洗。
3.2 数据类型转换
季节,节假日,工作日,星期日数等列数据目前都是int类型,为了便于进行数据可视化,可以使用map()和pd.Categorical()将这些列的数据转换成分类数据。例如季节数据1代表春天,可以将数值型数据1转换成文本型数据Spring,再转换成带顺序的分类型数据,这样在数据可视化时会自动保留Spring--Summer--Fall--Winter的季节顺序。
对于一年中任意一天,它只能是工作日或者非工作日,其中非工作日又分为节假日和周末,因此,任意一天的日期类型只能是workingday,或者weekend,或者holiday,因此可以用一列由这三者组成的数据替代workingday和holiday两列的数据。
# 制作数据副本 bike_rental = data.copy() # 定义day_type函数,功能为判断日期类型 def day_type(row): # 节假日则返回2 if row[holiday] == 1: return 2 # 非节假日但非工作日,即周末则返回1, 工作日返回0 elif row[workingday] == 0 and row[holiday] == 0: return 1 else: return 0 # 添加一列day_type,值为日期的日期类型 bike_rental[day_type] = bike_rental.apply(day_type, axis=1) # 使用map()函数转换数值为文本型 bike_rental[season] = bike_rental[season].map({1: Spring, 2: Summer, 3: Fall, 4: Winter}) bike_rental[day_type] = bike_rental[day_type].map({0: Workingday, 1: Weekend, 2: Holiday}) # 将文本型数据转换成分类数据 bike_rental[season] = pd.Categorical(values=bike_rental[season], ordered=True, categories=[Spring, Summer, Fall, Winter]) bike_rental[day_type] = pd.Categorical(values=bike_rental[day_type], ordered=True, categories=[Workingday, Weekend, Holiday]) bike_rental[weekday] = pd.Categorical(values=bike_rental[weekday], ordered=True, categories=[Mon, Tue, Wed, Thu, Fri, Sat, Sun]) # 将datetime列转换成行索引 bike_rental.set_index(datetime, inplace=True)4. 数据可视化及数据分析
# 设置作图风格 plt.style.use(fivethirtyeight) # 设置颜色组 c1 = (114/255,158/255,206/255) # 蓝 c2 = (255/255,158/255,74/255) # 橙 c3 = (103/255,191/255,92/255) # 绿 c4 = (237/255,102/255,93/255) # 红 c5 = (173/255,139/255,201/255) # 紫 c6 = (168/255,120/255,110/255) # 棕4.1 2011年至2012年的共享单车的总体租赁情况如何?
为了方便展示,统计每月的注册用户和非注册用户数据,以及注册用户所占的租车量比例。
# 统计每月的租车量数据 total = bike_rental[[casual, registered, count]].resample(M).sum().reset_index() # 计算注册用户租车量比例 total[rate] = total[registered] / total[count] # 将月份数据转换成文本型 total[month] = total[datetime].apply(lambda x: x.strftime(%b\n%Y) if x.strftime(%b) == Jan else x.strftime(%b)) # 设定作图尺寸 fig, ax1 = plt.subplots(figsize=(15,6)) # 根据每月租车数量作图 ax1 = plt.bar(np.arange(24), total[casual], label=Casual, color=c2, bottom=total[registered]) ax2 = plt.bar(np.arange(24), total[registered], label=Registered, color=c1) # 美化图表 plt.title(Monthly Bike Rental in 2011-2012) plt.xlim(-1,24) plt.ylim(0, 220000) plt.gca().get_yaxis().set_major_formatter(FuncFormatter(lambda x, p: format(int(x), ,))) plt.legend(loc=center left) plt.xticks(np.arange(24), total[month], rotation=0) # 根据注册用户租车比例作图 ax3 = plt.twinx() ax3 = plt.plot(np.arange(24), total[rate], label=2011, color=c3) # 美化图表 plt.ylim(0,1.1) plt.yticks([0.5, 0.75, 0.875, 1], [50%, 75%, 87.5%, 100%]) plt.grid(b=None) plt.text(x=20, y=0.94, s=Registered Rental %, color=c3, weight=bold, rotation=0, backgroundcolor=#f0f0f0) plt.axhline(y=0.01, linewidth=3, color=black, alpha=0.7) # 添加水印 plt.text(x = -1.5, y = -0.2, s = ©MilankakaZhang Data Source: Kaggle, fontsize = 14, color = grey) Text(-1.5,-0.2,©MilankakaZhang Data Source: Kaggle)
由上图可见:
2011年至2012年,租车数量总体呈现上升趋势,但明显受季节因素影响,春季最少,夏季秋季最多,冬季租车量高于春季。(这里回答了开头提出的第二个问题。) 注册用户是租车主力,租车比例基本每月都在75%以上。 注册用户在夏季和秋季的租车比例(~80%)低于春季和冬季(~90%)。可见非注册用户更倾向于在天气情况较为暖和的夏季和秋季租车。4.2 工作日,节假日的租车量有何不同?
# 获取包含日期数据的每日租车数据 daily_rental = bike_rental.groupby([year, month, day,weekday,day_type])[count].sum().reset_index() # 获取最低和最高的单日租车量值 daily_min = daily_rental[count].min() daily_max = daily_rental[count].max() # 设定图表 fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,8), sharey=True, gridspec_kw = {width_ratios:[1.5, 1]}) # 根据星期日数作每日租车量的箱线图 ax1 = daily_rental.boxplot(column=count, by=weekday, ax=ax1, showmeans=True, flierprops=dict(marker=o, markerfacecolor=c1)) # 美化图表 ax1.set_title(Daily Bike Rental by Weekday, fontsize=18) ax1.set_xlabel() ax1.set_ylim(0,9000) ax1.get_yaxis().set_major_formatter(FuncFormatter(lambda x, p: format(int(x), ,))) # add thousand separators ax1.tick_params(labelsize=14) ax1.axhline(daily_min, xmin=0, xmax=16, linewidth=1, linestyle=--, alpha=0.7) # 添加最低和最高值参考线 ax1.annotate(xy=(7.5, daily_min), s= 605, ha=left, va=center, fontsize=12, color=c1) ax1.annotate(xy=(7.5, daily_min + 300), s= Min, ha=left, va=center, fontsize=12, color=c1) ax1.axhline(daily_max, xmin=0, xmax=16, linewidth=1, linestyle=--, alpha=0.7) ax1.annotate(xy=(7.5, daily_max), s= 8714, ha=left, va=center, fontsize=12, color=c1) ax1.annotate(xy=(7.5, 9000), s= Max, ha=left, va=center, fontsize=12, color=c1) # 根据日期类型作箱线图 ax2 = daily_rental.boxplot(column=count, by=day_type, ax=ax2, showmeans=True, flierprops=dict(marker=o, markerfacecolor=c1)) # 美化图表 ax2.set_title(Daily Bike Rental by Day Type, fontsize=18) ax2.set_xlabel() ax2.tick_params(labelsize=14) ax2.axhline(daily_min, xmin=0, xmax=16, linewidth=1, linestyle=--, alpha=0.7) ax2.axhline(daily_max, xmin=0, xmax=16, linewidth=1, linestyle=--, alpha=0.7) # 隐去总标题 plt.suptitle() # 添加水印 ax1.text(x = 0, y = -1000, s = ©MilankakaZhang Data Source: Kaggle, fontsize = 14, color = grey) Text(0,-1000, ©MilankakaZhang Data Source: Kaggle)
由上图可见:
没有异常值出现,数据相对合理可信。 每周各天的平均租车量差距不大,在4500次上下范围波动,星期六的平均租车量相对最高,最高租车量也是在星期六达到。星期日的平均租车量相对最低,而星期五的最低租车量接近1500次,明显高于其他天数(<=1000)。 就日期类型而言,工作日,周末和节假日的平均租车量较为接近,节假日的租车量波动范围较小,日租车量中位数明显高于工作日和周末。可以判断用户更倾向在节假日租车,但过低的平均租车量可能是部分节假日出现在租车量清淡的春季导致的。下面继续探索注册用户和非注册用户的在不同天数下的租车情况。
# 计算非注册用户和注册用户的每日租车数据 daily_casual_rental = bike_rental.groupby([year, month, day,weekday,day_type])[casual].sum().reset_index() daily_registered_rental = bike_rental.groupby([year, month, day,weekday,day_type])[registered].sum().reset_index() # 合并数据 daily_user_rental = daily_casual_rental daily_user_rental[registered] = daily_registered_rental[registered] # 使用pd.melt将casual, registered两列变成user_type一列 daily_user_rental = daily_user_rental.melt(id_vars=[year, month, day, weekday, day_type], value_vars=[casual, registered], var_name=user_type, value_name=rental) # 设定图表 fig, (ax5, ax6) = plt.subplots(1, 2, figsize=(16,8), sharey=True, gridspec_kw = {width_ratios:[2, 1]}) fig.suptitle(Daily Bike Rental by Casual& Registered Users, fontsize=22) # 不同星期日数下的每日租车量箱线图 sns.boxplot(x="weekday", y="rental", hue="user_type", ax=ax5, data=daily_user_rental, showmeans=True, palette=[c1, c2], linewidth=1, meanprops=dict(marker=o, markerfacecolor=yellow, markeredgecolor=yellow)) # 美化图表 ax5.set_xlabel() ax5.set_ylabel() ax5.get_yaxis().set_major_formatter(FuncFormatter(lambda x, p: format(int(x), ,))) ax5.tick_params(labelsize=16) ax5.legend_.remove() # 根据不同日期类型下的每日租车量作箱线图 sns.boxplot(x="day_type", y="rental", hue="user_type", ax=ax6, data=daily_user_rental, showmeans=True, palette=[c1, c2], linewidth=1, meanprops=dict(marker=o, markerfacecolor=yellow, markeredgecolor=yellow)) # 美化图表 ax6.set_xlabel() ax6.set_ylabel() ax6.tick_params(labelsize=16) ax6.legend(fontsize=16) # 添加水印 ax5.text(x = -0.5, y = -1000, s = ©MilankakaZhang Data Source: Kaggle, fontsize = 14, color = grey) Text(-0.5,-1000, ©MilankakaZhang Data Source: Kaggle)
由上图可见:
出现4个异常值,数量很少,其中三个超过上限的异常值很可能是因为当天时节假日造成的。 注册用户更倾向在工作日租车,在周末的租车量(约3000/日)明显低于工作日(约4000/日),在节假日的租车量总体介于工作日和周末租车量之间。 *非注册用户在工作日租车量少(平均值低于1000/日),所占比例很低,在周末租车量(约1500/日)明显提高,但仍低于注册用户租车量。4.3 每天哪个时段的租车量较高?
fig, ax9 = plt.subplots(figsize=(16,6)) # 注册用户不同时间段和日期类型的租车量作图 ax9 = bike_rental.groupby([hour, day_type])[registered].mean().unstack().plot(ax=ax9, color=[c1,c4,c2], label=registered, marker=o) # 非注册用户不同时间段和日期类型的租车量作图 ax10 = bike_rental.groupby([hour, day_type])[casual].mean().unstack().plot(ax=ax9, linestyle=--, color=[c1,c4,c2], label=casual, marker=o) # 美化图表 ax9.set_title(Hourly Average Bike Rental by Casual& Registered Users) ax9.set_xlim(-0.5,24) ax9.set_xticks(np.arange(24)) ax9.set_xlabel(Hour) ax9.legend([Workingday Registered, Weekend Registered, Holiday Registered, Workingday Casual, Weekend Casual, Holiday Casual], fontsize=12) ax9.axhline(y=0.5, linewidth=3, color=black, alpha=0.7) # 添加水印 ax9.text(x = -1, y = -100, s = ©MilankakaZhang Data Source: Kaggle, fontsize = 14, color = grey) Text(-1,-100, ©MilankakaZhang Data Source: Kaggle)由上图可知:
用户主要在早上7点到晚上20点间租用共享单车,其中在非工作日,下午12点到下午17点是租车最多的时段。 注册用户在工作日的租车集中在7-9点和17-19点,即上下班时间段呈现明显的峰值,在节假日和周末没有明显峰值,因此可以判断注册用户以上班族以及学生为主。 非注册用户在工作日租车量很低,没有上下班时段的峰值,在周末和节假日的租车时间习惯上和注册用户区别不大,即集中在下午租车。4.4 天气情况对租车量的影响如何?
天气数据包含weather, temp, atemp, windspeed, humidity,下面逐一作图分析。
4.4.1 不同天气状况下的租车量分析
(字数限制关系,下面省略代码)
由天气状况-租车数据图可见:
天气状况好时的租车量高于天气状况差的时候。晴天/少云和多云/薄雾天气下的租车量明显高于雨雪天气时。暴雨/大学天气只出现了一次,因此平均租车数据不具参考性。 晴天/少云天气下的总租车量大大超过其他天气状况,可以判断华盛顿特区气候宜人,天气多以晴天/少云为主。4.4.2 不同风速下的租车量分析
由风速-租车量关系图可见:
左图x轴是风速,红色竖线的长度代表风速的分布比例,蓝色面积图是随风速增加的租车量累积。 超过80%的时间段风速在20以下,另外接近20%的时段风速在20-40区间,40以上的风速的时间段极少。鉴于风速大于30m/s已经属于飓风级别,因此推断风速单位是km/h。40km/h的风速已经相当于6级强风。 根据左图左侧y轴,华盛顿特区两年累积租车量约为210万次。 忽略风速40km/h以上的情况,风速10-35km/h/区间的平均租车量高于其他风速的租车量。用户更倾向在轻风和微风中使用共享单车,享受清风拂面,恣意踏车的感觉。4.4.3 不同体感温度下的租车量分析
由体感温度-租车量关系图可知:
体感温度主要分布在5到40摄氏度,分布相对平均。 考虑5到35摄氏度的区间,无论是注册用户还是非注册用户,平均租车量基本都随着温度的上升而明显提高。用户更倾向在温暖炎热的天气下租车。4.4.3 不同湿度下的租车量分析
由湿度-租车量关系图可知:
湿度主要集中在20至100区间,分布相对平均。 考虑湿度20-100的时候,注册用户和非注册用户的平均租车量都随着湿度的升高而显著下降。湿度20的时候租车量最高。