【機(jī)器學(xué)習(xí)】kaggle實(shí)戰(zhàn):6大回歸模型預(yù)測(cè)航班票價(jià)
公眾號(hào):尤而小屋
作者:Peter
編輯:Peter
今天給大家?guī)?lái)一篇新的kaggle案例文章:基于6大回歸模型預(yù)測(cè)航空公司機(jī)票的價(jià)格。
這篇文章涉及到的知識(shí)點(diǎn)會(huì)比較多,關(guān)鍵是數(shù)據(jù)預(yù)處理和特征工程部分:

原notebook的學(xué)習(xí)地址為:
https://www.kaggle.com/anshigupta01/flight-price-prediction/notebook
kaggle文章
基于LSTM模型實(shí)現(xiàn)共享自行車需求預(yù)測(cè)
基于隨機(jī)森林模型的心臟病患者預(yù)測(cè)分類
從統(tǒng)計(jì)和數(shù)據(jù)角度出發(fā),如何看待房?jī)r(jià)?
Plotly+Seaborn+Folium可視化探索愛(ài)彼迎租房數(shù)據(jù)
Plotly+Pandas+Sklearn:實(shí)現(xiàn)用戶聚類分群!
7000字,利用Python分析泰坦尼克數(shù)據(jù)!
導(dǎo)入庫(kù)

數(shù)據(jù)基本信息
先把數(shù)據(jù)導(dǎo)進(jìn)來(lái):
df?=?pd.read_excel("data_air.xlsx")
df.head()

查看數(shù)據(jù)的基本信息,包含:數(shù)據(jù)形狀、字段類型等

#?字段類型
df.dtypes
Airline????????????object
Date_of_Journey????object
Source?????????????object
Destination????????object
Route??????????????object
Dep_Time???????????object
Arrival_Time???????object
Duration???????????object
Total_Stops????????object
Additional_Info????object
Price???????????????int64
dtype:?object
??
#?全部字段
columns?=?df.columns.tolist()
columns
['Airline',
?'Date_of_Journey',
?'Source',
?'Destination',
?'Route',
?'Dep_Time',
?'Arrival_Time',
?'Duration',
?'Total_Stops',
?'Additional_Info',
?'Price']
具體字段的中文含義:
Airline:不同類型的航空公司 Date_of_Journey:旅客的旅行開(kāi)始日期 Source:旅客出發(fā)地 Destination:旅客目的地 Route:航班路線 Dep_Time:出發(fā)時(shí)間 Arrival_Time:抵達(dá)時(shí)間 Duration:持續(xù)時(shí)間;指的是航班完成從出發(fā)到目的地的旅程的整個(gè)時(shí)間 Total_Stops:總共停留地 Additional_Info:其他信息,比如:食物、設(shè)備信息等 Price:整個(gè)旅程的航班票價(jià)
希望可以理解中文含義,幫助進(jìn)行數(shù)據(jù)分析~

數(shù)值型字段的描述統(tǒng)計(jì)信息,這里主要是針對(duì)Price字段:

缺失值處理
通過(guò)上面的缺失值檢查,我們看到有兩個(gè)字段是存在缺失值的,進(jìn)行可視化。
missingno是一個(gè)可視化缺失值的庫(kù),方便使用,我們可以用pip install missingno 即可下載該庫(kù)
import?missingno?as?mso
mso.bar(df,color="blue")
plt.show()

缺失值刪除
正常的字段是10683條,其中兩個(gè)字段是10682條;缺失值的占比很小,考慮直接刪除的數(shù)據(jù)

時(shí)間相關(guān)字段處理
時(shí)間處理
通過(guò)pd.to_datetime()直接將字符型的數(shù)據(jù)轉(zhuǎn)成時(shí)間類型的數(shù)據(jù) 通過(guò)dt.day或者df.month 直接獲取天或者月的信息
#?將字段類型轉(zhuǎn)成時(shí)間相關(guān)類型
def?change_to_datetime(col):
????df[col]?=?pd.to_datetime(df[col])
對(duì)3個(gè)字段實(shí)施轉(zhuǎn)換:
#?3個(gè)字段的轉(zhuǎn)換
for?col?in?['Date_of_Journey','Dep_Time',?'Arrival_Time']:
????change_to_datetime(col)
查看轉(zhuǎn)換之后的字段類型:
df.dtypes
Airline????????????????????object
Date_of_Journey????datetime64[ns]
Source?????????????????????object
Destination????????????????object
Route??????????????????????object
Dep_Time???????????datetime64[ns]
Arrival_Time???????datetime64[ns]
Duration???????????????????object
Total_Stops????????????????object
Additional_Info????????????object
Price???????????????????????int64
dtype:?object
提取天和月
乘客旅程日期中(Date_of_Journey)單獨(dú)提取天和月,作為兩個(gè)字段
df["day"]?=?df["Date_of_Journey"].dt.day
df["month"]?=?df["Date_of_Journey"].dt.month
df.head()
最終生成了兩個(gè)新的字段:

#?刪除字段
df.drop('Date_of_Journey',?axis=1,?inplace=True)
起飛時(shí)間和抵達(dá)時(shí)間處理
主要提取兩個(gè)時(shí)間中的“小時(shí)”和“分鐘”信息;同時(shí)刪除原字段:

分別調(diào)用函數(shù)來(lái)提取信息:
extract_hour(df,'Dep_Time')
extract_minute(df,'Dep_Time')
drop_col(df,'Dep_Time')??#?刪除原字段
extract_hour(df,'Arrival_Time')
extract_minute(df,'Arrival_Time')
drop_col(df,'Arrival_Time')
df.head()

航班持續(xù)時(shí)間duration
1、將持續(xù)時(shí)間規(guī)范化處理,統(tǒng)一變成0h 1m
#?#?原文方法
#?duration=list(df['Duration'])
#?for?i?in?range(len(duration)):
#?????if?len(duration[i].split('?'))==2:
#?????????pass
#?????else:
#?????????if?'h'?in?duration[i]:?
#??????????????duration[i]=duration[i]?+?'?0m'?
#?????????else:
#??????????????duration[i]='0h?'+?duration[i]
下面是個(gè)人版本寫(xiě)法:


2、從Duration字段中提取小時(shí)和分鐘:

df.drop("Duration",inplace=True,axis=1)
3、字段類型轉(zhuǎn)化:查看dur_hour和dur_minute的字段類型變化

字段編碼
字段類型區(qū)分
主要是區(qū)分object和非object類型的字段信息。
1、針對(duì)字符型的字段
column?=?[column?for?column?in?df.columns?if?df[column].dtype?==?"object"]
column
['Airline',?'Source',?'Destination',?'Route',?'Total_Stops',?'Additional_Info']
2、數(shù)值型(連續(xù)型)字段
continuous_col?=?[column?for?column?in?df.columns?if?df[column].dtype?!=?"object"]
continuous_col
['Price',
?'day',
?'month',
?'Dep_Time_hour',
?'Dep_Time_minute',
?'Arrival_Time_hour',
?'Arrival_Time_minute',
?'dur_hour',
?'dur_minute']
2種編碼技術(shù)
Nominal?data?--?Data?that?are?not?in?any?order?-->one?hot?encoding
ordinal?data?--?Data?are?in?order?-->?labelEncoder
標(biāo)稱數(shù)據(jù):沒(méi)有任何順序,使用獨(dú)熱編碼oneot encoding 有序數(shù)據(jù):存在一定的順序,使用類型編碼labelEncoder
生成標(biāo)稱型字段組成的數(shù)據(jù)

不同字段編碼處理
航空公司-Airline
1、不同航空公司的數(shù)量統(tǒng)計(jì):


2、查看航空公司與價(jià)格關(guān)系
plt.figure(figsize=(15,8))
sns.boxplot(x="Airline",
????????????y="Price",
????????????data=df.sort_values('Price',ascending=False)
???????????)
plt.show()

3、2個(gè)明顯的結(jié)論
從上面的圖形中看出來(lái):
Jet Airways Business公司的機(jī)票價(jià)格是最高的 其他公司的價(jià)格中位數(shù)是比較接近的
4、實(shí)現(xiàn)獨(dú)熱編碼
由于航空公司屬性是一個(gè)標(biāo)稱數(shù)據(jù)的字段,我們進(jìn)行獨(dú)熱編碼,通過(guò)啞變量的方式來(lái)實(shí)現(xiàn):

停留地-Total_Stops
旅行期間的總共停留地,實(shí)施上面的同樣操作:
1、和價(jià)格的關(guān)系
plt.figure(figsize=(15,8))
sns.boxplot(x='Total_Stops',
????????????y='Price',
????????????data=df.sort_values('Price',ascending=False))
plt.show()

2、實(shí)施硬編碼;區(qū)別于航空公司的獨(dú)熱編碼

出發(fā)地source
出發(fā)地和價(jià)格的關(guān)系:
plt.figure(figsize=(18,12))
sns.catplot(x='Source',
????????????y='Price',
????????????data=df.sort_values('Price',ascending=False),kind='boxen')
plt.show()

獨(dú)熱編碼的過(guò)程:

目的地-destination
目的地的數(shù)量統(tǒng)計(jì):

目的地和價(jià)格的關(guān)系:

獨(dú)熱編碼的實(shí)現(xiàn):

路線Route
1、不同路線的數(shù)量統(tǒng)計(jì):

2、路線名稱提取
從上面的結(jié)果中看出來(lái),最長(zhǎng)的路線中有5個(gè)地名,我們一次提取。
沒(méi)有出現(xiàn)的數(shù)據(jù)則用NaN來(lái)表示:
categorical['Route1']=categorical['Route'].str.split('→').str[0]
categorical['Route2']=categorical['Route'].str.split('→').str[1]
categorical['Route3']=categorical['Route'].str.split('→').str[2]
categorical['Route4']=categorical['Route'].str.split('→').str[3]
categorical['Route5']=categorical['Route'].str.split('→').str[4]
categorical.head()

3、缺失值字段

for?i?in?['Route3',?'Route4',?'Route5']:
????categorical[i].fillna('None',inplace=True)
4、類型編碼LabelEncoder
from?sklearn.preprocessing?import?LabelEncoder
le?=?LabelEncoder()
for?i?in?['Route1',?'Route2',?'Route3',?'Route4',?'Route5']:
????categorical[i]=le.fit_transform(categorical[i])
????
categorical.head()

抵達(dá)時(shí)間/小時(shí)-Arrival_Time_hour
抵達(dá)目的地時(shí)間和價(jià)格的關(guān)系:
df.plot.hexbin(x='Arrival_Time_hour',
???????????????y='Price',
???????????????gridsize=15)
plt.show()

建模數(shù)據(jù)
刪除無(wú)效字段
生成的全部字段信息:
categorical.columns
Index(['Airline',?'Source',?'Destination',?'Total_Stops',?'Additional_Info',
???????'Route1',?'Route2',?'Route3',?'Route4',?'Route5'],
??????dtype='object')
將原始的無(wú)效字段直接刪除:
drop_col(categorical,'Airline')
drop_col(categorical,'Source')
drop_col(categorical,'Destination')
drop_col(categorical,'Additional_Info')
最終數(shù)據(jù)
將多個(gè)DataFrame進(jìn)行拼接,組成最終的建模,其中Price進(jìn)行最終的輸出特征
final_df=pd.concat([categorical,Airline,source,destination,df[continuous_col]],axis=1)
final_df.head()

離群點(diǎn)檢測(cè)
對(duì)上面生成的最終數(shù)據(jù)進(jìn)行離群點(diǎn)檢測(cè):


對(duì)離群點(diǎn)填充均值,查看填充后的效果:
final_df['Price']=np.where(final_df['Price']>=40000,??#?替換部分
???????????????????????????final_df['Price'].median(),?#?替換數(shù)據(jù)
???????????????????????????final_df['Price'])?#?替換字段
plot(final_df,?"Price")

數(shù)據(jù)切分
X=final_df.drop('Price',axis=1)
y=final_df['Price']
from?sklearn.model_selection?import?train_test_split
X_train,X_test,y_train,y_test?=?train_test_split(X,y,
?????????????????????????????????????????????????test_size=0.20,
?????????????????????????????????????????????????random_state=123)
特征選擇
本文中特征選擇使用的是 mutual_info_classif 庫(kù):
from?sklearn.feature_selection?import?mutual_info_classif
imp?=?pd.DataFrame(mutual_info_classif(X,y),
??????????????????index=X.columns)
imp.columns=['importance']
imp.sort_values(by='importance',ascending=False)

評(píng)價(jià)指標(biāo)
本次建模中引入3個(gè)評(píng)價(jià)指標(biāo):
r2_score(重點(diǎn)關(guān)注) mean_absolute_error mean_squared_error

from?sklearn.metrics?import?r2_score,mean_absolute_error,mean_squared_error
def?predict(ml_model):
????print("Model?is:?",?ml_model)
????
????model?=?ml_model.fit(X_train,?y_train)
????
????print("Training?score:?",?model.score(X_train,y_train))
????
????predictions?=?model.predict(X_test)
????print("Predictions:?",?predictions)
????print('-----------------')
????r2score?=?r2_score(y_test,?predictions)
????print("r2?score?is:?",?r2score)
????
????print('MAE:{}',?mean_absolute_error(y_test,predictions))
????print('MSE:{}',?mean_squared_error(y_test,predictions))
????print('RMSE:{}',?np.sqrt(mean_squared_error(y_test,predictions)))
????
????#?真實(shí)值和預(yù)測(cè)值的差值
????sns.distplot(y_test?-?predictions)
建模
導(dǎo)入多種模型:
#?邏輯回歸
from?sklearn.linear_model?import?LogisticRegression???
#?k近鄰回歸
from?sklearn.neighbors?import?KNeighborsRegressor??
#?決策樹(shù)回歸
from?sklearn.tree?import?DecisionTreeRegressor??
#?梯度提升回歸,隨機(jī)森林回歸
from?sklearn.ensemble?import?GradientBoostingRegressor,RandomForestRegressor??
隨機(jī)森林回歸樹(shù)-RandomForestRegressor

邏輯回歸-LogisticRegression

K近鄰回歸-KNeighborsRegressor

決策樹(shù)回歸DecisionTreeRegressor

支持向量機(jī)回歸-SVR

梯度提升回歸-GradientBoostingRegressor

模型調(diào)優(yōu)-Hypertunning the model
調(diào)優(yōu)尋參
#?采用隨機(jī)搜索調(diào)優(yōu)
from?sklearn.model_selection?import?RandomizedSearchCV
#?待調(diào)優(yōu)的參數(shù)
random_grid?=?{
????'n_estimators'?:?[100,?120,?150,?180,?200,220],
????'max_features':['auto','sqrt'],
????'max_depth':[5,10,15,20],
????}
#?建模擬合
rf=RandomForestRegressor()
rf_random=RandomizedSearchCV(
??estimator=rf,
??param_distributions=random_grid,
??cv=3,
??verbose=2,
??n_jobs=-1)
rf_random.fit(X_train,y_train)
多次運(yùn)行調(diào)優(yōu)后找到最佳的參數(shù)組合:

調(diào)優(yōu)后結(jié)果

通過(guò)r2_score指標(biāo)發(fā)現(xiàn):進(jìn)行參數(shù)調(diào)優(yōu)后,模型的效果得到提升~
補(bǔ)充:如何理解回歸模型的r2_score指標(biāo)
假設(shè)我們用表示數(shù)據(jù)真實(shí)的觀測(cè)值,用表示真實(shí)觀測(cè)值的平均值,用表示通過(guò)模型得到的預(yù)測(cè)值,則:
回歸平方和:SSR
SSR可以表示為;

即估計(jì)值與平均值的誤差,反映自變量與因變量之間的相關(guān)程度的偏差平方和
殘差平方和:SSE
SSE可以表示為:

即估計(jì)值與真實(shí)值的誤差,反映的是整個(gè)模型擬合程度
總離差平方和:SST

R2_score計(jì)算公式
R^2 score,即決定系數(shù),反映因變量的全部變異能通過(guò)回歸關(guān)系被自變量解釋的比例。計(jì)算公式:

也可以寫(xiě)成:

進(jìn)一步可以轉(zhuǎn)成:

此時(shí)分子就變成了我們常用的評(píng)價(jià)指標(biāo)均方誤差MSE,分母就變成了方差Var。r2_score在0-1之間,越接近1越好。
兩種常見(jiàn)的求解r2的方式:
#?1、利用python間接求解
from?sklearn.metrics?import?mean_squared_error
1?-?mean_squared_error(y_test,?y_pred)/?np.var(y_test)
#?2、sklearn直接求解
from?sklearn.metrics?import?r2_score
y_test?=?[1,?2,?3]
y_pred?=?[1.3,?2.1,?3.5]
r2_score(y_test,y_pred)??
往期精彩回顧
