今天给大家分享第三个kaggle竞赛项目,纽约出租车价格预测New-York-City-Taxi-Fare-Prediction。这个项目的特点是给到我们的数据集比较大,有5.3G,数据总量是5400W行。不过我们在做这个项目的时候并不需要这么多的数据量,下面我们就一起来看一下这个项目。
Part1.数据导入和初步分析
首先导入我们的数据集,由于数据量过大,我们只导入前500W行的数据进行建模。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
train = pd.read_csv('train.csv',nrows=5000000)
test = pd.read_csv('test.csv')
test_ids = test['key']
train.head()
可以看到我们本次的数据特征量还是比较少的,虽然数据总量大,特征只有8个。
train.info()
key:索引
fare_amount:价格
pickup_datetime:出租车接到客人的时间
pickup_longitude:出发时的经度
pickup_latitude:出发时的纬度
dropoff_longitude:到达时的经度
dropoff_latitude:到达时的纬度
passenger_count:乘客的数目
train.describe()
红圈圈住的数据当中是异常值:负的价格,乘客数目最小为0,乘客数目最大为208
而横线划出来的数据令人疑惑:什么样的旅程会产生1270的出租费用呢?
Part2.数据分析
首先观察价格特征的分布:
train.fare_amount.hist(bins=100,figsize = (16,8))
plt.xlabel("Fare Amount")
plt.ylabel("Frequency")
train[train.fare_amount <100 ].fare_amount.hist(bins=100, figsize = (16,8))
plt.xlabel("Fare Amount")
plt.ylabel("Frequency")
train[train.fare_amount >=100 ].fare_amount.hist(bins=100, figsize = (16,8))
plt.xlabel("Fare Amount")
plt.ylabel("Frequency")
train[train.fare_amount <100].shape
train[train.fare_amount >=100].shape
由上面的代码和图我们可以得到几个结论:
1、价格的分布大多数是在100以内的,少部分在100以上
2、100以内的价格大多数集中在0~20之间
3、100以外的价格大多数集中在200附近,有几个比较大的价格可能是异常值,也可能是去往机场的价格。
接下来观察乘客数目的分布:
train.passenger_count.hist(bins=100,figsize = (16,8))
plt.xlabel("passenger_count")
plt.ylabel("Frequency")
train[train.passenger_count<10].passenger_count.hist(bins=10,figsize = (16,8))
plt.xlabel("passenger_count")
plt.ylabel("Frequency")
train[train.passenger_count<7].passenger_count.hist(bins=10,figsize = (16,8))
plt.xlabel("passenger_count")
plt.ylabel("Frequency")
train[train.passenger_count>7].passenger_count.hist(bins=10,figsize = (16,8))
plt.xlabel("passenger_count")
plt.ylabel("Frequency")
train[train.passenger_count >7]
train[train.passenger_count ==0].shape
plt.figure(figsize= (16,8))
sns.boxplot(x = train[train.passenger_count< 7].passenger_count, y = train.fare_amount)
train[train.passenger_count <7][['fare_amount','passenger_count']].corr()
由上面的代码和图我们可以得到几个结论:
1、人数的分布大多数都在7以内,少部分在7以外
2、人数为7以外的数据中大部分数据坐标有缺失且人数都为208
3、有17602个数据是乘客人数为0,有可能是运货的出租车,也有可能是数据的缺失
4、由箱型图可以看出,人数小于7的出租车平均价格都比较接近
5、使用.corr()接口查看passenger_count 与fare_amount的关联程度并不高只有0.013
Part3.数据处理
1、空值处理
train.isnull().sum()#找出空值
train = train.dropna(how='any', axis=0)
36个缺失值对于我们500W的数据量显得微不足道,所以我选择直接将缺失值去掉
test = pd.read_csv('test.csv')
test_ids = test['key']
test.head()
test.isnull().sum()
对测试集进行同样的处理,不过测试集并没有缺失值
2、异常值处理
train = train[train.fare_amount>=0]
将价格为负数的数据去除
3、特征工程
①缩短训练集的范围
由于训练集的数据量比较大,我们可以根据测试集的坐标范围来对训练集进行一定的缩减
print(min(test.pickup_longitude.min(),test.dropoff_longitude.min()))
print(max(test.pickup_longitude.max(),test.dropoff_longitude.max()))
print(min(test.pickup_latitude.min(),test.dropoff_latitude.min()))
print(max(test.pickup_latitude.max(),test.dropoff_latitude.max()))
得到了-74.2到-73作为经度的选取范围,40.5到41.8作为纬度的选取范围
def select_train(df, fw):
return (df.pickup_longitude >= fw[0]) & (df.pickup_longitude <= fw[1]) & \
(df.pickup_latitude >= fw[2]) & (df.pickup_latitude <= fw[3]) & \
(df.dropoff_longitude >= fw[0]) & (df.dropoff_longitude <= fw[1]) & \
(df.dropoff_latitude >= fw[2]) & (df.dropoff_latitude <= fw[3])
fw = (-74.2, -73, 40.5, 41.8)
train = train[select_train(train, fw)]
利用select_train将训练集数据进行缩减
②构造新的时间特征
原本的时间特征并不适合我们直接使用,考虑到出租车不同时间段,不同年份,月份都可能会提价,我们要在原本的时间特征中提取出新的年,月,日,时作为新的特征,供我们的模型使用。
def deal_time_features(df):
df['pickup_datetime'] = df['pickup_datetime'].str.slice(0, 16)
df['pickup_datetime'] = pd.to_datetime(df['pickup_datetime'], utc=True, format='%Y-%m-%d %H:%M')
df['hour'] = df.pickup_datetime.dt.hour
df['month'] = df.pickup_datetime.dt.month
df["year"] = df.pickup_datetime.dt.year
df["weekday"] = df.pickup_datetime.dt.weekday
return df
train = deal_time_features(train)
test = deal_time_features(test)
train.head()
处理后的时间特征由时,月,年,周几组成
③构造新的距离特征
直接使用经纬度坐标不利于我们的模型运转,我们用转换公式将经纬度坐标转化为距离
def distance(x1, y1, x2, y2):
p = 0.017453292519943295
a = 0.5 - np.cos((x2 - x1) * p)/2 + np.cos(x1 * p) * np.cos(x2 * p) * (1 - np.cos((y2 - y1) * p)) / 2
dis = 0.6213712 * 12742 * np.arcsin(np.sqrt(a))
return dis
train['distance_miles'] = distance(train.pickup_latitude,train.pickup_longitude,train.dropoff_latitude,train.dropoff_longitude)
test['distance_miles'] = distance(test.pickup_latitude, test.pickup_longitude,test.dropoff_latitude,test.dropoff_longitude)
train.head()
train[(train['distance_miles']==0)&(train['fare_amount']==0)]
构造完距离特征后,我们会发现还有15个距离和价格都为0的无用数据,可以将它删除
train = train.drop(index= train[(train['distance_miles']==0)&(train['fare_amount']==0)].index, axis=0)
④特殊处理
1、删除fare_amount小于2.5的数据,因为纽约出租车的起步价为2.5
train = train.drop(index= train[train['fare_amount'] < 2.5].index, axis=0)
2、去除人数大于7的数据
train[train.passenger_count >= 7]
train = train.drop(index= train[train.passenger_count >= 7].index, axis=0)
Part4.数据建模
看一下数据处理完后的最终样子
train.describe().T
利用.corr接口看下这些新的特征跟价格的关联
train.corr()['fare_amount']
进入建模的步骤:
df_train = train.drop(columns= ['key','pickup_datetime'], axis= 1).copy()
df_test = test.drop(columns= ['key','pickup_datetime'], axis= 1).copy()
#使用copy后的数据进行建模
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df_train.drop('fare_amount',axis=1)
,df_train['fare_amount']
,test_size=0.2
,random_state = 42)
#用train_test_split分出训练集和测试集
import xgboost as xgb
params = {
'max_depth': 7,
'gamma' :0,
'eta':0.3,
'subsample': 1,
'colsample_bytree': 0.9,
'objective':'reg:linear',
'eval_metric':'rmse',
'silent': 0
}
def XGBmodel(X_train,X_test,y_train,y_test,params):
matrix_train = xgb.DMatrix(X_train,label=y_train)
matrix_test = xgb.DMatrix(X_test,label=y_test)
model=xgb.train(params=params,
dtrain=matrix_train,
num_boost_round=5000,
early_stopping_rounds=10,
evals=[(matrix_test,'test')])
return model
model = XGBmodel(X_train,X_test,y_train,y_test,params)
#建模
prediction = model.predict(xgb.DMatrix(df_test), ntree_limit = model.best_ntree_limit)
prediction
#数据预测
res = pd.DataFrame()
res['key'] = test_ids
res['fare_amount'] = prediction
res.to_csv('submission.csv', index=False)
#结果保存
Part5.小结
我的做法只是比较简单的思路,因为出租车价格我能想到的有关价格的因素只有不同时间段,还有距离,这两个因素影响的程度会比较大。如果有其他更好的想法和做法的话,欢迎在讨论区里面留言告诉博主。
另外,在kaggle社区上有一种做法是构造一个新的特征,代表坐标到当地三个不同机场的距离,这种做法博主在未调参时直接去使用,结果提高了0.03,我觉得提高并不算太大,作用也与距离有点重合,所以我最后没有采用。这里也贴出来分享给大家。
# def transform(data):
# # Distances to nearby airports,
# jfk = (-73.7781, 40.6413)
# ewr = (-74.1745, 40.6895)
# lgr = (-73.8740, 40.7769)
# data['pickup_distance_to_jfk'] = distance(jfk[1], jfk[0],
# data['pickup_latitude'], data['pickup_longitude'])
# data['dropoff_distance_to_jfk'] = distance(jfk[1], jfk[0],
# data['dropoff_latitude'], data['dropoff_longitude'])
# data['pickup_distance_to_ewr'] = distance(ewr[1], ewr[0],
# data['pickup_latitude'], data['pickup_longitude'])
# data['dropoff_distance_to_ewr'] = distance(ewr[1], ewr[0],
# data['dropoff_latitude'], data['dropoff_longitude'])
# data['pickup_distance_to_lgr'] = distance(lgr[1], lgr[0],
# data['pickup_latitude'], data['pickup_longitude'])
# data['dropoff_distance_to_lgr'] = distance(lgr[1], lgr[0],
# data['dropoff_latitude'], data['dropoff_longitude'])
# return data
# train = transform(train)
# test = transform(test)