Viviendas corporativas

Iniciaremos revisando todas las opciones de vivienda disponibles en la ciudad de Cali

#install.packages("devtools") # solo la primera vez
#devtools::install_github("dgonxalex80/paqueteMODELOS", force =TRUE)
library(paqueteMODELOS)
## Loading required package: boot
## Loading required package: broom
## Warning: package 'broom' was built under R version 4.2.3
## Loading required package: GGally
## Warning: package 'GGally' was built under R version 4.2.3
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.2.3
## Registered S3 method overwritten by 'GGally':
##   method from   
##   +.gg   ggplot2
## Loading required package: gridExtra
## Warning: package 'gridExtra' was built under R version 4.2.3
## Loading required package: knitr
## Warning: package 'knitr' was built under R version 4.2.3
## Loading required package: summarytools
data("vivienda")
summary (vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Primero vamos a realizar un filtro a la base de datos e incluya solo las ofertas de casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta.

Vamos a empezar por quitar los valores N/A ya que no nos sirven para filtrar y pasar todo a minúsculas para evitar problemas en los filtros.

datos <- vivienda

# Eliminar filas con valores NA
datos_limpio <- datos[complete.cases(datos), ]

# Convertir todo a minúsculas
datos_limpio <- data.frame(lapply(datos_limpio, as.character), stringsAsFactors = FALSE)
datos_limpio <- data.frame(lapply(datos_limpio, tolower))

filtro <- datos_limpio[datos_limpio$tipo == "casa" & datos_limpio$zona == "zona norte", ]


head(filtro, 3)
##     id       zona piso estrato preciom areaconst parqueaderos banios
## 6 1209 zona norte   02       5     320       150            2      4
## 7 1592 zona norte   02       5     780       380            2      3
## 8 4460 zona norte   02       4     625       355            3      5
##   habitaciones tipo barrio  longitud latitud
## 6            6 casa  acopi -76.51341 3.47968
## 7            3 casa  acopi -76.51674 3.48721
## 8            5 casa  acopi -76.53179  3.4059
table(filtro$tipo)
## 
## casa 
##  254
table(filtro$zona)
## 
## zona norte 
##        254

Including Plots

You can also embed plots, for example:

## Warning: package 'leaflet' was built under R version 4.2.3
## [1] 0
## [1] 0

Note that the echo = FALSE parameter was added to the code chunk to prevent printing of the R code that generated the plot.

Gráficamente podemos ver que la mayoría de los puntos o lugares seleccionados para la base 1 están ubicados en el norte de Cali. Sin embargo, vemos que dentro de la base 1 hay algunos lugares que están ubicados en el label “zona norte” y no pertecen a la zona norte de la ciudad.

Correlación entre la variable precio y características de la misma

# install.packages("plotly")
library(plotly)
## Warning: package 'plotly' was built under R version 4.2.3
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
library(ggplot2)

# Eliminar filas con valores NA en las variables de interés
datos_limpio_2 <- datos[complete.cases(datos[, c("preciom", "areaconst", "estrato", "banios", "habitaciones", "zona")]), ]


datos_limpio_2$zona <- as.factor(datos_limpio_2$zona)


plot_base <- ggplot(datos_limpio_2, aes(x = preciom, y = areaconst, color = estrato)) +
  geom_point() +
  facet_grid(~zona)


plot_interactivo <- ggplotly(plot_base)


plot_interactivo
plot_base2 <- ggplot(datos_limpio_2, aes(x = preciom, y = banios, color = estrato)) +
  geom_point() +
  facet_grid(~zona)

plot_interactivo2 <- ggplotly(plot_base2)


plot_interactivo2
plot_base3 <- ggplot(datos_limpio_2, aes(x = preciom, y = habitaciones, color = estrato)) +
  geom_point() +
  facet_grid(~zona)

plot_interactivo3 <- ggplotly(plot_base3)

# Mostrar el gráfico
plot_interactivo3

En relación al precio y el area construida, se ve que independiente de la zona y el estrato de vivienda, entre más grande es el área construida mayor es el precio de las viviendas, teniendo una correlación positiva. Respecto al número de baños y habitaciones la relación con el precio no es clara y se ve que se relaciona más con el estrato que con la cantidad de baños o habitaciones, ya que propiedades con la misma cantidad de baños o habitaciones, suben su precio dependiendo del estrato, siendo más caros los del estrato 5 y 6.

Modelos de regresión múltiple

# Estimar un modelo de regresión lineal múltiple
modelo_lm <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = datos_limpio_2)

# Resumen del modelo
summary(modelo_lm)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = datos_limpio_2)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1412.57   -88.36   -17.55    56.24  1101.38 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -380.7629    14.3638  -26.51   <2e-16 ***
## areaconst       0.8510     0.0222   38.33   <2e-16 ***
## estrato        97.9107     2.8342   34.55   <2e-16 ***
## habitaciones  -31.2744     2.2613  -13.83   <2e-16 ***
## parqueaderos   74.4922     2.5900   28.76   <2e-16 ***
## banios         61.0554     2.6271   23.24   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 177.2 on 6711 degrees of freedom
##   (1602 observations deleted due to missingness)
## Multiple R-squared:  0.7206, Adjusted R-squared:  0.7204 
## F-statistic:  3462 on 5 and 6711 DF,  p-value: < 2.2e-16
# Interpretación de los coeficientes

Intercepto (Constante): El intercepto del modelo es -380.76. Esto representa el precio esperado de una vivienda cuando todas las variables predictoras son cero. Sin embargo, en para un vivienda, no tiene sentido que ninguna variable sea cero, ni que el intercepto sea negativo.

Área Construida (areaconst): Por cada unidad adicional de área construida, el precio de la vivienda aumenta en promedio 0.8510.

Estrato: Por cada unidad adicional de estrato, el precio de la vivienda aumenta en promedio 97.9107.

Número de Habitaciones (habitaciones): Por cada unidad adicional en el número de habitaciones, el precio de la vivienda disminuye en promedio 31.2744.

Número de Parqueaderos (parqueaderos): Por cada unidad adicional en el número de parqueaderos, el precio de la vivienda aumenta en promedio 74.4922.

Número de Baños (banios): Por cada unidad adicional en el número de baños, el precio de la vivienda aumenta en promedio 61.0554.

Significancia Estadística:

Todos los coeficientes son estadísticamente significativos, como lo indican los valores de p para cada variable predictora (todos los p-valores son < 0.001).

Coeficiente de Determinación

El coeficiente de determinación del modelo es 0.7206, lo que significa que alrededor del 72.06% de la variabilidad en el precio de las viviendas puede ser explicada por las variables incluidas en el modelo. Esto sugiere que el modelo tiene un buen poder explicativo sobre los precios de las viviendas.

Ajuste del Modelo:

El estadístico F tiene un valor extremadamente alto (3462) con un p-valor prácticamente nulo, lo que indica que el modelo en su conjunto es altamente significativo.

El error estándar residual es 177.2.

Las variables incluidas en el modelo explican una proporción significativa de la variabilidad en los precios de las viviendas en Cali. El número de habitaciones parece tener un efecto negativo en el precio de la vivienda, lo cual a priori no tiene mucho sentido. Sin embargo, esto podría explicarse a una mayor disponibilidad de viviendas con un mayor número de habitaciones, lo que podría disminuir su precio en comparación con las viviendas con menos habitaciones en un mercado dado.

Validación de supuestos

Normalidad

# Gráfico de histograma de residuos
hist(resid(modelo_lm), breaks = 20, main = "Histograma de Residuos", xlab = "Residuos")

# Gráfico de cuantiles normales (QQ plot)
qqnorm(resid(modelo_lm))
qqline(resid(modelo_lm))

Los residuos no siguen una distribución normal. Aunque los residuos pueden no seguir una distribución normal, eso no necesariamente invalida todo el análisis. Adicional se pueden plicar transformaciones a las variables predictoras o a la variable de respuesta podría ayudar a mejorar la normalidad de los residuos. Pueden ser transformación logarítmica, la raíz cuadrada o la transformación Box-Cox.

Homocedasticidad

# Gráfico de residuos vs. valores ajustados
plot(fitted(modelo_lm), resid(modelo_lm), 
     main = "Residuos vs. Valores Ajustados",
     xlab = "Valores Ajustados", ylab = "Residuos")

# Prueba de Breusch-Pagan
library(lmtest)
## Warning: package 'lmtest' was built under R version 4.2.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.2.3
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
bptest(modelo_lm)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_lm
## BP = 1212.2, df = 5, p-value < 2.2e-16

Hay indicios de no homocedasticidad, por tanto, puede sesgar las estimaciones de los coeficientes y afectar la precisión de las pruebas de significancia estadística. Para corregir esto se pueden utilizar métodos de regresión robustos a la heterocedasticidad o transformar las variables para estabilizar la varianza de los residuos. Linealidad

# Gráfico de dispersión de valores ajustados vs. residuos
plot(resid(modelo_lm) ~ fitted(modelo_lm), 
     main = "Residuos vs. Valores Ajustados",
     xlab = "Valores Ajustados", ylab = "Residuos")

Gráficamente, no se cumple el supuesto de linealidad, se pueden aplicar transformaciones a las variables predictoras o a la variable de respuesta para lograr una relación más lineal. Por ejemplo, transformaciones logarítmicas.

Independencia de los Residuos

# Gráfico de residuos vs. secuencia de observaciones
plot(resid(modelo_lm), type = "l",
     main = "Residuos vs. Secuencia de Observaciones",
     xlab = "Secuencia de Observaciones", ylab = "Residuos")

Graficamente vemos que no hay correlación, la presencia de una distribución aleatoria de los residuos en el gráfico sugiere que el modelo de regresión se ajusta bien a los datos y que captura adecuadamente la estructura de correlación entre las variables predictoras y la variable de respuesta. Esto fortalece la confianza en las inferencias y predicciones derivadas del modelo, ya que los coeficientes estimados y las pruebas de significancia se basan en residuos que son independientes y no sesgados.

Entrenamiento de modelo

# Cargar la librería para partición de datos
#install.packages("caTools")
library(caTools)
## Warning: package 'caTools' was built under R version 4.2.3
# Establecer la semilla para reproducibilidad
set.seed(123)

# Realizar la partición aleatoria (70% entrenamiento, 30% prueba)
split <- sample.split(datos_limpio_2$preciom, SplitRatio = 0.7)

# Crear conjunto de datos de entrenamiento
datos_entrenamiento <- datos_limpio_2[split, ]

# Crear conjunto de datos de prueba
datos_prueba <- datos_limpio_2[!split, ]

# Ajustar el modelo con los datos de entrenamiento
modelo_entrenado <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = datos_entrenamiento)

# Mostrar los resultados del modelo
summary(modelo_entrenado)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = datos_entrenamiento)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1343.51   -87.85   -18.16    55.22  1104.10 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -372.32854   17.02637  -21.87   <2e-16 ***
## areaconst       0.80537    0.02523   31.92   <2e-16 ***
## estrato        94.70856    3.34440   28.32   <2e-16 ***
## habitaciones  -30.53394    2.68494  -11.37   <2e-16 ***
## parqueaderos   74.95196    3.03511   24.70   <2e-16 ***
## banios         64.17257    3.07699   20.86   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 176.8 on 4732 degrees of freedom
##   (1120 observations deleted due to missingness)
## Multiple R-squared:  0.7219, Adjusted R-squared:  0.7216 
## F-statistic:  2457 on 5 and 4732 DF,  p-value: < 2.2e-16

Estos resultados indican que el modelo tiene un buen ajuste a los datos de entrenamiento y que las variables explicativas (área construida, estrato, número de habitaciones, número de parqueaderos y número de baños) tienen una relación significativa con el precio de las viviendas.

# Crear un nuevo conjunto de datos con las características de la vivienda a predecir
nueva_vivienda <- data.frame(
  tipo = "casa",
  areaconst = 200,
  parqueaderos = 1,
  banios = 2,
  habitaciones = 4,
  estrato = sample(4:5, 1), # Selección aleatoria de estrato entre 4 y 5
  zona = "norte"
)

# Realizar la predicción del precio de la vivienda utilizando el modelo entrenado
precio_predicho <- predict(modelo_entrenado, newdata = nueva_vivienda)

# Mostrar el precio predicho
print(precio_predicho)
##        1 
## 343.4488

El precio predicho es de 343.4 Millones y tenemos un presupuesto de 350 Millones, por lo cual es posible encontrar una vivienda con ese valor.

nuevas_viviendas <- expand.grid(
  tipo = "casa",
  areaconst = 200,
  parqueaderos = 1,
  banios = 2,
  habitaciones = 4,
  estrato = c(4, 5),
  zona = "norte"
)

# Realizar predicciones de precios para las nuevas viviendas
nuevas_viviendas$precio_predicho <- predict(modelo_entrenado, newdata = nuevas_viviendas)

# Filtrar las viviendas que están dentro del presupuesto máximo
ofertas_potenciales <- nuevas_viviendas[nuevas_viviendas$precio_predicho <= 350000000, ]

# Seleccionar al menos cinco ofertas potenciales dentro del rango de precios deseado
ofertas_seleccionadas <- head(ofertas_potenciales, 5)

# Filtrar las viviendas originales que están dentro del presupuesto máximo
datos_potenciales <- datos_limpio_2[datos_limpio_2$preciom <= 350000000, ]

# Seleccionar al menos cinco ofertas potenciales dentro del rango de precios deseado
ofertas_originales_seleccionadas <- head(datos_potenciales, 5)

# Convertir las variables de longitud y latitud a numéricos
datos_limpio_2$longitud <- as.numeric(datos_limpio_2$longitud)
datos_limpio_2$latitud <- as.numeric(datos_limpio_2$latitud)

# Verificar si hay NA después de la conversión
sum(is.na(datos_limpio_2$longitud))  # Debería ser cero si no hay NA
## [1] 0
sum(is.na(datos_limpio_2$latitud))   # Debería ser cero si no hay NA
## [1] 0
ofertas_seleccionadas_con_coordenadas <- merge(ofertas_seleccionadas, ofertas_originales_seleccionadas[, c("latitud", "longitud")], by = "row.names")

colnames(ofertas_seleccionadas_con_coordenadas)[1] <- "Row.names"
colnames(ofertas_seleccionadas_con_coordenadas)[ncol(ofertas_seleccionadas_con_coordenadas)] <- "latitud"
colnames(ofertas_seleccionadas_con_coordenadas)[ncol(ofertas_seleccionadas_con_coordenadas)] <- "longitud"

library(leaflet)

# mapa
mapa2 <- leaflet() %>%
  addTiles() %>%
  addMarkers(data = ofertas_seleccionadas_con_coordenadas, lng = ~longitud, lat = ~latitud, popup = ~paste("Precio: ", round(precio_predicho/1e6, 2), " millones de pesos"))

# Mostrar el mapa
mapa2

Solo dos casas cumplieron con las condiciones establecidas y se seleccionaron en el conjunto de datos de ofertas potenciales, eesto significa que no hay suficientes viviendas que cumplan con esos criterios específicos. Si quisieramos obtener una muestra más grande sería necesario flexibilizar algunos de los criterios.

Las dos casas que encontramos fueron las siguientes:

A. tipo,casa,areaconst,200,parqueaderos,1,banios,2,habitaciones,4,estrato,4,zona,norte,precio_predicho,248.7403 b. tipo,casa,areaconst,200,parqueaderos,1,banios,2,habitaciones,4,estrato,5,zona,norte,precio_predicho,343.4488

En este caso, dada la similitud, en todas las características como en la ubicación, la vivienda A. parece mejor ya que cuesta casi 100 millones menos, con unas características y ubicación muy similar a la vivienda B.

Vivienda 2

# Crear un nuevo conjunto de datos con las características de la vivienda a predecir (apartamento)
nuevo_apartamento <- data.frame(
  tipo = "apartamento",
  areaconst = 300,
  parqueaderos = 3,
  banios = 3,
  habitaciones = 5,
  estrato = sample(5:6, 1), # Selección aleatoria de estrato entre 5 y 6
  zona = "sur"
)

# Realizar la predicción del precio del nuevo apartamento utilizando el modelo entrenado
precio_predicho_apartamento <- predict(modelo_entrenado, newdata = nuevo_apartamento)

# Mostrar el precio predicho del apartamento
print(precio_predicho_apartamento)
##       1 
## 607.528

Para la segunda vivienda, se estima con el modelo un valor de 607 millones

nuevos_apartamentos <- expand.grid(
  tipo = "apartamento",
  areaconst = 300,
  parqueaderos = 3,
  banios = 3,
  habitaciones = 5,
  estrato = c(5, 6),
  zona = "sur"
)

# Realizar predicciones de precios para los nuevos apartamentos
nuevos_apartamentos$precio_predicho <- predict(modelo_entrenado, newdata = nuevos_apartamentos)

# Filtrar los apartamentos que están dentro del presupuesto máximo
ofertas_potenciales_apartamentos <- nuevos_apartamentos[nuevos_apartamentos$precio_predicho <= 850000000, ]

# Seleccionar al menos cinco ofertas potenciales dentro del rango de precios deseado
ofertas_seleccionadas_apartamentos <- head(ofertas_potenciales_apartamentos, 5)


datos_potenciales_apartamentos <- datos_limpio_2[datos_limpio_2$preciom <= 850000000, ]


ofertas_originales_seleccionadas_apartamentos <- head(datos_potenciales_apartamentos, 5)


ofertas_seleccionadas_con_coordenadas_apartamentos <- merge(ofertas_seleccionadas_apartamentos, ofertas_originales_seleccionadas_apartamentos[, c("latitud", "longitud")], by = "row.names")

colnames(ofertas_seleccionadas_con_coordenadas_apartamentos)[1] <- "Row.names"
colnames(ofertas_seleccionadas_con_coordenadas_apartamentos)[ncol(ofertas_seleccionadas_con_coordenadas_apartamentos)] <- "latitud"
colnames(ofertas_seleccionadas_con_coordenadas_apartamentos)[ncol(ofertas_seleccionadas_con_coordenadas_apartamentos)] <- "longitud"

#  mapa
library(leaflet)

# Crear el mapa de apartamentos
mapa_apartamentos <- leaflet() %>%
  addTiles() %>%
  addMarkers(data = ofertas_seleccionadas_con_coordenadas_apartamentos, lng = ~longitud, lat = ~latitud, popup = ~paste("Precio: ", round(precio_predicho/1e6, 2), " millones de pesos"))

# Mostrar el mapa de apartamentos
mapa_apartamentos

Para la vivienda 2 (apartamento), encontramos el mismo problema, con las restricciones establecidas, el modelo solo es capaz de encontrar 2 viviendas que se acomodan a las condiciones solicitadas, con lo siguiente:

A. tipo,apartamento,areaconst,300,parqueaderos,3,banios,3,habitaciones,5,estrato,5,zona,sur,precio_predicho,607.528 B. tipo,apartamento,areaconst,300,parqueaderos,3,banios,3,habitaciones,5,estrato,6,zona,sur,precio_predicho,702.2366

En este caso, dada la similitud, en todas las características como en la ubicación, la vivienda A. parece mejor ya que cuesta casi 100 millones menos, con unas características y ubicación muy similar a la vivienda B.