1 - Introduccion

Volver al Inicio



El siguiente trabajo consiste en un análisis preliminar de los datos de la inmobiliaria Properatti luego de una visualización, limpieza y segmentación por CABA (Ver trabajo Aqui) para luego entrenar un modelo de Regresión Logística que nos sirva para predecir el precio en dolares por metro cuadrado.



Librerías

Para utilizar código Python en R Markdown se usa la librería reticulate.

library(reticulate)

Sys.setenv(RETICULATE_PYTHON = 'C:/ProgramData/Anaconda3/python.exe')

use_python('C:/ProgramData/Anaconda3/python.exe')


import pandas as pd
import numpy as np
import plotly.io as pio
import plotly.express as px
import plotly.figure_factory as ff
from time import sleep
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics
from math import sqrt
from sklearn.model_selection import cross_validate
import warnings
import matplotlib.pyplot as plt
import matplotlib.patches

warnings.filterwarnings('ignore')



data_location = "df_caba.csv"
df_caba = pd.read_csv(data_location, sep=",")
print('El data frame contiene: ',df_caba.shape[0], ' filas y ', df_caba.shape[1], ' columnas.')
## El data frame contiene:  30792  filas y  24  columnas.

2 - Lectura del Data Set

Volver al Inicio




df_caba = df_caba.loc[df_caba['place_name'] != 'Capital Federal']
df_caba.reset_index(drop=True, inplace=True)
df_caba.shape
## (29560, 24)

df_caba.head(5)
##    index property_type  ... ambientes parrilla
## 0      0            PH  ...       2.0        0
## 1      1     apartment  ...       2.0        0
## 2      2            PH  ...       3.0        0
## 3      3     apartment  ...       1.0        0
## 4      4     apartment  ...       2.0        0
## 
## [5 rows x 24 columns]

round(df_caba.isnull().sum() / df_caba.shape[0],2).sort_values(ascending = False)
## rooms                         0.52
## ambientes                     0.33
## price_usd_per_m2              0.28
## lat-lon                       0.24
## lat                           0.24
## lon                           0.24
## surface_total_in_m2           0.19
## price_per_m2                  0.15
## currency                      0.10
## price_aprox_local_currency    0.10
## price_aprox_usd               0.10
## price                         0.10
## surface_covered_in_m2         0.06
## geonames_id                   0.04
## image_thumbnail               0.02
## properati_url                 0.00
## description                   0.00
## title                         0.00
## index                         0.00
## property_type                 0.00
## state_name                    0.00
## place_with_parent_names       0.00
## place_name                    0.00
## parrilla                      0.00
## dtype: float64

Se pueden observar dispersiones en los datos que perjudican la estimación de los parámetros de una regresión lineal. La idea principal es homogeneizar los datos lo mejor posible sin alterar la naturaleza de los mismos para generalizar y minimizar los errores.


def getPlotGroupDos(data, number_color):
    x = data.groupby('place_name')['price_usd_per_m2'].std().sort_values(ascending = False).index
    y = data.groupby('place_name')['price_usd_per_m2'].std().sort_values(ascending = False).values
   
    df = pd.DataFrame({'x': x, 'y': y})
    fig = px.bar(df, x=x, y=y,
                title= 'Desvio Standard' + ' m2 ',
                color_discrete_sequence=[px.colors.qualitative.Alphabet[number_color]],
                width=800, height=600,
                template="simple_white"
                )
    return fig.show()
    


getPlotGroupDos(df_caba,  7)

def plot3d(df, x, y, z, color):
    fig = px.scatter_3d(df, 
                        title = "3D Plot",
                        x = x, 
                        y = y, 
                        z = z, 
                        color = color)
    fig.update_layout(template="plotly_dark",
                      width=800,
                      height=700)
    return fig.show()

hist_data    = []
group_labels = []
limite_precio = 9000
for i in range(len(df_caba['place_name'].unique())):
    hist_datas = np.array(df_caba.loc[( df_caba['place_name'].isin([df_caba['place_name'].unique()[i]]))&  (df_caba['price_usd_per_m2'] < limite_precio) ].price_usd_per_m2.value_counts().index)
    if len(hist_datas) == 1: continue
    hist_data.append(hist_datas)
    group_labels.append(df_caba['place_name'].unique()[i])

Distribucion inicial de los precios en dolares por metro cuadrado:


fig = ff.create_distplot(hist_data, group_labels, show_hist=False)
fig.update_layout(title_text='Distribucion del precio en USD por M2')

3 - Limpieza de Datos

Volver al Inicio




df_caba_model = df_caba[['price_usd_per_m2','property_type','place_name', 'price', 'price_aprox_usd', 'surface_total_in_m2', 'surface_covered_in_m2','rooms', 'ambientes', 'parrilla']]
df_caba_model.head(5)
##    price_usd_per_m2 property_type place_name  ...  rooms  ambientes  parrilla
## 0       1127.272727            PH  Mataderos  ...    NaN        2.0         0
## 1       1309.090909     apartment  Mataderos  ...    NaN        2.0         0
## 2               NaN            PH    Liniers  ...    NaN        3.0         0
## 3       3066.666667     apartment   Belgrano  ...    NaN        1.0         0
## 4       3000.000000     apartment   Belgrano  ...    NaN        2.0         0
## 
## [5 rows x 10 columns]

df_caba_model['ambientes'] = df_caba_model.apply(lambda x: x['ambientes'] if x['ambientes'] > 0 else x['rooms'], axis = 1)

df_caba_model.drop(columns=['rooms','price_aprox_usd','price'], inplace = True)

round(df_caba_model.isnull().sum() / df_caba_model.shape[0],2).sort_values(ascending = False)
## price_usd_per_m2         0.28
## surface_total_in_m2      0.19
## ambientes                0.19
## surface_covered_in_m2    0.06
## property_type            0.00
## place_name               0.00
## parrilla                 0.00
## dtype: float64

print('Luego de los drop queda un ',(df_caba_model.dropna().shape[0] / df_caba_model.shape[0]) * 100, '% del Data Set original')
## Luego de los drop queda un  56.123139377537214 % del Data Set original

x1 = np.array(df_caba_model.loc[df_caba_model['place_name'].isin(['Boedo'])].price_usd_per_m2.value_counts().sort_index(ascending = False).index)

hist_data = [x1]
group_labels = ['price_usd_per_m2'] 

fig = ff.create_distplot(hist_data, group_labels, bin_size=400)
fig.show()
index_drop_surface = df_caba_model.loc[(df_caba_model['place_name'].isin(['Boedo'])) & (df_caba_model['surface_covered_in_m2'].isin([324,350,566,600,800]))].index
index_drop_price_boedo = df_caba_model.loc[(df_caba_model['place_name'].isin(['Boedo'])) & (df_caba_model['price_usd_per_m2'] > 3000)].index
index_drop_price_sancristobal = df_caba_model.loc[(df_caba_model['place_name'].isin(['San Cristobal'])) & (df_caba_model['price_usd_per_m2'] > 3000)].index
df_caba_model.drop(index_drop_surface, inplace=True)
df_caba_model.drop(index_drop_price_boedo, inplace=True)
df_caba_model.drop(index_drop_price_sancristobal, inplace=True)
df_caba_model.reset_index(drop=True, inplace=True)
index_drop_surface_total = df_caba_model.loc[(df_caba_model['surface_total_in_m2'] > 800)].index
df_caba_model.drop(index_drop_surface_total, inplace=True)
df_caba_model.reset_index(drop=True, inplace=True)
index_drop_surface_covered = df_caba_model.loc[(df_caba_model['surface_covered_in_m2'] > 800)].index
df_caba_model.drop(index_drop_surface_covered, inplace=True)
df_caba_model.reset_index(drop=True, inplace=True)
index_drop_ambientes = df_caba_model.loc[(df_caba_model['ambientes'] > 10)].index
df_caba_model.drop(index_drop_ambientes, inplace=True)
df_caba_model.reset_index(drop=True, inplace=True)

4 - Visualizaciones Post Limpieza

Volver al Inicio



getPlotGroupDos(df_caba_model,  3)

plot3d(df_caba_model, 'price_usd_per_m2', 'surface_total_in_m2', 'ambientes', 'place_name')
hist_data    = []
group_labels = []

for i in range(len(df_caba_model['place_name'].unique())):
    hist_datas = np.array(df_caba_model.loc[( df_caba_model['place_name'].isin([df_caba_model['place_name'].unique()[i]]))].price_usd_per_m2.value_counts().index)
    if len(hist_datas) == 1: continue
    hist_data.append(hist_datas)
    group_labels.append(df_caba_model['place_name'].unique()[i])
fig = ff.create_distplot(hist_data, group_labels, show_hist=False)
fig.update_layout(title_text='Distribucion del precio en USD por M2')

5 - Variables Dummies

Volver al Inicio



El primer paso para estimar los parámetros del modelo es eliminar todas las filas que contienen al menos un dato nulo ya que el modelo no nos permite tener este tipo de valores.

En el segundo paso se seleccionan solo las observaciones que son departamentos para el modelo y se eliminan las filas de zonas que tienen pocas observaciones.

df_caba_model.dropna(axis=0, inplace = True)
df_caba_model = df_caba_model.loc[df_caba_model['property_type'] == 'apartment']
df_caba_model.place_name.value_counts(ascending=True)[0:10].index
## Index(['Catalinas', 'Villa Real', 'Villa Soldati', 'Velez Sarsfield',
##        'Parque Chas', 'Versalles', 'Pompeya', 'Villa Santa Rita',
##        'Parque Avellaneda', 'Palermo Viejo'],
##       dtype='object')
values = ['Catalinas', 'Villa Real', 'Villa Soldati', 'Velez Sarsfield',
       'Parque Chas', 'Versalles', 'Pompeya', 'Villa Santa Rita',
       'Parque Avellaneda', 'Agronomía']
df_caba_model = df_caba_model[~df_caba_model.place_name.isin(values)]

La matriz de correlación nos sirve para ver si hay variables que se correlacionan para poder evitar multicolinealidad en el modelo:


import seaborn as sns
correlation_mat = df_caba_model[['price_usd_per_m2', 'surface_total_in_m2', 'surface_covered_in_m2',
       'ambientes']].corr()
sns.heatmap(correlation_mat, annot = True)
plt.title("Matriz de Correlacion")
plt.show()

df_caba_model_final = df_caba_model.dropna(axis=0)
df_caba_model_final.reset_index(inplace = True,drop=True)

Se crean variable dummies para las features categóricas y se separa el data set en training y set

df_caba_model_dummies = pd.get_dummies(df_caba_model_final, drop_first = True)

6 - Modelo

Volver al Inicio



X = df_caba_model_dummies.drop(columns = ["price_usd_per_m2",'surface_total_in_m2'])
y = df_caba_model_dummies[["price_usd_per_m2"]]

Xtrain, Xtest, ytrain, ytest = train_test_split(X,y)

Se instancia y entrena el modelo de Linear Regression:

modelo = LinearRegression()
modelo.fit(Xtrain,ytrain)
## LinearRegression()

Analizo los Betas

diccionario = dict(zip(df_caba_model_dummies.drop(columns = ["price_usd_per_m2",'surface_total_in_m2']).columns,[round(value,2) for value in modelo.coef_.tolist()[0]]))

La presencia de Puerto Madero hace incrementar la variable dependiente


diccionario['place_name_Puerto Madero']
## 3477.36

Para evaluar el modelo se utiliza el MAE, la validación cruzada y el r2. El MAE es el elegido ya que nos da un valor robusto para nuestro analisis, el valor absoluto del error promedio del modelo

El error absoluto medio (Mean Absolut Error o MAE) es la media del valor absoluto de los errores:

\[ \frac 1n\sum_ {i = 1}^n |y_i-\hat{y}_i| \]

pred = modelo.predict(Xtest)

from sklearn.model_selection import cross_validate

cv_results = cross_validate(modelo, Xtrain, ytrain, cv=5, scoring=('r2', "neg_mean_absolute_error"))

print ('MAE:', metrics.mean_absolute_error(ytest, pred).round(2))
## MAE: 433.17
print ('R2:', metrics.r2_score(ytest, pred).round(2))
## R2: 0.6
print ('MAE CV:', cv_results["test_neg_mean_absolute_error"].mean().round(2)*-1)
## MAE CV: 434.15
print ('R2 CV:', cv_results["test_r2"].mean().round(2))
## R2 CV: 0.59
Modelo MAE R2 MAE CV R2 CV
Regresión Logística Nº1 434 0.60 432 0.59
pred_X = modelo.predict(X)
df_caba_model_dummies["Prediccion"] = pred_X
df_caba_model_dummies[["Prediccion", "price_usd_per_m2"]].sort_values(by = 'Prediccion').head(5)
##        Prediccion  price_usd_per_m2
## 4178  1152.495490        784.090909
## 4179  1152.495490        784.090909
## 4546  1161.954182        639.175258
## 3849  1178.810435       1166.666667
## 2728  1178.810435        405.000000

7 - Conclusiones

Volver al Inicio



Se realizo una ultima limpieza del set de datos seleccionando solo los departamentos y las localidades que tienen mas de 20 observaciones. El modelo arroja resultados interesantes teniendo en cuenta las dificultades al momento de tratar valores atípicos ya que estos mismos son perjudiciales para un modelo de regresión lineal múltiple que busca hacer una generalización lo mejor posible para los datos.

Las pruebas de validación cruzada son consistentes con el primer resultado re R2 y error cuadrático medio. Se puede concluir que es un modelo en donde las variables independientes explican un 60 % de la variabilidad del precio en dolares por metro cuadrado y el error promedio de estimación es de 430 Usd el metro cuadrado.