1 0) Carga de datos y preparación

# Paquetes necesarios
packs <- c(
  "devtools", "paqueteMODELOS", # datos
  "tidyverse", "janitor", "lubridate", "scales",
  "plotly", "leaflet", "sf",
  "broom", "gt", "performance", "car", "lmtest", "glue"
)
for(p in packs){ if(!requireNamespace(p, quietly=TRUE)) install.packages(p) }
# paqueteMODELOS desde GitHub si hace falta
if(!requireNamespace("paqueteMODELOS", quietly=TRUE)){
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}

# Carga de librerías
library(paqueteMODELOS)
library(tidyverse); library(janitor); library(lubridate); library(scales)
library(plotly); library(leaflet); library(sf)
library(broom); library(gt); library(performance); library(car); library(lmtest); library(glue)

# Datos
data("vivienda")
raw <- vivienda %>% clean_names()

# Vista general
glimpse(raw)
## Rows: 8,322
## Columns: 13
## $ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso         <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio       <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud     <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud      <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
# Normalización de tipos
vivi <- raw %>%
  mutate(
    estrato = as.integer(estrato),
    preciom = as.numeric(preciom),        # precio en millones de pesos
    areaconst = as.numeric(areaconst),
    parqueaderos = as.integer(parqueaderos),
    banios = as.integer(banios),
    habitaciones = as.integer(habitaciones),
    latitud = as.numeric(latitud),
    longitud = as.numeric(longitud),
    tipo = as.factor(tipo),
    zona = as.factor(zona),
    barrio = as.character(barrio)
  ) %>%
  filter(!is.na(preciom), !is.na(areaconst), !is.na(estrato)) %>%
  distinct()  # remover duplicados exactos si los hay

summary(vivi$preciom)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    58.0   220.0   330.0   433.9   540.0  1999.0

Nota: En todo el informe, preciom está en millones de pesos.

2 1) Filtro base1: Casas en Zona Norte + validación y mapa

base1 <- vivi %>% filter(tipo == "Casa", zona == "Zona Norte")

# Primeros 3 registros
head(base1, 3) %>% gt()
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1209 Zona Norte 02 5 320 150 2 4 6 Casa acopi -76.51341 3.47968
1592 Zona Norte 02 5 780 380 2 3 3 Casa acopi -76.51674 3.48721
4057 Zona Norte 02 6 750 445 NA 7 6 Casa acopi -76.52950 3.38527
# Tablas de control
vivi %>% count(tipo, zona, name = "n_ofertas") %>% arrange(desc(n_ofertas)) %>% gt()
tipo zona n_ofertas
Apartamento Zona Sur 2787
Casa Zona Sur 1939
Apartamento Zona Norte 1198
Apartamento Zona Oeste 1029
Casa Zona Norte 722
Casa Zona Oriente 289
Casa Zona Oeste 169
Casa Zona Centro 100
Apartamento Zona Oriente 62
Apartamento Zona Centro 24
# Comprobación básica de coherencia geográfica (coordenadas dentro de un rango plausible de Cali)
# Cali aprox: lat 3.2–3.7, lon -76.7 a -76.3 (rango amplio para detectar outliers)
base1 <- base1 %>% mutate(
  coord_ok = between(latitud, 3.2, 3.7) & between(longitud, -76.7, -76.3)
)

table_coord <- base1 %>% count(coord_ok, name = "n")

table_coord %>% gt()
coord_ok n
TRUE 722
# Mapa: todas las ofertas (gris) y Casas Zona Norte (color)
leaflet() %>% addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(data = vivi %>% filter(!is.na(latitud), !is.na(longitud)),
                   lng = ~longitud, lat = ~latitud, radius = 3,
                   opacity = 0.5, fillOpacity = 0.3, popup = ~paste0(
                     "<b>", tipo, " – ", zona, "</b><br>",
                     barrio, "<br>Área: ", areaconst, " m²",
                     "<br>Precio: $", scales::comma(preciom, accuracy = 0.1), " M"
                   )) %>%
  addCircleMarkers(data = base1 %>% filter(!is.na(latitud), !is.na(longitud)),
                   lng = ~longitud, lat = ~latitud, radius = 5,
                   opacity = 1, fillOpacity = 0.8, color = "#90ee90",
                   popup = ~paste0(
                     "<b>CASA – Zona Norte</b><br>", barrio,
                     "<br>Área: ", areaconst, " m²",
                     "<br>Estrato: ", estrato,
                     "<br>Hab: ", habitaciones, " | Baños: ", banios,
                     " | Parq: ", parqueaderos,
                     "<br>Precio: $", scales::comma(preciom, accuracy = 0.1), " M"
                   ))

Conclusiones:

Cantidad de ofertas: El filtro aplicado a la base de datos permitió identificar 722 casas ubicadas en la Zona Norte de Cali, lo que representa un volumen importante de alternativas para potenciales compradores en este sector.

Coherencia geográfica: Todas las viviendas filtradas presentan coordenadas dentro del rango esperado para el área urbana de Cali (coord_ok = TRUE en 722 casos). Esto indica que no existen registros con errores de georreferenciación ni ubicaciones fuera del perímetro de la ciudad.

Distribución espacial: En el mapa interactivo se observa una alta concentración de ofertas en la franja norte de la ciudad, lo que confirma la correcta clasificación de los registros como “Zona Norte”. Además, las viviendas aparecen distribuidas en varios barrios, lo que diversifica la oferta.

Ejemplos de registros: Los primeros casos muestran variación en área construida (150–445 m²), estrato (5–6), número de habitaciones y precio (320–780 millones), lo cual refleja heterogeneidad dentro de la misma zona. Esto será útil más adelante para modelar precios y segmentar opciones según presupuesto.

3 2) Análisis exploratorio enfocado en correlaciones (Plotly)

# Variables de interés
vars_num <- vivi %>% select(preciom, areaconst, estrato, banios, habitaciones)

# Matriz de correlación (Pearson por defecto)
cor_mat <- cor(vars_num, use = "pairwise.complete.obs")
cor_mat
##                preciom areaconst     estrato    banios habitaciones
## preciom      1.0000000 0.6873520  0.60980664 0.6691456   0.26409121
## areaconst    0.6873520 1.0000000  0.27432332 0.6484165   0.51691292
## estrato      0.6098066 0.2743233  1.00000000 0.4203218  -0.07137615
## banios       0.6691456 0.6484165  0.42032178 1.0000000   0.58990641
## habitaciones 0.2640912 0.5169129 -0.07137615 0.5899064   1.00000000
# Heatmap interactivo de correlaciones
plot_ly(x = colnames(cor_mat), y = rownames(cor_mat), z = cor_mat,
        type = "heatmap", hovertemplate = paste(
          "<b>%{x} ~ %{y}</b><br>", "cor: %{z:.2f}<extra></extra>"
        ))
# Dispersión precio vs área (toda la base) con color por zona
plot_ly(vivi, x = ~areaconst, y = ~preciom, color = ~zona, type = "scattergl", mode = "markers",
        hovertemplate = paste(
          "Zona: %{marker.color}<br>",
          "Área: %{x} m²<br>",
          "Precio: $%{y} M<extra></extra>"
        )) %>%
  layout(xaxis = list(title = "Área construida (m²)"), yaxis = list(title = "Precio (M)"))
# Boxplot Precio por Estrato (interactivo)
plot_ly(vivi, x = ~as.factor(estrato), y = ~preciom, type = "box", boxpoints = "outliers") %>%
  layout(xaxis = list(title = "Estrato"), yaxis = list(title = "Precio (M)"))
# Precio vs número de baños (casas), color por habitaciones
plot_ly(vivi %>% filter(tipo=="Casa"), x = ~banios, y = ~preciom, color = ~as.factor(habitaciones),
        type = "scatter", mode = "markers") %>%
  layout(xaxis = list(title = "Baños"), yaxis = list(title = "Precio (M)"), legend = list(title=list(text="Habitaciones")))

Conclusiones del análisis exploratorio

Área construida y precio:

Existe una correlación positiva moderada-alta (r ≈ 0.69) entre el área construida y el precio de la vivienda.

En el diagrama de dispersión se observa que a mayor área, los precios tienden a ser más altos, aunque con bastante dispersión en los valores superiores.

Estrato y precio:

El boxplot por estrato muestra que el precio se incrementa de manera clara conforme sube el estrato.

La correlación también es positiva (r ≈ 0.61), lo cual confirma que los inmuebles de estratos altos suelen ser más costosos.

Baños y habitaciones:

El número de baños tiene una correlación moderada con el precio (r ≈ 0.66), lo que sugiere que es una característica relevante en la valorización.

El número de habitaciones muestra una relación más débil con el precio (r ≈ 0.26), lo que indica que el impacto no es tan fuerte como el área o el estrato.

Interacciones entre variables:

Baños y área tienen alta correlación (r ≈ 0.65), lo que es lógico, ya que viviendas más grandes suelen tener más baños.

Habitaciones y baños también se correlacionan positivamente (r ≈ 0.59).

Esto indica cierta redundancia: algunas variables aportan información similar y habrá que revisarlo al momento de construir el modelo para evitar multicolinealidad.

Diferencias por zona:

El gráfico de dispersión precio–área por zonas evidencia que la Zona Sur concentra gran parte de la oferta con precios altos y áreas grandes.

La Zona Norte también presenta precios elevados, mientras que Oriente y Centro tienden a mostrar precios menores, incluso en áreas comparables.

Síntesis ejecutiva:

El análisis confirma que el precio de la vivienda está principalmente explicado por el área construida, el estrato y el número de baños, mientras que las habitaciones tienen menor peso individual. Además, la ubicación (zona) influye claramente en el nivel de precios, lo que justifica incluirla o controlarla en los modelos posteriores.

4 3) Modelo de regresión lineal múltiple (solo casas)

casas <- vivi %>% filter(tipo == "Casa")
modelo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = casas)
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = casas)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1190.80  -114.52   -25.94    74.59   986.16 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -413.87536   25.58852 -16.174  < 2e-16 ***
## areaconst       0.74227    0.02941  25.235  < 2e-16 ***
## estrato       116.07109    5.26618  22.041  < 2e-16 ***
## habitaciones  -14.74995    3.18137  -4.636 3.73e-06 ***
## parqueaderos   64.29943    3.47719  18.492  < 2e-16 ***
## banios         39.03498    4.05083   9.636  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 205.2 on 2480 degrees of freedom
##   (733 observations deleted due to missingness)
## Multiple R-squared:  0.6834, Adjusted R-squared:  0.6828 
## F-statistic:  1071 on 5 and 2480 DF,  p-value: < 2.2e-16
# Métricas de ajuste
r2 <- summary(modelo)$r.squared
r2_adj <- summary(modelo)$adj.r.squared
rmse <- sqrt(mean(residuals(modelo)^2))

glue("R2 = {round(r2,3)} | R2 ajustado = {round(r2_adj,3)} | RMSE = {round(rmse,2)} M")
## R2 = 0.683 | R2 ajustado = 0.683 | RMSE = 205 M
# Tabla ordenada de coeficientes
broom::tidy(modelo, conf.int = TRUE) %>%
  mutate(across(estimate:conf.high, ~round(.x,3))) %>%
  arrange(p.value) %>%
  gt()
term estimate std.error statistic p.value conf.low conf.high
(Intercept) -413.875 25.589 -16.174 0 -464.052 -363.698
areaconst 0.742 0.029 25.235 0 0.685 0.800
estrato 116.071 5.266 22.041 0 105.745 126.398
habitaciones -14.750 3.181 -4.636 0 -20.988 -8.512
parqueaderos 64.299 3.477 18.492 0 57.481 71.118
banios 39.035 4.051 9.636 0 31.092 46.978

Coeficiente de determinación (R²):

El modelo explica aproximadamente el 68.3% de la variabilidad del precio de las casas.

Es un ajuste aceptable considerando que en bienes raíces influyen factores no incluidos (ubicación exacta, antigüedad, acabados, etc.).

Error estándar residual (RMSE):

El error medio de predicción es de 205 millones, lo que marca la magnitud típica de desviación entre los valores observados y los predichos.

Esto significa que el modelo es útil para capturar tendencias, pero hay un margen de error considerable en casos individuales.

Significancia de las variables:

Área construida (0.742, p<0.001): por cada metro cuadrado adicional, el precio aumenta en promedio 0.74 millones.

Estrato (116.07, p<0.001): subir un nivel de estrato aumenta el precio en promedio 116 millones, manteniendo lo demás constante.

Parqueaderos (64.30, p<0.001): cada parqueadero adicional incrementa el precio en 64 millones.

Baños (39.04, p<0.001): cada baño adicional aumenta el precio en 39 millones.

Habitaciones (-14.75, p<0.001): sorprendentemente, más habitaciones reducen el precio en promedio, lo cual puede reflejar colinealidad con el área (casas grandes con muchas habitaciones no siempre son más costosas si el área por habitación es menor).

Intercepción: El intercepto (-413) carece de interpretación práctica, pues corresponde al precio estimado cuando todas las variables valen cero (situación irreal).

Conclucion general: El precio de las casas en Cali depende principalmente del área, el estrato y la dotación de parqueaderos y baños. El modelo explica dos tercios de la variabilidad de los precios, con un error promedio de ±205 millones. Se recomienda complementar con variables de localización y características constructivas para afinar la predicción.

5 4) Validación de supuestos

# Linealidad y homocedasticidad: residuales vs ajustados
plot_ly(x = fitted(modelo), y = resid(modelo), type = "scatter", mode = "markers") %>%
  layout(xaxis=list(title="Valores ajustados"), yaxis=list(title="Residuales"))
# Normalidad (QQ-plot)
qq <- qqnorm(resid(modelo), plot.it = FALSE)
plot_ly(x = qq$x, y = qq$y, type = "scatter", mode = "markers") %>%
  add_lines(x = sort(qq$x), y = sort(qq$x)) %>%
  layout(xaxis=list(title="Teórico"), yaxis=list(title="Muestral"))
# Breusch–Pagan (heterocedasticidad)
bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 321.2, df = 5, p-value < 2.2e-16
# Multicolinealidad (VIF)
car::vif(modelo)
##    areaconst      estrato habitaciones parqueaderos       banios 
##     1.541765     1.694080     1.594265     1.535105     2.059733
# Observaciones influyentes (Cook)
cook <- cooks.distance(modelo)
plot_ly(x = seq_along(cook), y = cook, type = "bar") %>%
  layout(xaxis=list(title="Índice"), yaxis=list(title="Cook's D"))

Concluciones: El modelo cumple con los supuestos de independencia y baja multicolinealidad, pero presenta problemas de heterocedasticidad y cierta no normalidad de residuales.

A nivel práctico, el modelo sigue siendo útil y significativo, pero para mejorarlo se podrían aplicar transformaciones (logaritmo del precio), usar modelos robustos o métodos no lineales.

También es recomendable revisar y depurar los outliers influyentes detectados por la distancia de Cook

6 5) Predicción – Solicitud Vivienda 1 (Casa, Norte)

Características requeridas: área = 200 m², parqueaderos = 1, baños = 2, habitaciones = 4, estrato 4 o 5.

new_casa <- expand.grid(
  areaconst = 200,
  estrato = c(4,5),
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)

pred_casa <- predict(modelo, newdata = new_casa, interval = "prediction", level = 0.90) %>% as.data.frame()
resultado_casa <- cbind(new_casa, round(pred_casa, 2))
resultado_casa %>% gt()
areaconst estrato habitaciones parqueaderos banios fit lwr upr
200 4 4 1 2 282.23 -55.71 620.18
200 5 4 1 2 398.30 60.23 736.38

Concluciones: .

El modelo estima que una casa con las características solicitadas tendría un precio esperado de 282 M en estrato 4 (ajustado al presupuesto disponible) y 398 M en estrato 5 (superior al crédito aprobado). Por lo tanto, la recomendación es centrar la búsqueda en casas de estrato 4 en la Zona Norte, donde es más probable encontrar opciones viables dentro del límite de financiación.

7 6) Sugerencia de ofertas – Vivienda 1 (tope $350 M)

# Estrategia de filtrado: match exacto de tipo y zona, y tolerancias razonables en área
# (±15%). Para baños/habitaciones/parqueaderos se piden mínimos.

match_ofertas <- function(data, tipo_req, zona_req, area_obj, area_tol = 0.15,
                          estrato_min = 4, estrato_max = 5,
                          banios_min = 2, hab_min = 4, parq_min = 1,
                          precio_max = 350){
  data %>%
    filter(
      tipo == tipo_req,
      zona == zona_req,
      areaconst >= area_obj*(1-area_tol) & areaconst <= area_obj*(1+area_tol),
      estrato >= estrato_min, estrato <= estrato_max,
      banios >= banios_min,
      habitaciones >= hab_min,
      parqueaderos >= parq_min,
      preciom <= precio_max,
      !is.na(latitud), !is.na(longitud)
    ) %>%
    mutate(
      score = abs(areaconst - area_obj) + pmax(0, preciom - precio_max) # menor es mejor
    ) %>% arrange(score, preciom)
}

ofertas_casa <- match_ofertas(
  vivi, tipo_req = "Casa", zona_req = "Zona Norte",
  area_obj = 200, estrato_min = 4, estrato_max = 5,
  banios_min = 2, hab_min = 4, parq_min = 1, precio_max = 350
)

head(ofertas_casa, 10) %>% select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, latitud, longitud) %>% gt()
barrio preciom areaconst estrato banios habitaciones parqueaderos latitud longitud
la flora 320 200 5 4 4 2 3.48893 -76.51524
la merced 320 200 4 4 4 2 3.48029 -76.51156
el bosque 350 200 5 3 4 3 3.48503 -76.53010
el bosque 335 202 5 4 5 1 3.48399 -76.53044
vipasa 340 203 5 3 4 2 3.48257 -76.51803
el bosque 350 203 5 2 5 2 3.48531 -76.51448
vipasa 300 205 5 5 6 2 3.48138 -76.51832
urbanización la merced 320 210 5 3 5 2 3.47600 -76.51200
la merced 350 216 5 2 4 2 3.48181 -76.51218
la flora 340 180 5 4 4 2 3.48675 -76.51633
# Mapa con al menos 5 ofertas (si existen)
leaflet(ofertas_casa %>% head(10)) %>% addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(~longitud, ~latitud, radius = 7, popup = ~paste0(
    "<b>", barrio, "</b><br>",
    "Precio: $", scales::comma(preciom), " M<br>",
    "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
    "Hab: ", habitaciones, " | Baños: ", banios, " | Parq: ", parqueaderos
  ))

Conclucion:

Las opciones identificadas cumplen con el presupuesto de $350 millones y las características solicitadas para la Vivienda 1. Los barrios La Flora, La Merced, El Bosque y Vipasa se perfilan como las alternativas más sólidas, tanto por ubicación como por precio. Se recomienda a la empresa cliente priorizar estas zonas, ya que ofrecen viviendas que satisfacen los requisitos de área, estrato y comodidades.

8 7) Repetición para Vivienda 2 (Apartamento, Sur, tope $850 M)

Requisitos: tipo = Apartamento, área = 300 m², parqueaderos = 3, baños = 3, habitaciones = 5, estrato 5 o 6, zona = Sur, crédito = $850 M.

# Predicción usando mismo modelo (nota: es de CASAS). Opciones:
#  a) Estimar un modelo análogo solo con Apartamentos; o
#  b) Un modelo combinado con factor(tipo).
# Aquí mostramos ambos para transparencia.

aptos <- vivi %>% filter(tipo == "Apartamento")
modelo_apt <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = aptos)
summary(modelo_apt)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = aptos)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1699.03   -57.72    -0.67    48.59  1005.44 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -278.47706   15.86822  -17.55   <2e-16 ***
## areaconst       2.00464    0.04839   41.42   <2e-16 ***
## estrato        56.24218    3.05907   18.39   <2e-16 ***
## habitaciones  -42.66447    3.80700  -11.21   <2e-16 ***
## parqueaderos   90.42324    4.14278   21.83   <2e-16 ***
## banios         54.84690    3.41824   16.05   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 137.7 on 4225 degrees of freedom
##   (869 observations deleted due to missingness)
## Multiple R-squared:  0.7845, Adjusted R-squared:  0.7843 
## F-statistic:  3077 on 5 and 4225 DF,  p-value: < 2.2e-16
new_apto <- expand.grid(
  areaconst = 300,
  estrato = c(5,6),
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)

pred_apto <- predict(modelo_apt, newdata = new_apto, interval = "prediction", level = 0.90) %>% as.data.frame()
resultado_apto <- cbind(new_apto, round(pred_apto, 2))
resultado_apto %>% gt()
areaconst estrato habitaciones parqueaderos banios fit lwr upr
300 5 5 3 3 826.61 599.36 1053.87
300 6 5 3 3 882.86 655.58 1110.14
# Sugerencia de ofertas (Sur, tope $850 M)
ofertas_apto <- match_ofertas(
  vivi, tipo_req = "Apartamento", zona_req = "Zona Sur",
  area_obj = 300, estrato_min = 5, estrato_max = 6,
  banios_min = 3, hab_min = 5, parq_min = 3, precio_max = 850
)

head(ofertas_apto, 10) %>% select(barrio, preciom, areaconst, estrato, banios, habitaciones, parqueaderos, latitud, longitud) %>% gt()
barrio preciom areaconst estrato banios habitaciones parqueaderos latitud longitud
seminario 670 300 5 5 6 3 3.40900 -76.55000
seminario 530 256 5 5 5 3 3.40748 -76.55408
leaflet(ofertas_apto %>% head(10)) %>% addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(~longitud, ~latitud, radius = 7, popup = ~paste0(
    "<b>", barrio, "</b><br>",
    "Precio: $", scales::comma(preciom), " M<br>",
    "Área: ", areaconst, " m² | Estrato: ", estrato, "<br>",
    "Hab: ", habitaciones, " | Baños: ", banios, " | Parq: ", parqueaderos
  ))

9 Concluciones

El modelo para apartamentos presenta buen ajuste (R²=0.785) y predice precios esperados de 826–883 M para la vivienda solicitada, valores alineados con el crédito aprobado de 850 M. Se identificaron dos opciones viables en el barrio Seminario (Zona Sur), con precios de 530 M y 670 M, que cumplen e incluso superan las características requeridas. Por tanto, existen alternativas en el mercado dentro del presupuesto y con condiciones favorables para la empresa.

10 8) Recomendaciones finales para María

María debe concentrarse en casas de estrato 4 en el norte y apartamentos de estrato 5 en el sur, donde hay oferta suficiente dentro del presupuesto aprobado. Se recomienda complementar el análisis con más variables de ubicación y aplicar modelos más robustos en futuras proyecciones para mejorar la precisión y reducir la influencia de outliers.

sessionInfo()
## R version 4.4.2 (2024-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 22631)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8  LC_CTYPE=Spanish_Colombia.utf8   
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C                     
## [5] LC_TIME=Spanish_Colombia.utf8    
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] glue_1.8.0           lmtest_0.9-40        zoo_1.8-12          
##  [4] car_3.1-3            carData_3.0-5        performance_0.15.1  
##  [7] gt_1.0.0             sf_1.0-21            leaflet_2.2.2       
## [10] plotly_4.11.0        scales_1.3.0         janitor_2.2.1       
## [13] lubridate_1.9.4      forcats_1.0.0        stringr_1.5.1       
## [16] dplyr_1.1.4          purrr_1.0.4          readr_2.1.5         
## [19] tidyr_1.3.1          tibble_3.2.1         tidyverse_2.0.0     
## [22] paqueteMODELOS_0.1.0 summarytools_1.1.4   knitr_1.50          
## [25] gridExtra_2.3        GGally_2.2.1         ggplot2_3.5.2       
## [28] broom_1.0.8          boot_1.3-31         
## 
## loaded via a namespace (and not attached):
##  [1] DBI_1.2.3               tcltk_4.4.2             remotes_2.5.0          
##  [4] rlang_1.1.5             magrittr_2.0.3          snakecase_0.11.1       
##  [7] matrixStats_1.5.0       e1071_1.7-16            compiler_4.4.2         
## [10] reshape2_1.4.4          vctrs_0.6.5             profvis_0.4.0          
## [13] crayon_1.5.3            pkgconfig_2.0.3         fastmap_1.2.0          
## [16] backports_1.5.0         magick_2.8.7            ellipsis_0.3.2         
## [19] pander_0.6.6            promises_1.3.2          rmarkdown_2.29         
## [22] tzdb_0.4.0              sessioninfo_1.2.3       xfun_0.52              
## [25] cachem_1.1.0            jsonlite_1.8.9          later_1.4.1            
## [28] pryr_0.1.6              R6_2.6.1                bslib_0.9.0            
## [31] stringi_1.8.4           RColorBrewer_1.1-3      pkgload_1.4.0          
## [34] jquerylib_0.1.4         Rcpp_1.0.14             usethis_3.1.0          
## [37] base64enc_0.1-3         leaflet.providers_2.0.0 httpuv_1.6.15          
## [40] timechange_0.3.0        tidyselect_1.2.1        rstudioapi_0.17.1      
## [43] abind_1.4-8             yaml_2.3.10             codetools_0.2-20       
## [46] miniUI_0.1.2            pkgbuild_1.4.6          lattice_0.22-6         
## [49] plyr_1.8.9              shiny_1.10.0            withr_3.0.2            
## [52] evaluate_1.0.3          ggstats_0.9.0           units_0.8-7            
## [55] proxy_0.4-27            urlchecker_1.0.1        xml2_1.3.6             
## [58] pillar_1.10.1           KernSmooth_2.23-24      checkmate_2.3.2        
## [61] insight_1.4.1           generics_0.1.3          hms_1.1.3              
## [64] munsell_0.5.1           xtable_1.8-4            class_7.3-22           
## [67] lazyeval_0.2.2          tools_4.4.2             data.table_1.16.4      
## [70] fs_1.6.5                rapportools_1.2         grid_4.4.2             
## [73] crosstalk_1.2.1         devtools_2.4.5          colorspace_2.1-1       
## [76] Formula_1.2-5           cli_3.6.3               viridisLite_0.4.2      
## [79] gtable_0.3.6            sass_0.4.9              digest_0.6.37          
## [82] classInt_0.4-11         farver_2.1.2            htmlwidgets_1.6.4      
## [85] memoise_2.0.1           htmltools_0.5.8.1       lifecycle_1.0.4        
## [88] httr_1.4.7              mime_0.12               MASS_7.3-61