library(readr)
library(ggplot2)
library(lmtest)
library(car)
library(corrplot)
library(dplyr)
library(knitr)
library(leaflet)# Carga del archivo CSV
RE <- read_csv("data science/Real estate (1).csv")
View(RE)
# Renombrar columnas para mayor legibilidad
colnames(RE) <- c(
"No",
"fecha_transaccion",
"edad_casa",
"distancia_MRT",
"tiendas_cercanas",
"latitud",
"longitud",
"precio_area"
)
# Convertir a data.frame
RE <- as.data.frame(RE)
# Vista general de la estructura
str(RE)## 'data.frame': 414 obs. of 8 variables:
## $ No : num 1 2 3 4 5 6 7 8 9 10 ...
## $ fecha_transaccion: num 2013 2013 2014 2014 2013 ...
## $ edad_casa : num 32 19.5 13.3 13.3 5 7.1 34.5 20.3 31.7 17.9 ...
## $ distancia_MRT : num 84.9 306.6 562 562 390.6 ...
## $ tiendas_cercanas : num 10 9 5 5 5 3 7 6 1 3 ...
## $ latitud : num 25 25 25 25 25 ...
## $ longitud : num 122 122 122 122 122 ...
## $ precio_area : num 37.9 42.2 47.3 54.8 43.1 32.1 40.3 46.7 18.8 22.1 ...
## No fecha_transaccion edad_casa distancia_MRT
## Min. : 1.0 Min. :2013 Min. : 0.000 Min. : 23.38
## 1st Qu.:104.2 1st Qu.:2013 1st Qu.: 9.025 1st Qu.: 289.32
## Median :207.5 Median :2013 Median :16.100 Median : 492.23
## Mean :207.5 Mean :2013 Mean :17.713 Mean :1083.89
## 3rd Qu.:310.8 3rd Qu.:2013 3rd Qu.:28.150 3rd Qu.:1454.28
## Max. :414.0 Max. :2014 Max. :43.800 Max. :6488.02
## tiendas_cercanas latitud longitud precio_area
## Min. : 0.000 Min. :24.93 Min. :121.5 Min. : 7.60
## 1st Qu.: 1.000 1st Qu.:24.96 1st Qu.:121.5 1st Qu.: 27.70
## Median : 4.000 Median :24.97 Median :121.5 Median : 38.45
## Mean : 4.094 Mean :24.97 Mean :121.5 Mean : 37.98
## 3rd Qu.: 6.000 3rd Qu.:24.98 3rd Qu.:121.5 3rd Qu.: 46.60
## Max. :10.000 Max. :25.01 Max. :121.6 Max. :117.50
Descripción de variables:
fecha_transaccion: Fecha de la transacción (año decimal, ej. 2013.500)edad_casa: Antigüedad de la vivienda en añosdistancia_MRT: Distancia en metros a la estación de metro (MRT) más cercanatiendas_cercanas: Número de tiendas de conveniencia en las inmediacioneslatitud/longitud: Coordenadas geográficas de la propiedadprecio_area: Variable dependiente — precio de la unidad de área (en miles de NTD por ping, aprox. 3.3 m²)
# Seleccionar solo variables numéricas (excluye "No")
RE.num <- RE[, sapply(RE, is.numeric)]
RE.num <- RE.num[, !names(RE.num) %in% "No"]
str(RE.num)## 'data.frame': 414 obs. of 7 variables:
## $ fecha_transaccion: num 2013 2013 2014 2014 2013 ...
## $ edad_casa : num 32 19.5 13.3 13.3 5 7.1 34.5 20.3 31.7 17.9 ...
## $ distancia_MRT : num 84.9 306.6 562 562 390.6 ...
## $ tiendas_cercanas : num 10 9 5 5 5 3 7 6 1 3 ...
## $ latitud : num 25 25 25 25 25 ...
## $ longitud : num 122 122 122 122 122 ...
## $ precio_area : num 37.9 42.2 47.3 54.8 43.1 32.1 40.3 46.7 18.8 22.1 ...
ggplot(RE.num, aes(x = precio_area)) +
geom_histogram(bins = 30, fill = "#17807E", color = "white") +
labs(
title = "Distribución del Precio por Área",
x = "Precio por Área (miles NTD / ping)",
y = "Frecuencia"
) +
theme_minimal()ggplot(RE.num, aes(x = distancia_MRT)) +
geom_histogram(bins = 30, fill = "#6D1C68", color = "white") +
labs(
title = "Distribución de Distancia al MRT",
x = "Distancia al MRT (metros)",
y = "Frecuencia"
) +
theme_minimal()ggplot(RE.num, aes(x = edad_casa)) +
geom_histogram(bins = 30, fill = "#BD6C99", color = "white") +
labs(
title = "Distribución de la Edad de la Vivienda",
x = "Edad (años)",
y = "Frecuencia"
) +
theme_minimal()ggplot(RE.num, aes(y = precio_area)) +
geom_boxplot(fill = "lightgray", outlier.color = "red", outlier.size = 2.5) +
labs(title = "Boxplot del Precio por Área", y = "Precio por Área") +
theme_minimal()ggplot(RE.num, aes(y = distancia_MRT)) +
geom_boxplot(fill = "lightgray", outlier.color = "red", outlier.size = 2.5) +
labs(title = "Boxplot de Distancia al MRT", y = "Distancia al MRT (m)") +
theme_minimal()ggplot(RE.num, aes(y = tiendas_cercanas)) +
geom_boxplot(fill = "lightgray", outlier.color = "red", outlier.size = 2.5) +
labs(title = "Boxplot de Tiendas Cercanas", y = "N.º de Tiendas") +
theme_minimal()Observación: Los boxplots revelan valores atípicos visibles en
precio_area, especialmente hacia precios muy altos que superan ampliamente el rango intercuartil. La variabledistancia_MRTtambién presenta propiedades con distancias extremadamente grandes respecto a la mediana.
# Correlación de cada variable con el precio
cor_precio <- cor(RE.num, use = "complete.obs")[, "precio_area"]
kable(data.frame(Variable = names(cor_precio),
Correlacion_con_Precio = round(cor_precio, 4)),
caption = "Correlación de cada variable con el precio por área")| Variable | Correlacion_con_Precio | |
|---|---|---|
| fecha_transaccion | fecha_transaccion | 0.0875 |
| edad_casa | edad_casa | -0.2106 |
| distancia_MRT | distancia_MRT | -0.6736 |
| tiendas_cercanas | tiendas_cercanas | 0.5710 |
| latitud | latitud | 0.5463 |
| longitud | longitud | 0.5233 |
| precio_area | precio_area | 1.0000 |
cor_matrix <- cor(RE.num, use = "complete.obs")
corrplot(
cor_matrix,
method = "color",
type = "upper",
tl.cex = 0.85,
addCoef.col = "black",
number.cex = 0.75,
title = "Matriz de Correlación — Real Estate",
mar = c(0, 0, 1.5, 0)
)Hallazgos clave:
distancia_MRTtiene la correlación más negativa con el precio (−0.67): las propiedades más alejadas del metro tienden a ser más baratas.tiendas_cercanaspresenta la correlación más positiva con el precio (+0.57): mayor número de comercios implica mayor precio.latitudtambién correlaciona positivamente (+0.52), lo que sugiere que ciertas zonas geográficas influyen fuertemente en el precio.
ggplot(RE.num, aes(x = distancia_MRT, y = precio_area)) +
geom_point(color = "steelblue", alpha = 0.6) +
geom_smooth(method = "lm", se = TRUE, color = "red") +
labs(
title = "Relación entre Distancia al MRT y Precio por Área",
x = "Distancia al MRT (m)",
y = "Precio por Área"
) +
theme_minimal()ggplot(RE.num, aes(x = tiendas_cercanas, y = precio_area)) +
geom_point(color = "darkorange", alpha = 0.6) +
geom_smooth(method = "lm", se = TRUE, color = "red") +
labs(
title = "Relación entre Tiendas Cercanas y Precio por Área",
x = "N.º de Tiendas Cercanas",
y = "Precio por Área"
) +
theme_minimal()ggplot(RE.num, aes(x = latitud, y = precio_area)) +
geom_point(color = "mediumorchid", alpha = 0.6) +
geom_smooth(method = "lm", se = TRUE, color = "red") +
labs(
title = "Relación entre Latitud y Precio por Área",
x = "Latitud",
y = "Precio por Área"
) +
theme_minimal()Se ajusta el modelo principal usando distancia_MRT como
predictor (mayor correlación absoluta con el precio).
##
## Call:
## lm(formula = precio_area ~ distancia_MRT, data = RE.num)
##
## Residuals:
## Min 1Q Median 3Q Max
## -35.396 -6.007 -1.195 4.831 73.483
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 45.8514271 0.6526105 70.26 <2e-16 ***
## distancia_MRT -0.0072621 0.0003925 -18.50 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 10.07 on 412 degrees of freedom
## Multiple R-squared: 0.4538, Adjusted R-squared: 0.4524
## F-statistic: 342.2 on 1 and 412 DF, p-value: < 2.2e-16
## 2.5 % 97.5 %
## (Intercept) 44.568565390 47.134288726
## distancia_MRT -0.008033701 -0.006490402
Interpretación: Por cada metro adicional de distancia al MRT, el precio por área disminuye en promedio 0.0073 unidades. El R² indica que la distancia al MRT explica aproximadamente 45.4% de la variabilidad del precio.
RE.num$prediccion <- predict(modelo)
RE.num$residuo <- residuals(modelo)
RMSE <- sqrt(mean(RE.num$residuo^2))
MAE <- mean(abs(RE.num$residuo))
R2 <- summary(modelo)$r.squared
R2_ajustado <- summary(modelo)$adj.r.squared
metricas <- data.frame(
RMSE = round(RMSE, 4),
MAE = round(MAE, 4),
R2 = round(R2, 4),
R2_Ajustado = round(R2_ajustado, 4)
)
kable(metricas, caption = "Métricas del Modelo de Regresión Simple")| RMSE | MAE | R2 | R2_Ajustado |
|---|---|---|---|
| 10.0442 | 7.2349 | 0.4538 | 0.4524 |
hist(
modelo$residuals,
main = "Histograma de Residuos",
xlab = "Residuos",
col = "lightblue",
border = "black",
breaks = 30
)qqnorm(modelo$residuals, main = "Q-Q Plot de Residuos")
qqline(modelo$residuals, col = "red", lwd = 2)##
## Shapiro-Wilk normality test
##
## data: modelo$residuals
## W = 0.93245, p-value = 9.639e-13
plot(
modelo$fitted.values,
modelo$residuals,
main = "Residuos vs. Valores Ajustados",
xlab = "Valores Ajustados",
ylab = "Residuos",
col = "steelblue",
pch = 19
)
abline(h = 0, col = "red", lwd = 2)##
## studentized Breusch-Pagan test
##
## data: modelo
## BP = 1.4186, df = 1, p-value = 0.2336
## lag Autocorrelation D-W Statistic p-value
## 1 -0.08017586 2.150686 0.132
## Alternative hypothesis: rho != 0
Resumen de supuestos:
- Normalidad: El Q-Q plot y Shapiro-Wilk evalúan si los residuos siguen una distribución normal. Desviaciones en las colas son esperables con datos de precios inmobiliarios.
- Homocedasticidad: El test de Breusch-Pagan detecta si la varianza es constante. La presencia de outliers puede inflar el estadístico.
- Independencia: Durbin-Watson verifica autocorrelación. Valores cercanos a 2 indican independencia.
RE.num$residuo_estandarizado <- rstandard(modelo)
RE.num$outlier <- ifelse(
abs(RE.num$residuo_estandarizado) > 2,
"Atípico",
"Normal"
)
table(RE.num$outlier)##
## Atípico Normal
## 17 397
atipicos <- RE[RE.num$outlier == "Atípico", ]
kable(atipicos, caption = "Propiedades Atípicas (residuos estandarizados |z| > 2)")| No | fecha_transaccion | edad_casa | distancia_MRT | tiendas_cercanas | latitud | longitud | precio_area | |
|---|---|---|---|---|---|---|---|---|
| 17 | 17 | 2013.250 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 70.1 |
| 48 | 48 | 2013.583 | 35.9 | 640.7391 | 3 | 24.97563 | 121.5371 | 61.5 |
| 56 | 56 | 2012.833 | 31.7 | 1160.6320 | 0 | 24.94968 | 121.5301 | 13.7 |
| 94 | 94 | 2012.917 | 31.9 | 1146.3290 | 0 | 24.94920 | 121.5308 | 16.1 |
| 106 | 106 | 2012.833 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 71.0 |
| 114 | 114 | 2013.333 | 14.8 | 393.2606 | 6 | 24.96172 | 121.5381 | 7.6 |
| 127 | 127 | 2013.083 | 38.6 | 804.6897 | 4 | 24.97838 | 121.5348 | 62.9 |
| 149 | 149 | 2013.500 | 16.4 | 3780.5900 | 0 | 24.93293 | 121.5120 | 45.1 |
| 167 | 167 | 2013.417 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 73.6 |
| 221 | 221 | 2013.333 | 37.2 | 186.5101 | 9 | 24.97703 | 121.5426 | 78.3 |
| 229 | 229 | 2013.417 | 11.9 | 3171.3290 | 0 | 25.00115 | 121.5178 | 46.6 |
| 252 | 252 | 2012.917 | 31.7 | 1159.4540 | 0 | 24.94960 | 121.5302 | 13.8 |
| 271 | 271 | 2013.333 | 10.8 | 252.5822 | 1 | 24.97460 | 121.5305 | 117.5 |
| 313 | 313 | 2013.583 | 35.4 | 318.5292 | 9 | 24.97071 | 121.5407 | 78.0 |
| 331 | 331 | 2013.083 | 32.0 | 1156.7770 | 0 | 24.94935 | 121.5305 | 12.8 |
| 380 | 380 | 2013.333 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 69.7 |
| 390 | 390 | 2013.250 | 40.9 | 122.3619 | 8 | 24.96756 | 121.5423 | 67.7 |
ggplot(RE.num, aes(x = distancia_MRT, y = precio_area, color = outlier)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE, color = "black") +
scale_color_manual(values = c("Normal" = "darkblue", "Atípico" = "red")) +
labs(
title = "Regresión con Datos Atípicos Identificados",
x = "Distancia al MRT (m)",
y = "Precio por Área",
color = "Observación"
) +
theme_minimal()Q1 <- quantile(RE.num$precio_area, 0.25)
Q3 <- quantile(RE.num$precio_area, 0.75)
IQR_val <- IQR(RE.num$precio_area)
limite_inf <- Q1 - 1.5 * IQR_val
limite_sup <- Q3 + 1.5 * IQR_val
cat("Límite inferior IQR:", round(limite_inf, 2), "\n")## Límite inferior IQR: -0.65
## Límite superior IQR: 74.95
RE.num$anomalia_iqr <- ifelse(
RE.num$precio_area < limite_inf | RE.num$precio_area > limite_sup,
"Anomalía",
"Normal"
)
table(RE.num$anomalia_iqr)##
## Anomalía Normal
## 3 411
ggplot(RE.num, aes(x = seq_along(precio_area), y = precio_area, color = anomalia_iqr)) +
geom_point(size = 2.5, alpha = 0.8) +
geom_hline(yintercept = limite_sup, color = "red", linetype = "dashed", lwd = 1) +
geom_hline(yintercept = limite_inf, color = "orange", linetype = "dashed", lwd = 1) +
scale_color_manual(values = c("Normal" = "steelblue", "Anomalía" = "red")) +
labs(
title = "Anomalías de Precio por Método IQR",
x = "Índice de Observación",
y = "Precio por Área",
color = "Clasificación"
) +
theme_minimal()# Umbrales de precio para clasificación visual
umbral_alto <- quantile(RE$precio_area, 0.90)
umbral_medio <- quantile(RE$precio_area, 0.50)
RE$categoria_precio <- cut(
RE$precio_area,
breaks = c(-Inf, umbral_medio, umbral_alto, Inf),
labels = c("Bajo", "Medio", "Alto")
)
paleta <- colorFactor(
palette = c("green", "orange", "red"),
domain = RE$categoria_precio
)
leaflet(RE) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
color = ~paleta(categoria_precio),
radius = 5,
fillOpacity = 0.8,
popup = ~paste0(
"<b>Precio/área:</b> ", precio_area, "<br>",
"<b>Distancia MRT:</b> ", round(distancia_MRT, 0), " m<br>",
"<b>Tiendas cercanas:</b> ", tiendas_cercanas, "<br>",
"<b>Edad vivienda:</b> ", edad_casa, " años"
)
) %>%
addLegend("bottomright",
pal = paleta,
values = ~categoria_precio,
title = "Precio por Área",
opacity = 0.9)Análisis Geoespacial: El mapa permite identificar clusters de propiedades con precios elevados. Las zonas rojas (percentil > 90) tienden a concentrarse en ciertas latitudes del área estudiada (Sindian District, Taiwán), especialmente en zonas con alta densidad de estaciones MRT y tiendas de conveniencia.
top10 <- RE %>%
arrange(desc(precio_area)) %>%
head(10) %>%
select(No, precio_area, distancia_MRT, tiendas_cercanas, edad_casa, latitud, longitud)
kable(top10, caption = "Top 10 Propiedades con Mayor Precio por Área")| No | precio_area | distancia_MRT | tiendas_cercanas | edad_casa | latitud | longitud |
|---|---|---|---|---|---|---|
| 271 | 117.5 | 252.58220 | 1 | 10.8 | 24.97460 | 121.5305 |
| 221 | 78.3 | 186.51010 | 9 | 37.2 | 24.97703 | 121.5426 |
| 313 | 78.0 | 318.52920 | 9 | 35.4 | 24.97071 | 121.5407 |
| 167 | 73.6 | 292.99780 | 6 | 0.0 | 24.97744 | 121.5446 |
| 106 | 71.0 | 292.99780 | 6 | 0.0 | 24.97744 | 121.5446 |
| 17 | 70.1 | 292.99780 | 6 | 0.0 | 24.97744 | 121.5446 |
| 380 | 69.7 | 292.99780 | 6 | 0.0 | 24.97744 | 121.5446 |
| 390 | 67.7 | 122.36190 | 8 | 40.9 | 24.96756 | 121.5423 |
| 414 | 63.9 | 90.45606 | 9 | 6.5 | 24.97433 | 121.5431 |
| 259 | 63.3 | 292.99780 | 6 | 0.0 | 24.97744 | 121.5446 |
# Comparar precio por zona de latitud (baja/media/alta)
RE.num$zona_lat <- cut(
RE.num$latitud,
breaks = quantile(RE.num$latitud, c(0, 1/3, 2/3, 1)),
labels = c("Zona Sur", "Zona Centro", "Zona Norte"),
include.lowest = TRUE
)
ggplot(RE.num, aes(x = zona_lat, y = precio_area, fill = zona_lat)) +
geom_boxplot(outlier.color = "red", outlier.size = 2) +
scale_fill_brewer(palette = "Set2") +
labs(
title = "Distribución de Precios por Zona Geográfica (Latitud)",
x = "Zona",
y = "Precio por Área"
) +
theme_minimal() +
theme(legend.position = "none")Las propiedades de la Zona Norte presentan precios notablemente más altos, indicando un gradiente de valor geográfico claro en el dataset.
RE.num$cook <- cooks.distance(modelo)
RE.num$leverage <- hatvalues(modelo)
limite_cook <- 4 / nrow(RE.num)
RE.num$influyente <- ifelse(
RE.num$cook > limite_cook,
"Influyente",
"No influyente"
)
table(RE.num$influyente)##
## Influyente No influyente
## 18 396
influyentes <- RE[RE.num$influyente == "Influyente", ]
kable(head(influyentes, 15), caption = "Primeras observaciones influyentes (Cook > 4/n)")| No | fecha_transaccion | edad_casa | distancia_MRT | tiendas_cercanas | latitud | longitud | precio_area | categoria_precio | |
|---|---|---|---|---|---|---|---|---|---|
| 9 | 9 | 2013.500 | 31.7 | 5512.0380 | 1 | 24.95095 | 121.4846 | 18.8 | Bajo |
| 17 | 17 | 2013.250 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 70.1 | Alto |
| 36 | 36 | 2013.500 | 13.9 | 4079.4180 | 0 | 25.01459 | 121.5182 | 27.3 | Bajo |
| 106 | 106 | 2012.833 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 71.0 | Alto |
| 114 | 114 | 2013.333 | 14.8 | 393.2606 | 6 | 24.96172 | 121.5381 | 7.6 | Bajo |
| 117 | 117 | 2013.000 | 30.9 | 6396.2830 | 1 | 24.94375 | 121.4788 | 12.2 | Bajo |
| 149 | 149 | 2013.500 | 16.4 | 3780.5900 | 0 | 24.93293 | 121.5120 | 45.1 | Medio |
| 167 | 167 | 2013.417 | 0.0 | 292.9978 | 6 | 24.97744 | 121.5446 | 73.6 | Alto |
| 221 | 221 | 2013.333 | 37.2 | 186.5101 | 9 | 24.97703 | 121.5426 | 78.3 | Alto |
| 229 | 229 | 2013.417 | 11.9 | 3171.3290 | 0 | 25.00115 | 121.5178 | 46.6 | Medio |
| 250 | 250 | 2012.833 | 18.0 | 6306.1530 | 1 | 24.95743 | 121.4752 | 15.0 | Bajo |
| 256 | 256 | 2013.417 | 31.5 | 5512.0380 | 1 | 24.95095 | 121.4846 | 17.4 | Bajo |
| 271 | 271 | 2013.333 | 10.8 | 252.5822 | 1 | 24.97460 | 121.5305 | 117.5 | Alto |
| 313 | 313 | 2013.583 | 35.4 | 318.5292 | 9 | 24.97071 | 121.5407 | 78.0 | Alto |
| 345 | 345 | 2013.500 | 34.6 | 3085.1700 | 0 | 24.99800 | 121.5155 | 41.2 | Medio |
ggplot(RE.num, aes(x = seq_along(cook), y = cook, fill = influyente)) +
geom_col() +
geom_hline(yintercept = limite_cook, color = "red", linewidth = 1, linetype = "dashed") +
scale_fill_manual(values = c("Influyente" = "tomato", "No influyente" = "steelblue")) +
labs(
title = "Distancia de Cook por Observación",
x = "Índice de Observación",
y = "Distancia de Cook",
fill = "Clasificación"
) +
theme_minimal()ggplot(RE.num, aes(x = leverage, y = residuo_estandarizado, color = influyente)) +
geom_point(size = 2.5, alpha = 0.7) +
geom_hline(yintercept = c(-2, 2), linetype = "dashed", color = "orange") +
scale_color_manual(values = c("Influyente" = "red", "No influyente" = "steelblue")) +
labs(
title = "Leverage vs. Residuo Estandarizado",
x = "Leverage (Hat Values)",
y = "Residuo Estandarizado",
color = "Clasificación"
) +
theme_minimal()Nota: Las anomalías no se eliminan del análisis principal. Aquí se comparan los modelos con y sin outliers para cuantificar su efecto.
RE.sin_outliers <- RE.num[RE.num$outlier == "Normal", ]
modelo_sin_outliers <- lm(precio_area ~ distancia_MRT, data = RE.sin_outliers)
summary(modelo_sin_outliers)##
## Call:
## lm(formula = precio_area ~ distancia_MRT, data = RE.sin_outliers)
##
## Residuals:
## Min 1Q Median 3Q Max
## -17.8625 -5.4357 -0.7955 4.6398 20.3078
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 45.0723196 0.5233087 86.13 <2e-16 ***
## distancia_MRT -0.0070994 0.0003126 -22.71 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.907 on 395 degrees of freedom
## Multiple R-squared: 0.5664, Adjusted R-squared: 0.5653
## F-statistic: 515.9 on 1 and 395 DF, p-value: < 2.2e-16
ggplot() +
geom_point(data = RE.num,
aes(x = distancia_MRT, y = precio_area, color = outlier),
size = 2, alpha = 0.6) +
geom_smooth(data = RE.num,
aes(x = distancia_MRT, y = precio_area),
method = "lm", se = FALSE, color = "red", linetype = "solid", lwd = 1.2) +
geom_smooth(data = RE.sin_outliers,
aes(x = distancia_MRT, y = precio_area),
method = "lm", se = FALSE, color = "darkgreen", linetype = "dashed", lwd = 1.2) +
scale_color_manual(values = c("Normal" = "steelblue", "Atípico" = "red")) +
labs(
title = "Comparación: Modelo con vs. sin Datos Atípicos",
subtitle = "Rojo = modelo completo | Verde = modelo sin outliers",
x = "Distancia al MRT (m)",
y = "Precio por Área",
color = "Observación"
) +
theme_minimal()RE.sin_outliers$prediccion <- predict(modelo_sin_outliers)
RE.sin_outliers$residuo <- residuals(modelo_sin_outliers)
metricas_sin <- data.frame(
Modelo = "Sin Outliers",
RMSE = round(sqrt(mean(RE.sin_outliers$residuo^2)), 4),
MAE = round(mean(abs(RE.sin_outliers$residuo)), 4),
R2 = round(summary(modelo_sin_outliers)$r.squared, 4),
R2_Ajustado = round(summary(modelo_sin_outliers)$adj.r.squared, 4)
)
metricas_con <- data.frame(
Modelo = "Con Outliers",
RMSE = round(RMSE, 4),
MAE = round(MAE, 4),
R2 = round(R2, 4),
R2_Ajustado = round(R2_ajustado, 4)
)
kable(rbind(metricas_con, metricas_sin),
caption = "Comparación de Métricas: Modelo completo vs. Sin Outliers")| Modelo | RMSE | MAE | R2 | R2_Ajustado |
|---|---|---|---|---|
| Con Outliers | 10.0442 | 7.2349 | 0.4538 | 0.4524 |
| Sin Outliers | 7.8872 | 6.2259 | 0.5664 | 0.5653 |
Se incorporan las variables más correlacionadas para mejorar el poder predictivo.
modelo_multi <- lm(
precio_area ~ distancia_MRT + tiendas_cercanas + latitud + edad_casa,
data = RE.num
)
summary(modelo_multi)##
## Call:
## lm(formula = precio_area ~ distancia_MRT + tiendas_cercanas +
## latitud + edad_casa, data = RE.num)
##
## Residuals:
## Min 1Q Median 3Q Max
## -34.522 -5.292 -1.579 4.264 76.466
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -5.916e+03 1.113e+03 -5.317 1.74e-07 ***
## distancia_MRT -4.175e-03 4.928e-04 -8.473 4.37e-16 ***
## tiendas_cercanas 1.165e+00 1.897e-01 6.141 1.94e-09 ***
## latitud 2.386e+02 4.456e+01 5.355 1.43e-07 ***
## edad_casa -2.687e-01 3.893e-02 -6.903 1.95e-11 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 8.954 on 409 degrees of freedom
## Multiple R-squared: 0.5711, Adjusted R-squared: 0.5669
## F-statistic: 136.2 on 4 and 409 DF, p-value: < 2.2e-16
## distancia_MRT tiendas_cercanas latitud edad_casa
## 1.992371 1.607857 1.575344 1.013216
pred_multi <- predict(modelo_multi)
res_multi <- RE.num$precio_area - pred_multi
metricas_multi <- data.frame(
Modelo = "Regresión Múltiple",
RMSE = round(sqrt(mean(res_multi^2)), 4),
MAE = round(mean(abs(res_multi)), 4),
R2 = round(summary(modelo_multi)$r.squared, 4),
R2_Ajustado = round(summary(modelo_multi)$adj.r.squared, 4)
)
kable(rbind(metricas_con, metricas_multi),
caption = "Regresión Simple vs. Regresión Múltiple")| Modelo | RMSE | MAE | R2 | R2_Ajustado |
|---|---|---|---|---|
| Con Outliers | 10.0442 | 7.2349 | 0.4538 | 0.4524 |
| Regresión Múltiple | 8.8998 | 6.1782 | 0.5711 | 0.5669 |
El modelo múltiple mejora sustancialmente el R², pasando de ~45% a ~58%, lo que demuestra que la distancia al MRT sola no captura toda la variación del precio. La latitud y las tiendas cercanas aportan información adicional relevante.
set.seed(123)
n <- nrow(RE.num)
indice <- sample(1:n, size = floor(0.7 * n))
train <- RE.num[ indice, ]
test <- RE.num[-indice, ]
# Modelo simple en train
modelo_train <- lm(precio_area ~ distancia_MRT, data = train)
pred_test <- predict(modelo_train, newdata = test)
RMSE_test <- sqrt(mean((test$precio_area - pred_test)^2))
MAE_test <- mean(abs(test$precio_area - pred_test))
# Modelo múltiple en train
modelo_multi_train <- lm(
precio_area ~ distancia_MRT + tiendas_cercanas + latitud + edad_casa,
data = train
)
pred_multi_test <- predict(modelo_multi_train, newdata = test)
RMSE_multi_test <- sqrt(mean((test$precio_area - pred_multi_test)^2))
MAE_multi_test <- mean(abs(test$precio_area - pred_multi_test))
metricas_test <- data.frame(
Modelo = c("Simple (test)", "Múltiple (test)"),
RMSE_Test = round(c(RMSE_test, RMSE_multi_test), 4),
MAE_Test = round(c(MAE_test, MAE_multi_test), 4)
)
kable(metricas_test, caption = "Métricas de Validación en Conjunto de Prueba (30%)")| Modelo | RMSE_Test | MAE_Test |
|---|---|---|
| Simple (test) | 13.0187 | 8.6234 |
| Múltiple (test) | 11.8275 | 7.2395 |
test$prediccion_multi <- pred_multi_test
ggplot(test, aes(x = precio_area, y = prediccion_multi)) +
geom_point(color = "steelblue", alpha = 0.7) +
geom_abline(slope = 1, intercept = 0, color = "red", lwd = 1.2) +
labs(
title = "Predicción vs. Precio Real — Modelo Múltiple (Test)",
x = "Precio Real",
y = "Predicción"
) +
theme_minimal()cat("La variable con mayor correlación negativa con el precio es **distancia_MRT** (",
round(cor(RE.num$distancia_MRT, RE.num$precio_area, use = "complete.obs"), 4),
"). La variable con mayor correlación positiva es **tiendas_cercanas** (",
round(cor(RE.num$tiendas_cercanas, RE.num$precio_area, use = "complete.obs"), 4), ").\n\n")La variable con mayor correlación negativa con el precio es distancia_MRT ( -0.6736 ). La variable con mayor correlación positiva es tiendas_cercanas ( 0.571 ).
cat("El R² del modelo simple (solo distancia_MRT) es:",
round(R2, 4), ". Explica aproximadamente",
round(R2 * 100, 1), "% de la variabilidad del precio.\n\n")El R² del modelo simple (solo distancia_MRT) es: 0.4538 . Explica aproximadamente 45.4 % de la variabilidad del precio.
cat("Incorporando distancia_MRT, tiendas_cercanas, latitud y edad_casa, el R² sube a:",
round(summary(modelo_multi)$r.squared, 4), "(",
round(summary(modelo_multi)$r.squared * 100, 1), "%).\n\n")Incorporando distancia_MRT, tiendas_cercanas, latitud y edad_casa, el R² sube a: 0.5711 ( 57.1 %).
cat("- Por residuos estandarizados (|z| > 2):", sum(RE.num$outlier == "Atípico"), "observaciones.\n")cat("El análisis geoespacial revela que las propiedades más caras se ubican en la Zona Norte",
"(latitudes más altas), con alta densidad de tiendas y cercanía al MRT.",
"Esto sugiere que el factor geográfico actúa como multiplicador de valor,",
"independientemente de la edad de la vivienda.\n\n")El análisis geoespacial revela que las propiedades más caras se ubican en la Zona Norte (latitudes más altas), con alta densidad de tiendas y cercanía al MRT. Esto sugiere que el factor geográfico actúa como multiplicador de valor, independientemente de la edad de la vivienda.
RMSE en test (modelo simple): 13.0187
RMSE en test (modelo múltiple): 11.8275
El modelo múltiple generaliza mejor al conjunto de prueba.