1 Anexos del Estudio

## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   zona = col_character(),
##   ..   piso = col_character(),
##   ..   estrato = col_double(),
##   ..   preciom = col_double(),
##   ..   areaconst = col_double(),
##   ..   parqueaderos = col_double(),
##   ..   banios = col_double(),
##   ..   habitaciones = col_double(),
##   ..   tipo = col_character(),
##   ..   barrio = col_character(),
##   ..   longitud = col_double(),
##   ..   latitud = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

1.1 Escenario de Estudio

La empresa inmobiliaria C&A (Casas y Apartamentos) ubicada en la ciudad de Cali, requiere crear un modelo que permita facilitar a sus asesores la búsqueda de inmuebles con características especificadas por los clientes, y de esta manera lograr optimizar el cierre de negocios.

Con este modelo, María la propietaria de la compañía C&A pretende responder al requerimiento de una empresa internacional, que pretende relocalizar a dos ejecutivos y sus familias en la ciudad de Cali y requiere adquirir dos inmuebles con caractrísticas y condiciones específicas para cada una, así:

Característica de los inmuebles Vivienda 1 Vivienda 2
Tipo Casa Apartamento
Área Construida (m2) 200 300
Parqueaderos 1 3
Baños 2 3
Habitaciones 4 5
Estrato 4 o 5 5 o 6
Zona Norte Sur
Crédito Preaprobado 350 Mill 850 Mill

1.2 Análisis Exploratorio de Datos

1.2.1 1. Descripción de variables

A continuación se detallan las variables que componen el dataset vivienda y su correspondiente clasificación según el contexto, contenido y naturaleza propia de cada una.

Nombre Variable Descripción de la Variable Contenido Variable Clasificación por Naturaleza
id Identificación única de la propiedad Número que identifica la propiedad dentro del data set Cuantitativa - discreta
zona Zona geográfica en la que se encuentra ubicado el inmueble. zona centro, zona norte, zona oeste, zona oriente, zona sur. Cualitativa - Nominal
piso Numero de pisos que conforman el inmueble Numero entre 1 y 12 Cuantitativa - discreta
estrato Estrato socioeconómico Valores de estrado desde el 3 al 6 Cualitativa - Ordinal
preciom Precio del inmueble expresado en millones Valores de los inmuebles Cuantitativa - continua
areaconst Área construida del inmueble Áreas de los inmuebles Cuantitativa - continua
parqueaderos Número de parqueaderos del inmueble Valores entre 1 a 10 Cuantitativa - discreta
banios Número de baños en el inmueble Valores entre 0 a 10 Cuantitativa - discreta
habitaciones Número de habitaciones en el inmueble Valores entre 0 a 10 Cuantitativa - discreta
tipo Tipo de inmueble Apartamento, Casa Cualitativa - nominal
barrio Barrio donde se encuentra ubicada la vivienda Nombre del barrio Cualitativa - nominal
longitud Coordenada geográfica Coordenada geográfica Cuantitativa - continua
latitud Coordenada geográfica Coordenada geográfica Cuantitativa - continua

1.2.2 2. Análisis e imputación de datos faltantes

pander(colSums(is.na(datos_vivienda)/8322*100))
Table continues below
id zona piso estrato preciom areaconst parqueaderos
0.03605 0.03605 31.7 0.03605 0.02403 0.03605 19.29
banios habitaciones tipo barrio longitud latitud
0.03605 0.03605 0.03605 0.03605 0.03605 0.03605

Durante la evaluación de calidad de los datos presentes en el dataset, al observar el procentaje de datos faltantes por cada caracteristica, se identificó que la variable piso presenta un 31.7% de datos faltantes y la variable parqueaderos presenta un 19.29%. Adicionalmente, se observa que las demás variables presentan alrededor del 0.04%.

Graficamente esta composición puede verse aqui:

## 
##  Variables sorted by number of missings: 
##      Variable        Count
##          piso 0.3169911079
##  parqueaderos 0.1928622927
##            id 0.0003604903
##          zona 0.0003604903
##       estrato 0.0003604903
##     areaconst 0.0003604903
##        banios 0.0003604903
##  habitaciones 0.0003604903
##          tipo 0.0003604903
##        barrio 0.0003604903
##      longitud 0.0003604903
##       latitud 0.0003604903
##       preciom 0.0002403268

Aunque existen técnicas de imputación para manejar datos faltantes en los dataset, en el caso de la variable piso se requeriría imputar más del 30% de los valores, lo que implicaría introducir una gran cantidad de información artificial, y podría sesgarse los resultados de los modelos de regresión multiple que se usarán en el presente informe, reduciendo la confiabilidad de las inferencias realizadas.

Buscando entonces priorizar la integridad del modelo y la validez de los resultados que de él se desprendan, se ha decidido excluir la variable piso del análisis.

Adicionalmente, en este análisis de los datos faltantes, se puede observar que dentro del dataset se presentan 3 observaciones que no contienen datos en ninguna característica. Estas serán eliminadas también del dataset.

## # A tibble: 3 × 12
##      id zona  estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <dbl> <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1    NA <NA>       NA      NA        NA           NA     NA           NA <NA> 
## 2    NA <NA>       NA      NA        NA           NA     NA           NA <NA> 
## 3    NA <NA>       NA     330        NA           NA     NA           NA <NA> 
## # ℹ 3 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>

Este ejercicio de eliminación de observaciones y características redujo la dimensión del dataset a 8.319 observaciones, quedando únicamente la variable parqueaderos con datos faltantes.

## [1] 8319   12

## 
##  Variables sorted by number of missings: 
##      Variable     Count
##  parqueaderos 0.1925712
##            id 0.0000000
##          zona 0.0000000
##       estrato 0.0000000
##       preciom 0.0000000
##     areaconst 0.0000000
##        banios 0.0000000
##  habitaciones 0.0000000
##          tipo 0.0000000
##        barrio 0.0000000
##      longitud 0.0000000
##       latitud 0.0000000

1.2.3 3. Análisis de ubicación geográfica de las zonas - relocalización

Como se observó en el ejercicio de Oferta Inmobiliaria desarrollado en la Unidad 1 del presente curso, el data set de vivienda presenta inconsistencias geográficas que pueden verse reflejadas al ubicar cada vivienda sobre el mapa de la ciudad de Cali. Se observan varias observaciones ubicadas en zonas de la que no corresponden con la clasificación asignada. Por ejemplo, algunos puntos etiquetados como “Zona Sur” aparecen geográficamente en la “Zona Centro” o “Zona Norte”, lo que sugiere un error en la codificación de la zona, la latitud/longitud o el estrato.

Dado que el ejercicio propuesto en esta unidad corresponde a análisis y modelación en donde la variable Zona cobra alta relevancia, para corregir esta ubicación de las viviendas, procederé a realizar un ejercicio que me permita realizar la re localización de las viviendas en la zona más adecuada según la latitud y longitud de cada punto, usando la distancia Haversine para calcular la distancia real sobre la superficie terrestre y luego aplicando un algoritmo de clustering para que de esta manera las viviendas queden ubicadas según los puntos geodésicos.

Se decide que se crearán 5 cluster para ajustar el dataset en la misma medida que las zonas originales, Zona Norte, Zona Sur, Zona Oriente, Zona Oeste, Zona Centro

Luego de este tratamiento, la zonas representadas por clusters quedarán asignadas asi: Zona Norte (Cluster 2), Zona Oeste (Cluster 1), Zona Oriente (Cluster 3), Zona Centro (Cluster 4), Zona Sur (Cluster 5) en la nueva variable *nueva_zona*

datos_vivienda2 <- datos_vivienda2 %>%
  mutate(
    nueva_zona = case_when(
      cluster == 2 ~ "Zona Norte",
      cluster == 1 ~ "Zona Oeste",
      cluster == 3 ~ "Zona Oriente",
      cluster == 4 ~ "Zona Centro",
      cluster == 5 ~ "Zona Sur",
      TRUE ~ "Sin Asignar" 
    )
  )

2 Anexos - Análisis VIVIENDA 1

2.1 1. Filtro Casa - Zona Norte

Supuesto de ubicación: Como se explicó en la parte superior del ejercicio, las zonas se recalcularon según un algoritmo de cluster tomando como ciertos los datos de longitud y latitud de cada punto.

# Filtrar por tipo y zona   
casas_norte <- subset(datos_vivienda2,                                                             tipo == "Casa" & nueva_zona == "Zona Norte")   
head(casas_norte,3) 
## # A tibble: 3 × 14
##      id zona    estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <dbl> <chr>     <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1  1209 Zona N…       5     320       150            2      4            6 Casa 
## 2  1592 Zona N…       5     780       380            2      3            3 Casa 
## 3   141 Zona N…       3     230       160           NA      2            3 Casa 
## # ℹ 5 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   cluster <int>, nueva_zona <chr>

Revisando la información relacionada a las viviendas tipo Casa y que están ubicadas en la Zona Norte de la ciudad de Cali, se observan 660 viviendas que cumplen con esta característica.

## [1] 660  14

La representación gráfica de las Casas ubicadas en la Zona Norte según los cluster de zona, se verifica en el siguiente mapa de la ciudad de Cali.

2.2 2. División del set de datos en entrenamiento y test

Antes de realizar el proceso de imputación de faltantes y validación de outliers, y para garantizar la validez del modelo predictivo y evitar problemas de sobreajuste, se realizará la separación del conjunto de datos en dos subconjuntos: entrenamiento (train) y prueba (test). Esta división permite evaluar el desempeño del modelo sobre datos no vistos previamente, reduciendo el sesgo y mejorando la capacidad de generalización.

La proporción de dicha separación será del 80% para entrenamiento y del 20% para prueba, asegurando una representación adecuada de las variables relevantes en ambos subconjuntos dado que no se cuenta con una cantidad de datos muy significativa, únicamente 660 observaciones.

set.seed(123)
split_norte = sample.split(casas_norte$preciom, SplitRatio = 0.8)
casas_norte_train = subset(casas_norte, split_norte == TRUE)
casas_norte_test = subset(casas_norte, split_norte == FALSE)

2.3 3. Análisis e imputación de datos faltantes

En el sector inmobiliario, las características de los inmuebles presentan variaciones significativas según su ubicación geográfica (zona) y el estrato en el que se encuentran ubicadas. Por ello, se propone imputar los valores faltantes de la variable parqueaderos utilizando estadísticas específicas de los estratos a los que pertenece el dato faltante, con el fin de preservar la coherencia contextual del análisis.

Para las viviendas tipo Casa ubicadas en la Zona Norte, los datos estadísticos mostrarían una mediana = 2.

str(casas_norte_train)
## tibble [546 × 14] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:546] 1209 141 243 504 604 ...
##  $ zona        : chr [1:546] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ estrato     : num [1:546] 5 3 3 3 5 5 5 5 4 3 ...
##  $ preciom     : num [1:546] 320 230 190 180 520 395 460 780 420 280 ...
##  $ areaconst   : num [1:546] 150 160 435 120 455 165 319 380 265 148 ...
##  $ parqueaderos: num [1:546] 2 NA NA NA NA NA NA NA NA 2 ...
##  $ banios      : num [1:546] 4 2 0 3 5 4 5 3 6 4 ...
##  $ habitaciones: num [1:546] 6 3 0 3 4 4 4 3 7 4 ...
##  $ tipo        : chr [1:546] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:546] "acopi" "acopi" "acopi" "acopi" ...
##  $ longitud    : num [1:546] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:546] 3.48 3.45 3.44 3.47 3.46 ...
##  $ cluster     : int [1:546] 2 2 2 2 2 2 2 2 2 2 ...
##  $ nueva_zona  : chr [1:546] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
pander(colSums(is.na(casas_norte_train)/546*100))
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 36.81 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0
## La mediana de la variable parqueaderos en las Casa de la Zona Norte: 2

Al realizar el análisis del dataset por estrato, la mediana de la variable parqueadero presenta variaciones así:

# Estadísticas por zona
stats_estrato_n <- casas_norte_train%>%
  group_by(estrato) %>%
  summarise(
    n = n(),
    parqueaderos_media_n = mean(parqueaderos, na.rm = TRUE),
    parqueaderos_mediana_n = median(parqueaderos, na.rm = TRUE),
)

knitr::kable(stats_estrato_n, caption = "Estadísticas por Estrato")
Estadísticas por Estrato
estrato n parqueaderos_media_n parqueaderos_mediana_n
3 270 1.470588 1
4 97 1.945205 2
5 158 2.363636 2
6 21 3.142857 2

Con el valor de la mediana de parqueadero por estrato, se realiza la imputación de los casos faltantes que corresponden a la característica parqueadero, dando como resultado un dataset sin datos faltantes.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   1.000   1.733   2.000  10.000
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 0 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0

2.4 4. Análisis e imputación de datos atípicos

Para identificar los datos atípicos dentro del data set se construirá una nueva característica que relacione las variables precio y área construida, de tal manera que puedan identificarse en función de esa relación, si los precios están desfasados con relación a los demás inmuebles de la respectiva zona.

# Crear columna precio_m2
casas_norte_train2$precio_m2 <- casas_norte_train2$preciom / casas_norte_train2$areaconst

head(casas_norte_train2[, c("id", "nueva_zona", "precio_m2")])
## # A tibble: 6 × 3
##      id nueva_zona precio_m2
##   <dbl> <chr>          <dbl>
## 1  1209 Zona Norte     2.13 
## 2   141 Zona Norte     1.44 
## 3   243 Zona Norte     0.437
## 4   504 Zona Norte     1.5  
## 5   604 Zona Norte     1.14 
## 6  1840 Zona Norte     2.39
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_train2$estrato <- as.factor(casas_norte_train2$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte <- plot_ly(
  data = casas_norte_train2,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte

2.4.1 a. Análisis Outliers estrato 3:

## [1]  3 13 28
## # A tibble: 3 × 6
##      id preciom areaconst habitaciones parqueaderos banios
##   <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1   243     190       435            0            1      0
## 2    77     190       410            2            1      2
## 3    97     220       450            7            1      4

De este análisis, se observa que el dataset completo presenta registros con cero en las variables banios y habitaciones, lo que no es real en viviendas tipo Casa. Se decide eliminar este tipo de datos del dataset para que no generen ruido en el modelo.

which(casas_norte_train2$banios == 0 | casas_norte_train2$habitaciones == 0)
## [1]   3  16  36 146 148 215 235 302 517
## # A tibble: 6 × 6
##      id preciom areaconst habitaciones parqueaderos banios
##   <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1   243     190      435             0            1      0
## 2   105     450      162.            0            1      2
## 3  1816     750      183             0            2      0
## 4   103     380      177             0            1      0
## 5    69     165       80             0            1      0
## 6  2905     530      455             4            2      0

Se procede a eliminar del dataset estos datos por considerarse atípicos y no tener ningún criterio para imputarlos:

casas_norte_train3 <- casas_norte_train2[-which(casas_norte_train2$banios == 0 | casas_norte_train2$habitaciones == 0), ]
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_train3$estrato <- as.factor(casas_norte_train3$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte3 <- plot_ly(
  data = casas_norte_train3,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte3

Continuando con el análisis del estrato 3, puede verse que se tienen viviendas con valores de precio_m2 mayores a 4.42 mill, dato que supera incluso los mayores valores en estrato 6, lo que no es común. Se procede a revisar los casos puntuales.

which(casas_norte_train3$precio_m2 > 3.9 & casas_norte_train2$estrato == 3)
## [1] 120 309 320 537
head(casas_norte_train3[c(120, 309,320, 537), c("id", "barrio", "precio_m2", "preciom", "areaconst", "habitaciones", "parqueaderos", "banios")])
## # A tibble: 4 × 8
##      id barrio      precio_m2 preciom areaconst habitaciones parqueaderos banios
##   <dbl> <chr>           <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1   719 el sena          4.43     310       70             7            1      3
## 2   181 pance            4.17    1250      300             4            2      5
## 3   573 portales d…      4.92     295       60             2            1      1
## 4    55 zona sur         4.32    1550      359.            5            2      6

De este análisis se observa que son viviendas que en su latitud y longitud presentan datos que los ubicaron por cluster en la zona norte, pero que algunos de ellos son de la zona sur.

Revisando el detalle se observa que algunos no corresponden al estrato 3 que es el objeto de análisis, lo que puede generar que se presenten como outlier.

  • El barrio “El Sena” es estrato 2, se dejará sin tratamiento dado que la base de datos solo incluye estratos iguales o superiores a 3.

  • El barrio “Pance” está ubicado en la zona sur, se eliminará de la base de datos.

  • El barrio “Portales de Comfandi” es estrato 3, continúa igual.

  • El barrio “zona sur” contiene variedad de estratos, pero por el nombre también se eliminará para el estudio.

casas_norte_train4 <- casas_norte_train3
which(casas_norte_train3$barrio == "pance" | casas_norte_train3$barrio == "zona sur")
## [1] 305 306 307 308 309 537
casas_norte_train4 <- casas_norte_train3[-which(casas_norte_train3$barrio == "pance" | casas_norte_train3$barrio == "zona sur"), ]
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_train4$estrato <- as.factor(casas_norte_train4$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte4 <- plot_ly(
  data = casas_norte_train4,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte4

Las viviendas que continúan con un valor de precio por metro cuadrado mayor a 4.4 mill se eliminarán del data set dado que los valores no corresponden a viviendas estrato 3.

which(casas_norte_train4$precio_m2 > 4.4 & casas_norte_train4$estrato == 3)
## [1] 120 315 524
head(casas_norte_train4[c(120, 315, 524), c("id", "barrio", "precio_m2", "preciom", "areaconst", "habitaciones", "parqueaderos", "banios")])
## # A tibble: 3 × 8
##      id barrio      precio_m2 preciom areaconst habitaciones parqueaderos banios
##   <dbl> <chr>           <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1   719 el sena          4.43     310        70            7            1      3
## 2   573 portales d…      4.92     295        60            2            1      1
## 3   502 zona norte       6.33     190        30            3            1      2
casas_norte_train5 <- casas_norte_train4[-which(casas_norte_train4$precio_m2 > 4.4 & casas_norte_train4$estrato == 3), ]
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_train5$estrato <- as.factor(casas_norte_train5$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte5 <- plot_ly(
  data = casas_norte_train5,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte5

2.4.2 b. Análisis Outliers estrato 5:

Puede verse que la mediana del estrato 5 está reflejando un menor valor que la mediana del estrato 4, lo que no es normal en el mercado inmobiliario.

which(casas_norte_train5$precio_m2 < 0.9 & casas_norte_train5$estrato == 5)
## [1] 115 187 217
head(casas_norte_train5[c(115, 187, 217), c("id", "barrio", "precio_m2", "preciom", "areaconst", "habitaciones", "parqueaderos", "banios")])
## # A tibble: 3 × 8
##      id barrio    precio_m2 preciom areaconst habitaciones parqueaderos banios
##   <dbl> <chr>         <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1  4349 el bosque     0.547     650      1188            6            4      6
## 2  3084 la flora      0.802     449       560            5            4      4
## 3  3346 la flora      0.808     420       520            6            4      4

Consultando el estrato real de los barrios a los que pertenecen estas viviendas, se identifica que algunos están mal ubicados en el estrato 5.

  • El barrio “El Bosque” es estrato 3, se actualizará en el dataset a estrato 3.

  • El barrio “La Flora” tiene viviendas de estrato 4 y 5. Los valores por metro cuadrado de las viviendas ubicadas en estrato 5 que sean inferiores al valor del primer cuartil del estrtao 4 (1.36 Mill) para las viviendas de el barrio “la flora”, serán reubicados en el estrato 4.

casas_norte_train6 <- casas_norte_train5
casas_norte_train6$estrato[casas_norte_train6$barrio == "el bosque"] <- 3
flora4 <- subset(casas_norte_train6, casas_norte_train6$estrato == 4 & casas_norte_train6$barrio =="la flora")

summary(flora4$precio_m2)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.154   1.366   1.587   1.593   1.813   2.111
casas_norte_train6$estrato[casas_norte_train6$precio_m2 < 1.36 & casas_norte_train6$barrio == "la flora" & casas_norte_train6$estrato == 5] <- 4
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_train6$estrato <- as.factor(casas_norte_train6$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte6 <- plot_ly(
  data = casas_norte_train6,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte6

El análisis de la distribución del precio por m2 y Estrato en las Casas de la Zona Norte de la ciudad de Cali evidencian una relación positiva entre el nivel socioeconómico y el valor de las viviendas. A medida que el estrato aumenta (de 3 a 6), se observa un incremento progresivo en la mediana del precio, así como una mayor dispersión en los valores de las viviendas, especialmente en el estrato 6. Esto sugiere que los hogares de estratos altos no solo tienden a tener viviendas más costosas, sino también una mayor heterogeneidad en el mercado inmobiliario. En contraste, los estratos 3 y 4 presentan rangos de precios más acotados y concentrados, lo que indica menor variabilidad en las características de las viviendas ofertadas.

2.5 5. Análisis de correlación entre variables

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.

# Seleccionar solo las columnas numéricas del data set
casas_numericas <- casas_norte_train6[, sapply(casas_norte_train6, is.numeric)]

# Eliminar las columnas que no quieres en la correlación
columnas_a_eliminar <- c("id", "cluster", "precio_m2", "latitud", "longitud")

casas_numericas <- casas_numericas[, !names(casas_numericas) %in% columnas_a_eliminar]

Veamos la correlación entre las variables a través de un mapa de calor

# 3. Calcular la matriz de correlación
# use = "complete.obs" maneja los valores NA
matriz_corr_n <- cor(casas_numericas, use = "complete.obs")

# 4. (Opcional pero recomendado) Redondear los valores para una mejor visualización
matriz_corr_n <- round(matriz_corr_n, 2)

# 5. Crear el gráfico de la matriz de correlación (modificación en 'colorscale')
fig_corr_n <- plot_ly(
  z = matriz_corr_n,               # La matriz de correlación va en el eje 'z'
  x = colnames(matriz_corr_n),     # Nombres de las variables en el eje X
  y = rownames(matriz_corr_n),     # Nombres de las variables en el eje Y
  type = "heatmap",              # Tipo de gráfico
  colorscale = "Blues"           # <--- ¡CAMBIA ESTA LÍNEA!
) %>%
layout(
  title = "Matriz de Correlación de Variables Numéricas"
)

# 6. Mostrar el gráfico
fig_corr_n
# Creamos un nuevo data frame para el gráfico con nombres más cortos
casas_para_grafico <- casas_numericas %>%
  rename(
    Precio = preciom,
    Área = areaconst,
    Parq = parqueaderos,
    Baños = banios,
    Hab = habitaciones
  )

# 3. Crear el gráfico usando el data frame con los nombres nuevos
ggp <- ggpairs(
  casas_para_grafico, # Usamos el nuevo data frame
  upper = list(continuous = "cor"),
  lower = list(continuous = "points"),
  diag = list(continuous = "densityDiag")
) +
  theme_bw()+
   theme(
    # Ajustar el texto del eje X (números)
    axis.text.x = element_text(size = 5, angle = 90, hjust = 1),
    
    # Ajustar el texto del eje Y (números)
    axis.text.y = element_text(size = 5)
  )

# 4. Convertir a plotly y mostrar
figura_final <- ggplotly(ggp)

figura_final

Veamos la dispersion de estas relaciones entre la variable precio y las demás variables numéricas y su comportamiento según el estrato.

# 2. PREPARAR LOS DATOS PARA LA GRILLA (tu código original, sin cambios)
datos_para_grilla <- casas_norte_train6 %>%
  select(preciom, Área = areaconst, Parq = parqueaderos, Baños = banios, Hab = habitaciones, estrato) %>%
  pivot_longer(
    cols = c("Área", "Parq", "Baños", "Hab"),
    names_to = "Variable",
    values_to = "Valor"
  ) %>%
  mutate(estrato = as.factor(estrato))

# 3. CREAR EL GRÁFICO ESTÁTICO y guardarlo en una variable
p <- ggplot(datos_para_grilla, aes(x = Valor, y = preciom, color = estrato)) +
  geom_point(alpha = 0.7) +
  facet_wrap(~ Variable, ncol = 2, scales = "free_x") +
  labs(
    title = "Precio vs. Características de la Vivienda",
    subtitle = "Dispersión coloreada por Estrato",
    y = "Precio (en millones)",
    x = "Valor de la Característica",
    color = "Estrato"
  ) +
  theme_minimal()

# 4. --- PASO FINAL: CONVERTIR A GRÁFICO INTERACTIVO ---
figura_interactiva <- ggplotly(p)

# 5. Mostrar la figura interactiva
figura_interactiva

Del análisis de correlación entre el precio de las viviendas (preciom) y las variables numéricas puede verse que:

Preciom vs areaconst
  • Un coeficiente de 0.766 indica una correlación positiva fuerte.

  • El diagrama de dispersión confirma visualmente la relación. Se observa una clara tendencia ascendente y a medida que aumenta el Área, el Precio tiende a aumentar considerablemente.

  • Esta es la variable que presenta una relación mas significativa con el precio y podría decirse que es uno de los predictores más importantes del precio.

Preciom vs Baños
  • Presenta un valor de correlación de 0.564 lo que muestra una correlación positiva moderada.

  • En el gráfico de dispersión puede verse que las viviendas con más baños tienden a agruparse en rangos de precios más altos. La tendencia es claramente positiva.

  • Esta variable tiene la segunda correlación más fuerte con el precio en el set de datos, mostrando que más baños equivalen a un precio más alto.

Preciom vs Parqueaderos
  • Con un coeficiente de 0.497, se puede ver una correlación positiva moderada.

  • El diagrama de dispersión muestra una tendencia positiva. Si bien hay una tendencia, la relación no es tan consistente.

  • La cantidad de parqueaderos también tiene una relación relevante, aunque es menor que el área y los baños.

Preciom vs Habitaciones
  • Con un valor de 0.286 se considera una correlación positiva débil, puede decirse que su poder predictivo es bajo.
  • El diagrama de dispersión refleja que los puntos forman una “nube” sin una tendencia ascendente clara. Hay casas con muchas habitaciones y precios bajos, y viceversa.
  • Así las cosas, el número de habitaciones, por sí solo, no parece ser un factor determinante para el precio de la vivienda en este conjunto de datos.

2.6 6. Modelo de regresión lineal múltiple

2.6.1 a. Modelado con todas las variables

Se genera el modelo de regresión lineal múltiple modelo_casas_norte para predecir la variable precio de las viviendas de las Casas de la Zona Norte de la ciudad de Cali, en función de las variables (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños)).

#Modelo con todas las variables
modelo_casas_norte <- lm(preciom ~ areaconst+banios+parqueaderos+habitaciones+
                                   factor(estrato), data = casas_norte_train6)
summary(modelo_casas_norte)
## 
## Call:
## lm(formula = preciom ~ areaconst + banios + parqueaderos + habitaciones + 
##     factor(estrato), data = casas_norte_train6)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -514.08  -51.01   -6.35   34.87  929.81 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       16.83202   15.81256   1.064 0.287609    
## areaconst          0.78944    0.04203  18.783  < 2e-16 ***
## banios            17.37015    5.13603   3.382 0.000774 ***
## parqueaderos      21.56015    4.68605   4.601 5.29e-06 ***
## habitaciones       3.15525    3.91299   0.806 0.420408    
## factor(estrato)4  71.32113   14.15239   5.040 6.45e-07 ***
## factor(estrato)5 129.73259   13.93525   9.310  < 2e-16 ***
## factor(estrato)6 466.02126   33.85754  13.764  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 113.7 on 520 degrees of freedom
## Multiple R-squared:  0.7646, Adjusted R-squared:  0.7614 
## F-statistic: 241.3 on 7 and 520 DF,  p-value: < 2.2e-16

2.6.2 b. Interpretación significancia y \(R^2\)

Del análisis de los resultados del modelo, puede verse que éste es estadísticamente significativo en su conjunto (\(p-value < 2.2e-16\)) y tiene un buen poder predictivo. Las variables incluidas (área, baños, parqueaderos, habitaciones y estrato) logran explicar aproximadamente el 76.14% de la variabilidad en el precio de las viviendas (\(R^2\) ajustado). Casi todos los predictores son individualmente significativos y sus efectos son lógicos en el contexto del mercado inmobiliario.

2.6.3 c. Interpretación de los Coeficientes

Área Construida
  • Coeficiente: 0.78944

  • Interpretación: Por cada metro cuadrado adicional de área construida, se espera que el precio de la vivienda aumente en 0.78944 unidades (asumiendo que los precios están en millones, serían $789.440 pesos), manteniendo constantes las demás variables.

Número de baños
  • Coeficiente: 17.37

  • Interpretación: Por cada baño adicional, se estima que el precio de la vivienda se incrementa en 17.37 unidades (millones), manteniendo el resto de variables sin cambio. Más baños se asocian a mayor comodidad y un mayor valor de la propiedad.

Parqueaderos
  • Coeficiente: 21.56

  • Interpretación: Cada parqueadero adicional está asociado con un aumento en el precio de 21.56 unidades (millones). En zonas urbanas donde el estacionamiento es un bien muy valorado, un parqueadero puede incrementar sustancialmente el precio de una vivienda.

Habitaciones
  • Coeficiente: 3.15 y p-value = 0.42

  • Interpretación: El modelo muestra que no hay evidencia estadística suficiente para afirmar que el número de habitaciones tiene un efecto real sobre el precio, una vez que ya se ha considerado el impacto de variables más importantes como el área, los baños y el estrato (p-value = 0.42). El pequeño aumento de 3.15 unidades que sugiere el modelo es tan pequeño que podría deberse simplemente al azar o al ruido en los datos. Por lo tanto, según este modelo, el número de habitaciones no es un predictor fiable del precio. .

Estrato
  • Observación importante: El modelo trata el estrato como una variable categórica y usa el nivel más bajo como referencia (en este caso, estrato 3). Los coeficientes de los demás estratos se interpretan en comparación con ese estrato 3.

  • factor(estrato 4): Coeficiente 71.32: Una vivienda en estrato 4 es, en promedio, 71.32 millones de pesos más cara que una en estrato 3 con las mismas características (área, baños, etc.).

  • factor(estrato 5): Coefiente 129.7: Una vivienda en estrato 5 es 129.7 millones más cara que una en estrato 3.

  • factor(estrato 6): Coeficiente 466: Una vivienda en estrato 6 es 466 millones más cara que una en estrato 3, mostrando un salto de valor muy grande entre los precios de estos estratos.

Como se observa, los resultados del modelo son muy coherentes con la realidad del mercado inmobiliario, donde el estrato tiene un impacto positivo, escalonado y muy fuerte en el precio final.

2.6.4 d. Ajustes del modelo e implicaciones

Tras analizar el modelo de regresión lineal que incluye todas las variables predictoras disponibles, el siguiente paso es proponer un modelo más óptimo. Para lograrlo, se empleará un método de selección de variables por pasos hacia adelante (forward stepwise selection), utilizando el Criterio de Información de Akaike (AIC) como métrica de decisión.

Este procedimiento busca encontrar un balance ideal entre la complejidad del modelo y su capacidad de ajuste, partiendo de un modelo nulo y añadiendo progresivamente solo aquellas variables que contribuyen de manera significativa a reducir el AIC. El objetivo es obtener un modelo final que, si bien puede no incluir todas las variables originales, represente la combinación más eficiente de predictores para explicar la variabilidad de la variable de respuesta.

# AIC Stepwise Forward
# se define el modelo  nulo  y= b0
modelo_b0_norte<- lm(preciom ~ 1, data=casas_norte_train6)

# se define el modelo con todos los predictores
modelo_all_norte <- lm(preciom ~ areaconst+parqueaderos+banios
                 +habitaciones+factor(estrato), data=casas_norte_train6)

# Se aplica el proceso forward stepwise regression
forward_norte <- step(modelo_b0_norte, 
                      direction='forward', 
                      scope=formula(modelo_all_norte), trace=0)

pander(forward_norte$anova)
Step Df Deviance Resid. Df Resid. Dev AIC
NA NA 527 28549048 5756
+ areaconst -1 16762239 526 11786808 5291
+ factor(estrato) -3 4413928 523 7372881 5049
+ banios -1 375190 522 6997690 5024
+ parqueaderos -1 268696 521 6728994 5005
# Visualización de los resultados 
# Df para el gráfico del AIC según cada paso
df_norte <- cbind.data.frame(Step=c(1:5),AIC=forward_norte$anova[6])
plot(AIC~Step,data=df_norte, type="o",col="firebrick4",lwd=1.5,pch=16)

El AIC busca un equilibrio perfecto entre la capacidad predictiva de un modelo y su simplicidad. Añadir habitaciones al modelo que ya contenía área, estrato, baños y parqueaderos, aportaba tan poca información nueva que la penalización por añadir una variable más (haciendo el modelo más complejo) resultaba en un AIC más alto.

partiendo de esta sugerencia se calculará un nuevo modelo modelo_casas_norte2 en donde la variable habitaciones no será incluída:

modelo_casas_norte2 <- lm(preciom ~ areaconst+banios+parqueaderos+factor(estrato), data = casas_norte_train6)
summary(modelo_casas_norte2)
## 
## Call:
## lm(formula = preciom ~ areaconst + banios + parqueaderos + factor(estrato), 
##     data = casas_norte_train6)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -524.22  -51.47   -7.07   36.23  929.58 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       23.58201   13.41054   1.758   0.0793 .  
## areaconst          0.79689    0.04099  19.442  < 2e-16 ***
## banios            19.77162    4.18300   4.727 2.94e-06 ***
## parqueaderos      21.32529    4.67542   4.561 6.35e-06 ***
## factor(estrato)4  68.98883   13.84902   4.981 8.60e-07 ***
## factor(estrato)5 126.11840   13.19033   9.561  < 2e-16 ***
## factor(estrato)6 458.86217   32.66177  14.049  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 113.6 on 521 degrees of freedom
## Multiple R-squared:  0.7643, Adjusted R-squared:  0.7616 
## F-statistic: 281.6 on 6 and 521 DF,  p-value: < 2.2e-16

2.6.5 e.Comparación de los modelos:

El modelo que excluye la variable habitaciones (el sugerido por el AIC) es teórica y estadísticamente superior. Aunque la diferencia en rendimiento es mínima, este modelo más simple es preferible por el principio de parsimonia y porque la variable habitaciones no aporta un valor predictivo significativo una vez que se consideran las otras variables.

Métrica Modelo 1 (Con habitaciones) Modelo 2 (Sin habitaciones) Conclusión
\(R^2\) Ajustado 0.7614 0.7616 Ligeramente mejor en el modelo simple.
Significancia de habitaciones p-valor = 0.42 No aplica La variable no es significativa.
Error Estándar Residual 113.7 113.6 Ligeramente menor (mejor) en el modelo simple.

Se concluye que la variable habitaciones no solo no mejora el modelo, sino que su inclusión “castiga” ligeramente su rendimiento ajustado por complejidad. El modelo más simple explica la misma cantidad de la variabilidad del precio, pero de una forma más eficiente.

2.6.6 f. Validación de supuestos del modelo

Se procede a validar los supuestos de Normalidad, Varianza constante y de Independencia de los errores.

  • Validación del Supuesto de Normalidad:

$$ \[\begin{align*} H_0: \text{Los errores distribuyen normal}\\ H_1: \text{Los errores no distribuyen normal} \end{align*}\]

$$ Se define un nivel de significancia \(\alpha = 0.05\)

# --- Pruebas de Hipótesis Formales ---
# Extraemos los residuos del modelo para usarlos en las pruebas
residuos_modelo <- residuals(modelo_casas_norte)

# Prueba de Shapiro-Wilk (muy potente, aunque sensible en muestras grandes)
shapiro.test(residuos_modelo)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuos_modelo
## W = 0.83197, p-value < 2.2e-16
# Prueba de Kolmogorov-Smirnov (con corrección de Lilliefors)
lillie.test(residuos_modelo)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  residuos_modelo
## D = 0.13102, p-value < 2.2e-16
# Prueba de Anderson-Darling (da más peso a las colas de la distribución)
ad.test(residuos_modelo)
## 
##  Anderson-Darling normality test
## 
## data:  residuos_modelo
## A = 16.082, p-value < 2.2e-16
# Prueba de Cramér-von Mises
cvm.test(residuos_modelo)
## 
##  Cramer-von Mises normality test
## 
## data:  residuos_modelo
## W = 2.8777, p-value = 7.37e-10
# --- Prueba Visual ---
# Gráfico Q-Q con bandas de confianza (muy informativo)
qqPlot(modelo_casas_norte$residuals, 
       main="Gráfico Q-Q para Normalidad de Residuos Casas Norte")

## [1] 288 377

Con el resultado de la prueba, puede verse que se rechaza la hipotesis hula \(H_0\), lo que quiere decir que los errores no distribuyen normal.

Gráficamente se puede ver que en las colas, los quantiles de los residuos tienen un comportamiento muy atípico con respecto a los quantiles de una distribución normal teórica. Así las cosas, puede decirse que los errores no distribuyen normal.

  • Validación del Supuesto de Varianza Constante:

$$ \[\begin{align*} H_0: \text{Los errores tienen varianza constante}\\ H_1: \text{Los errores no tienen varianza constante} \end{align*}\]

$$

Se define un nivel de significancia \(\alpha = 0.05\)

# --- Prueba Visual ---
# Gráfico de Residuos vs. Valores Ajustados
plot(fitted(modelo_casas_norte), residuals(modelo_casas_norte),
     xlab = "Valores Ajustados", ylab = "Residuos",
     main = "Residuos vs. Valores Ajustados", pch = 20)
abline(h = 0, col = "red", lwd = 2) # Línea de referencia en cero

# --- Prueba de Hipótesis Formal ---
# Prueba de Goldfeld-Quandt
# Se ordena por una variable que se sospecha que está relacionada con la varianza del error.
# 'areaconst' es una buena candidata, ya que es común que la variabilidad del precio aumente con el área.
gqtest(modelo_casas_norte, order.by = ~areaconst, data = casas_norte_train6)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo_casas_norte
## GQ = 7.133, df1 = 256, df2 = 256, p-value < 2.2e-16
## alternative hypothesis: variance increases from segment 1 to 2

Con el resultado de la prueba, puede verse que se rechaza la hipotesis hula \(H_0\), lo que quiere decir que los errores no tienen varianza constante, dado que los residuos tienen una varianza creciente frente a los valores ajustados. Es decir, la variabilidad va creciendo de la misma manera que aumenta el valor ajustado. Esto puede interpretarse como que el valor en viviendas con alto precio, es mucho mas variable que el valor en viviendas de menor precio.

Es importante saber que en la medida que requiera hacer predicciones de casas en la ZOna Norte con precios muy altos, el intervalo de confianza es mayor y el precio será mas volatil.

  • Validación del Supuesto de Independencia de los Errores

$$ \[\begin{align*} H_0: \text{Los errores son independientes}\\ H_1: \text{Los errores no son independientes} \end{align*}\]

$$

Se define un nivel de significancia \(\alpha = 0.05\)

# --- Prueba de Hipótesis Formal ---
# Prueba de Durbin-Watson
dwtest(modelo_casas_norte)
## 
##  Durbin-Watson test
## 
## data:  modelo_casas_norte
## DW = 1.914, p-value = 0.1419
## alternative hypothesis: true autocorrelation is greater than 0

Con un \(p_value = 0.1419\), se comprueba el supuesto de independencia en los errores al aceptar la hipótesis nula.

2.7 7. Test de los modelos

2.7.1 a. Imputar datos faltantes sobre bd test

Sobre los datos de test se realizará el mismo tratamiento que se realizó sobre los datos de train. Los valores faltantes de la variable parqueaderos se imputarán segun el comportameinto de la variable estrato.

str(casas_norte_test)
## tibble [114 × 14] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:114] 1592 565 1003 2875 1666 ...
##  $ zona        : chr [1:114] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ estrato     : num [1:114] 5 3 3 5 4 5 3 4 3 3 ...
##  $ preciom     : num [1:114] 780 500 380 390 275 380 180 350 280 260 ...
##  $ areaconst   : num [1:114] 380 210 300 357 120 151 136 126 179 225 ...
##  $ parqueaderos: num [1:114] 2 NA NA NA 1 1 NA NA 1 NA ...
##  $ banios      : num [1:114] 3 6 5 3 2 3 1 3 5 4 ...
##  $ habitaciones: num [1:114] 3 6 8 6 4 3 4 3 10 5 ...
##  $ tipo        : chr [1:114] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:114] "acopi" "acopi" "acopi" "acopi" ...
##  $ longitud    : num [1:114] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:114] 3.49 3.45 3.47 3.48 3.48 ...
##  $ cluster     : int [1:114] 2 2 2 2 2 2 2 2 2 2 ...
##  $ nueva_zona  : chr [1:114] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
pander(colSums(is.na(casas_norte_test)/114*100))
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 35.09 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0
## La mediana de la variable parqueaderos en las Casa de la Zona Norte test: 2

Al realizar el análisis del dataset por estrato, la mediana de la variable parqueadero presenta variaciones así:

# Estadísticas por zona
stats_estrato_n2 <- casas_norte_test%>%
  group_by(estrato) %>%
  summarise(
    n = n(),
    parqueaderos_media_n2 = mean(parqueaderos, na.rm = TRUE),
    parqueaderos_mediana_n2 = median(parqueaderos, na.rm = TRUE),
)

knitr::kable(stats_estrato_n2, caption = "Estadísticas por Estrato test")
Estadísticas por Estrato test
estrato n parqueaderos_media_n2 parqueaderos_mediana_n2
3 52 1.375000 1
4 23 1.857143 2
5 35 2.000000 2
6 4 4.333333 5

Con el valor de la mediana de parqueadero por estrato de la bd de train, se realiza la imputación de los casos faltantes que corresponden a la característica parqueadero, dando como resultado un dataset sin datos faltantes.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   1.000   1.693   2.000   6.000
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 0 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0

2.7.2 b. Imputar datos atípicos

Se aplican las mismas imputaciones que se realizaron sobre el dataset de train.

# Crear columna precio_m2
casas_norte_test2$precio_m2 <- casas_norte_test2$preciom / casas_norte_test2$areaconst

head(casas_norte_test2[, c("id", "nueva_zona", "precio_m2")])
## # A tibble: 6 × 3
##      id nueva_zona precio_m2
##   <dbl> <chr>          <dbl>
## 1  1592 Zona Norte      2.05
## 2   565 Zona Norte      2.38
## 3  1003 Zona Norte      1.27
## 4  2875 Zona Norte      1.09
## 5  1666 Zona Norte      2.29
## 6  1126 Zona Norte      2.52

Imputación Baños y habitaciones en cero

casas_norte_test3 <- casas_norte_test2
# --- BLOQUE ÚNICO Y SEGURO PARA IMPUTACIÓN ---

# Paso 1: Para máxima seguridad, creamos una copia de trabajo limpia.
# Así nos aseguramos de no modificar el dataframe original si algo sale mal.
df_para_filtrar <- casas_norte_test2

# Paso 2: Hacemos la verificación SOBRE LA COPIA que vamos a usar.
cat("--- Verificación ANTES del filtro ---\n")
## --- Verificación ANTES del filtro ---
print(table(df_para_filtrar$banios == 0 | df_para_filtrar$habitaciones == 0, useNA = "ifany"))
## 
## FALSE 
##   114
cat("Filas iniciales:", nrow(df_para_filtrar), "\n\n")
## Filas iniciales: 114
# Paso 3: Aplicamos el filtro SEGURO (quedarnos con lo bueno) sobre la copia.
condicion_para_conservar <- !is.na(df_para_filtrar$banios) & 
                            !is.na(df_para_filtrar$habitaciones) & 
                            df_para_filtrar$banios != 0 & 
                            df_para_filtrar$habitaciones != 0

casas_norte_test3 <- df_para_filtrar[condicion_para_conservar, ]


# Paso 4: Mostramos el resultado final.
cat("--- Verificación DESPUÉS del filtro ---\n")
## --- Verificación DESPUÉS del filtro ---
cat("Filas finales en casas_norte_test3:", nrow(casas_norte_test3), "\n")
## Filas finales en casas_norte_test3: 114
# Paso 5 (opcional): Visualizar el resultado.
# View(casas_norte_test3)
casas_norte_test4 <- casas_norte_test3

Imputación barrio pance y ZOna Sur

casas_norte_test4 <- casas_norte_test3[-which(casas_norte_test3$barrio == "pance" | casas_norte_test3$barrio == "zona sur"), ]

Imputación precio_m2 >4.4 y estrato 3

casas_norte_test5 <- casas_norte_test4
which(casas_norte_test4$precio_m2 > 4.4 & casas_norte_test4$estrato == 3)
## integer(0)
casas_norte_test6 <- casas_norte_test5

Imputación barrio el bosque con estrato 3

casas_norte_test6$estrato[casas_norte_test6$barrio == "el bosque"] <- 3

Imputación barrio la flora con estrato 4

flora42 <- subset(casas_norte_test6, casas_norte_test6$estrato == 4 & casas_norte_test6$barrio =="la flora")

summary(flora42$precio_m2)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       1       1       1       1       1       1
casas_norte_test6$estrato[casas_norte_test6$precio_m2 < 1.36 & casas_norte_test6$barrio == "la flora" & casas_norte_test6$estrato == 5] <- 4
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
casas_norte_test6$estrato <- as.factor(casas_norte_test6$estrato)

# Crear el gráfico de boxplot interactivo
fig_norte62 <- plot_ly(
  data = casas_norte_test6,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_norte62

2.7.3 c. Filtrar variables predictoras

Se construye un data set con las variables predictoras, el que será cargado para medir el desempeño del modelo.

variables_para_testn <- casas_norte_test6 %>%
  select(areaconst, banios, parqueaderos, habitaciones, estrato) %>%
  mutate(estrato = as.factor(estrato))

# Para verificar que funcionó, puedes ver la estructura del nuevo dataframe
str(variables_para_testn)
## tibble [112 × 5] (S3: tbl_df/tbl/data.frame)
##  $ areaconst   : num [1:112] 380 210 300 357 120 151 136 126 179 225 ...
##  $ banios      : num [1:112] 3 6 5 3 2 3 1 3 5 4 ...
##  $ parqueaderos: num [1:112] 2 1 1 2 1 1 1 2 1 1 ...
##  $ habitaciones: num [1:112] 3 6 8 6 4 3 4 3 10 5 ...
##  $ estrato     : Factor w/ 4 levels "3","4","5","6": 3 1 1 3 2 3 1 2 1 1 ...

2.7.4 d. Predicción de precio

Se procede a evaluar el rendimiento del modelo modelo_casas_norte con datos que nunca ha visto (el conjunto de test).

pred_norte <- predict(modelo_casas_norte, newdata = variables_para_testn)
# 1. Vector con los valores REALES de precio del conjunto de test
y_realn <- casas_norte_test6$preciom

# 2. Vector con los valores PREDICHOS por el modelo
y_predn <- pred_norte

# 3. (Verificación Opcional pero Recomendada)
# Comprobamos que ambos vectores tengan la misma cantidad de elementos. Si no, algo salió mal.
if(length(y_realn) == length(y_predn)) {
  print("¡Perfecto! Los datos reales y las predicciones están alineados.")
} else {
  print("¡Alerta! El número de valores reales y predicciones no coincide.")
}
## [1] "¡Perfecto! Los datos reales y las predicciones están alineados."
# --- Cálculo de Métricas en el Conjunto de Test ---

# 1. Error Cuadrático Medio Raíz (RMSE - Root Mean Squared Error)
# Esta métrica nos dice, en promedio, qué tan lejos están las predicciones del valor real.
# Está en las mismas unidades que la variable 'preciom' (ej. millones de pesos).
# Mientras más bajo, mejor.
rmse_testn <- sqrt(mean((y_realn - y_predn)^2))

# 2. Coeficiente de Determinación (R²) en Test
# Nos dice qué porcentaje de la variabilidad de los precios es explicado por el modelo en los datos nuevos.
# Mientras más cercano a 1 (o 100%), mejor.
ss_resn <- sum((y_realn - y_predn)^2)      # Suma de los residuos al cuadrado
ss_totn <- sum((y_realn - mean(y_realn))^2)  # Suma total de cuadrados
r2_testn <- 1 - (ss_resn / ss_totn)


# 3. Imprimir los resultados para verlos
cat("Rendimiento del modelo en el CONJUNTO DE TEST:\n")
## Rendimiento del modelo en el CONJUNTO DE TEST:
cat("RMSE:", rmse_testn, "\n")
## RMSE: 120.6892
cat("R²:", r2_testn, "\n")
## R²: 0.6453045
# 1. Obtenemos el R² del modelo en los datos de ENTRENAMIENTO
# La información está dentro del sumario de tu modelo.
r2_trainn <- summary(modelo_casas_norte)$r.squared

# 2. Imprimimos ambos para una comparación directa
cat("\n--- Comparación Final ---\n")
## 
## --- Comparación Final ---
cat("R² en Entrenamiento (datos vistos):", r2_trainn, "\n")
## R² en Entrenamiento (datos vistos): 0.7645949
cat("R² en Test (datos nuevos):", r2_testn, "\n")
## R² en Test (datos nuevos): 0.6453045

2.7.5 e. Interpretación de desempeño del modelo

Al revisar el desempeño del modelo puede verse sobreajuste (Overfitting), y se ve con la caída del \(R^2\)

  • R² en Entrenamiento: 0.765 (76.5%)

  • R² en Test: 0.645 (64.5%)

El modelo es capaz de explicar el 76.5% de la variabilidad de los precios en los datos que se usaron para entrenar. Sin embargo, cuando se le presentan los datos nuevos y que nunca ha visto (el conjunto de test), la capacidad explicativa cae al 64.5%. Esta caída de 12 puntos porcentuales es considerable.

Esta caída significa que el modelo “se memorizó” algunas particularidades y ruido de los datos de entrenamiento en lugar de aprender las verdaderas tendencias generales.

2.8 8. Predicción de precio primera solicitud

Con el modelo construido se debe predecir el precio de la vivienda con las características de la primera solicitud.

Característica de los inmuebles Vivienda 1
Tipo Casa
Área Construida (m2) 200
Parqueaderos 1
Baños 2
Habitaciones 4
Estrato 4 o 5
Zona Norte
Crédito Preaprobado 350 Mill
# 1. Crear un data frame con las características de las nuevas viviendas
# Los nombres de las columnas DEBEN ser idénticos a los del modelo original.
viviendas_a_predecir_norte <- data.frame(
  areaconst = 200,
  banios = 2,
  parqueaderos = 1,
  habitaciones = 4,
  estrato = c(4,5) # Usamos c() para crear una fila para cada estrato a predecir
)

# 2. Usar la función predict() para estimar el precio
predicciones_norte <- predict(modelo_casas_norte, newdata = viviendas_a_predecir_norte)

# 3. Mostrar los resultados de forma clara y ordenada
resultado_norte <- cbind(viviendas_a_predecir_norte, precio_estimado = predicciones_norte)

print(resultado_norte)
##   areaconst banios parqueaderos habitaciones estrato precio_estimado
## 1       200      2            1            4       4        314.9629
## 2       200      2            1            4       5        373.3744

Como puede verse, el precio estimado de las viviendas con las características descritas en estrato 4 asciende a 314.5 Mill, y las ubicadas en estrato 5 a 373.4 Mill. Así las cosas, con un crédito preaprobado de 350 Mill, se podría cubrir una casa con las características en estrato 4, pero no es suficiente para una vivienda en estrato 5 porque superan el valor de dicho crédito preaprobado. Para estas viviendas sería necesario ampliar el valor del cupo.

2.9 9. Ofertas Potenciales

Las casas ubicadas en la Zona Norte de la ciudad de Cali y que cumplen las características descritas por los clientes ascienden a 15.

# 2. Unificar los dataframes
dataframe_unificado_norte <- bind_rows(casas_norte_train6, casas_norte_test6)

# 3. (Opcional) Verificar las dimensiones
# El número de filas del nuevo dataframe debe ser la suma de las filas de los originales.
cat("Filas en train:", nrow(casas_norte_train6), "\n")
## Filas en train: 528
cat("Filas en test:", nrow(casas_norte_test6), "\n")
## Filas en test: 112
cat("Filas totales:", nrow(dataframe_unificado_norte), "\n")
## Filas totales: 640
casas_filtradas_norte <- subset(
  dataframe_unificado_norte,
  areaconst >= 200 &
    preciom <= 350 &
    (parqueaderos >= 1 & banios >= 2 & habitaciones >= 4 & estrato %in% c(4,5)))
head(casas_filtradas_norte)
## # A tibble: 6 × 15
##      id zona    estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <dbl> <chr>   <fct>     <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1  3453 Zona N… 5           340       240            2      5            6 Casa 
## 2   819 Zona N… 4           350       264            2      3            4 Casa 
## 3  1343 Zona N… 5           320       200            2      4            4 Casa 
## 4  3053 Zona N… 5           320       230            2      4            4 Casa 
## 5   937 Zona N… 4           350       280            2      3            4 Casa 
## 6   952 Zona N… 4           330       275            2      3            5 Casa 
## # ℹ 6 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   cluster <int>, nueva_zona <chr>, precio_m2 <dbl>

3 Anexos - Análisis VIVIENDA 2

3.1 1. Filtro Apartamento - Zona Sur

Supuesto de ubicación: Como se explicó en la parte superior del ejercicio, las zonas se recalcularon según un algoritmo de cluster tomando como ciertos los datos de longitud y latitud de cada punto.

# Filtrar por tipo y zona   
apartamentos_sur <- subset(datos_vivienda2,                                                             tipo == "Apartamento" & nueva_zona == "Zona Sur")   
head(apartamentos_sur,3) 
## # A tibble: 3 × 14
##      id zona    estrato preciom areaconst parqueaderos banios habitaciones tipo 
##   <dbl> <chr>     <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl> <chr>
## 1  6857 Zona N…       3     100        49           NA      1            2 Apar…
## 2  6412 Zona N…       6     620       135            2      3            4 Apar…
## 3  3479 Zona N…       6     620       480           NA      5            5 Apar…
## # ℹ 5 more variables: barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   cluster <int>, nueva_zona <chr>

Revisando la información relacionada a las viviendas tipo Apartamento y que están ubicadas en la Zona Sur de la ciudad de Cali, se observan 551 viviendas que cumplen con esta característica.

## [1] 551  14

La representación gráfica de los Apartamentos ubicados en la Zona Sur según los cluster de zona, se verifica en el siguiente mapa de la ciudad de Cali.

3.2 2. División del set de datos en entrenamiento y test

Antes de realizar el proceso de imputación de faltantes y validación de outliers, y para garantizar la validez del modelo predictivo y evitar problemas de sobreajuste, se realizará la separación del conjunto de datos en dos subconjuntos: entrenamiento (train) y prueba (test). Esta división permite evaluar el desempeño del modelo sobre datos no vistos previamente, reduciendo el sesgo y mejorando la capacidad de generalización.

La proporción de dicha separación será del 80% para entrenamiento y del 20% para prueba, asegurando una representación adecuada de las variables relevantes en ambos subconjuntos dado que no se cuenta con una cantidad de datos muy significativa, únicamente 551 observaciones.

set.seed(123)
split_sur = sample.split(apartamentos_sur$preciom, SplitRatio = 0.8)
apartamentos_sur_train = subset(apartamentos_sur, split_sur == TRUE)
apartamentos_sur_test = subset(apartamentos_sur, split_sur == FALSE)

3.3 3. Análisis e imputación de datos faltantes

En el sector inmobiliario, las características de los inmuebles presentan variaciones significativas según su ubicación geográfica (zona) y el estrato en el que se encuentran ubicadas. Por ello, se propone imputar los valores faltantes de la variable parqueaderos utilizando estadísticas específicas de los estratos a los que pertenece el dato faltante, con el fin de preservar la coherencia contextual del análisis.

Para las viviendas tipo Casa ubicadas en la Zona Norte, los datos estadísticos mostrarían una mediana = 2.

str(apartamentos_sur_train)
## tibble [457 × 14] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:457] 6857 6412 3559 3783 3805 ...
##  $ zona        : chr [1:457] "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ estrato     : num [1:457] 3 6 6 6 6 6 5 6 3 3 ...
##  $ preciom     : num [1:457] 100 620 310 400 520 450 650 690 135 230 ...
##  $ areaconst   : num [1:457] 49 135 93 110 140 118 130 180 60 63 ...
##  $ parqueaderos: num [1:457] NA 2 NA NA NA NA NA NA NA 1 ...
##  $ banios      : num [1:457] 1 3 3 3 3 3 5 3 2 2 ...
##  $ habitaciones: num [1:457] 2 4 3 3 3 3 3 3 2 2 ...
##  $ tipo        : chr [1:457] "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
##  $ barrio      : chr [1:457] "acopi" "acopi" "acopi" "acopi" ...
##  $ longitud    : num [1:457] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:457] 3.38 3.34 3.36 3.35 3.34 ...
##  $ cluster     : int [1:457] 5 5 5 5 5 5 5 5 5 5 ...
##  $ nueva_zona  : chr [1:457] "Zona Sur" "Zona Sur" "Zona Sur" "Zona Sur" ...
pander(colSums(is.na(apartamentos_sur_train)/457*100))
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 9.409 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0
## La mediana de la variable parqueaderos en las Casa de la Zona Sur: 2

Al realizar el análisis del dataset por estrato, la mediana de la variable parqueadero presenta variaciones así:

# Estadísticas por zona
stats_estrato_s <- apartamentos_sur_train%>%
  group_by(estrato) %>%
  summarise(
    n = n(),
    parqueaderos_media_s = mean(parqueaderos, na.rm = TRUE),
    parqueaderos_mediana_s = median(parqueaderos, na.rm = TRUE),
)

knitr::kable(stats_estrato_s, caption = "Estadísticas por Estrato")
Estadísticas por Estrato
estrato n parqueaderos_media_s parqueaderos_mediana_s
3 38 1.157895 1
4 34 1.076923 1
5 66 1.562500 1
6 319 2.150820 2

Con el valor de la mediana de parqueadero por estrato, se realiza la imputación de los casos faltantes que corresponden a la característica parqueadero, dando como resultado un dataset sin datos faltantes.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   2.000   1.888   2.000   4.000
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 0 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0

3.4 4. Análisis e imputación de datos atípicos

Para identificar los datos atípicos dentro del data set se construirá una nueva característica que relacione las variables precio y área construida, de tal manera que puedan identificarse en función de esa relación, si los precios están desfasados con relación a los demás inmuebles de la respectiva zona.

# Crear columna precio_m2
apartamentos_sur_train2$precio_m2 <- apartamentos_sur_train2$preciom / apartamentos_sur_train2$areaconst

head(apartamentos_sur_train2[, c("id", "nueva_zona", "precio_m2")])
## # A tibble: 6 × 3
##      id nueva_zona precio_m2
##   <dbl> <chr>          <dbl>
## 1  6857 Zona Sur        2.04
## 2  6412 Zona Sur        4.59
## 3  3559 Zona Sur        3.33
## 4  3783 Zona Sur        3.64
## 5  3805 Zona Sur        3.71
## 6  4010 Zona Sur        3.81
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
apartamentos_sur_train2$estrato <- as.factor(apartamentos_sur_train2$estrato)

# Crear el gráfico de boxplot interactivo
fig_sur <- plot_ly(
  data = apartamentos_sur_train2,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_sur

3.4.1 a. Análisis Outliers generales:

Del análisis de la Zona Norte se identificó que el dataset completo presenta registros con cero en las variables banios y habitaciones, lo que no es real en viviendas tipo Apartamento. Se decide eliminar este tipo de datos del dataset para que no generen ruido en el modelo.

which(apartamentos_sur_train2$banios == 0 | apartamentos_sur_train2$habitaciones == 0)
## [1] 221 278
## # A tibble: 2 × 6
##      id preciom areaconst habitaciones parqueaderos banios
##   <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1  7522     148        87            0            1      0
## 2  3654     580       138            0            2      4

Se procede a eliminar del dataset estos datos por considerarse atípicos y no tener ningún criterio para imputarlos:

apartamentos_sur_train3 <- apartamentos_sur_train2[-which(apartamentos_sur_train2$banios == 0 | apartamentos_sur_train2$habitaciones == 0), ]
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
apartamentos_sur_train3$estrato <- as.factor(apartamentos_sur_train3$estrato)

# Crear el gráfico de boxplot interactivo
fig_sur3 <- plot_ly(
  data = apartamentos_sur_train3,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_sur3

3.4.2 b. Análisis Outliers estrato 3:

## [1]  10 172
## # A tibble: 1 × 8
##      id barrio      estrato preciom areaconst habitaciones parqueaderos banios
##   <dbl> <chr>       <fct>     <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1  7946 alto jordán 3           230        63            2            1      2
  • Outliers barrio alto jordan: es un barrio estrato 2, a pesar de tener valores de precio_m2 mayores a 3.6 mill, que superan incluso los mayores valores en estrato 4, lo que puede no ser común, no se tienen criterios para considerar este registros como outlier. No se imputará.

3.4.3 c. Análisis Outliers estrato 5:

Puede verse que la mediana del estrato 5 está reflejando un menor valor que la mediana del estrato 4, lo que no es normal en el mercado inmobiliario.

apartamentos_sur_train4 <- apartamentos_sur_train3
which(apartamentos_sur_train4$precio_m2 < 0.9 & apartamentos_sur_train4$estrato == 5)
## [1] 433
head(apartamentos_sur_train4[c(433), c("id", "barrio", "precio_m2", "preciom", "areaconst", "habitaciones", "parqueaderos", "banios")])
## # A tibble: 1 × 8
##      id barrio      precio_m2 preciom areaconst habitaciones parqueaderos banios
##   <dbl> <chr>           <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1  6121 valle del …     0.321     299       932            3            1      3

El área construída de esta vivienda es muy grande, lo que hace que la relación de precio por metro cuadrado se caiga muchísimo. Se procede a eliminar este dato del dataset por considerarse Outlier

apartamentos_sur_train4 <- apartamentos_sur_train4[-which(apartamentos_sur_train4$precio_m2 < 0.9 & apartamentos_sur_train4$estrato == 5), ]
# Asegurarse de que 'estrato' sea un factor
# Esto garantiza que Plotly lo trate como una categoría discreta en el eje X.
apartamentos_sur_train4$estrato <- as.factor(apartamentos_sur_train4$estrato)

# Crear el gráfico de boxplot interactivo
fig_sur4 <- plot_ly(
  data = apartamentos_sur_train4,
  x = ~estrato,                  # La variable para agrupar en el eje X
  y = ~precio_m2,                # La variable numérica en el eje Y
  type = 'box',                  # Indicamos que queremos un boxplot
  color = ~estrato,              # (Opcional) Asigna un color a cada estrato
  colors = "viridis"             # (Opcional) Elige una paleta de colores
) %>% layout(
  title = "Comportamiento del Precio por m² por Estrato",
  xaxis = list(title = "Estrato"),
  yaxis = list(title = "Precio por Metro Cuadrado (precio_m2)")
)

# Mostrar el gráfico
fig_sur4

Este gráfico demuestra una clara correlación positiva entre el estrato y el precio por metro cuadrado, ya que a medida que el estrato aumenta, el precio también lo hace de forma progresiva. El estrato 3 es el más económico y con los precios más homogéneos, a pesar de mostrar algunos valores atípicos, mientras que el estrato 6 es el más costoso y presenta la mayor variabilidad en sus precios. En esencia, la gráfica confirma que los estratos más altos no solo son más caros por metro cuadrado, sino que su rango de precios también es considerablemente más amplio.

3.5 5. Análisis de correlación entre variables

Realicé un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio) 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.

# Seleccionar solo las columnas numéricas del data set
apartamentos_numericas <- apartamentos_sur_train4[, sapply(apartamentos_sur_train4, is.numeric)]

# Eliminar las columnas que no quieres en la correlación
columnas_a_eliminar <- c("id", "cluster", "precio_m2", "latitud", "longitud")

apartamentos_numericas <- apartamentos_numericas[, !names(apartamentos_numericas) %in% columnas_a_eliminar]

Veamos la correlación entre las variables a través de un mapa de calor

# 3. Calcular la matriz de correlación
# use = "complete.obs" maneja los valores NA
matriz_corr_s <- cor(apartamentos_numericas, use = "complete.obs")

# 4. (Opcional pero recomendado) Redondear los valores para una mejor visualización
matriz_corr_s <- round(matriz_corr_s, 2)

# 5. Crear el gráfico de la matriz de correlación (modificación en 'colorscale')
fig_corr_s <- plot_ly(
  z = matriz_corr_s,               # La matriz de correlación va en el eje 'z'
  x = colnames(matriz_corr_s),     # Nombres de las variables en el eje X
  y = rownames(matriz_corr_s),     # Nombres de las variables en el eje Y
  type = "heatmap",              # Tipo de gráfico
  colorscale = "Blues"           # <--- ¡CAMBIA ESTA LÍNEA!
) %>%
layout(
  title = "Matriz de Correlación de Variables Numéricas"
)

# 6. Mostrar el gráfico
fig_corr_s
# Creamos un nuevo data frame para el gráfico con nombres más cortos
apartamentos_para_grafico <- apartamentos_numericas %>%
  rename(
    Precio = preciom,
    Área = areaconst,
    Parq = parqueaderos,
    Baños = banios,
    Hab = habitaciones
  )

# 3. Crear el gráfico usando el data frame con los nombres nuevos
ggp_s <- ggpairs(
  apartamentos_para_grafico, # Usamos el nuevo data frame
  upper = list(continuous = "cor"),
  lower = list(continuous = "points"),
  diag = list(continuous = "densityDiag")
) +
  theme_bw()+
   theme(
    # Ajustar el texto del eje X (números)
    axis.text.x = element_text(size = 5, angle = 90, hjust = 1),
    
    # Ajustar el texto del eje Y (números)
    axis.text.y = element_text(size = 5)
  )

# 4. Convertir a plotly y mostrar
figura_final_s <- ggplotly(ggp_s)

figura_final_s

Veamos la dispersion de estas relaciones entre la variable precio y las demás variables numéricas y su comportamiento según el estrato.

# 2. PREPARAR LOS DATOS PARA LA GRILLA (tu código original, sin cambios)
datos_para_grilla_s <- apartamentos_sur_train4 %>%
  select(preciom, Área = areaconst, Parq = parqueaderos, Baños = banios, Hab = habitaciones, estrato) %>%
  pivot_longer(
    cols = c("Área", "Parq", "Baños", "Hab"),
    names_to = "Variable",
    values_to = "Valor"
  ) %>%
  mutate(estrato = as.factor(estrato))

# 3. CREAR EL GRÁFICO ESTÁTICO y guardarlo en una variable
p_s <- ggplot(datos_para_grilla_s, aes(x = Valor, y = preciom, color = estrato)) +
  geom_point(alpha = 0.7) +
  facet_wrap(~ Variable, ncol = 2, scales = "free_x") +
  labs(
    title = "Precio vs. Características de la Vivienda",
    subtitle = "Dispersión coloreada por Estrato",
    y = "Precio (en millones)",
    x = "Valor de la Característica",
    color = "Estrato"
  ) +
  theme_minimal()

# 4. --- PASO FINAL: CONVERTIR A GRÁFICO INTERACTIVO ---
figura_interactiva_s <- ggplotly(p_s)

# 5. Mostrar la figura interactiva
figura_interactiva_s

Del análisis de correlación entre el precio de las viviendas (preciom) y las variables numéricas puede verse que:

Preciom vs areaconst
  • Un coeficiente de 0.885 indica una correlación positiva fuerte.

  • El diagrama de dispersión confirma visualmente la relación. Se observa una clara tendencia ascendente y a medida que aumenta el Área, el Precio tiende a aumentar considerablemente.

  • Esta es la variable que presenta una relación mas significativa con el precio y podría decirse que es uno de los predictores más importantes del precio.

Preciom vs Baños
  • Presenta un valor de correlación de 0.725 lo que muestra una correlación positiva fuerta. Muestra que más baños equivalen a un precio más alto.

  • En el gráfico de dispersión puede verse que las viviendas con más baños tienden a agruparse en rangos de precios más altos. La tendencia es claramente positiva.

Preciom vs Parqueaderos
  • Con un coeficiente de 0.734, se puede ver una correlación positiva fuerte.

  • El diagrama de dispersión muestra una tendencia positiva. A más parqueaderos, la nube de puntos de precios tiende a estar más arriba.

Preciom vs Habitaciones
  • Con un valor de 0.415 se considera una correlación positiva moderada, puede decirse que su poder predictivo es bajo.
  • No hay una tendencia ascendente clara. El número de habitaciones, por sí solo, no parece ser un factor determinante para el precio de la vivienda en este conjunto de datos.

3.6 6. Modelo de regresión lineal múltiple

3.6.1 a. Modelado con todas las variables

Se genera el modelo de regresión lineal múltiple modelo_apartamentos_sur para predecir la variable precio de las viviendas de los apartamentos de la Zona Sur de la ciudad de Cali, en función de las variables (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños)).

#Modelo con todas las variables
modelo_apartamentos_sur <- lm(preciom ~ areaconst+banios+parqueaderos+
                                habitaciones+ factor(estrato), 
                              data = apartamentos_sur_train4)
summary(modelo_apartamentos_sur)
## 
## Call:
## lm(formula = preciom ~ areaconst + banios + parqueaderos + habitaciones + 
##     factor(estrato), data = apartamentos_sur_train4)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -633.89  -48.99   -6.68   46.15  665.68 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      -92.4376    31.5034  -2.934 0.003517 ** 
## areaconst          2.9641     0.1393  21.271  < 2e-16 ***
## banios            40.4397     7.8220   5.170 3.54e-07 ***
## parqueaderos      41.7850    11.1657   3.742 0.000206 ***
## habitaciones     -32.1794    10.5290  -3.056 0.002376 ** 
## factor(estrato)4  40.0529    28.9518   1.383 0.167225    
## factor(estrato)5  71.0869    25.4942   2.788 0.005524 ** 
## factor(estrato)6 126.5972    23.9287   5.291 1.92e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 121.5 on 446 degrees of freedom
## Multiple R-squared:  0.8349, Adjusted R-squared:  0.8323 
## F-statistic: 322.1 on 7 and 446 DF,  p-value: < 2.2e-16

3.6.2 b. Interpretación significancia y \(R^2\)

Del análisis de los resultados del modelo, puede verse que éste es estadísticamente significativo en su conjunto (\(p-value < 2.2e-16\)) y tiene un buen poder predictivo. Las variables incluidas (área, baños, parqueaderos, habitaciones y estrato) logran explicar aproximadamente el 83.49% de la variabilidad en el precio de las viviendas (\(R^2\) ajustado). Casi todos los predictores son individualmente significativos y sus efectos son lógicos en el contexto del mercado inmobiliario. Esto sugiere que el modelo tiene un ajuste muy bueno y un alto poder predictivo.

3.6.3 c. Interpretación de los Coeficientes

Área Construida
  • Coeficiente: 2.9641

  • Interpretación: Por cada metro cuadrado adicional de área construida, se espera que el precio de la vivienda aumente en 2.9641 unidades (asumiendo que los precios están en millones, serían $2.964.100 pesos), manteniendo constantes las demás variables.

Número de baños
  • Coeficiente: 40.4397

  • Interpretación: Por cada baño adicional, se estima que el precio de la vivienda se incrementa en 40.44 unidades (millones), manteniendo el resto de variables sin cambio. Más baños se asocian a mayor comodidad y un mayor valor de la propiedad.

Parqueaderos
  • Coeficiente: 41.7850

  • Interpretación: Cada parqueadero adicional está asociado con un aumento en el precio de 41.78 unidades (millones). En edificios de apartamentos, el estacionamiento es un bien muy valorado, un parqueadero puede incrementar sustancialmente el precio de una vivienda.

Habitaciones
  • Coeficiente: -32.1794

  • Interpretación: Indica que por cada habitación adicional, el precio disminuye en 32.18 unidades, manteniendo el área y las demás variables constantes. Estos se explica al comparar dos apartamentos con la misma área (ej. 100 m²), el que tiene más habitaciones (ej. 4 habitaciones) probablemente tendrá espacios más pequeños y menos valorados que el que tiene menos habitaciones (ej. 2 habitaciones amplias). El coeficiente negativo captura este efecto de “dividir” un mismo espacio.

Estrato
  • Observación importante: El modelo trata el estrato como una variable categórica y usa el nivel más bajo como referencia (en este caso, estrato 3). Los coeficientes de los demás estratos se interpretan en comparación con ese estrato 3. | |
  • factor(estrato 4): Coeficiente 40.05: Una vivienda en estrato 4 es, en promedio, 40.05 millones de pesos más cara que una en estrato 3 con las mismas características (área, baños, etc.). Sin embargo, su \(p-valor = 0.167\) es alto, lo que significa que esta diferencia no es estadísticamente significativa. | |
  • factor(estrato 5): Coefiente 71.09: Una vivienda en estrato 5 es 71.09 millones más cara que una en estrato 3. | |
  • factor(estrato 6): Coeficiente 126.5972 Una vivienda en estrato 6 es 126.60 millones más cara que una en estrato 3, mostrando una diferencia muy significativa. | Como se observa, los resultados del modelo son muy coherentes con la realidad del mercado inmobiliario, donde el estrato tiene un impacto positivo, escalonado y muy fuerte en el precio final. |

3.6.4 d. Ajustes del modelo e implicaciones

Tras analizar el modelo de regresión lineal que incluye todas las variables predictoras disponibles, el siguiente paso es proponer un modelo más óptimo. Para lograrlo, se empleará un método de selección de variables por pasos hacia adelante (forward stepwise selection), utilizando el Criterio de Información de Akaike (AIC) como métrica de decisión.

Este procedimiento busca encontrar un balance ideal entre la complejidad del modelo y su capacidad de ajuste, partiendo de un modelo nulo y añadiendo progresivamente solo aquellas variables que contribuyen de manera significativa a reducir el AIC. El objetivo es obtener un modelo final que, si bien puede no incluir todas las variables originales, represente la combinación más eficiente de predictores para explicar la variabilidad de la variable de respuesta.

# AIC Stepwise Forward
# se define el modelo  nulo  y= b0
modelo_b0_sur<- lm(preciom ~ 1, data=apartamentos_sur_train4)

# se define el modelo con todos los predictores
modelo_all_sur <- lm(preciom ~ areaconst+parqueaderos+banios
                 +habitaciones+factor(estrato), data=apartamentos_sur_train4)

# Se aplica el proceso forward stepwise regression
forward_sur <- step(modelo_b0_sur, 
                      direction='forward', 
                      scope=formula(modelo_all_sur), trace=0)

pander(forward_sur$anova)
Step Df Deviance Resid. Df Resid. Dev AIC
NA NA 453 39849647 5170
+ areaconst -1 31239240 452 8610408 4476
+ factor(estrato) -3 1228249 449 7382159 4412
+ banios -1 430989 448 6951170 4387
+ parqueaderos -1 233015 447 6718154 4373
+ habitaciones -1 137815 446 6580339 4366
# Visualización de los resultados 
# Df para el gráfico del AIC según cada paso
df_sur <- cbind.data.frame(Step=c(1:6),AIC=forward_sur$anova[6])
plot(AIC~Step,data=df_sur, type="o",col="firebrick4",lwd=1.5,pch=16)

El AIC busca un equilibrio perfecto entre la capacidad predictiva de un modelo y su simplicidad. Pero de la selección por pasos, puede verse que todas las variables analizadas impactan positivamente en el modelo y contribuyen a mejorar su capacidad predictiva. La evidencia clave es que el Criterio de Información de Akaike (AIC) disminuye en cada paso a medida que se agrega una nueva variable, pasando de un valor inicial de 5170 a un mínimo de 4366 en el modelo final. Dado que un AIC más bajo indica un mejor equilibrio entre el ajuste y la complejidad, el hecho de que siga bajando confirma que cada variable añade más información de la que cuesta.

El orden en que se incorporan también revela su jerarquía de importancia: areaconst es, por mucho, la variable de mayor impacto, seguida por factor(estrato) y banios. Las variables parqueaderos y habitaciones, aunque se añaden al final con una reducción menor del AIC, siguen siendo significativas para perfeccionar el ajuste del modelo. Partiendo de esta sugerencia se calculará un nuevo modelo modelo_apartamentos_sur2:

# El operador * asegura la jerarquía
modelo_jerarquico1 <- lm(preciom ~ areaconst * factor(estrato) + banios + parqueaderos + habitaciones , data = apartamentos_sur_train4)
summary(modelo_jerarquico1)
## 
## Call:
## lm(formula = preciom ~ areaconst * factor(estrato) + banios + 
##     parqueaderos + habitaciones, data = apartamentos_sur_train4)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -551.77  -49.23   -4.27   41.05  654.26 
## 
## Coefficients:
##                             Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                  65.8163    43.2084   1.523 0.128414    
## areaconst                     0.3537     0.4943   0.716 0.474669    
## factor(estrato)4           -157.2682    73.5581  -2.138 0.033062 *  
## factor(estrato)5            -61.9864    58.0802  -1.067 0.286438    
## factor(estrato)6            -75.9003    43.3113  -1.752 0.080391 .  
## banios                       43.9471     7.6370   5.755 1.62e-08 ***
## parqueaderos                 43.1679    10.9837   3.930 9.85e-05 ***
## habitaciones                -26.7554    10.3406  -2.587 0.009987 ** 
## areaconst:factor(estrato)4    2.8329     0.9586   2.955 0.003292 ** 
## areaconst:factor(estrato)5    2.0988     0.6253   3.357 0.000857 ***
## areaconst:factor(estrato)6    2.6836     0.4891   5.487 6.89e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 117.8 on 443 degrees of freedom
## Multiple R-squared:  0.8457, Adjusted R-squared:  0.8422 
## F-statistic: 242.8 on 10 and 443 DF,  p-value: < 2.2e-16
modelo_apartamentos_sur1 <- lm(preciom ~ areaconst+factor(estrato)+
                                 banios+parqueaderos+habitaciones, 
                               data = apartamentos_sur_train4)
summary(modelo_apartamentos_sur1)
## 
## Call:
## lm(formula = preciom ~ areaconst + factor(estrato) + banios + 
##     parqueaderos + habitaciones, data = apartamentos_sur_train4)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -633.89  -48.99   -6.68   46.15  665.68 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      -92.4376    31.5034  -2.934 0.003517 ** 
## areaconst          2.9641     0.1393  21.271  < 2e-16 ***
## factor(estrato)4  40.0529    28.9518   1.383 0.167225    
## factor(estrato)5  71.0869    25.4942   2.788 0.005524 ** 
## factor(estrato)6 126.5972    23.9287   5.291 1.92e-07 ***
## banios            40.4397     7.8220   5.170 3.54e-07 ***
## parqueaderos      41.7850    11.1657   3.742 0.000206 ***
## habitaciones     -32.1794    10.5290  -3.056 0.002376 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 121.5 on 446 degrees of freedom
## Multiple R-squared:  0.8349, Adjusted R-squared:  0.8323 
## F-statistic: 322.1 on 7 and 446 DF,  p-value: < 2.2e-16

3.6.5 e.Comparación de los modelos:

El modelo que excluye la variable habitaciones (el sugerido por el AIC) es teórica y estadísticamente superior. Aunque la diferencia en rendimiento es mínima, este modelo más simple es preferible por el principio de parsimonia y porque la variable habitaciones no aporta un valor predictivo significativo una vez que se consideran las otras variables.

Métrica Modelo 1 (todas las variables) Modelo 2 (con factor de estrato) Modelo 3 (en orden jerarquico) Conclusión
\(R^2\) Ajustado 0.8323 0.8422 0.8323 El Modelo 2 explica el 83.23% de la variabilidad del precio
Significancia de habitaciones p-valor = 2.2e-16 p-valor = 2.2e-16 p-valor = 2.2e-16 En el modelo 2 casi todas las variables son significativas, excepto el factor(estrato4) mostrando que no hay mucha diferencia entre este estrato y el estrato base (3)
Error Estándar Residual 121.5 117.8 121.5 El modelo 2 tiene menor error residual, lo que indica que sus predicciones son, en promedio, más cercanas a los valores reales.

El Modelo 2 es estadísticamente el mejor. Proporciona un ajuste más preciso a los datos al incluir la interacción entre el área construida y el estrato, lo que refleja de manera más fiel la dinámica del mercado inmobiliario.

3.6.6 f. Validación de supuestos del modelo

Se procede a validar los supuestos de Normalidad, Varianza constante y de Independencia de los errores.

  • Validación del Supuesto de Normalidad:

$$ \[\begin{align*} H_0: \text{Los errores distribuyen normal}\\ H_1: \text{Los errores no distribuyen normal} \end{align*}\]

$$ Se define un nivel de significancia \(\alpha = 0.05\)

# --- Pruebas de Hipótesis Formales ---
# Extraemos los residuos del modelo para usarlos en las pruebas
residuos_modelo_s <- residuals(modelo_jerarquico1)

# Prueba de Shapiro-Wilk (muy potente, aunque sensible en muestras grandes)
shapiro.test(residuos_modelo_s)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuos_modelo_s
## W = 0.83848, p-value < 2.2e-16
# Prueba de Kolmogorov-Smirnov (con corrección de Lilliefors)
lillie.test(residuos_modelo_s)
## 
##  Lilliefors (Kolmogorov-Smirnov) normality test
## 
## data:  residuos_modelo_s
## D = 0.13696, p-value < 2.2e-16
# Prueba de Anderson-Darling (da más peso a las colas de la distribución)
ad.test(residuos_modelo_s)
## 
##  Anderson-Darling normality test
## 
## data:  residuos_modelo_s
## A = 18.04, p-value < 2.2e-16
# Prueba de Cramér-von Mises
cvm.test(residuos_modelo_s)
## 
##  Cramer-von Mises normality test
## 
## data:  residuos_modelo_s
## W = 3.0935, p-value = 7.37e-10
# --- Prueba Visual ---
# Gráfico Q-Q con bandas de confianza (muy informativo)
qqPlot(modelo_jerarquico1$residuals, 
       main="Gráfico Q-Q para Normalidad de Residuos Apartamentos Sur")

## [1] 311 123

Con el resultado de la prueba, puede verse que se rechaza la hipotesis hula \(H_0\), lo que quiere decir que los errores no distribuyen normal.

Gráficamente se puede ver la desviación de la normalidad se produce principalmente en las colas (extremos) de la distribución donde se curvan y se alejan de la línea, mientras que el centro se ajusta relativamente bien.

  • Validación del Supuesto de Varianza Constante:

$$ \[\begin{align*} H_0: \text{Los errores tienen varianza constante}\\ H_1: \text{Los errores no tienen varianza constante} \end{align*}\]

$$

Se define un nivel de significancia \(\alpha = 0.05\)

# --- Prueba Visual ---
# Gráfico de Residuos vs. Valores Ajustados
plot(fitted(modelo_jerarquico1), residuals(modelo_jerarquico1),
     xlab = "Valores Ajustados", ylab = "Residuos",
     main = "Residuos vs. Valores Ajustados", pch = 20)
abline(h = 0, col = "red", lwd = 2) # Línea de referencia en cero

# --- Prueba de Hipótesis Formal ---
# Prueba de Goldfeld-Quandt
# Se ordena por una variable que se sospecha que está relacionada con la varianza del error.
# 'areaconst' es una buena candidata, ya que es común que la variabilidad del precio aumente con el área.
gqtest(modelo_jerarquico1, order.by = ~areaconst, data = apartamentos_sur_train4)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo_jerarquico1
## GQ = 9.6909, df1 = 216, df2 = 216, p-value < 2.2e-16
## alternative hypothesis: variance increases from segment 1 to 2

Con el resultado de la prueba, puede verse que se rechaza la hipotesis hula \(H_0\), lo que quiere decir que los errores no tienen varianza constante, dado que los residuos tienen una varianza creciente frente a los valores ajustados. Es decir, la variabilidad va creciendo de la misma manera que aumenta el valor ajustado. Esto puede interpretarse como que el valor en viviendas con alto precio, es mucho mas variable que el valor en viviendas de menor precio.

Es importante saber que en la medida que requiera hacer predicciones de los apartamentos en la Zona Sur con precios muy altos, el intervalo de confianza es mayor y el precio será mas volatil.

  • Validación del Supuesto de Independencia de los Errores

$$ \[\begin{align*} H_0: \text{Los errores son independientes}\\ H_1: \text{Los errores no son independientes} \end{align*}\]

$$

Se define un nivel de significancia \(\alpha = 0.05\)

# --- Prueba de Hipótesis Formal ---
# Prueba de Durbin-Watson
dwtest(modelo_jerarquico1)
## 
##  Durbin-Watson test
## 
## data:  modelo_jerarquico1
## DW = 1.7911, p-value = 0.01013
## alternative hypothesis: true autocorrelation is greater than 0

Con un \(p_value = 0.01013\), se comprueba el supuesto de independencia en los errores al aceptar la hipótesis nula.

3.7 7. Test de los modelos

3.7.1 a. Imputar datos faltantes sobre bd test

Sobre los datos de test se realizará el mismo tratamiento que se realizó sobre los datos de train. Los valores faltantes de la variable parqueaderos se imputarán segun el comportameinto de la variable estrato.

str(apartamentos_sur_test)
## tibble [94 × 14] (S3: tbl_df/tbl/data.frame)
##  $ id          : num [1:94] 3479 7026 4373 4184 6024 ...
##  $ zona        : chr [1:94] "Zona Norte" "Zona Norte" "Zona Sur" "Zona Sur" ...
##  $ estrato     : num [1:94] 6 5 6 5 4 5 4 5 6 6 ...
##  $ preciom     : num [1:94] 620 310 560 250 235 225 200 230 360 450 ...
##  $ areaconst   : num [1:94] 480 90 141 66.4 62 ...
##  $ parqueaderos: num [1:94] NA NA 3 1 1 1 1 1 1 2 ...
##  $ banios      : num [1:94] 5 3 5 2 2 2 2 2 2 3 ...
##  $ habitaciones: num [1:94] 5 4 4 3 3 2 3 3 3 3 ...
##  $ tipo        : chr [1:94] "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
##  $ barrio      : chr [1:94] "acopi" "acopi" "cataya real" "ciudad bochalema" ...
##  $ longitud    : num [1:94] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:94] 3.36 3.37 3.35 3.36 3.36 ...
##  $ cluster     : int [1:94] 5 5 5 5 5 5 5 5 5 5 ...
##  $ nueva_zona  : chr [1:94] "Zona Sur" "Zona Sur" "Zona Sur" "Zona Sur" ...
pander(colSums(is.na(apartamentos_sur_test)/94*100))
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 12.77 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0
## La mediana de la variable parqueaderos en los apartamentos en la zona sur test: 2

Al realizar el análisis del dataset por estrato, la mediana de la variable parqueadero presenta variaciones así:

# Estadísticas por zona
stats_estrato_s2 <- apartamentos_sur_train4%>%
  group_by(estrato) %>%
  summarise(
    n = n(),
    parqueaderos_media_s2 = mean(parqueaderos, na.rm = TRUE),
    parqueaderos_mediana_s2 = median(parqueaderos, na.rm = TRUE),
)

knitr::kable(stats_estrato_s2, caption = "Estadísticas por Estrato test")
Estadísticas por Estrato test
estrato n parqueaderos_media_s2 parqueaderos_mediana_s2
3 37 1.081081 1
4 34 1.058823 1
5 65 1.553846 1
6 318 2.144654 2

Con el valor de la mediana de parqueadero por estrato de la bd de train, se realiza la imputación de los casos faltantes que corresponden a la característica parqueadero, dando como resultado un dataset sin datos faltantes.

##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   1.000   2.000   1.819   2.000   4.000
Table continues below
id zona estrato preciom areaconst parqueaderos banios
0 0 0 0 0 0 0
habitaciones tipo barrio longitud latitud cluster nueva_zona
0 0 0 0 0 0 0

3.7.2 b. Imputar datos atípicos

Se aplican las mismas imputaciones que se realizaron sobre el dataset de train.

which(apartamentos_sur_test2$banios == 0 | apartamentos_sur_test2$habitaciones == 0)
## [1] 32
## # A tibble: 1 × 6
##      id preciom areaconst habitaciones parqueaderos banios
##   <dbl>   <dbl>     <dbl>        <dbl>        <dbl>  <dbl>
## 1  3553     320        92            3            1      0

Se procede a eliminar del dataset estos datos por considerarse atípicos y no tener ningún criterio para imputarlos:

apartamentos_sur_test3 <- apartamentos_sur_test2[-which(apartamentos_sur_train2$banios == 0 | apartamentos_sur_train2$habitaciones == 0), ]

Análisis Outliers estrato 3:

## integer(0)
  • No se aplica ninguna imputación porque no hay datos que cumplan

Análisis Outliers estrato 5:

Puede verse que la mediana del estrato 5 está reflejando un menor valor que la mediana del estrato 4, lo que no es normal en el mercado inmobiliario.

which(apartamentos_sur_test3$precio_m2 < 0.9 & apartamentos_sur_test3$estrato == 5)
## integer(0)
  • No se aplica ninguna imputación porque no hay datos que cumplan

3.7.3 c. Filtrar variables predictoras

Se construye un data set con las variables predictoras, el que será cargado para medir el desempeño del modelo.

variables_para_tests <- apartamentos_sur_test3 %>%
  select(areaconst, banios, parqueaderos, habitaciones, estrato) %>%
  mutate(estrato = as.factor(estrato))

# Para verificar que funcionó, puedes ver la estructura del nuevo dataframe
str(variables_para_tests)
## tibble [94 × 5] (S3: tbl_df/tbl/data.frame)
##  $ areaconst   : num [1:94] 480 90 141 66.4 62 ...
##  $ banios      : num [1:94] 5 3 5 2 2 2 2 2 2 3 ...
##  $ parqueaderos: num [1:94] 2 1 3 1 1 1 1 1 1 2 ...
##  $ habitaciones: num [1:94] 5 4 4 3 3 2 3 3 3 3 ...
##  $ estrato     : Factor w/ 4 levels "3","4","5","6": 4 3 4 3 2 3 2 3 4 4 ...

3.7.4 d. Predicción de precio

Se procede a evaluar el rendimiento del modelo modelo_jerarquico1con datos que nunca ha visto (el conjunto de test).

pred_sur <- predict(modelo_jerarquico1, newdata = variables_para_tests)
# 1. Vector con los valores REALES de precio del conjunto de test
y_reals <- apartamentos_sur_test3$preciom

# 2. Vector con los valores PREDICHOS por el modelo
y_preds <- pred_sur

# 3. (Verificación Opcional pero Recomendada)
# Comprobamos que ambos vectores tengan la misma cantidad de elementos. Si no, algo salió mal.
if(length(y_reals) == length(y_preds)) {
  print("¡Perfecto! Los datos reales y las predicciones están alineados.")
} else {
  print("¡Alerta! El número de valores reales y predicciones no coincide.")
}
## [1] "¡Perfecto! Los datos reales y las predicciones están alineados."
# --- Cálculo de Métricas en el Conjunto de Test ---

# 1. Error Cuadrático Medio Raíz (RMSE - Root Mean Squared Error)
# Esta métrica nos dice, en promedio, qué tan lejos están las predicciones del valor real.
# Está en las mismas unidades que la variable 'preciom' (ej. millones de pesos).
# Mientras más bajo, mejor.
rmse_tests <- sqrt(mean((y_reals - y_preds)^2))

# 2. Coeficiente de Determinación (R²) en Test
# Nos dice qué porcentaje de la variabilidad de los precios es explicado por el modelo en los datos nuevos.
# Mientras más cercano a 1 (o 100%), mejor.
ss_ress <- sum((y_reals - y_preds)^2)      # Suma de los residuos al cuadrado
ss_tots <- sum((y_reals - mean(y_reals))^2)  # Suma total de cuadrados
r2_tests <- 1 - (ss_ress / ss_tots)


# 3. Imprimir los resultados para verlos
cat("Rendimiento del modelo en el CONJUNTO DE TEST:\n")
## Rendimiento del modelo en el CONJUNTO DE TEST:
cat("RMSE:", rmse_tests, "\n")
## RMSE: 146.0979
cat("R²:", r2_tests, "\n")
## R²: 0.6217114
# 1. Obtenemos el R² del modelo en los datos de ENTRENAMIENTO
# La información está dentro del sumario de tu modelo.
r2_trains <- summary(modelo_jerarquico1)$r.squared

# 2. Imprimimos ambos para una comparación directa
cat("\n--- Comparación Final ---\n")
## 
## --- Comparación Final ---
cat("R² en Entrenamiento (datos vistos):", r2_trains, "\n")
## R² en Entrenamiento (datos vistos): 0.8457098
cat("R² en Test (datos nuevos):", r2_tests, "\n")
## R² en Test (datos nuevos): 0.6217114

3.7.5 e. Interpretación de desempeño del modelo

Al revisar el desempeño del modelo puede verse sobreajuste (Overfitting), y se ve con la caída del \(R^2\)

  • R² en Entrenamiento: 0.8457 (84.6%)

  • R² en Test: 0.6217 (62.17%)

El modelo es capaz de explicar el 84.6% de la variabilidad de los precios en los datos que se usaron para entrenar. Sin embargo, cuando se le presentan los datos nuevos y que nunca ha visto (el conjunto de test), la capacidad explicativa cae al 62.2%. Esta caída de 22 puntos porcentuales es muy considerable.

Esta caída significa que el modelo “se memorizó” los datos de entrenamiento (sobreajuste o “overfitting”) en lugar de aprender las reglas generales que los gobiernan.

3.8 8. Predicción de precio segunda solicitud

Con el modelo construido se debe predecir el precio de la vivienda con las características de la primera solicitud.

Característica de los inmuebles Vivienda 2
Tipo Apartamento
Área Construida (m2) 300
Parqueaderos 3
Baños 3
Habitaciones 5
Estrato 5 o 6
Zona Sur
Crédito Preaprobado 850 Mill
# 1. Crear un data frame con las características de las nuevas viviendas
# Los nombres de las columnas DEBEN ser idénticos a los del modelo original.
viviendas_a_predecir_sur <- data.frame(
  areaconst = 300,
  banios = 3,
  parqueaderos = 3,
  habitaciones = 5,
  estrato = c(5,6) # Usamos c() para crear una fila para cada estrato a predecir
)

# 2. Usar la función predict() para estimar el precio
predicciones_sur <- predict(modelo_jerarquico1, newdata = viviendas_a_predecir_sur)

# 3. Mostrar los resultados de forma clara y ordenada
resultado_sur <- cbind(viviendas_a_predecir_sur, precio_estimado = predicciones_sur)

print(resultado_sur)
##   areaconst banios parqueaderos habitaciones estrato precio_estimado
## 1       300      3            3            5       5        867.1317
## 2       300      3            3            5       6       1028.6517

Como puede verse, el precio estimado de las viviendas con las características descritas en estrato 5 asciende a 867.13 Mill, y las ubicadas en estrato 6 a 1028.65 Mill. Así las cosas, con un crédito preaprobado de 850 Mill, no sería suficiente para cubrir los requerimeintos de vivienda con los precios del mercado inmobiliario en la zona y con las características deseadas.

3.9 9. Ofertas Potenciales

Los apartamentos ubicados en la Zona Sur de la ciudad de Cali y que cumplen las características descritas por los clientes ascienden a 15.

# 2. Unificar los dataframes
dataframe_unificado_sur <- bind_rows(apartamentos_sur_train4, apartamentos_sur_test3)

# 3. (Opcional) Verificar las dimensiones
# El número de filas del nuevo dataframe debe ser la suma de las filas de los originales.
cat("Filas en train:", nrow(apartamentos_sur_train4), "\n")
## Filas en train: 454
cat("Filas en test:", nrow(apartamentos_sur_test3), "\n")
## Filas en test: 94
cat("Filas totales:", nrow(dataframe_unificado_sur), "\n")
## Filas totales: 548
apartamentos_filtrados_sur <- subset(
  dataframe_unificado_sur,
  areaconst >= 350 &
    preciom <= 950 &
    (parqueaderos >= 3 & banios >= 3 & habitaciones >= 5 & estrato %in% c(5,6)))
head(apartamentos_filtrados_sur)
## # A tibble: 0 × 15
## # ℹ 15 variables: id <dbl>, zona <chr>, estrato <fct>, preciom <dbl>,
## #   areaconst <dbl>, parqueaderos <dbl>, banios <dbl>, habitaciones <dbl>,
## #   tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>, cluster <int>,
## #   nueva_zona <chr>, precio_m2 <dbl>

Con los requerimientos descritos por el cliente, no se lograron ubicar viviendas disponibles en el momento.

4 Reporte Ejecutivo

Estimados señores,

Comprendiendo la importancia de una transición fluida para sus ejecutivos y sus familias al relocalizarse en la ciudad de Cali - Colombia, hemos llevado a cabo un análisis exhaustivo del mercado inmobiliario para identificar las propiedades que mejor se ajusten a sus necesidades.

Nos complace informar que nuestra búsqueda ha arrojado resultados muy positivos para la Vivienda 1, encontrando opciones que no solo cumplen con los requisitos técnicos solicitados, sino que también se alinean con el estilo de vida que buscan para su primer ejecutivo en la Zona Norte de la ciudad. Para la Vivienda 2, si bien no hemos encontrado una opción que se ajuste estrictamente a todos los requerimientos dentro del presupuesto establecido, estamos comprometidos a encontrar la mejor solución y ya estamos explorando alternativas viables que presentaremos a continuación.

Para la Vivienda 1, hemos seleccionado una serie de casas en la Zona Norte que cumplen con las especificaciones solicitadas. Estas propiedades se encuentran ubicadas en los estratos 4 y 5 y se ajustan al crédito preaprobado de 350 millones de pesos, garantizando una excelente relación entre calidad, ubicación y presupuesto. Estamos seguros de que estas opciones ofrecerán el confort y la calidad de vida que su ejecutivo y su familia merecen, en una de las zonas más apetecidas de la ciudad por su tranquilidad y acceso a servicios. A continuación le relacionamos un mapa de la ciudad de Cali donde puede ver la preselección de viviendas que se ajustan a las necesidades de su ejecutivo y su familia.

En cuanto a la Vivienda 2, nuestro análisis del mercado en la Zona Sur indica que los apartamentos con las características deseadas (300 m², 3 parqueaderos, 3 baños, 5 habitaciones, en estrato 5 o 6) superan el presupuesto de 850 millones de pesos. Si bien no hemos encontrado una opción que se ajuste estrictamente a todos los requerimientos, estamos comprometidos a encontrar la mejor solución. Recomendamos explorar alternativas como ajustar ligeramente los criterios de búsqueda o considerar otros sectores exclusivos que puedan ofrecer propiedades de alto valor que se alineen mejor con el presupuesto establecido, sin sacrificar la calidad de vida que buscan para su segundo ejecutivo.

Para asegurar que encontremos el hogar ideal para su segundo ejecutivo, hemos realizado un análisis detallado de los precios actuales del mercado en la Zona Sur, enfocándonos en apartamentos que cumplan con las características de alta gama solicitadas. Nuestro estudio, basado en el promedio de precios por metro cuadrado que adjuntamos, indica que las propiedades con un área de 300 m² en estratos 5 y 6 se valoran, respectivamente, en alrededor de los $1.029 millones de pesos. Entendemos que cada detalle es importante, y queremos ser transparentes para ajustar la búsqueda de la manera más efectiva. Con el fin de alinearnos al presupuesto preaprobado de $850 millones, podemos explorar juntos excelentes alternativas que ofrezcan el mejor balance entre las características prioritarias para el ejecutivo y el valor actual del mercado.

Quedamos a su entera disposición para acompañarlos en cada paso de este importante proceso de relocalización. Nuestro principal objetivo es asegurar que sus ejecutivos y sus familias encuentren en Cali no solo una nueva casa, sino un verdadero hogar. Será un placer para nosotros poner toda nuestra experiencia a su servicio para hacer de esta transición una experiencia exitosa y memorable. No duden en contactarnos para agendar una reunión y definir los próximos pasos.

Cordialmente,

Maria

CEO Inmobiliaria C&A