В Нью-Йорке проживает огромное количество людей, которые переезжают с места на место, а это означает, что поездки на такси составляют основную часть трафика. Почти половина жителей Нью-Йорка пользуется общественным транспортом, и, по оценкам, 54 процента не владеют автомобилем или личным транспортным средством. На самом деле ежегодно совершается 200 миллионов поездок на такси. Сумма такси, которую нам нужно будет заплатить, будет известна только после того, как мы прибудем в пункт назначения, и наша неловкость заставляет нас задуматься о том, как мы можем предсказать эту сумму, и точная оценка поможет нам контролировать наш бюджет.

Подробные исторические данные о поездках такси в Нью-Йорке можно получить в Комиссии по такси и лимузинам, которая охватывает более 1,1 миллиарда поездок. Она рассказывает историю Нью-Йорка — больше, чем просто список координат мест посадки и высадки такси.

В этом посте много всего, но если вам интересно самостоятельно проверить часть анализа, все в этом посте бесплатно. На GitHub вы найдете подробные инструкции по загрузке и анализу данные.

Данные

Загрузка и чтение данных

Набор данных можно загрузить из Комиссии по такси и лимузинам Нью-Йорка (TLC). Желтое такси имеет 18 полей столбцов, таких как время посадки и высадки, время посадки и высадки. -o местоположения, расстояния в пути, подробные тарифы, типы тарифов, типы оплаты, количество пассажиров, о которых сообщили водители, и более 6 миллионов строк за 2020 год в январе

import pandas as pd
import matplotlib.pyplot as plt
import scipy
import seaborn as sns
from sklearn import datasets
from PIL import Image
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Ridge 
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
import folium
import geopandas as gpd

В качестве образца берем 100000 строк

df = pd.read_csv('yellow_tripdata_2020-01.csv')
df.shape
(6405008, 18)
df_sample= df.sample(n = 1000000)

Давайте быстро посмотрим, как выглядят данные.

df_sample

df_sample.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1000000 entries, 6256628 to 486876
Data columns (total 18 columns):
 #   Column                 Non-Null Count    Dtype  
---  ------                 --------------    -----  
 0   VendorID               989879 non-null   float64
 1   tpep_pickup_datetime   1000000 non-null  object 
 2   tpep_dropoff_datetime  1000000 non-null  object 
 3   passenger_count        989879 non-null   float64
 4   trip_distance          1000000 non-null  float64
 5   RatecodeID             989879 non-null   float64
 6   store_and_fwd_flag     989879 non-null   object 
 7   PULocationID           1000000 non-null  int64  
 8   DOLocationID           1000000 non-null  int64  
 9   payment_type           989879 non-null   float64
 10  fare_amount            1000000 non-null  float64
 11  extra                  1000000 non-null  float64
 12  mta_tax                1000000 non-null  float64
 13  tip_amount             1000000 non-null  float64
 14  tolls_amount           1000000 non-null  float64
 15  improvement_surcharge  1000000 non-null  float64
 16  total_amount           1000000 non-null  float64
 17  congestion_surcharge   1000000 non-null  float64
dtypes: float64(13), int64(2), object(3)
memory usage: 145.0+ MB

мы разделили данные на три части: обучение, проверку и тестирование. С помощью scikit-learn он предоставляет несколько функций для разделения наборов данных на несколько подмножеств различными способами.

sample_train, sample_test = train_test_split(df_sample, test_size=0.2,random_state=0)
sample_test.shape
(200000, 18)
sample_train, sample_val = train_test_split(sample_train, test_size=0.25,random_state=0)
sample_val.shape
(200000, 18)
sample_train.shape
(600000, 18)
sample_train.head()

Пассажир не знает точно, как далеко он будет от начальной точки до конечной точки, поэтому расстояние (расстояние поездки), указанное в таблице, игнорируется. Использовались опорные координаты Нью-Йорка, геометрия из данных Taxi Zones в GeoJSON формат из NYC OpenData. OpenData использовался для определения местоположения каждой зоны, и было рассчитано расстояние между пунктом назначения и тройником.

df1 = gpd.read_file("NYC Taxi Zones.geojson")
df1.head(2)

Мы извлекли долготу и широту из столбцов геометрии с помощью геопанд.

df = gpd.read_file("NYC Taxi Zones.geojson")

df.head(2)

df["lon"] = df["geometry"].centroid.x
df["lat"] = df["geometry"].centroid.y
df.head(2)

Объединение данных долготы и широты для каждого местоположения

dfـPULocation = pd.read_csv('PULocation_lon_lat.cvs')
dfـPULocation.drop('Unnamed: 0', axis=1, inplace=True)
sample_train = pd.merge(sample_train,dfـPULocation, on='PULocationID')
sample_val = pd.merge(sample_val,dfـPULocation, on='PULocationID')
sample_test = pd.merge(sample_test,dfـPULocation, on='PULocationID')
df_DOLocationID = pd.read_csv('DOLocationID_lon_lat.cvs')
df_DOLocationID.drop('Unnamed: 0', axis=1, inplace=True)
sample_train = pd.merge(sample_train,df_DOLocationID, on='DOLocationID')
sample_val = pd.merge(sample_val,df_DOLocationID, on='DOLocationID')
sample_test =pd.merge(sample_test,df_DOLocationID, on='DOLocationID')

Подготовка данных для алгоритмов машинного обучения

Нулевые значения

sample_train.isnull().sum()
VendorID                 6003
tpep_pickup_datetime        0
tpep_dropoff_datetime       0
passenger_count          6003
trip_distance               0
RatecodeID               6003
store_and_fwd_flag       6003
PULocationID                0
DOLocationID                0
payment_type             6003
fare_amount                 0
extra                       0
mta_tax                     0
tip_amount                  0
tolls_amount                0
improvement_surcharge       0
total_amount                0
congestion_surcharge        0
PULocation_lon              0
PULocation_lat              0
DOLocationID_lon            0
DOLocationID_lat            0
dtype: int64

Похоже, что в выборке имеется незначительное количество нулевых записей, поэтому можно удалить их из выборки.

sample_train.dropna(inplace=True)
sample_val.dropna(inplace=True)
sample_test.dropna(inplace=True)

Выбросы, очистка и проектирование

sample_train.describe()

Статистический анализ обучающего набора показывает, что обучающий набор данных содержит некоторые выбросы. Наша модель не будет работать эффективно, если мы включим эти точки данных в обучающий набор, поэтому лучше их игнорировать.

Когда мы смотрим на статистическую сводку, мы обнаруживаем несколько вещей:

  • У нас отрицательные тарифы
  • Минимальная и максимальная долгота и широта выглядят нереально.
  • Минимальное количество пассажиров 0

Мы собираемся их исправить

  • Начальная стоимость такси составляет $2,5 по данным NYC.
  • Долгота Нью-Йорка составляет около -74, а широта около 41.
  • Удалить количество пассажиров 0
sample_train = sample_train.loc[sample_train['fare_amount']> 2.5]
sample_val = sample_val.loc[sample_val['fare_amount']> 2.5]
sample_test = sample_test.loc[sample_test['fare_amount']> 2.5]
sample_train = sample_train.loc[sample_train['PULocation_lat'].between(40,42)]
sample_train = sample_train.loc[sample_train['PULocation_lon'].between(-75,-72)]
sample_train = sample_train.loc[sample_train['DOLocationID_lat'].between(40,42)]
sample_train = sample_train.loc[sample_train['DOLocationID_lon'].between(-75,-72)]
sample_val = sample_val.loc[sample_val['PULocation_lat'].between(40,42)]
sample_val = sample_val.loc[sample_val['PULocation_lon'].between(-75,-72)]
sample_val = sample_val.loc[sample_val['DOLocationID_lat'].between(40,42)]
sample_val = sample_val.loc[sample_val['DOLocationID_lon'].between(-75,-72)]
sample_train = sample_train[sample_train['passenger_count'] >0]
sample_val =sample_val[sample_val['passenger_count'] >0]
sample_test =sample_test[sample_test['passenger_count'] >0]

В настоящее время Pandas рассматривает функцию «pickup_datetime» как тип объекта. Нам нужно преобразовать типы объектов в числовые типы, прежде чем включать их в нашу модель машинного обучения. Функция может быть преобразована в тип даты и времени, а тип даты и времени затем может быть преобразован в несколько атрибутов с использованием функций даты и времени Pandas. Из объекта даты и времени мы сможем создавать полезные атрибуты, такие как Год, Месяц, День, День недели, и час.

sample_train['tpep_pickup_datetime']=pd.to_datetime(sample_train['tpep_pickup_datetime'])
sample_train['tpep_dropoff_datetime']=pd.to_datetime(sample_train['tpep_dropoff_datetime'])
sample_val['tpep_pickup_datetime']=pd.to_datetime(sample_val['tpep_pickup_datetime'])
sample_val['tpep_dropoff_datetime']=pd.to_datetime(sample_val['tpep_dropoff_datetime'])
sample_train['pickup_day_no']=sample_train['tpep_pickup_datetime'].dt.weekday
sample_train['dropoff_day_no']=sample_train['tpep_dropoff_datetime'].dt.weekday
sample_val['pickup_day_no']=sample_val['tpep_pickup_datetime'].dt.weekday
sample_val['dropoff_day_no']=sample_val['tpep_dropoff_datetime'].dt.weekday
sample_train['pickup_hour']=sample_train['tpep_pickup_datetime'].dt.hour
sample_train['dropoff_hour']=sample_train['tpep_dropoff_datetime'].dt.hour
sample_val['pickup_hour']=sample_val['tpep_pickup_datetime'].dt.hour
sample_val['dropoff_hour']=sample_val['tpep_dropoff_datetime'].dt.hour
sample_train['pickup_timeofday']=sample_train['pickup_hour'].apply(time_of_day)
sample_train['dropoff_timeofday']=sample_train['dropoff_hour'].apply(time_of_day)
sample_val['pickup_timeofday']=sample_val['pickup_hour'].apply(time_of_day)
sample_val['dropoff_timeofday']=sample_val['dropoff_hour'].apply(time_of_day)

Вычисление расстояния по большому кругу между двумя точками на Земле (указанного в десятичных градусах), her вы найдете дополнительную информацию о расчете расстояния между двумя точками на Земле с помощью Python.

plt.figure(figsize=(8,5))
plt.title('difference between trip Distances and  Calculation actual distance')
comparison_column = pd.Series(np.where(sample_train["trip_distance"] == sample_train["distance"], True, False))
sns.kdeplot(data=sample_train['trip_distance'],label = "trip_distance",shade=True )
sns.kdeplot(data=sample_train['distance'],label = "distance",shade=True )
plt.legend();import numpy as np

def haversine_np(lon1, lat1, lon2, lat2):

    lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2

    c = 2 * np.arcsin(np.sqrt(a))
    km = 6367 * c
    return km
sample_train['distance'] = haversine_np(sample_train['PULocation_lon'],sample_train['PULocation_lat'],
                                    sample_train['DOLocationID_lon'],sample_train['DOLocationID_lat'] )
sample_val['distance'] = haversine_np(sample_val['PULocation_lon'],sample_val['PULocation_lat'],
                                    sample_val['DOLocationID_lon'],sample_val['DOLocationID_lat'] )
plt.figure(figsize=(8,5))
plt.title('difference between trip Distances and  Calculation actual distance')
comparison_column = pd.Series(np.where(sample_train["trip_distance"] == sample_train["distance"], True, False))
sns.kdeplot(data=sample_train['trip_distance'],label = "trip_distance",shade=True )
sns.kdeplot(data=sample_train['distance'],label = "distance",shade=True )
plt.legend();

В связи с тем, что погода влияет на расстояния во время поездок, а также на продолжительность времени, мы добавили данные о погоде за тот же период в январе 2020 года. Мы будем использовать данные о погоде из Центрального парка и Национального центра климатических данных. strong> для наших данных о погоде.

data_weather = pd.read_csv('weather20201.csv')
data_weather.rename(columns={'DATEday': 'pickup_day_no'}, inplace=True)
data_weather.head(2)

sample_train = pd.merge(sample_train,data_weather, on='pickup_day_no')
sample_val = pd.merge(sample_val,data_weather, on='pickup_day_no')

визуализация

Сумма тарифа

### Fare amountplt.figure(figsize=(14,4))
plt.hist(sample_train["fare_amount"],250)
plt.xlabel('Fare Amount')
plt.ylabel('Count')
plt.title('Histogram of Fare Amount')
plt.xlim(0,100);
plt.savefig('Histogram_Fare Amount.eps', format='eps')

Тарифы, которые наиболее распространены, очень маленькие, всего 6,5 и 4,5, что указывает на то, что это были очень короткие поездки.

Точечная диаграмма расстояния и суммы тарифа

plt.figure(figsize=(8,5))
plt.scatter(sample_train['distance'],sample_train['fare_amount'], alpha=0.2)
plt.xlabel('distance mils')
plt.ylabel('Fare $USD')
plt.title('Scatter Plot distance vs Fare amount');

Мы видим, что существует положительная корреляция между расстоянием и ценой.

day= sample_train["pickup_day_no"].copy()
day = day.astype(np.str)
day=day.replace({'0':'Monday', '1': 'Tuesday' , '2':'Wednesday', '3': 'Thursday' 
                 , '4': 'Friday', '5':'Saturday' , '6':'Sunday'},regex=True)


sample_train.pivot_table('fare_amount', index='pickup_hour', columns=day).plot(figsize=(10,5))
plt.xlabel('pickup hour');
plt.ylabel('Fare Amount');
plt.title('Fare amount for trips in every hour');

как мы видим Самая высокая цена за поездку в 5 утра

Самый посещаемый район

#Q1 : Where is the most visited area? 
location= sample_train["DOLocationID"]
location = location.astype(np.str)
location=location.replace({'236':'Upper East Side North', '161': 'Midtown Center' , '237':'Upper East Side South', '170': 'Murray Hill' 
                 , '162': 'Midtown East', '230':'Times Sq/Theatre District' , '142':'Lincoln Square East', '234': 'Union Sq'
                           , '186':'Penn Station/Madison Sq West' , '48':'Clinton East', '239':'Upper West Side South'},regex=True)
sample_train["location_name"] = location
n = 1
most_famous_Location= sample_train['location_name'].value_counts()[:n].index.tolist()
print('The most visited area is :',most_famous_Location)
# visulatiziton Q1 :
m=10
plt.figure(figsize=(10,8))
sample_train['location_name'].value_counts()[:m].plot.bar()
plt.xlabel("Area code  ")
plt.ylabel("The most visited area ")
plt.title('The most visited area ', fontsize=16)
#plt.savefig('mostـvisitedـarea.eps', format='eps')
plt.show();
The most visited area is : ['Upper East Side North']

Самый востребованный день для поездок

day= sample_train["pickup_day_no"].copy()
day = day.astype(np.str)
day=day.replace({'0':'Monday', '1': 'Tuesday' , '2':'Wednesday', '3': 'Thursday' 
                 , '4': 'Friday', '5':'Saturday' , '6':'Sunday'},regex=True)
# Q2 When is the most requested day for trips?
n=1
most_requested_day= day.value_counts()[:n].index.tolist()
print('The most requested day for trips is :',most_requested_day)
# visulatiziton Q2 :
plt.figure(figsize=((10,8)))
day.value_counts().plot.bar()
plt.xticks(rotation=50)
plt.xlabel("pickup Day ")
plt.ylabel("The most requested day")
plt.title('The most requested day for trips ', fontsize=16);
plt.savefig('mostـrequested_day.eps', format='eps')
plt.show();
The most requested day for trips is : ['Friday']

Количество пикапов за каждый день недели

def compute_pickups_per_day():
    time = sample_train[['tpep_pickup_datetime','tpep_dropoff_datetime']]
    time['tpep_pickup_datetime'] = pd.to_datetime(time['tpep_pickup_datetime'])
    time["pickup_day"] = time['tpep_pickup_datetime'].dt.strftime('%u').astype(int)
    time["pickup_hour"] = time['tpep_pickup_datetime'].dt.strftime('%H').astype(int)
    set(time['pickup_day'].values)
    day_pickups = []
    night_pickups = []
    mid_night_pickups = []
    afternoon_pickups = []
    
    for i in range(1,8):
     
        day_pickups.append(time[(time.pickup_day == i) & (time.pickup_hour >= 6) & (time.pickup_hour < 12)].shape[0])
        night_pickups.append(time[(time.pickup_day == i) & (time.pickup_hour >= 18) & (time.pickup_hour <= 23)].shape[0])
        mid_night_pickups.append(time[(time.pickup_day == i) & (time.pickup_hour >= 0) & (time.pickup_hour < 6)].shape[0])
        afternoon_pickups.append(time[(time.pickup_day == i) & (time.pickup_hour >= 12) & (time.pickup_hour < 18)].shape[0])
    days2 =pd.DataFrame({'morning':day_pickups, 'afternoon':afternoon_pickups, 'night':night_pickups, 'midnight':mid_night_pickups})
    days2['day']= ['Monday', 'Tuesday' ,'Wednesday',  'Thursday' ,'Friday', 'Saturday' , 'Sunday']
    days2.set_index('day', inplace=True)
    days2.plot(kind='bar', stacked=True,figsize=(15, 10))
    plt.title('Number of pickups for every Days of the week')
    plt.xlabel('Days of the week')
    plt.ylabel('Number of pickups')
    plt.tick_params(axis='both', which='minor')
    plt.plot()
# Q3 :When is the most requested Time for trips?
times_of_day_dframe = compute_pickups_per_day()

Большинство рейсов совершается в полночь по субботам. Интересно, что в полночь происходит больше пересадок по сравнению с воскресеньем, чем в субботу. Полуночные поездки по субботам самые высокие, а ночные рейсы в пятницу, кажется, самые высокие (пятничные вечеринки).

Наиболее часто используемый способ оплаты

pyment= sample_train["payment_type"].copy()
pyment = pyment.astype(np.int)
pyment = pyment.astype(np.str)
pyment=pyment.replace({'1':'Credit card', '2': 'Cash' , '3':'No charge', '4': 'Dispute'},regex=True)
sns.set_style("whitegrid")
plt.figure(figsize=(15,10))
Most_payment_method= pyment.value_counts()[:n].index.tolist()
print('The Most used payment method is :',Most_payment_method)
pyment.value_counts().plot.bar()
plt.xticks(rotation=50)
plt.xlabel("Method code")
plt.ylabel(" Most used payment method")
plt.title('The Most used payment method  ', fontsize=16);
plt.show();
The Most used payment method is : ['Credit card']

Количество пикапов для каждого типа количества пассажиров

sns.set_style("whitegrid")
fig, ax = plt.subplots()
fig.set_size_inches(12, 5)
sample_train['passenger_count'].value_counts().plot.bar()
plt.title('Number of pickups for every type of passenger count')
plt.xlabel('number of passengers')
plt.ylabel('Pickups');
plt.savefig('Number_pickups_passenger_count.eps', format='eps')

Корреляция

plt.figure(figsize=(12, 10))
sns.heatmap(sample_train.corr());

df_num_corr = sample_train.corr()['fare_amount'][:-1] # -1 because the latest row is SalePrice
golden_features_list = df_num_corr[abs(df_num_corr) > 0.5].sort_values(ascending=False)
print("There is {} strongly correlated values with SalePrice:\n{}".format(len(golden_features_list), golden_features_list))
There is 7 strongly correlated values with SalePrice:
fare_amount       1.000000
total_amount      0.976422
trip_distance     0.959899
distance          0.930520
tolls_amount      0.628549
tip_amount        0.571544
PULocation_lon    0.553054
Name: fare_amount, dtype: float64

Модель линейной регрессии

в линейной регрессии отношения моделируются с использованием функций линейного предиктора, неизвестные параметры модели которых оцениваются на основе данных. Такие модели называются линейными моделями.

Обучение и оценка на тренировочном наборе

Как мы видим, существует сильная корреляция между суммой проезда и общей суммой, суммой дорожных сборов, суммой чаевых; однако при использовании этих функций для обучения алгоритма машинного обучения мы столкнулись с утечками данных, поскольку данные содержат информацию, которую мы пытаемся предсказать.

Чтобы предотвратить утечку данных, важно извлечь правильные функции для модели. Эти функции не должны коррелировать с заданным выходным значением и не должны содержать информацию о выходе, которая недоступна во время прогнозирования.

Исходя из вышеизложенного, атрибутами, влияющими на стоимость проезда, являются фактическое расстояние, день, час, пробки, погодные условия, и мы будем использовать эти функции для обучения алгоритма машинного обучения.

columns_train = sample_train[['congestion_surcharge','distance','pickup_day_no','pickup_hour'
              ,'AWND','PRCP','SNOW','SNWD','TMAX','TMIN','work_hour','work_day']]
columns_val = sample_val[['congestion_surcharge','distance','pickup_day_no','pickup_hour'
              ,'AWND','PRCP','SNOW','SNWD','TMAX','TMIN','work_hour','work_day']]
X_train,y_train = columns_train, sample_train['fare_amount']
lr_model = LinearRegression()
lr_model.fit(X_train, y_train)
lr_model.score(X_train,y_train)
0.8683767225955492

Оценка набора проверки

X_val,y_val = columns_val, sample_val['fare_amount']
lr_model.score(X_val,y_val)
0.8593610272341413

Полиномиальная регрессия

мы использовали линейную модель, чтобы соответствовать нелинейным данным. Простой способ сделать это — добавить возможности каждой функции в качестве новых функций, а затем обучить линейную модель на этом расширенном наборе функций. Этот метод называется полиномиальной регрессией.

degree = 2
est = make_pipeline(PolynomialFeatures(degree), LinearRegression())
est.fit(X_train, y_train)
print('\ntrain R^2 score was:',est.score(X_train,y_train)) 
print('\nval R^2 score was:', est.score(X_val,y_val))
train R^2 score was: 0.8918236750559047

val R^2 score was: 0.8909233441476533

График ошибки прогноза

На графике ошибки прогнозирования фактические цели из набора данных отображаются в сравнении с прогнозами, сгенерированными нашей моделью.

from yellowbrick.regressor import prediction_error
visualizer = prediction_error(est, X_train, y_train, X_test, y_test)
visualizer.show();

График остатков

Используя визуализатор ResidualsPlot Visualizer от Yellowbrick, вы можете увидеть разницу между остатками (наблюдаемое значение прогнозируемого значения целевой переменной) на оси Y (вертикальной) и зависимой переменной на оси X (горизонтальной).

from yellowbrick.regressor import ResidualsPlot
visualizer = ResidualsPlot(make_pipeline(PolynomialFeatures(degree), LinearRegression()))
visualizer.fit(X_train, y_train)
visualizer.score(X_test, y_test)
visualizer.show();