Para: María Gerente, C&A (Casas y Apartamentos) Asunto: Respuesta a la solicitud de asesoría de la compañía internacional para la reubicación de dos familias en Cali Base de análisis: 8.322 ofertas del mercado inmobiliario de Cali de los últimos tres meses
Se construyeron dos modelos de valoración independientes, uno para casas de la Zona Norte (700 ofertas) y otro para apartamentos de la Zona Sur (2.777 ofertas)que explican el precio de oferta en función del área construida, el estrato, el número de habitaciones, baños y parqueaderos. Con esos modelos se estimó el precio de mercado de cada vivienda solicitada, se contrastó contra el crédito preaprobado y se seleccionaron las ofertas reales que mejor responden a cada requerimiento.
La forma funcional finalmente adoptada es logarítmica (log-precio en función de log-área), porque es la que mejor se aproxima a los supuestos del modelo lineal, en particular reduce de manera drástica la heterocedasticidad que invalida los intervalos del modelo en nivelesy la que menor error de predicción produce.
Un apunte metodológico que atraviesa todo el informe: los registros sin número de parqueaderos reportado se trataron como cero cupos, entendiendo que el campo vacío señala ausencia de parqueadero y no dato desconocido. El Anexo A justifica la decisión y el Anexo D mide sus consecuencias.
Características pedidas: 200 m², 4 habitaciones, 2 baños, 1 parqueadero, estrato 4 o 5, crédito preaprobado de 350 millones.
Tabla 1. Valor estimado de la vivienda solicitada frente al crédito preaprobado Solicitud 1.
| Escenario | Valor estimado de mercado | Intervalo de predicción 95% | ¿Cabe en el crédito? |
|---|---|---|---|
| Estrato 4 | 337 millones | 193 – 548 | Sí, con margen estrecho |
| Estrato 5 | 387 millones | 221 – 629 | No: excede el cupo |
Recomendación. Como muestra la Tabla 1, la solicitud es viable, pero el presupuesto obliga a concentrar la búsqueda en estrato 4, y aun allí el margen es estrecho: el valor estimado deja apenas 13 millones por debajo del tope de crédito. Una casa con esas características en estrato 5 cuesta en promedio cerca de 50 millones más, lo que ubica el valor esperado 37 millones por encima del cupo.
El mercado, sin embargo, ofrece alternativas: se identificaron 28 ofertas que cumplen simultáneamente el presupuesto y todas las condiciones del requerimiento, varias con un precio de lista muy por debajo del que predice el modelo. Los barrios donde se concentra la oportunidad son El Bosque, La Merced, La Flora, Vipasa y Prados del Norte.
Dos de ellas coinciden de manera exacta con lo pedido (200 m², 4 habitaciones, 4 baños, 2 parqueaderos) por 320 millones, es decir un 8,6% por debajo del cupo. Se sugiere iniciar por ellas.
Características pedidas: 300 m², 5 habitaciones, 3 baños, 3 parqueaderos, estrato 5 o 6, crédito preaprobado de 850 millones.
Tabla 2. Valor estimado de la vivienda solicitada frente al crédito preaprobado Solicitud 2.
| Escenario | Valor estimado de mercado | Intervalo de predicción 95% | ¿Cabe en el crédito? |
|---|---|---|---|
| Estrato 5 | 672 millones | 449 – 969 | Sí |
| Estrato 6 | 901 millones | 602 – 1.300 | No: excede el cupo |
Recomendación. Según la Tabla 2, la solicitud es viable en estrato 5, donde el valor esperado deja un margen cercano a 178 millones frente al crédito. En estrato 6, en cambio, el valor esperado (901 millones) supera el crédito en 51 millones, por lo que no se recomienda salvo que el cliente obtenga un aumento del prestamo.
Hay, sin embargo, una restricción de oferta que debe comunicarse al cliente antes que cualquier cifra de precio. Un apartamento de 300 m² con 5 habitaciones y 3 parqueaderos es un producto escaso en la Zona Sur: en toda la base de tres meses solo 3 ofertas cumplen íntegramente el requerimiento. Flexibilizando el mínimo a 4 habitaciones y 2 parqueaderos, el conjunto se amplía a 13 alternativas.
La oferta que mejor responde a la solicitud, y que cumple todas sus condicioneses un apartamento de 300 m², 6 habitaciones, 5 baños y 3 parqueaderos en el barrio Seminario, por 670 millones, es decir 180 millones por debajo del cupo aprobado.
Los barrios donde se concentra este tipo de producto son Seminario, Cuarto de Legua, San Fernando, El Ingenio y Ciudadela Pasoancho.
Los anexos reproducen íntegramente el procedimiento: filtros y verificación de las bases (Sección 1), análisis exploratorio (§2), estimación e interpretación de los modelos (Sección 3), validación de supuestos (Sección 4), predicción (Sección 5) y selección de ofertas (Sección 6) para la Solicitud 1; la Sección 7 repite los seis pasos para la Solicitud 2.
La base vivienda contiene 8.322 registros y 13
variables. La Tabla 3 documenta cada una: su nombre en
la base, la descripción del atributo que mide, su tipo estadístico y el
papel que cumple en este estudio.
| Variable | Descripción | Tipo de variable | Papel en el estudio |
|---|---|---|---|
| id | Identificador único del registro de la oferta | Identificador (entero) | No se usa |
| zona | Ubicación de la vivienda dentro de la ciudad: Zona Centro, Norte, Sur, Oeste u Oriente | Cualitativa nominal | Criterio de filtro (define base1 y base2) |
| piso | Piso que ocupa la vivienda (aplica principalmente a apartamentos) | Cuantitativa discreta (ordinal) | No se usa: 32% de faltantes y no aplica a casas |
| estrato | Estrato socioeconómico asignado al predio: 3, 4, 5 o 6 | Cualitativa ordinal (registrada como número) | Variable explicativa (se modela como factor) |
| preciom | Precio de oferta de la vivienda, en millones de pesos | Cuantitativa continua | Variable respuesta |
| areaconst | Área construida de la vivienda, en metros cuadrados | Cuantitativa continua | Variable explicativa principal |
| parqueaderos | Número de cupos de parqueadero del inmueble | Cuantitativa discreta | Variable explicativa |
| banios | Número de baños | Cuantitativa discreta | Variable explicativa |
| habitaciones | Número de habitaciones | Cuantitativa discreta | Variable explicativa |
| tipo | Tipo de vivienda: Casa o Apartamento | Cualitativa nominal | Criterio de filtro (define base1 y base2) |
| barrio | Barrio de ubicación de la vivienda (436 categorías distintas) | Cualitativa nominal | Descriptivo: identificación de las ofertas |
| longitud | Coordenada geográfica de longitud | Cuantitativa continua | Georreferenciación de los mapas |
| latitud | Coordenada geográfica de latitud | Cuantitativa continua | Georreferenciación de los mapas |
Dos precisiones sobre los tipos declarados en la Tabla
3: estrato viene codificado como
número pero es una variable cualitativa ordinal, la
distancia entre el estrato 3 y el 4 no es la misma que entre el 5 y el
6, y esa es exactamente la razón por la que en el Paso 3, se compara el
modelo que lo trata como número, contra el que lo trata como factor.
zona y tipo son nominales y
no entran como regresores porque, al definir las bases de trabajo, son
constantes dentro de cada modelo.
La Tabla 4 complementa el diccionario con la lectura
que R hace de cada campo y con su nivel de completitud. De ahí salen las
dos decisiones de limpieza del apartado siguiente: piso se
descarta por su alto porcentaje de faltantes y parqueaderos
exige un tratamiento explícito.
| Variable | Clase en R | Valores faltantes | % faltantes | Valores distintos |
|---|---|---|---|---|
| id | numeric | 3 | 0.0 | 8320 |
| zona | character | 3 | 0.0 | 6 |
| piso | character | 2638 | 31.7 | 13 |
| estrato | numeric | 3 | 0.0 | 5 |
| preciom | numeric | 2 | 0.0 | 540 |
| areaconst | numeric | 3 | 0.0 | 653 |
| parqueaderos | numeric | 1605 | 19.3 | 11 |
| banios | numeric | 3 | 0.0 | 12 |
| habitaciones | numeric | 3 | 0.0 | 12 |
| tipo | character | 3 | 0.0 | 3 |
| barrio | character | 3 | 0.0 | 437 |
| longitud | numeric | 3 | 0.0 | 2929 |
| latitud | numeric | 3 | 0.0 | 3680 |
La base contiene 8.322 registros y 13 variables. Antes de filtrar se aplicaron dos reglas de limpieza:
habitaciones = 0 o
banios = 0, imposibles en una vivienda habitable y
claramente errores de captura.La Tabla 5 deja trazabilidad del efecto de cada filtro sobre el número de registros.
datos <- vivienda %>%
filter(!is.na(preciom), !is.na(areaconst), !is.na(estrato),
!is.na(banios), !is.na(habitaciones), !is.na(tipo), !is.na(zona)) %>%
filter(habitaciones > 0, banios > 0) %>%
mutate(estrato_f = factor(estrato, levels = c(3, 4, 5, 6)),
precio_m2 = preciom / areaconst * 1000)| Etapa | Registros |
|---|---|
| Base original | 8322 |
| Sin faltantes en variables del modelo | 8319 |
| Sin habitaciones = 0 ni baños = 0 | 8243 |
| base1: Casas · Zona Norte | 700 |
| base2: Apartamentos · Zona Sur | 2777 |
Tratamiento de parqueaderos. Es la
única variable del modelo con faltantes masivos: 269 de 700 registros en
base1 (38,4%) y 402 de 2.777 en base2 (14,5%).
Se imputaron con el valor cero, bajo el siguiente argumento: Se asume que el dato ausente no significa, “se desconoce cuántos parqueaderos tiene”, sino “no tiene parqueadero que ofrecer”. El cero es la codificación natural de esa ausencia y, de hecho, es el único valor que la variable no toma nunca en la base original: el mínimo observado es 1, precisamente porque quien tiene un parqueadero lo reporta y quien no lo tiene deja el campo en blanco. Se considera que utilizar otra estrategía, como imputar la mediana, atribuiría uno o dos cupos de parqueo a inmuebles que probablemente no tienen ninguno, lo que sobreestimaría sus atributos.
imputar_parq <- function(d) {
d$parq_faltante <- is.na(d$parqueaderos) # se conserva la marca del faltante
d$parqueaderos <- ifelse(is.na(d$parqueaderos), 0, d$parqueaderos)
d
}| Nº de parqueaderos | Casas · Zona Norte | Apartamentos · Zona Sur |
|---|---|---|
| 0 | 269 | 402 |
| 1 | 160 | 1548 |
| 2 | 156 | 716 |
| 3 | 49 | 79 |
| 4 | 39 | 31 |
| 5 | 11 | 0 |
| 6 | 8 | 0 |
| 7 | 5 | 0 |
| 8 | 1 | 0 |
| 9 | 1 | 0 |
| 10 | 1 | 1 |
La Tabla 6 muestra la distribución resultante: el cero pasa a ser la categoría más frecuente en las casas del norte y la segunda en los apartamentos del sur.
Esta decisión tiene dos consecuencias que se documentan explícitamente más adelante y que conviene anticipar:
En el Anexo D se contrasta esta imputación contra las dos alternativas razonables, imputar la mediana y descartar los registros incompletospara verificar si la recomendación de negocio cambia.
base1 <- datos %>% filter(tipo == "Casa", zona == "Zona Norte")
base1 <- imputar_parq(base1)
dim(base1)## [1] 700 16
| zona | tipo | estrato | preciom | areaconst | habitaciones | banios | parqueaderos | barrio |
|---|---|---|---|---|---|---|---|---|
| Zona Norte | Casa | 5 | 320 | 150 | 6 | 4 | 2 | acopi |
| Zona Norte | Casa | 5 | 780 | 380 | 3 | 3 | 2 | acopi |
| Zona Norte | Casa | 6 | 750 | 445 | 6 | 7 | 0 | acopi |
| Variable | Categoría | Registros |
|---|---|---|
| tipo | Casa | 700 |
| zona | Zona Norte | 700 |
| estrato | 3 | 229 |
| 4 | 151 | |
| 5 | 267 | |
| 6 | 53 |
La Tabla 7 presenta los tres primeros registros y la
Tabla 8 confirma que el filtro operó correctamente: la
base contiene únicamente el nivel Casa de tipo y
el nivel Zona Norte de zona, y conserva los cuatro
estratos.
pal1 <- colorFactor("viridis", domain = base1$estrato)
leaflet(base1) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud, radius = 4, stroke = FALSE,
fillOpacity = 0.7, color = ~pal1(estrato),
popup = ~paste0("<b>", barrio, "</b><br>",
"Estrato ", estrato, "<br>",
"$", preciom, " millones<br>",
areaconst, " m² · ", habitaciones, " hab · ",
banios, " baños")) %>%
addLegend("bottomright", pal = pal1, values = ~estrato, title = "Estrato") %>%
addControl("Casas ofertadas Zona Norte", position = "topright")Figura 1. Localización de las 700 casas ofertadas en la Zona Norte, coloreadas por estrato.
No. La Figura 1 muestra un grupo de marcadores claramente desplazados hacia el sur de la ciudad. Las Tablas 9 y 10 cuantifican el fenómeno e identifican los barrios implicados.
| Indicador | Valor |
|---|---|
| Registros rotulados ‘Zona Norte’ | 1.888 |
| Con latitud < 3,42 (mitad sur de la ciudad) | 196 |
| Porcentaje | 10,4% |
| Barrio | Registros |
|---|---|
| acopi | 85 |
| Cali | 24 |
| la flora | 18 |
| brisas de los | 5 |
| prados del norte | 4 |
| torres de comfandi | 4 |
| urbanización la flora | 4 |
| valle del lili | 4 |
Interpretación de las inconsistencias. Detrás de los barrios listados en la Tabla 10 coexisten tres fenómenos distintos:
Cali o zona norte en el campo
barrio, que no identifican ubicación alguna; su coordenada
es prácticamente aleatoria.También se observa el problema simétrico en la Zona Sur: 192 registros rotulados Zona Sur tienen latitud superior a 3,45, principalmente de San Fernando y El Lido, barrios que muchos clasificarían como centro.
Implicación práctica: el rótulo zona es
una variable comercial, no geográfica. Se conserva como criterio de
filtro porque es el lenguaje en que el cliente formuló la solicitud,
pero antes de presentar una oferta a la familia debe verificarse
su ubicación real en el mapa, no su etiqueta.
La Tabla 11 resume la escala y la dispersión de las variables del modelo en esta base.
| Variable | Mín | Q1 | Mediana | Media | Q3 | Máx | D.E. |
|---|---|---|---|---|---|---|---|
| preciom | 89 | 256 | 390 | 443.2 | 550 | 1940 | 267.8 |
| areaconst | 30 | 140 | 240 | 262.1 | 336 | 1440 | 166.6 |
| habitaciones | 1 | 3 | 4 | 4.6 | 5 | 10 | 1.7 |
| banios | 1 | 2 | 3 | 3.6 | 4 | 10 | 1.5 |
| parqueaderos | 0 | 0 | 1 | 1.3 | 2 | 10 | 1.5 |
vars <- c("preciom", "areaconst", "estrato", "banios", "habitaciones", "parqueaderos")
mc <- round(cor(base1[, vars]), 3)
plot_ly(x = colnames(mc), y = rownames(mc), z = mc, type = "heatmap",
colors = colorRamp(c("#FFFFFF", "#2C7FB8")), zmin = 0, zmax = 1,
hovertemplate = "%{y} vs %{x}: %{z}<extra></extra>") %>%
add_annotations(x = rep(colnames(mc), each = length(vars)),
y = rep(rownames(mc), times = length(vars)),
text = as.vector(mc), showarrow = FALSE,
font = list(size = 11)) %>%
layout(title = "Matriz de correlaciones de Pearson casas Zona Norte")Figura 2. Matriz de correlaciones de Pearson entre el precio y las variables explicativas casas Zona Norte.
| Variable | Pearson | Spearman |
|---|---|---|
| Área construida | 0.730 | 0.820 |
| Estrato | 0.617 | 0.717 |
| Nº de baños | 0.568 | 0.645 |
| Nº de habitaciones | 0.375 | 0.435 |
| Nº de parqueaderos | 0.333 | 0.403 |
Lectura de la Figura 2 y la Tabla 12. El área construida es el determinante dominante (r = 0,730), seguida del estrato (0,617) y del número de baños (0,568). Las habitaciones y los parqueaderos muestran asociaciones más débiles; en el caso de los parqueaderos, buena parte de esa correlación bivariada (0,333) refleja que las casas sin cupo de parqueo reportado, codificadas en cerotienden también a ser las más pequeñas y de estrato más bajo, de modo que el efecto se desvanece al controlar por área y estrato. Es revelador que la correlación de Spearman sea sistemáticamente mayor que la de Pearson en todas las variables (por ejemplo 0,820 frente a 0,730 para el área): la relación es monótona pero no lineal, primer indicio de que conviene una especificación logarítmica.
g <- ggplot(base1, aes(areaconst, preciom, color = estrato_f,
text = paste0(barrio, "<br>", areaconst, " m² · $",
preciom, " M<br>Estrato ", estrato))) +
geom_point(alpha = 0.6, size = 1.8) +
geom_smooth(method = "lm", se = FALSE, aes(group = 1),
color = "grey30", linewidth = 0.7) +
scale_color_viridis_d(name = "Estrato") +
labs(title = "Precio vs. área construida casas Zona Norte",
x = "Área construida (m²)", y = "Precio (millones $)") +
theme_minimal()
ggplotly(g, tooltip = "text")Figura 3. Precio frente a área construida, por estrato casas Zona Norte. La recta gris es el ajuste lineal simple sobre el total.
La Figura 3 muestra el abanico característico de la heterocedasticidad: la dispersión de precios crece con el área. A igual metraje, los puntos de estrato superior se ubican consistentemente por encima, confirmando que el estrato actúa como un desplazador del nivel de precios.
g2 <- ggplot(base1, aes(estrato_f, preciom, fill = estrato_f)) +
geom_boxplot(alpha = 0.8, outlier.size = 0.8) +
scale_fill_viridis_d(guide = "none") +
labs(title = "Distribución del precio por estrato casas Zona Norte",
x = "Estrato", y = "Precio (millones $)") +
theme_minimal()
ggplotly(g2)Figura 4. Distribución del precio por estrato casas Zona Norte.
| Estrato | n | Precio medio | Mediana | Precio/m² (miles, mediana) |
|---|---|---|---|---|
| 3 | 229 | 243.3 | 210 | 1586 |
| 4 | 151 | 426.6 | 380 | 1636 |
| 5 | 267 | 550.4 | 480 | 1688 |
| 6 | 53 | 813.7 | 780 | 2107 |
La Figura 4 y la Tabla 13 muestran lo mismo desde dos ángulos: el salto de estrato 3 a 4 supera el 55% en la mediana y el de 5 a 6 es de nuevo abrupto. Los saltos no son constantes, lo que anticipa que tratar el estrato como variable numérica (imponiendo un incremento fijo por peldaño) es una simplificación cuestionable.
g3 <- ggplot(datos %>% filter(tipo == "Casa"), aes(zona, preciom, fill = zona)) +
geom_boxplot(alpha = 0.85, outlier.size = 0.6) +
scale_fill_viridis_d(guide = "none") +
labs(title = "Precio de casas por zona de la ciudad (base completa)",
x = NULL, y = "Precio (millones $)") +
theme_minimal() + theme(axis.text.x = element_text(angle = 20, hjust = 1))
ggplotly(g3)Figura 5. Distribución del precio de las casas según la zona de la ciudad, sobre la base completa.
Sobre la variable zona. El enunciado
pide analizar el precio en función de la zona, pero dentro de
base1 la zona es constante por construcción del filtro y
por tanto no puede entrar al modelo. La Figura 5, sobre
la base completa, muestra por qué la zona sí importa y
por qué la estrategia correcta es estimar un modelo separado por
zona y tipo: los niveles y la dispersión de precios difieren
marcadamente entre zonas, de modo que segmentar equivale a permitir que
todos los coeficientes, y no solo el interceptocambien entre
segmentos.
Se estiman y comparan tres especificaciones (Tabla 14):
Tabla 14. Especificaciones evaluadas.
| Modelo | Especificación | Idea |
|---|---|---|
| M1 | preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios |
La pedida en el enunciado; estrato numérico |
| M2 | igual, con estrato como factor |
Libera el supuesto de saltos iguales entre estratos |
| M3 | log(preciom) ~ log(areaconst) + factor(estrato) + ... |
Corrige no linealidad y heterocedasticidad; coeficientes en % |
b1_m1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base1)
b1_m2 <- lm(preciom ~ areaconst + estrato_f + habitaciones + parqueaderos + banios, data = base1)
b1_m3 <- lm(log(preciom) ~ log(areaconst) + estrato_f + habitaciones + parqueaderos + banios, data = base1)
summary(b1_m1)##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -927.0 -80.6 -16.7 51.8 1077.2
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -251.3563 32.3795 -7.76 0.00000000000003 ***
## areaconst 0.7838 0.0464 16.88 < 0.0000000000000002 ***
## estrato 86.1369 7.6451 11.27 < 0.0000000000000002 ***
## habitaciones 4.1154 4.8261 0.85 0.39
## parqueaderos -0.8209 4.3297 -0.19 0.85
## banios 30.2362 5.9136 5.11 0.00000041057644 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 158 on 694 degrees of freedom
## Multiple R-squared: 0.654, Adjusted R-squared: 0.651
## F-statistic: 262 on 5 and 694 DF, p-value: <0.0000000000000002
La Tabla 15 reproduce las estimaciones de M1 con sus errores estándar y su significancia.
| Término | Coeficiente | Error est. | t | p-valor | Significativo |
|---|---|---|---|---|---|
| (Intercept) | -251.356 | 32.379 | -7.76 | <0.001 | Sí |
| areaconst | 0.784 | 0.046 | 16.88 | <0.001 | Sí |
| estrato | 86.137 | 7.645 | 11.27 | <0.001 | Sí |
| habitaciones | 4.115 | 4.826 | 0.85 | 0.394 | No |
| parqueaderos | -0.821 | 4.330 | -0.19 | 0.850 | No |
| banios | 30.236 | 5.914 | 5.11 | <0.001 | Sí |
| Modelo | R2 | R2 ajustado | AIC | RMSE | MAE | MAPE (%) |
|---|---|---|---|---|---|---|
| M1 lineal | 0.654 | 0.651 | 9082.8 | 157.4 | 99.9 | 22.1 |
| M2 estrato factor | 0.664 | 0.660 | 9067.0 | 155.2 | 96.5 | 21.2 |
| M3 log-log | 0.784 | 0.782 | 136.8 | 156.1 | 97.8 | 21.7 |
La Tabla 16 compara las tres especificaciones. El R² de M1 es 0,654: las cinco variables explican el 65,4% de la variabilidad del precio de las casas del norte, y queda un 34,6% sin explicar. Para un modelo sencillo con solo cinco atributos físicos es un ajuste aceptable, pero insuficiente para tasar: el error típico es de 158 millones, cerca del 36% del precio medio. En términos prácticos, el modelo sirve para acotar un rango de negociación, no para fijar un precio.
Qué haría falta para mejorarlo:
##
## Call:
## lm(formula = preciom ~ areaconst + estrato_f + habitaciones +
## parqueaderos + banios, data = base1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -935.0 -70.9 -16.1 44.2 1084.5
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 17.4709 18.9453 0.92 0.36
## areaconst 0.7828 0.0458 17.08 < 0.0000000000000002 ***
## estrato_f4 73.8758 17.8290 4.14 0.000038429198598 ***
## estrato_f5 136.4636 17.2847 7.90 0.000000000000011 ***
## estrato_f6 324.3892 27.3767 11.85 < 0.0000000000000002 ***
## habitaciones 4.0558 4.7781 0.85 0.40
## parqueaderos 3.2041 4.3772 0.73 0.46
## banios 29.1329 5.9146 4.93 0.000001052969241 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 156 on 692 degrees of freedom
## Multiple R-squared: 0.664, Adjusted R-squared: 0.66
## F-statistic: 195 on 7 and 692 DF, p-value: <0.0000000000000002
##
## Call:
## lm(formula = log(preciom) ~ log(areaconst) + estrato_f + habitaciones +
## parqueaderos + banios, data = base1)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.7398 -0.1765 -0.0138 0.1558 1.0996
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.92636 0.10383 28.18 < 0.0000000000000002 ***
## log(areaconst) 0.45519 0.02330 19.54 < 0.0000000000000002 ***
## estrato_f4 0.25367 0.03110 8.16 0.0000000000000016 ***
## estrato_f5 0.39168 0.03092 12.67 < 0.0000000000000002 ***
## estrato_f6 0.66162 0.04768 13.88 < 0.0000000000000002 ***
## habitaciones 0.01520 0.00816 1.86 0.063 .
## parqueaderos 0.01110 0.00744 1.49 0.136
## banios 0.06013 0.01008 5.96 0.0000000039337489 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.265 on 692 degrees of freedom
## Multiple R-squared: 0.784, Adjusted R-squared: 0.782
## F-statistic: 360 on 7 and 692 DF, p-value: <0.0000000000000002
M2 confirma que los saltos entre estratos no son uniformes: pasar de estrato 3 a 4 vale 74 millones, mientras que pasar de 3 a 6 vale 324 millones, muy por encima del triple del primer salto. Imponer linealidad en el estrato, como hace M1, subestima el efecto en el precio de los estratos altos.
M3 es la mejor especificación. Sus coeficientes se leen como elasticidades y porcentajes:
El R² sube de 0,654 a 0,784, advirtiendo que los R² de M1 y M3 no son estrictamente comparables por estar en escalas distintas; la comparación válida es la del RMSE en millones de la Tabla 16, donde M3 también mejora.
Figura 6. Gráficos diagnósticos del modelo M1 (lineal en niveles) casas Zona Norte: residuos contra ajustados, QQ-plot, escala-localización y residuos contra apalancamiento.
Figura 7. Gráficos diagnósticos del modelo M3 (log-log) casas Zona Norte.
| Modelo | Shapiro-Wilk (normalidad) | Breusch-Pagan (homocedasticidad) | RESET (linealidad) | Durbin-Watson |
|---|---|---|---|---|
| M1 lineal | <0.0000000000000001 | <0.0000000000000001 | 0.542 | 1.635 |
| M2 estrato factor | <0.0000000000000001 | <0.0000000000000001 | 0.152 | 1.686 |
| M3 log-log | 0.00000253 | 0.0157 | 0.499 | 1.654 |
| Variable | VIF |
|---|---|
| areaconst | 1.67 |
| estrato | 1.59 |
| habitaciones | 1.85 |
| parqueaderos | 1.23 |
| banios | 2.11 |
Las pruebas formales aparecen en la Tabla 18 y los factores de inflación de varianza en la Tabla 19.
Resultados de la validación (Tabla 17):
Tabla 17. Síntesis de la validación de supuestos casas Zona Norte.
| Supuesto | M1 lineal | M3 log-log | Lectura |
|---|---|---|---|
| Linealidad | Se cumple (RESET p > 0,05) | Se cumple | Ninguno presenta error de especificación evidente |
| Normalidad de residuos | Se rechaza (p < 0,001) | Se rechaza, pero W pasa de 0,837 a 0,986 | Mejora sustancial con la transformación |
| Homocedasticidad | Se rechaza (p < 0,001) | Se rechaza al 5%, pero el p-valor sube a 0,016 | Mejora de varios órdenes de magnitud a favor de M3 |
| Independencia | DW ≈ 1,63 | DW ≈ 1,65 | Aceptable; DW es poco informativo en datos de corte transversal |
| Multicolinealidad | VIF máximo = 2,11 | Sin problema (umbral habitual: 5) |
Sugerencias de corrección:
nueva_v1_e4 <- data.frame(areaconst = 200, estrato = 4,
estrato_f = factor(4, levels = c(3,4,5,6)),
habitaciones = 4, parqueaderos = 1, banios = 2)
nueva_v1_e5 <- within(nueva_v1_e4, {estrato <- 5; estrato_f <- factor(5, levels = c(3,4,5,6))})
# M3 (modelo seleccionado) con corrección de smearing de Duan
smear1 <- mean(exp(residuals(b1_m3)))
exp(predict(b1_m3, nueva_v1_e4, interval = "prediction")) * c(smear1, 1, 1)## fit lwr upr
## 1 337 192.7 548.2
## fit lwr upr
## 1 386.9 221.3 629
| Escenario | Estimación | IC 95% inf | IC 95% sup | IP 95% inf | IP 95% sup |
|---|---|---|---|---|---|
| M1 lineal · estrato 4 | 326.1 | 307.6 | 344.5 | 15.1 | 637.0 |
| M1 lineal · estrato 5 | 412.2 | 386.0 | 438.4 | 100.7 | 723.7 |
| M2 factor · estrato 4 | 325.6 | 295.0 | 356.2 | 17.6 | 633.6 |
| M2 factor · estrato 5 | 388.2 | 360.2 | 416.2 | 80.4 | 695.9 |
| M3 log-log · estrato 4 | 337.0 | 308.5 | 342.4 | 192.7 | 548.2 |
| M3 log-log · estrato 5 | 386.9 | 356.0 | 391.1 | 221.3 | 629.0 |
Lectura de la Tabla 20. Con el modelo seleccionado (M3), una casa de 200 m², 4 habitaciones, 2 baños y 1 parqueadero en la Zona Norte se valora en 337 millones en estrato 4 y 387 millones en estrato 5. Los tres modelos de la Tabla 20 coinciden razonablemente en el punto central, lo que da confianza en la magnitud; difieren fuertemente en la amplitud del intervalo, y ahí M3 es el único creíble: los intervalos de predicción de M1 y M2 incluyen valores negativos, consecuencia directa de la heterocedasticidad diagnosticada en el Paso 4.
Conclusión para el cliente. El cupo de 350 millones es suficiente en estrato 4 (el valor estimado queda 13 millones por debajo del tope) pero queda corto en estrato 5, donde el valor esperado supera el crédito en 37 millones. Debe orientarse la búsqueda al estrato 4, admitiendo estrato 5 solo en ofertas puntualmente subvaloradas.
Se filtró base1 exigiendo todas las
condiciones del requerimiento: precio ≤ 350 millones, estrato 4 o 5, al
menos 4 habitaciones, al menos 2 baños, al menos 1 parqueadero, y área
entre 150 y 250 m² (±25% de los 200 m² pedidos). Resultaron 28
ofertas. Conviene notar que, bajo la imputación en cero, el
requisito de “al menos 1 parqueadero” excluye automáticamente
los inmuebles que no reportaron el dato: sin esa exigencia el
conjunto sería de 33 ofertas, es decir 5 candidatas quedan fuera por no
acreditar cupo de parqueo. Sobre ellas se calcularon dos
indicadores:
valor_modelo: precio que M3 predice
para esa vivienda concreta;brecha_pct: diferencia porcentual
entre el precio de lista y el valor del modelo. Negativa = la oferta
está por debajo de lo que valdría según el
mercado.of1 <- base1 %>%
filter(preciom <= 350, estrato %in% c(4, 5),
areaconst >= 150, areaconst <= 250,
habitaciones >= 4, banios >= 2, parqueaderos >= 1) %>%
mutate(valor_modelo = exp(predict(b1_m3, newdata = .)) * smear1,
brecha_pct = (preciom / valor_modelo - 1) * 100)
nrow(of1)## [1] 28
| Barrio | Estrato | Precio (millones) | Área (m²) | Hab | Baños | Parq | Precio/m² (miles) | Valor modelo (millones) | Brecha (%) |
|---|---|---|---|---|---|---|---|---|---|
| el bosque | 5 | 250 | 243 | 5 | 4 | 1 | 1029 | 484 | -48.4 |
| la merced | 4 | 230 | 250 | 5 | 3 | 2 | 920 | 407 | -43.4 |
| vipasa | 5 | 300 | 205 | 6 | 5 | 2 | 1463 | 488 | -38.6 |
| la merced | 5 | 321 | 249 | 5 | 5 | 1 | 1289 | 520 | -38.2 |
| la campiña | 5 | 340 | 240 | 6 | 5 | 2 | 1417 | 525 | -35.2 |
| la flora | 5 | 320 | 230 | 4 | 4 | 2 | 1391 | 470 | -31.9 |
| prados del norte | 5 | 330 | 246 | 4 | 4 | 2 | 1341 | 485 | -31.9 |
| el bosque | 5 | 342 | 250 | 6 | 4 | 1 | 1368 | 498 | -31.3 |
| Barrio | Estrato | Precio (millones) | Área (m²) | Hab | Baños | Parq | Precio/m² (miles) | Valor modelo (millones) | Brecha (%) |
|---|---|---|---|---|---|---|---|---|---|
| el bosque | 5 | 350 | 200 | 4 | 3 | 3 | 1750 | 420 | -16.7 |
| la flora | 5 | 320 | 200 | 4 | 4 | 2 | 1600 | 441 | -27.5 |
| la merced | 4 | 320 | 200 | 4 | 4 | 2 | 1600 | 384 | -16.7 |
| el bosque | 5 | 335 | 202 | 5 | 4 | 1 | 1658 | 445 | -24.7 |
| el bosque | 5 | 350 | 203 | 5 | 2 | 2 | 1724 | 400 | -12.5 |
pal_of1 <- colorNumeric("RdYlGn", domain = of1$brecha_pct, reverse = TRUE)
destacadas <- bind_rows(sel1, sel1b) %>% distinct()
leaflet(of1) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud, radius = 7, stroke = FALSE, fillOpacity = 0.8,
color = ~pal_of1(brecha_pct),
popup = ~paste0("<b>", barrio, "</b> · Estrato ", estrato,
"<br>Precio: $", preciom, " M",
"<br>Valor modelo: $", round(valor_modelo, 0), " M",
"<br>Brecha: ", round(brecha_pct, 1), "%",
"<br>", areaconst, " m² · ", habitaciones, " hab · ",
banios, " baños · ", parqueaderos, " parq."),
label = ~paste0(barrio, " · $", preciom, " M")) %>%
addCircleMarkers(data = destacadas, ~longitud, ~latitud, radius = 12,
fillOpacity = 0, color = "#111111", weight = 2) %>%
addLegend("bottomright", pal = pal_of1, values = ~brecha_pct,
title = "Brecha vs.<br>valor modelo (%)") %>%
addControl(paste0("Solicitud 1 las ", nrow(of1),
" ofertas que cumplen el requerimiento (círculo negro: destacadas)"),
position = "topright")Figura 8. Ofertas candidatas para la Solicitud 1. El color indica la brecha entre el precio de lista y el valor estimado por el modelo (verde = más subvalorada); el círculo negro marca las ofertas de las Tablas 21 y 22.
Se repiten los seis pasos anteriores para la segunda solicitud: apartamento de 300 m², 5 habitaciones, 3 baños, 3 parqueaderos, estrato 5 o 6, con crédito preaprobado de 850 millones.
base2 <- datos %>% filter(tipo == "Apartamento", zona == "Zona Sur")
base2 <- imputar_parq(base2)
dim(base2)## [1] 2777 16
| zona | tipo | estrato | preciom | areaconst | habitaciones | banios | parqueaderos | barrio |
|---|---|---|---|---|---|---|---|---|
| Zona Sur | Apartamento | 4 | 290 | 96 | 3 | 2 | 1 | acopi |
| Zona Sur | Apartamento | 3 | 78 | 40 | 2 | 1 | 1 | aguablanca |
| Zona Sur | Apartamento | 6 | 875 | 194 | 3 | 5 | 2 | aguacatal |
| Variable | Categoría | Registros |
|---|---|---|
| tipo | Apartamento | 2777 |
| zona | Zona Sur | 2777 |
| estrato | 3 | 200 |
| 4 | 1086 | |
| 5 | 1031 | |
| 6 | 460 |
pal2 <- colorFactor("viridis", domain = base2$estrato)
leaflet(base2) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud, radius = 3, stroke = FALSE, fillOpacity = 0.6,
color = ~pal2(estrato),
popup = ~paste0("<b>", barrio, "</b><br>Estrato ", estrato,
"<br>$", preciom, " millones<br>", areaconst, " m²")) %>%
addLegend("bottomright", pal = pal2, values = ~estrato, title = "Estrato") %>%
addControl("Apartamentos ofertados Zona Sur", position = "topright")Figura 9. Localización de los 2.777 apartamentos ofertados en la Zona Sur, coloreados por estrato.
Las Tablas 23 y 24 verifican el filtro. En la
Figura 9 se repite el fenómeno descrito en la Solicitud
1, ahora en sentido inverso: 192 de los 4.701 registros rotulados
Zona Sur tienen latitud superior a 3,45, es decir se ubican en
la franja centro-occidental. Predominan San Fernando, El
Lido y El Refugio, barrios que el mercado inmobiliario
comercializa como “sur” por continuidad de precio y perfil
socioeconómico, aunque administrativamente estén más cerca del centro.
La conclusión es la misma: la etiqueta zona es
comercial y debe validarse contra el mapa antes de presentar una
oferta.
| Variable | Pearson | Spearman |
|---|---|---|
| Área construida | 0.757 | 0.866 |
| Estrato | 0.673 | 0.752 |
| Nº de baños | 0.733 | 0.706 |
| Nº de habitaciones | 0.344 | 0.392 |
| Nº de parqueaderos | 0.682 | 0.713 |
g4 <- ggplot(base2, aes(areaconst, preciom, color = estrato_f,
text = paste0(barrio, "<br>", areaconst, " m² · $", preciom, " M"))) +
geom_point(alpha = 0.5, size = 1.5) +
scale_color_viridis_d(name = "Estrato") +
labs(title = "Precio vs. área construida apartamentos Zona Sur",
x = "Área construida (m²)", y = "Precio (millones $)") +
theme_minimal()
ggplotly(g4, tooltip = "text")Figura 10. Precio frente a área construida, por estrato apartamentos Zona Sur.
g5 <- ggplot(base2, aes(estrato_f, preciom, fill = estrato_f)) +
geom_boxplot(alpha = 0.8, outlier.size = 0.6) +
scale_fill_viridis_d(guide = "none") +
labs(title = "Precio por estrato apartamentos Zona Sur",
x = "Estrato", y = "Precio (millones $)") + theme_minimal()
ggplotly(g5)Figura 11. Distribución del precio por estrato apartamentos Zona Sur.
Diferencias relevantes frente al mercado de casas del norte (Tabla 25 y Figuras 10 y 11). Aquí los parqueaderos (r = 0,682) y los baños (r = 0,733) tienen correlaciones mucho más altas que en casas. Tiene sentido económico: en propiedad horizontal el parqueadero es un bien escaso que se transa y se escritura por separado, mientras que en una casa está incorporado al predio. En la Figura 11 y la Tabla 26 el estrato 6 se despega marcadamente del resto: su precio mediano (580 millones) duplica el del estrato 5.
| Estrato | n | Precio medio | Mediana | Precio/m² (miles, mediana) |
|---|---|---|---|---|
| 3 | 200 | 141.1 | 128 | 2041 |
| 4 | 1086 | 203.6 | 188 | 2700 |
| 5 | 1031 | 293.0 | 280 | 3056 |
| 6 | 460 | 593.6 | 580 | 3971 |
b2_m1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = base2)
b2_m2 <- lm(preciom ~ areaconst + estrato_f + habitaciones + parqueaderos + banios, data = base2)
b2_m3 <- lm(log(preciom) ~ log(areaconst) + estrato_f + habitaciones + parqueaderos + banios, data = base2)
smear2 <- mean(exp(residuals(b2_m3)))
summary(b2_m1)##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = base2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1222.3 -42.4 -2.0 35.7 937.0
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -216.2717 13.8692 -15.59 < 0.0000000000000002 ***
## areaconst 1.4241 0.0506 28.15 < 0.0000000000000002 ***
## estrato 56.0988 2.8172 19.91 < 0.0000000000000002 ***
## habitaciones -24.1698 3.5374 -6.83 0.00000000001 ***
## parqueaderos 49.4820 3.1549 15.68 < 0.0000000000000002 ***
## banios 50.9473 3.1500 16.17 < 0.0000000000000002 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 95.1 on 2771 degrees of freedom
## Multiple R-squared: 0.752, Adjusted R-squared: 0.752
## F-statistic: 1.68e+03 on 5 and 2771 DF, p-value: <0.0000000000000002
##
## Call:
## lm(formula = log(preciom) ~ log(areaconst) + estrato_f + habitaciones +
## parqueaderos + banios, data = base2)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.6556 -0.1214 0.0107 0.1322 0.8977
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.16582 0.06391 33.89 < 0.0000000000000002 ***
## log(areaconst) 0.64023 0.01718 37.27 < 0.0000000000000002 ***
## estrato_f4 0.24953 0.01533 16.27 < 0.0000000000000002 ***
## estrato_f5 0.38070 0.01616 23.55 < 0.0000000000000002 ***
## estrato_f6 0.67461 0.02053 32.86 < 0.0000000000000002 ***
## habitaciones -0.03601 0.00752 -4.79 0.0000018 ***
## parqueaderos 0.09944 0.00664 14.98 < 0.0000000000000002 ***
## banios 0.05833 0.00678 8.60 < 0.0000000000000002 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.195 on 2769 degrees of freedom
## Multiple R-squared: 0.855, Adjusted R-squared: 0.854
## F-statistic: 2.33e+03 on 7 and 2769 DF, p-value: <0.0000000000000002
| Modelo | R2 | R2 ajustado | AIC | RMSE | MAE | MAPE (%) |
|---|---|---|---|---|---|---|
| M1 lineal | 0.752 | 0.752 | 33188 | 95.0 | 57.8 | 21.2 |
| M2 estrato factor | 0.787 | 0.786 | 32776 | 88.2 | 52.5 | 18.2 |
| M3 log-log | 0.855 | 0.854 | -1190 | 78.3 | 46.5 | 15.7 |
La Tabla 27 compara las tres especificaciones, que aquí ordenan igual que en las casas: M3 domina en R² y en error de predicción.
Figura 12. Gráficos diagnósticos del modelo M1 (lineal
en niveles) apartamentos Zona Sur.
Figura 13. Gráficos diagnósticos del modelo M3 (log-log) apartamentos Zona Sur.
| Modelo | Shapiro-Wilk (normalidad) | Breusch-Pagan (homocedasticidad) | RESET (linealidad) | Durbin-Watson |
|---|---|---|---|---|
| M1 lineal | <0.0000000000000001 | <0.0000000000000001 | <0.0000000000000001 | 1.505 |
| M2 estrato factor | <0.0000000000000001 | <0.0000000000000001 | 0.00000000000000225 | 1.711 |
| M3 log-log | <0.0000000000000001 | <0.0000000000000001 | 0.0000015 | 1.550 |
| Variable | VIF |
|---|---|
| areaconst | 2.12 |
| estrato | 1.73 |
| habitaciones | 1.44 |
| parqueaderos | 1.84 |
| banios | 2.62 |
Con 2.777 observaciones, las pruebas formales de la Tabla 28 rechazan prácticamente todos los supuestos en los tres modelos. Ese resultado, sin embargo, dice menos de lo que parece: con muestras grandes cualquier desviación pequeña se vuelve estadísticamente significativa, de modo que aquí la evidencia gráfica de las Figuras 12 y 13 es más informativa que los p-valores. Y lo que muestran esas figuras es que la transformación logarítmica corrige casi todo lo que el modelo en niveles hacía mal.
En conjunto, el diagnóstico gráfico respalda la elección de M3 con más fuerza que las pruebas formales. M1 no es simplemente menos preciso: está mal especificado, y sus errores estándar e intervalos de predicción no son utilizables. M3 tiene defectos acotados, colas pesadas y estructura espacial no capturada, que justifican acompañarlo de errores estándar robustos, pero no invalidan su uso para estimar rangos de negociación.
Sugerencias: además de las cuatro planteadas para la Solicitud 1, aquí es especialmente recomendable incorporar como factor el barrio, la base tiene suficientes observaciones por barrio en el sur para estimarlos, y estimar por separado el estrato 6, cuyo comportamiento de precios se aparta claramente del resto del mercado.
nueva_v2_e5 <- data.frame(areaconst = 300, estrato = 5,
estrato_f = factor(5, levels = c(3,4,5,6)),
habitaciones = 5, parqueaderos = 3, banios = 3)
nueva_v2_e6 <- within(nueva_v2_e5, {estrato <- 6; estrato_f <- factor(6, levels = c(3,4,5,6))})
exp(predict(b2_m3, nueva_v2_e5, interval = "prediction")) * c(smear2, 1, 1)## fit lwr upr
## 1 671.8 449.1 968.5
## fit lwr upr
## 1 901.4 602.4 1300
| Escenario | Estimación | IC 95% inf | IC 95% sup | IP 95% inf | IP 95% sup |
|---|---|---|---|---|---|
| M1 lineal · estrato 5 | 671.9 | 651.0 | 692.8 | 484.2 | 859.6 |
| M1 lineal · estrato 6 | 728.0 | 706.9 | 749.1 | 540.3 | 915.7 |
| M2 factor · estrato 5 | 626.5 | 606.6 | 646.3 | 452.2 | 800.7 |
| M2 factor · estrato 6 | 787.7 | 767.2 | 808.2 | 613.3 | 962.0 |
| M3 log-log · estrato 5 | 671.8 | 634.1 | 685.9 | 449.1 | 968.5 |
| M3 log-log · estrato 6 | 901.4 | 849.5 | 921.7 | 602.4 | 1299.7 |
Advertencia de extrapolación. La predicción de la Tabla 30 es menos confiable que la de la Solicitud 1 y debe comunicarse así. La solicitud combina un área (300 m²) que solo alcanzan 31 de los 2.777 apartamentos de la base con un número de habitaciones (5) que solo tienen 28. Ambas condiciones se cumplen simultáneamente en apenas 11 registros. El modelo está prediciendo en un borde del espacio de datos, donde los intervalos son amplios y la discrepancia entre modelos es mayor (M3 estima 901 millones en estrato 6 frente a 788 de M2, una diferencia superior a los 100 millones).
Conclusión para el cliente. En estrato 5 el valor estimado (672 millones) deja un margen de 178 millones frente al crédito: la operación es holgadamente viable. En estrato 6 el valor estimado (901 millones) consume casi todo el cupo y el intervalo de predicción se extiende hasta 1.300 millones, por encima del crédito. Se recomienda priorizar estrato 5 y presentar estrato 6 solo si el cliente quiere buscar ampliar su finanziación.
A diferencia de la Solicitud 1, aquí el requerimiento completo deja un conjunto factible muy reducido, por lo que la búsqueda se organiza en dos niveles.
of2_estricto <- base2 %>%
filter(preciom <= 850, estrato %in% c(5, 6), areaconst >= 225,
habitaciones >= 5, banios >= 3, parqueaderos >= 3) %>%
mutate(valor_modelo = exp(predict(b2_m3, newdata = .)) * smear2,
brecha_pct = (preciom / valor_modelo - 1) * 100)
nrow(of2_estricto)## [1] 3
| Barrio | Estrato | Precio (millones) | Área (m²) | Hab | Baños | Parq | Precio/m² (miles) | Valor modelo (millones) | Brecha (%) |
|---|---|---|---|---|---|---|---|---|---|
| seminario | 5 | 530 | 256 | 5 | 5 | 3 | 2070 | 682 | -22.3 |
| seminario | 5 | 670 | 300 | 6 | 5 | 3 | 2233 | 728 | -8.0 |
| guadalupe | 5 | 730 | 573 | 5 | 8 | 3 | 1274 | 1361 | -46.4 |
La Tabla 31 deja ver el problema de fondo: en toda la base de tres meses existen apenas 3 apartamentos en la Zona Sur que reúnan simultáneamente estrato 5 o 6, más de 225 m², 5 o más habitaciones, 3 o más baños y 3 parqueaderos dentro del presupuesto. Este es, por sí solo, un resultado que María debe comunicar al cliente: no se trata de un problema de precio sino de escasez estructural de ese producto en el mercado caleño.
Relajando el mínimo a 4 habitaciones y 2 parqueaderos, y acotando el área a un rango de ±25% alrededor de los 300 m² solicitados, el conjunto se amplía a 13 alternativas.
of2 <- base2 %>%
filter(preciom <= 850, estrato %in% c(5, 6),
areaconst >= 225, areaconst <= 375,
habitaciones >= 4, banios >= 3, parqueaderos >= 2) %>%
mutate(valor_modelo = exp(predict(b2_m3, newdata = .)) * smear2,
brecha_pct = (preciom / valor_modelo - 1) * 100)
nrow(of2)## [1] 13
| Barrio | Estrato | Precio (millones) | Área (m²) | Hab | Baños | Parq | Precio/m² (miles) | Valor modelo (millones) | Brecha (%) |
|---|---|---|---|---|---|---|---|---|---|
| capri | 5 | 350 | 270 | 4 | 3 | 3 | 1296 | 651 | -46.2 |
| San Fernando | 5 | 350 | 258 | 5 | 4 | 2 | 1357 | 585 | -40.2 |
| cuarto de legua | 5 | 410 | 296 | 4 | 4 | 2 | 1387 | 662 | -38.1 |
| san fernando | 5 | 500 | 330 | 4 | 4 | 2 | 1515 | 710 | -29.6 |
| cuarto de legua | 5 | 520 | 320 | 4 | 4 | 2 | 1625 | 697 | -25.4 |
| pance | 6 | 600 | 230 | 4 | 5 | 2 | 2609 | 802 | -25.2 |
| seminario | 5 | 530 | 256 | 5 | 5 | 3 | 2070 | 682 | -22.3 |
| san fernando viejo | 5 | 485 | 259 | 4 | 4 | 2 | 1873 | 608 | -20.3 |
| el refugio | 5 | 490 | 237 | 4 | 4 | 2 | 2068 | 575 | -14.7 |
| el ingenio | 6 | 700 | 250 | 5 | 4 | 2 | 2800 | 770 | -9.1 |
| seminario | 5 | 670 | 300 | 6 | 5 | 3 | 2233 | 728 | -8.0 |
| ciudadela pasoancho | 5 | 650 | 275 | 5 | 5 | 2 | 2364 | 646 | 0.5 |
| ciudadela pasoancho | 5 | 650 | 249 | 4 | 4 | 2 | 2610 | 593 | 9.6 |
pal_of2 <- colorNumeric("RdYlGn", domain = of2$brecha_pct, reverse = TRUE)
leaflet(of2) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(~longitud, ~latitud, radius = 8, stroke = TRUE, weight = 1,
color = "#333333", fillColor = ~pal_of2(brecha_pct), fillOpacity = 0.85,
popup = ~paste0("<b>", barrio, "</b> · Estrato ", estrato,
"<br>Precio: $", preciom, " M",
"<br>Valor modelo: $", round(valor_modelo, 0), " M",
"<br>Brecha: ", round(brecha_pct, 1), "%",
"<br>", round(areaconst, 0), " m² · ", habitaciones,
" hab · ", banios, " baños · ", parqueaderos, " parq."),
label = ~paste0(barrio, " · $", preciom, " M")) %>%
addCircleMarkers(data = of2_estricto, ~longitud, ~latitud, radius = 12,
fillOpacity = 0, color = "#111111", weight = 2,
label = ~paste0("CUMPLE TODO: ", barrio, " · $", preciom, " M")) %>%
addLegend("bottomright", pal = pal_of2, values = ~brecha_pct,
title = "Brecha vs.<br>valor modelo (%)") %>%
addControl("Solicitud 2 ofertas ≤ 850 M$ (círculo negro: cumplen todo el requerimiento)",
position = "topright")Figura 14. Ofertas candidatas para la Solicitud 2. El círculo negro marca las tres ofertas que cumplen íntegramente el requerimiento (Tabla 31).
Se contrasta la regla adoptada, imputar cero, entendiendo el faltante como ausencia de parqueaderocontra las dos alternativas razonables: imputar la mediana de cada base y descartar los registros incompletos.
b1_sin <- datos %>% filter(tipo == "Casa", zona == "Zona Norte")
# (a) imputación por mediana
b1_med <- b1_sin %>%
mutate(parqueaderos = ifelse(is.na(parqueaderos),
median(parqueaderos, na.rm = TRUE), parqueaderos))
m_med <- lm(log(preciom) ~ log(areaconst) + estrato_f + habitaciones +
parqueaderos + banios, data = b1_med)
s_med <- mean(exp(residuals(m_med)))
# (b) solo casos completos
b1_cc <- base1 %>% filter(!parq_faltante)
m_cc <- lm(log(preciom) ~ log(areaconst) + estrato_f + habitaciones +
parqueaderos + banios, data = b1_cc)
s_cc <- mean(exp(residuals(m_cc)))
data.frame(
Enfoque = c("Imputación con CERO (adoptada)", "Imputación por mediana",
"Solo casos completos"),
n = c(nrow(base1), nrow(b1_med), nrow(b1_cc)),
R2 = round(c(summary(b1_m3)$r.squared, summary(m_med)$r.squared,
summary(m_cc)$r.squared), 3),
`Coef. parqueaderos` = round(c(coef(b1_m3)["parqueaderos"],
coef(m_med)["parqueaderos"],
coef(m_cc)["parqueaderos"]), 4),
`p-valor parq.` = round(c(summary(b1_m3)$coefficients["parqueaderos", 4],
summary(m_med)$coefficients["parqueaderos", 4],
summary(m_cc)$coefficients["parqueaderos", 4]), 4),
`Predicción estrato 4 (M)` = round(c(
exp(predict(b1_m3, nueva_v1_e4)) * smear1,
exp(predict(m_med, nueva_v1_e4)) * s_med,
exp(predict(m_cc, nueva_v1_e4)) * s_cc), 1),
`Predicción estrato 5 (M$)` = round(c(
exp(predict(b1_m3, nueva_v1_e5)) * smear1,
exp(predict(m_med, nueva_v1_e5)) * s_med,
exp(predict(m_cc, nueva_v1_e5)) * s_cc), 1),
check.names = FALSE
) %>% kbl(caption = "Tabla 33. Sensibilidad a la regla de imputación de parqueaderos Solicitud 1") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
row_spec(1, bold = TRUE, background = "#EAF2F8")| Enfoque | n | R2 | Coef. parqueaderos | p-valor parq. | Predicción estrato 4 (M) | Predicción estrato 5 (M$) |
|---|---|---|---|---|---|---|
| Imputación con CERO (adoptada) | 700 | 0.784 | 0.0111 | 0.1361 | 337.0 | 386.9 |
| Imputación por mediana | 700 | 0.786 | 0.0244 | 0.0112 | 331.1 | 380.8 |
| Solo casos completos | 431 | 0.728 | 0.0403 | 0.0000 | 341.3 | 386.2 |
Lo que sí es robusto: la recomendación de negocio. Las tres reglas de la Tabla 33 producen predicciones que difieren en menos de seis millones de pesos, un margen irrelevante frente al error típico del modelo, que es de decenas de millonesy las tres conducen a la misma conclusión operativa: la casa solicitada cabe en el crédito de 350 millones en estrato 4 y no en estrato 5. La respuesta que María debe dar al cliente no depende de cómo se traten los faltantes.
Lo que no es robusto: el coeficiente de
parqueaderos. En la Tabla 33 es
la única cantidad del estudio que cambia de manera apreciable. Con
imputación en cero el efecto es prácticamente nulo y no significativo;
con la mediana se duplica; y estimado solo sobre los 431 registros que
sí reportan el dato, se cuadruplica y alcanza significancia. El patrón
es el que predice la teoría del error de medición:
cuanto más ruido se introduce en un regresor, más se atenúa su
coeficiente hacia cero.
Esto no invalida la decisión de imputar con cero, que sigue siendo la interpretación correcta del origen del dato, pero sí obliga a una lectura honesta: el estudio no permite cuantificar con precisión cuánto vale un parqueadero en una casa de la Zona Norte. Si esa cifra fuera importante para el negocio, la vía correcta no es elegir la imputación que dé el resultado deseado, sino verificar contra los anuncios originales si el campo vacío significa realmente “sin parqueadero”. El modelo de apartamentos, donde el dato se reporta en el 86% de los casos, sí permite estimarlo con confianza.