案例:990万次骑行:纽约自行车共享系统分析

案例:990万次骑行:纽约自行车共享系统
__from 《像Excel一样使用Python进行数据分析》
 
 

System Data

 

Where do Citi Bikers ride? When do they ride? How far do they go? Which stations are most popular? What days of the week are most rides taken on? We've heard all of these questions and more from you, and we're happy to provide the data to help you discover the answers to these questions and more. We invite developers, engineers, statisticians, artists, academics and other interested members of the public to use the data we provide for analysis, development, visualization and whatever else moves you.

 

 

Citi Bike是纽约市在2013年5月27日启动的一项自行车共享计划,由“花旗银行”Citi Bank赞助并取名为“花旗单车”(Citi Bike)。
在曼哈顿,布鲁克林,皇后区和泽西市有8000辆自行车和500个车站。为纽约的居民和游客提供一种方便快捷,并且省钱的出门方式。
 加入----------解锁---------骑行--------归还
 
人们随处都能接到Citi Bike,并且在目的地归还。
使用Citi Bike的方法很简单,购买会员资格,然后在附件查找可以使用的Citi Bike,使用会员key解锁,在有效时间内(30-45minutes)归还并锁定Citi Bike。
由于每个Citi Bike的租赁站点都有GPS位置信息,因此可以记录到用户租赁和骑行过程中的数据。
Citi Bike官网提供了用户每一次骑行的数据,包括租赁开始及结束的位置及时间,整个骑行过程的时间,自行车ID,以及用户的性别和出生日期等数据。
我们将使用python对Citi Bike2015年的数据进行分析,以了解纽约自行车共享计划的情况。并回答诸如谁在使用Citi Bike?他们什么时间开始骑行?每次骑行的时间和距离,以及哪些骑行路线最受欢迎等问题。
 
 首先我们将需要使用的库文件导入到python中,这里包括pandas,numpy,matplotlib中用于数据可视化的pyplot,datetime。
后面根据不同的分析方向我们还会陆续导入其他的库文件。
  1. import pandas as pd
  2. import numpy as np
  3. import time,datetime
  4. import matplotlib.pyplot as plt
  5.  
  6. #读取Citi Bike的数据并创建数据表
  7. cb1=pd.DataFrame(pd.read_csv('201501-citibike-tripdata.csv'))
查看cb1表的维度
  1. cb1.shape #查看cb1表的维度
  2. (285552,15)
Citi Bike的数据是按月存储的,因此我们需要逐月读取并将各个月份的数据进行拼接。
  1. #对导入的数据表进行拼接,汇总后的数据表名为cb
  2. cb=cb1.append(cb2,ignore_index=False)
  3. #查看数据表维度
  4. cb.shape
  5. (9937969, 15)
2015年Citi Bike数据共包含993万行,15列数据。其中每一行数据都代表一次“Citi Bike”的租借和骑行记录。
                                ____以下与正文无关,关于pd.DataFrame.append()
                            
 
 
 
 我们首先对2015年Citi Bike的数据中的几个关键指标进行统计,在一年中共有497个租赁点,也就是车站,8477辆自行车被使用。
自行车的使用次数更是高达993万次,平均算下来每辆自行车每天被租借3.21次。
可见人们对Citi Bike的热情。
而每次的骑行时间平均为16.13分钟。
 以下为每个关键指标的统计代码和结果。
 
  1. #唯一租赁点数量计数
  2. len(cb['start station name'].unique())
  3. 497
  4. #唯一自行车ID计数
  5. len(cb['bikeid'].unique())
  6. 8477
  7. #每辆自行车租借频率
  8. cb['bikeid'].count()/len(cb['bikeid'].unique())
  9. 1172.3450513153239
  10. #每辆自行车每日租借频率
  11. cb['bikeid'].count()/len(cb['bikeid'].unique())/365
  12. 3.21190425-1789696
  13.  
  14. #每次租借平均时长(分钟)
  15. cb['tripduration'].sum()/cb['bikeid'].count()/60
  16. 16.134794237132358
 
 2015年的Citi Bike的使用量整体趋势由低到高,2月Citi Bike的使用量最低,然后使用量逐月增长,直到9月出现使用量最高值。11月使用量开始下降。这可能是由于季节和气温因素导致的。
我们下面按季度对骑行数据进行汇总并进行对比。
 
以下是按月汇总骑行数据并汇总折线图的代码。
  1. #对starttime设置日期格式
  2. cb['starttime']=pd.to_datetime(cb['starttime'])
  3.  
  4. #将starttime设置为数据表索引
  5. cb=cb.set_index('starttime')
  6.  
  7. #按月对骑行数据进行计数
  8. cb_month=cb.resample('M',how=len)
  9.  
  10. #提取汇总后的bikeid字段
  11. group_cb_month=cb_month['bikeid']
  12.  
  13. #汇总按月汇总的骑行次数折线图
  14. plt.rc('font',family='STXihei',size=15)
  15. a=np.array(group_cb_month,'g8',group_cb_month,'g-',color='#39A2E1',
  16. linewidth=3,markeredgewidth=3,markeredgecolor='#39A2E1',alpha=0.8)
  17. plt.xlabel('月份')
  18. plt.ylabel('租赁骑行次数')
  19. plt.title('2015年Citi Bike每月骑行次数')
  20. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='y',alpha=0.4)
  21. plt.xticks(a,('1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'))
  22. plt.show()
  23.  
 
 
 
 从2015年四个季度的数据来看,用户对Citi Bike的使用瘦季节因素的影响,第一季度气温最低,Citi Bike的使用量也比较低。第二季度第三季度为夏秋交替,使用量最高。
  1. #按季度对骑行数据进行计数
  2. cb_quarterly=cb.resample('Q',how=len)
  3. #提取按季度汇总后的bikeid字段
  4. group_cb_quarterly=cb_quarterly['bikeid']
  5. #绘制按季度汇总的骑行次数柱状图
  6. plt.rc('font',family='STXihei',size=15)
  7. a=np.array([1,2,3,4])
  8. plt.bar([1,2,3,4],group_cb_quarterly,color='#39A2E1',alpha=0.8,align='center',
  9. edgecolor='white')
  10. plt.xlabel('季度')
  11. plt.ylabel('租赁骑行次数')
  12. plt.title('2015年Citi Bike每季度骑行次数')
  13. plt.legend(['次数'],loc='upper right')
  14. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='y',alpha=0.4)
  15. plt.xticks(a,('一季度','二季度','三季度','四季度'))
  16. plt.show
 
 
 我们很好奇993万次骑行这个庞大的数据后面是哪些人在使用Citi Bike。
用于获得的数据中只有用户性别,出生日期和会员类别的数据,因此我们仅从这三个维度对Citi Bike的用户进行简单的描述。
 
 2015年使用Citi Bike服务的用户中,66%为男性用户,女性用户的占比为20%,另外还有13%的用户性别未知。这让我想起了北京自重25公斤的共享单车。
 以下是计算用户性别占比和绘制饼图的代码。
  1. #按用户性别进行汇总并计算不同性别的占比
  2. user_gender=cb.groupby('gender')['bikeid'].agg(len)/cb['bikeid'].count()*100
  3. #汇总用户性别占比饼图
  4. plt.rc('font',family='STXihei',size=15)
  5. colors=['#052B6C','#39A2E1','#EA1F29']
  6. name=['未知','男性','女性']
  7. plt.pie(user_gender,labels=name,colors=colors,explode=(0,0,0),startangle=60,autopct='%1.1f%%')
  8. plt.title('Citi Bike用户性别占比')
  9. plt.legend(['未知','男性','女性'],loc='upper left')
  10. plt.show()
 
 
 我们将Citi Bike的用户年龄分为四组,0-18岁为少年组,18-30岁为青年组,30-50岁为中年组,50岁以上为老年组。
其中少年组的租借和骑行次数最少,中年组的租借和骑行次数最高,其次为青年组。50岁以上的老年组也有相当数量的记录。
 
 以下是对用户年龄分组和汇总柱状图的代码,用户年龄由出生日期和当前年份计算得出,其中包含部分极端值。
  1. #查看出生日期的范围
  2. cb['birth year'].min(),cb['birth year'].max()
  3. (1885.0,1999.0)
这里1885年很怪异,按照这个出生日期,2016年时用户的年龄已经达到了131岁。我们不知道其中的具体原因,但由于Citi Bike整体的数据较为规范不需要清洗,因此我们选择保留这个值。
  1. #使用2015年与用户出生日期计算年龄
  2. cb['age']=2015-cb['birth year']
  3. #用户最小年龄17岁,最大年龄131岁
  4. cb['age'].min(),cb['age'].max()
  5. (17.0,131.0)
  6. #对用户年龄进行分组
  7. bins=[0,18,30,50,131]
  8. group_age=['少年','青年','中年','老年']
  9. cb['group_age']=pd.cut(cb['age'],bins,labels=group_age)
  10. #按年龄分组对数据进行汇总
  11. user_age=cb.groupby('group_age')['group_age'].agg(len)
  12. #生成用户年龄分布柱状图
  13. plt.rc('font',family='STXihei',size=15)
  14. a=np.array([1,2,3,4])
  15. plt.bar([1,2,3,4],user_age,color='#052B6C',alpha=0.8,align='center',edgecolor='white')
  16. plt.xlabel('年龄分组')
  17. plt.ylabel('租赁次数')
  18. plt.title('Citi Bike用户年龄分布')
  19. plt.legend(['次数'],loc='uppper right')
  20. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='y',alpha=0.4)
  21. plt.xticks(a,('少年','青年','中年','老年'))
  22. plt.show()
 
 Citi Bike对纽约本地居民,短期停留和游客提供了三种会员期限选择,按Citi Bike官网的推荐,年度会员适合于本地居民,也是最划算的一种租赁方式。3天的会员适合于短期停留或居住的用户,1天的会员适合于游客。因此我们也可以按不同的会员期限反向推断用户的身份。
 在2015年的数据表中,1天和3天的会员统称为Customer,年度会员称为Subscriber。从下面的占比数据中可以看出,绝大部分Citi Bike的用户为年度会员,占比高达86%。反向推测绝大部分用户为纽约本地常住居民。
 
 以下为计算用户会员类别和汇总饼图的代码。
  1. #按用户的会员类别进行汇总并计算占比
  2. user_type=cb.groupby('usertype')['bikeid'].agg(len)/cb['bikeid'].count()*100
  3. #汇总用户会员类别饼图
  4. plt.rc('font',family='STXihei',size=15)
  5. colors=['#EA1F29','#39A2E1']
  6. name=['Customer','Subscriber']
  7. plt.pie(user_type,labels=name,colors=colors,explode=(0,0),startangle=43,autopct='%1.1f%%')
  8. plt.legend(['Customer','Subscriber'],loc='upper left')
  9. plt.show()
 
 在993万次骑行的数据背后,是否存在一些规律?我们选择了5月(春季)的数据对用户使用Citi Bike的行为进行了统计和分析,这里既包括使用Citi Bike的时间,也包括骑行速度,热门租赁站点和骑行线路。
 Citi Bike的使用者大部分为城市居民,少部分为游客。在一天中商务7点-8点和下午的5点-6点是Citi Bike的使用高峰。这两个时间正好是上下班的高峰时间。除此之外中午12点-下午4点也有较高的使用量。
 以下是24小时使用趋势和绘制折线图的代码。
  1. #读取5月数据并创建数据表
  2. cb5=pd.DataFrame(pd.read_csv('201505-citibike-tripdata.csv'))
  3. #对starttime字段进行分列
  4. time_split=pd.DataFrame((x.split('')for x in cb5.starttime),index=cb5.index,columns=['start_date','star_time'])
  5. #对分列后的表与原数据表进行拼接
  6. cb5=pd.merge(cb5,time_split,right_index=True,left_index=True)
  7. #更改star_time字段为日期格式
  8. cb5['star_time']=pd.to_datetime(cb5['star_time'])
  9. #设置star_time为表索引
  10. cb5=cb5.set_index('star_time')
  11. #按小时对数据进行汇总
  12. star_hour=cb5.resample('H',how=len)
  13. #提取按小时汇总的bikeid数据
  14. ride_hour=star_hour['bikeid']
  15. #绘制24小时折线图
  16. plt.rc('font',family='STXihei',size=15)
  17. a=np.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23])
  18. plt.plot(ride_hour,'8',ride_hour,'g-',color='#052B6C',linewidth=3,markeredgewidth=3,markeredgecolor='#052B6C',alpha=0.8)
  19. plt.xlabel('24x小时')
  20. plt.ylabel('租赁次数')
  21. plt.title('Citi Bike用户24小时租赁次数')
  22. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='y',alpha=0.4)
  23. plt.xticks(a,('0','','','','','5','','','','','10','','','','','15','','','','','20','','',''))
  24. plt.show()
 
 在骑行时间方面,按照不同的会员类别Citi Bike对时间有不同的限制,1天和3天会员每次骑行的限制时间为30分钟,超过30分钟每增加15分钟收费4美金。年度会有每次骑行限制时间为45分钟,超过10分钟收费2.5美金,再次超出逐级收费。Citi Bike鼓励用户短途使用并且在接近限制时间时重新租赁新的自行车,避免产生超时费用,并且提高自行车的周转率。
我们对5月份用户的骑行时间进行处理和分组,通过下面的图表可以看出大部分用户的使用时间为10-20分钟。其次为30分钟和5分钟以内。超过30分钟的使用次数较少。即使在86%的用户为年度会员的情况下,使用时间在30-45分钟的情况也比较少。由此推测大部分的骑行为短途。
 以下为骑行时间分组和绘制柱状图的代码。
  1. #对骑行时间进行分组
  2. bins=[0,300,600,1200,1800,2700,2825827]
  3. #为每个分组命名
  4. group_tripduration=['5分钟','10分钟','20分钟','30分钟','45分钟','更长时间']
  5. #在原数据表中增加时间分组字段
  6. cb5['group_tripduration']=pd.cut(cb5['tripduration'],bins,labels=group_tripduration)
  7. #按分组对数据进行汇总计数
  8. group_minute=cb5.groupby('group_tripduration')['group_tripduration'].agg(len)
  9. #汇总骑行时间分组柱状图
  10. plt.rc('font',family='STXihei',size=15)
  11. a=np.array([1,2,3,4,5,6])
  12. plt.bar([1,2,3,4,5,6],group_minute,color='#9F713F',alpha=0.8,align='center',edgecolor='white')
  13. plt.xlabel('时间分组')
  14. plt.ylabel('骑行次数')
  15. plt.title('Citi Bike骑行时间分布')
  16. plt.legend(['时间'],loc='upper right')
  17. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='y',alpha=0.4)
  18. plt.xticks(a,('5分钟','10分钟','20分钟','30分钟','45分钟','更长时间'))
  19. plt.show()
 在Citi Bike的数据表中并没有直接提供每次骑行的速度数据,但包含了每个开始和结束站点的经纬度坐标。我们通过这些坐标计算出两个站点间的距离,并匹配到对应的骑行中。在经过与本次骑行所耗费的时间计算出这次骑行的平均速度。通过对5月的数据进行处理,用户的平均骑行速度为6.3公里/小时。
以下是计算骑行速度的代码。
  1. #导入库文件
  2. from math import radians,cos,sin,asin,sqrt
  3. import numpy as np
  4. import pandas as pd
  5. #通过经纬度计算距离的函数
  6. def haversine(lon1,lat1,lon2,lat2):#经度1,j维度1,经度2,维度2,(十进制度数)
  7. '''
  8. Calculate the great circle distance between two points
  9. on the earth (specified in decimal degrees)
  10. '''
  11. #将十进制度数转化为弧度
  12. lon1=map(radians,np.array(lon1))
  13. lat1=map(radians,np.array(lat1))
  14. lon2=map(radians,np.array(lon2))
  15. lat2=map(radians,np.array(lat2))
  16. lon1=np.array(list(lon1)).reshape(-1,1)
  17. lon2=np.array(list(lon2)).reshape(-1,1)
  18. lat1=np.array(list(lat1)).reshape(-1,1)
  19. lat2=np.array(list(lat2)).reshape(-1,1)
  20. #haversine公式
  21. dlon=lon2-lon1
  22. dlat=lat2-lat1
  23. a=np.sin(dlat/2)**2+np.cos(lat1)*np.cos(lat2)*np.sin(dlon/2)**2
  24. c=2*np.arcsin(np.sqrt(a))
  25. r=6371#地球平均半径,单位为公里
  26. return c*r*1000
  27. #计算每次骑行的米数并增加骑行距离字段
  28. cb5['meter']=haversine(cb['start station longitude'],cb5['start station latitude'],cb5['end station longitude'],cb5['end station latitude'])
  29. #将原数据表中的骑行时间由秒转化为小时
  30. cb5['duration_hour']=cb5['tripduration']/3600
  31. #将米转化为公里并与小时计算出速度
  32. cb5['spend']=cb5['meter']/1000/cb5['duration_hour']
  33. #现在我们有了每次骑行的速度,还需要计算出用户平均的骑行速度。
  34. #将每次骑行的米数求和并转化为公里
  35. km=cb5['meter'].sum()/1000
  36. #将每次骑行的秒数求和并转化为小时
  37. hour=cb5['tripduration'].sum()/3600
  38. #计算平均速度
  39. speed=km/hour
  40. #平均速度为6.31公里/小时
  41. speed
  42. 6.3101247093495
 
 在990万次骑行中,第一季度用户对Citi Bike的使用量最低,第三季度使用量最高。有明显的季度因素。下面我们导入纽约市2015年的气象数据,来看下天气的因素与Citi Bike间是否存在关联,并试着用天气的变化来预测Citi Bike的使用量。
 绘制每日最高气温与Citi Bike使用量的散点图,从图中可以发现Citi Bike的使用量与日最高气温间呈正相关,随着日最高气温的增长Citi Bike的使用量也在逐渐增长。接下来我们通过回归分析发现最高气温与Citi Bike间的R的平方值为0.6,两者存在正向关联。
换句话说日最高气温可以解释Citi Bike租赁和骑行数量60%的变化原因。因此,我们通过这种关联分布对不同气温下的Citi Bike的租赁数量进行了预测。
 
以下是绘制散点图和进行回归分析的代码。
  1. #读取2015年纽约市的气象数据
  2. weather=pd.DataFrame(pd.read_csv('823248'))
  3. #提取每日最高气温数据
  4. group_weather_day=weather['TMAX']
  5. #对2015年骑行时间按天汇总计算
  6. cb_day=cb.resample('D',how=len)
  7. #提取每日骑行数据量
  8. group_cb_day=cb_day['bikeid']
  9. #对每日最高气温和骑行量数据进行标准化处理
  10. from sklearn import preprocessing
  11. scaler=preprocessing.StandardScaler().fit(group_weather_day)
  12. X_Standard=scaler.transform(group_weather_day)
  13. scaler=preprocessing.StandardScaler().fit(group_cb_day)
  14. Y_Standard=sacler.transform(group_cb_day)
  15. #绘制最高气温和骑行数据散点图
  16. plt.rc('font',family='STXihei',size=15)
  17. plt.scatter(X_Standard,Y_Standard,60,color='#052B6C',marker='+',alpha=0.8,linewidth=1.5)
  18. plt.xlabel('日最高气温')
  19. plt.ylabel('Citi Bike租赁次数')
  20. plt.title('最高气温与Citi Bike之间的关系')
  21. plt.grid(color='#95a5a6',linestyle='--',linewidth=1,axis='both',alpha=0.4)
  22. plt.show()
  23. #设置每日最高气温为自变量X
  24. X=np.array(weather[['TMAX']])
  25. #设置每日骑行数量为因变量Y
  26. Y=np.array(cb-day[['bikeid']])
  27.  
  28. #导入线性回归库
  29. from sklearn import linear_model
  30. clf=linear_model.LinearRegression()
  31. clf.fit(X,Y)
  32.  
  33. #计算相关度
  34. clf.score(X,Y)
  35. 0.60841860048565455
  36.  
  37. #斜率
  38. clf.coef_
  39. array([[536.64465091]])
  40.  
  41. #截距
  42. clf.intercept
  43. array([-7282.61437278])
  44.  
  45. #分布对19,39和97华氏度的Citi Bike租赁量进行预测
  46. clf.predict(19)
  47. array([[2913.63399443]])
  48. clf.predict(39)
  49. array([[13646.52701255]])
  50. clf.predict(97)
  51. array([[44771.9167651]])
 通过前面的计算我们已经知道了每次骑行用户的年龄和骑行速度信息,那么这两者之间是否存在关联呢?
换句话说,是否随着年龄的增长骑行速度会逐渐减慢呢?
我们通过简单的一元回归来分析下两者间的联系。
这里我们只使用2015年5月的数据进行分析。
  1. #计算并增加年龄字段
  2. cb5['age']=2015-cb5['birth year']
  3. #提取年龄和骑行速度字段
  4. age_speed=cb5[['speed','age']]
  5. #去除骑行速度为0的数据
  6. age_speed=age_speed.dropna()
  7. #年龄设置为自变量X
  8. X=np.array(age_speed[['age']])
  9. #速度设置为因变量Y
  10. Y=np.array(age_speed[['speed']])
  11. #导入线性回归库
  12. from sklearn import linear_model
  13. #将数据导入模型
  14. clf=linear_model.LinearRegression()
  15. clf.fit(X,Y)
  16. #斜率
  17. clf.coef_
  18. array([[-0.02899706]])
  19. #截距
  20. clf.intercept_
  21. array([10.05513438])
  22. #R方
  23. clf.score(X,Y)
  24. 0.011272777068773165
从R方来看年龄与骑行速度间并没有关联,骑行速度并不会随着年龄的增长而增长。
这里主要的原因我想有两个。
第一Citi Bike的使用场所主要在城市里,并且多为短途。
第二是使用者除了城市居民上下班通勤外,还有一部分的游客,他们的目的是欣赏沿途美丽的风景因此骑行速度也会较慢。
  1. #20岁的骑行速度预测为9.4公里
  2. clf.predict(20)
  3. array([[9.47519327]])
  4. #50岁的骑行速度预测为8.6公里
  5. clf.predict(50)
  6. array([[8.60528161]])
 
 在Citi Bike的官网上有一个频道叫Explore NYC,里,里面提供了最热门的骑行线路和沿途的著名景观。我们对5月的数据进行统计,找出最受欢迎的骑行开始地点。以及受欢迎的骑行线路。

 对5月骑行数据进行统计,找出前10个最受欢迎的骑行开始地点及经纬度数据。将经纬度数据输入到plotly工具中,绘制出Citi Bike2015年5月在纽约最受欢迎的前10个租赁地点。
以下是前10个最受欢迎租赁点的统计代码及结果。
  1. #使用数据透视找出前10个最受欢迎地点的经纬度数据
  2. start_station=pd.pivot_table(cb5,index=['start station name','start station latitude','start station longitude'],values=['bikeid'],aggfunc=[len],fill_value=0,margins=True).head(10)
 

 继续前面的操作,在数据透视表的字段中增加结束地点和经纬度数据就可以看到一条完整的骑行线路数据,这里以1 Ave&E 15 St为骑行开始地点,使用pyplot工具描绘出一条用户的骑行线路。
上面的图中是从Google街景中截图的图片,也是骑行线路开始的地点。
下面是pyplot工具显示出的本次骑行开始和结束的位置。
由于我们只有开始和结束两个经纬度数据,因此显示为一条直线。
借助Google地图的骑行路线我们模拟出了用户这两点之间可能的骑行路线,这并非用户的真是骑行路线。但可以帮助我们更加详细的了解用户使用Citi Bike的情况。
 
以下是通过数据透视获得骑行位置和经纬度的代码。
  1. #获得骑行开始和结束地点及经纬度数据
  2. end_station=pd.pivot_table(cb5,index=['start station name','start station latitude','start station longitude','end station latitude','end station longitude','end station name'],values=['bikeid'],aggfunc=[len],fill_value=0,margins=True).head(10)
 

 

 
原文地址:https://www.cnblogs.com/zoe233/p/7026207.html