Привет еще раз, меня зовут Люман Аблаев. Сегодня я проверю твой проект.
Дальнейшее общение будет происходить на "ты" если это не вызывает никаких проблем.
Желательно реагировать на красные комментарии ('исправил', 'не понятно как исправить ошибку', ...)
Пожалуйста, не удаляй комментарии ревьюера, так как они повышают качество повторного ревью.
Комментарии будут в зеленой, синей или красной рамках:
Будет очень хорошо, если ты будешь помечать свои действия следующим образом:
Общее впечатление
- Спасибо за очередную качественную работу - видно, что приложено много усилий,
- Я оставил некоторые советы, надеюсь они будут полезными и интересными
- Есть некоторые недочеты, которые нужно поправить, но у тебя это не должно занять много времени)
- Жду обновленную работу
Общее впечатление[3]
- Спасибо за усердность!
- Было приятно с тобой сотрудничать.
- Недочеты исправлены - работа полностью корректна
- Не буду больше задерживать, продолжай в том же духе.
Описание проекта¶
Сервис по продаже автомобилей с пробегом «Не бит, не крашен» разрабатывает приложение, чтобы привлечь новых клиентов. В нём можно будет узнать рыночную стоимость своего автомобиля. Постройте модель, которая умеет её определять. В вашем распоряжении данные о технических характеристиках, комплектации и ценах других автомобилей. Критерии, которые важны заказчику: качество предсказания; время обучения модели; время предсказания модели.
Заказчику важны:
- качество предсказания;
- скорость предсказания;
- время обучения.
Подготовка данных¶
Загрузка данных¶
import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OrdinalEncoder, StandardScaler
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import GridSearchCV
import lightgbm as lgb
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.dummy import DummyRegressor
from sklearn.tree import DecisionTreeRegressor
import warnings
Загрузим данные получим первую информацию.
try:
df = pd.read_csv('c:/users/norfolk/documents/python/datasets/autos.csv')
except:
df = pd.read_csv('/datasets/autos.csv')
df.head()
size_a = len(df)
Пожалуйста предусмотри подгрузку данных на платформе, чтобы на платформе код отрабатывал без моего вмешательства
Успех[2]: 👍
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 354369 entries, 0 to 354368 Data columns (total 16 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 DateCrawled 354369 non-null object 1 Price 354369 non-null int64 2 VehicleType 316879 non-null object 3 RegistrationYear 354369 non-null int64 4 Gearbox 334536 non-null object 5 Power 354369 non-null int64 6 Model 334664 non-null object 7 Kilometer 354369 non-null int64 8 RegistrationMonth 354369 non-null int64 9 FuelType 321474 non-null object 10 Brand 354369 non-null object 11 Repaired 283215 non-null object 12 DateCreated 354369 non-null object 13 NumberOfPictures 354369 non-null int64 14 PostalCode 354369 non-null int64 15 LastSeen 354369 non-null object dtypes: int64(7), object(9) memory usage: 43.3+ MB
print(f'Дубликаты в датафрейме: {df.duplicated().sum()}')
df = df.drop_duplicates()
print(f'Дубликатов в датафрейме после удаления: {df.duplicated().sum()}')
Дубликаты в датафрейме: 4 Дубликатов в датафрейме после удаления: 0
df.isna().sum()
DateCrawled 0 Price 0 VehicleType 37490 RegistrationYear 0 Gearbox 19833 Power 0 Model 19705 Kilometer 0 RegistrationMonth 0 FuelType 32895 Brand 0 Repaired 71154 DateCreated 0 NumberOfPictures 0 PostalCode 0 LastSeen 0 dtype: int64
Переведем название колонок в удобоваримый вид(змеинный_регистр)
df.columns = df.columns.str.replace(r"([A-Z])", r" \1").str.lower().str.replace(' ', '_').str[1:]
df.head()
| date_crawled | price | vehicle_type | registration_year | gearbox | power | model | kilometer | registration_month | fuel_type | brand | repaired | date_created | number_of_pictures | postal_code | last_seen | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2016-03-24 11:52:17 | 480 | NaN | 1993 | manual | 0 | golf | 150000 | 0 | petrol | volkswagen | NaN | 2016-03-24 00:00:00 | 0 | 70435 | 2016-04-07 03:16:57 |
| 1 | 2016-03-24 10:58:45 | 18300 | coupe | 2011 | manual | 190 | NaN | 125000 | 5 | gasoline | audi | yes | 2016-03-24 00:00:00 | 0 | 66954 | 2016-04-07 01:46:50 |
| 2 | 2016-03-14 12:52:21 | 9800 | suv | 2004 | auto | 163 | grand | 125000 | 8 | gasoline | jeep | NaN | 2016-03-14 00:00:00 | 0 | 90480 | 2016-04-05 12:47:46 |
| 3 | 2016-03-17 16:54:04 | 1500 | small | 2001 | manual | 75 | golf | 150000 | 6 | petrol | volkswagen | no | 2016-03-17 00:00:00 | 0 | 91074 | 2016-03-17 17:40:17 |
| 4 | 2016-03-31 17:25:20 | 3600 | small | 2008 | manual | 69 | fabia | 90000 | 7 | gasoline | skoda | no | 2016-03-31 00:00:00 | 0 | 60437 | 2016-04-06 10:17:21 |
print('date_crawled', df['date_crawled'].astype('datetime64').dt.year.unique())
print('date_created',df['date_created'].astype('datetime64').dt.year.unique())
# df.pivot_table(index='postal_code', values='price', aggfunc = 'median').plot()
# df['postal_code'].sort_values().unique()
date_crawled [2016] date_created [2016 2015 2014]
Исходя из целей нашего проекта удалим колонки не представляющие для нас информационной ценности.
df = df.drop(['date_crawled', 'registration_month', 'date_created', 'number_of_pictures', 'postal_code', 'last_seen'], axis=1)
Работа с пропусками¶
Начинаем работу с пропусками. Посмотрим процентные соотношения, попробуем найти систематику для восстановления пропущенных значений
print('Доля пропусков в vehicle_type: {:.2%}'.format(df['vehicle_type'].isna().sum() / len(df['vehicle_type'])))
print('\n')
print('Доля пропусков в gearbox: {:.2%}'.format(df['gearbox'].isna().sum() / len(df['gearbox'])))
print('\n')
print('Доля пропусков в model: {:.2%}'.format(df['model'].isna().sum() / len(df['model'])))
print('\n')
print('Доля пропусков в fuel_type: {:.2%}'.format(df['fuel_type'].isna().sum() / len(df['fuel_type'])))
print('\n')
print('Доля пропусков в repaired: {:.2%}'.format(df['repaired'].isna().sum() / len(df['repaired'])))
Доля пропусков в vehicle_type: 10.58% Доля пропусков в gearbox: 5.60% Доля пропусков в model: 5.56% Доля пропусков в fuel_type: 9.28% Доля пропусков в repaired: 20.08%
print('Типы кузова:',df.loc[(df['model'] == 'golf')&(df['brand'] == 'volkswagen')]['vehicle_type'].unique())
print('Тип топлива:',df.loc[(df['fuel_type'] == df['fuel_type']) & (df['model'] == 'golf')]['fuel_type'].unique())
print('\n')
print('Типы трансмиссия по моделям:')
pd.pivot_table(df,index =['brand', 'model','gearbox'], values=['price'],aggfunc = 'count').head(10)
Типы кузова: [nan 'small' 'sedan' 'bus' 'other' 'wagon' 'convertible' 'coupe' 'suv'] Тип топлива: ['petrol' 'gasoline' 'lpg' 'cng' 'electric' 'other' 'hybrid'] Типы трансмиссия по моделям:
| price | |||
|---|---|---|---|
| brand | model | gearbox | |
| alfa_romeo | 145 | manual | 40 |
| 147 | auto | 29 | |
| manual | 520 | ||
| 156 | auto | 45 | |
| manual | 556 | ||
| 159 | auto | 26 | |
| manual | 183 | ||
| other | auto | 32 | |
| manual | 464 | ||
| spider | auto | 3 |
Промежуточный вывод: Пропусков довольно много. Если удалить все строки где есть хоть одно пропущенное значение, то мы потеряем очень много данных. Однако строки где пропущенных значений более 2, нам не нужны. Информационной ценности от таких объектов нет. Далее мы их удалим.
df = df.dropna(subset=['vehicle_type', 'gearbox', 'model','fuel_type', 'repaired'], thresh = 3)
df.info()
df.isna().sum()
<class 'pandas.core.frame.DataFrame'> Int64Index: 336337 entries, 0 to 354368 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 price 336337 non-null int64 1 vehicle_type 314215 non-null object 2 registration_year 336337 non-null int64 3 gearbox 327383 non-null object 4 power 336337 non-null int64 5 model 324321 non-null object 6 kilometer 336337 non-null int64 7 fuel_type 317797 non-null object 8 brand 336337 non-null object 9 repaired 281233 non-null object dtypes: int64(4), object(6) memory usage: 28.2+ MB
price 0 vehicle_type 22122 registration_year 0 gearbox 8954 power 0 model 12016 kilometer 0 fuel_type 18540 brand 0 repaired 55104 dtype: int64
Посмотрим уникальные значения в столбцах где есть пропуски. И определимся чем заменять пропуски.
print(df['vehicle_type'].unique())
print('\n')
print(df['gearbox'].unique())
print('\n')
print(df['model'].unique())
print('\n')
print(df['fuel_type'].unique())
print('\n')
print(df['repaired'].unique())
print('\n')
[nan 'coupe' 'suv' 'small' 'sedan' 'convertible' 'bus' 'wagon' 'other'] ['manual' 'auto' nan] ['golf' nan 'grand' 'fabia' '3er' '2_reihe' 'other' 'c_max' '3_reihe' 'passat' 'navara' 'ka' 'twingo' 'a_klasse' 'scirocco' '5er' 'meriva' 'arosa' 'c4' 'civic' 'transporter' 'punto' 'e_klasse' 'clio' 'kadett' 'kangoo' 'one' 'fortwo' '1er' 'b_klasse' 'signum' 'astra' 'a8' 'jetta' 'polo' 'fiesta' 'c_klasse' 'micra' 'vito' 'sprinter' '156' 'escort' 'forester' 'xc_reihe' 'scenic' 'a4' 'a1' 'combo' 'focus' 'tt' 'corsa' 'a6' 'jazz' 'omega' 'slk' '7er' '80' '147' 'z_reihe' 'sportage' 'sorento' 'v40' 'ibiza' 'mustang' 'eos' 'touran' 'getz' 'insignia' 'a3' 'almera' 'megane' 'lupo' 'r19' 'zafira' 'caddy' 'mondeo' 'cordoba' 'colt' 'impreza' 'vectra' 'berlingo' 'tiguan' 'i_reihe' 'sharan' '6_reihe' 'panda' 'up' 'seicento' 'ceed' '5_reihe' 'yeti' 'octavia' 'mii' 'rx_reihe' '6er' 'modus' 'fox' 'matiz' 'beetle' 'rio' 'touareg' 'logan' 'spider' 'cuore' 's_max' 'a2' 'galaxy' 'c3' 'viano' 's_klasse' '1_reihe' 'avensis' 'roomster' 'sl' 'kaefer' 'santa' 'cooper' 'leon' '4_reihe' 'a5' '500' 'laguna' 'ptcruiser' 'clk' 'primera' 'espace' 'x_reihe' 'exeo' '159' 'transit' 'juke' 'qashqai' 'carisma' 'accord' 'corolla' 'lanos' 'phaeton' 'verso' 'swift' 'rav' 'picanto' 'boxster' 'kalos' 'superb' 'stilo' 'alhambra' 'mx_reihe' 'roadster' 'ypsilon' 'cayenne' 'galant' 'justy' '90' 'sirion' 'crossfire' 'agila' 'duster' 'cr_reihe' 'v50' 'c_reihe' 'v_klasse' 'm_klasse' 'yaris' 'c5' 'aygo' 'cc' '911' 'bora' 'forfour' 'm_reihe' '100' 'cl' 'tigra' '300c' 'spark' 'kuga' 'x_type' 'ducato' 's_type' 'x_trail' 'toledo' 'altea' 'voyager' 'carnival' 'v70' 'bravo' 'antara' 'tucson' 'c1' 'citigo' 'jimny' 'wrangler' 'lybra' 'q7' 'lancer' 'captiva' 'fusion' 'c2' 'discovery' 'sandero' 'note' '900' 'cherokee' 'clubman' 'samara' 'defender' '601' 'cx_reihe' 'legacy' 'pajero' 'auris' 'niva' 's60' 'nubira' 'vivaro' 'g_klasse' 'freelander' 'lodgy' '850' 'range_rover' 'q3' 'serie_2' 'calibra' 'glk' 'charade' 'croma' 'outlander' 'doblo' 'musa' 'move' '9000' 'v60' '145' '200' 'b_max' 'range_rover_sport' 'aveo' 'terios' 'rangerover' 'q5' 'range_rover_evoque' 'materia' 'delta' 'gl' 'kalina' 'amarok' 'elefantino' 'i3' 'kappa' 'serie_3' 'serie_1'] ['petrol' 'gasoline' nan 'lpg' 'other' 'hybrid' 'cng' 'electric'] [nan 'yes' 'no']
Отрабатываем оставшиеся пропуски
df['vehicle_type'].fillna('other', inplace=True)
df['gearbox'].fillna('no_info', inplace=True)
df['model'].fillna('other', inplace=True)
df['fuel_type'].fillna('other', inplace=True)
df['repaired'].fillna('no_info', inplace=True)
df.isna().sum()
price 0 vehicle_type 0 registration_year 0 gearbox 0 power 0 model 0 kilometer 0 fuel_type 0 brand 0 repaired 0 dtype: int64
df.head()
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480 | other | 1993 | manual | 0 | golf | 150000 | petrol | volkswagen | no_info |
| 1 | 18300 | coupe | 2011 | manual | 190 | other | 125000 | gasoline | audi | yes |
| 2 | 9800 | suv | 2004 | auto | 163 | grand | 125000 | gasoline | jeep | no_info |
| 3 | 1500 | small | 2001 | manual | 75 | golf | 150000 | petrol | volkswagen | no |
| 4 | 3600 | small | 2008 | manual | 69 | fabia | 90000 | gasoline | skoda | no |
Посмотрим на распределения не бинарных данных
df['price'].hist(bins=2000)
plt.title('Гистограмма цены')
plt.xlabel('Цена')
plt.ylabel('Кол-во объявлений')
Text(0, 0.5, 'Кол-во объявлений')
df.loc[(df['price'] < 150) &(df['price'] > 5)]
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 417 | 120 | small | 1995 | manual | 0 | polo | 150000 | petrol | volkswagen | no_info |
| 924 | 140 | coupe | 1997 | manual | 0 | megane | 150000 | petrol | renault | yes |
| 943 | 98 | small | 1997 | manual | 53 | swift | 150000 | petrol | suzuki | no |
| 964 | 100 | wagon | 1995 | manual | 116 | passat | 150000 | petrol | volkswagen | yes |
| 966 | 130 | small | 1996 | manual | 45 | polo | 150000 | petrol | volkswagen | no_info |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 353661 | 100 | sedan | 1998 | manual | 88 | 3_reihe | 150000 | petrol | mazda | yes |
| 353669 | 100 | small | 1994 | manual | 60 | polo | 150000 | other | volkswagen | yes |
| 353673 | 90 | small | 1998 | manual | 60 | other | 150000 | petrol | citroen | yes |
| 354018 | 100 | small | 1996 | manual | 0 | polo | 150000 | petrol | volkswagen | no_info |
| 354309 | 99 | small | 1999 | manual | 60 | arosa | 150000 | petrol | seat | yes |
1939 rows × 10 columns
plt.xlim(1960,2019)
df['registration_year'].hist(bins=10000)
plt.title('Гистограмма год регистрации')
plt.xlabel('Год')
plt.ylabel('Кол-во объявлений')
Text(0, 0.5, 'Кол-во объявлений')
plt.xlim(0,500)
df['power'].hist(bins =5000)
plt.title('Гистограмма мощьности а/м')
plt.xlabel('Мощность')
plt.ylabel('Кол-во объявлений')
Text(0, 0.5, 'Кол-во объявлений')
df['kilometer'].hist(bins=100)
plt.title('Гистограмма по пробегу')
plt.xlabel('Пробег')
plt.ylabel('Кол-во объявлений')
Text(0, 0.5, 'Кол-во объявлений')
Промежуточное: Для построения моделей нам необходимо выбрать диапазоны типовых данных. Это значит обработать выбросы т выбрать интервалы.
1) Год регистрации тс 1990-2016
2)Мощность двигателя менее 300 л.с. включительно. А значени 0 заменим медианным.
3)Колонка с типом топлива, в ней произведем замену "gasoline" на "petrol". Т.к. это одно и тоже, но мы уменьшим кол-во категорий.
4)После всех замен проверим на явные дубликаты.
5) Восстановим 0 значения цены медианами учитывая бренд, модель и год регистрации авто. Далее определеим интервал wtys от 250 до 11750.
1) изменил интервал года регистрации по 2016
2) Восстановил 0 значения по цене и переопределил интервал 250
3) Выше по коду добавил ечейку для просмотра уникальных значений удаляемых столбцов.
df
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480 | other | 1993 | manual | 0 | golf | 150000 | petrol | volkswagen | no_info |
| 1 | 18300 | coupe | 2011 | manual | 190 | other | 125000 | gasoline | audi | yes |
| 2 | 9800 | suv | 2004 | auto | 163 | grand | 125000 | gasoline | jeep | no_info |
| 3 | 1500 | small | 2001 | manual | 75 | golf | 150000 | petrol | volkswagen | no |
| 4 | 3600 | small | 2008 | manual | 69 | fabia | 90000 | gasoline | skoda | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 354363 | 1150 | bus | 2000 | manual | 0 | zafira | 150000 | petrol | opel | no |
| 354364 | 0 | other | 2005 | manual | 0 | colt | 150000 | petrol | mitsubishi | yes |
| 354366 | 1199 | convertible | 2000 | auto | 101 | fortwo | 125000 | petrol | smart | no |
| 354367 | 9200 | bus | 1996 | manual | 102 | transporter | 150000 | gasoline | volkswagen | no |
| 354368 | 3400 | wagon | 2002 | manual | 100 | golf | 150000 | gasoline | volkswagen | no_info |
336337 rows × 10 columns
df = df.loc[(df['registration_year'] >= 1990) & (df['registration_year'] <= 2016)]
df = df.loc[(df['power'] <= 300)]
df['power'] = df['power'].replace(0, df['power'].median())
df['fuel_type'] = df['fuel_type'].replace('gasoline','petrol')
print('Кол-во явных дубликатов:', df.duplicated().sum())
df = df.drop_duplicates().reset_index(drop=True)
df_piv = df.pivot_table(index=['brand','model','registration_year'], values='price', aggfunc ='median')
df_piv = df_piv.reset_index()
for i in range(len(df['price'])):
if df.loc[i,'price'] == 0:
models = df.loc[i,'model']
brands = df.loc[i,'brand']
year = df.loc[i,'registration_year']
df.loc[i,'price'] = df_piv.loc[(df_piv['model'] == models)
& (df_piv['brand'] == brands)
&(df_piv['registration_year'] == year)]['price'].values[0]
df =df.loc[(df['price'] >=250)]
df.reset_index(drop =True, inplace = True)
print('Количество отсеянных данных: {:.2%}'.format(1 - len(df['price']) / size_a))
Кол-во явных дубликатов: 43076 Количество отсеянных данных: 25.27%
df
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480.0 | other | 1993 | manual | 105 | golf | 150000 | petrol | volkswagen | no_info |
| 1 | 18300.0 | coupe | 2011 | manual | 190 | other | 125000 | petrol | audi | yes |
| 2 | 9800.0 | suv | 2004 | auto | 163 | grand | 125000 | petrol | jeep | no_info |
| 3 | 1500.0 | small | 2001 | manual | 75 | golf | 150000 | petrol | volkswagen | no |
| 4 | 3600.0 | small | 2008 | manual | 69 | fabia | 90000 | petrol | skoda | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 264832 | 3200.0 | sedan | 2004 | manual | 225 | leon | 150000 | petrol | seat | yes |
| 264833 | 2499.5 | other | 2005 | manual | 105 | colt | 150000 | petrol | mitsubishi | yes |
| 264834 | 1199.0 | convertible | 2000 | auto | 101 | fortwo | 125000 | petrol | smart | no |
| 264835 | 9200.0 | bus | 1996 | manual | 102 | transporter | 150000 | petrol | volkswagen | no |
| 264836 | 3400.0 | wagon | 2002 | manual | 100 | golf | 150000 | petrol | volkswagen | no_info |
264837 rows × 10 columns
df['price'].hist(bins = 2000)
plt.title('Гистограмма цены')
plt.xlabel('Цена')
plt.ylabel('Кол-во объявлений')
Text(0, 0.5, 'Кол-во объявлений')
Советую посмотреть на дату выгрузки анкет - там тоже можно увидеть полезную информацию по поводу границ года регистраци.
Вопрос на подумать: Как думаешь а машины с 0-ой мощностью могут быть просто без двигателя?
А/м с 0-й мощьностью и отсутствующим двигателем, вполне возможно. Но смущает количество таких объявлений, это ~12% от всего датасета. Как их выделить из даных? Я не увидел зависимости и связей для однозначного ответа.
Мне трудно принять такую гипотезу, что 1/10 часть авто продается без двигателя.
PS: Готов получить качественный нагоняй)
Согласен полностью
При продаже авто, принято указывать мощность автомобиля из техпаспорта, поэтому такой сценарий маловероятен, но даже если он вероятен, то я бы отнес такие машины смело в другую категорию, потому что машинами их назвать тяжело.
Привидем типы данных в соответствие
df = df.astype({"vehicle_type":'category',
"gearbox":'category',
"model":'category',
"fuel_type":'category',
"brand":'category',
"repaired":'category'})
Обучение моделей¶
Для обучения моделейнужно подготовить данные. Првести кодировани, масштабировать признаки, разбить на выборки.
Кодируем для линейной регрессии методом OHE
df_ohe = pd.get_dummies(df, drop_first=True)
df_ohe.head()
| price | registration_year | power | kilometer | vehicle_type_convertible | vehicle_type_coupe | vehicle_type_other | vehicle_type_sedan | vehicle_type_small | vehicle_type_suv | ... | brand_smart | brand_sonstige_autos | brand_subaru | brand_suzuki | brand_toyota | brand_trabant | brand_volkswagen | brand_volvo | repaired_no_info | repaired_yes | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480.0 | 1993 | 105 | 150000 | 0 | 0 | 1 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 1 | 18300.0 | 2011 | 190 | 125000 | 0 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 2 | 9800.0 | 2004 | 163 | 125000 | 0 | 0 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 3 | 1500.0 | 2001 | 75 | 150000 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 4 | 3600.0 | 2008 | 69 | 90000 | 0 | 0 | 0 | 0 | 1 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 305 columns
df
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480.0 | other | 1993 | manual | 105 | golf | 150000 | petrol | volkswagen | no_info |
| 1 | 18300.0 | coupe | 2011 | manual | 190 | other | 125000 | petrol | audi | yes |
| 2 | 9800.0 | suv | 2004 | auto | 163 | grand | 125000 | petrol | jeep | no_info |
| 3 | 1500.0 | small | 2001 | manual | 75 | golf | 150000 | petrol | volkswagen | no |
| 4 | 3600.0 | small | 2008 | manual | 69 | fabia | 90000 | petrol | skoda | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 264832 | 3200.0 | sedan | 2004 | manual | 225 | leon | 150000 | petrol | seat | yes |
| 264833 | 2499.5 | other | 2005 | manual | 105 | colt | 150000 | petrol | mitsubishi | yes |
| 264834 | 1199.0 | convertible | 2000 | auto | 101 | fortwo | 125000 | petrol | smart | no |
| 264835 | 9200.0 | bus | 1996 | manual | 102 | transporter | 150000 | petrol | volkswagen | no |
| 264836 | 3400.0 | wagon | 2002 | manual | 100 | golf | 150000 | petrol | volkswagen | no_info |
264837 rows × 10 columns
Совет: Также как аналог pd.get_dummies хочу посоветовать обратить внимание на OneHotEncoder от sklearn (это более сложный и продвинутый инструмент). Здесь можно посмотреть примеры https://colab.research.google.com/drive/1_gAMXcQKoCShB_l8FNtYEejMnosm9mvt?usp=sharing
Успех[2]: В будущем обязательно переходи на него, без него никуда
Разделим данные на выборки.
target = df_ohe['price']
features = df_ohe.drop('price', axis=1)
(features_train,
features_valid,
target_train,
target_valid) = train_test_split(features, target, test_size=0.4, random_state=12345)
(features_valid,
features_test,
target_valid,
target_test) = train_test_split(features_valid, target_valid, test_size=0.5, random_state=12345)
print(features_train.shape)
print(features_valid.shape)
print(features_test.shape)
(158902, 304) (52967, 304) (52968, 304)
numeric = (features_train.columns.values)
print(numeric[1:10])
['power' 'kilometer' 'vehicle_type_convertible' 'vehicle_type_coupe' 'vehicle_type_other' 'vehicle_type_sedan' 'vehicle_type_small' 'vehicle_type_suv' 'vehicle_type_wagon']
pd.options.mode.chained_assignment = None
warnings.filterwarnings('ignore')
scaler = StandardScaler()
scaler.fit(features[numeric])
features_train_new=pd.DataFrame(index=features_train.index)
features_valid_new=pd.DataFrame(index=features_valid.index)
features_test_new=pd.DataFrame(index=features_test.index)
features_train_new[numeric] = scaler.transform(features_train[numeric])
features_valid_new[numeric] = scaler.transform(features_valid[numeric])
features_test_new[numeric] = scaler.transform(features_test[numeric])
print(features_valid_new)
registration_year power kilometer vehicle_type_convertible \
218765 -1.316110 -1.480012 0.598298 -0.259409
149831 0.665355 1.662217 0.598298 -0.259409
54053 1.385888 -0.069215 -3.163597 -0.259409
32615 0.845488 -0.368475 0.598298 -0.259409
195838 -0.775711 -0.133343 0.598298 -0.259409
... ... ... ... ...
208113 1.746155 0.016287 -2.626183 -0.259409
67048 -0.415444 -0.197470 0.598298 -0.259409
155995 1.025622 1.234702 0.598298 -0.259409
239101 -1.135977 -0.411227 0.598298 -0.259409
252843 0.485222 1.555338 -0.745236 3.854919
vehicle_type_coupe vehicle_type_other vehicle_type_sedan \
218765 -0.22226 -0.221352 1.637908
149831 -0.22226 -0.221352 -0.610535
54053 -0.22226 -0.221352 1.637908
32615 -0.22226 -0.221352 1.637908
195838 -0.22226 -0.221352 -0.610535
... ... ... ...
208113 -0.22226 -0.221352 1.637908
67048 -0.22226 -0.221352 -0.610535
155995 -0.22226 -0.221352 -0.610535
239101 -0.22226 -0.221352 -0.610535
252843 -0.22226 -0.221352 -0.610535
vehicle_type_small vehicle_type_suv vehicle_type_wagon ... \
218765 -0.556414 -0.19617 -0.506597 ...
149831 -0.556414 -0.19617 1.973955 ...
54053 -0.556414 -0.19617 -0.506597 ...
32615 -0.556414 -0.19617 -0.506597 ...
195838 -0.556414 -0.19617 1.973955 ...
... ... ... ... ...
208113 -0.556414 -0.19617 -0.506597 ...
67048 -0.556414 5.09761 -0.506597 ...
155995 -0.556414 -0.19617 1.973955 ...
239101 -0.556414 -0.19617 1.973955 ...
252843 -0.556414 -0.19617 -0.506597 ...
brand_smart brand_sonstige_autos brand_subaru brand_suzuki \
218765 -0.130504 -0.068029 -0.048559 -0.083964
149831 -0.130504 -0.068029 -0.048559 -0.083964
54053 -0.130504 -0.068029 -0.048559 -0.083964
32615 -0.130504 -0.068029 -0.048559 -0.083964
195838 -0.130504 -0.068029 -0.048559 -0.083964
... ... ... ... ...
208113 -0.130504 -0.068029 -0.048559 -0.083964
67048 -0.130504 -0.068029 -0.048559 -0.083964
155995 -0.130504 -0.068029 -0.048559 -0.083964
239101 -0.130504 -0.068029 -0.048559 -0.083964
252843 -0.130504 -0.068029 -0.048559 -0.083964
brand_toyota brand_trabant brand_volkswagen brand_volvo \
218765 -0.122429 -0.014412 1.980676 -0.09965
149831 -0.122429 -0.014412 -0.504878 -0.09965
54053 -0.122429 -0.014412 -0.504878 -0.09965
32615 -0.122429 -0.014412 1.980676 -0.09965
195838 -0.122429 -0.014412 -0.504878 -0.09965
... ... ... ... ...
208113 -0.122429 -0.014412 -0.504878 -0.09965
67048 -0.122429 -0.014412 -0.504878 -0.09965
155995 -0.122429 -0.014412 -0.504878 -0.09965
239101 -0.122429 -0.014412 1.980676 -0.09965
252843 -0.122429 -0.014412 -0.504878 -0.09965
repaired_no_info repaired_yes
218765 -0.437962 -0.344538
149831 -0.437962 -0.344538
54053 -0.437962 -0.344538
32615 -0.437962 2.902436
195838 -0.437962 2.902436
... ... ...
208113 -0.437962 -0.344538
67048 -0.437962 -0.344538
155995 -0.437962 -0.344538
239101 -0.437962 -0.344538
252843 -0.437962 -0.344538
[52967 rows x 304 columns]
Линейная регрессия¶
%%time
model = LinearRegression(normalize=True)
model.fit(features_train_new, target_train)
predictions = model.predict(features_valid_new)
rmse = mean_squared_error(target_valid, predictions)**0.5
print(rmse)
2330.0771857025625 Wall time: 2.97 s
Промежуточное: Обучение прошло успешно. Значение метрики RMSE < 2500. Оставлям эту модель для дальнейшего изучения
Качество можно считать двумя способами:
- на валидационной
- используя кросс-валидационные метрики
Исправь это здесь и везде внизу пожалуйста
Перешёл к расчетам на валидационных выборках
Успех[2]: 👍
Дерево решений¶
Проведем порядковое кодирование. Оно необходимо для моделей дерева и леса. Далее опять разбиваем на выборки и проводим обучение двух моделей
# df_1 = df.copy()
# enc = OrdinalEncoder()
# enc.fit(df[['vehicle_type','registration_year', 'gearbox', 'power',
# 'model', 'kilometer','fuel_type',
# 'brand', 'repaired']])
# df[['vehicle_type','registration_year', 'gearbox', 'power',
# 'model', 'kilometer','fuel_type',
# 'brand', 'repaired']] = enc.transform(df[['vehicle_type','registration_year', 'gearbox', 'power',
# 'model', 'kilometer','fuel_type',
# 'brand', 'repaired']])
# target_oe = df['price']
# features_oe = df.drop('price', axis=1)
# (features_train_oe,
# features_test_oe,
# target_train_oe,
# target_test_oe) = train_test_split(features_oe, target_oe, test_size=0.25, random_state=12345)
df_1 = df.copy()
target_oe = df['price']
features_oe = df.drop('price', axis=1)
(features_train_oe,
features_valid_oe,
target_train_oe,
target_valid_oe) = train_test_split(features_oe, target_oe, test_size=0.25, random_state=12345)
(features_valid_oe,
features_test_oe,
target_valid_oe,
target_test_oe) = train_test_split(features_valid_oe, target_valid_oe, test_size=0.5, random_state=12345)
col_oe= ['vehicle_type','registration_year', 'gearbox', 'power',
'model', 'kilometer','fuel_type',
'brand', 'repaired']
enc = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)
enc.fit(features_train_oe[col_oe])
features_train_oe[col_oe] = enc.transform(features_train_oe[col_oe])
print(features_train_oe)
features_valid_oe[col_oe] = enc.transform(features_valid_oe[col_oe])
features_test_oe[col_oe] = enc.transform(features_test_oe[col_oe])
vehicle_type registration_year gearbox power model kilometer \
223619 6.0 15.0 0.0 284.0 166.0 3.0
195099 5.0 7.0 1.0 56.0 176.0 0.0
251530 7.0 15.0 0.0 196.0 28.0 12.0
238770 6.0 14.0 0.0 246.0 67.0 12.0
123109 2.0 16.0 1.0 146.0 11.0 11.0
... ... ... ... ... ... ...
158838 4.0 14.0 1.0 71.0 166.0 12.0
47873 6.0 12.0 0.0 246.0 67.0 12.0
86398 5.0 19.0 1.0 101.0 150.0 12.0
77285 4.0 19.0 1.0 118.0 6.0 10.0
217570 1.0 18.0 0.0 116.0 8.0 6.0
fuel_type brand repaired
223619 5.0 3.0 0.0
195099 5.0 9.0 1.0
251530 5.0 1.0 1.0
238770 5.0 26.0 0.0
123109 5.0 2.0 0.0
... ... ... ...
158838 5.0 35.0 0.0
47873 3.0 26.0 0.0
86398 5.0 24.0 0.0
77285 5.0 2.0 0.0
217570 5.0 25.0 0.0
[198627 rows x 9 columns]
Ошибка: Отмечу, что encoder правильно применять после разбиения данных и обучать только на обучающей выборке, для остальных выборок просто использовать transform. Примеры использования с объяснениями можно найти и посмотреть по той же ссылке https://colab.research.google.com/drive/1_gAMXcQKoCShB_l8FNtYEejMnosm9mvt?usp=sharing
у нас на платформе по теории было :
encoder = OrdinalEncoder()
data_ordinal = pd.DataFrame(encoder.fit_transform(data), columns=data.columns)
target = data_ordinal['Claim']
features = data_ordinal.drop('Claim', axis=1)
features_train, features_valid, target_train, target_valid = train_test_split(features, target, test_size=0.25,random_state=12345)
На этом все. А выполняя проект тебе опытный человек говорит что "вы сударь не истинны в своих изысканиях". Как жить дальше?)
P.S. это не притензия, исключительно конструктив
Успех[2]: Любой препроцессор (encoder, vectorizer, scaler...) важно обучать на обучающих данных, чтобы избегать проблемы с переобучением.
Если препроцессор обучен на полных данных, то это может привести к тому, что модель будет слишком хорошо адаптирована к тестовым данным, мы получим слишком оптимистично-смещенные оценки, а по факту может оказаться, что модель может обобщать свои знания на реальный мир.
for depth in range(1, 40, 3):
model = DecisionTreeRegressor(random_state=12345, max_depth = depth)
model.fit(features_train_oe, target_train_oe)
predictions_valid_oe = model.predict(features_valid_oe)
rmse = mean_squared_error(target_valid_oe, predictions_valid_oe)**0.5
print('Глубина:', depth)
print('RMSE для решающего дерева:', rmse)
print('')
print()
Глубина: 1 RMSE для решающего дерева: 3528.734669197044 Глубина: 4 RMSE для решающего дерева: 2525.2175175139423 Глубина: 7 RMSE для решающего дерева: 2100.3781325418963 Глубина: 10 RMSE для решающего дерева: 1906.6426241059025 Глубина: 13 RMSE для решающего дерева: 1801.3677321376624 Глубина: 16 RMSE для решающего дерева: 1804.2496212700403 Глубина: 19 RMSE для решающего дерева: 1860.8638984972042 Глубина: 22 RMSE для решающего дерева: 1902.2747871350903 Глубина: 25 RMSE для решающего дерева: 1911.8087490420621 Глубина: 28 RMSE для решающего дерева: 1921.3130402486581 Глубина: 31 RMSE для решающего дерева: 1924.6493721822608 Глубина: 34 RMSE для решающего дерева: 1912.1911610981438 Глубина: 37 RMSE для решающего дерева: 1917.4108819759274
Промежуточное: Модель удовлетворяет первоначальному условию RMSE < 2500. Параметры лучшей модели Глубина: 13 RMSE для решающего дерева: 1801.37.
Оставляем модель для дальнейшего изучения.
Случайный лес регрессии¶
best_est = 0
best_depth = 0
rmse_p = 2500
for est in range(10, 40, 10):
for depth in range (10, 20):
model = RandomForestRegressor(random_state = 12345, n_estimators = est, max_depth = depth, n_jobs = -1)
model.fit(features_train_oe, target_train_oe)
predictions_valid_oe = model.predict(features_valid_oe)
rmse = mean_squared_error(target_valid_oe, predictions_valid_oe)**0.5
if rmse < rmse_p:
best_est = est
best_depth = depth
best_rmse = rmse
print(" RMSE:", best_rmse,
"\n Количество деревьев:", best_est,
"\n Максимальная глубина:", best_depth)
RMSE: 1577.4541679723566 Количество деревьев: 30 Максимальная глубина: 19
Промежуточное: Модель удовлетворяет первоначальному условию RMSE < 2500. Параметры лучшей модели Количество деревьев: 30 Максимальная глубина: 19 RMSE: 1577.45
Оставляем модель для дальнейшего изучения.
Проверка. У нас есть резервная копия датасета?
df_1
| price | vehicle_type | registration_year | gearbox | power | model | kilometer | fuel_type | brand | repaired | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 480.0 | other | 1993 | manual | 105 | golf | 150000 | petrol | volkswagen | no_info |
| 1 | 18300.0 | coupe | 2011 | manual | 190 | other | 125000 | petrol | audi | yes |
| 2 | 9800.0 | suv | 2004 | auto | 163 | grand | 125000 | petrol | jeep | no_info |
| 3 | 1500.0 | small | 2001 | manual | 75 | golf | 150000 | petrol | volkswagen | no |
| 4 | 3600.0 | small | 2008 | manual | 69 | fabia | 90000 | petrol | skoda | no |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 264832 | 3200.0 | sedan | 2004 | manual | 225 | leon | 150000 | petrol | seat | yes |
| 264833 | 2499.5 | other | 2005 | manual | 105 | colt | 150000 | petrol | mitsubishi | yes |
| 264834 | 1199.0 | convertible | 2000 | auto | 101 | fortwo | 125000 | petrol | smart | no |
| 264835 | 9200.0 | bus | 1996 | manual | 102 | transporter | 150000 | petrol | volkswagen | no |
| 264836 | 3400.0 | wagon | 2002 | manual | 100 | golf | 150000 | petrol | volkswagen | no_info |
264837 rows × 10 columns
LightGBM¶
target = df_1['price']
features = df_1.drop('price', axis=1)
#разделим выборки на обучающую и тестовую
(features_train_l,
features_valid_l,
target_train_l,
target_valid_l) = train_test_split(features, target, test_size=0.4, random_state=12345)
(features_valid_l,
features_test_l,
target_valid_l,
target_test_l) = train_test_split(features_valid_l, target_valid_l, test_size=0.4, random_state=12345)
model = lgb.LGBMRegressor(random_state=12345)
model.fit(features_train_l, target_train_l)
predictions = model.predict(features_train_l)
rmse = mean_squared_error(target_train_l, predictions)**0.5
print(rmse)
1483.8446419648124
Провели обучение с использованием градиентного бустинга без определения гиперпараметров. RMSE составило 1119.59. Далее попробуем улучшить модель используя GridSearcCV.
param_grid = {'n_estimators': [10, 100, 200], 'num_leaves': [n for n in range(20, 200, 20)],'max_depth':[20]}
model = lgb.LGBMRegressor(random_state=12345, n_jobs = -1)
par_model=GridSearchCV(estimator=model,
param_grid=param_grid,
scoring='neg_root_mean_squared_error',
cv=3,
verbose=3)
par_model.fit(features_train_l, target_train_l)
data_gs = pd.DataFrame(par_model.cv_results_)
display(par_model.best_params_)
display(par_model.best_score_*-1)
Fitting 3 folds for each of 27 candidates, totalling 81 fits [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=20;, score=-2545.584 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=20;, score=-2526.043 total time= 0.0s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=20;, score=-2513.586 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=40;, score=-2440.624 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=40;, score=-2415.747 total time= 0.0s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=40;, score=-2412.783 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=60;, score=-2397.767 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=60;, score=-2369.766 total time= 0.0s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=60;, score=-2366.230 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=80;, score=-2367.377 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=80;, score=-2339.072 total time= 0.0s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=80;, score=-2339.410 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=100;, score=-2346.828 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=100;, score=-2318.170 total time= 0.0s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=100;, score=-2321.871 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=120;, score=-2330.796 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=120;, score=-2304.752 total time= 0.1s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=120;, score=-2304.355 total time= 0.0s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=140;, score=-2320.388 total time= 0.0s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=140;, score=-2295.341 total time= 0.1s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=140;, score=-2294.618 total time= 0.1s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=160;, score=-2308.411 total time= 0.1s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=160;, score=-2284.514 total time= 0.1s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=160;, score=-2287.066 total time= 0.1s [CV 1/3] END max_depth=20, n_estimators=10, num_leaves=180;, score=-2303.308 total time= 0.1s [CV 2/3] END max_depth=20, n_estimators=10, num_leaves=180;, score=-2277.112 total time= 0.1s [CV 3/3] END max_depth=20, n_estimators=10, num_leaves=180;, score=-2279.435 total time= 0.1s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=20;, score=-1575.393 total time= 0.3s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=20;, score=-1566.274 total time= 0.3s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=20;, score=-1577.862 total time= 0.3s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=40;, score=-1541.361 total time= 0.4s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=40;, score=-1534.852 total time= 0.4s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=40;, score=-1545.990 total time= 0.4s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=60;, score=-1525.283 total time= 0.4s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=60;, score=-1520.683 total time= 0.4s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=60;, score=-1531.569 total time= 0.4s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=80;, score=-1515.973 total time= 0.6s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=80;, score=-1515.412 total time= 0.5s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=80;, score=-1522.408 total time= 0.7s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=100;, score=-1511.992 total time= 0.8s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=100;, score=-1505.649 total time= 0.8s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=100;, score=-1516.723 total time= 0.6s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=120;, score=-1507.115 total time= 0.6s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=120;, score=-1501.398 total time= 0.6s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=120;, score=-1512.361 total time= 0.6s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=140;, score=-1506.433 total time= 0.7s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=140;, score=-1500.630 total time= 0.7s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=140;, score=-1511.585 total time= 0.7s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=160;, score=-1500.532 total time= 0.7s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=160;, score=-1496.953 total time= 0.8s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=160;, score=-1507.902 total time= 0.7s [CV 1/3] END max_depth=20, n_estimators=100, num_leaves=180;, score=-1503.888 total time= 0.8s [CV 2/3] END max_depth=20, n_estimators=100, num_leaves=180;, score=-1497.303 total time= 0.8s [CV 3/3] END max_depth=20, n_estimators=100, num_leaves=180;, score=-1502.891 total time= 0.8s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=20;, score=-1544.652 total time= 0.5s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=20;, score=-1536.256 total time= 0.5s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=20;, score=-1545.587 total time= 0.5s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=40;, score=-1516.067 total time= 0.6s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=40;, score=-1511.174 total time= 0.6s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=40;, score=-1524.821 total time= 0.6s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=60;, score=-1506.860 total time= 0.8s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=60;, score=-1501.971 total time= 0.8s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=60;, score=-1513.638 total time= 0.8s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=80;, score=-1500.516 total time= 0.9s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=80;, score=-1499.458 total time= 0.9s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=80;, score=-1506.825 total time= 0.9s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=100;, score=-1501.057 total time= 1.0s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=100;, score=-1492.339 total time= 1.0s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=100;, score=-1501.817 total time= 1.0s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=120;, score=-1494.934 total time= 1.1s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=120;, score=-1490.666 total time= 1.1s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=120;, score=-1502.107 total time= 1.1s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=140;, score=-1499.600 total time= 1.2s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=140;, score=-1491.320 total time= 1.2s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=140;, score=-1502.000 total time= 1.2s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=160;, score=-1493.666 total time= 1.3s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=160;, score=-1489.739 total time= 1.3s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=160;, score=-1499.524 total time= 1.3s [CV 1/3] END max_depth=20, n_estimators=200, num_leaves=180;, score=-1497.315 total time= 1.4s [CV 2/3] END max_depth=20, n_estimators=200, num_leaves=180;, score=-1491.677 total time= 1.4s [CV 3/3] END max_depth=20, n_estimators=200, num_leaves=180;, score=-1497.348 total time= 1.4s
{'max_depth': 20, 'n_estimators': 200, 'num_leaves': 160}
1494.309760336883
Промежуточное: Модель удовлетворяет первоначальному условию RMSE < 2500. Параметры лучшей модели:
'max_depth': 20, 'n_estimators': 200, 'num_leaves': 160
RMSE: 1494.30
Можно продолжить подбирать параметры, но время обучения начинает очень сильно возрастать.
Оставляем модель для дальнейшего изучения.
Анализ моделей¶
Линейная регрессия¶
%%time
model = LinearRegression(normalize=True)
model.fit(features_train_new, target_train)
Wall time: 2.61 s
LinearRegression(normalize=True)
%%time
predictions = model.predict(features_valid_new)
rmse = mean_squared_error(target_valid, predictions)**0.5
print(rmse)
2330.0771857025625 Wall time: 35 ms
время предсказания можно считать тремя способами:
- на валидационной
- на тренировочной
- можно вытаскивать все метрики интересующие заказчика лаконично из GridSearchCV/RandomizedSearchCV, все они лежат в
.cv_results_
Исправь это здесь и везде внизу пожалуйста
Успех[2]: Пойдем смотреть)
Промежуточный вывод:
1)Модель обучилась за 2.82с.
2) Предсказание заняло 25мс.
3) RMSE-метрика составила 2330.08
Дерево решений¶
%%time
model = DecisionTreeRegressor(random_state=12345, max_depth = 16)
model.fit(features_train_oe, target_train_oe)
Wall time: 465 ms
DecisionTreeRegressor(max_depth=16, random_state=12345)
%%time
predictions_valid_oe = model.predict(features_valid_oe)
rmse = mean_squared_error(target_valid_oe, predictions_valid_oe)**0.5
print('RMSE для решающего дерева:', rmse)
RMSE для решающего дерева: 1804.2496212700403 Wall time: 8.99 ms
Кажется здесь тестовая залетела случайно
Кроме того ты назвал предсказания на тестой выборке переменной
predictions_train_oe, что уже странно, а для вывода rmse вообще использовал непонятно откуда взятый predictions_valid_oe =)
Исправил невнимательность.
Промежуточный вывод:
1)Модель обучилась за 474мс.
2) Предсказание заняло 10мс.
3) RMSE-метрика составила 1804.25
Лес регрессии¶
%%time
model = RandomForestRegressor(random_state = 12345, n_estimators = 40, max_depth = 19)
model.fit(features_train_oe, target_train_oe)
Wall time: 14.5 s
RandomForestRegressor(max_depth=19, n_estimators=40, random_state=12345)
%%time
predictions_valid = model.predict(features_valid_oe)
rmse = mean_squared_error(target_valid_oe, predictions_valid)**0.5
print(rmse)
1571.9850801721952 Wall time: 334 ms
Ошибка[2]: predictions_valid и predictions_valid_oe - не одно и тоже
Исправил невнимательность.
Промежуточный вывод:
1)Модель обучилась за 14.9с.
2) Предсказание заняло 334мс.
3) RMSE-метрика составила 1571.99
LGBM¶
%%time
model = lgb.LGBMRegressor(random_state=12345,n_estimators = 200, max_depth=20, num_leaves = 140)
model.fit(features_train_l, target_train_l)
Wall time: 1.18 s
LGBMRegressor(max_depth=20, n_estimators=200, num_leaves=140,
random_state=12345)
%%time
predictions = model.predict(features_valid_l)
rmse = mean_squared_error(target_valid_l, predictions)**0.5
print(rmse)
1463.0587817647986 Wall time: 428 ms
Исправил невнимательность.
Промежуточный вывод:
1) Модель обучилась за 1.35с.
2) Предсказание заняло 446мс.
3) RMSE-метрика составила 1463.06
Выводы: Перед нами стояла задачи построить модели для прогнозирования стоимости автомобиля на одной из площадок. Основное внимание заказчик уделяет 3 пунктам: скорость обучения, скорость предсказания, метрика качества RMSE.
Метрика качества вполне конкретизирована и составляет 2500. Из этого мы делаем вывод что все модели построенные нами удовлетворяют нас. Но у нас есть еще два параметра которые даны в довольно свободной форме "время работы".
Исходя из качества метрики и скорости обучения можно выделить 1 приоритетную модель
Модель LGBM 1) обучение завершилось за 1.35с, предсказание заняло 446мс, RMSE-метрика составила 1463.02
Далее проверим наш результат на тестовой выборке
Итоговая модель¶
%%time
model = lgb.LGBMRegressor(random_state=12345,n_estimators = 200, max_depth=20, num_leaves = 140)
model.fit(features_train_l, target_train_l)
Wall time: 1.18 s
LGBMRegressor(max_depth=20, n_estimators=200, num_leaves=140,
random_state=12345)
%%time
predictions = model.predict(features_test_l)
rmse = mean_squared_error(target_test_l, predictions)**0.5
print(rmse)
1490.8960354529308 Wall time: 282 ms
Влияние признаков на модель¶
col_graph=features_train_l.columns
importances = model.feature_importances_
indices = np.argsort(importances)
plt.barh(range(len(indices)), importances[indices])
plt.yticks(range(len(indices)), col_graph[indices])
plt.title('Влияние признаков')
plt.xlabel('Важность в условных единицах')
plt.show()
feat_importances = pd.Series(importances, index=col_graph).sort_values(ascending=False)
feat_importances = feat_importances.to_frame()
feat_importances.columns=['Относительная важность']
display(feat_importances)
| Относительная важность | |
|---|---|
| power | 8073 |
| registration_year | 7015 |
| kilometer | 3786 |
| model | 3473 |
| brand | 1521 |
| repaired | 1315 |
| vehicle_type | 1306 |
| gearbox | 1204 |
| fuel_type | 107 |
Вывод¶
Наша итоговая задача состояла в том, что-бы построить лучшую модель опираясь на 3 показателя: время лбучения, время предсказания, метрика качества RMSE не более 2500.
Исходя из реализованных моделей, делаем вывод что самая удачная модель LGBMRegressor с параметрами: n_estimators = 200, max_depth=20, num_leaves = 140.
Проверив обученную модель на тестовой выборке получаем следующий результат:
1) Время обучения: 1,2 сек
2) Время предсказания: 282мс
3) RMSE = 1490.9
Наиболее важными критериями при прогнозировании являются: мощьность и год регистрации
Чек-лист проверки¶
Поставьте 'x' в выполненных пунктах. Далее нажмите Shift+Enter.
- Jupyter Notebook открыт
- Весь код выполняется без ошибок
- Ячейки с кодом расположены в порядке исполнения
- Выполнена загрузка и подготовка данных
- Выполнено обучение моделей
- Есть анализ скорости работы и качества моделей
Также если говорить, что можно ещё улучшить в подобных проектах, то я бы выделил такие моменты:
Для понимания, а какие в итоге факторы важны при моделировании, можно выводить их важность, использую feature_importances_, ну и график заодно. Это будет смотреться очнь здорово: результативная метрика и график важности факторов)))
У нас разный возраст машин. Есть гипотеза, что для разных возрастов - своё ценообразование. Поэтому, можно попробовать ввести фактор "тип возраста" (ретро, супер-ретро, старая, новая... надо подумать..)..
А строить предсказания после 1980г. можно из без этих категорий, информации много и ценообразование относительно линейное.
Ответ[2]: может для каждого кластера свою модель использовать, и тогда может модели смогут решать узкие задачи более точно, но это всего-лишь гипотеза... нужно пробовать)