Exploración de los datos

Base de datos

head(vivienda)
## # A tibble: 6 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  1147 Zona O… <NA>        3     250        70            1      3            6
## 2  1169 Zona O… <NA>        3     320       120            1      2            3
## 3  1350 Zona O… <NA>        3     350       220            2      2            4
## 4  5992 Zona S… 02          4     400       280            3      5            3
## 5  1212 Zona N… 01          5     260        90            1      2            3
## 6  1724 Zona N… 01          5     240        87            1      3            3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

dimension de la base de datos

dim(vivienda)
## [1] 8322   13

Valores faltantes

colSums(is.na(vivienda))
##           id         zona         piso      estrato      preciom    areaconst 
##            3            3         2638            3            2            3 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1605            3            3            3            3            3 
##      latitud 
##            3

Descripción de la base de datos

summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Tipos de datos en variables categoricas

Variable tipo

table(vivienda$tipo)
## 
## Apartamento        Casa 
##        5100        3219

Variable zona

table(vivienda$zona)
## 
##  Zona Centro   Zona Norte   Zona Oeste Zona Oriente     Zona Sur 
##          124         1920         1198          351         4726

Variable piso

table(vivienda$piso)
## 
##   01   02   03   04   05   06   07   08   09   10   11   12 
##  860 1450 1097  607  567  245  204  211  146  130   84   83

Variable estrato

table(vivienda$estrato)
## 
##    3    4    5    6 
## 1453 2129 2750 1987

Transformaciones

Eliminación de filas vacias

df_cleaned <- vivienda %>% filter(!is.na(id) & id != "")
colSums(is.na(df_cleaned))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0         2635            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##         1602            0            0            0            0            0 
##      latitud 
##            0

Normalizacion de variables

df_cleaned$piso <- as.numeric(df_cleaned$piso)
head(df_cleaned)
## # A tibble: 6 × 13
##      id zona     piso estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <dbl>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  1147 Zona O…    NA       3     250        70            1      3            6
## 2  1169 Zona O…    NA       3     320       120            1      2            3
## 3  1350 Zona O…    NA       3     350       220            2      2            4
## 4  5992 Zona S…     2       4     400       280            3      5            3
## 5  1212 Zona N…     1       5     260        90            1      2            3
## 6  1724 Zona N…     1       5     240        87            1      3            3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Codificación de variables

print(table(df_cleaned$estrato))
## 
##    3    4    5    6 
## 1453 2129 2750 1987
df_cleaned$E4 <- as.numeric(df_cleaned$estrato==4)
df_cleaned$E5 <- as.numeric(df_cleaned$estrato==5)
df_cleaned$E6 <- as.numeric(df_cleaned$estrato==6)

df_cleaned$T1 <- as.numeric(df_cleaned$tipo=='Casa')

df_cleaned$Z1 <- as.numeric(df_cleaned$zona=='Zona Centro')
df_cleaned$Z2 <- as.numeric(df_cleaned$zona=='Zona Norte')
df_cleaned$Z3 <- as.numeric(df_cleaned$zona=='Zona Oeste')
df_cleaned$Z4 <- as.numeric(df_cleaned$zona=='Zona Sur')

head(df_cleaned)
## # A tibble: 6 × 21
##      id zona     piso estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <dbl>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  1147 Zona O…    NA       3     250        70            1      3            6
## 2  1169 Zona O…    NA       3     320       120            1      2            3
## 3  1350 Zona O…    NA       3     350       220            2      2            4
## 4  5992 Zona S…     2       4     400       280            3      5            3
## 5  1212 Zona N…     1       5     260        90            1      2            3
## 6  1724 Zona N…     1       5     240        87            1      3            3
## # ℹ 12 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   E4 <dbl>, E5 <dbl>, E6 <dbl>, T1 <dbl>, Z1 <dbl>, Z2 <dbl>, Z3 <dbl>,
## #   Z4 <dbl>

Eliminar variables sin relevancia

df_cleaned <- df_cleaned %>% dplyr::select(-id)
head(df_cleaned)
## # A tibble: 6 × 20
##   zona     piso estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <chr>   <dbl>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1 Zona O…    NA       3     250        70            1      3            6 Casa 
## 2 Zona O…    NA       3     320       120            1      2            3 Casa 
## 3 Zona O…    NA       3     350       220            2      2            4 Casa 
## 4 Zona S…     2       4     400       280            3      5            3 Casa 
## 5 Zona N…     1       5     260        90            1      2            3 Apar…
## 6 Zona N…     1       5     240        87            1      3            3 Apar…
## # ℹ 11 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>, E4 <dbl>,
## #   E5 <dbl>, E6 <dbl>, T1 <dbl>, Z1 <dbl>, Z2 <dbl>, Z3 <dbl>, Z4 <dbl>

Imputación de datos

# Subconjunto de datos para "Casa"
dfCasa <- subset(df_cleaned, tipo == "Casa")
dfApto <- subset(df_cleaned, tipo == "Apartamento")
mediaPisoCasa <- round(mean(dfCasa$piso, na.rm = TRUE))
mediaPisoApto <- round(mean(dfApto$piso, na.rm = TRUE))
df_cleaned <- df_cleaned %>%
  dplyr::mutate(piso = ifelse(tipo == "Casa" & is.na(piso), mediaPisoCasa, piso)) %>%
  dplyr::mutate(piso = ifelse(tipo == "Apartamento" & is.na(piso), mediaPisoApto, piso))

colSums(is.na(df_cleaned))
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            0            0            0            0            0         1602 
##       banios habitaciones         tipo       barrio     longitud      latitud 
##            0            0            0            0            0            0 
##           E4           E5           E6           T1           Z1           Z2 
##            0            0            0            0            0            0 
##           Z3           Z4 
##            0            0
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            0            0            0            0            0            0 
##       banios habitaciones         tipo       barrio     longitud      latitud 
##            0            0            0            0            0            0 
##           E4           E5           E6           T1           Z1           Z2 
##            0            0            0            0            0            0 
##           Z3           Z4 
##            0            0

1. Filtro de la base de datos

Realice un filtro a la base de datos e incluya sólo las ofertas de apartamentos. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta.

dfApto <- subset(df_cleaned, tipo == "Apartamento")
head(dfApto, 3)
## # A tibble: 3 × 20
##   zona     piso estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <chr>   <dbl>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1 Zona N…     1       5     260        90            1      2            3 Apar…
## 2 Zona N…     1       5     240        87            1      3            3 Apar…
## 3 Zona N…     1       4     220        52            2      2            3 Apar…
## # ℹ 11 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>, E4 <dbl>,
## #   E5 <dbl>, E6 <dbl>, T1 <dbl>, Z1 <dbl>, Z2 <dbl>, Z3 <dbl>, Z4 <dbl>

2. Análisis exploratorio

Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.

2.1. Martrix de correlación

cor_matrix <- dfApto %>%
  select(preciom, areaconst, banios, habitaciones, parqueaderos) %>%
  cor()

cor_data <- as.data.frame(as.table(cor_matrix))

# Crear el gráfico de calor con valores de correlación
p_heatmap <- ggplot(cor_data, aes(Var1, Var2, fill = Freq)) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = "blue", high = "red", mid = "white", 
                       midpoint = 0, limit = c(-1, 1), space = "Lab", 
                       name = "Correlación") +
  labs(title = "Imágen 2.1. Mapa de calor de correlaciones", x = "", y = "") +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, 
                                   size = 12, hjust = 1)) +
  coord_fixed() +
  geom_text(aes(label = round(Freq, 2)), color = "black", size = 4)  # Añadir valores

# Convertir el gráfico en interactivo con plotly
ggplotly(p_heatmap)

2.2. Grafico de disperción de precio con relación Área construida

p1 <- ggplot(dfApto, aes(x = preciom, y = areaconst)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.2. Relación entre Área Precio y Construida", x = "Precio de la Vivienda", y = "Área Construida")
ggplotly(p1)

2.3. Gráfico de disperción Precio vs Estrato

p1 <- ggplot(dfApto, aes(x = preciom, y = estrato)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.3. Relación entre Precio y Estrato", x = "Precio en millones de pesos", y = "Estrato")
ggplotly(p1)

2.4. Gráfico de disperción Precio vs Número de baños

p1 <- ggplot(dfApto, aes(x = preciom, y = banios)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.4 Relación entre Precio y Número de baños", x = "Precio en millones", y = "Numero de baños")
ggplotly(p1)

2.5. Gráfico de disperción Precio vs Número de habitaciones

p1 <- ggplot(dfApto, aes(x = preciom, y = habitaciones)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.5 Relación entre Precio y Número de habitaciones", x = "Precio en millones", y = "Numero de habitaciones")
ggplotly(p1)

2.6. Distribución de Precios por zona

p2 <- ggplot(dfApto, aes(x = zona, y = preciom, fill = zona)) +
  geom_boxplot() +
  labs(title = "Imagen 2.6. Distribución de Precio por Zona", x = "Zona", y = "Precio de la Vivienda")
ggplotly(p2)

2.7. Relación entre area construida y parqueaderos

p1 <- ggplot(dfApto, aes(x = areaconst, y = parqueaderos)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.7 Relación entre areaconst y Parqueaderos", x = "Area construida", y = "Numero de parqueaderos")
ggplotly(p1)

2.8. Relación entre area construida y baños

p1 <- ggplot(dfApto, aes(x = areaconst, y = banios)) +
  geom_point(colour = '#F8766D', size = 1, shape=5) +
  labs(title = "Imagen 2.7 Relación entre areaconst y Baños", x = "Area construida", y = "Numero de baños")
ggplotly(p1)

2.8. Conclusiones punto 2

  1. Imagen 2.1. Se puede ver que las correlaciones más altas con relación al precio son número de baños y área construida 0.74 y 0.83 respectivamente. Las correlaciones positivas nos indican que al aumentar el área o el numero de baños, el precio del inmueble aumenta.
  2. Imagen 2.1. Aunque no se pide en el punto analizar el numero de parqueaderos, se encuentra una correlación alta y positiva respecto al precio con un valor de 0.74.
  3. Imagen 2.2. Se puede ver que la concentración mas alta de inmuebles esta areaconst < 500 y preciom < 500.
  4. Imagen 2.3. Los precios mas altos se concentran en los estratos 5 y 6.
  5. Imagen 2.4. El mayor numero de inmuebles tiene entre 2 y 5 baños. Y se ve claramente que al aumentar el numero de baños aumentas el precio.
  6. Imagen 2.6. Los precios mas altos se encuentran entre en la Zona Oeste.
  7. Imagen 2.6. La mayor cantidad de datos atípicos se encuentra en las Zona Norte y Zona Sur.
  8. Imagen 2.6. Los precios mas bajos se encuentran entre en la Zona Oriente.

3. Módelo de regresión lineal

Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente R2 y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

3.1. Entrenamiento del modelo

modelo <- lm(preciom ~ areaconst + E4 + E5 + E6 + parqueaderos + banios, data = dfApto)
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + E4 + E5 + E6 + parqueaderos + 
##     banios, data = dfApto)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1570.36   -46.68     0.40    40.22   976.75 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -139.98484    6.62400 -21.133  < 2e-16 ***
## areaconst       1.89670    0.04224  44.899  < 2e-16 ***
## E4             32.24345    6.28857   5.127 3.05e-07 ***
## E5             50.21416    6.40333   7.842 5.37e-15 ***
## E6            193.79512    8.04815  24.079  < 2e-16 ***
## parqueaderos   78.76461    3.73532  21.086  < 2e-16 ***
## banios         37.54780    2.70023  13.905  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 129.5 on 5093 degrees of freedom
## Multiple R-squared:  0.7997, Adjusted R-squared:  0.7995 
## F-statistic:  3390 on 6 and 5093 DF,  p-value: < 2.2e-16

4. Validación de supuestos

  1. Si las demás variables se mantienen estables, el por cada metro cuadrado adicional el precio aumenta 1.89670.
  2. Si el estrato es 4 el precio aumenta 32.24345.
  3. Si el estrato es 5 el precio aumenta 50.21416.
  4. Si el estrato es 6 el precio aumenta 193.79512.
  5. Por cada parqueadero adicional el precio aumenta 78.76461.
  6. Por cada baño adicional el precio aumenta 37.54780.
  7. El \(R^2\) nos indica que aproximadamente el 79.97% de la variabilidad en el precio de las viviendas se explica por las variables del modelo. Esto sugiere que el modelo tiene un buen ajuste, pero hay un 20.03% de la variabilidad que no está siendo explicada por estas variables.
  8. Las predicciones del modelo tienen un error aproximado de 129.5 unidades monetarias comparada con el valor ofertado para el inmueble.
  9. F-statistic es 3390, lo que es bastante alto. Esto indica que al menos una de las variables independientes (predictoras) en el modelo es útil para predecir la variable dependiente (preciom).
  10. El p-value aproximadamente 0, por lo tanto, podemos rechazar la hipótesis nula de que todos los coeficientes de regresión son cero al mismo tiempo. En su conjunto las variables independientes tienen una relación significativa con la variable dependiente (preciom).

4.1 Posibles mejoras al modelo

  1. Pre-procesar los apartamentos con valores extremos por ejemplo:
    1. Casas con pocos metros cuadrados y con preciom bastante altos como se muestra en la imagen 2.2
    2. En la imagen 2.6 se puede ver la gran cantidad de casos atípicos especialmente en la zona norte y sur.
    3. En la imagen 2.7 se puede ver una casa con 10 parqueaderos y 50\(m^2\)
    4. En la imagen 2.8 se puede ver varios apartamentos entre 50\(m^2\) y 60\(m^2\) con 3 y 4 baños. Algo que es poco probable, lo que nos indica o un error en los metros cuadrados de la vivienda o un error al momento del ingreso de la información.

5. Entrenamiento modelo 70/30

set.seed(123) 
n <- nrow(dfApto)
train_indices <- sample(1:n, size = 0.7 * n)

train_data <- df_cleaned[train_indices, ]  # 70% entrenamiento
test_data <- df_cleaned[-train_indices, ]  # 30% validación
modelo_train <- lm(preciom ~ areaconst + E4 + E5 + E6 + parqueaderos + banios, data = train_data)
summary(modelo_train)
## 
## Call:
## lm(formula = preciom ~ areaconst + E4 + E5 + E6 + parqueaderos + 
##     banios, data = train_data)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1267.04   -70.22    -7.20    46.48  1224.32 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -67.13288    7.96801  -8.425  < 2e-16 ***
## areaconst      0.76638    0.02652  28.902  < 2e-16 ***
## E4            54.22494    8.45144   6.416 1.58e-10 ***
## E5           105.84973    8.10811  13.055  < 2e-16 ***
## E6           312.60479    9.60989  32.529  < 2e-16 ***
## parqueaderos  73.63249    3.45341  21.322  < 2e-16 ***
## banios        34.85708    2.60837  13.364  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 163.8 on 3563 degrees of freedom
## Multiple R-squared:  0.7296, Adjusted R-squared:  0.7291 
## F-statistic:  1602 on 6 and 3563 DF,  p-value: < 2.2e-16

6. Predicciones

predicciones_test <- predict(modelo_train, newdata = test_data)

7. Calculo MSE, MAE y \(R^2\)

# Hacer predicciones en el conjunto de validación
predicciones_test <- predict(modelo_train, newdata = test_data)

# Calcular el MSE (Error Cuadrático Medio)
mse <- mean((test_data$preciom - predicciones_test)^2)

# Calcular el MAE (Error Absoluto Medio)
mae <- mean(abs(test_data$preciom - predicciones_test))

# Calcular el R^2
ss_total <- sum((test_data$preciom - mean(test_data$preciom))^2)
ss_residual <- sum((test_data$preciom - predicciones_test)^2)
r_squared <- 1 - (ss_residual / ss_total)

# Mostrar los resultados
cat("El Error Cuadrático Medio (MSE) es:", mse, "\n")
## El Error Cuadrático Medio (MSE) es: 28247.91
cat("El Error Absoluto Medio (MAE) es:", mae, "\n")
## El Error Absoluto Medio (MAE) es: 98.85741
cat("El coeficiente de determinación (R^2) es:", r_squared, "\n")
## El coeficiente de determinación (R^2) es: 0.7537592

7.1 Interpretación

7.1.1. MSE 28247.91

Este valor es relativamente alto, lo que sugiere que el modelo tiene algunos errores en las predicciones. El modelo aunque explica un buen porcentaje, al rededor de 75%, no puede explicar el 25% y esto produce un MSE relativamente alto.

7.1.2. MAE 98.85741%

El MAE indica que, en promedio, el modelo se desvía en aproximadamente 98.86 unidades monetarias de los precios reales. Este valor proporciona una idea más intuitiva del tamaño promedio de los errores en las predicciones. Aunque los errores en el MAE son relativamente pequeños, sugiere que el modelo comete errores consistentes al predecir los precios, pero no penaliza tanto los errores más grandes como lo hace el MSE.

7.1.3. \(R^2\) 0.7538

Este valor nos indica que el modelo esta capturando 75% de la variabilidad de los precios de las viviendas. Esto sugiere que las variables (área construida, estrato, número de parqueaderos y baños) son factores importantes en la determinación del precio pero no explican 25% de la variabilidad.

Fin