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 |
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)
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 | ▁▇▆▅▃ |
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.
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.
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)
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.
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
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
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
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.
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.
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")
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
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)
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 | ▁▅▅▅▇ |
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.
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
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)
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.
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)
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
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
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).
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.
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")
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
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.
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.