同步更新在個人網(wǎng)站:http://www.wangpengcufe.com/machinelearning/pythonml-pythonml8/
目錄
1、項(xiàng)目背景
2、項(xiàng)目主要應(yīng)用技術(shù)
3、項(xiàng)目數(shù)據(jù)
4、項(xiàng)目過程
4.1、導(dǎo)庫
4.2、數(shù)據(jù)讀取
4.3、數(shù)據(jù)探索
4.4、數(shù)據(jù)預(yù)處理
4.4.1、重復(fù)值處理
4.4.2、異常值處理
4.4.3、缺失值處理
4.4.4、特征構(gòu)造
4.5、特征工程
4.5.1、特征相關(guān)性分析
4.5.2、目標(biāo)相關(guān)性分析
4.6、模型搭建
4.6.1、構(gòu)建特征值和目標(biāo)值
4.6.2、劃分訓(xùn)練和測試集
4.6.3、模型搭建
4.7、模型評估
4.8、模型預(yù)測及可視化
4.9、特征重要性分析
4.10、模型保存
4.11、模型部署
4.12、接口測試
一、項(xiàng)目背景:
客流預(yù)測是城市軌道交通規(guī)劃設(shè)計(jì)和運(yùn)營管理的基本依據(jù),已成為城市軌道交通建設(shè)過程中的重要環(huán)節(jié)。隨著我國城市軌道交通路網(wǎng)的不斷完善,城市軌道交通客流預(yù)測的重要性也越來越明顯。本項(xiàng)目對城市軌道交通客流的統(tǒng)計(jì)特征及客流組合預(yù)測方法進(jìn)行了研究,通過對城市軌道交通客流歷史數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析,得出軌道交通客流的基本統(tǒng)計(jì)特征。主要表現(xiàn)為:節(jié)假日客流的統(tǒng)計(jì)特征與平常日客流明顯不同且不同節(jié)假日客流具有不同的統(tǒng)計(jì)特征;平常日客流具有一定的非線性和非平穩(wěn)性,并且以周為時間單位不斷波動;通過對軌道交通客流進(jìn)行聚類分析,可以將平常日客流分為工作日客流和周末客流兩大類。
客流研究的方向有很多,本項(xiàng)目主要圍繞平常日客流展開分析。
二、項(xiàng)目主要應(yīng)用技術(shù)
本項(xiàng)目用到的主要技術(shù)包括:
- 數(shù)據(jù)科學(xué):numpy,pandas
- 畫圖:matplotlib,seaborn
- 數(shù)據(jù)建模:sklearn
- 算法:DecisionTreeRegressor,RandomForestRegressor,LogisticRegression,LinearRegression,Ridge,Lasso
- 集成算法:xgboost,catboost,AdaBoostRegressor,GradientBoostingRegressor
- 模型保存:joblib
- 模型部署:flask
另外測試工具用的 RESTClient 。
三、項(xiàng)目數(shù)據(jù)
客流數(shù)據(jù):
date : 日期
p_flow : 客流
天氣數(shù)據(jù)采集:
date : 日期
weekday : 星期
top_temp : 最高溫
bot_temp : 最低溫
weather_ : 天氣
wind_ : 風(fēng)速
air : 空氣質(zhì)量
構(gòu)建數(shù)據(jù):
is_holiday : 是否節(jié)假日(1表示節(jié)假日,0表示工作日)
month :當(dāng)前日期所屬月份
dayofweek :當(dāng)前日期所屬星期
pre_date_flow : 前一天的客流
MA5 : 前5日移動平均客流
MA10 : 前10日移動平均客流
研究對象:p_flow : 客流
數(shù)據(jù)下載地址:
nanning_weather.csv
nanning_line1.csv 密碼:9898
四、項(xiàng)目過程
4.1、導(dǎo)庫:
# 數(shù)據(jù)科學(xué)
import numpy as np
import pandas as pd
# 畫圖相關(guān)
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family']='SimHei' # 中文亂碼
plt.rcParams['axes.unicode_minus']=False # 負(fù)號無法正常顯示
%config InlineBackend.figure_format='svg' # 像素清晰
from scipy.cluster.hierarchy import linkage, dendrogram # 繪制樹狀圖像
# 忽略警告
import warnings
warnings.filterwarnings('ignore')
# 特征選擇
from sklearn.preprocessing import LabelEncoder, MinMaxScaler, StandardScaler, OrdinalEncoder, OneHotEncoder
# 特征工程
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split
# 分類模型
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
from sklearn.naive_bayes import BernoulliNB # 伯努利貝葉斯
# 回歸模型
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LogisticRegression # 一元線性回歸模型
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge # 嶺回歸
from sklearn.linear_model import Lasso
# 集成算法
from xgboost import XGBRegressor
from catboost import CatBoostRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn.ensemble import GradientBoostingRegressor
# 模型評估
from sklearn.metrics import recall_score, precision_score, roc_auc_score, accuracy_score
4.2、數(shù)據(jù)讀?。?/h4>
df = pd.read_csv(r'E:\Files\公司業(yè)務(wù)\pkπ\(zhòng)2020-05\nanning_line1.csv', encoding='utf-8', parse_dates=['date'])
weather = pd.read_csv(r'E:\Files\公司業(yè)務(wù)\pkπ\(zhòng)2020-06\nanning_weather.csv', encoding='gbk',parse_dates=['date'])
df = pd.read_csv(r'E:\Files\公司業(yè)務(wù)\pkπ\(zhòng)2020-05\nanning_line1.csv', encoding='utf-8', parse_dates=['date'])
weather = pd.read_csv(r'E:\Files\公司業(yè)務(wù)\pkπ\(zhòng)2020-06\nanning_weather.csv', encoding='gbk',parse_dates=['date'])
這里的數(shù)據(jù)讀取的是公司在某城市1號線項(xiàng)目1年的真實(shí)客流數(shù)據(jù),讀取之后放到了df中。而天氣數(shù)據(jù)是爬取的網(wǎng)上的天氣網(wǎng)站的19年某城市的歷史天氣數(shù)據(jù)。
4.3、數(shù)據(jù)探索
數(shù)據(jù)形狀:
df.shape

顯示客流數(shù)據(jù)有365行,2列。
weather.shape

顯示天氣數(shù)據(jù)有365行,7列。
數(shù)據(jù)類型:
df.info()

顯示有365行不為空的客流數(shù)據(jù)。
weather.info()

顯示有365行不為空的天氣數(shù)據(jù)。
查看前幾行:
df.head()

客流數(shù)據(jù)的前幾行。
weather.head()

天氣數(shù)據(jù)的前幾行。
數(shù)據(jù)概覽:
df.describe()

weather.describe()

數(shù)據(jù)合并:
df = pd.merge(df,weather,on='date')

4.4、數(shù)據(jù)預(yù)處理
4.4.1、重復(fù)值處理:
查看重復(fù)值:
df[df.duplicated()]

顯示無重復(fù)數(shù)據(jù)。
查看重復(fù)數(shù)量:
df.duplicated().sum()

查看重復(fù)數(shù)據(jù)進(jìn)行確認(rèn),結(jié)果是0,缺失無重復(fù)數(shù)據(jù)。
4.4.2、異常值處理
查看數(shù)據(jù)分布:
讓我們來查看一下1年當(dāng)中數(shù)據(jù)的分布:
plt.figure(figsize=(15,5))
plt.plot(df.iloc[:,0],df.iloc[:,1]/10000, label='XX1號線')
plt.legend()
plt.show()

從圖中我們可以看出,1年的日客流數(shù)據(jù)存在節(jié)假日客流和非節(jié)假日客流之分。節(jié)假日客流的統(tǒng)計(jì)特征與平常日客流明顯不同且不同節(jié)假日客流具有不同的統(tǒng)計(jì)特征,例如春節(jié)期間,客流是明顯下降的,而五一,十一期間數(shù)據(jù)陡增;平常日客流具有一定的非線性和非平穩(wěn)性,并且以周為時間單位不斷波動。我們來看看異常值。
查看異常值:
plt.figure(figsize=(10,5))
p = df.boxplot(return_type='dict') #畫箱線圖,直接使用DataFrame的方法
x = p['fliers'][0].get_xdata() # 'flies'即為異常值的標(biāo)簽
y = p['fliers'][0].get_ydata()
y.sort()
for i in range(len(x)):
if i>0:
plt.annotate(y[i], xy = (x[i],y[i]), xytext=(x[i]+0.05 -0.10/(y[i]-y[i-1]),y[i]))
else:
plt.annotate(y[i], xy = (x[i],y[i]), xytext=(x[i]+0.10,y[i]))
plt.show()

找出異常數(shù)據(jù):
df[df['p_flow'].isin(y)]

y = p['fliers'][0].get_ydata() , 返回的y就是異常值,用isin()函數(shù)判斷DataFrame中是否存在特定的值;然后根據(jù)布爾索引,得到DataFrame中的異常值。
刪除異常值:
df.drop(df[df['p_flow'].isin(y)].index, inplace=True)
查看刪除的效果,再執(zhí)行一遍箱線圖:
plt.figure(figsize=(10,5))
p = df.boxplot(return_type='dict') #畫箱線圖,直接使用DataFrame的方法
x = p['fliers'][0].get_xdata() # 'flies'即為異常值的標(biāo)簽
y = p['fliers'][0].get_ydata()
y.sort()
for i in range(len(x)):
if i>0:
plt.annotate(y[i], xy = (x[i],y[i]), xytext=(x[i]+0.05 -0.10/(y[i]-y[i-1]),y[i]))
else:
plt.annotate(y[i], xy = (x[i],y[i]), xytext=(x[i]+0.10,y[i]))
plt.show()

可以看到異常值已經(jīng)清除完畢。
4.4.3、缺失值處理
df[df['flow'].isnull()]

提示數(shù)據(jù)中無缺失值。
4.4.4、特征構(gòu)造
增加是否節(jié)假日:
df['is_holiday'] = df['date'].apply(lambda x: 1 if is_holiday(x)==True else 0 )
df.index = range(df.shape[0]) # 恢復(fù)索引

客流特征可能跟今天是否為節(jié)假日有關(guān),節(jié)假日包括法定節(jié)日和周末。一般來說非工作日(周末和節(jié)假日)的客流和工作日的客流是有差異的,所以增加了這個特征。
增加月份:
df['month'] = df['date'].map(lambda x: x.month)

不同月份的客流可能會存在差異,故增加此特征進(jìn)行探索。
增加星期:
df['dayofweek']=df['date'].dt.dayofweek +1 # +1 之后數(shù)字幾就代表星期幾
客流可能存在以星期為規(guī)律變換的現(xiàn)象,所以提取了星期作為特征之一。
增加前一天的數(shù)據(jù):
df['pre_date_flow'] = df.loc[:,['p_flow']].shift(1)
作為時間序列問題,前一天的客流和今天的客流可能是存在某種關(guān)系的。比如前一天是第一天開始放假,客流下降,那么今天的客流也大概率是下降的。這里用了 shift()函數(shù)來對數(shù)據(jù)進(jìn)行平移,參數(shù)1表示平移1個單位,取昨天的數(shù)據(jù)。如果是-1就表示平移到下一個單位,取當(dāng)前時間的第二天數(shù)據(jù)。

5日移動平均:
df['MA5'] = df['p_flow'].rolling(5).mean()
同樣考慮時間序列問題,前5天的平均客流也是會對今天的客流數(shù)據(jù)產(chǎn)生影響。故增加此特征。

10日移動平均:
df['MA10'] = df['p_flow'].rolling(10).mean()
同理增加10日移動平均客流。

刪除節(jié)假日:
holiday_list =[ '2019-01-01'
,'2019-02-04','2019-02-05','2019-02-06','2019-02-07','2019-02-08','2019-02-09','2019-02-10'
,'2019-04-05','2019-04-06','2019-04-07'
,'2019-05-01','2019-05-02','2019-05-03','2019-05-04'
,'2019-06-07','2019-06-08','2019-06-09'
,'2019-09-13','2019-09-14','2019-09-15'
,'2019-10-01','2019-10-02','2019-10-03','2019-10-04','2019-10-05','2019-10-06','2019-10-07'
]
df = df[df['date'].isin(holiday_list) == False]
df.index = range(df.shape[0])
df.head()

節(jié)假日對于日常規(guī)律的客流數(shù)據(jù)而言,是異常值,所以進(jìn)行刪除。
刪除缺失值:
df.dropna(inplace=True)

4.5、特征工程
4.5.1、特征相關(guān)性分析
獲取所有特征變量:
feature = df.drop(['p_flow'],axis=1)

特征之間的相關(guān)性,得到相關(guān)性矩陣:
corr = feature.corr()

特征矩陣進(jìn)行熱力圖可視化展示:
plt.figure(figsize=(10,6))
ax = sns.heatmap(corr, xticklabels=corr.columns,
yticklabels=corr.columns, linewidths=0.2, cmap="RdYlGn",annot=True)
plt.title("Correlation between variables")

特征相關(guān)性分析,是為了查看特征之間是否存在多重共線性,如果有多重共線性的話,就要對相關(guān)性特別高的特征進(jìn)行有選擇的刪除。從熱力圖的結(jié)果來看,MA5和MA10的相關(guān)性是最高的,但也可以接受,不需要對特征進(jìn)行刪除。
4.5.2、目標(biāo)相關(guān)性分析
df_onehot = pd.get_dummies(df)
df_onehot.head()

可視化展示:
plt.figure(figsize=(20,4))
df_onehot.corr()['p_flow'].sort_values(ascending=False).plot(kind='bar')
plt.title('Correlation between p_flow and variables')

分析: 從上圖中我們可以看出:
1、客流和前一天的客流(pre_date_flow), 是否為節(jié)假日(is_holiday), 周期(dayofweek), 前10日平均客流(MA10),前5日平均客流(MA5) 是有相關(guān)性的,并且是正相關(guān)。
2、從天氣情況來看,客流和低溫(top_temp_12C),大風(fēng) (wind_東南風(fēng)5級) , 高溫(top_temp_35C),成正相關(guān)。和舒服的氣溫(weather晴天),舒適的溫度(bot_temp_23C),成負(fù)相關(guān)。說明惡劣天氣的時候,選擇乘坐地鐵的人比較多,而天氣好的時候,大家出行的可選性比較多。
3、從星期來看,和星期五,星期六,星期日成正相關(guān),和周一,周二,周三,周四成負(fù)相關(guān)。說明周末的客流數(shù)更多,而工作日的客流更少。由于我們?nèi)〉臄?shù)據(jù)是普通城市的客流數(shù)據(jù),這個和一線城市的客流情況是不同的。我們下面來通過對星期進(jìn)行聚類來驗(yàn)證。
# 按星期聚類
table = pd.pivot_table(df, index=['dayofweek'], values=['p_flow'], aggfunc='sum') # 對數(shù)據(jù)進(jìn)行聚合
plt.figure(figsize=(11,4))
Z = linkage(table, method='ward', metric='euclidean')
p = dendrogram(Z,0)
plt.xlabel("星期", fontsize=14)
plt.ylabel("歐式距離", fontsize=14)
plt.title("XX1號線星期聚類圖", fontsize=16)
plt.show()

可以看出,周一,周二,周三,周四,是聚成一類,周五,周六,周日聚成一類。
4.6、模型搭建
4.6.1、構(gòu)建特征值和目標(biāo)值
# 構(gòu)建特征值X 和目標(biāo)值 Y
X = df[['is_holiday','month','dayofweek','pre_date_flow','MA5','MA10']]
y = df['p_flow']
X.index = range(X.shape[0])

4.6.2、劃分訓(xùn)練集和測試集
X_length = X.shape[0]
split = int(X_length*0.9)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]
需要注意的是,這里的模型是一個時間序列問題,需要用前面的時間數(shù)據(jù)來預(yù)測后面的時間序列問題,故在此用前90%作為訓(xùn)練集,后10%作為測試集。而不能用train_test_split方法,對訓(xùn)練集和測試集進(jìn)行隨機(jī)劃分。
4.6.3、模型搭建
# 回歸算法
Regressors=[["Random Forest",RandomForestRegressor()]
,["Decision Tree",DecisionTreeRegressor()]
,["Lasso",Lasso()]
,["AdaBoostRegressor", AdaBoostRegressor()]
,["GradientBoostingRegressor", GradientBoostingRegressor()]
,["XGB", XGBRegressor()]
,["CatBoost", CatBoostRegressor(logging_level='Silent')]
]
reg_result=[]
names=[]
prediction=[]
for name,reg in Regressors:
reg = reg.fit(X_train, y_train)
y_pred=reg.predict(X_test)
#回歸評估
mae = mean_absolute_error(y_test,y_pred)
mse = mean_squared_error(y_test,y_pred)
r2= r2_score(y_test,y_pred)
class_eva=pd.DataFrame([mae,mse,r2])
reg_result.append(class_eva)
name=pd.Series(name)
names.append(name)
y_pred=pd.Series(y_pred)
prediction.append(y_pred)
4.7、模型評估
names=pd.DataFrame(names)
names=names[0].tolist()
result=pd.concat(reg_result,axis=1)
result.columns=names
result.index=["mae","mse","r2"]
result

我們可以看到隨機(jī)森林的r^2表現(xiàn)最好,mse均方誤差也是最小,那么我們選擇用隨機(jī)森林進(jìn)行預(yù)測。
4.8、模型預(yù)測及可視化:
rfc = RandomForestRegressor(n_estimators=50
,random_state=1
,bootstrap=True
,oob_score=True
)
rfc = rfc.fit(X_train, y_train)
y_pred = reg.predict(X_test)
預(yù)測結(jié)果可視化:
plt.figure(figsize=(15,6))
plt.title('預(yù)測結(jié)果圖')
plt.plot(y_test.ravel(),label='真實(shí)值')
plt.plot(y_pred,label='預(yù)測值')
plt.xticks([])
plt.legend()
plt.show()

4.9、特征重要性分析
rfc.feature_importances_
features = X_train.columns
impor = pd.DataFrame([*zip(features, rfc.feature_importances_)])
impor.columns= ['feature', 'importance']
impor.sort_values(by='importance', inplace = True)
# 隨機(jī)森林模型選擇特征重要性
plt.barh(impor['feature'], height=0.5, width=impor['importance'])
plt.title("隨機(jī)森林模型選擇特征重要性")
plt.show()

4.10、模型保存:
import joblib
# 模型保存
joblib.dump(reg, 'keliu.pkl')
4.11、模型部署:
from flask import Flask,request,jsonify
import joblib
app = Flask(__name__)
# /keliu是路由地址,methods是支持http方法,可以分為POST和GET
@app.route('/keliu',methods=["POST"])
def keliu():
# 獲得post傳過來的數(shù)據(jù)
data = request.get_json(force=True)
model = joblib.load('keliu.pkl')
pro = model.predict(data)
info = {'result': str(pro)}
return jsonify(info)#返回結(jié)果
if __name__ == '__main__':
# host='0.0.0.0'是為了接口能被局域網(wǎng)中其他機(jī)器訪問,port為端口號
app.run(host='0.0.0.0', port=8080)
4.12、接口測試:
接口調(diào)取

接口返回:
