公众号:尤而小屋 作者:Peter 编辑:Peter
今天给大家带来一篇新的kaggle案例文章: 基于6大回归模型预测航空公司机票的价格 。
这篇文章涉及到的知识点会比较多,关键是数据预处理 和特征工程 部分:
原notebook的学习地址为:
https://www.kaggle.com/anshigupta01/flight-price-prediction/notebook
kaggle文章
基于LSTM模型实现共享自行车需求预测
6大监督学习模型:毒蘑菇分类
基于随机森林模型的心脏病患者预测分类
从统计和数据角度出发,如何看待房价?
Plotly+Seaborn+Folium可视化探索爱彼迎租房数据
Plotly+Pandas+Sklearn:实现用户聚类分群!
7000字,利用Python分析泰坦尼克数据!
导入库 数据基本信息 先把数据导进来:
df = pd.read_excel("data_air.xlsx" ) df.head()
查看数据的基本信息,包含:数据形状、字段类型等
# 字段类型 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' ]
具体字段的中文含义:
Date_of_Journey:旅客的旅行开始日期 Duration:持续时间;指的是航班完成从出发到目的地的旅程的整个时间 Additional_Info:其他信息,比如:食物、设备信息等 希望可以理解中文含义,帮助进行数据分析~
数值型字段的描述统计信息,这里主要是针对Price字段:
缺失值处理 通过上面的缺失值检查,我们看到有两个字段是存在缺失值的,进行可视化。
missingno是一个可视化缺失值的库,方便使用,我们可以用 pip install missingno
即可下载该库
import missingno as mso mso.bar(df,color="blue" ) plt.show()
缺失值删除 正常的字段是10683条,其中两个字段是10682条;缺失值的占比很小,考虑直接删除的数据
时间相关字段处理 时间处理
通过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()
最终生成了两个新的字段:
# 删除字段 df.drop('Date_of_Journey' , axis=1 , inplace=True )
起飞时间和抵达时间处理 主要提取两个时间中的“小时”和“分钟”信息;同时删除原字段:
分别调用函数来提取信息:
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()
航班持续时间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]
下面是个人版本写法:
2、从Duration字段中提取小时和分钟:
df.drop("Duration" ,inplace=True ,axis=1 )
3、字段类型转化:查看dur_hour和dur_minute的字段类型变化
字段编码 字段类型区分 主要是区分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 生成标称型字段组成的数据 不同字段编码处理 航空公司-Airline 1、不同航空公司的数量统计:
2、查看航空公司与价格关系
plt.figure(figsize=(15 ,8 )) sns.boxplot(x="Airline" , y="Price" , data=df.sort_values('Price' ,ascending=False ) ) plt.show()
3、2个明显的结论
从上面的图形中看出来:
Jet Airways Business公司的机票价格是最高的 4、实现独热编码
由于航空公司属性是一个标称数据的字段,我们进行独热编码,通过哑变量 的方式来实现:
停留地-Total_Stops 旅行期间的总共停留地,实施上面的同样操作:
1、和价格的关系
plt.figure(figsize=(15 ,8 )) sns.boxplot(x='Total_Stops' , y='Price' , data=df.sort_values('Price' ,ascending=False )) plt.show()
2、实施硬编码 ;区别于航空公司的独热编码
出发地source 出发地和价格的关系:
plt.figure(figsize=(18 ,12 )) sns.catplot(x='Source' , y='Price' , data=df.sort_values('Price' ,ascending=False ),kind='boxen' ) plt.show()
独热编码的过程:
目的地-destination 目的地的数量统计:
目的地和价格的关系:
独热编码的实现:
路线Route 1、不同路线的数量统计:
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()
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()
抵达时间/小时-Arrival_Time_hour 抵达目的地时间和价格的关系:
df.plot.hexbin(x='Arrival_Time_hour' , y='Price' , gridsize=15 ) plt.show()
建模数据 删除无效字段 生成的全部字段信息:
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()
离群点检测 对上面生成的最终数据进行离群点检测:
对离群点填充均值,查看填充后的效果:
final_df['Price' ]=np.where(final_df['Price' ]>=40000 , # 替换部分 final_df['Price' ].median(), # 替换数据 final_df['Price' ]) # 替换字段 plot(final_df, "Price" )
数据切分 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 )
评价指标 本次建模中引入3个评价指标:
from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_errordef 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 逻辑回归-LogisticRegression K近邻回归-KNeighborsRegressor 决策树回归DecisionTreeRegressor 支持向量机回归-SVR 梯度提升回归-GradientBoostingRegressor 模型调优-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)
多次运行调优后找到最佳的参数组合:
调优后结果 通过r2_score指标发现:进行参数调优后,模型的效果得到提升~
补充:如何理解回归模型的r2_score指标 假设我们用 表示数据真实的观测值,用 表示真实观测值的平均值 ,用 表示通过模型得到的预测值,则:
回归平方和:SSR SSR可以表示为;
即估计值与平均值的误差,反映自变量与因变量之间的相关程度的偏差平方和
残差平方和:SSE SSE可以表示为:
即估计值与真实值的误差,反映的是整个模型拟合程度
总离差平方和:SST R2_score计算公式 R^2 score,即决定系数,反映因变量的全部变异能通过回归关系被自变量解释的比例。计算公式:
也可以写成:
进一步可以转成:
此时分子就变成了我们常用的评价指标均方误差MSE ,分母就变成了方差Var 。r2_score在0-1之间,越接近1越好。
两种常见的求解r2的方式:
# 1、利用python间接求解 from sklearn.metrics import mean_squared_error1 - 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)