В Нью-Йорке проживает огромное количество людей, которые переезжают с места на место, а это означает, что поездки на такси составляют основную часть трафика. Почти половина жителей Нью-Йорка пользуется общественным транспортом, и, по оценкам, 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();