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
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.
# 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.
# 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.
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.
# 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.
# 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.