#!/usr/bin/env python
# coding: utf-8

# # Predicción precio vivienda con RNA

# Usaremos el set de datos SaratogaHouses del paquete mosaicData de R.  Incluye información de más de 1700 viviendas de NY (Estados Unidos) recogiendo información de 15 variables:
# 
#     price: precio de la vivienda.
#     lotSize: metros cuadrados de la vivienda.
#     age: antigüedad de la vivienda.
#     landValue: valor del terreno.
#     livingArea: metros cuadrados habitables.
#     pctCollege: porcentaje del vecindario con título universitario.
#     bedrooms: número de dormitorios.
#     firplaces: número de chimeneas.
#     bathrooms: número de cuartos de baño (el valor 0.5 hace referencia a cuartos de baño sin ducha).
#     rooms: número de habitaciones.
#     heating: tipo de calefacción.
#     fuel: tipo de alimentación de la calefacción (gas, electricidad o diesel).
#     sewer: tipo de desagüe.
#     waterfront: si la vivienda tiene vistas al lago.
#     newConstruction: si la vivienda es de nueva construcción.
#     centralAir: si la vivienda tiene aire acondicionado.
# 

# Veamos si podemos modelar el precio del alquiler en base a dichos datos y variables

# In[1]:


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
get_ipython().run_line_magic('matplotlib', 'inline')
plt.style.use('fivethirtyeight')

from sklearn.neural_network import MLPRegressor
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.compose import make_column_selector
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import KFold
from sklearn import set_config
import multiprocessing

import warnings
warnings.filterwarnings('ignore')

import os


# In[2]:


os.chdir ("/home/mydoctor/Documents/03.Trabajos/01.C2B/21.Deep Learning - C2B (15h)/datos")
df = pd.read_csv("saratogaHousing.csv")


# In[3]:


df


# In[4]:


# Renombramos las columnas
df.columns = ["precio", "metros_totales", "antiguedad", "precio_terreno",
                 "metros_habitables", "universitarios", "dormitorios", 
                 "chimenea", "banyos", "habitaciones", "calefaccion",
                 "consumo_calefacion", "desague", "vistas_lago",
                 "nueva_construccion", "aire_acondicionado"]


# ### EDA

# In[5]:


df.info()


# Todos las columnas tienen los tipos correctos

# In[6]:


df.isna().sum().sort_values()


# No tenemos problemas de completitud

# In[54]:


# Comprobamos cómo se distruyen los datos de precio de venta

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(6, 3))
sns.histplot(df, x='precio', kde=True,ax=ax)
sns.set_style("white")
ax.set_title("Distribución Precio")
ax.set_xlabel('precio');


# In[8]:


# Pintamos las variables numéricas

fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(20, 15))
axes = axes.flat
columnas_numeric = df.select_dtypes(include=['float64', 'int']).columns
columnas_numeric = columnas_numeric.drop('precio')

for i, colum in enumerate(columnas_numeric):
    sns.histplot(
        data    = df,
        x       = colum,
        stat    = "count",
        kde     = True,
        color   = (list(plt.rcParams['axes.prop_cycle'])*2)[i]["color"],
        line_kws= {'linewidth': 2},
        alpha   = 0.3,
        ax      = axes[i]
    )
    axes[i].set_title(colum, fontsize = 15, fontweight = "bold")
    axes[i].tick_params(labelsize = 15)
    axes[i].set_xlabel("")
    axes[i].set_ylabel("")
    
    
fig.tight_layout()
plt.subplots_adjust(top = 0.9)
fig.suptitle('Distribución variables numéricas', fontsize = 10, fontweight = "bold");


# La variable chimenea adopta pocos estados, en casos como éstos es conveniente tratarla como categórica.

# In[9]:


df.chimenea = df.chimenea.astype("str")
df.chimenea.value_counts()


# In[10]:


# Comprobamos y pintamos las variables categóricas
df.select_dtypes(include=['object']).describe()


# In[11]:


fig, axes = plt.subplots(nrows=3, ncols=3, figsize=(12, 5))
axes = axes.flat
columnas_object = df.select_dtypes(include=['object']).columns

for i, colum in enumerate(columnas_object):
    df[colum].value_counts().plot.barh(ax = axes[i])
    axes[i].set_title(colum, fontsize = 14)
    axes[i].set_xlabel("")

# Se eliminan los axes vacíos
for i in [7, 8]:
    fig.delaxes(axes[i])
    
fig.tight_layout()


# Si alguno de los niveles de una variable cualitativa tiene muy pocas observaciones en comparación a los otros niveles, puede ocurrir que, durante la validación cruzada o bootstrapping, algunas particiones no contengan ninguna observación de dicha clase (varianza cero), lo que puede dar lugar a errores. Para este caso, hay que tener precaución con la variable chimenea. Se unifican los niveles de 2, 3 y 4 en un nuevo nivel llamado "2_mas".

# In[12]:


mapeo = {'2': "2_mas",
               '3': "2_mas",
               '4': "2_mas"}

df['chimenea'] = df['chimenea'] .map(mapeo).fillna(df['chimenea'])

df.chimenea.value_counts().sort_index()


# ### División de los datos en Train y Test

# In[13]:


from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
                                        df.drop('precio', axis = 'columns'),
                                        df['precio'],
                                        train_size   = 0.7,
                                        random_state = 123,
                                        shuffle      = True
                                    )


# In[14]:


print("Partición de entrenamento")
print("-----------------------")
display(y_train.describe())
display(X_train.describe())
display(X_train.describe(include = 'object'))
print(" ")

print("Partición de test")
print("-----------------------")
display(y_test.describe())
display(X_test.describe())
display(X_test.describe(include = 'object'))


# ### Preprocesado y modelado

# In[42]:


# Identificación de columnas numéricas y categóricas
numeric_cols = X_train.select_dtypes(include=['float64', 'int']).columns.to_list()
cat_cols = X_train.select_dtypes(include=['object', 'category']).columns.to_list()


# Transformaciones para las variables numéricas
numeric_transformer = Pipeline(
                        steps=[('scaler', StandardScaler())])

# Transformaciones para las variables categóricas
categorical_transformer = Pipeline(
                            steps=[('onehot', OneHotEncoder(handle_unknown='ignore'))])

# Para tener en cuenta aquellas columnas que no requieren transformación, necesitamos usar el argumento reminder=passthrough
preprocessor = ColumnTransformer(
                    transformers=[
                        ('numeric', numeric_transformer, numeric_cols),
                        ('cat', categorical_transformer, cat_cols)],
                    remainder='passthrough')

# Combinamos los pasos de preprocesado y el modelo en un mismo pipeline
pipe = Pipeline([('preprocessing', preprocessor),
                 ('modelo', MLPRegressor(solver = 'lbfgs', max_iter= 6000))])


# ### Validación cruzada y búsqueda de hiperparámetros

# In[43]:


hiperparametros = {
    'modelo__hidden_layer_sizes': [(10), (20), (10, 10)],
    'modelo__alpha': np.logspace(-3, 3, 10),
    'modelo__learning_rate_init': [0.001, 0.01]
}


# In[44]:


grid = RandomizedSearchCV(
        estimator  = pipe,
        param_distributions = hiperparametros,
        n_iter     = 50,
        scoring    = 'neg_mean_squared_error',
        n_jobs     = multiprocessing.cpu_count() - 1,
        cv         = 5, 
        verbose    = 0,
        random_state = 123,
        return_train_score = True)

grid.fit(X = X_train, y = y_train)


# ### Resultados y evaluación final

# In[50]:


resultados = pd.DataFrame(grid.cv_results_)
resultados.filter(regex = '(param.*|mean_t|std_t)').drop(columns = 'params').sort_values('mean_test_score', ascending = False).head(10)


# In[48]:


modelo_final = grid.best_estimator_
predicciones = modelo_final.predict(X = X_test)
rmse = mean_squared_error(
        y_true = y_test,
        y_pred = predicciones,
        squared = False
       )
print('Error de test (rmse): ', rmse)


# In[51]:


modelo_final['modelo'].get_params()

