Este informe presenta un análisis detallado de la oferta inmobiliaria caleña, con el objetivo de identificar y proponer alternativas de vivienda en respuesta a dos solicitudes específicas. Para ello, se emplea una base de datos que contiene características clave de los inmuebles como la ubicación, el tamaño, el precio, el estrato socioeconómico, el número de habitaciones, baños, parqueaderos y el tipo de vivienda. Sobre esta información se aplican técnicas de ciencia de datos para la limpieza, el análisis exploratorio y el modelado, lo que permite obtener predicciones del precio de los inmuebles. Finalmente, se incluyen tablas y mapas interactivos que facilitan la visualización de las ofertas seleccionadas y respaldan la toma de decisiones de acuerdo con las necesidades del cliente.
Para la preparación y limpieza de la base de datos primero hacemos una inspección inicial de la estructura del dataset.
# Tabla con estructura de los datos
Estructura <- data.frame(
Variable = names(vivienda),
Tipo = sapply(vivienda, function(x) paste(class(x), collapse=",")),
Ejemplo = sapply(vivienda, function(x) paste(utils::head(x, 2), collapse = ", "))
)
knitr::kable(Estructura, caption = "Estructura del dataset original")| Variable | Tipo | Ejemplo | |
|---|---|---|---|
| id | id | numeric | 1147, 1169 |
| zona | zona | character | Zona Oriente, Zona Oriente |
| piso | piso | character | NA, NA |
| estrato | estrato | numeric | 3, 3 |
| preciom | preciom | numeric | 250, 320 |
| areaconst | areaconst | numeric | 70, 120 |
| parqueaderos | parqueaderos | numeric | 1, 1 |
| banios | banios | numeric | 3, 2 |
| habitaciones | habitaciones | numeric | 6, 3 |
| tipo | tipo | character | Casa, Casa |
| barrio | barrio | character | 20 de julio, 20 de julio |
| longitud | longitud | numeric | -76.51168, -76.51237 |
| latitud | latitud | numeric | 3.43382, 3.43369 |
Seguido, convertimos a factor las variables categóricas clave (zona, tipo, barrio, estrato) para evitar interpretarlas como numéricas por error en analisis posteriores. Además, eliminamos registros duplicados, eliminamos variables no necesarias para el análisis como la columna id e identificamos los valores faltantes en cada variable.
# Identificar los duplicados
num_duplicados <- sum(duplicated(vivienda))
removed_pct <- 100 * num_duplicados / nrow(vivienda)
knitr::kable(
data.frame(
Registros = nrow(vivienda),
Duplicados = num_duplicados
),
caption = "Conteo de duplicados"
)| Registros | Duplicados |
|---|---|
| 8322 | 1 |
# Eliminar duplicados conservando la primera aparición
ev_before <- nrow(vivienda)
vivienda <- dplyr::distinct(vivienda)
ev_after <- nrow(vivienda)
# Eliminar la variable id
vivienda <- vivienda %>% select(-id)
# Conteo y % de NAs por columna
na_tbl <- vivienda %>%
dplyr::summarise(dplyr::across(dplyr::everything(), ~ sum(is.na(.)))) %>%
tidyr::pivot_longer(dplyr::everything(), names_to = "Variable", values_to = "NA_n") %>%
dplyr::mutate(Total = nrow(vivienda), NA_pct = round(100 * NA_n / Total, 2)) %>%
dplyr::arrange(dplyr::desc(NA_pct))
knitr::kable(na_tbl, caption = "Valores faltantes por variable")| Variable | NA_n | Total | NA_pct |
|---|---|---|---|
| piso | 2637 | 8321 | 31.69 |
| parqueaderos | 1604 | 8321 | 19.28 |
| zona | 2 | 8321 | 0.02 |
| estrato | 2 | 8321 | 0.02 |
| areaconst | 2 | 8321 | 0.02 |
| banios | 2 | 8321 | 0.02 |
| habitaciones | 2 | 8321 | 0.02 |
| tipo | 2 | 8321 | 0.02 |
| barrio | 2 | 8321 | 0.02 |
| longitud | 2 | 8321 | 0.02 |
| latitud | 2 | 8321 | 0.02 |
| preciom | 1 | 8321 | 0.01 |
Primero, se eliminan las NAs de variables con pocos valores faltantes. En el caso de La variable piso, se decide excluirla del análisis debido a que no presenta una definición clara y consistente de esta en el dataset: en apartamentos, el valor parece indicar el piso en el que está ubicada la vivienda, mientras que en casas podría referirse al número total de pisos (con valores atipicos > 4) o a otra característica no especificada. Esta ambigüedad, junto a su numerosa cantidad de faltantes (31.69%) impide su uso confiable en los análisis posteriores. Por otro laso, para tratar la variable parqueaderos se utiliza una imputación mediante MICE.
# Eliminar las NAs de variables con pocos valores faltantes
columnas_limpiar <- c("zona", "estrato", "preciom", "areaconst", "banios", "latitud","longitud", "habitaciones", "tipo")
vivienda_limpia1 <- vivienda %>%
filter(if_all(all_of(columnas_limpiar), ~ !is.na(.)))
# Eliminar la variable piso
vivienda_limpia2 <- vivienda_limpia1 %>% select(-piso)
# Seleccionar variables relevantes para la imputación
vars_imputacion <- c("parqueaderos", "areaconst", "preciom", "habitaciones", "banios")
# Imputación con MICE
set.seed(123)
mice_data <- mice(vivienda_limpia2[, vars_imputacion],
m = 1, # número de imputaciones
method = "pmm", # predictive mean matching
maxit = 5, # iteraciones
seed = 123)##
## iter imp variable
## 1 1 parqueaderos
## 2 1 parqueaderos
## 3 1 parqueaderos
## 4 1 parqueaderos
## 5 1 parqueaderos
vivienda_mice <- vivienda_limpia2
vivienda_mice[, vars_imputacion] <- complete(mice_data)
vivienda_mice$parqueaderos <- round(vivienda_mice$parqueaderos, 0)Finalmente, para el tratamiento de outliers, se obtiene un analisis inicial por variable y se analiza si los atípicos de variables como habitaciones, baños y parqueaderos se asocian con propiedades de alta gama (mayor área/precio), pues si esto se cumple, se considera no eliminarlos para no perder información relevante del mercado alto.
# Función para detectar outliers y generar tabla
detectar_outliers_tabla <- function(df) {
num_vars <- df[, sapply(df, is.numeric), drop = FALSE]
resumen <- data.frame(Variable = character(), N_outliers = integer())
for (var in names(num_vars)) {
datos <- num_vars[[var]]
Q1 <- quantile(datos, 0.25, na.rm = TRUE)
Q3 <- quantile(datos, 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
lim_inf <- Q1 - 1.5 * IQR_val
lim_sup <- Q3 + 1.5 * IQR_val
outliers <- sum(datos < lim_inf | datos > lim_sup, na.rm = TRUE)
resumen <- rbind(resumen, data.frame(Variable = var, N_outliers = outliers))
}
# Ordenar de mayor a menor cantidad de outliers
resumen[order(-resumen$N_outliers), , drop = FALSE]
}
# Mostrar tabla
tabla_outliers <- detectar_outliers_tabla(vivienda_mice)
knitr::kable(
tabla_outliers,
caption = "Conteo de outliers por variable"
)| Variable | N_outliers | |
|---|---|---|
| 6 | habitaciones | 888 |
| 4 | parqueaderos | 630 |
| 2 | preciom | 552 |
| 3 | areaconst | 382 |
| 7 | longitud | 130 |
| 5 | banios | 72 |
| 1 | estrato | 0 |
| 8 | latitud | 0 |
# Función para obtener índices de outliers
get_outlier_idx <- function(x) {
Q1 <- quantile(x, 0.25, na.rm = TRUE)
Q3 <- quantile(x, 0.75, na.rm = TRUE)
IQR_val <- Q3 - Q1
lower <- Q1 - 1.5 * IQR_val
upper <- Q3 + 1.5 * IQR_val
which(x < lower | x > upper)
}
# Variables a evaluar
vars_check <- c("habitaciones", "banios", "parqueaderos")
# Usar el dataset imputado con MICE
df <- vivienda_mice
# Detectar y comparar
for (var in vars_check) {
idx_outliers <- get_outlier_idx(df[[var]])
cat("Variable:", var)
cat("Número de outliers:", length(idx_outliers))
# Revisar si coinciden con precios altos o área alta
summary_precio <- summary(df$preciom[idx_outliers])
summary_area <- summary(df$areaconst[idx_outliers])
cat("Resumen PrecioM en outliers:")
print(summary_precio)
cat("Resumen AreaConst en outliers:")
print(summary_area)
# Porcentaje de outliers que están en el 30% superior de precio o área
p_precio <- mean(df$preciom[idx_outliers] > quantile(df$preciom, 0.7, na.rm = TRUE))
p_area <- mean(df$areaconst[idx_outliers] > quantile(df$areaconst, 0.7, na.rm = TRUE))
cat(sprintf("Porcentaje de outliers con PrecioM alto (>70%%): %.1f%%\n", 100 * p_precio))
cat(sprintf("Porcentaje de outliers con AreaConst alta (>70%%): %.1f%%\n", 100 * p_area))
}## Variable: habitacionesNúmero de outliers: 888Resumen PrecioM en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 89.0 310.0 425.0 531.8 650.0 1940.0
## Resumen AreaConst en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 35.0 200.0 296.0 321.2 410.0 1600.0
## Porcentaje de outliers con PrecioM alto (>70%): 42.1%
## Porcentaje de outliers con AreaConst alta (>70%): 73.2%
## Variable: baniosNúmero de outliers: 72Resumen PrecioM en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 175.0 586.2 740.0 902.8 1300.0 1940.0
## Resumen AreaConst en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 128.0 323.8 450.0 458.1 537.8 910.0
## Porcentaje de outliers con PrecioM alto (>70%): 86.1%
## Porcentaje de outliers con AreaConst alta (>70%): 95.8%
## Variable: parqueaderosNúmero de outliers: 630Resumen PrecioM en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 190 720 950 1018 1300 1999
## Resumen AreaConst en outliers: Min. 1st Qu. Median Mean 3rd Qu. Max.
## 50.0 285.2 356.0 412.1 494.2 1586.0
## Porcentaje de outliers con PrecioM alto (>70%): 91.7%
## Porcentaje de outliers con AreaConst alta (>70%): 93.0%
Ya que una proporción sustancial de outliers cae en los tramos superiores de precio y área, se sugiere que representan viviendas de alta gama y deben conservarse. El dataset final se guarda como vivienda_final, quedando con las siguientes dimensiones:
# Dataset final
vivienda_final <- vivienda_mice
# Resumen final
knitr::kable(
data.frame(
Registros = nrow(vivienda_final),
Variables = ncol(vivienda_final)
), caption = "Dimensiones del dataset final")| Registros | Variables |
|---|---|
| 8319 | 11 |
Para el análisis inicial, estudiamos la correlación de la variable precio con el resto de variables numéricas.
# Correlación entre precio y variables numéricas
vars <- vivienda_final %>%
select(preciom, areaconst, banios, habitaciones, parqueaderos)
ct <- psych::corr.test(vars, use = "pairwise", method = "pearson")
corrplot(ct$r,
method = "color",
type = "lower",
addCoef.col = "black",
tl.col = "black",
number.cex = .7,
p.mat = ct$p,
sig.level = 0.05,
insig = "blank")El gráfico nos muestra una alta correlación positiva significativa entre la variable precio con el area construida, el número de baños y los parqueaderos, mostrando que estas son caracteristicas determinantes para explicar la dinámica de precios en el mercado inmobiliario analizado.
p_violin <- plot_ly(
vivienda_final,
x = ~estrato,
y = ~preciom,
type = "violin",
points = "all", # muestra puntos
box = list(visible = TRUE), # añade boxplot dentro
meanline = list(visible = TRUE)
) %>%
plotly::layout(
title = "Precio por estrato",
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio")
)
p_violinEl gráfico muestra que el precio aumenta claramente con el estrato: las medianas y cuartiles son más altos a medida que sube el estrato. También, se presenta una mayor dispersión (violines más anchos y colas largas) en estratos altos, donde aparecen outliers de precio elevado. Aun así, se observa cierto solapamiento entre estratos vecinos, lo que sugiere que el estrato no explica por sí solo el precio y pueden influir, como se vio en la matriz de correlación, otros factores como el área, el número de baños y parqueaderos, entre otros.
# Boxplot interactivo Precio vs Zona
p_zona <- plot_ly(
data = vivienda_final,
x = ~zona,
y = ~preciom,
type = "box",
boxpoints = "all",
jitter = 0.35,
pointpos = -1.6,
marker = list(size = 5, opacity = 0.35),
hovertemplate = "Zona: %{x}<br>Precio: $%{y:,.0f}<extra></extra>",
color = ~zona,
showlegend = FALSE
) %>%
layout(
title = "Distribución del precio por zona",
xaxis = list(title = "Zona"),
yaxis = list(title = "Precio", tickformat = ",.0f")
)
p_zonaEl gráfico muestra que la zona es un factor clave en la determinación del precio de la vivienda. Mientras que la Zona Oriente concentra los valores más bajos, las Zonas Norte y Oeste presentan precios más elevados y una amplia dispersión, lo que sugiere que pueden coexistir en la misma zona, viviendas de diferentes segmentos (propiedades de lujo y propiedades más económicas). Por su parte, la Zona Centro y Zona Sur exhiben precios intermedios, con el Centro más homogéneo y el Sur más heterogéneo.
Se filtra la base de datos según las caracteristicas de Zona y Tipo de cada solicitud. A continuación se presentan algunos ejemplos para cada solicitud y un mapa interactivo que nos ayuda a visualizar la ubicación de las viviendas obtenidas en cada filtro:
# Filtrar datos
base1 <- vivienda_final %>%
filter(tipo == "Casa" & zona == "Zona Norte")
# Mostrar tabla
kable(
head(base1, 3),
caption = "Resultados Solicitud 1"
)| zona | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|
| Zona Norte | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51341 | 3.47968 |
| Zona Norte | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.51674 | 3.48721 |
| Zona Norte | 6 | 750 | 445 | 3 | 7 | 6 | Casa | acopi | -76.52950 | 3.38527 |
# Filtrar datos
base2 <- vivienda_final %>%
filter(tipo == "Apartamento" & zona == "Zona Sur")
# Mostrar tabla
kable(
head(base1, 3),
caption = "Resultados Solicitud 2"
)| zona | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|
| Zona Norte | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51341 | 3.47968 |
| Zona Norte | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.51674 | 3.48721 |
| Zona Norte | 6 | 750 | 445 | 3 | 7 | 6 | Casa | acopi | -76.52950 | 3.38527 |
# Filtrar coordenadas válidas
base1_map <- base1 %>% filter(!is.na(latitud), !is.na(longitud))
base2_map <- base2 %>% filter(!is.na(latitud), !is.na(longitud))
# Crear mapa interactivo
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron) %>%
# Solicitud 1
addCircleMarkers(
data = base1_map,
lng = ~longitud, lat = ~latitud,
radius = 6, stroke = FALSE, fillOpacity = 0.75,
color = "#1f77b4",
label = ~paste0(
"Zona: ", zona,
"<br>Tipo: ", tipo,
"<br>Precio: $", comma(preciom)
),
group = "Resultados Solicitud 1"
) %>%
# Solicitud 2
addCircleMarkers(
data = base2_map,
lng = ~longitud, lat = ~latitud,
radius = 6, stroke = FALSE, fillOpacity = 0.75,
color = "#ff7f50",
label = ~paste0(
"Zona: ", zona,
"<br>Tipo: ", tipo,
"<br>Precio: $", comma(preciom)
),
group = "Resultados Solicitud 2"
) %>%
# Control de capas
addLayersControl(
overlayGroups = c("Resultados Solicitud 1", "Resultados Solicitud 2"),
options = layersControlOptions(collapsed = FALSE)
) %>%
# Leyenda
addLegend(
position = "bottomright",
colors = c("#1f77b4", "#ff7f50"),
labels = c("Resultados Solicitud 1", "Resultados Solicitud 2"),
title = "Solicitudes"
)El mapa confirma una mayor concentración de inmuebles en el Norte para la Solicitud 1 y en el Sur para la Solicitud 2, lo que concuerda con los filtros aplicados; sin embargo, también se observan puntos fuera de las zonas esperadas, lo que puede atribuirse a problemas de calidad en los datos como errores de geocodificación, inconsistencias en la variable zona, registros ubicados en límites administrativos o errores de digitación. Debido a esto, en análisis futuros se recomienda realizar una revision de la base de datos que permitra validar la correspondencia espacial antes de realizar análisis más detallados.
Se ajusta un modelo de regresión lineal multiple para la predicción del precio de las viviendas usando como variables predictoras el area construida, el estrato, el número de habitaciones, parqueaderos y baños .
# Division de datos en Train/Test (70/30)
n <- nrow(vivienda_final)
idx_train <- sample(seq_len(n), size = floor(0.7 * n))
train <- vivienda_final[idx_train, ]
test <- vivienda_final[-idx_train, ]
# Ajuste del modelo
modelo <- lm(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = train
)
# Coeficientes (tabla)
tabla_resultados <- tidy(modelo) %>%
mutate(
estimate = round(estimate, 2),
std.error = round(std.error, 2),
statistic = round(statistic, 2),
p.value = formatC(p.value, format = "f", digits = 6)
) %>%
rename(
Variable = term,
Coeficiente = estimate,
`Error Estándar` = std.error,
`Estadístico t` = statistic,
`Valor p` = p.value
)
kable(
tabla_resultados,
caption = "Resultados del modelo de regresión lineal múltiple (entrenamiento)"
)| Variable | Coeficiente | Error Estándar | Estadístico t | Valor p |
|---|---|---|---|---|
| (Intercept) | -353.82 | 13.25 | -26.71 | 0.000000 |
| areaconst | 0.86 | 0.02 | 37.98 | 0.000000 |
| estrato | 94.46 | 2.73 | 34.63 | 0.000000 |
| habitaciones | -25.49 | 2.21 | -11.54 | 0.000000 |
| parqueaderos | 75.10 | 2.73 | 27.46 | 0.000000 |
| banios | 51.23 | 2.59 | 19.75 | 0.000000 |
# Métricas de ajuste
tabla_metricas <- glance(modelo) %>%
mutate(
r.squared = round(r.squared, 4),
adj.r.squared = round(adj.r.squared, 4)
) %>%
select(r.squared, adj.r.squared) %>%
rename(
`R²` = r.squared,
`R² Ajustado` = adj.r.squared
)
kable(
tabla_metricas,
caption = "Métricas de ajuste del modelo"
)| R² | R² Ajustado |
|---|---|
| 0.738 | 0.7378 |
# Predicciones en datos de prueba
pred_test <- predict(modelo, newdata = test)
# Indicadores de rendimiento en datos de prueba
rmse_val <- rmse(actual = test$preciom, predicted = pred_test)
mae_val <- mae(actual = test$preciom, predicted = pred_test)
mape_val <- mape(actual = test$preciom, predicted = pred_test) * 100 # en %
tabla_desempeno <- data.frame(
RMSE = round(rmse_val, 3),
MAE = round(mae_val, 3),
MAPE = paste0(round(mape_val, 2), " %")
)
kable(
tabla_desempeno,
caption = "Desempeño predictivo en el set de prueba"
)| RMSE | MAE | MAPE |
|---|---|---|
| 174.6 | 108.693 | 27.42 % |
El modelo ajustado muestra que todas las variables incluidas son estadísticamente significativas en la explicación del precio de la vivienda. El coeficiente de área construida indica que por cada metro cuadrado adicional, el precio aumenta en promedio 0.86 millones de pesos, lo cual confirma la importancia del tamaño en la valorización del inmueble. En el caso del estrato, el numero de parqueaderos y de baños, se observa que un incremento de una unidad se asocia con un aumento de 94.46, 75.10 y 51.23 millones en el precio, respectivamente, lo que refleja la fuerte influencia del nivel socioeconómico, la ubicación y las caracteristicas que agregan comodidad al inmueble. Por otro lado, el número de habitaciones presenta un coeficiente negativo (–25.49 millones), lo cual puede explicarse porque, al controlar por área construida y número de baños, las habitaciones adicionales no implican necesariamente un mayor valor de mercado. En otras palabras, el modelo sugiere que más cuartos no siempre se traducen en un precio más alto si no están acompañados de mayor área, baños o parqueaderos.
El modelo presenta un R² de 0.7338, lo que significa que aproximadamente el 73.4% de la variabilidad del precio de la vivienda se explica por las variables incluidas para el análisis. El R² ajustado, que penaliza por el número de variables, es prácticamente igual, lo que indica que todas las variables aportan de manera consistente al modelo y no hay sobreajuste evidente.
La métricas obtenidas en el set de prueba muestran un MAE de 108.7 millones, lo que significa que, en promedio, las predicciones del modelo se desvían del valor real de la vivienda en alrededor de 109 millones. El MAPE de 27.4% indica que el error relativo de predicción es cercano a un tercio del precio real, lo cual refleja un ajuste moderado, es decir, el modelo logra capturar tendencias generales del precio, pero presenta limitaciones para predecir con alta precisión casos individuales.
A continuación se evaluan los supuestos del modelo:
##
## studentized Breusch-Pagan test
##
## data: modelo
## BP = 940.28, df = 5, p-value < 2.2e-16
El gráfico de residuos vs valores ajustados muestra cierta curvatura y un aumento en la dispersión a medida que crece el valor predicho, lo que indica problemas de linealidad y heterocedasticidad. Esto se confirma con la prueba de Breusch-Pagan (p-valor < 0.05), que rechaza la hipótesis de homocedasticidad.
##
## Asymptotic one-sample Kolmogorov-Smirnov test
##
## data: scale(residuos)
## D = 0.13628, p-value < 2.2e-16
## alternative hypothesis: two-sided
El gráfico Q-Q evidencia desviaciones sistemáticas de la línea diagonal, particularmente en las colas de la distribución, lo cual sugiere el incumplimiento del supuesto de normalidad de los residuos. Este hallazgo se corrobora estadísticamente mediante la prueba de Kolmogorov-Smirnov (p-valor < 0.05), que lleva al rechazo de la hipótesis nula de normalidad en la distribución de los residuos.
##
## Durbin-Watson test
##
## data: modelo
## DW = 1.9904, p-value = 0.3562
## alternative hypothesis: true autocorrelation is greater than 0
El estadístico Durbin-Watson obtenido fue DW = 1.9904 con un p-valor = 0.3562, lo que indica que no existe evidencia de autocorrelación en los residuos. Por lo tanto, se cumple el supuesto de independencia, y los errores pueden considerarse aleatorios, sin patrones de correlación.
## areaconst estrato habitaciones parqueaderos banios
## 2.121480 1.622302 2.025966 1.855657 2.852366
Todos los valores de de VIF son menore a 5, lo que indica que no existe multicolinealidad significativa entre las variables y que cada una aporta informacion distinta al modelo.
Dado que el modelo presenta problemas de linealidad, heterocedasticidad y falta de normalidad en los residuos, se recomienda considerar transformaciones de la variable dependiente (por ejemplo, aplicar logaritmo al precio), utilizar errores estándar robustos para mitigar la heterocedasticidad o explorar modelos alternativos más flexibles, como la regresión robusta o los modelos generalizados (GLM), que permitan captar relaciones no lineales y mejorar la validez de las inferencias.
Utilizando el modelo previamente ajustado, se hace la predicción según las caracteristicas solicitadas:
Tipo: Casa
Area construida: 200 m2
Parqueaderos: 1
Baños: 2
Habitaciones: 4
Estrato: 4 o 5
Zona: Norte
Precio: 350 millones
vivienda1 <- data.frame(
areaconst = 200,
estrato = 4,
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
vivienda2 <- data.frame(
areaconst = 200,
estrato = 5,
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
pred1 <- predict(modelo, newdata = vivienda1)
pred2 <- predict(modelo, newdata = vivienda2)
cat("Precio predicho para la vivienda con estrato 4:", pred1, "millones de pesos")## Precio predicho para la vivienda con estrato 4: 271.3301 millones de pesos
## Precio predicho para la vivienda con estrato 5: 365.7948 millones de pesos
Con un crédito preaprobado de 350 millones, únicamente la alternativa con estrato 4 se ajusta al presupuesto. En consecuencia, se sugieren cinco candidatas priorizando aquellas con valores más cercanos al precio, área construida, al número de habitaciones por considerarse las características más esenciales en la decisión de compra.
AREA_OBJ <- 200
HAB_OBJ <- 4
BAN_OBJ <- 2
PARK_OBJ <- 1
ESTR_OBJ <- 4
BUDGET <- 350
pred_ref <- predict(modelo, newdata = data.frame(
areaconst = AREA_OBJ,
estrato = ESTR_OBJ,
habitaciones = HAB_OBJ,
parqueaderos = PARK_OBJ,
banios = BAN_OBJ
))
# Candidatas: Casa, Zona Norte y dentro del presupuesto
candidatas <- vivienda_final %>%
filter(
tipo == "Casa",
zona == "Zona Norte",
!is.na(preciom),
preciom <= BUDGET
) %>%
mutate(
# Puntuación de cercanía a la solicitud
score =
0.35*abs(preciom - as.numeric(pred_ref) +
0.35*abs(areaconst - AREA_OBJ) +
0.20*abs(habitaciones - HAB_OBJ) +
0.05*abs(banios - BAN_OBJ) +
0.05*abs(parqueaderos - PARK_OBJ))
) %>%
arrange(score)
# Tomar 5 ofertas
ofertas_top <- candidatas %>% slice_head(n = 5)
# Tabla resumen de las ofertas
kable(
ofertas_top %>%
select(preciom, areaconst, estrato, habitaciones, banios, parqueaderos, zona, tipo),
caption = "Top 5 ofertas potenciales para la solicitud 1"
)| preciom | areaconst | estrato | habitaciones | banios | parqueaderos | zona | tipo |
|---|---|---|---|---|---|---|---|
| 270 | 196 | 3 | 4 | 2 | 1 | Zona Norte | Casa |
| 230 | 82 | 3 | 6 | 6 | 2 | Zona Norte | Casa |
| 250 | 135 | 3 | 4 | 3 | 1 | Zona Norte | Casa |
| 190 | 435 | 3 | 0 | 0 | 1 | Zona Norte | Casa |
| 253 | 140 | 4 | 4 | 3 | 2 | Zona Norte | Casa |
# Mapa interactivo con ofertas
leaflet(ofertas_top) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
popup = ~paste(
"Precio:", preciom, "M<br>",
"Área:", areaconst, "m²<br>",
"Hab/Baños/Parq:", habitaciones, "/", banios, "/", parqueaderos, "<br>",
"Estrato:", estrato, "<br>",
"Zona:", zona
)
)Las cinco casas seleccionadas cumplen con el límite de 350 millones de pesos y se encuentran en la Zona Norte, de acuerdo con la solicitud. Dos de las opciones corresponden a estrato 4, que es el que mejor se ajusta al crédito disponible, mientras que el resto pertenecen a estrato 3, lo que ofrece alternativas con diferente nivel socioeconómico. En cuanto a las características físicas, todas cuentan con entre 4 y 6 habitaciones, 2 a 6 baños y al menos un parqueadero, condiciones coherentes con lo solicitado por el cliente (con una oferta atipica con 0 habitaciones y baños). Estas ofertas ofrecen opciones viables que permiten al cliente analizar con detalle los inmuebles para elegir lo que mejor balancee precio, tamaño y ubicación.
Utilizando el modelo previamente ajustado, se hace la predicción según las caracteristicas solicitadas:
Tipo: Apartamento
Area construida: 300 m2
Parqueaderos: 3
Baños: 3
Habitaciones: 5
Estrato: 5 o 6
Zona: Sur
Precio: 850 millones
vivienda3 <- data.frame(
areaconst = 300,
estrato = 5,
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
vivienda4 <- data.frame(
areaconst = 300,
estrato = 6,
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
pred3 <- predict(modelo, newdata = vivienda3)
pred4 <- predict(modelo, newdata = vivienda4)
cat("Precio predicho para la vivienda con estrato 5:", pred3, "millones de pesos")## Precio predicho para la vivienda con estrato 5: 627.5852 millones de pesos
## Precio predicho para la vivienda con estrato 6: 722.0499 millones de pesos
Al igual que con la solicitud anterior, se hacen obtienen las mejores 5 ofertas que se muestran a continuación:
AREA_OBJ2 <- 300
HAB_OBJ2 <- 5
BAN_OBJ2 <- 3
PARK_OBJ2 <- 3
ESTR_OBJ2 <- 5
BUDGET2 <- 850
pred_ref <- predict(modelo, newdata = data.frame(
areaconst = AREA_OBJ2,
estrato = ESTR_OBJ2,
habitaciones = HAB_OBJ2,
parqueaderos = PARK_OBJ2,
banios = BAN_OBJ2
))
# Candidatas: Casa, Zona Norte y dentro del presupuesto
candidatas2 <- vivienda_final %>%
filter(
tipo == "Apartamento",
zona == "Zona Sur",
!is.na(preciom),
preciom <= BUDGET2
) %>%
mutate(
# Puntuación de cercanía a la solicitud
score =
0.35*abs(preciom - as.numeric(pred_ref)) +
0.35*abs(areaconst - AREA_OBJ2) +
0.20*abs(habitaciones - HAB_OBJ2) +
0.05*abs(banios - BAN_OBJ2) +
0.05*abs(parqueaderos - PARK_OBJ2)
) %>%
arrange(score)
# Tomar 5 ofertas
ofertas_top2 <- candidatas2 %>% slice_head(n = 5)
# Tabla resumen de las ofertas
kable(
ofertas_top2 %>%
select(preciom, areaconst, estrato, habitaciones, banios, parqueaderos, zona, tipo),
caption = "Top 5 ofertas potenciales para la solicitud 2"
)| preciom | areaconst | estrato | habitaciones | banios | parqueaderos | zona | tipo |
|---|---|---|---|---|---|---|---|
| 670 | 300 | 5 | 6 | 5 | 3 | Zona Sur | Apartamento |
| 650 | 275 | 5 | 5 | 5 | 2 | Zona Sur | Apartamento |
| 650 | 249 | 5 | 4 | 4 | 2 | Zona Sur | Apartamento |
| 655 | 241 | 6 | 3 | 3 | 2 | Zona Sur | Apartamento |
| 630 | 210 | 5 | 3 | 2 | 2 | Zona Sur | Apartamento |
# Mapa interactivo con ofertas
leaflet(ofertas_top2) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud, lat = ~latitud,
popup = ~paste(
"Precio:", preciom, "M<br>",
"Área:", areaconst, "m²<br>",
"Hab/Baños/Parq:", habitaciones, "/", banios, "/", parqueaderos, "<br>",
"Estrato:", estrato, "<br>",
"Zona:", zona
)
)Los cinco apartamentos seleccionados cumplen con los requisitos centrales de la solicitud, es decir, se ajustan al presupuesto disponible, se ubican en la Zona Sur y presentan áreas construidas y estratos acordes a lo solicitado. En cuanto a otras características como el número de habitaciones, baños y parqueaderos, las ofertas muestran una amplia variedad: algunas opciones ofrecen espacios más amplios con mayor cantidad de habitaciones y baños, mientras que otras presentan configuraciones más reducidas pero igualmente funcionales, acompañadas de menores exigencias en parqueaderos, lo que puede traducirse en ahorro en el costo total. Esto brinda al cliente la posibilidad de comparar alternativas y seleccionar la vivienda que mejor equilibre sus necesidades de espacio, comodidades y presupuesto.