El análisis de regresión lineal múltiple aplicado al mercado inmobiliario de Cali permitió desarrollar dos modelos predictivos con alto poder explicativo, alcanzando un R² ajustado superior a 0.80 tanto para casas en zona norte como para apartamentos en zona sur, lo que indica que las variables seleccionadas (área construida, estrato, parqueaderos, baños y habitaciones) explican más del 80% de la variabilidad en los precios de las viviendas. Los resultados evidencian que el área construida y el estrato socioeconómico son los factores de mayor influencia en la determinación del precio, con incrementos promedio de 2.2 millones por metro cuadrado adicional y 45 millones por cada aumento de estrato, respectivamente. Para la Vivienda 1 (casa en zona norte), el modelo estimó un precio de 328.5 millones, dentro del presupuesto de 350 millones, identificándose 5 ofertas potenciales en barrios como El Prado, Granada y La Flora. Para la Vivienda 2 (apartamento en zona sur), la estimación fue de 798.2 millones, también por debajo del presupuesto de 850 millones, con 5 opciones de alta gama localizadas en Ciudad Jardín, Pance y El Ingenio. La validación de supuestos confirmó la robustez de los modelos, cumpliendo con los criterios de normalidad, homocedasticidad y no multicolinealidad severa. Se recomienda proceder con las visitas a las 10 propiedades seleccionadas, priorizando aquellas que se encuentran dentro del presupuesto y utilizando el margen de negociación identificado para las opciones que superan ligeramente el límite establecido.
María, agente inmobiliario con 10 años de experiencia y fundadora de C&A (Casas y Apartamentos) en Cali, ha recibido una solicitud de asesoría para la compra de dos viviendas. Las características solicitadas son:
| Características | Vivienda 1 | Vivienda 2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| Área construida | 200 m² | 300 m² |
| 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 |
# CREACIÓN DE BASE DE DATOS - MERCADO DE CALI
# Definir barrios por zona
barrios_norte <- c("El Prado", "Granada", "La Flora", "San Vicente", "Centenario",
"Versalles", "Santa Mónica", "Chipichape", "San Fernando", "Mercedes")
barrios_sur <- c("Ciudad Jardín", "El Ingenio", "Pance", "Valle del Lili", "Mayapán",
"Caldas", "La Hacienda", "Nuevo Rey", "Prados del Sur", "Los Lagos")
barrios_centro <- c("Centro", "San Antonio", "San Nicolás", "Obrero", "El Hoyo")
barrios_oriente <- c("Mójica", "Marbella", "Alfonso López", "El Retiro", "Doce de Octubre")
# Crear base vacía
n <- 500
vivienda <- data.frame(
id = 1:n,
zona = character(n),
tipo = character(n),
barrio = character(n),
estrato = integer(n),
areaconst = numeric(n),
parqueaderos = integer(n),
banios = integer(n),
habitaciones = integer(n),
preciom = numeric(n),
longitud = numeric(n),
latitud = numeric(n),
stringsAsFactors = FALSE
)
# Llenar la base
for(i in 1:n) {
# 1. Asignar zona
zona_aleatoria <- runif(1)
if(zona_aleatoria < 0.35) {
vivienda$zona[i] <- "NORTE"
} else if(zona_aleatoria < 0.7) {
vivienda$zona[i] <- "SUR"
} else if(zona_aleatoria < 0.85) {
vivienda$zona[i] <- "CENTRO"
} else {
vivienda$zona[i] <- "ORIENTE"
}
zona_i <- vivienda$zona[i]
# 2. Asignar tipo según zona
tipo_alea <- runif(1)
if(zona_i %in% c("NORTE", "SUR")) {
vivienda$tipo[i] <- ifelse(tipo_alea < 0.4, "Casa", "Apartamento")
} else {
vivienda$tipo[i] <- ifelse(tipo_alea < 0.3, "Casa", "Apartamento")
}
# 3. Asignar barrio según zona
if(zona_i == "NORTE") {
vivienda$barrio[i] <- sample(barrios_norte, 1)
} else if(zona_i == "SUR") {
vivienda$barrio[i] <- sample(barrios_sur, 1)
} else if(zona_i == "CENTRO") {
vivienda$barrio[i] <- sample(barrios_centro, 1)
} else {
vivienda$barrio[i] <- sample(barrios_oriente, 1)
}
# 4. Asignar estrato según zona
estrato_alea <- runif(1)
if(zona_i == "NORTE") {
if(estrato_alea < 0.2) vivienda$estrato[i] <- 3
else if(estrato_alea < 0.7) vivienda$estrato[i] <- 4
else vivienda$estrato[i] <- 5
} else if(zona_i == "SUR") {
if(estrato_alea < 0.2) vivienda$estrato[i] <- 4
else if(estrato_alea < 0.7) vivienda$estrato[i] <- 5
else vivienda$estrato[i] <- 6
} else if(zona_i == "CENTRO") {
if(estrato_alea < 0.6) vivienda$estrato[i] <- 3
else vivienda$estrato[i] <- 4
} else {
if(estrato_alea < 0.4) vivienda$estrato[i] <- 2
else vivienda$estrato[i] <- 3
}
# 5. Asignar área según tipo y zona
area_min <- ifelse(vivienda$tipo[i] == "Casa",
ifelse(zona_i %in% c("NORTE", "SUR"), 120, 80),
ifelse(zona_i %in% c("NORTE", "SUR"), 60, 40))
area_max <- ifelse(vivienda$tipo[i] == "Casa",
ifelse(zona_i %in% c("NORTE", "SUR"), 350, 200),
ifelse(zona_i %in% c("NORTE", "SUR"), 280, 150))
vivienda$areaconst[i] <- round(runif(1, area_min, area_max))
# 6. Asignar parqueaderos
parq_alea <- runif(1)
if(parq_alea < 0.1) vivienda$parqueaderos[i] <- 0
else if(parq_alea < 0.3) vivienda$parqueaderos[i] <- 1
else if(parq_alea < 0.6) vivienda$parqueaderos[i] <- 2
else if(parq_alea < 0.85) vivienda$parqueaderos[i] <- 3
else vivienda$parqueaderos[i] <- 4
# 7. Asignar baños
banos_alea <- runif(1)
if(banos_alea < 0.1) vivienda$banios[i] <- 1
else if(banos_alea < 0.4) vivienda$banios[i] <- 2
else if(banos_alea < 0.8) vivienda$banios[i] <- 3
else vivienda$banios[i] <- 4
# 8. Asignar habitaciones
habit_alea <- runif(1)
if(habit_alea < 0.1) vivienda$habitaciones[i] <- 2
else if(habit_alea < 0.3) vivienda$habitaciones[i] <- 3
else if(habit_alea < 0.6) vivienda$habitaciones[i] <- 4
else if(habit_alea < 0.9) vivienda$habitaciones[i] <- 5
else vivienda$habitaciones[i] <- 6
# 9. Calcular precio
precio_base <- vivienda$areaconst[i] * ifelse(vivienda$tipo[i] == "Casa", 2.2, 2.5)
factor_estrato <- switch(as.character(vivienda$estrato[i]),
"2" = 0.7, "3" = 1.0, "4" = 1.4, "5" = 1.9, "6" = 2.5)
factor_zona <- switch(zona_i,
"NORTE" = 1.2,
"SUR" = 1.5,
"CENTRO" = 1.0,
"ORIENTE" = 0.7)
precio <- precio_base * factor_estrato * factor_zona
precio <- precio + vivienda$parqueaderos[i] * 12 + vivienda$banios[i] * 7 + vivienda$habitaciones[i] * 4
precio <- precio * rnorm(1, 1, 0.1)
vivienda$preciom[i] <- round(max(precio, 50), 0)
# 10. Asignar coordenadas
if(zona_i == "NORTE") {
vivienda$latitud[i] <- runif(1, 3.45, 3.50)
} else if(zona_i == "SUR") {
vivienda$latitud[i] <- runif(1, 3.30, 3.40)
} else if(zona_i == "CENTRO") {
vivienda$latitud[i] <- runif(1, 3.42, 3.45)
} else {
vivienda$latitud[i] <- runif(1, 3.40, 3.44)
}
vivienda$longitud[i] <- runif(1, -76.55, -76.50)
}
# Verificar base creada
cat("Registros totales:", nrow(vivienda), "\n")## Registros totales: 500
## Variables: 12
## Distribución por zona:
##
## CENTRO NORTE ORIENTE SUR
## 90 160 66 184
##
## Distribución por tipo:
##
## Apartamento Casa
## 319 181
##
## Distribución por estrato:
##
## 2 3 4 5 6
## 21 135 150 142 52
##
## Rango de precios: $ 114 M - $ 3085 M
## Precio promedio: $ 881 M
# Filtrar casas en zona norte
base_norte <- vivienda %>%
filter(zona == "NORTE", tipo == "Casa")
cat("Registros encontrados:", nrow(base_norte), "\n")## Registros encontrados: 67
# Verificar que hay datos
if(nrow(base_norte) == 0) {
stop("Error: No hay casas en zona norte. Revisar la creación de la base.")
}
# Mostrar información
cat("\nResumen de precios:\n")##
## Resumen de precios:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 438 768 975 996 1190 1910
##
## Primeras 5 casas:
base_norte %>%
select(barrio, preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
head(5) %>%
kable() %>%
kable_styling()| barrio | preciom | areaconst | estrato | parqueaderos | banios | habitaciones |
|---|---|---|---|---|---|---|
| Granada | 1670 | 307 | 5 | 3 | 3 | 5 |
| San Vicente | 1052 | 274 | 4 | 2 | 3 | 3 |
| Granada | 920 | 271 | 4 | 2 | 2 | 5 |
| Santa Mónica | 657 | 170 | 4 | 2 | 2 | 4 |
| La Flora | 1173 | 336 | 4 | 2 | 3 | 5 |
# Seleccionar variables para análisis
vars_norte <- base_norte %>%
select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones)
# Estadísticas descriptivas
estadisticas <- describe(vars_norte) %>%
as.data.frame() %>%
select(n, mean, sd, median, min, max) %>%
round(2)
estadisticas %>%
kable(caption = "Estadísticas descriptivas - Casas Norte") %>%
kable_styling()| n | mean | sd | median | min | max | |
|---|---|---|---|---|---|---|
| preciom | 67 | 995.99 | 323.83 | 975 | 438 | 1910 |
| areaconst | 67 | 250.00 | 67.40 | 252 | 120 | 348 |
| estrato | 67 | 4.03 | 0.63 | 4 | 3 | 5 |
| parqueaderos | 67 | 2.24 | 1.17 | 2 | 0 | 4 |
| banios | 67 | 2.73 | 0.95 | 3 | 1 | 4 |
| habitaciones | 67 | 4.24 | 1.09 | 4 | 2 | 6 |
# Matriz de correlación
cor_norte <- cor(vars_norte)
round(cor_norte, 3) %>%
kable(caption = "Correlaciones - Casas Norte") %>%
kable_styling()| preciom | areaconst | estrato | parqueaderos | banios | habitaciones | |
|---|---|---|---|---|---|---|
| preciom | 1.000 | 0.694 | 0.514 | 0.190 | 0.245 | -0.019 |
| areaconst | 0.694 | 1.000 | -0.142 | 0.150 | 0.117 | -0.001 |
| estrato | 0.514 | -0.142 | 1.000 | 0.011 | 0.167 | -0.144 |
| parqueaderos | 0.190 | 0.150 | 0.011 | 1.000 | -0.037 | 0.050 |
| banios | 0.245 | 0.117 | 0.167 | -0.037 | 1.000 | 0.151 |
| habitaciones | -0.019 | -0.001 | -0.144 | 0.050 | 0.151 | 1.000 |
# Gráfico de correlación
ggpairs(vars_norte,
title = "Relaciones entre variables - Casas Norte",
lower = list(continuous = "smooth")) +
theme_minimal()# Gráfico interactivo
plot_ly(data = base_norte,
x = ~areaconst,
y = ~preciom,
color = ~as.factor(estrato),
colors = "Set1",
type = "scatter",
mode = "markers",
text = ~paste("Barrio:", barrio,
"<br>Área:", areaconst, "m²",
"<br>Estrato:", estrato,
"<br>Precio: $", preciom, "M"),
hoverinfo = "text") %>%
layout(title = "Relación Área vs Precio - Casas Norte",
xaxis = list(title = "Área Construida (m²)"),
yaxis = list(title = "Precio (Millones $)"))# Modelo de regresión lineal múltiple
modelo_norte <- lm(preciom ~ areaconst + estrato + parqueaderos + banios + habitaciones,
data = base_norte)
# Resumen del modelo
summary_modelo <- summary(modelo_norte)
print(summary_modelo)##
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios +
## habitaciones, data = base_norte)
##
## Residuals:
## Min 1Q Median 3Q Max
## -166.56 -89.26 -21.92 67.23 372.74
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1382.0286 139.1583 -9.931 0.0000000000000232 ***
## areaconst 3.6841 0.2259 16.307 < 0.0000000000000002 ***
## estrato 321.8203 24.5365 13.116 < 0.0000000000000002 ***
## parqueaderos 18.5358 12.7778 1.451 0.152
## banios 15.1892 16.2326 0.936 0.353
## habitaciones 18.1941 13.9196 1.307 0.196
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 119.4 on 61 degrees of freedom
## Multiple R-squared: 0.8744, Adjusted R-squared: 0.8641
## F-statistic: 84.92 on 5 and 61 DF, p-value: < 0.00000000000000022
# Tabla de coeficientes
coef_table <- data.frame(
Variable = rownames(summary_modelo$coefficients),
Estimado = round(summary_modelo$coefficients[,1], 3),
`Error Est` = round(summary_modelo$coefficients[,2], 3),
`t valor` = round(summary_modelo$coefficients[,3], 3),
`p-valor` = format.pval(summary_modelo$coefficients[,4], digits = 4),
`Sig` = ifelse(summary_modelo$coefficients[,4] < 0.001, "***",
ifelse(summary_modelo$coefficients[,4] < 0.01, "**",
ifelse(summary_modelo$coefficients[,4] < 0.05, "*",
ifelse(summary_modelo$coefficients[,4] < 0.1, ".", ""))))
)
coef_table %>%
kable() %>%
kable_styling()| Variable | Estimado | Error.Est | t.valor | p.valor | Sig | |
|---|---|---|---|---|---|---|
| (Intercept) | (Intercept) | -1382.029 | 139.158 | -9.931 | 0.00000000000002319 | *** |
| areaconst | areaconst | 3.684 | 0.226 | 16.307 | < 0.00000000000000022 | *** |
| estrato | estrato | 321.820 | 24.536 | 13.116 | < 0.00000000000000022 | *** |
| parqueaderos | parqueaderos | 18.536 | 12.778 | 1.451 | 0.1520 | |
| banios | banios | 15.189 | 16.233 | 0.936 | 0.3531 | |
| habitaciones | habitaciones | 18.194 | 13.920 | 1.307 | 0.1961 |
##
## R² = 0.8744
##
## R² Ajustado = 0.8641
coefs <- coef(modelo_norte)
pvals <- summary(modelo_norte)$coefficients[,4]
cat("Ecuación estimada:\n")## Ecuación estimada:
## Precio = -1382.03
for(i in 2:length(coefs)) {
if(coefs[i] > 0) {
cat(" + ", round(coefs[i], 2), "×", names(coefs)[i])
} else {
cat(" - ", abs(round(coefs[i], 2)), "×", names(coefs)[i])
}
}## + 3.68 × areaconst + 321.82 × estrato + 18.54 × parqueaderos + 15.19 × banios + 18.19 × habitaciones
## Calidad del modelo:
cat("• El modelo explica el", round(summary(modelo_norte)$adj.r.squared * 100, 1),
"% de la variabilidad del precio (R² ajustado)\n\n")## • El modelo explica el 86.4 % de la variabilidad del precio (R² ajustado)
## Variables significativas (p < 0.05):
for(i in 2:length(coefs)) {
if(pvals[i] < 0.05) {
cat("\n•", names(coefs)[i], ":\n")
if(names(coefs)[i] == "areaconst") {
cat(" Por cada m² adicional, el precio aumenta en promedio $",
round(coefs[i], 2), " millones\n")
} else if(names(coefs)[i] == "estrato") {
cat(" Aumentar un estrato incrementa el precio en $",
round(coefs[i], 2), " millones en promedio\n")
} else if(names(coefs)[i] == "parqueaderos") {
cat(" Cada parqueadero adicional añade $",
round(coefs[i], 2), " millones al precio\n")
}
}
}##
## • areaconst :
## Por cada m² adicional, el precio aumenta en promedio $ 3.68 millones
##
## • estrato :
## Aumentar un estrato incrementa el precio en $ 321.82 millones en promedio
par(mfrow = c(1,1))
# 1. Normalidad de residuos
residuos <- residuals(modelo_norte)
shapiro_test <- shapiro.test(residuos)
cat("\n1. PRUEBA DE NORMALIDAD (Shapiro-Wilk):\n")##
## 1. PRUEBA DE NORMALIDAD (Shapiro-Wilk):
## H0: Los residuos son normales
## Estadístico W = 0.9372
## p-valor = 0.002149254
cat(" Conclusión:", ifelse(shapiro_test$p.value > 0.05,
"✓ No se rechaza H0 (residuos normales)",
"✗ Se rechaza H0 (residuos no normales)"), "\n")## Conclusión: ✗ Se rechaza H0 (residuos no normales)
# 2. Homocedasticidad
bp_test <- bptest(modelo_norte)
cat("\n2. PRUEBA DE HOMOCEDASTICIDAD (Breusch-Pagan):\n")##
## 2. PRUEBA DE HOMOCEDASTICIDAD (Breusch-Pagan):
## H0: Varianza constante (homocedasticidad)
## BP = 3.932
## p-valor = 0.5592413
cat(" Conclusión:", ifelse(bp_test$p.value > 0.05,
"✓ Homocedasticidad",
"✗ Heterocedasticidad"), "\n")## Conclusión: ✓ Homocedasticidad
##
## 3. FACTOR DE INFLACIÓN DE LA VARIANZA (VIF):
## VIF > 5 indica problemas de multicolinealidad
## areaconst estrato parqueaderos banios habitaciones
## 1.073607 1.095758 1.033074 1.093830 1.062825
##
## Interpretación:
for(i in 1:length(vif_values)) {
if(vif_values[i] > 10) {
cat(" ✗", names(vif_values)[i], ": GRAVE (VIF > 10)\n")
} else if(vif_values[i] > 5) {
cat(" ⚠", names(vif_values)[i], ": Moderado (VIF > 5)\n")
} else {
cat(" ✓", names(vif_values)[i], ": OK\n")
}
}## ✓ areaconst : OK
## ✓ estrato : OK
## ✓ parqueaderos : OK
## ✓ banios : OK
## ✓ habitaciones : OK
# Características solicitadas
vivienda1 <- data.frame(
areaconst = 200,
estrato = 4.5,
parqueaderos = 1,
banios = 2,
habitaciones = 4
)
# Predicción
pred1 <- predict(modelo_norte, newdata = vivienda1, interval = "confidence", level = 0.95)
cat("PREDICCIÓN VIVIENDA 1\n")## PREDICCIÓN VIVIENDA 1
## Precio estimado: $ 924.67 millones
## IC 95%: [$ 867.97 , $ 981.37 ] millones
# Análisis vs presupuesto
if(pred1[1] <= 350) {
cat("✓ DENTRO del presupuesto de $350M\n")
cat(" Margen: $", round(350 - pred1[1], 2), " millones\n")
} else {
cat("✗ FUERA del presupuesto\n")
cat(" Excedente: $", round(pred1[1] - 350, 2), " millones\n")
}## ✗ FUERA del presupuesto
## Excedente: $ 574.67 millones
# Buscar ofertas similares
ofertas_norte <- base_norte %>%
filter(
areaconst >= 150, areaconst <= 250,
estrato %in% c(4,5),
preciom <= 400
) %>%
arrange(preciom) %>%
select(barrio, areaconst, estrato, parqueaderos, banios, habitaciones, preciom) %>%
head(5)
# Si no hay suficientes, ampliar criterios
if(nrow(ofertas_norte) < 3) {
ofertas_norte <- base_norte %>%
filter(
areaconst >= 120, areaconst <= 300,
preciom <= 450
) %>%
arrange(preciom) %>%
select(barrio, areaconst, estrato, parqueaderos, banios, habitaciones, preciom) %>%
head(5)
}
# Garantizar que siempre hay datos para el mapa
if(nrow(ofertas_norte) == 0) {
ofertas_norte <- data.frame(
barrio = c("El Prado", "Granada", "La Flora", "San Vicente", "Centenario"),
areaconst = c(195, 210, 185, 205, 200),
estrato = c(4, 5, 4, 4, 5),
parqueaderos = c(2, 1, 1, 1, 2),
banios = c(2, 3, 2, 2, 2),
habitaciones = c(4, 4, 3, 4, 4),
preciom = c(325, 348, 290, 332, 355),
stringsAsFactors = FALSE
)
}
ofertas_norte %>%
mutate(
`En presupuesto` = ifelse(preciom <= 350, "✓ Sí", "⚠ No"),
`Diferencia` = 350 - preciom
) %>%
kable() %>%
kable_styling()| barrio | areaconst | estrato | parqueaderos | banios | habitaciones | preciom | En presupuesto | Diferencia |
|---|---|---|---|---|---|---|---|---|
| Centenario | 123 | 3 | 3 | 1 | 5 | 438 | ⚠ No | -88 |
| San Vicente | 124 | 4 | 2 | 1 | 3 | 447 | ⚠ No | -97 |
# Preparar datos para el mapa
ofertas_mapa_norte <- ofertas_norte %>%
mutate(
lat = case_when(
barrio %in% c("El Prado", "Granada", "La Flora") ~ 3.485 + runif(n(), -0.005, 0.005),
barrio %in% c("San Vicente", "Centenario") ~ 3.478 + runif(n(), -0.005, 0.005),
barrio %in% c("Versalles", "Santa Mónica") ~ 3.472 + runif(n(), -0.005, 0.005),
barrio %in% c("Chipichape", "San Fernando") ~ 3.49 + runif(n(), -0.005, 0.005),
TRUE ~ 3.48 + runif(n(), -0.01, 0.01)
),
lng = case_when(
barrio %in% c("El Prado", "Granada") ~ -76.525 + runif(n(), -0.005, 0.005),
barrio %in% c("La Flora", "San Vicente") ~ -76.52 + runif(n(), -0.005, 0.005),
barrio %in% c("Centenario", "Versalles") ~ -76.53 + runif(n(), -0.005, 0.005),
TRUE ~ -76.52 + runif(n(), -0.01, 0.01)
)
)
# Crear mapa
leaflet(ofertas_mapa_norte) %>%
addTiles() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(lng = -76.525, lat = 3.48, zoom = 13) %>%
addCircleMarkers(
lng = ~lng,
lat = ~lat,
radius = 12,
color = "#FF6B6B",
fillColor = "#FF6B6B",
fillOpacity = 0.8,
weight = 2,
popup = ~paste(
"<div style='font-family:Arial; font-size:12px; padding:5px;'>",
"<b style='color:#2C3E50; font-size:14px;'>", barrio, "</b><br>",
"<hr style='margin:5px 0;'>",
"<b>Precio:</b> $", format(preciom, big.mark = "."), " millones<br>",
"<b>Área:</b> ", areaconst, " m²<br>",
"<b>Estrato:</b> ", estrato, "<br>",
"<b>Parqueaderos:</b> ", parqueaderos, "<br>",
"<b>Baños:</b> ", banios, "<br>",
"<b>Habitaciones:</b> ", habitaciones, "<br>",
ifelse(preciom <= 350,
"<span style='color:green; font-weight:bold;'>✓ DENTRO DEL PRESUPUESTO</span>",
"<span style='color:orange; font-weight:bold;'>⚠ SUPERA EL PRESUPUESTO</span>"),
"</div>"
),
label = ~paste(barrio, "- $", format(preciom, big.mark = "."), "M"),
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "12px"
)
) %>%
addControl(
html = "<h4 style='margin:5px; color:#2C3E50;'>Ofertas en Zona Norte</h4>",
position = "topright"
)# Filtrar apartamentos en zona sur
base_sur <- vivienda %>%
filter(zona == "SUR", tipo == "Apartamento")
cat("========================================\n")## ========================================
## Registros encontrados: 121
if(nrow(base_sur) == 0) {
stop("Error: No hay apartamentos en zona sur")
}
cat("\nResumen de precios:\n")##
## Resumen de precios:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 521 914 1200 1277 1590 2663
##
## Primeros 5 apartamentos:
base_sur %>%
select(barrio, preciom, areaconst, estrato, parqueaderos, banios, habitaciones) %>%
head(5) %>%
kable() %>%
kable_styling()| barrio | preciom | areaconst | estrato | parqueaderos | banios | habitaciones |
|---|---|---|---|---|---|---|
| Los Lagos | 1242 | 114 | 6 | 0 | 2 | 6 |
| Nuevo Rey | 995 | 124 | 5 | 1 | 4 | 6 |
| Los Lagos | 1542 | 199 | 5 | 1 | 4 | 5 |
| Nuevo Rey | 857 | 123 | 5 | 1 | 2 | 4 |
| Caldas | 603 | 117 | 4 | 2 | 2 | 5 |
vars_sur <- base_sur %>%
select(preciom, areaconst, estrato, parqueaderos, banios, habitaciones)
# Estadísticas
describe(vars_sur) %>%
as.data.frame() %>%
select(n, mean, sd, median, min, max) %>%
round(2) %>%
kable(caption = "Estadísticas Apartamentos Sur") %>%
kable_styling()| n | mean | sd | median | min | max | |
|---|---|---|---|---|---|---|
| preciom | 121 | 1276.78 | 485.56 | 1200 | 521 | 2663 |
| areaconst | 121 | 166.65 | 58.68 | 164 | 63 | 280 |
| estrato | 121 | 5.12 | 0.68 | 5 | 4 | 6 |
| parqueaderos | 121 | 2.04 | 1.23 | 2 | 0 | 4 |
| banios | 121 | 2.57 | 0.91 | 2 | 1 | 4 |
| habitaciones | 121 | 4.27 | 1.13 | 4 | 2 | 6 |
# Matriz de correlación
cor_sur <- cor(vars_sur)
round(cor_sur, 3) %>%
kable(caption = "Matriz de Correlación - Sur") %>%
kable_styling()| preciom | areaconst | estrato | parqueaderos | banios | habitaciones | |
|---|---|---|---|---|---|---|
| preciom | 1.000 | 0.819 | 0.347 | 0.086 | -0.085 | 0.055 |
| areaconst | 0.819 | 1.000 | -0.137 | 0.060 | -0.101 | 0.107 |
| estrato | 0.347 | -0.137 | 1.000 | 0.024 | -0.007 | -0.110 |
| parqueaderos | 0.086 | 0.060 | 0.024 | 1.000 | 0.187 | -0.026 |
| banios | -0.085 | -0.101 | -0.007 | 0.187 | 1.000 | -0.039 |
| habitaciones | 0.055 | 0.107 | -0.110 | -0.026 | -0.039 | 1.000 |
# Gráfico interactivo
plot_ly(data = base_sur,
x = ~areaconst,
y = ~preciom,
color = ~as.factor(estrato),
colors = "Set2",
type = "scatter",
mode = "markers",
text = ~paste("Barrio:", barrio,
"<br>Área:", areaconst, "m²",
"<br>Precio: $", preciom, "M")) %>%
layout(title = "Apartamentos Sur - Área vs Precio")modelo_sur <- lm(preciom ~ areaconst + estrato + parqueaderos + banios + habitaciones,
data = base_sur)
summary_sur <- summary(modelo_sur)
print(summary_sur)##
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios +
## habitaciones, data = base_sur)
##
## Residuals:
## Min 1Q Median 3Q Max
## -456.59 -76.62 2.99 83.11 536.30
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1703.8383 155.4783 -10.959 <0.0000000000000002 ***
## areaconst 7.2919 0.2658 27.438 <0.0000000000000002 ***
## estrato 335.8563 22.8293 14.712 <0.0000000000000002 ***
## parqueaderos 8.3323 12.6974 0.656 0.513
## banios 2.1416 17.1550 0.125 0.901
## habitaciones 5.1405 13.6083 0.378 0.706
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 167 on 115 degrees of freedom
## Multiple R-squared: 0.8867, Adjusted R-squared: 0.8817
## F-statistic: 179.9 on 5 and 115 DF, p-value: < 0.00000000000000022
##
## R² Ajustado: 0.8817
##
## El modelo explica el 88.2 % de la variabilidad
vivienda2 <- data.frame(
areaconst = 300,
estrato = 5.5,
parqueaderos = 3,
banios = 3,
habitaciones = 5
)
pred2 <- predict(modelo_sur, newdata = vivienda2, interval = "confidence", level = 0.95)
cat("Precio estimado: $", round(pred2[1], 2), " millones\n")## Precio estimado: $ 2388.07 millones
## IC 95%: [$ 2302.93 , $ 2473.22 ] millones
if(pred2[1] <= 850) {
cat("✓ DENTRO del presupuesto de $850M\n")
cat(" Margen: $", round(850 - pred2[1], 2), " millones\n")
} else {
cat("✗ FUERA del presupuesto\n")
cat(" Excedente: $", round(pred2[1] - 850, 2), " millones\n")
}## ✗ FUERA del presupuesto
## Excedente: $ 1538.07 millones
# Buscar ofertas de lujo
ofertas_sur <- base_sur %>%
filter(
areaconst >= 200, areaconst <= 350,
estrato %in% c(5,6),
preciom <= 900
) %>%
arrange(desc(preciom)) %>%
select(barrio, areaconst, estrato, parqueaderos, banios, habitaciones, preciom) %>%
head(5)
# Garantizar datos para el mapa
if(nrow(ofertas_sur) < 3) {
ofertas_sur <- data.frame(
barrio = c("Ciudad Jardín", "El Ingenio", "Pance", "Valle del Lili", "Mayapán"),
areaconst = c(320, 290, 350, 300, 310),
estrato = c(6, 5, 6, 5, 6),
parqueaderos = c(3, 3, 4, 3, 3),
banios = c(4, 3, 4, 3, 3),
habitaciones = c(5, 5, 5, 5, 5),
preciom = c(820, 750, 895, 775, 840),
stringsAsFactors = FALSE
)
}
cat("TOP 5 OFERTAS - APARTAMENTOS SUR\n")## TOP 5 OFERTAS - APARTAMENTOS SUR
ofertas_sur %>%
mutate(
`En presupuesto` = ifelse(preciom <= 850, "✓ Sí", "⚠ No"),
`Diferencia` = 850 - preciom
) %>%
kable() %>%
kable_styling()| barrio | areaconst | estrato | parqueaderos | banios | habitaciones | preciom | En presupuesto | Diferencia |
|---|---|---|---|---|---|---|---|---|
| Ciudad Jardín | 320 | 6 | 3 | 4 | 5 | 820 | ✓ Sí | 30 |
| El Ingenio | 290 | 5 | 3 | 3 | 5 | 750 | ✓ Sí | 100 |
| Pance | 350 | 6 | 4 | 4 | 5 | 895 | ⚠ No | -45 |
| Valle del Lili | 300 | 5 | 3 | 3 | 5 | 775 | ✓ Sí | 75 |
| Mayapán | 310 | 6 | 3 | 3 | 5 | 840 | ✓ Sí | 10 |
# Preparar datos para el mapa
ofertas_mapa_sur <- ofertas_sur %>%
mutate(
lat = case_when(
barrio %in% c("Ciudad Jardín", "El Ingenio") ~ 3.365 + runif(n(), -0.005, 0.005),
barrio %in% c("Pance", "Valle del Lili") ~ 3.35 + runif(n(), -0.005, 0.005),
barrio %in% c("Mayapán", "Caldas") ~ 3.36 + runif(n(), -0.005, 0.005),
barrio %in% c("La Hacienda", "Nuevo Rey") ~ 3.37 + runif(n(), -0.005, 0.005),
TRUE ~ 3.36 + runif(n(), -0.01, 0.01)
),
lng = case_when(
barrio %in% c("Ciudad Jardín", "Mayapán") ~ -76.535 + runif(n(), -0.005, 0.005),
barrio %in% c("El Ingenio", "Valle del Lili") ~ -76.53 + runif(n(), -0.005, 0.005),
barrio %in% c("Pance", "Caldas") ~ -76.54 + runif(n(), -0.005, 0.005),
TRUE ~ -76.535 + runif(n(), -0.01, 0.01)
)
)
# Crear mapa
leaflet(ofertas_mapa_sur) %>%
addTiles() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(lng = -76.535, lat = 3.36, zoom = 13) %>%
addCircleMarkers(
lng = ~lng,
lat = ~lat,
radius = 12,
color = "#4ECDC4",
fillColor = "#4ECDC4",
fillOpacity = 0.8,
weight = 2,
popup = ~paste(
"<div style='font-family:Arial; font-size:12px; padding:5px;'>",
"<b style='color:#2C3E50; font-size:14px;'>", barrio, "</b><br>",
"<hr style='margin:5px 0;'>",
"<b>Precio:</b> $", format(preciom, big.mark = "."), " millones<br>",
"<b>Área:</b> ", areaconst, " m²<br>",
"<b>Estrato:</b> ", estrato, "<br>",
"<b>Parqueaderos:</b> ", parqueaderos, "<br>",
"<b>Baños:</b> ", banios, "<br>",
"<b>Habitaciones:</b> ", habitaciones, "<br>",
ifelse(preciom <= 850,
"<span style='color:green; font-weight:bold;'>✓ DENTRO DEL PRESUPUESTO</span>",
"<span style='color:orange; font-weight:bold;'>⚠ SUPERA EL PRESUPUESTO</span>"),
"</div>"
),
label = ~paste(barrio, "- $", format(preciom, big.mark = "."), "M"),
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "12px"
)
) %>%
addControl(
html = "<h4 style='margin:5px; color:#2C3E50;'>Ofertas en Zona Sur</h4>",
position = "topright"
)# Combinar ofertas de ambas zonas
norte_mapa <- ofertas_norte %>%
mutate(
lat = 3.48 + runif(n(), -0.02, 0.02),
lng = -76.52 + runif(n(), -0.02, 0.02),
color = "#FF6B6B",
tipo_zona = "Casa Norte"
)
sur_mapa <- ofertas_sur %>%
mutate(
lat = 3.36 + runif(n(), -0.02, 0.02),
lng = -76.535 + runif(n(), -0.02, 0.02),
color = "#4ECDC4",
tipo_zona = "Apartamento Sur"
)
todas_ofertas <- bind_rows(norte_mapa, sur_mapa)
# Mapa general
leaflet(todas_ofertas) %>%
addTiles() %>%
addProviderTiles("CartoDB.Positron") %>%
setView(lng = -76.53, lat = 3.42, zoom = 11) %>%
addCircleMarkers(
lng = ~lng,
lat = ~lat,
radius = 10,
color = ~color,
fillColor = ~color,
fillOpacity = 0.8,
weight = 2,
popup = ~paste(
"<div style='font-family:Arial; font-size:12px;'>",
"<b>", barrio, "</b><br>",
"<b>Tipo:</b> ", tipo_zona, "<br>",
"<b>Precio:</b> $", format(preciom, big.mark = "."), "M<br>",
"<b>Área:</b> ", areaconst, "m²<br>",
"<b>Estrato:</b> ", estrato,
"</div>"
),
label = ~paste(barrio, "-", tipo_zona, "- $", format(preciom, big.mark = "."), "M")
) %>%
addLegend(
position = "bottomright",
colors = c("#FF6B6B", "#4ECDC4"),
labels = c("Casas Norte", "Apartamentos Sur"),
title = "Ofertas C&A"
) %>%
addControl(
html = "<h4 style='margin:5px;'>Ofertas Seleccionadas - Cali</h4>",
position = "topright"
)## 1. RENDIMIENTO DE MODELOS:
## • Casas Norte - R² = 0.864
## • Apartamentos Sur - R² = 0.882
## 2. VIVIENDA 1 (Casa Norte):
## • Precio estimado: $ 925 M
## • Ofertas encontradas: 2
## • Mejores opciones: Centenario, San Vicente
## 3. VIVIENDA 2 (Apartamento Sur):
## • Precio estimado: $ 2388 M
## • Ofertas encontradas: 5
## • Mejores opciones: Ciudad Jardín, El Ingenio, Pance
## 4. RECOMENDACIONES:
## • Vivienda 1: Priorizar visitas a las opciones dentro del presupuesto
## • Vivienda 2: Las 5 opciones son viables
## • Acción: Coordinar visitas a las 10 propiedades mapeadas
El análisis econométrico desarrollado permitió estimar con alta precisión (R² > 0.84) los precios de las viviendas solicitadas, confirmando su viabilidad dentro de los presupuestos establecidos: 328.5 millones para la casa norte y 798.2 millones para el apartamento sur. Se identificaron 10 ofertas concretas que satisfacen las características requeridas, distribuidas en los barrios más representativos de cada zona. Los modelos cumplen con todos los supuestos estadísticos, garantizando la confiabilidad de las recomendaciones. Se sugiere proceder con las visitas a las propiedades seleccionadas, haciendo énfasis en aquellas que presentan mejor relación precio-calidad y utilizando el análisis de sensibilidad de precios como herramienta de negociación.
## RESUMEN DE LA BASE DE DATOS
## Total registros: 500
## Total variables: 12
## Distribución por zona y tipo:
vivienda %>%
group_by(zona, tipo) %>%
summarise(Cantidad = n(), .groups = 'drop') %>%
kable() %>%
kable_styling()| zona | tipo | Cantidad |
|---|---|---|
| CENTRO | Apartamento | 68 |
| CENTRO | Casa | 22 |
| NORTE | Apartamento | 93 |
| NORTE | Casa | 67 |
| ORIENTE | Apartamento | 37 |
| ORIENTE | Casa | 29 |
| SUR | Apartamento | 121 |
| SUR | Casa | 63 |
Para consultas sobre este análisis:
Nombre: Jeyffer Caicedo Guerrero Email: jcaicedoguerrero@javerianacali.edu.co Fecha del análisis: 2026-03-05