下面来尝试预测美国西雅图弗雷蒙特桥的自行车流量,数据源自不同天气、季节和其他条件下通过该桥的自行车统计数据。我们在 3.12 节见过这些数据。
在本节中,我们将自行车数据与其他数据集连接起来,确定哪些天和季节因素(温度、降雨量和白昼时间)会影响通过这座桥的自行车流量。NOAA 已经提供了每日的站点天气预报(http://www.ncdc.noaa.gov/cdo-web/searchdatasetid=GHCND)数据(我用的站点 ID 是 USW00024233),可以用 Pandas 轻松将两份数据连接起来。然后,创建一个简单的线性回归模型来探索与自行车数量相关的天气和其他因素,从而评估任意一种因素对骑车人数的影响。
值得注意的是,这是一个演示在统计模型框架中如何应用 Scikit-Learn工具的案例,模型参数被假设为具有可以解释的含义。就像前面介绍过的那样,虽然这并不是一个介绍标准机器学习方法的案例,但是对模型的解释在其他模型中也会用到。
01/加载数据&数据清洗
首先加载两个数据集,用日期作索引:importpandasaspd
counts=pd.read_csv(r'...\BicycleWeather.csv',
index_col='DATE',parse_dates=True)
weather=pd.read_csv('...\BicycleWeather.csv',
index_col='DATE',parse_dates=True)
然后计算每一天的自行车流量,将结果放到一个新的 DataFrame 中:daily=counts.resample('d').sum()
daily['Total']=daily.sum(axis=1)
daily=daily[['Total']]#removeothercolumns重采样(Resampling)指的是把时间序列的频度变为另一个频度的过程。
把高频度的数据变为低频度叫做降采样(downsampling),
把低频度变为高频度叫做增采样(upsampling)
在之前的分析中,我们发现同一周内每一天的模式都是不一样的。因此,我们在数据中加上 7 列 0~1 值表示星期几:days=['Mon','Tue','Wed','Thu','Fri','Sat','Sun']foriinrange(7):
daily[days[i]]=(daily.index.dayofweek==i).astype(float)
我们觉得骑车人数在节假日也有所变化。因此,再增加一列表示当天是否为节假日:frompandas.tseries.holidayimportUSFederalHolidayCalendarcal=USFederalHolidayCalendar()holidays=cal.holidays('','')
daily=daily.join(pd.Series(1,index=holidays,name='holiday'))
daily['holiday'].fillna(0,inplace=True)
我们还认为白昼时间也会影响骑车人数。因此,用标准的天文计算来添加这列信息:defhours_of_daylight(date,axis=23.44,latitude=47.61):
"""Computethehoursofdaylightforthegivendate"""
days=(date-pd.datetime(2000,12,21)).days
m=(1.-np.tan(np.radians(latitude))
*np.tan(np.radians(axis)*np.cos(days*2*np.pi/365.25)))
return24.*np.degrees(np.arccos(1-np.clip(m,0,2)))/180.
daily['daylight_hrs']=list(map(hours_of_daylight,daily.index))
daily[['daylight_hrs']].plot()
plt.ylim(8,17)
图:西雅图数据白昼时间可视化
我们还可以增加每一天的平均气温和总降雨量。除了降雨量的数值之外,再增加一个标记表示是否下雨(是否降雨量为 0):#温度是按照1/10摄氏度统计的,首先转换为摄氏度
weather['TMIN']/=10
weather['TMAX']/=10
weather['Temp(C)']=0.5*(weather['TMIN']+weather['TMAX'])
##降雨量也是按照1/10mm统计的,转化为英寸
weather['PRCP']/=254
weather['dryday']=(weather['PRCP']==0).astype(int)
daily=daily.join(weather[['PRCP','Temp(C)','dryday']])
最后,增加一个从 1 开始递增的计数器,表示一年已经过去了多少天。
这个特征可以让我们看到每一年自行车流量的增长或减少:daily['annual']=(daily.index-daily.index[0]).days/365.
数据已经准备就绪,来看看前几行:
02/ 线性回归建模
有了上面那些数据之后,就可以选择需要使用的列,然后对数据建立线性回归模型。我们不在模型中使用截距,而是设置 fit_intercept =False,因为每一天的总流量(Total 字段)基本上可以作为当天的截距:(其实此线性回归模型使用截距,即设置 fit_intercept = True,拟合结果也不变。)#Dropanyrowswithnullvalues
daily.dropna(axis=0,how='any',inplace=True)
column_names=['Mon','Tue','Wed','Thu','Fri','Sat','Sun','holiday',
'daylight_hrs','PRCP','dryday','Temp(C)','annual']
X=daily[column_names]
y=daily['Total']
model=LinearRegression(fit_intercept=False)
model.fit(X,y)
daily['predicted']=model.predict(X)
最后,对比自行车真实流量(Total 字段)与预测流量(predicted 字
段):daily[['Total','predicted']].plot(alpha=0.5);
显然,我们丢失了一些关键特征,尤其是夏天的预测数据。要么是由于特征没有收集全(即可能还有其他因素会影响人们是否骑车),要么是有一些非线性关系我们没有考虑到(例如,可能人们在温度过高或过低时都不愿意骑车)。但是,这个近似解已经足以说明问题。下面让我们看看模型的系数,评估各个特征对每日自行车流量的影响:params=pd.Series(model.coef_,index=X.columns)
paramsMon-102705.310902
Tue-103180.477941
Wed-102563.059452
Thu-101897.155223
Fri-102744.245636
Sat-102493.825830
Sun-102273.532756
holiday1201.764795
daylight_hrs-268.922844
PRCP3318.213270
dryday-9333.436183
Temp(C)-99.737649
annual-4388.501859
dtype:float64
如果不对这些数据的不确定性进行评估,那么它们很难具有解释力。可以用自举重采样方法快速计算数据的不确定性:
fromsklearn.utilsimportresample
np.random.seed(1)err=np.std([model.fit(*resample(X,y)).coef_
foriinrange(1000)],0)
有了估计误差之后,再来看这些结果:print(pd.DataFrame({'effect':params.round(0),
'error':err.round(0)}))
effecterror
Mon-102705.01664.0
Tue-103180.01662.0
Wed-102563.01635.0
Thu-101897.01675.0
Fri-102744.01634.0
Sat-102494.01653.0
Sun-102274.01638.0
holiday1202.01712.0
daylight_hrs-269.0156.0
PRCP3318.01163.0
dryday-9333.0722.0
Temp(C)-100.068.0
annual-4389.0280.0
首先,星期特征是比较稳定的,工作日骑车的人数显然比周末和节假日要多。其次,白昼时间每增加 1 小时,就平均增加 129 ± 9 个骑车的人;而温度每上升 1 度,则增加 65 ± 4 个骑车的人;如果那天没下雨,那么骑车人数增加 546 ± 33 人;降雨量每增加 1 英寸,骑车人数减少665 ± 62 人。当所有影响因素都生效之后,一年中每多一天骑车人数增加(日环比增幅)28 ± 18 人。
我们的模型的确丢失了一些重要信息。例如,变量的非线性影响因素(例如降雨和寒冷天气的影响)和非线性趋势(例如人们在温度过高或过低时可能都不愿意骑车)在模型中都没有体现。另外,我们丢掉了一些细颗粒度的数据(例如下雨天的早晨和下雨天的傍晚之间的差异),还忽略了相邻日期彼此间的相关性(例如下雨的星期二对星期三骑车人数的影响,或者滂沱大雨之后意外的雨过天晴对骑车人数的影响),这些都可能对骑车人数产生影响。