Índice

  1. Descripción de la actividad

  2. Objetivo

  3. Filtrado inicial y depuración de datos

  4. Análisis exploratorio de datos

  5. Estimación e interpretación del modelo

  6. Validación de supuestos

  7. Estudio para los apartamento del sur

8 Discusión de resultados

  1. Anexos

1. Descripción de la actividad

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:

library(paqueteMODELOS)
library(scales)
library(dplyr)
library(ggplot2)
library(kableExtra)
library(knitr)


Caracteristicas <- c("Tipo","área construida","parqueaderos","baños","habitaciones","estrato","zona","crédito preaprobado")
Vivienda_1 <- c("Casa","200","1","2","4","4 o 5","Norte","350 millones")
Vivienda_2 <-c("Apartamento","300","3","3","5","5 o 6","Sur","850 millones")


Tabla_enunciado <-data.frame(Caracteristicas,Vivienda_1,Vivienda_2)

colnames(Tabla_enunciado) <-c("Característica","Vivienda 1","Vivienda 2")

knitr::kable(Tabla_enunciado,
             booktabs = TRUE,
             caption = "<center><b><span style='font-size:18px'> Inmuebles de interés </span></b></center>",
) %>%
  kable_styling(full_width = FALSE)
Inmuebles de interés
Característica Vivienda 1 Vivienda 2
Tipo Casa Apartamento
área construida 200 300
parqueaderos 1 3
baños 2 3
habitaciones 4 5
estrato 4 o 5 5 o 6
zona Norte Sur
crédito preaprobado 350 millones 850 millones

2. Objetivo

Proporcionar un análisis sustentado mediante técnicas modelación para aconsejar a los ejecutivos en la toma de desiciones en el sector inmobiliario.

3. Filtrado inicial y depuración de datos

Los datos proporcionados para realizar el estudio se presentan a continuación:

knitr::kable(vivienda[1:3,],
             booktabs = TRUE,
             format = "html",
             caption = "<center><b><span style='font-size:18px'> Datos de oferta inmobiliaria </span></b></center>",
) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), 
                full_width = FALSE)
Datos de oferta inmobiliaria
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.51537 3.43566

Inicialmente como lo propone el ejercicio se realiza el filtrado de datos basándose en la zona norte de la ciudad y en el tipo de vivienda, adicionalmente se filtran las coordenadas teniendo en cuenta las delimitaciones de la zona proporcionadas por el IDESC.

La zona norte de Cali está comprendida desde la KR 1 entre la CL 1 OESTE hasta el separador vial ubicado entre las CL 25 y CL 26, vía por la cual se continúa hasta la KR 7, y desde este punto, siguiendo el trazado del corredor férreo hasta llegar a la CL 88, a partir de la cual, se continua hacia el norte por el límite del suelo urbano hasta finalizar en el punto de inicio en la KR 1. Aproximadamente, esto corresponde a longitudes mayores a “-76.54” y latitudes menores o iguales a 3.45.

Obteniendo como resultado la siguiente distribución

mapa_cali <- read_sf("C:/Users/AnalíticaSilice/Documents/Maestría en ciencia de datos/Segundo semestre 2026_I/Modelos/Actividad Caso C&A/mc_barrios/mc_barrios.shp")

vivienda_puntos <- st_as_sf(datos_filtrado2, 
                            coords = c("longitud", "latitud"), 
                            crs = 4326)

ggplot() +
  
  geom_sf(data = mapa_cali, fill = "white", color = "gray80") +
  
  geom_sf(data = vivienda_puntos, aes(color = "lightcoral"), size = 1.2, alpha = 0.6) +
  
  
  theme_minimal() +
  labs(title = "Mapa de Casas en la Zona Norte",
       caption = "Fuente: IDESC",
       color = "Inmuebles") +
theme(legend.position = "none")

4. Análisis exploratorio de datos

Preliminarmente, se realiza un estudio con indicadores de tendencia central, posición y varianza para comprender los datos, esta información se ilustra en la sección de anexos, por otra parte, el siguiente gráfico de cajas y bigotes permite identificar de manera visual los datos atípicos.

datos_f <- datos_filtrado2[names(datos_filtrado2) %in% c("preciom","areaconst","parqueaderos","banios","habitaciones")]
datos_long <- datos_f %>%
  pivot_longer(
    cols = everything(),
    names_to = "Variable",
    values_to = "Valor"
  )

ggplot(datos_long, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "lightcoral", alpha = 0.6) +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5),
    axis.text.x = element_text(angle = 45, hjust = 1)
  ) +
  labs(
    title = "Diagramas de caja de variables continuas",
    x = "Variables",
    y = "Valor"
  ) +
  scale_y_continuous(labels = function(x) format(x, scientific = FALSE))

Los datos atípicos están presentes mayoritariamente en las variables de precio y el área construida estos datos pueden ofrecer información valiosa para el modelo y sus estimaciones, por lo que se opta por conservarlos. Las variables predictoras para el precio son “estrato”,“areaconst”,“parqueaderos”,“banios” y “habitaciones”. A continuación, se realiza un análisis entre estas y la variable objetivo que es el precio.

4.1 Diagramas de relación entre variables

Al graficar cada una de las variables con respecto al precio del inmueble no es posible determinar una clara relación entre estas, hay una tendencia entre área construida y precio que posee una amplia varianza en sus puntos, por lo que es necesario determinar los coeficientes de correlación de Pearson o Spearman según corresponda.

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

library(ggplot2)
library(patchwork)

plots <- list()

for (var in vars_continuas) {

p2 <- ggplot(datos_filtrado2, aes_string(x = var, y = "preciom")) +
  geom_point(fill = "lightcoral", alpha = 0.6) +
  labs(
    title = paste(var,"vs preciom"),
    x = var,
    y = "preciom") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5)
  ) +
  scale_y_continuous(labels = function(x) format(x, scientific = FALSE))

plots[[var]] <- p2

}

wrap_plots(plots)

Para las variables “estrato”,“areaconst”,“parqueaderos”, “baños” y “habitaciones” se emplea un coeficiente de Spearman, mientras que para el área construida se procede a usar un coeficiente de Pearson. En la siguiente tabla se representan los resultados de estos coeficientes.

pearsonareas <- round(cor(datos_filtrado2$areaconst, datos_filtrado2$preciom, method = "pearson"),digits = 2)

Spearmanestrato <-round(cor(datos_filtrado2$estrato, datos_filtrado2$preciom, method = "spearman"),digits = 2)

Spearmanhabitaciones <- round(cor(datos_filtrado2$habitaciones,datos_filtrado2$preciom, method = "spearman"),digits = 2)

Spearmanbanios <- round(cor(datos_filtrado2$banios,datos_filtrado2$preciom, method = "spearman"),digits = 2)

Spearmanparqueaderos <- round(cor(datos_filtrado2$parqueaderos,datos_filtrado2$preciom, method = "spearman"),digits = 2)


r <-c("r")


Tabla_enunciado <-data.frame(r,pearsonareas,Spearmanestrato,Spearmanhabitaciones,Spearmanbanios,Spearmanparqueaderos)

colnames(Tabla_enunciado) <-c(" - ","Área construida","Estrato","Habitaciones","Baños","Parqueaderos")

knitr::kable(Tabla_enunciado,
             booktabs = TRUE,
             caption = "<center><b><span style='font-size:18px'> Coeficientes de correlación </span></b></center>",
) %>%
  kable_styling(full_width = FALSE)
Coeficientes de correlación
Área construida Estrato Habitaciones Baños Parqueaderos
r 0.69 0.72 0.38 0.64 0.5

De lo anterior, podemos destacar que hay una relación positiva entre el área construida, estrato y baños con respecto al precio del inmueble por lo que se opta por usar estas tres dentro del modelo, para el caso de las variables como habitaciones y parqueaderos no estarían dentro de este dado que la correlación no es lo suficientemente alta con respecto a las otras, además para las casas la mayor parte no posee un registro por lo que esta variable puede tener un impacto en inmuebles de tipo apartamento.

5. Modelo de regresión lineal múltiple

Previamente, se realizan los siguientes supuestos y posteriormente se validan estos conforme los resultados.

\[ \text{Los errores son variables aleatorias normales.} \] \[ \text{Los errores tienen media cero.} \] \[ \text{Los errores tienen una varianza constante (homocedasticidad).} \] \[ \text{No hay correlación entre los errores, lo que se conoce como independencia de los errores.} \]

Se procede a emplear un modelo de regresión lineal múltiple con las siguientes variables:

  • Área construida
  • Estrato
  • Baños

Cabe resaltar que este modelo emplea los siguientes parámetros:

\[ Y_i = \beta_0 + \beta_1 X_{i1} + \beta_2 X_{i2} + \cdots + \beta_k X_{ik} + \varepsilon_i, \qquad i = 1,2,\ldots,n \] En este caso es necesario implementar una escala logarítmica para las variables como precio y área construida dado que son variables continuas con magnitudes distantes con respecto a los valores de las variables categóricas. Aplicando, el modelo se obtiene los siguientes resultados:

modelo_multiple <- lm(log(datos_filtrado2$preciom) ~ log(datos_filtrado2$areaconst) + datos_filtrado2$estrato + datos_filtrado2$banios, data = datos_filtrado2)


summary(modelo_multiple)
## 
## Call:
## lm(formula = log(datos_filtrado2$preciom) ~ log(datos_filtrado2$areaconst) + 
##     datos_filtrado2$estrato + datos_filtrado2$banios, data = datos_filtrado2)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.72581 -0.17979 -0.02112  0.14152  1.24376 
## 
## Coefficients:
##                                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    2.137217   0.098335  21.734  < 2e-16 ***
## log(datos_filtrado2$areaconst) 0.497274   0.022669  21.936  < 2e-16 ***
## datos_filtrado2$estrato        0.216185   0.012462  17.348  < 2e-16 ***
## datos_filtrado2$banios         0.059291   0.008512   6.966 7.42e-12 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2838 on 716 degrees of freedom
## Multiple R-squared:  0.7724, Adjusted R-squared:  0.7715 
## F-statistic: 810.1 on 3 and 716 DF,  p-value: < 2.2e-16

Se obtiene un \(R^2\) de 0.77 el cual resulta en un modelo que explica el 77% del comportamiento de los datos obtenidos, el otro 23% puede ser producto de otras variables que no se están cuantificando como, lujo antigüedad o tipo y calidad de acabados, es importante considerar que el intercepto cuando las variables predictoras son 0 es de 2.13, además los resultados nos permiten identificar que la variación en el precio en un 50% depende del área construida, 21% del estrato y un 6% es con respecto a la cantidad de baños.

6. Validación de supuestos

6.1 Normalidad :

Inicialmente es necesario verificar si el modelo es válido y esto implica el comportamiento de los residuos al azar, como se muestra en la siguiente gráfica este no tiene sesgos ni una amplia dispersión, además la línea roja es casi que horizontal sobre el valor de 0.

plot(modelo_multiple, which = 1)

Al realizar la prueba de Shapiro-Wilk, se obtuvo un p-valor inferior a 0.05, lo que indica que el supuesto de normalidad de los errores no se cumple estrictamente producto de valores atípicos como lo pueden ser inmuebles con una gran área construida o con un elevado número de baños.

shapiro.test(residuals(modelo_multiple))
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo_multiple)
## W = 0.97465, p-value = 7.673e-10

6.2 Homocedasticidad:

Realizando el test de Goldfeld-Quandt se obtiene un p-valor superior a 0.33, lo que termina de confirmar que los errores tienen una varianza constante.

lmtest::gqtest(modelo_multiple)
## 
##  Goldfeld-Quandt test
## 
## data:  modelo_multiple
## GQ = 1.0467, df1 = 356, df2 = 356, p-value = 0.3335
## alternative hypothesis: variance increases from segment 1 to 2

6.3 Independencia:

Finalmente, se aplicó la prueba de Durbin-Watson obteniendo un estadístico de 1.578 esto indica una correlación positiva, sin embargo el p-valor es menor que 0.05 lo cual quiere decir que hay una dependencia entre los errores dado que por lo que son del mismo tipo de inmueble y de la misma zona de la ciudad.

car::durbinWatsonTest(modelo_multiple)
##  lag Autocorrelation D-W Statistic p-value
##    1       0.2103679      1.578567       0
##  Alternative hypothesis: rho != 0

Por último, se puede concluir que solo se están cumpliendo dos supuestos de los cuatro planteados. Es necesario depurar aún más la base de datos, principalmente inmuebles con una gran área construida y con una cantidad atípica de baños, habitaciones y parqueaderos, esto podría mejorar el \(R^2\) del modelo y a su vez la exactitud de las predicciones.

7. Predicción de valor para la propiedad 1.

Recopilando los valores de la primera solicitud se procede a predecir el posible valor del inmueble, es importante considerar que estas estimaciones se realizaron si la casa fuera estrato 4 obteniendo como resultado 316 millones de pesos. Este valor está dentro del margen del crédito hipotecario preaprobado.

modelo_multiple <- lm(
  log(preciom) ~ log(areaconst) + estrato + banios,
  data = datos_filtrado2
)

nuevo_inmueble <- data.frame(
  areaconst = 200,
  estrato = 4,
  banios = 2
)

precio_predicho <- exp(predict(modelo_multiple, newdata = nuevo_inmueble))

precio_predicho
##       1 
## 315.846

8. Predicción de valores para la propiedad 2.

Finalmente, se procede a aplicar los mismos pasos empleados anteriormente, pero para la segunda propiedad, iniciando con el filtrado y verificación de las coordenadas suministradas.

mapa_cali <- read_sf("C:/Users/AnalíticaSilice/Documents/Maestría en ciencia de datos/Segundo semestre 2026_I/Modelos/Actividad Caso C&A/mc_barrios/mc_barrios.shp")

vivienda_puntos <- st_as_sf(datos_filtrado3, 
                            coords = c("longitud", "latitud"), 
                            crs = 4326)

ggplot() +
  
  geom_sf(data = mapa_cali, fill = "white", color = "gray80") +
  
  geom_sf(data = vivienda_puntos, aes(color = "lightcoral"), size = 1.2, alpha = 0.6) +
  
  
  theme_minimal() +
  labs(title = "Mapa de Apartamentos en la Zona Sur",
       caption = "Fuente: IDESC",
       color = "Inmuebles") +
theme(legend.position = "none")

Para el modelo de regresión múltiple para el caso de los apartamentos se incluye la variable parqueadero dado que este atributo tiene más valores en los apartamentos que en las casas. Como resultado se obtiene un \(R^2\) de 0.84 el cual es significativamente mayor que el anterior. El peso de las variables incrementa un poco más en donde el 68% dependen de del área construida, el 22% del estrato, 8% de los baños y el 3% depende del parqueadero.

modelo_multiple2 <- lm(log(datos_filtrado3$preciom) ~ log(datos_filtrado3$areaconst) + datos_filtrado3$estrato + datos_filtrado3$banios + datos_filtrado3$parqueaderos, data = datos_filtrado3)


summary(modelo_multiple2)
## 
## Call:
## lm(formula = log(datos_filtrado3$preciom) ~ log(datos_filtrado3$areaconst) + 
##     datos_filtrado3$estrato + datos_filtrado3$banios + datos_filtrado3$parqueaderos, 
##     data = datos_filtrado3)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.77346 -0.12229  0.00789  0.13419  0.79954 
## 
## Coefficients:
##                                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)                    1.235815   0.063169  19.563   <2e-16 ***
## log(datos_filtrado3$areaconst) 0.683535   0.017900  38.187   <2e-16 ***
## datos_filtrado3$estrato        0.226686   0.006822  33.228   <2e-16 ***
## datos_filtrado3$banios         0.077165   0.007384  10.451   <2e-16 ***
## datos_filtrado3$parqueaderos   0.003746   0.002874   1.303    0.193    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.2106 on 2299 degrees of freedom
## Multiple R-squared:  0.8401, Adjusted R-squared:  0.8398 
## F-statistic:  3019 on 4 and 2299 DF,  p-value: < 2.2e-16

Para concluir se procede a estimar la predicción del precio del inmueble 2 que da como resultado un valor de 668 millones de pesos el cual se ajusta al margen del crédito hipotecario preaprobado.

modelo_multiple2 <- lm(
  log(preciom) ~ log(areaconst) + estrato + banios,
  data = datos_filtrado3
)

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

precio_predicho <- exp(predict(modelo_multiple2, newdata = nuevo_inmueble))

precio_predicho
##        1 
## 668.4182

9. Conclusiones

Antes de aconsejar por la compra de un inmueble sobre otro es necesario mejorar la calidad de los datos, dado que hay muchas inconsistencias entre la zona en donde dicen estar los inmuebles y sus coordenadas, por lo que al filtrar y revisar estos datos se pierden. Se recomienda la compra del apartamento en la Zona Sur dado que el modelo empleado para la predicción de su valor presenta un mejor \(R^2\) además cabe resaltar que las estimaciones se realizaron para inmuebles del estrato 4 para la casa y 5 para el caso del apartamento, por lo que inmuebles con un estrato mayor, repercutiría en un mayor precio.

10. Anexo

library(kableExtra)

var_continuas <- datos_filtrado2[names(datos_filtrado2) %in% c("preciom","areaconst","parqueaderos","banios","habitaciones")]
var_continuas <- names(var_continuas)[sapply(var_continuas, is.numeric)]

#Indicadores de Tendencia central, dispersiC3n y posiciC3n
calc_stats <- function(columna){
  
  media <- round(mean(columna, na.rm = TRUE), 3)
  mediana <- round(median(columna, na.rm = TRUE), 3)
  maximo <- max(columna, na.rm = TRUE)
  minimo <- min(columna, na.rm = TRUE)
  rango <- round((max(columna, na.rm = TRUE) - min(columna, na.rm = TRUE)), 3)
  varianza <- round(var(columna, na.rm = TRUE), 3)
  Desv_Estandar <- round(sd(columna, na.rm = TRUE), 3)
  Coef_Variacion <- round(((Desv_Estandar / media) * 100), 3)
  
  Q1 <- quantile(columna, 0.25, na.rm = TRUE)
  Q3 <- quantile(columna, 0.75, na.rm = TRUE)
  IQR_val <- Q3 - Q1
  lim_inf <- Q1 - 1.5 * IQR_val
  lim_sup <- Q3 + 1.5 * IQR_val
  
  datos_atipicos <- columna[columna < lim_inf | columna > lim_sup]
  
  if(length(datos_atipicos) == 0){
    valores_txt <- "Ninguno"
  } else {
    valores_unicos <- unique(datos_atipicos)
    valores_txt <- paste(head(valores_unicos, 5), collapse = ", ")
    if(length(valores_unicos) > 5){
      valores_txt <- paste0(valores_txt, ", ...")
    }
  }
  
  return(list(
    Media = media,
    Mediana = mediana,
    Minimo = minimo,
    Maximo = maximo,
    Rango = rango,
    Varianza = varianza,
    Desv_Estandar = Desv_Estandar,
    Coef_Variacion = Coef_Variacion,
    Cantidad_de_valores_atipicos = length(datos_atipicos),
    Valores_atipicos = valores_txt
  ))
}
resultados <- lapply(datos_filtrado2[, var_continuas, drop = FALSE], calc_stats)
resultados_df <- do.call(rbind, lapply(resultados, as.data.frame))
resultados_df <- cbind(Variable = names(resultados), resultados_df)
resultados_df$Varianza <- format(resultados_df$Varianza, scientific = FALSE)
outliers_df <- resultados_df[, c("Variable", "Cantidad_de_valores_atipicos")]
resultados_df %>%
  kable(
    col.names = c(
      "Variable",
      "Media",
      "Mediana",
      "Mínimo",
      "Máximo",
      "Rango",
      "Varianza",
      "Desv. Estándar",
      "Coef. Variación",
      "Cantidad de valores atípicos",
      "Valores atípicos"
    ),
    align = "c", 
    caption = "<center><b><span style='font-size:18px'> Indicadores de tendencia central, dispersión, posición y valores atípicos</span></b></center>",
    row.names = FALSE)%>%
  
  kable_styling(full_width = FALSE, position = "center")
Indicadores de tendencia central, dispersión, posición y valores atípicos
Variable Media Mediana Mínimo Máximo Rango Varianza Desv. Estándar Coef. Variación Cantidad de valores atípicos Valores atípicos
preciom 442.292 369.5 85 1940 1855 87082.975 295.098 66.720 34 1100, 1350, 1590, 1800, 1200, …
areaconst 258.540 230.5 30 1500 1470 28866.708 169.902 65.716 22 750, 700, 1188, 736, 920, …
parqueaderos 1.432 1.0 0 10 10 2.454 1.567 109.427 20 6, 7, 8, 10, 9
banios 3.556 3.0 0 10 10 2.356 1.535 43.166 17 8, 9, 10
habitaciones 4.601 4.0 0 10 10 3.472 1.863 40.491 36 10, 9