文章目录
前言
本项目分析了影响单车租赁数的众多因素:年月日期时间、温度、湿度、季节、天气、风速,并对华盛顿地区的单车租赁需求进行预测。
本项目使用的模块:numpy、pandas、matplotlib、seaborn、datatime
一、数据采集与处理
1、数据来源
数据平台:Kaggle,一个数据建模和数据分析竞赛平台。
数据链接:https://www.kaggle.com/c/bike-sharing-demand
数据内容:美国华盛顿共享单车租赁数据,数据提供了跨越两年的每天不同时刻单车租赁数据,包含天气信息和日期信息,训练集(train.csv)由每月前19天的数据组成,测试集(test.csv)是每月第20天到当月底的数据。
data_train = pd.read_csv('train.csv')
data_test = pd.read_csv('test.csv')
2、数据预处理
(1)缺失值检查
#缺失值检查
data_train.info()
print('-'*40)
data_test.info()
运行结果:
表明数据集没有内容缺失
(2)异常值处理
第一,绘制直方图,观察数据分布情况
# 查看是否符合高斯分布
fig,axes = plt.subplots(1,3)
# 设置图形的尺寸,单位为英寸。1英寸等于2.54cm
fig.set_size_inches(18,5)
sns.distplot(data_train['count'],bins=100,ax=axes[0])
sns.distplot(data_train['casual'],bins=100,ax=axes[1])
sns.distplot(data_train['registered'],bins=100,ax=axes[2])
第二,数据统计信息描述
第三、箱线图(sns.boxplot函数)观察数据分布情况
fig,axes = plt.subplots(1,3)
fig.set_size_inches(12,6)
sns.boxplot(data = data_train['count'],ax=axes[0])
axes[0].set(xlabel='count')
sns.boxplot(data = data_train['casual'], ax=axes[1])
axes[1].set(xlabel='casual')
sns.boxplot(data = data_train['registered'], ax=axes[2])
axes[2].set(xlabel='registered')
第四,删除异常值(长尾,大值),并做对数log处理
# 去除异常值 将大于μ+3σ的数据值作为异常值
def drop_outlier(data,col):
mask = np.abs(data[col]-data[col].mean())<(3*data[col].std())
data = data.loc[mask]
# 可视化剔除异常值后的col和col_log
data[col+'_log'] = np.log1p(data[col])
f, [ax1, ax2] = plt.subplots(1,2, figsize=(15,6))
sns.distplot(data[col], ax=ax1)
ax1.set_title(col+'分布')
sns.distplot(data[col+'_log'], ax=ax2)
ax2.set_title(col+'_log分布')
return data
data_train = drop_outlier(data_train,'count')
data_train = drop_outlier(data_train,'casual')
data_train = drop_outlier(data_train,'registered')

二、可视化分析(统计学)
1.特征分解
将datetime特征拆分为日期、星期、年、月、日、小时
#特征分解
def split_datetime(data):
data['date'] = data['datetime'].apply(lambda x:x.split()[0])
data['weekday'] =data['date'].apply(lambda x:datetime.strptime(x,'%Y-%m-%d').isoweekday())
data['year'] = data['date'].apply(lambda x:x.split('-')[0]).astype('int')
data['month'] = data['date'].apply(lambda x:x.split('-')[1]).astype('int')
data['day'] = data['date'].apply(lambda x:x.split('-')[2]).astype('int')
data['hour'] = data['datetime'].apply(lambda x:x.split()[1].split(':')[0]).astype('int')
return data
data_train = split_datetime(data_train)
data_train.head()
data_train.info()
2.整体关系图(pairplot)
cols =['season','holiday','workingday','weekday','weather','temp',
'atemp','humidity','windspeed','hour']
sns.pairplot(data_train ,x_vars=cols,
y_vars=['casual','registered','count'],
plot_kws={'alpha': 0.2})
可以观察到:
一季度出行人数总体偏少
非假日借车总数比假日借车总数要高
会员在工作日出行多,节假日出行少,临时用户则相反
租赁数量随天气等级上升而减少
温度、湿度对非会员影响较大,对会员影响较小
小时数对租赁情况影响明显,会员呈现两个高峰,非会员呈正态分布
3.相关性分析(heatmap)
#查看各特征与count的相关性
corr = data_train.corr()
plt.subplots(figsize=(14,14))
sns.heatmap(corr,annot=True,vmax=1,cmap='YlGnBu')
# 降序
np.abs(corr['count']).sort_values(ascending=False)
运行结果:
count 1.000000
registered 0.977642
count_log 0.845294
registered_log 0.839080
casual_log 0.758863
casual 0.711105
hour 0.442659
temp 0.376656
atemp 0.372705
humidity 0.307362
month 0.176115
season 0.173657
year 0.171519
weather 0.123578
windspeed 0.116767
workingday 0.046246
weekday 0.022100
day 0.015243
holiday 0.002421
Name: count, dtype: float64
可以看出特征对count的影响力度分别为:
hour(时段)>temp(温度)>atemp(体感温度)>humidity(湿度)>month(月份)>season(季节)>year(年份)>weather(天气等级)>windspeed(风速)>workingday(工作日)>weekday(星期几)>day(天数)>(holiday)节假日
4.参看各因素与count的关系
(1)hour
# hour总体变化趋势
date = data_train.groupby(['hour'], as_index=False).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
fig = plt.figure(figsize=(18,6))
ax = fig.add_subplot(1,1,1)
# 使用总量
plt.plot(date['hour'], date['count'], linewidth=1.3)
# 会员使用量
plt.plot(date['hour'], date['registered'], linewidth=1.3)
# 非会员使用量
plt.plot(date['hour'], date['casual'], linewidth=1.3)
plt.legend()
# 工作日与非工作日下,hour与count的关系
date = data_train.groupby(['workingday','hour'], as_index=False).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
mask = date['workingday'] == 1
workingday_date= date[mask].drop(['workingday','hour'],axis=1).reset_index(drop=True)
nworkingday_date = date[~mask].drop(['workingday','hour'],axis=1).reset_index(drop=True)
fig, axes = plt.subplots(1,2,sharey = True)
workingday_date.plot(figsize=(15,5),title ='working day',ax=axes[0])
axes[0].set(xlabel='hour')
nworkingday_date.plot(figsize=(15,5),title ='nonworkdays',ax=axes[1])
axes[1].set(xlabel='hour')
可以看出:
*工作日
会员用户(registered)上下班时间是两个用车高峰,而中午也会有一个小高峰,猜测可能是外出午餐的人。
临时用户(casual)起伏比较平缓,高峰期在17点左右。
会员用户(registered)的用车数量远超临时用户(casual)。
*非工作日
租赁数量(count)随时间呈现一个正态分布,高峰在12点左右,低谷在4点左右,且分布比较均匀。
(2)temp
# 按温度取平均值
temp = data_train.groupby(['temp'], as_index=True).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
temp.plot(figsize=(10,5),title='温度与count的变化趋势')
(3)humidity
# 湿度
humidity = data_train.groupby(['humidity'], as_index=True).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
humidity.plot(figsize=(10,5),title='湿度与count的变化趋势')
(4)month
# 月使用量变化趋势
date = data_train.groupby(['month'], as_index=False).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
fig = plt.figure(figsize=(18,6))
ax = fig.add_subplot(1,1,1)
plt.plot(date['month'], date['count'] , linewidth=1.3 , label = '使用总量' )
plt.plot(date['month'], date['registered'] , linewidth=1.3 , label = '会员使用量' )
plt.plot(date['month'], date['casual'] , linewidth=1.3 , label = '非会员使用量' )
plt.legend()
(5)season
day_df=data_train.groupby('date').agg({'year':'mean','season':'mean',
'casual':'sum', 'registered':'sum'
,'count':'sum','temp':'mean',
'atemp':'mean'})
season_df = day_df.groupby(['year','season'], as_index=True).agg({'casual':'mean',
'registered':'mean',
'count':'mean'})
temp_df = day_df.groupby(['year','season'], as_index=True).agg({'temp':'mean',
'atemp':'mean'})
fig = plt.figure(figsize=(10,10))
xlables = season_df.index.map(lambda x:str(x))
ax1 = fig.add_subplot(2,1,1)
ax1.set_title('这两年count随季节的总体趋势')
plt.plot(xlables,season_df)
plt.legend(['casual','registered','count'])
ax2 = fig.add_subplot(2,1,2)
ax2.set_title('这两年count随季节的总体趋势')
plt.plot(xlables,temp_df)
plt.legend(['temp','atemp'])
无论是临时用户还是会员用户用车的数量都在秋季迎来高峰,而春季度用户数量最低
(6)weather
weather_df = data_train.groupby('weather',as_index=True).agg({'casual':'mean',
'registered':'mean'})
weather_df.plot.bar(stacked=True)
(7)windspeed
# 风速
# 化为整数型数据
data_train['windspeed'] = data_train['windspeed'].astype(int)
windspeed = data_train.groupby(['windspeed'], as_index=True).agg({'count':'mean',
'registered':'mean',
'casual':'mean'})
windspeed.plot(figsize=(10,8))
(8)日期(直方图)
三、基于机器学习的模型预测
1.特征工程
首先,数据异常值处理,并取对数
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
#训练集去除3倍方差以外数据
train_std = train[np.abs(train['count']-train['count'].mean())<=(3*train['count'].std())]
train_std.reset_index(drop=True,inplace=True)
train_std.shape
test.shape
#对数据进行对数变换后的分布,特别注意后面预测数据时需要取指数变换
ylabels = train_std['count']
ylabels_log = np.log(ylabels)
sns.distplot(ylabels_log)
其次,多类别型数据转为二分型类别(one-hot)
机器学习算法无法直接用于数据分类。数据分类必须转换为数字二进制才能进一步进行。one-hot编码是分类变量作为二进制向量的表示。这首先要求将分类值映射到整数值。然后,每个整数值被表示为二进制向量,除了整数的索引之外,它都是零值,它被标记为1。get_dummies 是利用pandas实现one hot encode的方式。
#利用one-hot将多类别型数据转为二分型类别
combine_feature = combine_train_test[['temp','humidity','weather','season','year','weather',
'month','weekday','hour','workingday','windspeed','count']]
combine_feature.info()
cols = ['month','season','weather','year']
combine_feature = pd.get_dummies(combine_feature,columns=cols,prefix_sep='_')
combine_feature.info()
最后,划分数据集
原始数据:
利用train_test_split随机分配train_data为新的训练集和数据集
train_X, test_X, train_y, test_y = train_test_split(source_X,
source_y,
train_size = 0.80)
2.随机森林算法
#模型一随机森林
from sklearn.ensemble import RandomForestRegressor
# 模型参数
forest_parmas = {'n_estimators':[1300,1500,1700], 'max_depth':range(20,30,4)}
Model = RandomForestRegressor(oob_score=True,n_jobs=-1,random_state = 42)
Model = get_best_model_and_accuracy(Model,forest_parmas ,train_X, train_y)
Model=Model.best_estimator_
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=24,
max_features='auto', max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, n_estimators=1500, n_jobs=-1,
oob_score=True, random_state=42, verbose=0, warm_start=False)
# 分类问题,score得到的是模型的正确率
Model.score(test_X,test_y)
# 袋外分数
Model.oob_score_
# 模型保存
from sklearn.externals import joblib
joblib.dump(Model, "rf.pkl", compress=9)#将模型保存为rfpkl
from sklearn.metrics import mean_absolute_error
pre_y = Model.predict(test_X.values)#预测
pre_y = np.exp(pre_y)#这里格外注意,因为特征工程中将count值取对数,所以这里指数变换
test_y = np.exp(test_y)#这里格外注意,因为特征工程中将count值取对数,所以这里指数变换,测试集真实值
构建模型
模型训练,优化参数
模型预测
时间:电脑程序运行时间5min
正确率:
# 分类问题,score得到的是模型的正确率
Model.score(test_X,test_y)
0.9544882922113979
3.xgboost模型预测
#模型二xgboost
import sys
sys.path.append("D:/python/Anaconda3/Lib/site-packages")#添加python搜索路径
import xgboost
import xgboost as xg
# 模型参数 subsample:对于每棵树,随机采样的比例
xg_parmas = {'subsample':[i/10.0 for i in range(6,10)],
'colsample_bytree':[i/10.0 for i in range(6,10)]} # 控制每棵随机采样的列数的占比
xg_model = xg.XGBRegressor(max_depth=8,min_child_weight=6,gamma=0.4)
xg_model = get_best_model_and_accuracy(xg_model,xg_parmas,train_X.values, train_y.values)
xg_model=xg_model.best_estimator_
xg_model
xg.XGBRegressor(base_score=0.5, booster=None, colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=0.9, gamma=0.4, gpu_id=-1,
importance_type='gain', interaction_constraints=None,
learning_rate=0.300000012, max_delta_step=0, max_depth=8,
min_child_weight=6, missing=None, monotone_constraints=None,
n_estimators=100, n_jobs=0, num_parallel_tree=1,
objective='reg:squarederror', random_state=0, reg_alpha=0,
reg_lambda=1, scale_pos_weight=1, subsample=0.9, tree_method=None,
validate_parameters=False, verbosity=None)
from sklearn.metrics import mean_absolute_error
pre_y = xg_model.predict(test_X.values)#预测
pre_y = np.exp(pre_y)#这里格外注意,因为特征工程中将count值取对数,所以这里指数变换
Best Accuracy: 0.9519465121003385
Best Parameters: {‘colsample_bytree’: 0.9, ‘subsample’: 0.9}
Average Time to Fit (s): 1.136
Average Time to Score (s): 0.019
**对比发现,随机森林正确率更高。
四、GUI界面设计
# 创建GUI窗口打开图像 并显示在窗口中
from PIL import Image, ImageTk # 导入图像处理函数库
import tkinter as tk # 导入GUI界面函数库
import time
# 创建窗口 设定大小并命名
window = tk.Tk()
window.title('共享单车租赁数量影响因数可视化分析系统')
window.geometry('600x900')
global img_png6
global img_png1
global img_png2
global img_png3
global img_png4
global img_png5
var = tk.StringVar() # 这时文字变量储存器
global img_png7
# 创建打开图像和显示图像函数
def Open_Img():
global img_png1
global img_png2
global img_png3
global img_png4
global img_png5
var.set('已打开')
Img1 = Image.open('humidity.gif')
img_png1 = ImageTk.PhotoImage(Img1)
Img2 = Image.open('temp.gif')
img_png2 = ImageTk.PhotoImage(Img2)
Img3 = Image.open('weather.gif')
img_png3 = ImageTk.PhotoImage(Img3)
Img4 = Image.open('weekday.gif')
img_png4 = ImageTk.PhotoImage(Img4)
Img5 = Image.open('working_day.gif')
img_png5 = ImageTk.PhotoImage(Img5)
def Show_Img():
global img_png1
global img_png2
global img_png3
global img_png4
global img_png5
var.set('已显示') # 设置标签的文字为 'you hit me'
label_Img = tk.Label(window, image=img_png1)
label_Img.place(x=150,y=190,height=100,width=300)
label_Img = tk.Label(window, image=img_png2)
label_Img.place(x=150,y=310,height=100,width=300)
label_Img = tk.Label(window, image=img_png3)
label_Img.place(x=150,y=430,height=100,width=300)
label_Img = tk.Label(window, image=img_png4)
label_Img.place(x=150,y=550,height=100,width=300)
label_Img = tk.Label(window, image=img_png5)
label_Img.place(x=150,y=670,height=200,width=300)
# 创建文本窗口,显示当前操作状态
Label_Show = tk.Label(window,
textvariable=var, # 使用 textvariable 替换 text, 因为这个可以变化
bg='blue', font=('Arial', 12), width=15, height=2)
Label_Show.pack()
# 创建打开图像按钮
btn_Open = tk.Button(window,
text='打开图像', # 显示在按钮上的文字
width=15, height=2,
command=Open_Img) # 点击按钮式执行的命令
btn_Open.pack() # 按钮位置
# 创建显示图像按钮
btn_Show = tk.Button(window,
text='显示图像', # 显示在按钮上的文字
width=15, height=2,
command=Show_Img) # 点击按钮式执行的命令
btn_Show.pack() # 按钮位置
# 运行整体窗口
window.mainloop()
总结
本项目主要对华盛顿共享单车使用量影响因素进行可视化分析,并利用机器学习对使用量进行预测。