<jupyter_start><jupyter_text>Importar librerías<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns
import numpy as np

%matplotlib inline<jupyter_output><empty_output><jupyter_text>Cargamos el archivo<jupyter_code>df = pd.read_csv("vehicle_prices.csv")
df.head()
df.shape<jupyter_output><empty_output><jupyter_text>Análisis de los NA's<jupyter_code>df.isnull().sum()<jupyter_output><empty_output><jupyter_text>Hay una posibilidad que los NA's entre las dos columnas estén directamente relacionados.  Distribución de los datos<jupyter_code>df.hist(figsize=(15,17))<jupyter_output><empty_output><jupyter_text>Ninguna de las columnas cuenta con distribución normal, todas se encuentran sesgadas. Por lo mismo voy a utilizar un StandardScaler para poder volver la distribución normal.<jupyter_code>import missingno as msno
msno.matrix(df)<jupyter_output><empty_output><jupyter_text>Confirmamos la sospecha que los missing values están relacionados, entre ambas columnas. El KNN imputer se puede utilizar cuando existen datos faltantes que están relacionados entre sí. Podríamos simplemente aplicar un mean/median/mode imputer, pero sería menos óptimo para el modelo.  Dividir las X's y la Y Quitamos la variable model, dado a que cuenta con numerosos modelos. El problema con esto es que al momento de hacer split la data, corremos riesgo que algunos modelos se encuentren solo en el training set y otros solo en el testing set. Esto puede dar problema al hacer los predictions, ya que no existirían los thetas necesarios para lidiar con los modelos foráneos.<jupyter_code>y = df['price'].copy()
df_2 = df.drop(['price','model'], axis=1) #dejar solo las variables X<jupyter_output><empty_output><jupyter_text>Splitting DataSeparamos el dataset en 70% training y 30% testing, ponemos un random state para reproducibilidad y ponemos el parámetro shuffle como *True*  para que haga shuffle la data<jupyter_code>from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(df_2, y, test_size=0.3, random_state=20, shuffle=True)<jupyter_output><empty_output><jupyter_text>Separamos las columnas categóricas con las númericas<jupyter_code>num_col = df_2.select_dtypes(include='number').columns
cat_col = df_2.select_dtypes(exclude='number').columns
cat_col<jupyter_output><empty_output><jupyter_text>PipelinesUtilizamos pipeline poder aplicar one hot encoding a las categóricas (hace dummify las variables para poder representarse en números y que el modelo de machine learning logre procesarlo), KNN imputer para las dos columnas númericas que tienen NA's, y finalmente un Normalize normalizar la data númerica  Por qué utilizamos normalize y no standard scaler?Sabemos que la mayoría de la data no tiene una forma de campana Gaussiana, por lo cual es más adecuado utilizar el Normalizer. Si la data tuviera una distribución normal, hubieramos utilizado el Standard Scaler<jupyter_code>from sklearn.pipeline import Pipeline
from sklearn.impute import KNNImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import Normalizer

cat_pipeline = Pipeline([
        ('oh_enc', OneHotEncoder())
    ])

num_pipeline = Pipeline([
        ('knn_imputer', KNNImputer()),
        ('normalize_data', Normalizer())
    ])<jupyter_output><empty_output><jupyter_text>El full pipeline contiene los dos pipelines, como también las columnas objetivo a las que se van a aplicar las transformaciones<jupyter_code>from sklearn.compose import ColumnTransformer
full_pipeline = ColumnTransformer([
        ("cat", cat_pipeline, cat_col),
        ("num", num_pipeline, num_col)
    ])
X_train_processed = full_pipeline.fit_transform(X_train)
print(X_train_processed.shape)
X_test_processed = full_pipeline.fit_transform(X_test)
print(X_test_processed.shape)<jupyter_output>(32562, 23)<jupyter_text>ModelsAplicaremos varios modelos, los modelos serán entrenados con el *X_train_processed* set. Después se evaluará la métrica *mean absolute error* sacando las predicciones que saque el modelo con el input de *X_test_processed* y comparándolas con el *y_test*.  Linear RegressionPonemos *fit_intercept=True* para utilizar el intercepto en los cálculos, y ponemos *n_jobs=-1* para que el modelo utilice todos los núcleos de la computadora al procesar la información.<jupyter_code>from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
lin_reg = LinearRegression(fit_intercept=True, n_jobs=-1)
lin_reg.fit(X_train_processed, y_train)
y_test_pred = lin_reg.predict(X_test_processed)

print('Mean Absolute Error:', mean_absolute_error(y_test, y_test_pred))<jupyter_output>Mean Absolute Error: 3438.030372827222<jupyter_text>El error absoluto está alto, pero hay que analizar los demás modelos para ver si se encuentra relativamente alto o bajo.  Decision Tree RegressorEl parámetro de *splitter* sirve para escoger de qué manera se divide el árbol. Normalmente se hace con el método *best* que sería por medio del coeficiente gini que se establecen las divisiones. Sin embargo, existe la opción de hacerlo random puesto que a veces es mejor un método totalmente aleatorio para poder utilizar en la regresión del decision tree. La opción de *max_depth* significa qué tan profundo puede ser el árbol / cuántos niveles puede tener. Dejamos un intervalo de 5,10,15 porque si incrementamos la profundidad corremos riesgo a que el decision tree haga overfit. Finalmente el *min_samples_leaf* se refiere a la cantidad de observaciones que deben de estar presentes en el leaf node para poder considerarse un leaf node.El *random_state* se le puede pasar cualquier número, simplemente es para objetivos de reproducibilidad del modelo. El *criterion* lo dejamos como *mae* que es Mean Absolute Error. Nuestra métrica de decisión de modelo es *Mean Absolute Error* por lo que es adecuado utilizar el mismo. El *CV* se refiere a la cantidad de cross-validations o k-folds que deseamos realizar, lo mantenemos en 3 por temas de tiempo y poder computacional. *Verbose* con valor de 1, como lo dice en la documentación de sklearn despliega: "the computation time for each fold and parameter candidate is displayed". *n_jobs* con valor -1 es para que el modelo utilice todos los procesadores de la computadora al momento de procesar el modelo. *return_train_score* lo dejamos como True, sin embargo esto solo se refleja cuando uno saca *cv_results* para que incluya los training scores.<jupyter_code>from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import GridSearchCV
param_grid = [ {
                'splitter': ['best','random'],
                'max_depth': [5,10,15], 'min_samples_leaf': [3, 10, 12]
}
             ]

# create instance
dtree_r = DecisionTreeRegressor(random_state=20, criterion='mae')

# search for best solution from combinations using a defined score function
grid_search = GridSearchCV(dtree_r, param_grid, cv=3,
                           scoring='neg_mean_absolute_error',
                           return_train_score=True,
                           verbose=1,n_jobs=-1)

grid_search.fit(X_train_processed, y_train)
dtree_best = grid_search.best_estimator_
dtree_best
grid_search.best_score_<jupyter_output><empty_output><jupyter_text>Este modelo es el que ha tenido mejor performance que los demás, pero tenemos que ver el resto para decidir el modelo a utilizar al final.  AdaBoostRegressorEl método de boosting, se refiere a la forma de entrenar modelos de una manera secuencial. Es decir, que va aprende a base de las etapas anteriores. Corrigiendo los errores de los procesos anteriores para tener un modelo más óptimo. Boosting incluye varias etapas de training, y al final se utiliza un group weighted voting para clasificar/hacer regressions. El *learning_rate* en términos simples, detalla qué tan rápido o lento aprende el modelo. Un valor más cercano a 1 aprende más rápido, y si es más alejado de 1 aprende más lento. Los *n_estimators* se refiere al número de sequential trees que se realizarán en el modelo. (Se refiere a árboles ya que el default estimator del modelo es decision tree. El parámetro de *loss* simplemente detalla cómo va a penalizar los errores del modelo, linear siendo el más permisivo y exponential el que más penaliza. El *random_state* se le puede pasar cualquier número, simplemente es para objetivos de reproducibilidad del modelo. El *CV* se refiere a la cantidad de cross-validations o k-folds que deseamos realizar, lo mantenemos en 3 por temas de tiempo y poder computacional. *Verbose* con valor de 1, como lo dice en la documentación de sklearn despliega: "the computation time for each fold and parameter candidate is displayed". *n_jobs* con valor -1 es para que el modelo utilice todos los procesadores de la computadora al momento de procesar el modelo. *return_train_score* lo dejamos como True, sin embargo esto solo se refleja cuando uno saca *cv_results* para que incluya los training scores.<jupyter_code>from sklearn.ensemble import AdaBoostRegressor
param_grid = [ {
                'learning_rate': [0.001,0.1,1],
                'n_estimators': [50,100],
                'loss': ['linear','squared','exponential']
}
             ]
# create instance
ada_r = AdaBoostRegressor(random_state=20)

# search for best solution from combinations using a defined score function
grid_search = GridSearchCV(ada_r, param_grid, cv=3,
                           scoring='neg_mean_absolute_error',
                           return_train_score=True,
                           verbose=1,n_jobs=-1)

grid_search.fit(X_train_processed, y_train)
ada_best = grid_search.best_estimator_
ada_best
grid_search.best_score_<jupyter_output><empty_output><jupyter_text>Este modelo ha sido el peor de los que llevamos hasta ahorita, ya que tiene el Mean Absolute Error más alto.  GradientBoostRegressorEl *random_state* se le puede pasar cualquier número, simplemente es para objetivos de reproducibilidad del modelo. El parámetro *sub_sample* lo dejamos en 0.8, si el valor es menor a cero hace que el modelo reduzca variance/overfitting. *Max_features* lo establecemos como 7, puesto que nuestro dataset simplemente tiene 8 features. Dejamos el número más bajo que el total de features para poder reducir los features más relevantes al modelo.<jupyter_code>from sklearn.ensemble import GradientBoostingRegressor

gradient_r = GradientBoostingRegressor(random_state=20, subsample=0.8, max_features=7)

gradient_r.fit(X_train_processed, y_train)
y_test_pred = gradient_r.predict(X_test_processed)
print('Mean Absolute Error:', mean_absolute_error(y_test, y_test_pred))<jupyter_output>Mean Absolute Error: 2894.147156389215<jupyter_text>El modelo tiene un performance similar al decision tree regressor, por lo que estos dos han sido los mejores hasta ahorita.  KNN Neighbours Regressor El modelo trabaja con la metodología de KNN neighbours, donde las predicciones son realizadas con los vecinos a los que se encuentra más cercano el valor de predicción. El parámetro de *weights* puede tomar forma como uniform, donde toma en cuenta los pesos de todos los neighbors de igual manera, o distance que mientras más cercano sea el neighbor va a tener más influencia que los que se encuentren más lejos. El *algorithm* lo dejamos en auto, el modelo escoge entre *kd_tree*, *ball_tree* o *brute*. Son varios algoritmos que utiliza el modelo y dependiendo de los valores que se le pasen en la función de *fit*, escoge el más apropiado. El *CV* se refiere a la cantidad de cross-validations o k-folds que deseamos realizar, lo mantenemos en 3 por temas de tiempo y poder computacional. *Verbose* con valor de 1, como lo dice en la documentación de sklearn despliega: "the computation time for each fold and parameter candidate is displayed". *n_jobs* con valor -1 es para que el modelo utilice todos los procesadores de la computadora al momento de procesar el modelo. *return_train_score* lo dejamos como True, sin embargo esto solo se refleja cuando uno saca *cv_results* para que incluya los training scores.<jupyter_code>from sklearn.neighbors import KNeighborsRegressor
param_grid = [ {
                'weights': ['uniform','distance']      
}
             ]
# create instance
knn_r = KNeighborsRegressor(algorithm='auto')

# search for best solution from combinations using a defined score function
grid_search = GridSearchCV(knn_r, param_grid, cv=3,
                           scoring='neg_mean_absolute_error',
                           return_train_score=True,
                           verbose=1,n_jobs=-1)

grid_search.fit(X_train_processed, y_train)
knn_best = grid_search.best_estimator_
knn_best
grid_search.best_score_<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.057600251118261, 'fraction_numerical': 0.009259985874597818, 'mean_word_length': 5.01699716713881, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '134419', 'license': 'MIT', 'n_tokens_mistral': 3995, 'n_tokens_neox': 3857, 'n_words': 1484, 'path': 'mlm-project3-Walter-20180006.ipynb', 'repo_name': 'hwalter734/VehiclePricePrediction-Model', 'size': '12743'}