library(paqueteMODELOS)
library(dplyr)
library(plotly)
library(corrplot)
library(VIM)
library(stringi)
library(stringr)
library(leaflet)
library(lmtest)
library(car)

Introducción al caso

Este informe responde a la solicitud de una compañía internacional interesada en adquirir dos viviendas en la ciudad de Cali.
Se emplean técnicas de regresión lineal múltiple para analizar la relación entre las características de las viviendas y su precio, con el fin de dar recomendaciones basadas en evidencia.

1. Análisis Exploratorio de Datos, limpieza y transformación

Se realiza un análisis exploratorio del conjunto de datos de viviendas en Cali, seguido por la definición de estrategias de limpieza y transformación de los datos, con el objetivo de preparar la base en condiciones óptimas para aplicar un modelo de regresión lineal múltiple.

# Cargar la base de datos
data("vivienda")
df <- vivienda
str(df)

1.1. Resumen estadístico del conjunto de datos

El conjunto de datos contiene 8,322 registros y 13 variables, de las cuales 4 son de tipo texto (character) y 9 son numéricas (numeric). A continuación, se detallan las principales características estadísticas:

summary(df)
glimpse(df)

Variables numéricas

  • ID (id): Identificador numérico de la vivienda, con valores que van desde 1 hasta 8319.
  • Estrato (estrato): Oscila entre 3 y 6, con una mediana de 5. El estrato más frecuente es el 5.
  • Precio en millones (preciom): Rango entre 58 y 1,999, con una media de 433.9 y una mediana de 330. Presenta únicamente 2 valores faltantes.
  • Área construida en m² (areaconst): Varía entre 30 y 1,745, con un promedio de 174.9 y mediana de 123. Tiene 3 valores faltantes.
  • Parqueaderos (parqueaderos): Entre 1 y 10 espacios, con una media de 1.83. Se detectan 1,605 valores faltantes.
  • Baños (banios): Entre 0 y 10, con una media de 3.11 y 3 valores faltantes.
  • Habitaciones (habitaciones): Entre 0 y 10, con una media de 3.61 y 3 valores faltantes.
  • Latitud (latitud): Va de 3.333 a 3.498, con una media de 3.418 y 3 valores faltantes.
  • Longitud (longitud): Va de -76.59 a -76.46, con una media de -76.53 y 3 valores faltantes.

Variables de texto

  • Zona (zona): Variable categórica que clasifica la ubicación en zonas como Zona Oriente, Zona Sur, entre otras. No presenta valores faltantes.
  • Piso (piso): Nivel o planta de la vivienda, con datos faltantes (NA). Se almacena como texto, incluso para valores numéricos.
  • Tipo de vivienda (tipo): Clasifica la propiedad en categorías como Casa, Apartamento, etc. No presenta valores faltantes.
  • Barrio (barrio): Nombre del barrio o sector. No presenta valores faltantes.

1.2. Distribución de variables numéricas

Se visualizan las distribuciones de Precio en millones (preciom), Área construida en m² (areaconst), Parqueaderos (parqueaderos), Baños (banios) y Habitaciones (habitaciones).

Distribución del precio de las viviendas:

Se construyó un histograma interactivo que muestra la dispersión de los precios en millones de pesos.

plot_ly(df, x = ~preciom, type = "histogram") %>%
  layout(title = list(text = "Distribución del Precio de las Viviendas (millones)"))

El resultado evidencia que la mayoría de las propiedades se concentran en precios cercanos a los 300–400 millones, mientras que existen valores significativamente superiores que corresponden a propiedades de lujo, lo cual genera una cola larga hacia la derecha (asimetría positiva).

Distribución del área construida en m²:

El histograma de área muestra que la mayor parte de las viviendas se ubican entre 50 y 200 m². Sin embargo, aparecen valores extremos (superiores a 1.000 m²) asociados a casas de gran tamaño. Esta variable también presenta asimetría positiva y potenciales outliers.

plot_ly(df, x = ~areaconst, type = "histogram") %>%
  layout(title = "Distribución del Área construida (m²)")

Distribución del número de parqueaderos: La gráfica refleja que la gran mayoría de viviendas tiene 1 o 2 parqueaderos, con muy pocos casos que superan los 4. El patrón indica una concentración fuerte y algunos registros aislados en los valores más altos.

plot_ly(df, x = ~parqueaderos, type = "histogram") %>%
  layout(title = "Distribución del Número de parqueaderos")

Distribución del número de baños:

Se observa que las viviendas suelen contar con 2 o 3 baños, siendo menos frecuente encontrar propiedades con 5 o más. La distribución presenta sesgo hacia la derecha, lo que refleja que la mayoría de los casos tienen un número limitado de baños.

plot_ly(df, x = ~banios, type = "histogram") %>%
  layout(title = "Distribución del Número de baños")

Distribución del número de habitaciones:

El histograma muestra que lo más común es encontrar entre 3 y 4 habitaciones por vivienda. También se identifican viviendas con hasta 10 habitaciones, lo cual constituye un caso atípico respecto al patrón central.

plot_ly(df, x = ~habitaciones, type = "histogram") %>%
  layout(title = "Distribución del Número de habitaciones")

1.3. Detección de valores atípicos

La detección de valores atípicos se realiza mediante boxplots para todas las variables numéricas.
Adicionalmente, se aplica la regla del rango intercuartílico (IQR):
\[ \text{Atípico si } x < Q1 - 1.5 \times IQR \quad \text{o} \quad x > Q3 + 1.5 \times IQR \]

numeric_vars <- c("preciom","areaconst","parqueaderos","banios","habitaciones")

plots <- lapply(numeric_vars, function(var){
  plot_ly(df, y = df[[var]], type = "box", name = var) %>%
    layout(title = paste("Boxplot de", var))
})

subplot(plots, nrows = 2, margin = 0.07, shareY = FALSE, titleX = TRUE, titleY = TRUE) %>%
  layout(title = "Boxplots de variables numéricas")
outlier_count <- function(x){
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  lower <- Q1 - 1.5*IQR
  upper <- Q3 + 1.5*IQR
  sum(x < lower | x > upper, na.rm = TRUE)
}

data.frame(
  Variable = numeric_vars,
  Outliers = sapply(df[numeric_vars], outlier_count)
)
  • Precio de las viviendas (preciom): se detectaron 552 valores atípicos, correspondientes principalmente a viviendas de lujo con precios muy superiores al promedio. Estos casos generan una fuerte asimetría en la distribución.
  • Área construida (areaconst): se identificaron 382 outliers, vinculados a propiedades con áreas extraordinariamente grandes (superiores a 1.000 m²), que distorsionan la concentración central de los datos.
  • Número de parqueaderos (parqueaderos): presenta 567 registros atípicos, asociados a viviendas con más de 4 parqueaderos, lo cual es poco frecuente en la oferta inmobiliaria.
  • Número de baños (banios): únicamente 72 outliers, que corresponden a propiedades con 7 o más baños. Aunque son menos frecuentes, reflejan casos de viviendas de gran tamaño.
  • Número de habitaciones (habitaciones): la variable con mayor cantidad de atípicos (888 casos), debido a viviendas con más de 7 habitaciones, que se apartan del patrón central (3 a 4 habitaciones).

1.4. Distribución de variables categóricas

Frecuencia por tipo de vivienda (tipo)

La gráfica de barras confirma que la base está dominada por Apartamentos, seguidos de Casas, lo que refleja la oferta inmobiliaria más activa en Cali.

df %>%
  count(tipo) %>%
  plot_ly(x = ~tipo, y = ~n, type = "bar") %>%
  layout(title = "Frecuencia por Tipo de Vivienda")

Frecuencia por zona (zona)

Las viviendas se concentran principalmente en la Zona Sur y Zona Norte, mientras que las demás zonas muestran una participación menor. Esto sugiere que la dinámica inmobiliaria es más intensa en esas áreas de la ciudad.

df %>%
  count(zona) %>%
  plot_ly(x = ~zona, y = ~n, type = "bar") %>%
  layout(title = "Frecuencia por Zona")

Frecuencia por estrato (estrato)

El gráfico de barras revela que el estrato 5 es el más frecuente en el conjunto de datos, seguido por los estratos 4 y 6. Esto implica que la mayoría de la oferta se concentra en los estratos medio-altos de la ciudad.

df %>%
  count(estrato) %>%
  mutate(estrato = factor(estrato, levels = 3:6)) %>%
  plot_ly(x = ~estrato, y = ~n, type = "bar") %>%
  layout(
    title = "Frecuencia por Estrato",
    xaxis = list(title = "Estrato", tickmode = "array", tickvals = 3:6)
  )

1.5. Análisis bivariado

En esta sección se exploran las relaciones entre las variables independientes y la variable de interés Precio de la vivienda (preciom).

1.5.2. Correlaciones entre variables numéricas

Se calcula la matriz de correlaciones de Pearson entre variables numéricas.

num_data <- df %>% select(preciom, areaconst, parqueaderos, banios, habitaciones, estrato) %>% na.omit()
cor_matrix <- cor(num_data)

corrplot(cor_matrix, method = "color", type = "upper", tl.col = "black", tl.srt = 45,
         title = "Matriz de correlaciones", mar = c(0,0,1,0))

La matriz de correlaciones permitió identificar las relaciones lineales entre las variables numéricas del conjunto de datos. Se observó que:
- El precio (preciom) presenta una correlación positiva fuerte con el área construida (areaconst), lo que confirma que a mayor área, mayor valor de la propiedad.
- Variables como número de baños (banios) y habitaciones (habitaciones) también muestran correlaciones positivas moderadas con el precio, indicando que estas características influyen en la valorización.
- El estrato mantiene una correlación positiva con el precio, aunque más baja, lo cual es consistente con la lógica socioeconómica: viviendas en estratos más altos suelen tener mayor valor.
- El número de parqueaderos exhibe correlación positiva débil, lo que sugiere que su aporte al precio existe pero no es tan determinante como el área o los baños.

1.5.2. Relaciones entre variables numéricas y el precio

Se visualizan diagramas de dispersión con ajuste de línea de tendencia para identificar patrones lineales o no lineales.

num_vars <- c("areaconst", "parqueaderos", "banios", "habitaciones", "estrato")

plots_num <- lapply(num_vars, function(var){
  plot_ly(df, x = df[[var]], y = df$preciom, type = "scatter", mode = "markers",
          marker = list(opacity = 0.5, size = 6),
          name = var) %>%
    layout(title = paste("Precio vs", var),
           xaxis = list(title = var),
           yaxis = list(title = "Precio (millones)"))
})

subplot(plots_num, nrows = 3, margin = 0.07, shareY = TRUE, titleX = TRUE, titleY = TRUE) %>%
  layout(title = "Precio Vs. Variables numéricas")

Los diagramas de dispersión evidenciaron que:
- La relación más clara se da entre área construida y precio, con una tendencia ascendente aunque con presencia de outliers (casas muy grandes con precios atípicos).
- En parqueaderos vs precio, la nube de puntos se concentra en 1 y 2 parqueaderos, con ligeras diferencias de precio, lo cual confirma que no es un factor de gran impacto.
- La relación de baños y habitaciones con el precio también muestra una tendencia ascendente: a mayor número de baños y habitaciones, el precio tiende a ser más alto, aunque con alta dispersión.
- Con respecto al estrato, los precios aumentan en promedio conforme sube el estrato, aunque los datos muestran traslape, es decir, viviendas de estrato 4 pueden alcanzar precios cercanos a las de estrato 5 o 6.

1.5.3. Relación entre variables categóricas y precio

Se analizan las diferencias en el precio medio de las viviendas según categorías como tipo de vivienda, zona y estrato.

cat_vars <- c("tipo","zona","estrato")

plots_cat <- lapply(cat_vars, function(var){
  plot_ly(df, x = df[[var]], y = df$preciom, type = "box",
          boxpoints = "outliers",
          name = var) %>%
    layout(title = paste("Precio por", var),
           yaxis = list(title = "Precio (millones)"))
})

subplot(plots_cat, nrows = 2, margin = 0.07, shareY = TRUE, titleX = TRUE, titleY = TRUE) %>%
  layout(title = "Precio Vs. Variables categóricas")

Los boxplots permitieron comparar el precio de las viviendas según las categorías:
- Tipo de vivienda (tipo): los Apartamentos tienden a concentrarse en precios medios, mientras que las Casas presentan mayor variabilidad, incluyendo propiedades de alto valor.
- Zona (zona): la Zona Sur y la Zona Norte concentran los precios más altos, mientras que otras zonas (Centro y Oriente) se asocian con viviendas de menor valor en promedio.
- Estrato (estrato): los precios aumentan de forma progresiva en los estratos más altos, confirmando la expectativa de que el nivel socioeconómico es un determinante importante del valor de la vivienda.

1.6. Limpieza y transformación de datos

Con la información obtenida en el análisis exploratorio, el siguiente paso es preparar la base de datos para la implementación del modelo de regresión logística multivariado.
El tratamiento de valores faltantes mediante imputación diferenciada, la corrección de atípicos mediante winsorización selectiva y la homogenización de categorías asegurarán que la base resultante sea consistente, completa y adecuada para el modelado estadístico.

1.6.1. Tratamiento de valores faltantes

El análisis exploratorio reveló problemas de diferente magnitud en los datos faltantes:

  • Parqueaderos: presenta una gran cantidad de valores ausentes (~20%).
  • Baños, habitaciones y área construida: tienen faltantes muy reducidos (<0.1%).
  • Precio, estrato y coordenadas (latitud, longitud): solo presentan casos aislados sin información.

Dado lo anterior, se adoptan las siguientes estrategias:

  1. Parqueaderos (parqueaderos): se aplicará el método de imputación k-Nearest Neighbors (k-NN), utilizando los 5 vecinos más cercanos en características como área, estrato y número de habitaciones. Esto permite conservar la variabilidad real y evita sesgos que produciría una imputación simple.
  2. Banios, habitaciones y areaconst: se imputarán mediante la mediana, dado que los faltantes son escasos y esta medida es robusta frente a la presencia de atípicos.
  3. Precio, estrato y coordenadas: dado el bajo volumen de faltantes, se imputarán con la mediana (numéricas) o con la categoría más frecuente (categóricas), asegurando consistencia sin comprometer el análisis.
# Tratamiento de valores faltantes
df_clean <- df

# 1. Imputación con kNN para parqueaderos
df_clean <- kNN(df_clean, variable = "parqueaderos", k = 5, imp_var = FALSE)

# 2. Imputación con mediana para variables con pocos NA
impute_median <- function(x){
  x[is.na(x)] <- median(x, na.rm = TRUE)
  return(x)
}

df_clean$banios <- impute_median(df_clean$banios)
df_clean$habitaciones <- impute_median(df_clean$habitaciones)
df_clean$areaconst <- impute_median(df_clean$areaconst)
df_clean$preciom <- impute_median(df_clean$preciom)
df_clean$estrato <- impute_median(df_clean$estrato)
df_clean$latitud <- impute_median(df_clean$latitud)
df_clean$longitud <- impute_median(df_clean$longitud)

# 3. Imputación con moda para variables categóricas
impute_mode <- function(x){
  ux <- unique(x[!is.na(x)])
  mode_val <- ux[which.max(tabulate(match(x, ux)))]
  x[is.na(x)] <- mode_val
  return(x)
}

df_clean$zona <- impute_mode(df_clean$zona)
df_clean$tipo <- impute_mode(df_clean$tipo)
df_clean$barrio <- impute_mode(df_clean$barrio)
df_clean$piso <- impute_mode(df_clean$piso)

1.6.2. Tratamiento de valores atípicos

El análisis mediante boxplots e IQR permitió identificar una cantidad significativa de valores extremos:

  • Precio (preciom): 552 outliers, correspondientes a viviendas de lujo.
  • Área construida (areaconst): 382 outliers, asociados a casas muy grandes.
  • Parqueaderos (parqueaderos): 567 outliers, con más de 4 parqueaderos.
  • Baños (banios): 72 outliers, en viviendas con 7 o más baños.
  • Habitaciones (habitaciones): 888 outliers, en viviendas con más de 7 habitaciones.

El tratamiento propuesto será diferenciado:

  1. Outliers plausibles (viviendas de lujo o de gran tamaño): se mantendrán, ya que reflejan la realidad del mercado inmobiliario y son información valiosa para el modelo.
  2. Valores extremos poco representativos o erróneos: se aplicará un proceso de winsorización (reemplazo por el valor máximo/mínimo permitido según IQR) para reducir su impacto sin eliminarlos del todo.
  3. Variables discretas (baños, habitaciones, parqueaderos): se revisará la coherencia de registros extremos (ejemplo: viviendas con 0 baños o 10 habitaciones). Aquellos que no correspondan a casos plausibles serán corregidos o eliminados.
# Winsorización de valores atípicos usando IQR
cap_outliers <- function(x){
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  lower <- Q1 - 1.5 * IQR
  upper <- Q3 + 1.5 * IQR
  x[x < lower] <- lower
  x[x > upper] <- upper
  return(x)
}

# Aplicar winsorización a variables numéricas relevantes
df_clean$preciom <- cap_outliers(df_clean$preciom)
df_clean$areaconst <- cap_outliers(df_clean$areaconst)
df_clean$parqueaderos <- cap_outliers(df_clean$parqueaderos)
df_clean$banios <- cap_outliers(df_clean$banios)
df_clean$habitaciones <- cap_outliers(df_clean$habitaciones)

1.6.3. Homogeneización de variables categóricas

Las variables categóricas presentan variaciones en su escritura que deben unificarse para evitar fragmentación de categorías:

  • Zona y barrio: se normalizarán a minúsculas, corrigiendo tildes y eliminando variaciones ortográficas.
  • Tipo de vivienda: se mantendrá en categorías consistentes (Casa, Apartamento), corrigiendo abreviaturas o errores de digitación.
  • Estrato: se tratará como variable numérica discreta y se validará que solo contenga valores entre 3 y 6.
# PASO 1: Limpieza de texto (minúsculas, sin tildes)
df_clean <- df_clean %>%
  mutate(
    zona   = tolower(trimws(zona)),
    barrio = tolower(trimws(barrio)),
    tipo   = tolower(trimws(tipo)),
    piso   = tolower(trimws(piso))
  ) %>%
  mutate(
    zona   = stri_trans_general(zona, "Latin-ASCII"),
    barrio = stri_trans_general(barrio, "Latin-ASCII"),
    tipo   = stri_trans_general(tipo, "Latin-ASCII")
  )

# Conversión a factor
df_clean$zona <- as.factor(df_clean$zona)
df_clean$barrio <- as.factor(df_clean$barrio)
df_clean$tipo <- as.factor(df_clean$tipo)
df_clean$estrato <- factor(df_clean$estrato, levels = 3:6, ordered = TRUE)

1.6.4. Validación y Corrección Geográfica de Zonas

Un paso fundamental para la fiabilidad del modelo es asegurar que la zona asignada a cada vivienda sea coherente con sus coordenadas geográficas (latitud, longitud). La ubicación es uno de los factores que más influye en el precio de una propiedad, por lo que una clasificación incorrecta introduciría un sesgo significativo en el análisis y en las futuras predicciones.

Para verificar la consistencia de los datos, se implementó una estrategia en cuatro pasos:

  1. Definir Límites Geográficos:
    En ausencia de límites oficiales geoespaciales para las zonas de Cali, se establecieron polígonos aproximados (cajas delimitadoras o bounding boxes) basados en el conocimiento general de la ciudad y sus principales arterias viales y características geográficas (como la Calle 5, el corredor del río Cauca, etc.). Esta es una aproximación heurística y funcional para validar los datos.

  2. Reclasificar las Viviendas:
    Se creó una nueva columna temporal, zona_verificada, asignando a cada propiedad una zona según sus coordenadas de latitud y longitud.

  3. Comparar y Visualizar:
    Para cuantificar las inconsistencias, se generó una tabla de contingencia que cruza la zona original con la zona_verificada. Adicionalmente, se creó un mapa interactivo para visualizar geográficamente las propiedades mal clasificadas.

  4. Corregir los Datos:
    Finalmente, se tomó la decisión de reemplazar los valores de la columna zona original por los de la zona_verificada, que son geográficamente más precisos.

# 1. & 2. Definir límites y reclasificar
# Nota: Estos límites son aproximaciones para fines de validación.
df_clean <- df_clean %>%
  mutate(
    zona_verificada = case_when(
      # Zona Sur: al sur de la Calle 5 y al oeste del corredor del río Cauca
      latitud < 3.40 & longitud > -76.55 ~ "zona sur",
      # Zona Norte: al norte de la Calle 34
      latitud > 3.47 & longitud < -76.48 & longitud > -76.55 ~ "zona norte",
      # Zona Oeste: al oeste de la Carrera 15
      longitud < -76.545 & latitud > 3.40 & latitud < 3.47 ~ "zona oeste",
      # Zona Centro: polígono central
      latitud >= 3.44 & latitud <= 3.46 & longitud >= -76.54 & longitud <= -76.52 ~ "zona centro",
      # Zona Oriente: al este del centro
      longitud > -76.51 ~ "zona oriente",
      TRUE ~ "sin clasificar" # Default para revisión
    )
  )

# 3. Comparar con una tabla de contingencia
contingencia <- table(Original = df_clean$zona, Verificada = df_clean$zona_verificada)
kable(contingencia, caption = "Tabla de Contingencia: Zona Original vs. Zona Verificada")
Tabla de Contingencia: Zona Original vs. Zona Verificada
sin clasificar zona centro zona norte zona oeste zona oriente zona sur
zona centro 70 46 3 0 4 1
zona norte 371 164 1010 45 208 122
zona oeste 250 117 17 778 25 11
zona oriente 107 11 8 4 198 23
zona sur 978 157 41 327 182 3044

La tabla de contingencia muestra inconsistencias críticas.
Por ejemplo, se observa que 515 viviendas etiquetadas originalmente como zona sur en realidad se ubican geográficamente en la zona oeste. De igual manera, 306 propiedades de la zona oeste original pertenecen a la zona sur.

Esta es la discrepancia más notable y sugiere un error sistemático en el etiquetado inicial de los datos, el cual debe ser corregido para no afectar el modelo.

El siguiente mapa visualiza estas y otras discrepancias.

# Filtrar solo los registros con discrepancias para visualizarlos
discrepancias <- df_clean %>%
  filter(zona != zona_verificada & zona_verificada != "sin clasificar")

# Crear paleta de colores para el mapa
pal <- colorFactor(palette = "viridis", domain = df_clean$zona)

leaflet(data = discrepancias) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
                   "<b>Zona Original:</b>", zona, "<br>",
                   "<b>Zona Verificada:</b>", zona_verificada),
    color = ~pal(zona), # Colorear por la zona original para ver el error
    stroke = FALSE,
    fillOpacity = 0.7
  ) %>%
  addLegend("bottomright", pal = pal, values = ~zona,
    title = "Zona Original (Incorrecta)",
    opacity = 1
  ) %>%
  setView(lng = -76.5225, lat = 3.43722, zoom = 12)

El análisis confirma que la variable zona original contiene errores de clasificación significativos que la hacen poco fiable.
Para garantizar la integridad del modelo, se procede a corregir la columna zona usando la zona_verificada como fuente de verdad.

# 4. Corregir la variable 'zona' y preparar para comparación
zona_original <- df %>% 
  filter(id %in% df_clean$id) %>% # Asegurar que comparamos los mismos registros
  count(zona, name = "conteo") %>% 
  mutate(Version = "1. Original")

df_clean <- df_clean %>%
  filter(zona_verificada != "sin clasificar") %>%
  mutate(zona = as.factor(zona_verificada)) %>%
  select(-zona_verificada)

zona_corregida <- df_clean %>% 
  count(zona, name = "conteo") %>% 
  mutate(Version = "2. Corregida")

# Combinar ambos conteos para un gráfico comparativo
zonas_comparacion <- bind_rows(zona_original, zona_corregida)

# Gráfico comparativo
plot_ly(zonas_comparacion, x = ~zona, y = ~conteo, color = ~Version, type = 'bar') %>%
  layout(
    title = "<b>Comparación de Distribución por Zona (Antes y Después)</b>",
    yaxis = list(title = "Número de Viviendas"),
    xaxis = list(title = "Zona"),
    barmode = 'group',
    legend = list(orientation = 'h', xanchor = "center", x = 0.5, y = -0.2)
  )

El gráfico comparativo muestra claramente el impacto de esta corrección en la distribución de las viviendas:
se observa un aumento drástico en el número de registros para la zona oeste (barra naranja) y una disminución proporcional en la zona sur (barra azul).

Este rebalanceo asegura que el modelo aprenderá de la ubicación geográfica real, lo que aumentará su precisión y la fiabilidad de sus predicciones.

# Aplicar conversión al df_clean final.
df_clean$tipo <- as.factor(df_clean$tipo)
df_clean$estrato <- factor(df_clean$estrato, levels = 3:6, ordered = TRUE)
# La variable barrio se puede omitir para el modelo si tiene alta cardinalidad
# df_clean$barrio <- as.factor(df_clean$barrio)

2. Asesoría para la Compra de Viviendas

Con los datos limpios y validados, procedemos a construir un modelo predictivo para estimar el valor de las viviendas y, con base en él, asesorar a María en la solicitud de la compañía internacional.

2.1. Estimación del Modelo de Regresión Lineal Múltiple

Para obtener predicciones más robustas y cumplir con los supuestos de la regresión lineal, se tomaron dos decisiones clave para el modelado:

  • Transformación de la Variable Respuesta: Como se observó en el EDA, la variable preciom tiene una fuerte asimetría positiva.Para corregir esto y estabilizar la varianza, se modelará el logaritmo natural del precio (log(preciom)).
    Las predicciones se convertirán de nuevo a la escala original (millones de pesos) al final del proceso.

  • Inclusión de Variables Categóricas: El análisis bivariado demostró que el tipo de vivienda y la zona (corregida) son determinantes importantes del precio. Por lo tanto, se incluirán en el modelo junto con las variables solicitadas para aumentar su poder predictivo.

El modelo a estimar es:

\[ \log(\text{precio}) = \beta_0 + \beta_1 \cdot \text{areaconst} + \beta_2 \cdot \text{estrato} + \beta_3 \cdot \text{habitaciones} + \beta_4 \cdot \text{parqueaderos} + \dots \]

El modelo de regresión lineal múltiple con variable dependiente transformada (log(preciom)) mostró lo siguiente:

  • Área construida: coeficiente positivo y estadísticamente significativo, confirma que a mayor área, mayor precio.
  • Estrato: coeficientes crecientes y significativos, evidencian que el estrato socioeconómico eleva el valor de la propiedad.
  • Número de habitaciones y baños: aportes positivos y significativos; más cuartos/baños incrementan el precio esperado.
  • Parqueaderos: impacto positivo, aunque menor.
  • Tipo y zona: diferencias claras de precio entre casas/apartamentos y entre zonas.
    El modelo estimado tiene un R² Ajustado de 0.85, lo que indica que explica aproximadamente el 85 % de la variabilidad en el precio, demostrando un buen ajuste a los datos.

2.2. Validación de supuestos del modelo

Se revisan los supuestos clásicos:

  • Normalidad de los residuos
  • Homoscedasticidad (varianza constante)
  • Independencia de los errores
  • Multicolinealidad

Al revisar los supuestos del modelo se encontraron los siguientes puntos:

  • Los residuos (los “errores” del modelo) no siguen exactamente una distribución normal, aunque la gran cantidad de datos hace que esto no sea un problema grave para el análisis.
  • Se detectó que la variabilidad de los errores no es constante: en algunas partes del modelo los errores se dispersan más que en otras. Esto significa que el modelo podría ser menos preciso en ciertos rangos de precios.
  • No se encontraron señales importantes de que las variables se estén repitiendo o duplicando información entre ellas (no hay multicolinealidad fuerte).
  • Tampoco se observan problemas de dependencia entre las observaciones, ya que cada vivienda es independiente de las demás.

Teniendo en cuenta lo anterior, el modelo tiene algunas limitaciones (principalmente en la variabilidad de los errores), pero sigue siendo confiable para hacer predicciones y orientar la toma de decisiones. Para mejorar, en un futuro se podrían usar ajustes que hagan el modelo más robusto frente a estas variaciones.

3. Asesoría para la Compra de Vivienda 1

3.1. Perfil y Predicción de Precio

La primera solicitud corresponde a una Casa en la Zona Norte con las siguientes características y un crédito pre-aprobado de $350 millones:

  • Área Construida: 200 m²
  • Parqueaderos: 1
  • Baños: 2
  • Habitaciones: 4
  • Estrato: 4 o 5

Utilizando nuestro modelo, estimamos el precio para ambas opciones de estrato.

3.2. Ofertas potenciales Vivienda 1

El análisis de la base de datos permitió identificar propiedades con características similares dentro del rango de precio establecido.
Se destacan casas en la Zona Norte cuyo precio no sobrepasa los $350 millones, mostrando opciones viables para ser consideradas como alternativas de compra.

pal1 <- colorFactor("Blues", domain = ofertas1$zona)

leaflet(data = ofertas1) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
                   "<b>Área:</b>", areaconst, "m²<br>",
                   "<b>Habitaciones:</b>", habitaciones,
                   " | <b>Baños:</b>", banios),
    color = ~pal1(zona),
    stroke = FALSE,
    fillOpacity = 0.7
  ) %>%
  addLegend("bottomright", pal = pal1, values = ~zona,
            title = "Ofertas Vivienda 1",
            opacity = 1
  ) %>%
  fitBounds(~min(longitud), ~min(latitud), ~max(longitud), ~max(latitud))

4. Asesoría para la Compra de Vivienda 2

4.1. Perfil y Predicción de Precio

La segunda solicitud corresponde a un Apartamento en la Zona Sur con las siguientes características y un crédito pre-aprobado de $850 millones:

  • Área Construida: 300 m²
  • Parqueaderos: 3
  • Baños: 3
  • Habitaciones: 5
  • Estrato: 5 o 6

Utilizando nuestro modelo, estimamos el precio para ambas opciones de estrato.

4.2. Ofertas potenciales Vivienda 2

Se identificaron apartamentos en la Zona Sur con precios menores o iguales a $850 millones.
Estas alternativas permiten respaldar la decisión de compra con base en evidencia cuantitativa, asegurando que la compañía evalúe viviendas que se ajustan a su capacidad de inversión.

pal2 <- colorFactor("Reds", domain = ofertas2$zona)

leaflet(data = ofertas2) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(
    ~longitud, ~latitud,
    popup = ~paste("<b>Precio:</b>", preciom, "M<br>",
                   "<b>Área:</b>", areaconst, "m²<br>",
                   "<b>Habitaciones:</b>", habitaciones,
                   " | <b>Baños:</b>", banios),
    color = ~pal2(zona),
    stroke = FALSE,
    fillOpacity = 0.7
  ) %>%
  addLegend("bottomright", pal = pal2, values = ~zona,
            title = "Ofertas Vivienda 2",
            opacity = 1
  ) %>%
  fitBounds(~min(longitud), ~min(latitud), ~max(longitud), ~max(latitud))

5. Conclusiones y Recomendaciones

  • El modelo de regresión lineal múltiple utilizado demostró un buen nivel de ajuste, explicando una parte importante de la variabilidad en el precio de las viviendas.
  • La zona geográfica y el estrato socioeconómico resultaron variables determinantes en la formación de precios, reforzando la necesidad de haber corregido los errores de clasificación detectados en la base original.
  • En el caso de la Vivienda 1, las opciones más convenientes se concentran en estrato 4, dentro del rango del crédito aprobado.
  • Para la Vivienda 2, las alternativas más realistas corresponden a apartamentos de estrato 5, ya que en estrato 6 el mercado ofrece principalmente propiedades de lujo que exceden el presupuesto.
  • El análisis respalda que las decisiones de compra deben basarse en una evaluación integral que combine características estructurales, ubicación y precio.