Se inicia con la instalación y carga de paquetes, que contienen la base de datos “vivienda”. Se cuenta con un total de 8,322 registros y 13 variables.
# Cargar los datos
data("vivienda")
# Visión general de los datos
glimpse(vivienda)
## Rows: 8,322
## Columns: 13
## $ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
## $ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
## $ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
## $ preciom <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
## $ areaconst <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
## $ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, NA, 3, 2, 2, 1, 4, 2, 2, 2,…
## $ banios <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
## $ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
## $ tipo <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
## $ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
## $ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
## $ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
Se inicia realizando el filtro de la variable “tipo” por Apartamento
# Filtrar la variable "tipo" por Apartamento
data_apt <- vivienda %>%
filter(tipo == "Apartamento")
t1<-table(data_apt$zona, data_apt$tipo)
df1<-as.data.frame.matrix(t1)
kable(df1, caption = "Distribución por tipo y zona", format = "markdown")
| Apartamento | |
|---|---|
| Zona Centro | 24 |
| Zona Norte | 1198 |
| Zona Oeste | 1029 |
| Zona Oriente | 62 |
| Zona Sur | 2787 |
Se identifica por cada variable el total de datos faltantes
md.pattern(data_apt,rotate.names = TRUE)
## id zona estrato preciom areaconst banios habitaciones tipo barrio longitud
## 3182 1 1 1 1 1 1 1 1 1 1
## 1049 1 1 1 1 1 1 1 1 1 1
## 537 1 1 1 1 1 1 1 1 1 1
## 332 1 1 1 1 1 1 1 1 1 1
## 0 0 0 0 0 0 0 0 0 0
## latitud parqueaderos piso
## 3182 1 1 1 0
## 1049 1 1 0 1
## 537 1 0 1 1
## 332 1 0 0 2
## 0 869 1381 2250
Se realiza una imputación basada en la mediana, donde se empleó la mediana calculada para cada tipo de vivienda, en este caso “Apartamentos”, para completar los valores faltantes en las variables correspondientes a ese tipo de vivienda.
# Calcular medianas por tipo de vivienda
data_apt$piso <- as.numeric(data_apt$piso)
medianas <- data_apt %>%
group_by(tipo) %>%
summarise(mediana_piso = median(piso, na.rm = TRUE),
mediana_parqueaderos = median(parqueaderos, na.rm = TRUE))
medianas
# Completar valores faltantes usando la mediana correspondiente
bd_clean <- data_apt %>%
left_join(medianas, by = "tipo") %>% # Unir las medianas calculadas al dataframe original
mutate(piso = if_else(is.na(piso), mediana_piso, piso),
parqueaderos = if_else(is.na(parqueaderos), mediana_parqueaderos,
parqueaderos)) %>%
select(-mediana_piso, -mediana_parqueaderos) # Eliminar columnas auxiliares
colSums(is.na(bd_clean))
## id zona piso estrato preciom areaconst
## 0 0 0 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 0 0 0
## latitud
## 0
Obteniendo finalmente el conjunto de datos:
bd_clean %>%
head() %>%
sample_n(3) %>%
kable("html", caption = " ") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE, position = "center")
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1724 | Zona Norte | 1 | 5 | 240 | 87 | 1 | 3 | 3 | Apartamento | acopi | -76.51700 | 3.36971 |
| 4386 | Zona Norte | 1 | 5 | 310 | 137 | 2 | 3 | 4 | Apartamento | acopi | -76.53105 | 3.38296 |
| 5424 | Zona Norte | 3 | 4 | 320 | 108 | 2 | 3 | 3 | Apartamento | acopi | -76.53638 | 3.40770 |
Se realiza un análisis exploratorio de los datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, número de baños, número de hábitaciones y zona donde se ubica la vivienda.
Se eliminan las variables que no se emplearan en el modelo de regresión multiple como son: “id”, “piso”, “barrio”, “longitud” y “latitud”.
bd_clean$estrato = as.factor(bd_clean$estrato)
bd_fin = subset(bd_clean, select = -c (id, piso, barrio, longitud, latitud))
bd_fin = bd_fin%>% mutate(across(where(is.character), as.factor))
glimpse(bd_fin) #visualización del nuevo dataset
## Rows: 5,100
## Columns: 8
## $ zona <fct> Zona Norte, Zona Norte, Zona Norte, Zona Norte, Zona Nort…
## $ estrato <fct> 5, 5, 4, 5, 6, 4, 5, 3, 3, 6, 6, 4, 5, 6, 5, 5, 3, 5, 5, …
## $ preciom <dbl> 260, 240, 220, 310, 520, 320, 385, 100, 175, 820, 450, 29…
## $ areaconst <dbl> 90, 87, 52, 137, 98, 108, 103, 49, 80, 377, 103, 96, 124,…
## $ parqueaderos <dbl> 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 3, 2, 1, 1, 1, 1, 1, …
## $ banios <dbl> 2, 3, 2, 3, 2, 3, 2, 1, 2, 4, 3, 2, 3, 3, 1, 3, 1, 4, 2, …
## $ habitaciones <dbl> 3, 3, 3, 4, 2, 3, 3, 2, 3, 4, 4, 3, 3, 4, 2, 3, 5, 4, 2, …
## $ tipo <fct> Apartamento, Apartamento, Apartamento, Apartamento, Apart…
summarytools::descr(bd_fin[,3:7])
## Descriptive Statistics
## bd_fin
## N: 5100
##
## areaconst banios habitaciones parqueaderos preciom
## ----------------- ----------- --------- -------------- -------------- ---------
## Mean 112.78 2.62 2.97 1.47 366.94
## Std.Dev 69.36 1.07 0.68 0.71 289.22
## Min 35.00 0.00 0.00 1.00 58.00
## Q1 68.00 2.00 3.00 1.00 175.00
## Median 90.00 2.00 3.00 1.00 279.00
## Q3 130.00 3.00 3.00 2.00 430.00
## Max 932.00 8.00 9.00 10.00 1950.00
## MAD 41.51 1.48 0.00 0.00 176.43
## IQR 62.00 1.00 0.00 1.00 255.00
## CV 0.61 0.41 0.23 0.48 0.79
## Skewness 2.61 0.90 0.06 2.20 2.16
## SE.Skewness 0.03 0.03 0.03 0.03 0.03
## Kurtosis 11.17 0.71 3.82 11.13 5.43
## N.Valid 5100.00 5100.00 5100.00 5100.00 5100.00
## Pct.Valid 100.00 100.00 100.00 100.00 100.00
#Analisis de correlación
correlaciones<-round(cor(bd_fin[,3:7], method = "spearman"), 3)
correlaciones
## preciom areaconst parqueaderos banios habitaciones
## preciom 1.000 0.890 0.736 0.772 0.326
## areaconst 0.890 1.000 0.706 0.800 0.468
## parqueaderos 0.736 0.706 1.000 0.647 0.302
## banios 0.772 0.800 0.647 1.000 0.489
## habitaciones 0.326 0.468 0.302 0.489 1.000
# Visualización de la matriz de correlación
plot_ly(z = correlaciones,
x = colnames(correlaciones),
y = rownames(correlaciones),
type = "heatmap",
colorscale = "Cividis") %>%
layout(title = "Matriz de Correlación (Spearman)",
xaxis = list(title = ""),
yaxis = list(title = ""))
Se puede evidenciar una fuerte relación entre el precio y el area construida con un 0.89, como también el número de baños y el area construida con un 0.8.
# Gráfico de dispersión Precio vs Área Construida
plot_ly(data = bd_clean,
x = ~areaconst,
y = ~preciom,
type = 'scatter',
mode = 'markers',
marker = list(color = 'rgba(0, 128, 255, 0.6)',
size = 10,
line = list(color = 'rgba(0, 128, 255, 1.0)', width = 1))) %>%
layout(title = "Precio vs Área Construida",
xaxis = list(title = "Área Construida (m²)"),
yaxis = list(title = "Precio (millones de COP)"))
En el análisis bivariado del precio (en millones de pesos COP) frente a la variable área construida, se evidencia que el precio de la vivienda tiende a aumentar a medida que aumenta el área construida. Este patrón sugiere que, en general, viviendas con una mayor área construida tienen precios más altos.
# Gráfico de cajas Precio vs Estrato
plot_ly(data = bd_fin,
x = ~as.factor(estrato),
y = ~preciom,
type = 'box',
marker = list(color = 'rgba(255, 165, 0, 0.6)')) %>%
layout(title = "Precio vs Estrato",
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio (millones de COP)"))
La relación entre el precio y la variable estrato, se observa que el precio de la vivienda varía según el estrato. Los estratos más altos presentan unos valores más elevados, mientras los más bajos están asociados con precios más bajos.
# Gráfico de cajas interactivo para Precio vs Número de Baños
bd_fin$banios_cat <- cut(bd_fin$banios,
breaks = c(-Inf, 1, 3, 5, Inf),
labels = c("0 a 1", "2 a 3", "4 a 5", "6 o más"))
plot_ly(data = bd_fin,
x = ~as.factor(banios_cat),
y = ~preciom,
type = 'box',
marker = list(color = 'rgba(34, 139, 34, 0.6)')) %>%
layout(title = "Precio vs Número de Baños",
xaxis = list(title = "Número de Baños"),
yaxis = list(title = "Precio (millones de COP)"))
Las viviendas con mayor número de baños son las de precios más elevados. Se presenta una gran concentración en apartamentos con 2 a 3 baños.
# Gráfico de cajas Precio vs Número de Habitaciones
bd_fin$habitaciones_cat <- cut(bd_fin$habitaciones,
breaks = c(-Inf, 1, 3, 5, Inf),
labels = c("0 a 1", "2 a 3", "4 a 5", "6 o más"))
plot_ly(data = bd_fin,
x = ~as.factor(habitaciones_cat),
y = ~preciom,
type = 'box',
marker = list(color = 'rgba(220, 20, 60, 0.6)')) %>%
layout(title = "Precio vs Número de Habitaciones",
xaxis = list(title = "Número de Habitaciones"),
yaxis = list(title = "Precio (millones de COP)"))
Se presenta una mayor concentración en apartamentos con 2 a 3 habitaciones.
# Gráfico de cajas Precio vs Zona
plot_ly(data = bd_fin,
x = ~zona,
y = ~preciom,
type = 'box',
marker = list(color = 'rgba(255, 215, 0, 0.6)')) %>%
layout(title = "Precio vs Zona",
xaxis = list(title = "Zona"),
yaxis = list(title = "Precio (millones de COP)"))
Se evidencia que en la zona oeste se encuentran los apartamentos con los precios más altos, mientras que en el oriente se concentran los de precios más bajos.
Se realiza la estimación de un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ).
modelo <- lm(preciom ~ areaconst + estrato + habitaciones +
parqueaderos + banios, data = bd_fin)
summary(modelo)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = bd_fin)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1619.72 -47.00 0.33 41.79 994.91
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -66.22217 9.61351 -6.888 6.32e-12 ***
## areaconst 1.94971 0.04133 47.174 < 2e-16 ***
## estrato4 23.70568 6.24480 3.796 0.000149 ***
## estrato5 46.25870 6.32620 7.312 3.04e-13 ***
## estrato6 182.90504 7.98194 22.915 < 2e-16 ***
## habitaciones -32.82124 3.14638 -10.431 < 2e-16 ***
## parqueaderos 83.55719 3.63753 22.971 < 2e-16 ***
## banios 45.48708 2.89468 15.714 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 127.1 on 5092 degrees of freedom
## Multiple R-squared: 0.8072, Adjusted R-squared: 0.807
## F-statistic: 3046 on 7 and 5092 DF, p-value: < 2.2e-16
\[ \text{Precio} = \beta_0 + \beta_1 \cdot \text{areaconst} + \beta_2 \cdot \text{estrato4} + \beta_3 \cdot \text{estrato5} + \beta_4 \cdot \text{estrato6} + \beta_5 \cdot \text{habitaciones} + \beta_6 \cdot \text{parqueaderos} + \beta_7 \cdot \text{banios} \]
Intercepto:El valor estimado del intercepto es -66.22, lo que significa que cuando todas las variables predictoras tienen un valor de 0, el precio de la vivienda sería -66.22 millones de COP. Este valor negativo del intercepto sugiere que el modelo puede no ser apropiado para extrapolar a valores extremos o fuera del rango de las variables observadas en los datos.
Área construida (`areaconst): El coeficiente es 1.95, lo que significa que, en promedio, por cada metro cuadrado adicional de área construida, el precio de la vivienda aumenta en 1.95 millones de COP, manteniendo constantes las demás variables. Este coeficiente es altamente significativo (p-valor < 2e-16).
Estrato:
Número de habitaciones
(habitaciones): El coeficiente es negativo
(-32.82), lo que sugiere que, manteniendo constantes las demás
variables, un aumento en el número de habitaciones se asocia con una
disminución en el precio de la vivienda en 32.82 millones de
COP.
Número de parqueaderos
(parqueaderos): Un parqueadero adicional se asocia
con un aumento de 83.56 millones de COP en el precio, manteniendo todo
lo demás constante.
Número de baños (banios): Cada baño
adicional se asocia con un aumento de 45.49 millones de COP en el
precio, lo que también es un efecto importante y significativo.
Todos los coeficientes tienen p-valores muy bajos, lo que indica que son altamente significativos y que existe suficiente evidencia para concluir que estas variables tienen un impacto real sobre el precio de las viviendas.
Esto significa que el 80.72% de la variabilidad en los precios de las viviendas puede explicarse por las variables incluidas en el modelo (área construida, estrato, número de habitaciones, parqueaderos y baños).
El valor ajustado es casi el mismo, lo que sugiere que el número de variables en el modelo no está influyendo de manera negativa en el ajuste del modelo.
Para validar los supuestos del modelo de regresión lineal multiple, se evalúan los aspectos como linealidad, homocedasticidad, normalidad de los residuos, independencia de los errores y multicolinealidad.
par(mfrow = c(2, 2))
plot(modelo, which = 1) #Linealidad y Homocedasticidad
plot(modelo, which = 2) #Normalidad de los residuos
hist(residuals(modelo),main = "Histograma de los residuos", xlab = "Residuos", breaks = 30) #Histograma de residuos
plot(modelo, which = 5) #Detección de puntos influyentes
ad.test(residuals(modelo)) #Anderson-Darling Normalidad
##
## Anderson-Darling normality test
##
## data: residuals(modelo)
## A = 211.74, p-value < 2.2e-16
El resultado sugiere que los residuos del modelo no se distribuyen normalmente, lo que puede afectar la validez de los intervalos de confianza y las pruebas de hipótesis que dependen del supuesto de normalidad de los errores.
Se puede intentar transformar la variable dependiente o las variables independientes para mejorar la normalidad de los residuos. Usando por ejemplo, transformaciones logarítmicas, cuadráticas o de Box-Cox.
vif(modelo) #Test de Multicolinealidad con VIF
## GVIF Df GVIF^(1/(2*Df))
## areaconst 2.595056 1 1.610918
## estrato 2.031266 3 1.125368
## habitaciones 1.428472 1 1.195187
## parqueaderos 2.101723 1 1.449732
## banios 3.021915 1 1.738366
Los valores de VIF indican que no hay multicolinealidad grave en las variables del modelo. Aunque el valor de VIF más alto es para el número de baños (1.74), sigue siendo bastante bajo. Esto implica que los coeficientes estimados del modelo son estables, y no hay una necesidad urgente de eliminar o combinar variables.
# R cuadrado y estadísticas relacionadas
rsq <- summary(modelo)$r.squared
adj_rsq <- summary(modelo)$adj.r.squared
mse <- summary(modelo)$sigma
estadisticas <- data.frame(
"R2" = rsq,
"R2_ajustado" = adj_rsq,
"Error estándar de la estimación" = mse)
kable(estadisticas, format = "markdown")
| R2 | R2_ajustado | Error.estándar.de.la.estimación |
|---|---|---|
| 0.8072373 | 0.8069723 | 127.0683 |
El modelo tiene un buen ajuste con un \(R^2\) y un \(R^2\) ajustado cercanos a 0.81, lo que sugiere que el modelo explica bien los datos. Sin embargo, el Error estándar es de 127.07 lo que significa que, en promedio, las predicciones del modelo difieren de los valores reales en aproximadamente 127 unidades, puede ser alto dependiendo de la escala de los precios de las casas.
Se realiza una partición en los datos de forma aleatoria donde el 70% es un set para entrenar el modelo y 30% para prueba. Se estima el modelo con la muestra del 70%.
set.seed(123)
# Partición 70% para entrenamiento y 30% para prueba
particion <- createDataPartition(bd_fin$preciom, p = 0.70,list = FALSE,times = 1)
bdtrain <- bd_fin[particion, ]
bdtest <- bd_fin[-particion, ]
modelo_ajus <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos +
banios, data = bdtrain)
summary(modelo_ajus)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = bdtrain)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1582.76 -46.11 0.13 39.56 1005.01
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -62.77851 11.43968 -5.488 4.35e-08 ***
## areaconst 1.91049 0.04911 38.900 < 2e-16 ***
## estrato4 26.59849 7.46417 3.563 0.000371 ***
## estrato5 50.04263 7.57955 6.602 4.65e-11 ***
## estrato6 196.43085 9.55759 20.552 < 2e-16 ***
## habitaciones -30.15853 3.72182 -8.103 7.29e-16 ***
## parqueaderos 82.70181 4.42541 18.688 < 2e-16 ***
## banios 40.56418 3.46532 11.706 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 127.4 on 3564 degrees of freedom
## Multiple R-squared: 0.7996, Adjusted R-squared: 0.7992
## F-statistic: 2032 on 7 and 3564 DF, p-value: < 2.2e-16
Usando los datos de prueba (30%) se realizan predicciones con el modelo anterior
prediccion = predict(modelo_ajus, newdata = bdtest)
comparacion = data.frame(Real = bdtest$preciom, Predicción = prediccion)
head(comparacion)
Se hace el calculo el error cuadrático medio, el error absoluto medio y el \(R^2\)
metricas <- postResample(pred = prediccion, obs = bdtest$preciom)
metricas
## RMSE Rsquared MAE
## 126.6924954 0.8232781 76.2202024
RMSE (Root Mean Square Error): Un valor de 126.69 indica que, en promedio, el modelo se equivoca por aproximadamente 126.69 unidades en el precio predicho.
\(R^2\) (Coeficiente de Determinación): Un valor de 0.82 significa que el modelo explica el 82% de la variabilidad en el precio de las casas. Esto sugiere un buen ajuste del modelo a los datos.
MAE (Mean Absolute Error): 76.22 Un MAE de 76.22 significa que, en promedio, las predicciones del modelo se desvían en 76.22 unidades del valor real. Es una métrica más robusta frente a los errores atípicos en comparación con el RMSE.
Estos resultados indican que el modelo tiene un rendimiento relativamente bueno en términos de precisión y capacidad de explicación.
Ajuste del Modelo: El modelo tiene un buen ajuste general, con un \(R^2\) alto y un \(R^2\) ajustado similar, sugiriendo que el modelo captura bien la variabilidad en los datos.
Precisión de las Predicciones: Los errores de predicción (MSE y MAE) proporcionarán información adicional sobre la precisión del modelo. Los valores específicos de MSE y MAE ayudarán a evaluar la precisión de las predicciones.
Problemas Potenciales: La falta de normalidad en los residuos y la autocorrelación positiva sugieren que el modelo puede no estar cumpliendo todos los supuestos ideales. Esto podría requerir ajustes en el modelo, como transformaciones de variables, inclusión de variables adicionales o el uso de modelos alternativos que no asuman normalidad de residuos.