Прогнозирование расхода топлива автомобиля
by Kornievskaya Anastasia Andreevna
__1. Описание набора данных и признаков __
В данном проекте рассматривается задача прогнозирования расхода топлива по городу для автомобилей по их параметрам. Данные по авто были нагло стырены с интернета. Данный прогноз можно использовать для предсказания расхода для вновь выпускаемых автомобилей в процессе их проектирования с целью оптимизации (если захочется такое прогнозировать)
Список переменных:
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import os
import numpy as np
import pandas as pd
from scipy.sparse import csr_matrix, hstack
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV, LinearRegression, LassoCV
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer, TfidfTransformer
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.metrics import roc_auc_score, mean_absolute_error, explained_variance_score, mean_squared_error
from sklearn.ensemble import RandomForestClassifier
from collections import Counter
import pickle
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
2. Первичный анализ данных
cars = pd.read_csv('../../data/cars.csv', delimiter=';', index_col='Obs')
cars['Invoice']=cars['Invoice'].apply(lambda x: float(x[1:].replace(',','.'))) # уберем доллар из цены
cars['Cylinders']=cars['Cylinders'].apply(lambda x: int(x.replace('.','0'))) # для ротерных двигателей поставим 0
y=cars['MPG_City']
cars=cars.drop(['MPG_City', 'MPG_Highway'], axis=1) # будет не честно прогнозировать расход по городу через расход на трассе
cars.head()
Разобьем нашу выборку на тренировочный и тестовый набор для финальной проверки прогноза.
X_train, X_valid, y_train, y_valid = train_test_split(cars, y,
test_size=0.3, random_state=17)
X_train.info()
3. Первичный визуальный анализ данных
В данных довольно много категориальных признаков, но сначала посмотрим на числовые
sns.set_context(
"notebook",
font_scale = 2,
rc = {
"figure.figsize" : (12, 9),
"axes.titlesize" : 18
}
)
sns.heatmap(X_train[['Horsepower', 'Weight' , 'Wheelbase' ,'Length','EngineSize', 'Cylinders' , 'Invoice']].corr()\
, annot=True, fmt='.2f')
sns.pairplot(pd.concat([X_train[['Horsepower', 'Weight' , 'Wheelbase' ,'Length',\
'EngineSize', 'Cylinders' , 'Invoice']], y_train], axis=1))
4. Инсайты, найденные зависимости
Из корреляции видно, что пары признаков
Length и Wheelbase (длина и колесная база)
EngineSize и Cylinders (объем двигателя и число цилиндров)
сильно коррелированы (что довольно очевидно из предметной области) и по графикам видно, что зависимость линейна до невозможности, что будет плохо для нашей модели. Можем еще посмотреть на веса (ниже). Также видно, что MPG_City зависит от признаков не линейно (на первый взглят квадратично) мы это потом используем для генерации новых признаков.
5. Выбор метрики (совмещено с п.6)
6. Выбор модели
Я буду прогнозировать расход топлива как непрерывный отклик, т.е. с помощью линейной регрессии. Это обусловленно природой данных и возможностью интерпретировать соответствующие метрики.
Метрику возьмем довольно простую и понятную, а именно mean_absolute_error. Она нам покажет, но сколько в среднем мы "отдаляемся" от истиного значения.
7. Предобработка данных
Как ни крути, а для нормальной работы модели нужно стандартизовать данные. Используем StandardScaler
PCA использовать не будем. Для этого есть несколько причин. Во первых, мы уже убедились, что зависимость не оченьто и ленейна, а вовторых, потеряется интерпретируемость
scaler=StandardScaler()
X_train_scaled=scaler.fit_transform(X_train[['Horsepower', 'Weight' , 'Wheelbase' ,'Length'\
,'EngineSize', 'Cylinders' , 'Invoice']])
8. Кросс-валидация и настройка гиперпараметров модели
LR=LinearRegression()
LR.fit(X_train_scaled, y_train)
LR.coef_
Видно, что признак Cylinders имеет самый маленький вес - удалим
LR=LinearRegression()
LR.fit(X_train_scaled[:,[0,1,2,3,4,6]], y_train)
LR.coef_
видим, что Length тоже имеет довольно малый вес даже после удаления Cylinders + он сильно коррелирован с Wheelbase, так что тоже удалим
вообще надо посмотреть, что мы там по удаляли
score=cross_val_score(LinearRegression(), X_train_scaled, y_train, scoring='mean_absolute_error')
-np.mean(score)
score=cross_val_score(LinearRegression(), X_train_scaled[:,[0,1,2,4,6]], y_train, scoring='mean_absolute_error')
-np.mean(score)
ну.. мы удалили признаки и не сильно пострадал скор - это круто!
label_encoder = LabelEncoder()
label_encoder.fit(X_train['Type'])
X_train['Type2']=label_encoder.transform(X_train['Type'])
onehot_encoder = OneHotEncoder(sparse=False)
onehot_encoder.fit(X_train[['Type2']])
encoded_categorical_columns = pd.DataFrame(onehot_encoder.transform(X_train[['Type2']]))
t=pd.concat([pd.DataFrame(X_train_scaled[:,[0,1,2,4,6]], columns=['Horsepower', 'Weight' , 'Wheelbase' \
,'EngineSize', 'Invoice']), encoded_categorical_columns], axis=1)
LR=LinearRegression()
LR.fit(t.drop(0, axis=1), y_train)
LR.coef_
В общем не буду долго грузить этим отбором признаков - оставили тип Sedan и Sports
score=cross_val_score(LinearRegression(), t.drop([0,1,4,5], axis=1), y_train, scoring='mean_absolute_error')
-np.mean(score)
вуаля, немного улучшили.
X_train_scaled=t.drop([0,1,4,5], axis=1)
X_train_scaled=X_train_scaled.rename(columns={2: label_encoder.inverse_transform(2), 3: label_encoder.inverse_transform(3)})
X_train_scaled.head()
__9. Создание новых признаков и описание этого процесса __
Помните, там выше было что-то про нелинейную зависимость из графиков. ВОТ ОНА. Хотя на самом деле здесь имеет влияние не столько квадраты признаков,сколько сцепленный признак Horsepower*Weight
#X_train_scaled['Horsepower2']=X_train_scaled['Horsepower']*X_train_scaled['Horsepower']
#X_train_scaled['Weight2']=X_train_scaled['Weight']*X_train_scaled['Weight']
X_train_scaled['Horsepower_Weight']=X_train_scaled['Weight']*X_train_scaled['Horsepower']
score=cross_val_score(LinearRegression(), X_train_scaled, y_train, scoring='mean_absolute_error')
-np.mean(score)
не плохо, правда? наша модель довольно заметно улучшилась. Ну и это понятно из природы данных - чем тяжелее машина и более мощный в ней двигатель, тем больше она сожрет топлива, особенно в городе, где приходится много стоять
LR=LinearRegression()
LR.fit(X_train_scaled, y_train)
LR.coef_
заметим, что еще немного можно улучшить скор, убрав цену
score=cross_val_score(LinearRegression(), X_train_scaled.drop('Invoice', axis=1), y_train, scoring='mean_absolute_error')
-np.mean(score)
X_train_scaled=X_train_scaled.drop('Invoice', axis=1)
X_train_scaled.head()
__10. Построение кривых валидации и обучения __
from sklearn.learning_curve import learning_curve
def plot_with_std(x, data, **kwargs):
mu, std = data.mean(1), data.std(1)
lines = plt.plot(x, mu, '-', **kwargs)
plt.fill_between(x, mu - std, mu + std, edgecolor='none',
facecolor=lines[0].get_color(), alpha=0.2)
def plot_learning_curve(clf, X, y, scoring, cv=5):
train_sizes = np.linspace(0.05, 1, 20)
n_train, val_train, val_test = learning_curve(clf,
X, y, train_sizes, cv=cv,
scoring=scoring, n_jobs = -1)
plot_with_std(n_train, val_train, label='training scores', c='green')
plot_with_std(n_train, val_test, label='validation scores', c='red')
plt.xlabel('Training Set Size'); plt.ylabel(scoring)
plt.legend()
plot_learning_curve(LinearRegression(),X_train_scaled, y_train, scoring='mean_absolute_error')
И тут все довольно хорошо: Кривые сходятся, "зазор" маленький, причем тренд идет к нулю.
__11. Прогноз для тестовой или отложенной выборке __
X_valid_scaled=scaler.transform(X_valid[['Horsepower', 'Weight' , 'Wheelbase' ,'Length'\
,'EngineSize', 'Cylinders' , 'Invoice']])
X_valid['Type2']=label_encoder.transform(X_valid['Type'])
encoded_categorical_columns2 = pd.DataFrame(onehot_encoder.transform(X_valid[['Type2']]))
t2=pd.concat([pd.DataFrame(X_valid_scaled[:,[0,1,2,4,6]], columns=['Horsepower', 'Weight' , 'Wheelbase' \
,'EngineSize', 'Invoice']), encoded_categorical_columns2], axis=1)
X_valid_scaled=t2.drop([0,1,4,5], axis=1)
X_valid_scaled=X_valid_scaled.rename(columns={2: label_encoder.inverse_transform(2), 3: label_encoder.inverse_transform(3)})
X_valid_scaled['Horsepower_Weight']=X_valid_scaled['Weight']*X_valid_scaled['Horsepower']
X_valid_scaled=X_valid_scaled.drop('Invoice', axis=1)
X_valid_scaled.head()
LR=LinearRegression()
LR.fit(X_train_scaled, y_train)
mean_absolute_error(y_valid, LR.predict(X_valid_scaled))
__12. Выводы __
В результате мы получили зависимость расхода топлива от параметров автомобиля, подвердили, что есть нелинейная зависимость от этих параметров. Можно было бы еще подобавлять параметры из категориальных признаков, но, к сожалению, у меня они только ухудшали скор. Как уже говорилось выше, данный прогноз можно применять при проектировании авто, ведь если целевая группа - бюджетный потребитель, то расход топлива - один из основных показателей при выборе авто в такой группе клиентов.