Problema

Se solicita asesoría para la compra de dos viviendas por parte de una compañía internacional que desea ubicar a dos de sus empleados con sus familias en la ciudad. Las solicitudes incluyen las siguientes condiciones:

El siguiente informe ejecutivo analiza los dos casos y se dan recomendaciones. Como soporte del informe se anexan las estimaciones, validaciones y comparación de modelos requeridos. A continuación se desarrolla el análisis del problea planteado y se presenta el informe de acuerdo a los predios solicitados.

Características Vivienda.1 Vivienda.2
Tipo Casa Apartamento
Área construida 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 millones 850 millones

Caso vivienda 1 (Zona Norte) - Análisis exporatorio

A partir de una exploración inicial de los datos se identifica un conjunto de datos de vienda conformado por 8.322 observaciones que cuentan con 13 variables que incluyen 4 variables categóricas: “zona”,“tipo”, “estrato” y “barrio”, y 9 variables numéricas, sin tener en cuenta el identificador (id): “piso”, “estrato”, “preciom”, “areaconst”,“parqueadero”, “banios”, “habitaciones”, “longitud” y “latitud”. Se identificaron los datos faltantes para cada una de las variables concluyendo que solo hay vacíos en las columnas “parqueadero” y “piso”. A continuación se muestran las primeras 5 columnas del conjunto de datos:

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

Como parte del análisis exploratorio comenzaremos analizando el primer caso, para ello se aplica un filtro para la viviendas de tipo “Casa” en la Zona Norte y se grafican en un mapa.

data_base1 <- vivienda %>%
  filter(zona == "Zona Norte" & tipo == "Casa")
#head(data_base1,3)
# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = data_base1$estrato)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(data_base1) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(estrato),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Estrato:", estrato, "<br>Zona:", zona),
    label = ~estrato             # Etiqueta que aparece al pasar el mouse
  )

map  # Muestra el mapa

Cómo se observa en el mapa, hay viviendas que no corresponden a la zona Norte según la ubicación de sus coordenadas. En este caso se procede a hacer una limpieza de los datos. Para ello, se carga una capa geográfica tipo Shapefile con la información de las comunas de Cali como se muetra a continuación. Adicionalmente, el SRS de la capa tuvo que ser transformado del EPSG 6249 al EPSG 4326 para que la capa pueda ser intersectada espacialmente con los puntos geográficos del dataset de “Viviendas”.

A continuación se muestra la capa cargada de Comunas de Cali.

# Cargar la capa geográfica (shapefile, GeoJSON, etc.)
capa_geo <- st_read("/Users/ricardoortiz/Downloads/mc_comunas/mc_comunas.shp") 
Reading layer `mc_comunas' from data source 
  `/Users/ricardoortiz/Downloads/mc_comunas/mc_comunas.shp' using driver `ESRI Shapefile'
Simple feature collection with 22 features and 4 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 1054753 ymin: 860192.1 xmax: 1068492 ymax: 879441.5
Projected CRS: MAGNA-SIRGAS / Cali urban grid
capa_geo <- st_transform(capa_geo, crs = 6249)

#Convertir de EPSG:6249 (Magna-SIRGAS Bogotá) a EPSG:4326 (WGS84)
comunas_wgs84 <- st_transform(capa_geo, crs = 4326)
plot(comunas_wgs84$geometry, col = "blue", main = "Comunas de Cali (EPSG:4326)")

Lo que se hará ahora es filtrar las viviendas de la zona Norte a partir de sus coordenadas. Para ello, a partir de la capa cargada se hará un posterior cruce geográfico con la capa de vivienda la cuál es tranformada también en un objeto espacial. A continuación se muestran las primeras 3 filas de la tabla resultante de ese cruce espacial.

#  Convertir data_base1 en objeto espacial sin perder latitud y longitud
data_base1_sf <- data_base1 %>%
  mutate(latitud_orig = latitud, longitud_orig = longitud) %>%  # Copiar las coordenadas originales
  st_as_sf(coords = c("longitud", "latitud"), crs = 4326)

#  Realizar cruce espacial
data_b1_cruzado <- st_join(data_base1_sf, comunas_wgs84, left = TRUE)

#  Restaurar latitud y longitud como columnas separadas
data_b1_cruzado <- data_b1_cruzado %>%
  mutate(latitud = st_coordinates(.)[, 2],  # Extraer latitud de la geometría
         longitud = st_coordinates(.)[, 1]) %>%
  st_drop_geometry()  # Opcional: quitar la columna de geometría si ya no se necesita

# Ver las primeras filas
head(data_b1_cruzado, 3)
# A tibble: 3 × 19
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1209 Zona N… 02          5     320       150            2      4            6
2  1592 Zona N… 02          5     780       380            2      3            3
3  4057 Zona N… 02          6     750       445           NA      7            6
# ℹ 10 more variables: tipo <chr>, barrio <chr>, latitud_orig <dbl>,
#   longitud_orig <dbl>, comuna <int>, nombre <chr>, shape_leng <dbl>,
#   shape_area <dbl>, latitud <dbl>, longitud <dbl>

Ahora se procede a filtrar los datos a partir de la columna “comuna” agregada durante el cruce espacial y se seleccionan las comunas correspondientes a la zona Norte, que en este caso corresponden a las comunas 2, 4, 5 y 6. El resultado se grafica en un mapa y se valida el resultado.

# Filtrar el dataframe data_b1_ajustado según los valores de la columna comuna en data_b1_cruzado
data_b1_norte <- data_b1_cruzado %>%
  filter(comuna %in% c(2, 4, 5, 6))
# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = data_b1_norte$estrato)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(data_b1_norte) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(estrato),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Estrato:", estrato, "<br>Zona:", zona),
    label = ~estrato             # Etiqueta que aparece al pasar el mouse
  )

map  # Muestra el mapa

Como resultado se obtiene un dataset con 527 viviendas filtradas para la zonas de interés. Ahora que corroboramos que los datos corresponden efectivamente a la Zona Norte se realiza el análisis de los mismos a través del paquete SKIM, con este se busca dar una mirada rápida al comportamiento de los datos y verificar si hay valores vacíos y el resumen estadístico de cada variable:

# Resumen con mejor formato
skim(data_b1_norte)
Data summary
Name data_b1_norte
Number of rows 527
Number of columns 19
_______________________
Column type frequency:
character 5
numeric 14
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
zona 0 1.00 10 10 0 1 0
piso 229 0.57 2 2 0 4 0
tipo 0 1.00 4 4 0 1 0
barrio 0 1.00 5 25 0 74 0
nombre 0 1.00 8 8 0 4 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id 0 1.00 2037.63 1596.52 88.00 621.00 1413.00 3225.50 7226.00 ▇▃▃▂▁
estrato 0 1.00 4.19 0.96 3.00 3.00 4.00 5.00 6.00 ▆▅▁▇▁
preciom 0 1.00 422.76 247.73 110.00 244.00 380.00 530.00 1800.00 ▇▅▁▁▁
areaconst 0 1.00 254.56 161.66 30.00 135.00 234.00 332.00 1440.00 ▇▃▁▁▁
parqueaderos 175 0.67 2.23 1.46 1.00 1.00 2.00 3.00 10.00 ▇▂▁▁▁
banios 0 1.00 3.50 1.46 0.00 2.00 3.00 4.00 10.00 ▅▇▃▁▁
habitaciones 0 1.00 4.59 1.74 0.00 3.00 4.00 5.00 10.00 ▁▇▃▂▁
latitud_orig 0 1.00 3.48 0.01 3.45 3.47 3.48 3.48 3.50 ▂▇▆▇▃
longitud_orig 0 1.00 -76.51 0.01 -76.55 -76.52 -76.52 -76.50 -76.48 ▁▇▆▅▃
comuna 0 1.00 3.05 1.44 2.00 2.00 2.00 5.00 6.00 ▇▁▁▃▁
shape_leng 0 1.00 21228.99 8820.44 8438.86 11375.09 27855.55 27855.55 27855.55 ▅▁▁▁▇
shape_area 0 1.00 8516703.76 3111218.14 4199641.93 4519035.88 10863187.42 10863187.42 10863187.42 ▅▁▁▁▇
latitud 0 1.00 3.48 0.01 3.45 3.47 3.48 3.48 3.50 ▂▇▆▇▃
longitud 0 1.00 -76.51 0.01 -76.55 -76.52 -76.52 -76.50 -76.48 ▁▇▆▅▃

Ajuste de la variable “parqueaderos” y “piso”

Se identifican las columnas con datos faltanes, en este caso, la variable parqueaderos y piso. Para la variable parqueaderos, se asume que los 175 datos faltantes corresponden a viviendas sin parqueadero, por lo que se ajusta el valor faltante a 0. Para la variable piso se asume que por tratarse de una casa tiene al menos 1 piso y se imputan los valores.

data_b1_norte <- data_b1_norte %>%
  mutate(parqueaderos = ifelse(is.na(parqueaderos) | parqueaderos == "", 0, parqueaderos))

data_b1_norte <- data_b1_norte %>%
  mutate(piso = ifelse(is.na(piso) | piso == "", 1, piso))

Adicionalmente, se hace una análisis de correlaciones entre las variables. El siguiente gráfico muestra la correlación de Pearson entre las variables del modelo, indicando la fuerza y dirección de la relación entre el precio de la vivienda (preciom) y las demás características.

# Seleccionar solo variables numéricas para la correlación
datos_numericos <- data_b1_norte %>%
  select(preciom, areaconst, estrato, banios, habitaciones) %>%
  na.omit()  # Eliminar filas con valores faltantes

# Calcular matriz de correlación
matriz_cor <- cor(datos_numericos)

# Visualizar con corrplot
corrplot(matriz_cor, method = "color", addCoef.col = "black", tl.cex = 1.2)

En el gráfico se muestra una fuerte correlación positiva entre preciom y areaconst (0.74), indicando que a mayor área construida, mayor precio de la vivienda. Esta es la relación más fuerte en la tabla, lo que indica que el área construida es un factor clave en la determinación del precio.

Adicionalmente, se muestra también una correlación positiva moderada - alta entre preciom y estrato (0.61) indicando que a mayor estrato, mayor precio de la vivienda. Esto tiene sentido, ya que los estratos más altos suelen asociarse con viviendas más costosas debido a la ubicación y calidad de los servicios.

Por último, El número de baños también influye en el precio, aunque en menor medida.El número de habitaciones tiene menor impacto que el área construida y se evidencia una relación moderada entre algunas variables predictoras, lo que sugiere posibles interacciones a considerar en el modelo de regresión.

Debido a la alta correlación entre el área construida, el precio y los estratos, se agrega una gráfica de dispersión que muestra la relación lineal entre ellas. La gráfica muestra esta relación, señalando también que no son las únicas variables que explican su comportamiento pues aunque a precios y areas bajas muestran una tendencia lineal, cuando estos valores crecen suelen ser más dispersos.

p1 <- ggplot(data_b1_norte, aes(x = areaconst, y = preciom)) +
  geom_point(aes(color = estrato), alpha = 0.7) +
  geom_smooth(method = "lm", col = "red") +
  labs(title = "Relación entre Precio y Área Construida",
       x = "Área Construida (m²)", y = "Precio (millones)") +
  theme_minimal()

ggplotly(p1)
`geom_smooth()` using formula = 'y ~ x'

Se analiza la presencia de datos atípicos en las variables, principalmente en lo que corresponde a Precio y Estrato. Estos datos atípicos pueden arrojar cierto ruido al modelo y disminuir su precisión.

p2 <- ggplot(data_b1_norte, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio por Estrato",
       x = "Estrato", y = "Precio (millones)") +
  theme_minimal()

ggplotly(p2)

Como se observa en el gráfico anterior, hay evidencia de valores atípicos en el precio de las viviendas para los estratos 4 y 5 principalmente, que son los estratos de interés para el caso de la vivienda 1. Para resolver esto se procede a eliminar los valores atípicos usando los límites del rango intercuartílico para cada estrato. A continuación se muestra el Boxplot resultante de este ajuste.

library(dplyr)

# Calcular los límites del IQR para cada estrato
outliers_filtered <- data_b1_norte %>%
  group_by(estrato) %>%
  mutate(
    Q1 = quantile(preciom, 0.25, na.rm = TRUE),
    Q3 = quantile(preciom, 0.75, na.rm = TRUE),
    IQR = Q3 - Q1,
    lower_bound = Q1 - 1.5 * IQR,
    upper_bound = Q3 + 1.5 * IQR
  ) %>%
  filter(preciom >= lower_bound & preciom <= upper_bound) %>%
  select(-Q1, -Q3, -IQR, -lower_bound, -upper_bound)  # Remover columnas auxiliares

# Visualizar el nuevo boxplot sin outliers
p2_filtered <- ggplot(outliers_filtered, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio por Estrato (Sin Atípicos)",
       x = "Estrato", y = "Precio (millones)") +
  theme_minimal()

ggplotly(p2_filtered)
data_b1_norte <- outliers_filtered

Como resultado se liminan cerca de 24 viviendas con datos atípicos, no se considera grave ya que según se oberva en el boxplot seguramente exceden el valor del crédito preaprobado por lo que tampoco se podrían tener en cuenta en la oferta final del cliente.

Al eliminar los valores atípicos se evita que efecto que pueden tener en el modelo de regresión, en una prueba ejecutada con el modelo conservando los datos atípicos el R2 alcanzó un valor de 0.64, mientras que al quitar los atípicos, llegó al 0.71. Mostrando una mayor precisión.

Modelo de regesión lineal múltiple Vivienda 1 (Caso 1)

A continuación se realiza un modelo estadístico de regregión líneal multiple con el que se busca estimar el precio de una vivienda con dichas características.

modelo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = data_b1_norte)
summary(modelo)

Call:
lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
    banios, data = data_b1_norte)

Residuals:
    Min      1Q  Median      3Q     Max 
-568.17  -58.55  -12.88   39.47  563.04 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -173.6702    26.1454  -6.642 8.11e-11 ***
areaconst       0.5209     0.0418  12.462  < 2e-16 ***
estrato        79.2293     6.5367  12.121  < 2e-16 ***
habitaciones    1.2175     3.8839   0.313    0.754    
parqueaderos   14.4383     3.5815   4.031 6.42e-05 ***
banios         24.3744     4.9421   4.932 1.11e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 107.8 on 497 degrees of freedom
Multiple R-squared:  0.7153,    Adjusted R-squared:  0.7124 
F-statistic: 249.7 on 5 and 497 DF,  p-value: < 2.2e-16

El modelo es significativo en su conjunto y explica una parte considerable de la variabilidad del precio (71.2%). Sin embargo, el área construida, estrato, parqueaderos y baños son variables clave en la determinación del precio. El número de habitaciones no es significativo, lo que sugiere que podría no ser una variable relevante o que su efecto ya está capturado por otras variables. La dispersión de los residuos sugiere posibles outliers o heterocedasticidad, lo que puede requerir ajustes o transformaciones adicionales en el modelo.

En este caso se recomienda revisar la normalidad y homocedasticidad de los residuos, y evaluar posibles interacciones o transformaciones para mejorar la precisión del modelo. A continuación se realizará la validación de los supuestos del modelo.

Supuesto de linealidad

En este caso se observa que los residuos están dispersos aleatoriamente alrededor de 0, por lo que se puede asumir que el supuesto se cumple. Por lo que los resultados del modelo pueden ser confiables ya que no se observa ningún tipo de patrón enlos residuos.

plot(modelo$fitted.values, resid(modelo),
     main = "Gráfico de residuos vs. valores ajustados",
     xlab = "Valores ajustados",
     ylab = "Residuos",
     pch = 20, col = "blue")
abline(h = 0, col = "red", lwd = 2)

Supuesto de normalidad de los residuos

A continuación se procede a verificar si los errores siguen una distribución normal, lo cual es clave para hacer inferencias válidas (intervalos de confianza y pruebas de hipótesis).

# Histograma de residuos
hist(resid(modelo), breaks = 20, col = "lightblue", main = "Histograma de Residuos")

# Q-Q plot
qqnorm(resid(modelo))
qqline(resid(modelo), col = "red", lwd = 2)

shapiro.test(modelo$residuals)

    Shapiro-Wilk normality test

data:  modelo$residuals
W = 0.92044, p-value = 1.222e-15

Como se observa en el histograma de frecuencias de los residuos y en el Q-Q Plot, los residuos tienden a ajustarse a la línea, indicando una tendencia a una distribución normal en los residuos, por lo que se podría concluir que el modelo cumple con el supuesto de normalidad en los residuos.

Supuesto de homocedasticidad

Este supuesto tiene como objetivo verificar si la varianza de los residuos es constante, para ellos se aplica la prueba de Breusch-Pagan y se realiza un gráfico de dispersión de residuos.

bptest(modelo)  # Prueba de Breusch-Pagan

    studentized Breusch-Pagan test

data:  modelo
BP = 105.32, df = 5, p-value < 2.2e-16

El test de Breusch-Pagan detecta si la varianza de los errores cambia sistemáticamente con los valores de las variables explicativas, en este caso tenemos un p-value < a 0,05 por lo que se rechaza la Ho, lo que sugiere que hay heterocedasticidad en el modelo. En este caso se podría aplicar una transformación en la variable dependiente (preciom).

El siguiente gráfico muestra los residuos tienen una forma de cono (residuos más dispersos a medida que aumenta y^) lo que indica que la varianza de los errores crece con los valores ajustados.}

plot(modelo$fitted.values, abs(resid(modelo)),
     main = "Prueba de Homocedasticidad",
     xlab = "Valores ajustados",
     ylab = "Residuos absolutos",
     pch = 20, col = "blue")
abline(h = 0, col = "red", lwd = 2)

#### Independencia de los residuos

Esta prueba busca verificar que los residuos no están correlacionados entre sí.Para lo cuál se aplica la prueba de Durbin-Watson. En esta prueba un valor cercano a 2 (>1.5 & < 2.5 indica que no hay autocorrelación fuerte. Sin embargo, el p-value, menor que 0,05 indica que se rechaza la Ho de ausencia de autocorrelación. El test de Durbin-Watson sugiere problemas de autocorrelación, lo que podría afectar la validez de las inferencias del modelo. En este caso se considera necesario realizar tranformaciones en las variables o verificar si es posible incluir otras variables al modelo, como por ejemplo, la ubicación a partir de las coordenadas.

dwtest <- dwtest(modelo)  # Prueba de independencia de residuos
dwtest

    Durbin-Watson test

data:  modelo
DW = 1.7767, p-value = 0.0052
alternative hypothesis: true autocorrelation is greater than 0

Supuesto de NO Colinealidad

Con este supuesto se verifica si las variables predictoras están fuertemente correlacionadas entre sí. Para ello aplicamos el método VIF (Factor de Inflación de Varianza), con este método si para las variables se identifica un VIF < 5, se evidencia que no hay problemas de colinealidad. Como se muestra a continuacón, ninguna de las variables mostró un VIF > 5 por lo que se podría descartar este problema en el modelo para el cálculo del precio de la vivienda.

vif <- vif(modelo)  # Calcular VIF para cada variable
vif
   areaconst      estrato habitaciones parqueaderos       banios 
    1.706171     1.690588     1.764913     1.345494     2.162065 

Modelo ajustado (log)

Teniendo en cuenta los resultados de los anteriores supuestos se realiza una transformación lograrítmica en el modelo en la variable dependiente (preciom) y en la variable de area construida (areaconst). Como se muestra en el resumen del modelo el R2 aumenta a un 0.8 lo que genera mayor confianza en los resultados del modelo.

# Aplicar transformación logarítmica
data_b1_norte$log_preciom <- log(data_b1_norte$preciom)   # Precio de la vivienda
data_b1_norte$log_areaconst <- log(data_b1_norte$areaconst)  # Área construida

#  Ajustar modelo de regresión con las variables transformadas
modelo_log <- lm(log_preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios, 
                 data = data_b1_norte)

# Resumen del nuevo modelo
summary(modelo_log)

Call:
lm(formula = log_preciom ~ log_areaconst + estrato + habitaciones + 
    parqueaderos + banios, data = data_b1_norte)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.59764 -0.14307 -0.00462  0.13903  0.76809 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   2.556190   0.101101  25.284  < 2e-16 ***
log_areaconst 0.425719   0.025015  17.019  < 2e-16 ***
estrato       0.185637   0.014946  12.420  < 2e-16 ***
habitaciones  0.006125   0.008351   0.733    0.464    
parqueaderos  0.033225   0.007735   4.295 2.10e-05 ***
banios        0.050493   0.010672   4.731 2.91e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2318 on 497 degrees of freedom
Multiple R-squared:  0.8069,    Adjusted R-squared:  0.8049 
F-statistic: 415.3 on 5 and 497 DF,  p-value: < 2.2e-16

Al aplicar la técnica de Breush Pagan para verificar la homocedasticidad se observa una clara mejora en el modelo con un p-value > 0.05, que indica que no hay evidencia de heterocedasticidad.

bptest(modelo_log)

    studentized Breusch-Pagan test

data:  modelo_log
BP = 7.7209, df = 5, p-value = 0.1723

Predicción del valor para las codiciones de vivienda 1 con estrato 4.

Con el modelo ajustado (modelo_log) se procede a calcular el precio de la vivienda para las características mencionadas, para ello se crea un dataframe con las características de la vivienda. Debido a la transformación Log en la variable explicativa del área construida, se aplica el logaritmo a la condición de entrada. Al final se revierte el logaritmo de la predicción para tener el precio estimado de la vivienda. Como la condición es que la vivienda sea de estrato 4 o 5, primero se ejecuta la regresión para el estrato 4.

#  Crear un dataframe con las características específicas para predicción
nueva_vivienda <- data.frame(
  tipo = factor("Casa", levels = levels(data_b1_norte$tipo)),  # Asegurar mismo nivel de factor
  log_areaconst = log(200),  # Área construida
  parqueaderos = 1,  # Número de parqueaderos
  banios = 2,  # Número de baños
  habitaciones = 4,  # Número de habitaciones
  estrato = 4,  # Estrato
  zona = factor("Zona Norte", levels = levels(data_b1_norte$zona))  # Asegurar niveles
)

# Predecir el precio usando el modelo ajustado
precio_predicho <- predict(modelo_log, newdata = nueva_vivienda)

# Mostrar el resultado


precio_predicho <- exp(precio_predicho)
precio_predicho
       1 
302.7915 

El valor para una vivienda de estrato 4 con las características señaladas ronda alrededor de los 303 millones de pesos, lo que está dentro del rango de vivienda que se pueden adquirir con el crédito preaprobado.

Predicción del valor para las condiciones de vivienda 1 con estrato 5.

Se ejecuta nuevamente el modelo para viviendas de estrato 5. En este caso el valor predicho es mayor al crédito preaprobado, por lo que se sugiere que la búsqueda se realice sobre viviendas de estrato 4.

# 1️⃣ Crear un dataframe con las características específicas para predicción
nueva_vivienda <- data.frame(
  tipo = factor("Casa", levels = levels(data_b1_norte$tipo)),  # Asegurar mismo nivel de factor
  log_areaconst = log(200),  # Área construida
  parqueaderos = 1,  # Número de parqueaderos
  banios = 2,  # Número de baños
  habitaciones = 4,  # Número de habitaciones
  estrato = 5,  # Estrato
  zona = factor("Zona Norte", levels = levels(data_b1_norte$zona))  # Asegurar niveles
)

# 2️⃣ Predecir el precio usando el modelo ajustado
precio_predicho <- predict(modelo_log, newdata = nueva_vivienda)

# 3️⃣ Mostrar el resultado


precio_predicho <- exp(precio_predicho)
precio_predicho
       1 
364.5563 

Para estrato 5, el precio estimado para la vivienda es superior a los 364 millones, lo que excede el valor del crédito preaprobado.

Mapa de posibles viviendas (Caso 1- Vivienda 1).

A continuación se muestra de forma gráfica (mapa) las opciones de vivienda que cumplen con las características mínimas establecidas. Se establece un rango de precios que va entre 302.79 millones de acuerdo al valor predicho y el valor del crédito preaprobado (350 millones).

# Filtrar viviendas con precio entre 314 y 350 millones
viviendas_opcionadas <- data_b1_norte %>%
  filter(preciom >= 302.79 & preciom <= 350 & areaconst >= 200 & parqueaderos >= 1 & habitaciones >= 4 & banios >= 2 & estrato >=4 &estrato <=5 & tipo == "Casa")

Mapa de viviendas disponibles para el caso 1 (Vivienda 1)

Se muestra un mapa con las 27 viviendas opcionadas para ser tomadas para el caso 1 que cumplen con las condiciones mínimas establecidas. En el mapa podra hacer clic sobre cada punto y ver el valor y el área en metros cuadrados. Tener en cuenta que estás pueden tener condiciones incluso superiores a las establecidas teniendo en cuenta que hay un crédito preaprobado por un valor más alto que aque estimado en el modelo ajustado (transformación logarítmica).

# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = viviendas_opcionadas$preciom)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(viviendas_opcionadas) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(preciom),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Precio:", preciom, "<br>Area:", areaconst),
    label = ~paste("Precio:", preciom, "<br>Area:", areaconst)# Etiqueta que aparece al pasar el mouse
  )

map 

Caso vivienda 2 (Zona Sur) - Análisis exporatorio

Ahora se procede a realizar el análisis y construir el modelo para el segundo caso, para ello se aplica un filtro para la viviendas de tipo “Apartamento” en la Zona Sur y se grafican en un mapa.

data_base2 <- vivienda %>%
  filter(zona == "Zona Sur" & tipo == "Apartamento")
#head(data_base1,3)
# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = data_base2$estrato)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(data_base2) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(estrato),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Estrato:", estrato, "<br>Zona:", zona),
    label = ~estrato             # Etiqueta que aparece al pasar el mouse
  )

map  # Muestra el mapa

Cómo se observa en el mapa, hay viviendas que no corresponden a la zona Sur según la ubicación de sus coordenadas. En este caso se procede a hacer una limpieza de los datos. Para ello, utilizamos la capa cargada durante el caso 1 con las comunas de Cali y se hace el respectivo cruce geográfico.

#  Convertir data_base1 en objeto espacial sin perder latitud y longitud
data_base2_sf <- data_base2 %>%
  mutate(latitud_orig = latitud, longitud_orig = longitud) %>%  # Copiar las coordenadas originales
  st_as_sf(coords = c("longitud", "latitud"), crs = 4326)

#  Realizar cruce espacial
data_b2_cruzado <- st_join(data_base2_sf, comunas_wgs84, left = TRUE)

#  Restaurar latitud y longitud como columnas separadas
data_b2_cruzado <- data_b2_cruzado %>%
  mutate(latitud = st_coordinates(.)[, 2],  # Extraer latitud de la geometría
         longitud = st_coordinates(.)[, 1]) %>%
  st_drop_geometry()  # Opcional: quitar la columna de geometría si ya no se necesita

# Ver las primeras filas
head(data_b2_cruzado, 3)
# A tibble: 3 × 19
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  5098 Zona S… 05          4     290        96            1      2            3
2   698 Zona S… 02          3      78        40            1      1            2
3  8199 Zona S… <NA>        6     875       194            2      5            3
# ℹ 10 more variables: tipo <chr>, barrio <chr>, latitud_orig <dbl>,
#   longitud_orig <dbl>, comuna <int>, nombre <chr>, shape_leng <dbl>,
#   shape_area <dbl>, latitud <dbl>, longitud <dbl>

Ahora se procede a filtrar los datos a partir de la columna “comuna” agregada durante el cruce espacial y se seleccionan las comunas correspondientes a la zona Sur, que en este caso corresponden a las comunas 17 y 22. El resultado se grafica en un mapa y se valida el resultado.

# Filtrar el dataframe data_b1_ajustado según los valores de la columna comuna en data_b1_cruzado
data_b2_sur <- data_b2_cruzado %>%
  filter(comuna %in% c(17,22))
# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = data_b2_sur$estrato)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(data_b2_sur) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(estrato),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Estrato:", estrato, "<br>Zona:", zona),
    label = ~estrato             # Etiqueta que aparece al pasar el mouse
  )

map  # Muestra el mapa

Como resultado se obtiene un dataset con 1.856 viviendas filtradas para la zonas de interés. Ahora que corroboramos que los datos corresponden efectivamente a la Zona Sur se realiza el análisis de los mismos a través del paquete SKIM, con este se busca dar una mirada rápida al comportamiento de los datos y verificar si hay valores vacíos y el resumen estadístico de cada variable:

# Resumen con mejor formato
skim(data_b2_sur)
Data summary
Name data_b2_sur
Number of rows 1856
Number of columns 19
_______________________
Column type frequency:
character 5
numeric 14
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
zona 0 1.00 8 8 0 1 0
piso 401 0.78 2 2 0 12 0
tipo 0 1.00 11 11 0 1 0
barrio 0 1.00 4 23 0 66 0
nombre 0 1.00 9 9 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
id 0 1.00 3742.62 1659.78 1250.00 2171.75 3564.50 5262.25 7664.00 ▇▆▅▅▂
estrato 0 1.00 4.81 0.79 3.00 4.00 5.00 5.00 6.00 ▁▇▁▇▅
preciom 0 1.00 320.78 204.78 97.00 195.00 260.00 350.00 1750.00 ▇▂▁▁▁
areaconst 0 1.00 99.61 54.19 40.00 68.00 87.00 110.00 932.00 ▇▁▁▁▁
parqueaderos 226 0.88 1.45 0.69 1.00 1.00 1.00 2.00 10.00 ▇▁▁▁▁
banios 0 1.00 2.57 0.93 0.00 2.00 2.00 3.00 7.00 ▁▇▅▁▁
habitaciones 0 1.00 2.96 0.60 0.00 3.00 3.00 3.00 6.00 ▁▂▇▂▁
latitud_orig 0 1.00 3.37 0.02 3.33 3.37 3.37 3.38 3.41 ▂▂▇▅▂
longitud_orig 0 1.00 -76.53 0.01 -76.55 -76.54 -76.53 -76.52 -76.51 ▁▅▅▅▇
comuna 0 1.00 18.17 2.12 17.00 17.00 17.00 17.00 22.00 ▇▁▁▁▂
shape_leng 0 1.00 16873.83 832.73 15371.43 17335.13 17335.13 17335.13 17335.13 ▂▁▁▁▇
shape_area 0 1.00 12218205.40 829721.71 10721224.18 12677841.89 12677841.89 12677841.89 12677841.89 ▂▁▁▁▇
latitud 0 1.00 3.37 0.02 3.33 3.37 3.37 3.38 3.41 ▂▂▇▅▂
longitud 0 1.00 -76.53 0.01 -76.55 -76.54 -76.53 -76.52 -76.51 ▁▅▅▅▇

Ajuste de la variable “parqueaderos” y “piso” en las viviendas de Zona Sur.

Se identifican las columnas con datos faltanes, en este caso, la variable parqueaderos y piso. Para la variable parqueaderos, se asume que los 226 datos faltantes corresponden a viviendas sin parqueadero, por lo que se ajusta el valor faltante a 0. Para la variable piso, por ser apartamentos se considera valiosa. Pero al no tener información y no estar dentro de las características de la vivienda, se descarta cualquier ajuste.

data_b2_sur <- data_b2_sur %>%
  mutate(parqueaderos = ifelse(is.na(parqueaderos) | parqueaderos == "", 0, parqueaderos))

Adicionalmente, se hace una análisis de correlaciones entre las variables. El siguiente gráfico muestra la correlación de Pearson entre las variables del modelo, indicando la fuerza y dirección de la relación entre el precio de la vivienda (preciom) y las demás características.

# Seleccionar solo variables numéricas para la correlación
datos_numericos2 <- data_b2_sur %>%
  select(preciom, areaconst, estrato, banios, habitaciones) %>%
  na.omit()  # Eliminar filas con valores faltantes

# Calcular matriz de correlación
matriz_cor2 <- cor(datos_numericos2)

# Visualizar con corrplot
corrplot(matriz_cor2, method = "color", addCoef.col = "black", tl.cex = 1.2)

En el gráfico se muestra una fuerte correlación positiva entre preciom y areaconst (0.77), un poco más alta que la que se vió en la zona norte para las viviendas tipo Casa. Esto igualmente indica que a mayor área construida, mayor precio de la vivienda. Esta es la relación más fuerte en la tabla, lo que indica que el área construida es un factor clave en la determinación del precio, al igual que el número de baños y el estrato.

Se analiza la presencia de datos atípicos en las variables, principalmente en lo que corresponde a Precio y Estrato. Estos datos atípicos pueden arrojar cierto ruido al modelo y disminuir su precisión.

p3 <- ggplot(data_b2_sur, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio por Estrato",
       x = "Estrato", y = "Precio (millones)") +
  theme_minimal()

ggplotly(p3)

Como se observa en el gráfico anterior, hay evidencia de valores atípicos en el precio de las viviendas para los estratos 5 y 6 principalmente, que son los estratos de interés para el caso de la vivienda 2. Para resolver esto se procede a eliminar los valores atípicos usando los límites del rango intercuartílico para cada estrato, igual que como se hizo para la zona Norte. A continuación se muestra el Boxplot resultante de este ajuste.

library(dplyr)

# Calcular los límites del IQR para cada estrato
outliers_filtered <- data_b2_sur %>%
  group_by(estrato) %>%
  mutate(
    Q1 = quantile(preciom, 0.25, na.rm = TRUE),
    Q3 = quantile(preciom, 0.75, na.rm = TRUE),
    IQR = Q3 - Q1,
    lower_bound = Q1 - 1.5 * IQR,
    upper_bound = Q3 + 1.5 * IQR
  ) %>%
  filter(preciom >= lower_bound & preciom <= upper_bound) %>%
  select(-Q1, -Q3, -IQR, -lower_bound, -upper_bound)  # Remover columnas auxiliares

# Visualizar el nuevo boxplot sin outliers
p3_filtered <- ggplot(outliers_filtered, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
  geom_boxplot(alpha = 0.7) +
  labs(title = "Distribución del Precio por Estrato (Sin Atípicos)",
       x = "Estrato", y = "Precio (millones)") +
  theme_minimal()

ggplotly(p3_filtered)
data_b2_sur <- outliers_filtered

Como resultado se liminan cerca de 24 viviendas con datos atípicos, no se considera grave ya que según se oberva en el boxplot seguramente exceden el valor del crédito preaprobado por lo que tampoco se podrían tener en cuenta en la oferta final del cliente.

Al eliminar los valores atípicos se evita que efecto que pueden tener en el modelo de regresión, en una prueba ejecutada con el modelo conservando los datos atípicos el R2 alcanzó un valor de 0.64, mientras que al quitar los atípicos, llegó al 0.71. Mostrando una mayor precisión.

Modelo de regesión lineal múltiple Caso 2

Teniendo en cuenta el resultado del ejercicio con la vivienda 1 se aplica un modelo con transformación logarítmica en la variable dependiente (preciom) y la variable predictora del área construida.

# Aplicar transformación logarítmica
data_b2_sur$log_preciom <- log(data_b2_sur$preciom)   # Precio de la vivienda
data_b2_sur$log_areaconst <- log(data_b2_sur$areaconst)  # Área construida

#  Ajustar modelo de regresión con las variables transformadas
modelo2_log <- lm(log_preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios, 
                 data = data_b2_sur)

# Resumen del nuevo modelo
summary(modelo2_log)

Call:
lm(formula = log_preciom ~ log_areaconst + estrato + habitaciones + 
    parqueaderos + banios, data = data_b2_sur)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.58518 -0.10508  0.01204  0.12442  0.62529 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    1.786889   0.076780  23.273   <2e-16 ***
log_areaconst  0.590448   0.022198  26.599   <2e-16 ***
estrato        0.187190   0.007884  23.743   <2e-16 ***
habitaciones  -0.017620   0.008800  -2.002   0.0454 *  
parqueaderos   0.102745   0.008313  12.360   <2e-16 ***
banios         0.074343   0.007807   9.523   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.184 on 1797 degrees of freedom
Multiple R-squared:  0.8545,    Adjusted R-squared:  0.8541 
F-statistic:  2110 on 5 and 1797 DF,  p-value: < 2.2e-16

La interpretación general del resultado es que las variables log_areaconst, estrato, parqueaderos y banios tienen un impacto positivo significativo en el precio. habitaciones tiene un efecto leve y negativo, lo que podría deberse a correlaciones con otras variables (ej., área construida). Respecto al aporte de cada variable se podría concluir lo siguiente:

En general el modelo presenta un buen ajuste ya que explica el 85.45% de la variabilidad en el logaritmo del precio y el promedio del error en términos del log-precio es relativamente bajo (0.184).

A continuación se realizará la validación de los supuestos del modelo

Supuesto de linealidad

En este caso se observa que los residuos están dispersos aleatoriamente alrededor de 0, al igual que con el caso 1, por lo que se puede asumir que el supuesto se cumple. Por lo que los resultados del modelo pueden ser confiables ya que no se observa ningún tipo de patrón enlos residuos.

plot(modelo2_log$fitted.values, resid(modelo2_log),
     main = "Gráfico de residuos vs. valores ajustados",
     xlab = "Valores ajustados",
     ylab = "Residuos",
     pch = 20, col = "blue")
abline(h = 0, col = "red", lwd = 2)

Supuesto de normalidad de los residuos

A continuación se procede a verificar si los errores siguen una distribución normal, lo cual es clave para hacer inferencias válidas (intervalos de confianza y pruebas de hipótesis). Este modelo tuvo mejor comportamiento que el modelo 1 para el caso 1 de vivienda y se observan los residuos más ajustado a la línea (roja). Aunque, el grápico de distribución del error muestra una tendencia hacía la derecha.

# Histograma de residuos
hist(resid(modelo2_log), breaks = 20, col = "lightblue", main = "Histograma de Residuos")

# Q-Q plot
qqnorm(resid(modelo2_log))
qqline(resid(modelo2_log), col = "red", lwd = 2)

shapiro.test(modelo2_log$residuals)

    Shapiro-Wilk normality test

data:  modelo2_log$residuals
W = 0.96081, p-value < 2.2e-16

Debido a la incertidumbre se aplica una prueba de Shapiro-Wil, donde el W muestra que los datos están muy cerca a una distribución normal (1). Sin embargo, el p-value es menor que 0,05 por lo que genera dudas sobre la normalidad de los residuos. Sin embargo, remitiendonos al Q-Q plot se asume que están cerca de la normalidad, pero que el resultado se ve afectado por la asimetría.

Supuesto de homocedasticidad

Este supuesto tiene como objetivo verificar si la varianza de los residuos es constante, para ellos se aplica la prueba de Breusch-Pagan y se realiza un gráfico de dispersión de residuos.

bptest(modelo2_log)  # Prueba de Breusch-Pagan

    studentized Breusch-Pagan test

data:  modelo2_log
BP = 304.11, df = 5, p-value < 2.2e-16

Al igual que en el modelo para el caso 1 El test de Breusch-Pagan muestra un p-value < a 0,05 por lo que se rechaza la Ho, lo que sugiere que hay heterocedasticidad en el modelo. Sin embargo, el gráfico muestra los residuos no tiene una forma de cono (residuos más dispersos a medida que aumenta y^) lo que indica que la varianza de los errores no crecen con los valores ajustados, pero no tienen una varianza constante.

plot(modelo2_log$fitted.values, abs(resid(modelo2_log)),
     main = "Prueba de Homocedasticidad",
     xlab = "Valores ajustados",
     ylab = "Residuos absolutos",
     pch = 20, col = "blue")
abline(h = 0, col = "red", lwd = 2)

Independencia de los residuos

En esta prueba un valor cercano a 2 (>1.5 & < 2.5 indica que no hay autocorrelación fuerte. Sin embargo, el p-value, menor que 0,05 indica que se rechaza la Ho de ausencia de autocorrelación. El test de Durbin-Watson sugiere problemas de autocorrelación, lo que podría afectar la validez de las inferencias del modelo. En este caso se considera necesario realizar tranformaciones en las variables o verificar si es posible incluir otras variables al modelo, como por ejemplo, la ubicación a partir de las coordenadas o en este caso, el “piso” pues por ser apartamentos tiende a tener un efecto mas directo en el precio.

dwtest <- dwtest(modelo2_log)  # Prueba de independencia de residuos
dwtest

    Durbin-Watson test

data:  modelo2_log
DW = 1.531, p-value < 2.2e-16
alternative hypothesis: true autocorrelation is greater than 0

Supuesto de NO Colinealidad

Con este supuesto se verifica si las variables predictoras están fuertemente correlacionadas entre sí. Para ello aplicamos el método VIF (Factor de Inflación de Varianza), con este método si para las variables se identifica un VIF < 5, se evidencia que no hay problemas de colinealidad. Como se muestra a continuacón, ninguna de las variables mostró un VIF > 5 por lo que se podría descartar este problema en el modelo para el cálculo del precio de la vivienda para el caso 2.

vif <- vif(modelo2_log)  # Calcular VIF para cada variable
vif
log_areaconst       estrato  habitaciones  parqueaderos        banios 
     3.589257      2.078381      1.415889      2.086940      2.628205 

Predicción del valor para las codiciones de vivienda 2 con estrato 5.

Con el modelo ajustado (modelo_log) se procede a calcular el precio de la vivienda para las características mencionadas, para ello se crea un dataframe con las características de la vivienda. Debido a la transformación Log en la variable explicativa del área construida, se aplica el logaritmo a la condición de entrada. Al final se revierte el logaritmo de la predicción para tener el precio estimado de la vivienda. Como la condición es que la vivienda sea de estrato 4 o 5, primero se ejecuta la regresión para el estrato 4.

#  Crear un dataframe con las características específicas para predicción
nueva_vivienda2 <- data.frame(
  tipo = "Apartamento",
  log_areaconst = log(300),  # Área construida
  parqueaderos = 3,  # Número de parqueaderos
  banios = 3,  # Número de baños
  habitaciones = 5,  # Número de habitaciones
  estrato = 5  # Estrato
)

# Predecir el precio usando el modelo ajustado
precio_predicho2 <- predict(modelo2_log, newdata = nueva_vivienda2)

# Mostrar el resultado


precio_predicho2 <- exp(precio_predicho2)
precio_predicho2
       1 
688.0001 

El valor para una vivienda de estrato 5 con las características señaladas ronda alrededor de los 688 millones de pesos, lo que está dentro del rango de vivienda que se pueden adquirir con el crédito preaprobado (850 millones).

Predicción del valor para las condiciones de vivienda 2 con estrato 6.

Se ejecuta nuevamente el modelo para viviendas de estrato 5. En este caso el valor predicho es mayor al crédito preaprobado, por lo que se sugiere que la búsqueda se realice sobre viviendas de estrato 4.

# Crear un dataframe con las características específicas para predicción
nueva_vivienda2 <- data.frame(
  tipo = "Apartamento",
  log_areaconst = log(300),  # Área construida
  parqueaderos = 3,  # Número de parqueaderos
  banios = 3,  # Número de baños
  habitaciones = 5,  # Número de habitaciones
  estrato = 6  # Estrato
)

#  Predecir el precio usando el modelo ajustado
precio_predicho2 <- predict(modelo2_log, newdata = nueva_vivienda2)

#  Mostrar el resultado


precio_predicho2 <- exp(precio_predicho2)
precio_predicho2
       1 
829.6292 

Para estrato 6, el precio estimado para la vivienda está por debajo de los 850 millones, lo que aún está dentro del rango del crédito preaprobado.

Mapa de posibles viviendas (Caso 1- Vivienda 1).

A continuación se muestra de forma gráfica (mapa) las opciones de vivienda que cumplen con las características mínimas establecidas. Se establece un rango de precios que va entre 688.1 millones de acuerdo al valor predicho y el valor del crédito preaprobado (850 millones), lo que permite cubrir un rango de viviendas que va entre los estratos 5 y 6.

Sin embargo, al aplicar el filtro con las condiciones establecidas, no se encuentran viviendas disponibles que cumplan con todas las características. Aunque se ajustan al precio, no se ajustan a las condiciones de número de habitaciones, baños o parqueaderos.

# Filtrar viviendas con precio entre 688 y 850 millones
viviendas_opcionadas2 <- data_b2_sur %>%
  filter(preciom >= 688.1 & preciom <= 850 & areaconst >= 300 & parqueaderos >= 3 & habitaciones >= 5 & banios >= 3 & estrato >=5 &estrato <=6 & tipo == "Apartamento")

head(viviendas_opcionadas2,3)
# A tibble: 0 × 21
# Groups:   estrato [0]
# ℹ 21 variables: id <dbl>, zona <chr>, piso <chr>, estrato <dbl>,
#   preciom <dbl>, areaconst <dbl>, parqueaderos <dbl>, banios <dbl>,
#   habitaciones <dbl>, tipo <chr>, barrio <chr>, latitud_orig <dbl>,
#   longitud_orig <dbl>, comuna <int>, nombre <chr>, shape_leng <dbl>,
#   shape_area <dbl>, latitud <dbl>, longitud <dbl>, log_preciom <dbl>,
#   log_areaconst <dbl>

En este caso se recomienda mantener algunas condiciones pero mayor flexibilidad en el número de parqueaderos, habitaciones y baños. Si es el caso se se identificó una vivienda que cumple los criterios de precio, area construida mínima y parqueaderos, aunque con solo 3 baños y 3 habitaciones.

# Filtrar viviendas con precio entre 688 y 850 millones
viviendas_opcionadas2 <- data_b2_sur %>%
  filter(preciom >= 688.1 & preciom <= 850 & areaconst >= 300 & tipo == "Apartamento")

Mapa de viviendas disponibles para el caso 2 (Vivienda 2)

Se muestra un mapa con 1 vivienda opcionada para ser tomadas para el caso 2 que cumple parcialmente con las condiciones mínimas establecidas. En el mapa podra hacer clic sobre cada punto y ver el valor y el área en metros cuadrados.

# Genera una paleta de colores usando "Set1" para diferenciar los estratos.
colores_estrato <- colorFactor(palette = "Set1", domain = viviendas_opcionadas2$preciom)

# Se crea el mapa utilizando leaflet y el dataset 'data_base1'
map <- leaflet(viviendas_opcionadas2) %>%
  # Agrega la capa base de mosaicos (tiles)
  addTiles() %>%
  # Agrega marcadores circulares en las coordenadas especificadas
  addCircleMarkers(
    lng = ~longitud,              # Columna de longitud
    lat = ~latitud,               # Columna de latitud
    color = ~colores_estrato(preciom),  # Asigna color según el valor de 'estrato'
    radius = 3,                   # Define el tamaño del marcador
    # Combina información para el popup: muestra tanto 'estrato' como 'zona'
    popup = ~paste("Precio:", preciom, "<br>Area:", areaconst),
    label = ~paste("Precio:", preciom, "<br>Area:", areaconst)# Etiqueta que aparece al pasar el mouse
  )

map 

CONCLUSIONES Y RECOMENDACIONES.

Vivienda 1.

El análisis de precios de vivienda utilizando un modelo de regresión con transformación logarítmica permitió estimar el valor de una vivienda en función de sus características estructurales y su estrato socioeconómico. Los resultados muestran que el área construida y el estrato son los factores con mayor impacto en la determinación del precio, mientras que variables como el número de habitaciones no resultaron estadísticamente significativas. Si bien el modelo presenta un buen ajuste, explicando aproximadamente el 80.5% de la variabilidad en los precios, se identificaron problemas de heterocedasticidad y autocorrelación en los residuos, lo que sugiere la necesidad de aplicar transformaciones adicionales o incluir nuevas variables, como la ubicación geográfica.

A partir de las predicciones realizadas, se determinó que una vivienda de estrato 4 con las características analizadas tendría un precio estimado cercano a los 303 millones de pesos, lo que se encuentra dentro del rango del crédito preaprobado. En contraste, para estrato 5, el precio estimado supera los 364 millones de pesos, lo que excede el monto disponible, sugiriendo que la búsqueda debería centrarse en viviendas de estrato 4. Finalmente, se identificaron 27 opciones de vivienda que cumplen con las condiciones establecidas, lo que permite tomar decisiones informadas con base en los resultados del modelo. Sin embargo, para mejorar la confiabilidad de las estimaciones y garantizar su aplicabilidad en la toma de decisiones, es recomendable realizar ajustes adicionales que corrijan la heterocedasticidad y la autocorrelación detectadas en los residuos.

Vivienda 2.

El análisis del modelo de regresión ajustado para el caso 2 de vivienda en la zona sur muestra un alto poder explicativo, con un R² ajustado de 0.854, lo que indica que las variables seleccionadas explican una gran proporción de la variabilidad en el precio de la vivienda. Se observa que la variable del área construida tiene un efecto positivo y significativo en el precio, al igual que el estrato, el número de parqueaderos y los baños, mientras que el número de habitaciones presenta una relación negativa, aunque con un efecto marginal. La validación de los supuestos del modelo sugiere que los residuos están distribuidos aleatoriamente y no presentan patrones evidentes, lo que respalda la confiabilidad del modelo. Sin embargo, las pruebas de Breusch-Pagan y Durbin-Watson evidencian problemas de heterocedasticidad y autocorrelación, lo que podría afectar la precisión de las inferencias. Se recomienda considerar transformaciones adicionales o la inclusión de variables como la ubicación geográfica o el piso en el que se encuentra el apartamento.

Con base en el modelo, el precio estimado para una vivienda en estrato 5 ronda los 688 millones de pesos, lo que se encuentra dentro del rango del crédito preaprobado. Para estrato 6, el valor predicho también se encuentra dentro del límite de financiación, aunque no se identificaron viviendas que cumplieran todas las condiciones establecidas en términos de habitaciones, baños y parqueaderos. Se recomienda flexibilizar algunos criterios de búsqueda para ampliar las opciones disponibles.