天天看点

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

看了唐国梁老师视频讲的《机器学习实战 纽约出租车车费预测》受益匪浅,这一案例相对简单,适合初学者研究学习。

纽约出租车车费预测的背景是基于过去7年出租车行驶和收费的记录,预测未来某个时间点打车的车费,包括train.csv(训练集)、test.csv(测试集)、sample_submission.csv。

train.csv共1048576条数据,有key、fare_amount、pickup_datetime、pickup_longitude、pickup_latitude、dropoff_longitude、dropoff_latitude、passenger_count八个字段。

test.csv共9914条数据,有key、pickup_datetime、pickup_longitude、pickup_latitude、dropoff_longitude、dropoff_latitude、passenger_count七个字段。test.csv与train.csv相比少了fare_amount字段,该字段正是本次文章的目标-预测测试集数据中的车费。

sample_submission.csv只有key、fare_amount两个字段,共9914条数据。sample_submission.csv的意义是将测试集中预测出的车费赋给fare_amount展示。

背景和文档交代清楚了,接下来要考虑的是如何进行代码的编写了。代码编写逻辑如下:加载数据集,数据分析、清洗,模型训练和数据预测。其中数据分析、清洗是要结合业务进行深入的分析、筛选,也是本篇文章内容讲解的重点。

现在正式进行代码的编写,首先导入本次代码逻辑实现需要用到的库,如下:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import sklearn           

第一步:加载数据集

因为train.csv有1048576条数据,所以用nrow获取1000000条数据。

train = pd.read_csv("train.csv", nrows=1000000) # 加载训练集           
test = pd.read_csv("test.csv") # 加载测试集           

第二步:数据分析、清洗

train.shape # 训练集的形状           

执行上面的代码会获得1000000条数据,8个字段的数据统计

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape运行结果示意图

test.shape # 测试集的形状           

执行上面的代码会获得9914条数据,7个字段的数据统计

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

test.shape运行结果示意图

train.head() # 默认显示前5行训练集数据           

运行train.head()会获得前5条数据,分析这五条数据初步可知:1、key和pickup_datetme前半部分的数据相同,表示的上车时间;2、上车经度和下车经度在73~74之间,上车纬度和下车纬度集中在40之间;3、正常情况下乘客总数≥1;4、票价在4~17元之间

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.head()运行结果示意图

test.head() # 默认显示前5行测试集数据           

运行test.head()得到test.csv的结果与train.csv的结果一致,唯一的区别就是没有fare_amount字段。

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

test.head() 运行结果示意图

train.describe() # 训练集描述           

看train.descibe()的运行结果,发现了几处异常:1、车费的最小值是-44.9,顾客坐了出租车还要倒贴给顾客钱,不合情理;2、经度范围:-180~180,维度范围:-90~90。看上车经纬度和下车经纬度的最大值和最小值都超出了范围;3、乘客总数的最大值竟然是208。这几个异常会通过接下来的逻辑处理清洗掉。

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.describe()运行结果示意图

test.describe() # 测试集描述           

看test.csv的数据稍微好些,经纬度在正常范围内,不过乘客总数的最大值是6是有问题的。

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

test.describe()运行结果示意图

以上是对数据的查看、分析,现在开始对数据进行清洗

1 检查数据中是否有空值

train.isnull().sum().sort_values(ascending=True) # 统计空值的数量,根据数量大小排序           

训练集的下车经纬度有10条空值

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

训练集空值数量统计示意图

test.isnull().sum().sort_values(ascending=True) # 统计空值的数量,根据数量大小排序           

测试集的数据正常,没有空值

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

测试集空值数量统计示意图

# 删除掉train中为空的数据(10行)
train.drop(train[train.isnull().any(axis=1)].index, axis=0, inplace=True)           

train.isnull()指的是查出空值数据,any(axis=1)指的是将查出空值数据的 这一列,以行为基线从左到右删除这一行,以上的理解是查阅了相关资料后得出,到现在还是有一些疑惑,欢迎大佬指正。train.drop(train[train.isnull().any(axis=1)].index就是根据索引获取该条数据并删掉。

axis=0表示一行;inplace=True执行后会在代码中生效,后续得到的数据都会是删除空数据后的数据。

train.shape # 比原始数据少了10行           

运行train.shape,训练集中已经将含有下车经纬度为空的10条数据删掉

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape运行示意图

2 检查车费fare_amount这一列

#查看车费这列的数据(车费不可能为负数)
train['fare_amount'].describe()           

前面分析过,车费最小值是负数,这次针对车费单独描述,运行结果表明还是车费最小值有问题

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train['fare_amount'].describe()运行结果示意图

# 统计train中车费小于0的数据有多少
#引入collections
from collections import Counter           
Counter(train['fare_amount']<0) 
# 有38行数据,车费小于0
#运行结果:车费大于0的是False,车费小于0的是True           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

Counter(train['fare_amount']<0)运行结果示意图

# 删除掉车费小于0的数据
train.drop(train[train['fare_amount']<0].index, axis=0, inplace=True)           
train['fare_amount'].describe() # 查看车费数据           

结果展示正常

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train['fare_amount'].describe() 运行结果示意图

# 可视化(直方图):0 < 票价 < 100
train[train.fare_amount<100].fare_amount.hist(bins=100, figsize=(14,3))
plt.xlabel('fare $USD')
plt.title("Histogram")           

看结果,0~20元的车费最多

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

0~100元票价运行结果示意图

3 检查乘客passenger_count这一列

# 检查乘客列的数据
train['passenger_count'].describe()           

从获得的结果可知,乘客总数最大值208人有问题,包括司机,出租车最多能坐5人

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train['passenger_count'].describe()代码运行示意图

个人理解乘客总数最多是4,代码是判断乘客人数是否大于6,大于6就是异常数据,这里按照老师讲的视频来吧

# 查看乘客人数大于6的数据
train[train['passenger_count']>6]           

运行代码发现了一条大于6的异常数据

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train[train['passenger_count']>6]运行结果示意图

# 删除这个离异值
train.drop(train[train['passenger_count']>6].index, axis=0, inplace=True)           

4 检查上车点的经度和维度

1.经度范围:-180~180

2.维度范围:-90~90

train['pickup_latitude'].describe() #查看上车点维度数据(min和max的值异常)           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

上车维度数据描述

# 维度小于-90的数据(有3行)
train[train['pickup_latitude']<-90]           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

rain[train['pickup_latitude']<-90]运行结果示意图

# 经度大于90的数据(有9行)
train[train['pickup_latitude']>90]           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train[train['pickup_latitude']>90]运行结果示意图

#  删除这些离异值数据
train.drop(train[(train['pickup_latitude']<-90) | (train['pickup_latitude']>90)].index, axis=0, inplace=True)           
train.shape #查看训练集数据           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape代码运行结果示意图

# 查看上车点经度数据(min值异常)
train['pickup_longitude'].describe()           

看运行结果,上车经度最小值是有问题的

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train['pickup_longitude'].describe()代码运行结果示意图

# 查看经度小于-180的数据
train[train['pickup_longitude']<-180]           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train[train['pickup_longitude']<-180]代码运行结果示意图

# 删除这些离异值
train.drop(train[train['pickup_longitude']<-180].index, axis=0, inplace=True)           
train.shape           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape代码运行结果示意图

5 检查下车点的经度和维度

# 删除那些维度小于-90,大于90的数据
train.drop(train[(train['dropoff_latitude']<-90) | (train['dropoff_latitude']>90)].index, axis=0, inplace=True)           
# 删除掉那些经度小于-180,大于180的数据
train.drop(train[(train['dropoff_longitude']<-180)|(train['dropoff_longitude']>180)].index, axis=0,inplace=True)           
train.shape           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape代码运行结果示意图

6 可视化地图,清理一些离异值

这块的代码逻辑感觉很重要,做出来的图也很炫

# 1 在test数据集上确定一个区域框,删除掉train数据集中不在区域框内的奇异点

# (1) 维度最小值,维度最大值
min(test.pickup_latitude.min(), test.dropoff_latitude.min()), \
max(test.pickup_latitude.max(), test.dropoff_latitude.max())           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

确定上车、下车维度范围

# (2) 经度最小值,经度最大值
min(test.pickup_longitude.min(), test.dropoff_longitude.min()), \
max(test.pickup_longitude.max(), test.dropoff_longitude.max())           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

确定上车、下车经度范围

# (3) 根据指定的区域框,除掉那些奇异点
#该方法通过确定的经纬度范围,获取经纬度范围内的数据,不符合的数据筛选出去
def select_within_boundingbox(df, BB):
    return (df.pickup_longitude >= BB[0]) & (df.pickup_longitude <= BB[1]) & \
            (df.pickup_latitude >= BB[2]) & (df.pickup_latitude <= BB[3]) & \
            (df.dropoff_longitude >= BB[0]) & (df.dropoff_longitude <= BB[1]) & \
            (df.dropoff_latitude >= BB[2]) & (df.dropoff_latitude <= BB[3])           
# 将经纬度的最大值、最小值赋予变量BB
BB = (-74.5, -72.8, 40.5, 41.8)

#截图
#视频中访问的是一个网址,网址加载图片很慢,直接访问会报错。这里直接加载下载好的图片
nyc_map = plt.imread('map_normal.jpg')           
BB_zoom = (-74.3, -73.7, 40.5, 40.9) # 放大后的地图,将最小值变小,最大值变大
# 截图(放大)
nyc_map_zoom = plt.imread('map_enlarge.jpg')           
train = train[select_within_boundingbox(train, BB)] # 删除区域之外的点           
train.shape           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.shape代码运行结果示意图

# (4) 在地图显示这些点
def plot_on_map(df, BB, nyc_map, s=10, alpha=0.2):
    fig,axs = plt.subplots(1, 2, figsize=(16, 10))
    # 第一个子图
    axs[0].scatter(df.pickup_longitude, df.pickup_latitude, alpha=alpha, c='r', s=s)
    axs[0].set_xlim(BB[0], BB[1])
    axs[0].set_ylim(BB[2], BB[3])
    axs[0].set_title('PickUp Locations')
    axs[0].imshow(nyc_map, extent=BB)
    
    # 第二个子图
    axs[1].scatter(df.dropoff_longitude, df.dropoff_latitude, alpha=alpha, c='r', s=s)
    axs[1].set_xlim((BB[0], BB[1]))
    axs[1].set_ylim((BB[2], BB[3]))
    axs[1].set_title('Dropoff locations')
    axs[1].imshow(nyc_map, extent=BB)           
# 将训练集数据、经纬度范围传入plot_on_map方法中运行
plot_on_map(train, BB, nyc_map, s=1, alpha=0.3)           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

上车、下车经纬度范围示意图

#将训练集数据、放大后的经纬度范围传入plot_on_map方法中运行
plot_on_map(train, BB_zoom,nyc_map_zoom, s=1, alpha=0.3)           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

放大后的上车、下车经纬度范围示意图

7 检查数据类型

train.dtypes #查看训练集的数据类型           

运行结果显示key、pickup_datetime是字符串类型

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

训练集数据类型示意图

# 日期类型转换:key, pickup_datetime

for dataset in [train, test]:
    dataset['key'] = pd.to_datetime(dataset['key'])
    dataset['pickup_datetime'] = pd.to_datetime(dataset['pickup_datetime'])           

8 日期数据进行分析

1.year

2.month

3.day

4.hour

5.day of week

# 增加5列,分别是:year, month, day, hour, day of week

for dataset in [train, test]:
    dataset['year'] = dataset['pickup_datetime'].dt.year
    dataset['month'] = dataset['pickup_datetime'].dt.month
    dataset['day'] = dataset['pickup_datetime'].dt.day
    dataset['hour'] = dataset['pickup_datetime'].dt.hour
    dataset['day of week'] = dataset['pickup_datetime'].dt.dayofweek           
train.head()           

看运行结果,每行多了五列数据,分别是year、month、day、hour、day of week

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train.head()代码运行结果示意图

test.head()           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

test.head()代码运行结果示意图

9 根据经纬度计算距离

# 计算公式

def distance(lat1, long1, lat2, long2):
    data = [train, test]
    for i in data:
        R = 6371 # 地球半径(单位:千米)
        phi1 = np.radians(i[lat1])
        phi2 = np.radians(i[lat2])
        
        delta_phi = np.radians(i[lat2]-i[lat1])
        delta_lambda = np.radians(i[long2]-i[long1])
        
        #a = sin²((φB - φA)/2) + cos φA . cos φB . sin²((λB - λA)/2)
        a = np.sin(delta_phi / 2.0) ** 2 + np.cos(phi1) * np.cos(phi2) * np.sin(delta_lambda / 2.0) ** 2
        
        # c = 2 * atan2( √a, √(1-a))
        c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))
        
        # d = R*c
        d = (R * c) # 单位:千米
        i['H_Distance'] = d
        
     return d           

说实话,distance()方法用到的具体数学原理我也没看懂,抽空需要好好学习了解下。distance()方法经过一系列逻辑计算后,将上车下车的距离赋值给i['H_Distance'],因为是for循环,i代表训练集和测试集,所以训练集、测试集都多了一个字段H_Distance。

此外,该方法将上下车距离返回。

distance('pickup_latitude','pickup_longitude', 'dropoff_latitude', 'dropoff_longitude')           

调用此方法,返回了第二次遍历的测试集的每条出租车行驶的距离。

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

测试集每条出租车行驶距离数据

train.head() #多了一个字段H_Distance           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

训练集每条出租车行驶距离数据

test.head() #多了一个字段H_Distance
           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

测试集每条出租车行驶距离数据

# 统计距离为0, 票价为0的数据
train[(train['H_Distance']==0) & (train['fare_amount']==0)]           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

距离为0,票价为0的数据

# 删除
train.drop(train[(train['H_Distance']==0) & (train['fare_amount']==0)].index, axis=0, inplace=True)           
# 统计距离为0,票价不为0的数据

# 原因1:司机等待乘客很长时间,乘客最终取消了订单,乘客依旧支付了等待的费用;
# 原因2:车辆的经纬度没有被准确录入或缺失;

len(train[(train['H_Distance']==0) & (train['fare_amount']!=0)]) #运行代码得到的结果是10478           
# 删除
train.drop(train[(train['H_Distance']==0) & (train['fare_amount']!=0)].index, axis=0, inplace=True)           

10 新的字段:每公里车费:根据距离、车费,计算每公里的车费

train['fare_per_mile'] = train.fare_amount / train.H_Distance
train.fare_per_mile.describe()           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

fare_per_mile数据描述示意图

train.head() #代码运行后,多了fare_per_mile列           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

train_head()代码运行结果示意图

# 统计每一年的不同时间段的每小时车费

train.pivot_table('fare_per_mile', index='hour', columns='year').plot(figsize=(14, 6))
plt.ylabel('Fare $USD/mile')           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

每一年的不同时间段的每小时车费统计结果示意图

第3步:模型训练和数据预测

train.columns #查看训练集的每个字段           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

训练集包含的字段

X_train = train.iloc[:,[3,4,5,6,7,8,9,10,11,12,13]] #获取训练集中3~13字段中的所有数据           
y_train = train.iloc[:, [1]] # 获取fare_amount(车费)这一列的所有数据            
y_train           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

训练集中每一列车费示意图

X_train.shape   #代码运行后,得到X_train的模型(968537, 11),即有968537条数据,每条数据有11个字段           
y_train.shape #代码运行后,得到y_train的模型(968537,1),即有968537条数据,每条数据有1个字段           

注意:从下面的代码运行开始,涉及到代码的训练、预测。因为数据量比较大,因此得到结果的时间会较长,需耐心等待

# 随机森林实现

from sklearn.ensemble import RandomForestRegressor

rf = RandomForestRegressor()
rf.fit(X_train, y_train)           

上面的代码运行完后会返回RandomForestRegressor(),这个方法的原理还不是很明白,后续也是要花时间学习其原理的。

test.columns #查看测试集包含的每个字段           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

测试集包含的字段示意图

rf_predict = rf.predict(test.iloc[:, [2,3,4,5,6,7,8,9,10,11,12]]) 
#1.test.iloc[:, [2,3,4,5,6,7,8,9,10,11,12]]获取测试集2~12列所有行的数据
#2.rf为通过随机森林算法将训练集训练好的模型,rf.predict()方法直接调用test.iloc进行预测           
submission = pd.read_csv("sample_submission.csv") #读取sample_submission.csv           
submission.head()           

submission结果显示:fare_amount那一列为11.35.下面的代码就是将随机森林对测试集预测的票价赋值给submission中的fare_amount。

对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

sample_submission.csv前5条数据展示示意图

# 提交
submission = pd.read_csv("sample_submission.csv")
submission['fare_amount'] = rf_predict
submission.to_csv("submission_1.csv", index=False)
submission.head()           
对《机器学习实战 纽约出租车车费预测代码详解》的理解,含源码

测试集票价的预测结果

至此,出租车票价预测讲完了。大家一起学习,欢迎各位大佬交流、指正,谢谢!

源码下载地址:

#头条创作挑战赛#

继续阅读