1 - Introduccion
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
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
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
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
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
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
- El modelo arroja los siguientes resultados:
| 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
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.