提供两年的每小时租金数据。训练集是每个月的前19天,而测试集是每月的20号到月底。必须仅使用租借期之前的可用信息来预测测试集涵盖的每个小时内租用的自行车总数。
一般而言,数据由甲方提供。若甲方不提供数据,则需要根据相关问题从网络爬取,或者以问卷调查形式收集。本次共享单车数据分析项目数据源于Kaggle(https://www.kaggle.com/c/bike-sharing-demand/data)。
// 载入工具包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime
import matplotlib
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.metrics import r2_score
%matplotlib inline
// 载入数据
train=pd.read_csv('/python/bike-sharingdemand/train.csv')
test=pd.read_csv('/python/bike-sharing-demand/test.csv')
//查看数据前5行
train.head()
//查看数据基本信息
train.info()
test.info()
训练数据共有12列,10886样本,并且数据无缺失;测试数据共有9列,6493样本。测试数据相对于训练数据,缺少casual(未注册用户租车数量)、registered(注册用户租车数量)、count(总租车数量)三列,这需要通过最终的模型预测得出。
datetime:时间。年月日小时格式
season:季节。1:春天;2:夏天;3:秋天;4:冬天
holiday:是否节假日。0:否;1:是
workingday:是否工作日。0:否;1:是
weather:天气。1:晴天;2:阴天;3:小鱼或小雪;4:恶劣天气
temp:实际温度
atemp:体感温度
humidity:湿度
windspeed:风速
casual:未注册用户租车数量
registered:注册用户租车数量
count:总租车数量
//查看数据基本信息
train.info()
test.info()
数据没有缺失值,因此不需要进行缺失值的处理
//数据描述
train.describe()
可以发现最终需要预测的租赁量(count)的标准差很大。通过画图看下分布:
plt.rcParams['font.family'] = ['Arial Unicode MS'] #mac和windows有区别,本次使用的是mac
plt.rcParams['axes.unicode_minus']=False #可以显示负数
plt.figure(figsize=(10,10))
plt.hist(train['count'],bins=20)
plt.title('租赁量分布趋势')
plt.xlabel('租赁量(count)')
整体的分布倾斜比较严重,需要处理一下,以便于最后不会过拟合。
处理方法:将3个标准差以外的数据排除,然后对count做log变换,并查看变换后的分布。
#排除3个标准差以外的数据
train=train[np.abs(train['count']-train['count'].mean())<=3*train['count'].std()]
fig=plt.figure()
plt.subplot(1,1,1)
sns.distplot(train['count'])
plt.title('移除异常点后的租赁量分布')
plt.xlabel('租赁量(count)')
#对数变换
y=train['count'].values
y_log=np.log(y)
sns.distplot(y_log)
plt.title('log变换后的count分布')
plt.savefig('log.png')
我们可以看到:对数变换后的数据,图形的倾斜没有那么 严重,差异也变小了。
由于日期时间特征由年、月、日和具体小时组成,还可以根据日期计算其星期,因此我们可以将日期时间拆分成年、月、日、时和星期5个特征。
# 为了方便整体处理,现将测试集和训练集合并
combined=train.append(test)
combined['year']=combined.datetime.apply(lambdax:x.split()[0].split('-')[0]).astype('int')
combined['month']=combined.datetime.apply(lambda x:x.split()[0].split('-')[1]).astype('int')
combined['date']=combined.datetime.apply(lambda x:x.split()[0])
combined['hour']=combined.datetime.apply(lambda x:x.split()[1].split(':')[0]).astype('int')
combined['weekday']=combined.date.apply( lambda x : datetime.strptime(x,'%Y-%m-%d').isoweekday())
#每个特征对租借量的影响
fig,axes=plt.subplots(2,2)
fig.set_size_inches(12,10)
#(1) 时间维度——年份
sns.boxplot(train['year'],train['count'],ax=axes[0,0])
#(2) 时间维度——月份
sns.pointplot(train['month'],train['count'],ax=axes[0,1])
#(3) 时间维度——季节
sns.boxplot(train['season'],train['count'],ax=axes[1,0])
#(4) 时间维度——时间(小时)
sns.pointplot(train['hour'],train['count'],ax=axes[1,1])
axes[0,0].set(xlabel='year',title='年份对租赁的影响')
axes[0,1].set(xlabel='month',title='月份对租赁的影响')
axes[1,0].set(xlabel='season',title='季节对租赁的影响')
axes[1,1].set(xlabel='hour',title='时间对租赁的影响')
"年份对租赁量的影响"图:2012年的租赁量比2011年的高,说明随着时间的推移,共享单车逐渐被更多的人接受,使用人数也越来越多。
“月份对租赁量的影响图”:月份对租赁量的影响显著,从1月份开始使用人数逐渐增多,到6月份达到顶峰,随后到10月份急剧下降,这明显和季节有关。
“季节对租赁量的影响图”:夏季和秋季骑车人数较多,并在秋季(天气适宜时)达到顶峰。但由于季节和月份的影响基本重合,且月份更加详细,因此建立模型时选取月份特征,去掉季节特征。
“时间对租赁量的影响图”:通过图形发现,每天有2个高峰期,分别是早上8点左右和下午17点左右,正好是工作日的上下班高峰。因此可以联想到节假日和星期对租赁量的影响。
fig, axes = plt.subplots(2,1,figsize=(16, 10))
sns.pointplot(train['hour'],train['count'],hue=train['workingday'],ax=plt.subplot(2,1,1))
sns.pointplot(train['hour'],train['count'],hue=train['holiday'],ax=plt.subplot(2,1,2))
可以看出,工作日早晚上班高峰期租借量高,其余时间租借量低;节假日中午及午后租借量较高。符合人们的的出行用车规律。
# 天气的影响
sns.boxplot(train['weather'],train['count'])
#温度、湿度、风速的影响
sns.pairplot(train[['temp', 'atemp', 'humidity', 'windspeed', 'count']])
作出多个连续变量之间的相关图时,可以比较任意两个变量之间的相关关系。图中temp和atemp的形状大体相似,因此可以在后续建模中用temp,删除掉atemp。
#1、计算相关系数,并快速查看
corr_df = train.corr()
influence_order = corr_df['count'].sort_values(ascending=False)
influence_order_abs = abs(corr_df['count']).sort_values(ascending=False)
print(influence_order)
print(influence_order_abs)
从相关系数可以看出,天气(包括温度、湿度)对租借数存在明显影响,其中temp和atemp的意义及其与count的相关系数十分接近,因此可以只取atemp作为温度特征。此外,year、month、season等时间因素对count也存在明显影响,而holiday和weekday与count的相关系数极小。
为了更加直观地展现所有特征之间的影响,作相关系数热力图:
corr_df1=abs(corr_df)
fig=plt.gcf()
fig.set_size_inches(30,12)
sns.heatmap(data=corr_df1,square=True,annot=True,cbar=True)
通过各项分析后我们在这里将时段(hour)、温度(temp)、湿度(humidity)、年份(year)、月份(month)、季节(season)、天气等级(weather)、风速(windspeed)、星期几(weekday)、是否工作日(workingday)、是否假日(holiday),作为特征值。
由于年份(year)、月份(month)、季节(season)、天气等级(weather)多类别型数据,我们使用one-hot转化成多个二分型类别。
month_one_hot = pd.get_dummies(combined['month'], prefix='month')
year_one_hot = pd.get_dummies(combined['year'], prefix='year')
season_one_hot = pd.get_dummies(combined['season'], prefix='season')
weather_one_hot = pd.get_dummies(combined['weather'], prefix='weather')
combined_one_hot = pd.concat([combined,month_one_hot,year_one_hot,season_one_hot,weather_one_hot],axis=1)
分开训练集和测试集
train_df = combined_one_hot.loc[combined['count'].isnull() == False]
test_df = combined_one_hot.loc[combined['count'].isnull() == True]
datetime_col = test_df['datetime']
ylables = train_df['count']
# 对数变换
y_log= np.log(ylables)
删除不需要的特征值
drop_columns = ['casual','count','datetime','registered','date','atemp','month','year','season','weather']
train_df = train_df.drop(drop_columns,axis=1)
test_df = test_df.drop(drop_columns,axis=1)
因为交叉验证的过程会有点久,因此涉及的参数比较简单。
# 训练集转换为训练数据和验证数据
x_train,x_test,y_train,y_test=train_test_split(train_df,y_log,test_size=0.2)
print(x_train.shape)
print(x_test.shape)
# 随机森林的超参数:决策树的数量、每个树的深度、
rf = RandomForestClassifier()
# 网格搜素与交叉验证
param = {"n_estimators":[100,200],"max_depth":[5,8,10]}
gc = GridSearchCV(rf,param_grid=param,cv=5)
gc.fit(x_train,y_train.astype('int'))
print("查看选择的参数模型:",gc.best_params_)
rf_true = RandomForestClassifier(n_estimators=200,max_depth=10,random_state=10,min_samples_split=10)
rf_true.fit(x_train,y_train.astype('int'))
predict_final = rf_true.predict(x_test)
print("测试集上的准确率:",rf_true.score(x_test,y_test.astype('int')))
print("测试集上的召回率:",r2_score(y_test,predict_final))
测试集上的准确率: 0.6219739292364991
测试集上的召回率: 0.6982082945228215
模型准确率比较低,应该继续调整参数进行验证,选择最优参数。。。。。。
datas = np.exp(rf_true.predict(test_df))
final=pd.DataFrame({'datetime':datetime_col,'count':datas})
final.to_csv('./result-final.csv')