【机器学习】kaggle实战:6大回归模型预测航班票价
共 8853字,需浏览 18分钟
· 2022-03-15
公众号:尤而小屋
作者:Peter
编辑:Peter
今天给大家带来一篇新的kaggle案例文章:基于6大回归模型预测航空公司机票的价格。
这篇文章涉及到的知识点会比较多,关键是数据预处理和特征工程部分:
![](https://filescdn.proginn.com/ec5c2a309546fe48274cf0930292f4ed/81f8dc71ca64019898181942e210c4b6.webp)
原notebook的学习地址为:
https://www.kaggle.com/anshigupta01/flight-price-prediction/notebook
kaggle文章
Plotly+Seaborn+Folium可视化探索爱彼迎租房数据
Plotly+Pandas+Sklearn:实现用户聚类分群!
导入库
![](https://filescdn.proginn.com/b70aca51be61a71da08ad976e6275d4e/2f3a3427d75c2a340492439cb11a029a.webp)
数据基本信息
先把数据导进来:
df = pd.read_excel("data_air.xlsx")
df.head()
![](https://filescdn.proginn.com/27f521d02a2826f85e267a0c082c37b0/83cd79b8a84c9089c2b29dc70668a624.webp)
查看数据的基本信息,包含:数据形状、字段类型等
![](https://filescdn.proginn.com/b7c7638e90112abc8ccf0b9e38d46da9/7e6aabf9e5ded6067a18f4b58cc24d21.webp)
# 字段类型
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:旅客的旅行开始日期 Source:旅客出发地 Destination:旅客目的地 Route:航班路线 Dep_Time:出发时间 Arrival_Time:抵达时间 Duration:持续时间;指的是航班完成从出发到目的地的旅程的整个时间 Total_Stops:总共停留地 Additional_Info:其他信息,比如:食物、设备信息等 Price:整个旅程的航班票价
希望可以理解中文含义,帮助进行数据分析~
![](https://filescdn.proginn.com/caa49289e477479dfc40f2d1e090465d/997ce99ef33f011b16ab392048d569c8.webp)
数值型字段的描述统计信息,这里主要是针对Price字段:
![](https://filescdn.proginn.com/0ef8fdc9f32775037a2d63e17109ae9b/68f6ae0254be181c80b288305017f89f.webp)
缺失值处理
通过上面的缺失值检查,我们看到有两个字段是存在缺失值的,进行可视化。
missingno是一个可视化缺失值的库,方便使用,我们可以用pip install missingno
即可下载该库
import missingno as mso
mso.bar(df,color="blue")
plt.show()
![](https://filescdn.proginn.com/851abbc302adc2a914bdeeb1b23627a8/52f8c473d134638994de931642768cd7.webp)
缺失值删除
正常的字段是10683条,其中两个字段是10682条;缺失值的占比很小,考虑直接删除的数据
![](https://filescdn.proginn.com/2797bf311362256af9578d681f7c3a04/26ecdbe88c05ac9a15deedd7f71621d5.webp)
时间相关字段处理
时间处理
通过pd.to_datetime()直接将字符型的数据转成时间类型的数据 通过dt.day或者df.month 直接获取天或者月的信息
# 将字段类型转成时间相关类型
def change_to_datetime(col):
df[col] = pd.to_datetime(df[col])
对3个字段实施转换:
# 3个字段的转换
for col in ['Date_of_Journey','Dep_Time', 'Arrival_Time']:
change_to_datetime(col)
查看转换之后的字段类型:
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)单独提取天和月,作为两个字段
df["day"] = df["Date_of_Journey"].dt.day
df["month"] = df["Date_of_Journey"].dt.month
df.head()
最终生成了两个新的字段:
![](https://filescdn.proginn.com/0bf66d753ec7d484f4d16c1cc716b4b9/e44b41ce58634f9a46b3dc343eb006b1.webp)
# 删除字段
df.drop('Date_of_Journey', axis=1, inplace=True)
起飞时间和抵达时间处理
主要提取两个时间中的“小时”和“分钟”信息;同时删除原字段:
![](https://filescdn.proginn.com/c06f2f4ce694197b3a189df723954665/4ef5a703f40ea2132e07cbe0fc337acc.webp)
分别调用函数来提取信息:
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()
![](https://filescdn.proginn.com/0201e9ec9630dc31dc8247b3f083798e/a529e007dc4132ae06e1b84be8cbf7f5.webp)
航班持续时间duration
1、将持续时间规范化处理,统一变成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]
下面是个人版本写法:
![](https://filescdn.proginn.com/aaa72d8042f948029ae34794164b829e/e07ac8be86a1e63caad76584b47ce868.webp)
![](https://filescdn.proginn.com/fa22d500f69238cbbb5cb58d0628894d/110f4ef29b9efdda12593f6b69a38a77.webp)
2、从Duration字段中提取小时和分钟:
![](https://filescdn.proginn.com/f3627b912f3d31008044bb0473bdce22/bca32193916c8e6f5bb6bbf6d7a05590.webp)
df.drop("Duration",inplace=True,axis=1)
3、字段类型转化:查看dur_hour和dur_minute的字段类型变化
![](https://filescdn.proginn.com/8c92faed89e0f176ece059c64fa4b847/2fc0279d98dafe998258f2b04b88d99c.webp)
字段编码
字段类型区分
主要是区分object和非object类型的字段信息。
1、针对字符型的字段
column = [column for column in df.columns if df[column].dtype == "object"]
column
['Airline', 'Source', 'Destination', 'Route', 'Total_Stops', 'Additional_Info']
2、数值型(连续型)字段
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种编码技术
Nominal data -- Data that are not in any order -->one hot encoding
ordinal data -- Data are in order --> labelEncoder
标称数据:没有任何顺序,使用独热编码oneot encoding 有序数据:存在一定的顺序,使用类型编码labelEncoder
生成标称型字段组成的数据
![](https://filescdn.proginn.com/3c7163bdff582ab175c49e71211541e5/c6359826cb34a7b0c49446dc5c86be9e.webp)
不同字段编码处理
航空公司-Airline
1、不同航空公司的数量统计:
![](https://filescdn.proginn.com/6383b6f074334cad9a8e422c30530ca3/f73fcd5a2251df3b8f12b35ffcb41bd6.webp)
![](https://filescdn.proginn.com/0259b24fbe02ca57b10921ad2b5c566c/95a3fd567f8507de02a060be8905a57d.webp)
2、查看航空公司与价格关系
plt.figure(figsize=(15,8))
sns.boxplot(x="Airline",
y="Price",
data=df.sort_values('Price',ascending=False)
)
plt.show()
![](https://filescdn.proginn.com/718bc449b957e47a9c788e0a7bcfd287/c91bdc9bee434f07fb52f27c2b3f57bc.webp)
3、2个明显的结论
从上面的图形中看出来:
Jet Airways Business公司的机票价格是最高的 其他公司的价格中位数是比较接近的
4、实现独热编码
由于航空公司属性是一个标称数据的字段,我们进行独热编码,通过哑变量的方式来实现:
![](https://filescdn.proginn.com/fb6a18da2528b20d1aca590d0e38ab37/bc98e718dfba3b8cfecaad9b9d728e2e.webp)
停留地-Total_Stops
旅行期间的总共停留地,实施上面的同样操作:
1、和价格的关系
plt.figure(figsize=(15,8))
sns.boxplot(x='Total_Stops',
y='Price',
data=df.sort_values('Price',ascending=False))
plt.show()
![](https://filescdn.proginn.com/eca23ab2f33940969fcb0b3c81aaad3f/180d03194cb08e75de223b13d322a674.webp)
2、实施硬编码;区别于航空公司的独热编码
![](https://filescdn.proginn.com/0ee1d382793fbc82371a41f2e8ee3d9b/f5a3b777e317ad4c35d03610810a84f9.webp)
出发地source
出发地和价格的关系:
plt.figure(figsize=(18,12))
sns.catplot(x='Source',
y='Price',
data=df.sort_values('Price',ascending=False),kind='boxen')
plt.show()
![](https://filescdn.proginn.com/a1f1d57b6afd3bcdc793488d352bde39/639e976a708dd26cdc94de29b598e33e.webp)
独热编码的过程:
![](https://filescdn.proginn.com/7887dbb3a56f4a6dd1e2feb5940846f9/18cbd8c7e14fafc9ad4b16818d4061a3.webp)
目的地-destination
目的地的数量统计:
![](https://filescdn.proginn.com/8311a264c63f7b150a905be30ec157fb/e28d216ec789eca742ffcad4aecd15d6.webp)
目的地和价格的关系:
![](https://filescdn.proginn.com/1fa1b954d37d944239e81d4c24d7bc88/697c5f0edfc483fa384d6ee9d25bf956.webp)
独热编码的实现:
![](https://filescdn.proginn.com/803c88a1e7ce73faa9fb584e9d48bc0d/4d94e6cb47d7c12657e2f9ff73ee50c9.webp)
路线Route
1、不同路线的数量统计:
![](https://filescdn.proginn.com/c6581b1707f5971846040b40170ef1f4/aa912402620a5613b10257f184b121e9.webp)
2、路线名称提取
从上面的结果中看出来,最长的路线中有5个地名,我们一次提取。
没有出现的数据则用NaN来表示:
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()
![](https://filescdn.proginn.com/53f754b220c8a74adffb3412bbc5212f/ca9e703e894e5e7ac3b17c56ebf06095.webp)
3、缺失值字段
![](https://filescdn.proginn.com/f9eb82caf9a9c1e9f8bd65c77cbeac5a/7bd936e975084fd68c9240bb06a17d92.webp)
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()
![](https://filescdn.proginn.com/33f6f9cc4bfbd919e8e75c8407b75373/627a7ac2b56003611c6d89090294dd12.webp)
抵达时间/小时-Arrival_Time_hour
抵达目的地时间和价格的关系:
df.plot.hexbin(x='Arrival_Time_hour',
y='Price',
gridsize=15)
plt.show()
![](https://filescdn.proginn.com/f26dbe59ca8badd3b69c7bdfd7087d51/ac64e6127fae609dea04e48cf23a20a1.webp)
建模数据
删除无效字段
生成的全部字段信息:
categorical.columns
Index(['Airline', 'Source', 'Destination', 'Total_Stops', 'Additional_Info',
'Route1', 'Route2', 'Route3', 'Route4', 'Route5'],
dtype='object')
将原始的无效字段直接删除:
drop_col(categorical,'Airline')
drop_col(categorical,'Source')
drop_col(categorical,'Destination')
drop_col(categorical,'Additional_Info')
最终数据
将多个DataFrame进行拼接,组成最终的建模,其中Price进行最终的输出特征
final_df=pd.concat([categorical,Airline,source,destination,df[continuous_col]],axis=1)
final_df.head()
![](https://filescdn.proginn.com/39914b876b0454695a76c29be8094326/867ae25319efde4f90721ebf6978b674.webp)
离群点检测
对上面生成的最终数据进行离群点检测:
![](https://filescdn.proginn.com/a672d5161f27cb41e040cabc5e4a5581/543a68108b95ca7e29b9bd8f5081a68f.webp)
![](https://filescdn.proginn.com/e6c426cf5309dc1239cc9bdd5edcd67b/6347672270da82ef6ff83a96d34cfa5f.webp)
对离群点填充均值,查看填充后的效果:
final_df['Price']=np.where(final_df['Price']>=40000, # 替换部分
final_df['Price'].median(), # 替换数据
final_df['Price']) # 替换字段
plot(final_df, "Price")
![](https://filescdn.proginn.com/941ee334854f2f4514d3b9d5034c5c29/268c77d5dec0a8a042de786d3085518e.webp)
数据切分
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 库:
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)
![](https://filescdn.proginn.com/fba773d7d899dc536e2e87f6ed452b3a/11f21771b26f3b0936c108347452902a.webp)
评价指标
本次建模中引入3个评价指标:
r2_score(重点关注) mean_absolute_error mean_squared_error
![](https://filescdn.proginn.com/9443f485319df1eb6c59d08c3f0a3970/1dcef1b20dd33092514f75918c9dffe6.webp)
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)))
# 真实值和预测值的差值
sns.distplot(y_test - predictions)
建模
导入多种模型:
# 逻辑回归
from sklearn.linear_model import LogisticRegression
# k近邻回归
from sklearn.neighbors import KNeighborsRegressor
# 决策树回归
from sklearn.tree import DecisionTreeRegressor
# 梯度提升回归,随机森林回归
from sklearn.ensemble import GradientBoostingRegressor,RandomForestRegressor
随机森林回归树-RandomForestRegressor
![](https://filescdn.proginn.com/d9bf9a57ae364b5e122e850656e2280d/59314083faf8e10fb7abac5c141de66e.webp)
逻辑回归-LogisticRegression
![](https://filescdn.proginn.com/cdcd5b6a2e889b4b3149a4b3274b667a/ae3aa5ef2ef75d95e6fcb336c5306fad.webp)
K近邻回归-KNeighborsRegressor
![](https://filescdn.proginn.com/a83fad5d8d7620e450df7ebe95cdfc61/06dba74bb1ef57cc7f9b0eac239cab10.webp)
决策树回归DecisionTreeRegressor
![](https://filescdn.proginn.com/7b4741edb0f878735c191fb725a756d2/65a1fbee398426adfe81699d300b7f09.webp)
支持向量机回归-SVR
![](https://filescdn.proginn.com/e6f845e7810ec48690a303a4753f4d94/5a9401373f19c0e8d5346924966d0e80.webp)
梯度提升回归-GradientBoostingRegressor
![](https://filescdn.proginn.com/92c224a16c5bb2a0b1c27d91577da840/b2366789543dcf95dbaa2d1840f4db07.webp)
模型调优-Hypertunning the model
调优寻参
# 采用随机搜索调优
from sklearn.model_selection import RandomizedSearchCV
# 待调优的参数
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)
多次运行调优后找到最佳的参数组合:
![](https://filescdn.proginn.com/efa12b13e328ff297359d297cea5a762/160db6dd5cd5f7c3f8c77343aa22bf2a.webp)
调优后结果
![](https://filescdn.proginn.com/bb889890d79baa3eaa1a60a3aad5c275/b508087c4cdcb6b497badfea155648f4.webp)
通过r2_score指标发现:进行参数调优后,模型的效果得到提升~
补充:如何理解回归模型的r2_score指标
假设我们用表示数据真实的观测值,用表示真实观测值的平均值,用表示通过模型得到的预测值,则:
回归平方和:SSR
SSR可以表示为;
![](https://filescdn.proginn.com/1bb11e934dffc74862f60260e276fd0b/4fa1a9a06933273fc6f4baa3d2b5a575.webp)
即估计值与平均值的误差,反映自变量与因变量之间的相关程度的偏差平方和
残差平方和:SSE
SSE可以表示为:
![](https://filescdn.proginn.com/9d5518c961ab23e42e014d55e5640ae8/0f0c59ea57723eefbdf9922b986f498f.webp)
即估计值与真实值的误差,反映的是整个模型拟合程度
总离差平方和:SST
![](https://filescdn.proginn.com/7d5e65e8c9158b165e9721dba171715e/bfa5060f26e06e883aef409e2b16f4c4.webp)
R2_score计算公式
R^2 score,即决定系数,反映因变量的全部变异能通过回归关系被自变量解释的比例。计算公式:
![](https://filescdn.proginn.com/2d2fa2622e1dcb102f7dd32fe9c9e69a/71a5ad32d0f6922ed5c06bfac1afe6d3.webp)
也可以写成:
![](https://filescdn.proginn.com/9692939831d73e32c693a9d744587c8e/213bc4d90dcfc6bbedf6d4122559dc5b.webp)
进一步可以转成:
![](https://filescdn.proginn.com/b856b6284d6a5595d8982c5fd5826fb3/f6fd98b07f9463664e6b42b2dc3d0417.webp)
此时分子就变成了我们常用的评价指标均方误差MSE,分母就变成了方差Var。r2_score在0-1之间,越接近1越好。
两种常见的求解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)
往期精彩回顾
适合初学者入门人工智能的路线及资料下载 (图文+视频)机器学习入门系列下载 中国大学慕课《机器学习》(黄海广主讲) 机器学习及深度学习笔记等资料打印 《统计学习方法》的代码复现专辑 AI基础下载 机器学习交流qq群955171419,加入微信群请扫码: