<jupyter_start><jupyter_text>Data exploration, feature engineering и трениране на различни модели за предвиждане на цена. Използван е dataset [Used cars database](https://www.kaggle.com/orgesleka/used-cars-database) от Kaggle. Добавяне на нужните библиотеки и филтриране на warning-ите:<jupyter_code>import sys

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

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score

%matplotlib inline

pd.options.display.max_rows = 15

warnings.filterwarnings('ignore')<jupyter_output><empty_output><jupyter_text>Първо зареждаме данните - csv файл с около 370000 записа.<jupyter_code>original_df = pd.read_csv('data/autos.csv', encoding = "ISO-8859-1")<jupyter_output><empty_output><jupyter_text>Нека погледнем как изглежда dataset-а.<jupyter_code>original_df<jupyter_output><empty_output><jupyter_text>И всички колони и техните типове:<jupyter_code>original_df.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 371528 entries, 0 to 371527
Data columns (total 20 columns):
dateCrawled            371528 non-null object
name                   371528 non-null object
seller                 371528 non-null object
offerType              371528 non-null object
price                  371528 non-null int64
abtest                 371528 non-null object
vehicleType            333659 non-null object
yearOfRegistration     371528 non-null int64
gearbox                351319 non-null object
powerPS                371528 non-null int64
model                  351044 non-null object
kilometer              371528 non-null int64
monthOfRegistration    371528 non-null int64
fuelType               338142 non-null object
brand                  371528 non-null object
notRepairedDamage      299468 non-null object
dateCreated            371528 non-null object
nrOfPictures           371528 non-null int64
postalCode             371528 non-null int64
lastSeen              [...]<jupyter_text>На пръв поглед веднага виждаме колоните, които биха били най-полезни - price, vehicleType, yearOfRegistration, gearbox, powerPS (мощност в конски сили), model, kilometer, fuelType, brand.Да погледнем и допълнителната информация за числовите колони.<jupyter_code>original_df.describe()<jupyter_output><empty_output><jupyter_text>Забелязваме няколко проблема:- в година на регистрация имаме странни записи - 1000 и 9999, трябва да изрежем записите със странни години.- в колоната мощност имаме записи с прекалено малки (0) и големи (20000, вероятно кораб?) стойности.- всички записи имат 0 броя снимки (колоната е ненужна). Проверяваме какво се съдържа в колоните носещи съмнителна информация:<jupyter_code>print(original_df.seller.unique())
print(original_df.abtest.unique())
print(original_df.offerType.unique())
print(len(original_df.postalCode.unique()))<jupyter_output>['privat' 'gewerblich']
['test' 'control']
['Angebot' 'Gesuch']
8150<jupyter_text>- има 2 типа обяви - частни и бизнес - има само 3 записа gewerblich => махаме колоната.- има 2 типа abtest - не знаем какво значи => пропускаме го засега.- има 2 типа оферти - Angebot и Gesuch, като от втората има само 12 записа => махаме колоната.- има 8150 вида пощенски кодове, които са категорни данни, too much features => махаме колоната.<jupyter_code>print(len(original_df[original_df['seller'] == 'gewerblich']))
print(len(original_df[original_df['offerType'] == 'Gesuch']))<jupyter_output>3
12<jupyter_text>Освен тези, ще махнем и някои други ненужни колони.<jupyter_code>df = original_df
df.drop(['nrOfPictures', 'seller', 'abtest', 'offerType', 'dateCrawled', 'lastSeen', 'dateCreated', 'postalCode'] ,axis='columns', inplace=True)<jupyter_output><empty_output><jupyter_text>Сега да проверим колко празни полета имаме във всяка колона:<jupyter_code>df.isnull().sum().sort_values()
print("скоростни кутии - ", df.gearbox.unique())
print("гориво - ", df.fuelType.unique())
print("тип кола - ", df.vehicleType.unique())
print("брой различни модели - ", len(df.model.unique()))<jupyter_output>скоростни кутии -  ['manuell' 'automatik' nan]
гориво -  ['benzin' 'diesel' nan 'lpg' 'andere' 'hybrid' 'cng' 'elektro']
тип кола -  [nan 'coupe' 'suv' 'kleinwagen' 'limousine' 'cabrio' 'bus' 'kombi' 'andere']
брой различни модели -  252<jupyter_text>Има доста празни записи. Ще направим следното:- gearbox, fuelType, vehicleType и model са категорни и процентно няма много липси, затова махаме редовете, където нямат стойност.- малко по-късно ще решим какво да правим с моделите.- изчистваме думичката "andere" (other) от колоните в които се среща.- първо ще пробваме да махнем всички редове, в които липсва notRepairedDamage.<jupyter_code>df = df.dropna()<jupyter_output><empty_output><jupyter_text>Ще махнем и колоната с името, засега няма нужда от усложнения.<jupyter_code>df = df.drop('name', 1)
df = df.drop(df[df.fuelType == 'andere'].index)
df = df.drop(df[df.vehicleType == 'andere'].index)<jupyter_output><empty_output><jupyter_text>Както и всички повтарящи се редове.<jupyter_code>df = df.drop_duplicates()
df<jupyter_output><empty_output><jupyter_text>Останаха 237235 реда.Да пробваме линейна регресия само по числовите колони:<jupyter_code>def get_x_y(dataframe):
    X = dataframe[dataframe.describe().columns]
    X = X.drop('price', 1)
    y = dataframe['price']
    
    print('X: {}\ny: {}'.format(X.shape, y.shape))
    
    return (X, y)
(X, y) = get_x_y(df)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1337)
model = LinearRegression().fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.00025802657984
test score:  0.00268833306831<jupyter_text>Много зле.Да разгледаме стойностите в други колони и да пробваме да ги ограничим.<jupyter_code>df['yearOfRegistration'].hist(bins=100).set_xlabel("Year of registration")<jupyter_output><empty_output><jupyter_text>Виждаме, че преди 1980 има много малко записи, затова ще пробваме да predict-ваме за последните 20-25 години.<jupyter_code>prices = df[(df.price < 250000) & (df.yearOfRegistration < 2016) &(df.yearOfRegistration > 1995)]
prices['price'].hist(bins=100).set_xlabel("Price")<jupyter_output><empty_output><jupyter_text>Също така и че цените на повечето коли са до около 50000.<jupyter_code>powers = df[(df.powerPS < 2000) & (df.yearOfRegistration > 1995) & (df.yearOfRegistration < 2016)]
powers['powerPS'].hist(bins=50).set_xlabel("Power in PS")<jupyter_output><empty_output><jupyter_text>И повечето коли с мощност под 2000 к.с. са всъщност в интервала от 25 до 550.<jupyter_code>df = df[(df.yearOfRegistration <= 2016) & (df.yearOfRegistration >= 1995) 
      & (df.price >= 250) & (df.price <= 50000) 
      & (df.powerPS >= 25)& (df.powerPS <= 550)]

restricted_df = df
(X, y) = get_x_y(restricted_df)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1337)
model = LinearRegression().fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.706338422268
test score:  0.706745226293<jupyter_text>Това показва колко много outlier-и има.Нека пробваме one hot encoding за останалите колони.Няма да one hot encode-ваме модела, защото заема 252 уникални стойности.<jupyter_code>restricted_df['model'] = restricted_df['model'].astype(str)
restricted_df['brand'] = restricted_df['brand'].astype(str)
encoded_restricted_df = restricted_df

vehicleTypes = restricted_df.vehicleType.unique()
gearboxes = restricted_df.gearbox.unique()
fuelTypes = restricted_df.fuelType.unique()
notRepairedDamages = restricted_df.notRepairedDamage.unique()
brands = restricted_df.brand.unique()
#models = restricted_df.model.unique()


for v in vehicleTypes:
    encoded_restricted_df[v] = (encoded_restricted_df.vehicleType == v).astype(float)
    
for g in gearboxes:
    encoded_restricted_df[g] = (encoded_restricted_df.gearbox == g).astype(float)
    
for f in fuelTypes:
    encoded_restricted_df[f] = (encoded_restricted_df.fuelType == f).astype(float)
    
for nrd in notRepairedDamages:
    encoded_restricted_df[nrd] = (encoded_restricted_df.notRepairedDamage == nrd).astype(float)
    
for b in brands:
    encoded_restricted_df[b] = (encoded_restricted_df.brand == b).astype(float)
  
#for m in models:
#    encoded_restricted_df[m] = (encoded_restricted_df.model == m).astype(float)
  
encoded_restricted_df = encoded_restricted_df.drop(['vehicleType', 'gearbox', 'fuelType', 'notRepairedDamage','brand'],  axis=1)



encoded_restricted_df
encoded_restricted_df = encoded_restricted_df.drop('model', 1)
(X, y) = get_x_y(encoded_restricted_df)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1337)
model = LinearRegression().fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.762491524104
test score:  0.762058309058<jupyter_text>Да видим как изглежда разпределението на цената:<jupyter_code>sns.distplot(encoded_restricted_df.price)<jupyter_output><empty_output><jupyter_text>Не изглежда добре. Да пробваме да логаритмуваме.<jupyter_code>sns.distplot(np.log10(encoded_restricted_df['price'] + 0.0000000001))<jupyter_output><empty_output><jupyter_text>Много по-добре.Да пробваме модела така.<jupyter_code>encoded_restricted_df['price'] = np.log10(encoded_restricted_df['price'] + 0.0000000001)
(X, y) = get_x_y(encoded_restricted_df)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1337)
model = LinearRegression().fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.862640714508
test score:  0.862183594169<jupyter_text>10% увеличение.Да пробваме и за мощността?<jupyter_code>sns.distplot(encoded_restricted_df.powerPS)
sns.distplot(np.log2(encoded_restricted_df['powerPS'] + 0.0000000001))
encoded_restricted_df['powerPS'] = np.log2(encoded_restricted_df['powerPS'] + 0.0000000001)
(X, y) = get_x_y(encoded_restricted_df)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1337)
model = LinearRegression().fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.863234786798
test score:  0.862485950084<jupyter_text>Минимално подобрение.Сега да пробваме и с регуляризация.  Ridge<jupyter_code>alphas = [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10]

for a in alphas:
    ridge = Ridge(alpha = a).fit(X_train, y_train)
    print("alpha:", a)
    print("train score:", ridge.score(X_train, y_train))
    print("test score: ", ridge.score(X_test, y_test))<jupyter_output>alpha: 0.001
train score: 0.863234786795
test score:  0.862485951371
alpha: 0.003
train score: 0.863234786777
test score:  0.862485953944
alpha: 0.01
train score: 0.863234786573
test score:  0.862485962947
alpha: 0.03
train score: 0.863234784811
test score:  0.862485988622
alpha: 0.1
train score: 0.863234766101
test score:  0.862486077939
alpha: 0.3
train score: 0.863234630159
test score:  0.862486328676
alpha: 1
train score: 0.863233734009
test score:  0.862487159832
alpha: 3
train score: 0.86323106677
test score:  0.862489200921
alpha: 10
train score: 0.86322411728
test score:  0.862493209736<jupyter_text>Lasso<jupyter_code>alphas = [0.0001, 0.001, 0.01, 0.1, 1]

for a in alphas:
    lasso = Lasso(alpha = a, max_iter=100000).fit(X_train, y_train)
    print("alpha:", a)
    print("Features used:  {}/{}".format(np.sum(lasso.coef_ != 0), np.shape(X_train)[1]))
    print("train score:", lasso.score(X_train, y_train))
    print("test score: ", lasso.score(X_test, y_test))<jupyter_output>alpha: 0.0001
Features used:  49/60
train score: 0.862760792631
test score:  0.862048271484
alpha: 0.001
Features used:  28/60
train score: 0.856437388645
test score:  0.855472754322
alpha: 0.01
Features used:  12/60
train score: 0.813241313233
test score:  0.811622567579
alpha: 0.1
Features used:  3/60
train score: 0.669593466082
test score:  0.669139709138
alpha: 1
Features used:  2/60
train score: 0.344936100143
test score:  0.343930781265<jupyter_text>От регуляризацията няма почти никакъв ефект.Нека пробваме с random forest.Но преди това да покажем как изглеждат коефициентите.<jupyter_code>def linear_regression_features(X, model):
    plt.figure(figsize=(12,8))
    barplot = sns.barplot(x=X.columns, y=model.coef_, orient='vertical')
    plt.setp(barplot.get_xticklabels(), rotation=90)
    plt.grid(True)
linear_regression_features(X, model)
model = RandomForestRegressor(random_state=1337, 
                              criterion="mse").fit(X_train, y_train)
print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))<jupyter_output>train score: 0.963645675543
test score:  0.875995994451<jupyter_text>Забелязваме overfitting.Да пристъпим към последния коз - GridSearchCV.  Следващите 2 стъпки отнемат поне 5-6 минути!<jupyter_code>search = GridSearchCV(model, {'n_estimators': [10, 50],
                             'min_samples_leaf': [1, 3, 5, 10]})
search.fit(X, y)

pd.DataFrame(search.cv_results_)[['rank_test_score', 'mean_test_score', 'params']].sort_values(by='rank_test_score').head(10)
forest = RandomForestRegressor(random_state=1337, 
                                min_samples_leaf=5,
                                n_estimators=500).fit(X_train, y_train)
print("train score:", forest.score(X_train, y_train))
print("test score: ", forest.score(X_test, y_test))<jupyter_output>train score: 0.927102655838
test score:  0.891536963872<jupyter_text>Определено доста по-малък overfitting. Освен това, изпълнението е прекалено бавно - над 10 минути. Да направим и cross validation за моделът на линейната регресия.<jupyter_code>X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1337)

X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=1337)
model = LinearRegression()
model = model.fit(X_train, y_train)

print("train score:", model.score(X_train, y_train))
print("test score: ", model.score(X_test, y_test))
print("val score: ", model.score(X_val, y_val))
model = LinearRegression()

scores = cross_val_score(model, X, y, cv=100, scoring='r2')
print("Mean: {}".format(scores.mean()))
print("Std: {}".format(scores.std()))
print("Min: {}".format(scores.min()))
print("Max: {}".format(scores.max()))<jupyter_output>Mean: 0.8628662093875279
Std: 0.008797494736470813
Min: 0.8426348805577054
Max: 0.8803141689008799
{'fraction_non_alphanumeric': 0.11743630573248408, 'fraction_numerical': 0.07430997876857749, 'mean_word_length': 5.576352530541012, 'pattern_counts': {'":': 0, '<': 103, '<?xml version=': 0, '>': 105, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '530993', 'license': 'MIT', 'n_tokens_mistral': 6529, 'n_tokens_neox': 6012, 'n_words': 1217, 'path': 'HW_1/Task_1/Cars Data Exploration.ipynb', 'repo_name': 'Martinski95/FMI_Machine_Learning', 'size': '15072'}