## 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>
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 |
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 |
| 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
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*
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.
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.
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.
## 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" ...
| 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")| 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
| 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 |
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## [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.
## [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_norte3Continuando 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.
## [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.
## [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_norte4Las 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.
## [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_norte5Puede 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.
## [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.
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_norte6El 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.
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_finalVeamos 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_interactivaDel análisis de correlación entre el precio de las viviendas (preciom) y las variables numéricas puede verse que:
| Preciom vs areaconst |
|
| Preciom vs Baños |
|
| Preciom vs Parqueaderos |
|
| Preciom vs Habitaciones |
|
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
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.
| Área Construida |
|
| Número de baños |
|
| Parqueaderos |
|
| Habitaciones |
|
| Estrato |
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. |
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
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.
Se procede a validar los supuestos de Normalidad, Varianza constante y de Independencia de los errores.
$$ \[\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
##
## Lilliefors (Kolmogorov-Smirnov) normality test
##
## data: residuos_modelo
## D = 0.13102, p-value < 2.2e-16
##
## Anderson-Darling normality test
##
## data: residuos_modelo
## A = 16.082, p-value < 2.2e-16
##
## 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.
$$ \[\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.
$$ \[\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\)
##
## 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.
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.
## 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" ...
| 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")| 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
| 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 |
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
# --- 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 ---
##
## FALSE
## 114
## 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 ---
## Filas finales en casas_norte_test3: 114
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
## integer(0)
Imputación barrio el bosque con estrato 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_norte62Se 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 ...
Se procede a evaluar el rendimiento del modelo modelo_casas_norte con datos que nunca ha visto (el conjunto de test).
# 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:
## RMSE: 120.6892
## 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 ---
## R² en Entrenamiento (datos vistos): 0.7645949
## R² en Test (datos nuevos): 0.6453045
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.
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.
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
## Filas en test: 112
## 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)))## # 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>
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.
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.
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.
## 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" ...
| 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")| 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
| 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 |
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_surDel 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.
## [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## [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
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.
## [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_sur4Este 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.
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_sVeamos 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_sDel análisis de correlación entre el precio de las viviendas (preciom) y las variables numéricas puede verse que:
| Preciom vs areaconst |
|
| Preciom vs Baños |
|
| Preciom vs Parqueaderos |
|
| Preciom vs Habitaciones |
|
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
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.
| Área Construida |
|
|
| Número de baños |
|
|
| Parqueaderos |
|
|
| Habitaciones |
|
|
| Estrato |
|
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
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.
Se procede a validar los supuestos de Normalidad, Varianza constante y de Independencia de los errores.
$$ \[\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
##
## 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
##
## 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.
$$ \[\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.
$$ \[\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\)
##
## 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.
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.
## 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" ...
| 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")| 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
| 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 |
Se aplican las mismas imputaciones que se realizaron sobre el dataset de train.
## [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)
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.
## integer(0)
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 ...
Se procede a evaluar el rendimiento del modelo modelo_jerarquico1con datos que nunca ha visto (el conjunto de test).
# 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:
## RMSE: 146.0979
## 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 ---
## R² en Entrenamiento (datos vistos): 0.8457098
## R² en Test (datos nuevos): 0.6217114
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.
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.
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
## Filas en test: 94
## 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)))## # 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.
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