1. Indice

  1. Indice
  2. Enunciado
  3. Exploración de Datos Inicial
  4. Construcción y Análisis Base 1: Casas Zona Norte
  5. Modelo de Regresión Lineal Múltiple para Base 1
  6. Conclusiones y Ofertas Potenciales (Vivienda 1)
  7. Construcción y Análisis Base 1: Apartamentos Zona Sur
  8. Modelo de Regresión Lineal Múltiple para Base 2
  9. Conclusiones y Ofertas Potenciales (Vivienda 2)
  10. Referencias
  11. Anexos

2. Enunciado

Enunciado

Maria comenzó como agente de bienes raíces en Cali hace 10 años. Después de laborar dos años para una empresa nacional, se traslado a Bogotá y trabajó para otra agencia de bienes raíces. Sus amigos y familiares la convencieron de que con su experiencia y conocimientos del negocio debía abrir su propia agencia. Terminó por adquirir la licencia de intermediario y al poco tiempo fundó su propia compañía, C&A (Casas y Apartamentos) en Cali. Santiago y Lina, dos vendedores de la empresa anterior aceptaron trabajar en la nueva compaña. En la actualidad ocho agentes de bienes raíces colaboran con ella en C&A.

Actualmente las ventas de bienes raíces en Cali se han visto disminuidas de manera significativa en lo corrido del año. Durante este periodo muchas instituciones bancarias de ahorro y vivienda están prestando grandes sumas de dinero para la industria y la construcción comercial y residencial. Cuando el efecto producto de las tensiones políticas y sociales disminuya, se espera que la actividad económica de este sector se reactive.

Hace dos días, María recibió una carta solicitando asesoría para la compra de dos viviendas por parte de una compañía internacional que desea ubicar a dos de sus empleados con sus familias en la ciudad. Las solicitudes incluyen las siguientes condiciones:

Ayude a María a responder la solicitud, mediante técnicas modelación que usted conoce. Ella requiere le envíe un informe ejecutivo donde analice los dos casos y sus recomendaciones (Informe). Como soporte del informe debe anexar las estimaciones, validaciones y comparación de modelos requeridos (Anexos) .

Pasos requeridos para la obtención de resultados

  1. Realice un filtro a la base de datos e incluya solo las ofertas de : base1: casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta. (Adicional un mapa con los puntos de las bases. Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?).

  2. Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.

  3. Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deber están contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente \(R^2\) y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).

  4. Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).

  5. Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.

  6. Con las predicciones del modelo sugiera potenciales ofertas que responda a la solicitud de la vivienda 1. Tenga encuentra que la empresa tiene crédito pre-aprobado de máximo 350 millones de pesos. Realice un análisis y presente en un mapa al menos 5 ofertas potenciales que debe discutir.

  7. Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.

Base de datos

data("vivienda")
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>

3. Exploración de Datos Inicial

En esta sección se retoman y adaptan resultados de un análisis exploratorio global previamente elaborado para el informe Evaluación de la oferta inmobiliaria urbana (https://rpubs.com/jcmolano/1398005), con el propósito de documentar el proceso de revisión, depuración y caracterización general del conjunto de datos antes de construir las subbases específicas requeridas para separar viviendas de zona norte y zona sur. Este paso preliminar permite trabajar posteriormente con información más consistente al momento de filtrar las ofertas por tipo de inmueble y zona de la ciudad, así como al desarrollar los análisis exploratorios, los modelos de regresión y las conclusiones para cada modelo.

3.1. Acerca de la base de datos

La base de datos vivienda contiene 8.322 registros y 13 variables asociadas a inmuebles residenciales. Cada observación corresponde a una vivienda identificada por un id y caracterizada por su ubicación (zona y barrio, además de coordenadas geográficas longitud y latitud), por rasgos físicos de la vivienda (por ejemplo, piso, área construida y tipo de inmueble), y por variables habitacionales como número de habitaciones, baños y parqueaderos. También incluye información socioeconómica y de mercado, como el estrato y el precio (variable preciom). En general, la base integra variables categóricas (zona, piso, tipo, barrio) y numéricas (estrato, preciom, areaconst, parqueaderos, baños, habitaciones, longitud y latitud). La información presente en la base de datos se expone en la Tabla 1.

Tabla 1: Información general de la base de datos

reactable(vivienda, rownames = TRUE)

3.2. Algunas consideraciones e inconsistencias

Como parte del análisis descriptivo, primero se identifican los tipos de variables presentes en la base de datos (Tabla 2).

tipos <- sapply(vivienda, class)

tabla_tipos <- data.frame(
  Nombre = names(tipos),
  Tipo = as.character(tipos),
  row.names = NULL
)


tagList(
  h4("Tabla 2: Tipos de datos"),
  reactable(tabla_tipos,
          columns = list(
            Nombre = colDef(name = "Nombre de la variable"),
            Tipo = colDef(name = "Tipo de dato")
          )))

Tabla 2: Tipos de datos

En particular, las variables zona, piso, tipo y barrio corresponden a etiquetas cualitativas sin significado numérico directo, por lo que se transformaron a factores. Asimismo, variables como estrato, número de parqueaderos, baños y habitaciones, aunque registradas numéricamente, representan conteos o niveles discretos; por ello, en esta etapa exploratoria se analizan como variables categóricas para facilitar tablas de frecuencia y visualizaciones descriptivas. En la etapa de modelación (regresión), estas variables se reconvertirán a formato numérico según lo requiera el modelo.

Tras aplicar las transformaciones, se obtiene nuevamente la tabla de tipos de variables (Tabla 3), ahora con los cambios reflejados en el dataset:

vivienda$zona<-as.factor(unlist(vivienda$zona))
vivienda$piso<-as.factor(unlist(vivienda$piso))
vivienda$tipo<-as.factor(unlist(vivienda$tipo))
vivienda$barrio<-as.factor(unlist(vivienda$barrio))

vivienda$estrato<-as.factor(unlist(vivienda$estrato))
vivienda$parqueaderos<-as.factor(unlist(vivienda$parqueaderos))
vivienda$banios<-as.factor(unlist(vivienda$banios))
vivienda$habitaciones<-as.factor(unlist(vivienda$habitaciones))


tipos <- sapply(vivienda, class)

tabla_tipos <- data.frame(
  Nombre = names(tipos),
  Tipo = as.character(tipos),
  row.names = NULL
)


tagList(
  h4("Tabla 3: Tipos de datos posterior a la transformación de variables"),
  reactable(tabla_tipos,
          columns = list(
            Nombre = colDef(name = "Nombre de la variable"),
            Tipo = colDef(name = "Tipo de dato")
          )))

Tabla 3: Tipos de datos posterior a la transformación de variables

Posterior a la conversión de variables a factor, se procede a realizar un resumen estadístico general de la base de datos (Figura 1). Este paso permite obtener una primera visión de la distribución de cada variable, identificando valores mínimos, máximos, medias, medianas y cuartiles en el caso de las variables numéricas, así como las frecuencias absolutas para las variables de tipo factor.

De esta manera, el resumen constituye una herramienta inicial para detectar posibles valores atípicos, inconsistencias y patrones relevantes que orientarán el análisis posterior.

Figura 1: Resumen de las variables presentes en el conjunto de datos

summary(vivienda)
##        id                 zona           piso      estrato        preciom      
##  Min.   :   1   Zona Centro : 124   02     :1450   3   :1453   Min.   :  58.0  
##  1st Qu.:2080   Zona Norte  :1920   03     :1097   4   :2129   1st Qu.: 220.0  
##  Median :4160   Zona Oeste  :1198   01     : 860   5   :2750   Median : 330.0  
##  Mean   :4160   Zona Oriente: 351   04     : 607   6   :1987   Mean   : 433.9  
##  3rd Qu.:6240   Zona Sur    :4726   05     : 567   NA's:   3   3rd Qu.: 540.0  
##  Max.   :8319   NA's        :   3   (Other):1103               Max.   :1999.0  
##  NA's   :3                          NA's   :2638               NA's   :2       
##    areaconst       parqueaderos      banios      habitaciones 
##  Min.   :  30.0   1      :3155   2      :2946   3      :4097  
##  1st Qu.:  80.0   2      :2475   3      :1993   4      :1729  
##  Median : 123.0   3      : 520   4      :1456   2      : 926  
##  Mean   : 174.9   4      : 384   5      : 890   5      : 679  
##  3rd Qu.: 229.0   5      :  68   1      : 496   6      : 318  
##  Max.   :1745.0   (Other): 115   (Other): 538   (Other): 570  
##  NA's   :3        NA's   :1605   NA's   :   3   NA's   :   3  
##           tipo                 barrio        longitud         latitud     
##  Apartamento:5100   valle del lili:1008   Min.   :-76.59   Min.   :3.333  
##  Casa       :3219   ciudad jardín : 516   1st Qu.:-76.54   1st Qu.:3.381  
##  NA's       :   3   pance         : 409   Median :-76.53   Median :3.416  
##                     la flora      : 366   Mean   :-76.53   Mean   :3.418  
##                     santa teresita: 262   3rd Qu.:-76.52   3rd Qu.:3.452  
##                     (Other)       :5758   Max.   :-76.46   Max.   :3.498  
##                     NA's          :   3   NA's   :3        NA's   :3

El resumen descriptivo de la base muestra que las viviendas presentan una amplia variabilidad en sus características físicas y de mercado. El área construida oscila aproximadamente entre 30 y 1745 unidades, con una mediana cercana a 123, lo que indica predominio de viviendas de tamaño medio, aunque existen algunos inmuebles considerablemente más grandes. El precio presenta una dispersión importante, con valores desde cerca de 58 hasta casi 2000, sugiriendo heterogeneidad en el mercado inmobiliario analizado. En cuanto a las características habitacionales, la mayoría de viviendas cuenta con entre 2 y 4 habitaciones y entre 2 y 4 baños, mientras que el número de parqueaderos suele concentrarse entre 1 y 2. La variable tipo muestra predominio de apartamentos sobre casas. Finalmente, se identifican algunos valores faltantes en varias variables, especialmente en parqueaderos y piso.

A partir del resumen descriptivo se identificó la presencia de valores faltantes en algunas variables de la base de datos. Dado que los valores ausentes pueden influir en los resultados de los análisis posteriores, se realizará una revisión de su magnitud y distribución dentro del conjunto de datos. Este análisis permitirá decidir el tratamiento más adecuado, ya sea mediante eliminación de registros incompletos, imputación de valores o uso de métodos que toleren datos faltantes, con el fin de garantizar la consistencia y confiabilidad de los resultados.

3.3. Identificación de datos faltantes

Para el conjunto de datos analizado, en la Tabla 4 se presenta el número de valores faltantes por variable. A partir de esta revisión se observa que varias variables contienen datos ausentes, aunque su presencia no es homogénea en toda la base. Algunas variables presentan muy pocos valores faltantes, mientras que en otras la ausencia de información es más notable. La identificación de estos registros incompletos resulta fundamental, ya que los datos faltantes pueden afectar los análisis estadísticos posteriores y, por tanto, es necesario evaluarlos antes de proceder con el modelamiento o la exploración multivariada.

Tabla 4: Total de valores nulos

faltantes <- data.frame(
  Variable = names(colSums(is.na(vivienda))),
  N_Faltantes = as.vector(colSums(is.na(vivienda)))
)


reactable(
  faltantes,
  columns = list(
    Variable = colDef(name = "Nombre de la variable"),
    N_Faltantes = colDef(name = "Número de valores faltantes")
  )
)

Al analizar la distribución de los valores faltantes se identificó un patrón particular en tres registros específicos, los cuales presentan valores ausentes en variables clave del conjunto de datos (Ver Tabla 5). En particular, estos registros carecen de identificador válido (id) y presentan información incompleta en variables relevantes como el precio. Dado que representan una proporción muy pequeña del total de observaciones y que la ausencia de estas variables limita su utilidad analítica, se decidió eliminarlos de la base de datos. Esta decisión permite reducir la presencia de datos faltantes sin afectar de manera significativa la representatividad del conjunto, además de facilitar los análisis posteriores al trabajar con información consistente.

Tabla 5: Valores faltantes en los registros id

reactable(vivienda[is.na(vivienda$id), ], rownames = TRUE)

Posterior a la eliminación de esos registros faltantes, se verifica de nuevo cuantos valores faltantes existen en el conjunto de datos (Tabla 6).

Tabla 6: Total de valores nulos posterior a la eliminación de los registros nulos

vivienda <- vivienda[!is.na(vivienda$id), ]
faltantes <- data.frame(
  Variable = names(colSums(is.na(vivienda))),
  N_Faltantes = as.vector(colSums(is.na(vivienda)))
)


reactable(
  faltantes,
  columns = list(
    Variable = colDef(name = "Nombre de la variable"),
    N_Faltantes = colDef(name = "Número de valores faltantes")
  )
)

Como resultado, se encontró que las únicas variables que aún presentan información faltante son piso y parqueaderos. No obstante, la magnitud de estos valores ausentes es considerablemente menor en comparación con la situación inicial, lo que indica que la depuración previa permitió mejorar la consistencia del conjunto de datos. En consecuencia, se procederá a analizar con mayor detalle estos valores faltantes restantes con el fin de definir la estrategia más adecuada para su tratamiento antes de continuar la separación de la base de datos en inmuebles presentes en zona norte y zona sur.

3.3.1. Variable parqueadero

Para decidir el tratamiento de los valores faltantes en la variable parqueaderos se analizó su distribución por categorías. Se observó en la Tabla 8 que la ausencia de información no es aleatoria, sino que se concentra especialmente por estrato socioeconómico: en el estrato 3, el 52.9% de los registros presentan parqueaderos como NA, mientras que esta proporción disminuye en estratos más altos (22.9% en estrato 4, 8.3% en estrato 5 y 5.9% en estrato 6). Por tipo de inmueble, la proporción de NA es del 17.0% en apartamentos y del 22.8% en casas (Tabla 7).

Tabla 7: Proporción de valores faltantes de parqueaderos por tipo de vivienda

t1 <- prop.table(table(is.na(vivienda$parqueaderos), vivienda$tipo), 2)
t1_red <- round(100 * t1, 1)

t1_df <- as.data.frame.matrix(t1_red)
t1_df$Estado <- rownames(t1_df)
rownames(t1_df) <- NULL

reactable(
  t1_df,
  columns = list(
    Estado = colDef(name = "¿Parqueaderos es NA?"),
    Apartamento = colDef(name = "Apartamento (%)"),
    Casa = colDef(name = "Casa (%)")
  )
)

Tabla 8: Proporción de valores faltantes de parqueaderos por estrato

t2 <- prop.table(table(is.na(vivienda$parqueaderos), vivienda$estrato), 2)
t2_red <- round(100 * t2, 1)

t2_df <- as.data.frame.matrix(t2_red)
t2_df$Estado <- rownames(t2_df)
rownames(t2_df) <- NULL



reactable(
  t2_df,
  columns = list(
    Estado = colDef(name = "¿Parqueaderos es NA?"),
    `3` = colDef(name = "Estrato 3 (%)"),
    `4` = colDef(name = "Estrato 4 (%)"),
    `5` = colDef(name = "Estrato 5 (%)"),
    `6` = colDef(name = "Estrato 6 (%)")
  )
)

Dado que en el conjunto de datos no se registran valores cero para parqueaderos y que el patrón de faltantes es coherente con viviendas que probablemente no disponen de estacionamiento (en especial en estratos bajos), se imputó el valor 0 para los casos con NA como supuesto operativo, interpretando estos registros como inmuebles sin parqueadero.

vivienda$parqueaderos <- as.numeric(as.character(vivienda$parqueaderos))


vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <- 0


sum(is.na(vivienda$parqueaderos)) 
## [1] 0

3.3.2. Variable piso

En la variable piso se identificaron valores faltantes que debían ser tratados antes de continuar con el análisis. Dado que esta variable corresponde a una característica categórica del inmueble, no resultaba apropiado imputar valores numéricos promedio ni aplicar métodos de interpolación. En su lugar, se optó por imputar los valores faltantes mediante la moda, es decir, el valor más frecuente observado en los registros. Para mejorar la coherencia de la imputación, esta se realizó considerando primero agrupaciones de viviendas con características similares (según tipo de inmueble y zona), asignando el piso más frecuente dentro de cada grupo.

moda <- function(x){
  x <- x[!is.na(x)]
  if (length(x) == 0) return(NA)
  names(sort(table(x), decreasing = TRUE))[1]
}

vivienda <- vivienda %>%
  group_by(tipo, zona) %>%
  mutate(piso = ifelse(is.na(piso), moda(piso), piso)) %>%
  ungroup()

sum(is.na(vivienda$piso)) 
## [1] 0

En conclusión, la revisión inicial permitió identificar y tratar inconsistencias relevantes en la base de datos, especialmente en relación con tipos de variables y valores faltantes. A partir de esta depuración se dispone de un conjunto de datos más consistente. En las siguientes secciones se construirán las subbases correspondientes a cada solicitud y se abordarán los pasos de exploración, modelación, validación y recomendación de ofertas.

4. Construcción y análisis Base 1: Casas Zona Norte

En esta sección se construye y analiza la Base 1, conformada por las ofertas de vivienda correspondientes a casas ubicadas en la Zona Norte de la ciudad. A partir de ella se desarrollarán las etapas de verificación del filtro, análisis exploratorio, modelación del precio y selección de inmuebles potencialmente adecuados para el cliente.

4.1. Construcción Base 1: Casas Zona Norte

Como primera verificación del proceso de selección, se presentan a continuación los tres primeros registros de la Base 1, construida a partir de las observaciones correspondientes a viviendas tipo Casa ubicadas en la Zona Norte. Esta visualización permite corroborar de manera preliminar la estructura de la subbase y las variables disponibles para los análisis posteriores.

Tabla 9: Primeros tres registros de la Base 1

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


reactable(
  head(base1, 3),
  columns = list(
    id = colDef(name = "ID"),
    zona = colDef(name = "Zona"),
    barrio = colDef(name = "Barrio"),
    estrato = colDef(name = "Estrato"),
    preciom = colDef(name = "Precio (millones)"),
    areaconst = colDef(name = "Área construida"),
    tipo = colDef(name = "Tipo"),
    habitaciones = colDef(name = "Habitaciones"),
    banios = colDef(name = "Baños"),
    parqueaderos = colDef(name = "Parqueaderos"),
    piso = colDef(name = "Piso"),
    longitud = colDef(name = "Longitud"),
    latitud = colDef(name = "Latitud")
  ),
  defaultPageSize = 3,
  showPageSizeOptions = FALSE,
  pagination = FALSE
)

Una vez revisados los primeros registros de la Base 1, es necesario comprobar de manera explícita que el proceso de filtrado se realizó correctamente. Para ello, a continuación se presentan tablas de frecuencia para las variables tipo y zona, con el fin de verificar que todas las observaciones incluidas en esta subbase correspondan exclusivamente a viviendas tipo Casa localizadas en la Zona Norte. Esta validación permite asegurar la consistencia de la partición realizada antes de avanzar hacia el análisis exploratorio y el modelamiento del precio.

Tabla 10: Verificación Base 1 por Tipo de Inmueble

tab_tipo_base1 <- as.data.frame(table(base1$tipo))
names(tab_tipo_base1) <- c("Tipo", "Frecuencia")

reactable(
  tab_tipo_base1,
  columns = list(
    Tipo = colDef(name = "Tipo de inmueble"),
    Frecuencia = colDef(name = "Frecuencia")
  )
)

Tabla 11: Verificación Base 1 por Zona

tab_zona_base1 <- as.data.frame(table(base1$zona))
names(tab_zona_base1) <- c("Zona", "Frecuencia")

reactable(
  tab_zona_base1,
  columns = list(
    Zona = colDef(name = "Zona"),
    Frecuencia = colDef(name = "Frecuencia")
  )
)

Los resultados de las Tablas 10 y 11 confirman que el proceso de filtrado fue realizado correctamente. En particular, la Base 1 quedó conformada por 722 registros, todos correspondientes a viviendas del tipo Casa y localizadas en la Zona Norte. Por tanto, se verifica que la subbase construida es consistente con las condiciones iniciales de la primera solicitud y resulta adecuada para continuar con el análisis exploratorio y la modelación posterior.

A continuación, la Figura 2 presenta un mapa con la ubicación geográfica de las viviendas incluidas en la Base 1, utilizando las coordenadas de longitud y latitud registradas en el conjunto de datos. Esta visualización permite inspeccionar la distribución espacial de las ofertas seleccionadas y verificar, de manera complementaria, la coherencia entre la clasificación por zona y la ubicación geográfica reportada.

Figura 2: Distribución espacial de las viviendas tipo Casa ubicadas en la Zona Norte

base1_map <- base1 %>% filter(!is.na(longitud), !is.na(latitud))

leaflet(base1_map) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 4,
    stroke = TRUE,
    weight = 1,
    fillOpacity = 0.7,
    popup = ~paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Tipo:</b> ", tipo,
      "<br><b>Zona:</b> ", zona,
      "<br><b>Precio (millones):</b> ", preciom,
      "<br><b>Área construida:</b> ", areaconst,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Hab:</b> ", habitaciones,
      " | <b>Baños:</b> ", banios,
      " | <b>Parq:</b> ", parqueaderos
    )
  )

En la Figura 2 se observa la distribución espacial de las casas clasificadas como Zona Norte dentro de la Base 1. La mayor concentración de puntos se ubica efectivamente en el sector norte de la ciudad, lo cual es consistente con el filtro aplicado. No obstante, también se identifican algunos registros cuya ubicación aparece en zonas centrales e incluso hacia el sur del área urbana. Este comportamiento puede explicarse porque la variable zona corresponde a una clasificación registrada en la base de datos y no necesariamente a una delimitación geográfica estricta con límites cartográficos formales. Adicionalmente, la presencia de puntos aislados podría estar asociada a imprecisiones en las coordenadas geográficas o a inmuebles localizados cerca de áreas limítrofes entre zonas. En consecuencia, el mapa se interpreta como una verificación visual complementaria al filtro categórico realizado, más que como una validación cartográfica exacta de la localización de cada inmueble.

4.2. Análisis exploratorio Base 1

En esta sección se realiza un análisis exploratorio enfocado en la Base 1 con el fin de examinar cómo se relaciona el precio (preciom) con variables físicas y socioeconómicas del inmueble.

Figura 3: Precio vs Área del Inmueble

base1_eda <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst))
  ) %>%
  filter(!is.na(preciom), !is.na(areaconst))

base1_eda <- base1_eda %>%
  mutate(
    hover = paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Área:</b> ", areaconst,
      "<br><b>Precio (millones):</b> ", preciom
    )
  )

p1 <- plot_ly(
  data = base1_eda,
  x = ~areaconst,
  y = ~preciom,
  type = "scatter",
  mode = "markers",
  text = ~hover,
  hoverinfo = "text"
) %>%
  layout(
    title = "Precio vs Área construida (Base 1)",
    xaxis = list(title = "Área construida"),
    yaxis = list(title = "Precio (millones)")
  )

p1

La Figura 3 muestra una relación positiva entre el área construida y el precio de los inmuebles en la Base 1. En términos generales, a medida que aumenta el área del inmueble, también tiende a incrementarse su precio. No obstante, la nube de puntos presenta una dispersión apreciable, especialmente en viviendas de mayor tamaño, lo que indica que inmuebles con áreas similares pueden tener precios diferentes. Asimismo, se observa que la mayor concentración de viviendas se encuentra en rangos intermedios de área y precio, mientras que algunos inmuebles de gran tamaño se ubican en niveles de precio considerablemente más altos.

Figura 4: Precio vs Estrato socioeconómico (Base 1)

base1_eda2 <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    estrato = as.factor(estrato)
  ) %>%
  filter(!is.na(preciom), !is.na(estrato))

# Boxplot interactivo
p_estrato <- plot_ly(
  data = base1_eda2,
  x = ~estrato,
  y = ~preciom,
  type = "box",
  boxpoints = "all",        
  jitter = 0.35,            
  pointpos = 0,
  hovertemplate = paste(
    "<b>Estrato:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por estrato (Base 1)",
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (millones)")
  )

p_estrato

La Figura 4 evidencia un patrón creciente del precio conforme aumenta el estrato socioeconómico. En particular, la mediana del precio en estrato 3 es notablemente inferior a la observada en estratos 4, 5 y 6, y se aprecia un incremento progresivo en la tendencia central a medida que se pasa a estratos más altos. Adicionalmente, la dispersión del precio tiende a ser mayor en los estratos superiores (especialmente en 5 y 6), lo que sugiere una mayor heterogeneidad de oferta dentro de estos segmentos. Aunque existe cierto solapamiento entre estratos consecutivos, el comportamiento general muestra que el estrato constituye una variable fuertemente asociada con el valor de mercado de las viviendas en la Base 1.

Figura 5: Precio vs Número de habitaciones (Base 1)

base1_hab <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    habitaciones = as.factor(habitaciones)
  ) %>%
  filter(!is.na(preciom), !is.na(habitaciones))


base1_hab <- base1_hab %>%
  mutate(habitaciones = factor(habitaciones, levels = sort(unique(as.numeric(as.character(habitaciones))))))

p_hab <- plot_ly(
  data = base1_hab,
  x = ~habitaciones,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Habitaciones:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de habitaciones (Base 1)",
    xaxis = list(title = "Número de habitaciones"),
    yaxis = list(title = "Precio (millones)")
  )

p_hab

La Figura 5 presenta la distribución del precio según el número de habitaciones en la Base 1. En general, se aprecia una tendencia a que viviendas con mayor número de habitaciones presenten precios más altos, especialmente al comparar categorías intermedias (por ejemplo, entre 3 y 6 habitaciones) con las categorías más bajas. Sin embargo, el gráfico también evidencia una alta dispersión dentro de varias categorías, lo que sugiere que el número de habitaciones por sí solo no determina el precio y que existen otros factores relevantes (como el estrato, el área construida o la ubicación por barrio). Adicionalmente, se observa que la mayor concentración de observaciones se ubica en valores intermedios (alrededor de 3 a 6 habitaciones), mientras que categorías altas (por ejemplo 9 o 10) tienen menos registros y muestran mayor variabilidad. Finalmente, la presencia de registros con 0 habitaciones sugiere una posible inconsistencia o un criterio particular de registro en la base de datos, por lo que esta categoría debe interpretarse con precaución.

n_hab0 <- sum(as.numeric(as.character(base1$habitaciones)) == 0, na.rm = TRUE)
cat("En la Base 1 se identificaron", n_hab0, "registros con habitaciones = 0.\n")
## En la Base 1 se identificaron 20 registros con habitaciones = 0.

Se identificaron 20 registros con habitaciones = 0. Dado que esta categoría no tiene una interpretación directa para viviendas tipo casa y puede reflejar inconsistencias en el registro, en esta etapa exploratoria se conservan para no alterar la composición general de la subbase. No obstante, en la etapa de modelación se considerará un filtro de control de calidad (por ejemplo, habitaciones ≥ 1) para evitar que estos casos afecten la estimación e interpretación del modelo.

Figura 6: Precio vs Número de baños (Base 1)

base1_ban <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    banios = as.factor(banios)
  ) %>%
  filter(!is.na(preciom), !is.na(banios))


base1_ban <- base1_ban %>%
  mutate(banios = factor(banios, levels = sort(unique(as.numeric(as.character(banios))))))

p_ban <- plot_ly(
  data = base1_ban,
  x = ~banios,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Baños:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de baños (Base 1)",
    xaxis = list(title = "Número de baños"),
    yaxis = list(title = "Precio (millones)")
  )

p_ban

La Figura 6 muestra la distribución del precio según el número de baños en la Base 1. En general, se observa una tendencia creciente: las viviendas con mayor número de baños tienden a presentar precios más altos, especialmente al comparar categorías intermedias y altas (por ejemplo, 4–6 baños) frente a categorías bajas (1–2 baños). No obstante, se aprecia variabilidad dentro de cada categoría, lo cual indica que el número de baños está asociado al precio pero no lo determina de manera exclusiva. Adicionalmente, se identifican registros con 0 baños, situación que sugiere una posible inconsistencia o un criterio particular de registro en la base de datos.

n_hab0 <- sum(as.numeric(as.character(base1$banios)) == 0, na.rm = TRUE)
cat("En la Base 1 se identificaron", n_hab0, "registros con baños = 0.\n")
## En la Base 1 se identificaron 10 registros con baños = 0.

Se identificaron 10 registros con baños = 0. Dado que esta categoría no tiene una interpretación directa para viviendas tipo casa y puede reflejar inconsistencias en el registro, en esta etapa exploratoria se conservan para no alterar la composición general de la subbase. No obstante, en la etapa de modelación se considerará un filtro de control de calidad (por ejemplo, baños≥ 1) para evitar que estos casos afecten la estimación e interpretación del modelo.

Figura 7: Precio vs Número de parqueaderos (Base 1)

base1_parq <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    parqueaderos = as.factor(parqueaderos)
  ) %>%
  filter(!is.na(preciom), !is.na(parqueaderos))


base1_parq <- base1_parq %>%
  mutate(parqueaderos = factor(parqueaderos, levels = sort(unique(as.numeric(as.character(parqueaderos))))))

p_parq <- plot_ly(
  data = base1_parq,
  x = ~parqueaderos,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Parqueaderos:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de parqueaderos (Base 1)",
    xaxis = list(title = "Número de parqueaderos"),
    yaxis = list(title = "Precio (millones)")
  )

p_parq

La Figura 7 muestra la distribución del precio según el número de parqueaderos en la Base 1. En general, se observa una tendencia a que las viviendas con mayor número de parqueaderos presenten precios más altos, particularmente al comparar categorías bajas (0–1 parqueadero) con categorías intermedias y altas (por ejemplo, 3 o más). A diferencia de las variables habitaciones y baños, la categoría 0 parqueaderos mantiene una interpretación directa (inmuebles sin estacionamiento), por lo que su presencia resulta coherente dentro de la base y no implica necesariamente una inconsistencia. También se aprecia variabilidad dentro de cada categoría, lo cual sugiere que el número de parqueaderos se asocia con el precio pero convive con otros factores que generan diferencias de valor en el mercado.

En consecuencia, para la etapa de modelación se aplicará un criterio de control de calidad sobre registros con 0 habitaciones o 0 baños (por ejemplo, habitaciones ≥ 1 y banios ≥ 1). En contraste, la categoría 0 parqueaderos se conservará, dado que representa un escenario plausible en el mercado inmobiliario y constituye información relevante para explicar variaciones en el precio.

En síntesis, el análisis exploratorio de la Base 1 sugiere que el precio presenta una asociación positiva con el área construida y con variables relacionadas con el nivel socioeconómico y las características del inmueble. En particular, la distribución por estrato muestra un incremento progresivo del precio al pasar a estratos más altos, mientras que el número de habitaciones, baños y parqueaderos también tiende a relacionarse con precios mayores, aunque con una dispersión considerable dentro de cada categoría. Estos resultados indican que el precio no se explica por una única variable, sino por la combinación de varios atributos físicos y socioeconómicos; por ello, en la siguiente sección se ajustará un modelo de regresión lineal múltiple para evaluar conjuntamente el efecto de estas variables sobre el precio en la Base 1.

5. Modelo de Regresión Lineal Múltiple para Base 1

En esta sección se construye un modelo de regresión lineal múltiple para la Base 1 con el propósito de explicar el comportamiento del precio (preciom) a partir de un conjunto de variables físicas y socioeconómicas del inmueble. A diferencia del análisis exploratorio previo (bivariado), el modelo permite evaluar el efecto de cada variable controlando por las demás, lo cual facilita una interpretación más precisa de los factores asociados al valor de mercado de las viviendas. De acuerdo con el enunciado, se consideran como variables explicativas el área construida (areaconst), el estrato, el número de habitaciones, baños y parqueaderos. Para asegurar consistencia en la estimación, previo al ajuste se aplican criterios de control de calidad sobre registros con valores no interpretables (por ejemplo, habitaciones = 0 o banios = 0), manteniendo categorías plausibles como parqueaderos = 0.

5.1. Ajuste del modelo

A continuación, se presenta el resumen del ajuste del modelo de regresión lineal múltiple para la Base 1. La interpretación se enfoca en el signo y magnitud de los coeficientes estimados, su significancia estadística y la capacidad explicativa del modelo a través del coeficiente de determinación (\(R^2\)).

Figura 8: Ajuste del modelo de regresión lineal múltiple

base1_modelo <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst)),
    estrato = as.numeric(as.character(estrato)),
    habitaciones = as.numeric(as.character(habitaciones)),
    banios = as.numeric(as.character(banios)),
    parqueaderos = as.numeric(as.character(parqueaderos))
  ) %>%
  filter(habitaciones >= 1, banios >= 1) %>%
  na.omit()

modelo1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
              data = base1_modelo)

summary(modelo1)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base1_modelo)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -926.97  -80.56  -16.71   51.84 1077.19 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -251.35629   32.37948  -7.763 2.98e-14 ***
## areaconst       0.78378    0.04642  16.883  < 2e-16 ***
## estrato        86.13688    7.64507  11.267  < 2e-16 ***
## habitaciones    4.11542    4.82613   0.853    0.394    
## parqueaderos   -0.82085    4.32974  -0.190    0.850    
## banios         30.23622    5.91360   5.113 4.11e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 158.1 on 694 degrees of freedom
## Multiple R-squared:  0.654,  Adjusted R-squared:  0.6515 
## F-statistic: 262.3 on 5 and 694 DF,  p-value: < 2.2e-16

El modelo ajustado para la Base 1 fue: \[\widehat{preciom}=-251.36+0.784(\text{areaconst})+86.14(\text{estrato})+4.12(\text{habitaciones}) -0.82(\text{parqueaderos})+30.24(\text{banios})\]

De acuerdo con los resultados, el área construida presenta un efecto positivo y estadísticamente significativo: por cada unidad adicional de área construida, el precio esperado aumenta en aproximadamente 0.784 millones, manteniendo constantes el estrato, el número de habitaciones, baños y parqueaderos (\(p < 0.001\)). De igual forma, el estrato muestra un efecto positivo y altamente significativo: un incremento de un estrato se asocia con un aumento promedio de aproximadamente 86.14 millones en el precio, controlando por el área construida, habitaciones, baños y parqueaderos (\(p < 0.001\)). La variable baños también resulta significativa y positiva: cada baño adicional se asocia con un incremento promedio cercano a 30.24 millones, manteniendo constantes las demás variables del modelo (\(p < 0.001\)).

En contraste, las variables habitaciones \(p = 0.394\) y parqueaderos \(p = 0.850\) no resultaron estadísticamente significativas dentro del modelo, una vez controlado por área, estrato y baños. Esto sugiere que, para esta subbase, su contribución marginal al precio no es claramente diferenciable de cero cuando se consideran simultáneamente las demás covariables, lo cual puede estar relacionado con colinealidad con otras características del inmueble (por ejemplo, viviendas con más habitaciones o parqueaderos también tienden a tener mayor área o mayor estrato).

En términos de capacidad explicativa, el modelo presenta un \(R^2 = 0.654\) y ajustado \(R^2 = 0.652\), lo que indica que aproximadamente el 65% de la variabilidad observada en el precio puede ser explicada por las variables incluidas. Adicionalmente, la prueba global del modelo (estadístico F = 262.3, (\(p < 0.001\)) sugiere que, en conjunto, las covariables incorporadas aportan información relevante para explicar el precio de las viviendas en la Base 1.

5.2. Validación de supuestos

En esta sección se evalúan los supuestos principales del modelo de regresión lineal múltiple ajustado para la Base 1. En particular, se revisa la relación lineal entre predictores y respuesta, la normalidad de los residuos, la homocedasticidad (varianza constante) y la presencia de observaciones influyentes. Esta validación permite determinar si el modelo es adecuado para inferencia y predicción, o si requiere ajustes adicionales.

Figura 9: Diagnóstico de supuestos del modelo de regresión lineal múltiple (Base 1)

par(mfrow = c(2, 2))
plot(modelo1)

La Figura 9 presenta los gráficos diagnósticos estándar para evaluar los supuestos del modelo de regresión lineal múltiple ajustado sobre la Base 1. En el gráfico Residuals vs Fitted se observa que los residuos se distribuyen alrededor de cero sin una curvatura marcada, lo que sugiere que la relación lineal capturada por el modelo es razonable en términos generales. Sin embargo, se aprecia un incremento de la dispersión de los residuos para valores ajustados más altos, lo cual es consistente con un posible comportamiento de varianza no constante.

El gráfico Normal Q–Q muestra que los residuos siguen de manera aproximada la línea teórica en la zona central, pero presentan desviaciones en las colas, lo que indica que la normalidad se cumple de forma parcial (adecuada en el centro y menos precisa en valores extremos). El gráfico Scale–Location refuerza lo anterior, ya que la tendencia ascendente sugiere que la variabilidad de los residuos aumenta con el valor ajustado, lo cual apunta a cierta heterocedasticidad.

Finalmente, el gráfico Residuals vs Leverage permite identificar observaciones con potencial influencia. Aunque la mayoría de puntos presenta leverage bajo, se observan algunos casos con leverage relativamente alto (por ejemplo, el registro señalado como 612), lo que sugiere la presencia de observaciones con mayor peso relativo en el ajuste del modelo. En conjunto, los diagnósticos muestran un comportamiento general coherente con el modelo lineal, junto con señales de desviaciones moderadas asociadas principalmente a la dispersión creciente y a los extremos de la distribución de residuos.

5.3. Predicción del precio de la vivienda solicitada

En esta sección se estima el precio esperado para la vivienda solicitada utilizando el modelo de regresión lineal múltiple ajustado para la Base 1. Dado que la solicitud contempla estrato 4 o 5, se calculan dos predicciones, manteniendo constantes el resto de características del inmueble.

nuevo_4 <- data.frame(
  areaconst = 200,
  estrato = 4,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)

nuevo_5 <- data.frame(
  areaconst = 200,
  estrato = 5,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)

pred_4 <- predict(modelo1, newdata = nuevo_4)
pred_5 <- predict(modelo1, newdata = nuevo_5)

cat("Predicción para estrato 4 (millones):", round(pred_4, 2), "\n")
## Predicción para estrato 4 (millones): 326.06
cat("Predicción para estrato 5 (millones):", round(pred_5, 2), "\n")
## Predicción para estrato 5 (millones): 412.2

Con base en el modelo de regresión lineal múltiple ajustado para la Base 1, se estimó el precio esperado de la vivienda solicitada (casa en Zona Norte, área construida de 200, 4 habitaciones, 2 baños y 1 parqueadero) bajo las dos alternativas de estrato contempladas. Para estrato 4, la predicción del precio es de 326.06 millones, mientras que para estrato 5 la predicción asciende a 412.20 millones. La diferencia entre ambas estimaciones se explica por el efecto positivo del estrato en el modelo, manteniendo constantes las demás características del inmueble. Estas predicciones se utilizarán como referencia para la búsqueda y recomendación de ofertas que se ajusten al presupuesto máximo del cliente (350 millones) en la siguiente sección.

6. Conclusiones y ofertas potenciales (Vivienda 1)

En esta sección se identifican y presentan ofertas potenciales para atender la primera solicitud de vivienda, utilizando como punto de partida la Base 1 (casas ubicadas en la Zona Norte). La búsqueda se realiza aplicando los criterios definidos por el cliente (estrato, número de habitaciones, baños, parqueaderos, área y presupuesto máximo de crédito) y tomando como referencia la predicción de precio obtenida en la sección anterior. Con base en ello, se seleccionan al menos cinco inmuebles que cumplen las condiciones y se muestra su ubicación en un mapa, con el fin de apoyar la recomendación final.

Tabla 12: Top 5 ofertas potenciales (Vivienda 1)

base1_ofertas <- base1 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst)),
    estrato = as.numeric(as.character(estrato)),
    habitaciones = as.numeric(as.character(habitaciones)),
    banios = as.numeric(as.character(banios)),
    parqueaderos = as.numeric(as.character(parqueaderos))
  )

ofertas_filtradas <- base1_ofertas %>%
  filter(
    estrato %in% c(4, 5),
    preciom <= 350,
    habitaciones >= 4,
    banios >= 2,
    parqueaderos >= 1,
    areaconst >= 200
  )


precio_objetivo <- 326.06

ofertas_top5 <- ofertas_filtradas %>%
  mutate(
   
    score = abs(areaconst - 200) +
            15*abs(habitaciones - 4) +
            20*abs(banios - 2) +
            20*abs(parqueaderos - 1) +
            0.5*abs(preciom - precio_objetivo)
  ) %>%
  arrange(score) %>%
  slice(1:5)




tabla_ofertas_top5_full <- ofertas_top5 %>%
  transmute(
    id = id,
    zona = zona,
    tipo = tipo,
    barrio = barrio,
    estrato = estrato,
    areaconst = round(areaconst, 0),
    habitaciones = habitaciones,
    banios = banios,
    parqueaderos = parqueaderos,
    preciom = round(preciom, 2),
    longitud = round(as.numeric(as.character(longitud)), 6),
    latitud  = round(as.numeric(as.character(latitud)), 6)
  )

reactable(
  tabla_ofertas_top5_full,
  columns = list(
    id = colDef(name = "ID", minWidth = 80),
    zona = colDef(name = "Zona", minWidth = 110),
    tipo = colDef(name = "Tipo", minWidth = 90),
    barrio = colDef(name = "Barrio", minWidth = 160),
    estrato = colDef(name = "Estrato", minWidth = 80),
    areaconst = colDef(name = "Área", minWidth = 80),
    habitaciones = colDef(name = "Hab.", minWidth = 70),
    banios = colDef(name = "Baños", minWidth = 70),
    parqueaderos = colDef(name = "Parq.", minWidth = 70),
    preciom = colDef(
      name = "Precio (millones)",
      minWidth = 130,
      format = colFormat(digits = 2)
    ),
    longitud = colDef(name = "Longitud", minWidth = 110),
    latitud = colDef(name = "Latitud", minWidth = 110)
  )
)

La Tabla 12 presenta las cinco ofertas seleccionadas como potenciales para la Vivienda 1, obtenidas a partir del filtrado de la Base 1 (casas en Zona Norte) bajo los criterios del cliente. Todas las opciones se encuentran dentro del presupuesto máximo de 350 millones, con precios entre 320 y 350 millones, y presentan áreas construidas cercanas a la solicitada (entre 200 y 216). En cuanto a características internas, las viviendas cumplen con los mínimos requeridos: 4 o más habitaciones, 2 o más baños y al menos 1 parqueadero. Adicionalmente, las cinco ofertas pertenecen al estrato 5, lo cual es consistente con la solicitud (estrato 4 o 5) y se alinea con la predicción del modelo, que estimó un precio mayor para estrato 5. Finalmente, las ofertas se distribuyen en barrios del norte como La Merced, El Bosque, Vipasa y La Flora, lo que aporta alternativas dentro de distintos sectores de la zona. La Figura 10 muestra la ubicación de las cinco ofertas seleccionadas dentro de la Zona Norte.

Figura 10: Ubicación geográfica de las 5 ofertas seleccionadas (Vivienda 1)

ofertas_map <- ofertas_top5 %>%
  mutate(
    longitud = as.numeric(as.character(longitud)),
    latitud = as.numeric(as.character(latitud))
  ) %>%
  filter(!is.na(longitud), !is.na(latitud))

leaflet(ofertas_map) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 6,
    stroke = TRUE,
    weight = 1,
    fillOpacity = 0.8,
    popup = ~paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Área:</b> ", areaconst,
      "<br><b>Hab:</b> ", habitaciones,
      " | <b>Baños:</b> ", banios,
      " | <b>Parq:</b> ", parqueaderos,
      "<br><b>Precio (millones):</b> ", preciom
    )
  )

En general, los puntos se concentran en el sector norte de la ciudad, lo cual es coherente con el filtro aplicado en la construcción de la Base 1. Esta visualización complementa la recomendación al permitir identificar la distribución espacial de las alternativas propuestas.

En conclusión, a partir de la Base 1 se construyó un modelo de regresión para estimar el precio esperado de la vivienda solicitada y, con base en el presupuesto máximo del cliente, se identificaron cinco ofertas que cumplen los criterios establecidos. En la siguiente sección se repetirá el mismo procedimiento para la segunda solicitud (apartamento en Zona Sur).

7. Construcción y análisis Base 2: Apartamentos Zona Sur

En esta sección se construye y analiza la Base 2, conformada por las ofertas de vivienda correspondientes a apartamentos ubicados en la Zona Sur de la ciudad. Esta subbase se define a partir de la información previamente depurada y constituye el punto de partida para atender la segunda solicitud planteada en la actividad. A partir de ella se desarrollarán las etapas de verificación del filtro, análisis exploratorio, modelación del precio, predicción y selección de ofertas potenciales para el cliente.

7.1. Construcción y análisis Base 2: Apartamentos Zona Sur

Como primera verificación del proceso de selección, se presentan a continuación los tres primeros registros de la Base 2, construida a partir de las observaciones correspondientes a viviendas tipo apartamentos ubicadas en la Zona Sur. Esta visualización permite corroborar de manera preliminar la estructura de la subbase y las variables disponibles para los análisis posteriores.

Tabla 13: Primeros tres registros de la Base 2

base2 <- vivienda %>%
  filter(tipo == "Apartamento", zona == "Zona Sur")

reactable(
  head(base2, 3),
  columns = list(
    id = colDef(name = "ID"),
    zona = colDef(name = "Zona"),
    barrio = colDef(name = "Barrio"),
    estrato = colDef(name = "Estrato"),
    preciom = colDef(name = "Precio (millones)"),
    areaconst = colDef(name = "Área construida"),
    tipo = colDef(name = "Tipo"),
    habitaciones = colDef(name = "Habitaciones"),
    banios = colDef(name = "Baños"),
    parqueaderos = colDef(name = "Parqueaderos"),
    piso = colDef(name = "Piso"),
    longitud = colDef(name = "Longitud"),
    latitud = colDef(name = "Latitud")
  ),
  defaultPageSize = 3,
  showPageSizeOptions = FALSE,
  pagination = FALSE
)

Una vez revisados los primeros registros de la Base 2, es necesario comprobar de manera explícita que el proceso de filtrado se realizó correctamente. Para ello, a continuación se presentan tablas de frecuencia para las variables tipo y zona, con el fin de verificar que todas las observaciones incluidas en esta subbase correspondan exclusivamente a viviendas tipo Casa localizadas en la Zona Norte. Esta validación permite asegurar la consistencia de la partición realizada antes de avanzar hacia el análisis exploratorio y el modelamiento del precio.

Tabla 14: Verificación Base 2 por Tipo de Inmueble

tab_tipo_base2 <- as.data.frame(table(base2$tipo))
names(tab_tipo_base2) <- c("Tipo", "Frecuencia")

reactable(
  tab_tipo_base2,
  columns = list(
    Tipo = colDef(name = "Tipo de inmueble"),
    Frecuencia = colDef(name = "Frecuencia")
  )
)

Tabla 15: Verificación Base 2 por Zona

tab_zona_base2 <- as.data.frame(table(base2$zona))
names(tab_zona_base2) <- c("Zona", "Frecuencia")

reactable(
  tab_zona_base2,
  columns = list(
    Zona = colDef(name = "Zona"),
    Frecuencia = colDef(name = "Frecuencia")
  )
)

Los resultados de las Tablas 14 y 15 confirman que el proceso de filtrado fue realizado correctamente. En particular, la Base 2 quedó conformada por 2787 registros, todos correspondientes a viviendas del tipo Apartamento y localizadas en la Zona Sur. Por tanto, se verifica que la subbase construida es consistente con las condiciones iniciales de la segunda solicitud y resulta adecuada para continuar con el análisis exploratorio y la modelación posterior.

A continuación, la Figura 11 presenta un mapa con la ubicación geográfica de las viviendas incluidas en la Base 2, utilizando las coordenadas de longitud y latitud registradas en el conjunto de datos. Esta visualización permite inspeccionar la distribución espacial de las ofertas seleccionadas y verificar, de manera complementaria, la coherencia entre la clasificación por zona y la ubicación geográfica reportada.

Figura 11: Distribución espacial de las viviendas tipo Apartamento ubicadas en la Zona Sur

base2_map <- base2 %>% filter(!is.na(longitud), !is.na(latitud))

leaflet(base2_map) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 4,
    stroke = TRUE,
    weight = 1,
    fillOpacity = 0.7,
    popup = ~paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Tipo:</b> ", tipo,
      "<br><b>Zona:</b> ", zona,
      "<br><b>Precio (millones):</b> ", preciom,
      "<br><b>Área construida:</b> ", areaconst,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Hab:</b> ", habitaciones,
      " | <b>Baños:</b> ", banios,
      " | <b>Parq:</b> ", parqueaderos
    )
  )

La Figura 11 muestra la distribución espacial de los inmuebles incluidos en la Base 2 (apartamentos ubicados en la Zona Sur). Se observa una alta concentración de puntos en el sector sur–suroccidental de la ciudad, lo cual es consistente con el filtro aplicado para construir esta subbase. No obstante, también aparecen algunos registros dispersos hacia zonas centrales e incluso en áreas más al norte del mapa. Esta situación puede explicarse porque la variable zona corresponde a una clasificación registrada en la base de datos y no necesariamente a una delimitación cartográfica formal con límites estrictos. Adicionalmente, la presencia de puntos aislados puede estar asociada a imprecisiones en las coordenadas de longitud/latitud o a inmuebles localizados cerca de zonas limítrofes. En consecuencia, el mapa se interpreta como una verificación visual complementaria a la clasificación por zona utilizada en el filtrado.

7.2. Análisis exploratorio Base 2

En esta sección se realiza un análisis exploratorio enfocado en la Base 2 con el fin de examinar cómo se relaciona el precio (preciom) con variables físicas y socioeconómicas del inmueble.

Figura 12: Precio vs Área del Inmueble

base2_eda <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst))
  ) %>%
  filter(!is.na(preciom), !is.na(areaconst))

base2_eda <- base2_eda %>%
  mutate(
    hover = paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Área:</b> ", areaconst,
      "<br><b>Precio (millones):</b> ", preciom
    )
  )

p1 <- plot_ly(
  data = base2_eda,
  x = ~areaconst,
  y = ~preciom,
  type = "scatter",
  mode = "markers",
  text = ~hover,
  hoverinfo = "text"
) %>%
  layout(
    title = "Precio vs Área construida (Base 2)",
    xaxis = list(title = "Área construida"),
    yaxis = list(title = "Precio (millones)")
  )

p1

La Figura 12 muestra la relación entre el área construida y el precio para los inmuebles de la Base 2 (apartamentos en Zona Sur). En general, se observa una asociación positiva: a medida que aumenta el área construida, el precio tiende a incrementarse. La mayor concentración de observaciones se ubica en rangos relativamente bajos e intermedios de área, lo cual sugiere que la oferta de apartamentos en esta subbase se compone principalmente de inmuebles de tamaño medio. No obstante, el gráfico evidencia una dispersión importante del precio para áreas similares, lo que indica que el valor del inmueble no depende únicamente del tamaño, sino que está influenciado por otros factores como el estrato y características internas (baños, habitaciones y parqueaderos). Finalmente, se observan algunos apartamentos de área considerablemente mayor que se asocian con precios altos, aunque su frecuencia dentro de la subbase es menor.

Figura 13: Precio vs Estrato socioeconómico (Base 2)

base2_eda2 <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    estrato = as.factor(estrato)
  ) %>%
  filter(!is.na(preciom), !is.na(estrato))

# Boxplot interactivo
p_estrato <- plot_ly(
  data = base2_eda2,
  x = ~estrato,
  y = ~preciom,
  type = "box",
  boxpoints = "all",        
  jitter = 0.35,            
  pointpos = 0,
  hovertemplate = paste(
    "<b>Estrato:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por estrato (Base 2)",
    xaxis = list(title = "Estrato"),
    yaxis = list(title = "Precio (millones)")
  )
p_estrato

La Figura 13 evidencia un patrón claramente creciente del precio conforme aumenta el estrato socioeconómico en la Base 2. Los apartamentos de estrato 3 presentan los valores más bajos y una dispersión relativamente menor, mientras que en estratos 4 y 5 se observa un incremento progresivo tanto en la mediana como en el rango de precios. En estrato 6 se concentra el nivel de precios más alto y también la mayor variabilidad, lo que sugiere un segmento de mercado más heterogéneo en términos de valor. Aunque existe cierto solapamiento entre estratos consecutivos, el comportamiento general indica que el estrato es una variable fuertemente asociada con el precio de los apartamentos en la Zona Sur.

Figura 14: Precio vs Número de habitaciones (Base 2)

base2_hab <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    habitaciones = as.factor(habitaciones)
  ) %>%
  filter(!is.na(preciom), !is.na(habitaciones))


base2_hab <- base2_hab %>%
  mutate(habitaciones = factor(habitaciones, levels = sort(unique(as.numeric(as.character(habitaciones))))))

p_hab <- plot_ly(
  data = base2_hab,
  x = ~habitaciones,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Habitaciones:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de habitaciones (Base 2)",
    xaxis = list(title = "Número de habitaciones"),
    yaxis = list(title = "Precio (millones)")
  )

p_hab

La Figura 14 presenta la distribución del precio según el número de habitaciones en la Base 2. En términos generales, se observa una tendencia a que apartamentos con mayor número de habitaciones presenten precios más altos, especialmente al comparar categorías intermedias (3–4 habitaciones) con categorías altas (5–6 habitaciones). No obstante, el gráfico evidencia una dispersión importante dentro de varias categorías, lo que sugiere que el número de habitaciones está asociado al precio, pero no lo explica por sí solo, dado que intervienen otros factores como el estrato, el área construida y características adicionales del inmueble.

Adicionalmente, se identifican registros con 0 habitaciones, categoría que no tiene una interpretación directa para apartamentos residenciales y que podría reflejar inconsistencias o criterios particulares de registro. Por esta razón, estos casos se conservan en la etapa exploratoria para no alterar la composición de la subbase.

n_hab0 <- sum(as.numeric(as.character(base2$habitaciones)) == 0, na.rm = TRUE)
cat("En la Base 2 se identificaron", n_hab0, "registros con habitaciones = 0.\n")
## En la Base 2 se identificaron 8 registros con habitaciones = 0.

Se identificaron 8 registros con habitaciones = 0 dentro de la Base 2. Dado que esta categoría no tiene una interpretación directa para apartamentos residenciales y puede reflejar inconsistencias en el registro, en esta etapa exploratoria se conservan para no alterar la composición general de la subbase. No obstante, en la etapa de modelación se aplicará un criterio de control de calidad (por ejemplo, habitaciones ≥ 1) para evitar que estos casos afecten la estimación e interpretación del modelo.

Figura 15: Precio vs Número de baños (Base 2)

base2_ban <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    banios = as.factor(banios)
  ) %>%
  filter(!is.na(preciom), !is.na(banios))


base2_ban <- base2_ban %>%
  mutate(banios = factor(banios, levels = sort(unique(as.numeric(as.character(banios))))))

p_ban <- plot_ly(
  data = base2_ban,
  x = ~banios,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Baños:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de baños (Base 2)",
    xaxis = list(title = "Número de baños"),
    yaxis = list(title = "Precio (millones)")
  )

p_ban

La Figura 15 muestra la distribución del precio según el número de baños en la Base 2. En general, se observa una tendencia creciente: los apartamentos con un mayor número de baños tienden a presentar precios más altos, especialmente al comparar categorías bajas (1–2 baños) con categorías intermedias y altas (4–6 baños). No obstante, el gráfico evidencia variabilidad dentro de cada categoría, lo que sugiere que el número de baños se asocia con el precio pero convive con otros factores relevantes (por ejemplo, estrato y área construida) que también influyen en el valor del inmueble.

Adicionalmente, se identifican 6 registros con 0 baños, categoría que no tiene una interpretación directa para apartamentos residenciales y podría reflejar inconsistencias o criterios particulares de registro. Por esta razón, estos casos se conservan en la etapa exploratoria para no alterar la composición general de la subbase, pero en la etapa de modelación se aplicará un criterio de control de calidad (por ejemplo, baños ≥ 1) para evitar que afecten la estimación e interpretación del modelo.

n_hab0 <- sum(as.numeric(as.character(base2$banios)) == 0, na.rm = TRUE)
cat("En la Base 2 se identificaron", n_hab0, "registros con baños = 0.\n")
## En la Base 2 se identificaron 6 registros con baños = 0.

Figura 16: Precio vs Número de parqueaderos (Base 2)

base2_parq <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    parqueaderos = as.factor(parqueaderos)
  ) %>%
  filter(!is.na(preciom), !is.na(parqueaderos))


base2_parq <- base2_parq %>%
  mutate(parqueaderos = factor(parqueaderos, levels = sort(unique(as.numeric(as.character(parqueaderos))))))

p_parq <- plot_ly(
  data = base2_parq,
  x = ~parqueaderos,
  y = ~preciom,
  type = "box",
  boxpoints = "all",
  jitter = 0.35,
  pointpos = 0,
  hovertemplate = paste(
    "<b>Parqueaderos:</b> %{x}<br>",
    "<b>Precio:</b> %{y} mill.<extra></extra>"
  )
) %>%
  layout(
    title = "Distribución del precio por número de parqueaderos (Base 2)",
    xaxis = list(title = "Número de parqueaderos"),
    yaxis = list(title = "Precio (millones)")
  )

p_parq

La Figura 16 presenta la distribución del precio según el número de parqueaderos en la Base 2. En general, se observa una tendencia creciente: los apartamentos con mayor número de parqueaderos tienden a registrar precios más altos, especialmente al comparar categorías bajas (0–1 parqueadero) con categorías intermedias y altas (2–4 parqueaderos). A diferencia de las variables habitaciones y baños, la categoría 0 parqueaderos mantiene una interpretación directa (inmuebles sin estacionamiento), por lo que su presencia es coherente en el análisis exploratorio. Adicionalmente, se aprecia variabilidad dentro de cada categoría, lo cual sugiere que el número de parqueaderos se asocia con el precio, pero el valor final también depende de otras características del inmueble como el estrato y el área construida.

En síntesis, el análisis exploratorio de la Base 2 muestra que el precio se asocia de manera positiva con el área construida y con variables que reflejan el nivel socioeconómico y las características del inmueble. En particular, el estrato presenta un patrón claramente creciente en el precio, y variables como habitaciones, baños y parqueaderos tienden a relacionarse con valores más altos, aunque con dispersión dentro de cada categoría. Estos resultados sugieren que el precio no depende de un único atributo, sino de la combinación de varios factores; por ello, en la siguiente sección se ajustará un modelo de regresión lineal múltiple para evaluar conjuntamente el efecto de estas variables sobre el precio en la Base 2.

8. Modelo de Regresión Lineal Múltiple para Base 2

En esta sección se ajusta un modelo de regresión lineal múltiple para la Base 2 (apartamentos en Zona Sur) con el fin de cuantificar cómo varía el precio (preciom) en función de características estructurales y socioeconómicas del inmueble. A partir de los patrones observados en el análisis exploratorio, se evalúa el aporte conjunto del área construida, el estrato, el número de habitaciones, baños y parqueaderos para explicar el valor de mercado. Este enfoque permite estimar el efecto de cada variable mientras las demás se mantienen constantes, y servirá como base para predecir el precio de la vivienda solicitada en el segundo caso.

8.1. Ajuste del modelo

A continuación, se presenta el resumen del ajuste del modelo de regresión lineal múltiple para la Base 2.

Figura 17: Ajuste del modelo de regresión lineal múltiple

base2_modelo <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst)),
    estrato = as.numeric(as.character(estrato)),
    habitaciones = as.numeric(as.character(habitaciones)),
    banios = as.numeric(as.character(banios)),
    parqueaderos = as.numeric(as.character(parqueaderos))
  ) %>%
  filter(habitaciones >= 1, banios >= 1) %>%
  na.omit()

modelo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
              data = base2_modelo)

summary(modelo2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = base2_modelo)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1222.34   -42.37    -2.02    35.68   936.95 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -216.27172   13.86922 -15.594  < 2e-16 ***
## areaconst       1.42415    0.05059  28.150  < 2e-16 ***
## estrato        56.09880    2.81722  19.913  < 2e-16 ***
## habitaciones  -24.16977    3.53740  -6.833 1.02e-11 ***
## parqueaderos   49.48202    3.15490  15.684  < 2e-16 ***
## banios         50.94729    3.14997  16.174  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 95.12 on 2771 degrees of freedom
## Multiple R-squared:  0.7521, Adjusted R-squared:  0.7517 
## F-statistic:  1682 on 5 and 2771 DF,  p-value: < 2.2e-16

El modelo ajustado para la Base 2 fue: \[\widehat{preciom}=-216.27+1.42(\text{areaconst})+56.10(\text{estrato})-24.17(\text{habitaciones})+49.48(\text{parqueaderos})+50.95(\text{banios})\] donde (preciom) está medido en millones. Los coeficientes representan el cambio esperado en el precio al variar cada predictor, manteniendo constantes las demás variables del modelo.

De acuerdo con los resultados, el área construida presenta un efecto positivo y altamente significativo: por cada unidad adicional de área, el precio esperado aumenta en aproximadamente 1.42 millones (\(p < 0.001\)). El estrato también muestra un efecto positivo y significativo: un incremento de un estrato se asocia con un aumento promedio cercano a 56.10 millones en el precio (\(p < 0.001\)). Asimismo, las variables parqueaderos y baños resultan positivas y significativas: cada parqueadero adicional se asocia con un incremento de aproximadamente 49.48 millones, y cada baño adicional con cerca de 50.95 millones (\(p < 0.001\) en ambos casos).

En contraste, el coeficiente de habitaciones resulta negativo y estadísticamente significativo (\(p < 0.001\)). Esto indica que, una vez controlado por área construida, estrato, baños y parqueaderos, un aumento en el número de habitaciones se asocia con una reducción promedio de aproximadamente 24.17 millones en el precio esperado. Este resultado puede interpretarse como una condición: para un área dada, más habitaciones suelen implicar una mayor subdivisión del espacio, lo que puede estar asociado a configuraciones menos valoradas en ciertos segmentos del mercado, además de posibles relaciones entre variables (por ejemplo, correlación entre área y número de habitaciones).

En términos de capacidad explicativa, el modelo presenta un (\(R^2 = 0.752\)) (ajustado (\(R^2 = 0.752\))), lo que indica que aproximadamente el 75% de la variabilidad observada en el precio puede ser explicada por las variables incluidas. Adicionalmente, la prueba global del modelo (estadístico (F = 1682), (p < 0.001)) confirma que, en conjunto, las covariables incorporadas aportan información relevante para explicar el precio de los apartamentos en la Base 2.

8.2. Validación de supuestos

En esta sección se evalúan los supuestos principales del modelo de regresión lineal múltiple ajustado para la Base 2. En particular, se revisa la relación lineal entre predictores y respuesta, la normalidad de los residuos, la homocedasticidad (varianza constante) y la presencia de observaciones influyentes. Esta validación permite determinar si el modelo es adecuado para inferencia y predicción, o si requiere ajustes adicionales.

Figura 18: Diagnóstico de supuestos del modelo de regresión lineal múltiple (Base 2)

par(mfrow = c(2, 2))
plot(modelo2)

La Figura 18 presenta los gráficos diagnósticos estándar para evaluar los supuestos del modelo de regresión lineal múltiple ajustado sobre la Base 2. En el gráfico Residuals vs Fitted los residuos se distribuyen alrededor de cero sin una curvatura pronunciada, lo que sugiere que la forma lineal del modelo es razonable en términos generales. Sin embargo, se aprecia un aumento de la dispersión de los residuos a medida que crecen los valores ajustados, lo cual es consistente con un comportamiento de varianza no constante.

En el gráfico Normal Q–Q los residuos siguen aproximadamente la línea teórica en la zona central, pero presentan desviaciones en los extremos, lo que indica que la normalidad se cumple de forma parcial, especialmente en las colas. El gráfico Scale–Location refuerza este hallazgo, ya que la tendencia ascendente sugiere un incremento en la variabilidad de los residuos con el valor ajustado, compatible con heterocedasticidad.

Finalmente, el gráfico Residuals vs Leverage permite identificar observaciones con potencial influencia. La mayoría de registros presenta leverage bajo, aunque se observan algunos casos con leverage relativamente alto (por ejemplo, el registro señalado como 2373), lo que sugiere la presencia de observaciones con mayor peso relativo en el ajuste. En conjunto, los diagnósticos muestran un ajuste general coherente con el modelo lineal, con señales de dispersión creciente y desviaciones en los extremos de la distribución de residuos.

8.3. Predicción del precio de la vivienda solicitada

En esta sección se estima el precio esperado para la vivienda solicitada utilizando el modelo de regresión lineal múltiple ajustado para la Base 2. Dado que la solicitud contempla estrato 5 o 6, se calculan dos predicciones, manteniendo constantes el resto de características del inmueble.

nuevo2_5 <- data.frame(
  areaconst = 300,
  estrato = 5,
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)

nuevo2_6 <- data.frame(
  areaconst = 300,
  estrato = 6,
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)

pred2_5 <- predict(modelo2, newdata = nuevo2_5)
pred2_6 <- predict(modelo2, newdata = nuevo2_6)

cat("Predicción para estrato 5 (millones):", round(pred2_5, 2), "\n")
## Predicción para estrato 5 (millones): 671.91
cat("Predicción para estrato 6 (millones):", round(pred2_6, 2), "\n")
## Predicción para estrato 6 (millones): 728

Con base en el modelo de regresión lineal múltiple ajustado para la Base 2, se estimó el precio esperado del apartamento solicitado (Zona Sur, área construida de 300, 5 habitaciones, 3 baños y 3 parqueaderos) para los dos estratos contemplados. Para estrato 5, la predicción del precio es de 671.91 millones, mientras que para estrato 6 la estimación asciende a 728.00 millones. La diferencia entre ambas predicciones se explica por el efecto positivo del estrato en el modelo, manteniendo constantes las demás características del inmueble. Estas estimaciones se utilizarán como referencia para identificar ofertas que cumplan las condiciones del cliente y el presupuesto máximo de 850 millones en la siguiente sección.

9. Conclusiones y Ofertas Potenciales (Vivienda 2)

En esta sección se identifican y presentan ofertas potenciales para atender la segunda solicitud de vivienda, utilizando como punto de partida la Base 2 (apartamentos ubicados en la Zona Sur). La búsqueda se realiza aplicando los criterios definidos por el cliente (estrato, número de habitaciones, baños, parqueaderos, área y presupuesto máximo de crédito) y tomando como referencia la predicción de precio obtenida en la sección anterior para estratos 5 y 6. Con base en ello, se seleccionan al menos cinco inmuebles que cumplen las condiciones y se muestra su ubicación en un mapa, con el fin de respaldar la recomendación final del inmueble más adecuado.

Tabla 16: Top ofertas potenciales (Vivienda 2)

base2_ofertas <- base2 %>%
  mutate(
    preciom = as.numeric(as.character(preciom)),
    areaconst = as.numeric(as.character(areaconst)),
    estrato = as.numeric(as.character(estrato)),
    habitaciones = as.numeric(as.character(habitaciones)),
    banios = as.numeric(as.character(banios)),
    parqueaderos = as.numeric(as.character(parqueaderos))
  )


ofertas2_filtradas <- base2_ofertas %>%
  filter(
    estrato %in% c(5, 6),
    preciom <= 850,
    habitaciones >= 5,
    banios >= 3,
    parqueaderos >= 3,
    areaconst >= 300
  )


precio_objetivo2 <- 671.91

ofertas2_top5 <- ofertas2_filtradas %>%
  mutate(
    score = abs(areaconst - 300) +
      15*abs(habitaciones - 5) +
      20*abs(banios - 3) +
      20*abs(parqueaderos - 3) +
      0.5*abs(preciom - precio_objetivo2)
  ) %>%
  arrange(score) %>%
  slice(1:5)


tabla_ofertas2_top5 <- ofertas2_top5 %>%
  transmute(
    id = id,
    zona = zona,
    tipo = tipo,
    barrio = barrio,
    estrato = estrato,
    areaconst = round(areaconst, 0),
    habitaciones = habitaciones,
    banios = banios,
    parqueaderos = parqueaderos,
    preciom = round(preciom, 2),
    longitud = round(as.numeric(as.character(longitud)), 6),
    latitud  = round(as.numeric(as.character(latitud)), 6)
  )

reactable(
  tabla_ofertas2_top5,
  columns = list(
    id = colDef(name = "ID", minWidth = 80),
    zona = colDef(name = "Zona", minWidth = 110),
    tipo = colDef(name = "Tipo", minWidth = 110),
    barrio = colDef(name = "Barrio", minWidth = 160),
    estrato = colDef(name = "Estrato", minWidth = 80),
    areaconst = colDef(name = "Área", minWidth = 80),
    habitaciones = colDef(name = "Hab.", minWidth = 70),
    banios = colDef(name = "Baños", minWidth = 70),
    parqueaderos = colDef(name = "Parq.", minWidth = 70),
    preciom = colDef(
      name = "Precio (millones)",
      minWidth = 140,
      format = colFormat(digits = 2)
    ),
    longitud = colDef(name = "Longitud", minWidth = 110),
    latitud = colDef(name = "Latitud", minWidth = 110)
  )
)

La Tabla 16 presenta las ofertas identificadas para la Vivienda 2 al aplicar los criterios de búsqueda de manera estricta en la Base 2 (apartamentos en Zona Sur). Bajo estas condiciones, se encontraron únicamente dos inmuebles que cumplen simultáneamente con el presupuesto máximo (≤ 850 millones), estrato permitido (5 o 6), y los mínimos requeridos de habitaciones (≥ 5), baños (≥ 3) y parqueaderos (≥ 3). Ambas alternativas pertenecen al estrato 5 y se ubican en los barrios Seminario y Guadalupe, con precios de 670 y 730 millones, respectivamente. En cuanto al área construida, una de las ofertas coincide exactamente con el valor solicitado (300), mientras que la segunda presenta un área considerablemente mayor (573), manteniéndose dentro del presupuesto definido. Este resultado sugiere que, bajo los requerimientos planteados, la disponibilidad de apartamentos comparables en la muestra es limitada, por lo que estas dos opciones representan las alternativas más cercanas dentro del conjunto de datos analizado.

Figura 19: Ubicación geográfica de las ofertas potenciales seleccionadas (Vivienda 2)

ofertas2_map <- ofertas2_top5 %>%
  mutate(
    longitud = as.numeric(as.character(longitud)),
    latitud  = as.numeric(as.character(latitud))
  ) %>%
  filter(!is.na(longitud), !is.na(latitud))

leaflet(ofertas2_map) %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 6,
    stroke = TRUE,
    weight = 1,
    fillOpacity = 0.8,
    popup = ~paste0(
      "<b>ID:</b> ", id,
      "<br><b>Barrio:</b> ", barrio,
      "<br><b>Estrato:</b> ", estrato,
      "<br><b>Área:</b> ", round(areaconst, 0),
      "<br><b>Hab:</b> ", habitaciones,
      " | <b>Baños:</b> ", banios,
      " | <b>Parq:</b> ", parqueaderos,
      "<br><b>Precio (millones):</b> ", round(preciom, 2)
    )
  )

La Figura 19 presenta la ubicación geográfica de las ofertas potenciales identificadas para la Vivienda 2. Se observa que las alternativas se encuentran dentro del área correspondiente a la Zona Sur, y el mapa permite verificar visualmente su localización aproximada a partir de las coordenadas registradas en la base de datos. Dado que en la Tabla 16 solo se encontraron dos inmuebles que cumplen simultáneamente los criterios definidos (estrato permitido, presupuesto y mínimos de área, habitaciones, baños y parqueaderos), la figura complementa la recomendación al mostrar su distribución espacial y facilitar la comparación territorial entre las opciones seleccionadas.

10. Referencias Bibliográficas

  • Breusch, T. S., & Pagan, A. R. (1979). A simple test for heteroscedasticity and random coefficient variation. Econometrica, 47(5), 1287–1294.
  • James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An Introduction to Statistical Learning: With Applications in R (2nd ed.). Springer.
  • Kutner, M. H., Nachtsheim, C. J., Neter, J., & Li, W. (2004). Applied Linear Statistical Models (5th ed.). McGraw-Hill/Irwin.
  • Sievert, C. (2020). Interactive Web-Based Data Visualization with R, plotly, and shiny. Chapman & Hall/CRC.
  • Cheng, J., Karambelkar, B., & Xie, Y. (2024). leaflet: Create Interactive Web Maps with the JavaScript “Leaflet” Library (R package). CRAN.

Enlaces consultados:

11. Anexos

Librerias en R

library(plotly)
library(DT)
library(reactable)
library(leaflet)
library(reactablefmtr)
library(paqueteMODELOS)
library(htmltools)
library(dplyr)