1 Librerías

library(readr)
library(ggplot2)
library(lmtest)
library(car)
library(corrplot)
library(dplyr)
library(knitr)
library(leaflet)

2 Carga y Preparación de Datos

# 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 ...
summary(RE)
##        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ños
  • distancia_MRT: Distancia en metros a la estación de metro (MRT) más cercana
  • tiendas_cercanas: Número de tiendas de conveniencia en las inmediaciones
  • latitud / longitud: Coordenadas geográficas de la propiedad
  • precio_area: Variable dependiente — precio de la unidad de área (en miles de NTD por ping, aprox. 3.3 m²)

3 Análisis Exploratorio de Datos (EDA)

3.1 Subconjunto Numérico

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

3.2 Distribuciones — Variable Objetivo

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()

3.3 Boxplots — Detección Visual de Anomalías

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 variable distancia_MRT también presenta propiedades con distancias extremadamente grandes respecto a la mediana.


4 Correlación entre Variables

# 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")
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_MRT tiene 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_cercanas presenta la correlación más positiva con el precio (+0.57): mayor número de comercios implica mayor precio.
  • latitud también correlaciona positivamente (+0.52), lo que sugiere que ciertas zonas geográficas influyen fuertemente en el precio.

5 Dispersión y Regresión Simple

5.1 Precio vs. Distancia MRT

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()

5.2 Precio vs. Tiendas Cercanas

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()

5.3 Precio vs. Latitud (Zona Geográfica)

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()


6 Modelo de Regresión Lineal Simple

Se ajusta el modelo principal usando distancia_MRT como predictor (mayor correlación absoluta con el precio).

modelo <- lm(precio_area ~ distancia_MRT, data = RE.num)

summary(modelo)
## 
## 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
confint(modelo)
##                      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.


7 Métricas del Modelo Simple

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")
Métricas del Modelo de Regresión Simple
RMSE MAE R2 R2_Ajustado
10.0442 7.2349 0.4538 0.4524

8 Supuestos del Modelo

8.1 Normalidad de Residuos

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 (muestra ≤ 5000)
shapiro.test(modelo$residuals)
## 
##  Shapiro-Wilk normality test
## 
## data:  modelo$residuals
## W = 0.93245, p-value = 9.639e-13

8.2 Homocedasticidad (Breusch-Pagan)

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)

bptest(modelo)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 1.4186, df = 1, p-value = 0.2336

8.3 Independencia de Residuos (Durbin-Watson)

durbinWatsonTest(modelo)
##  lag Autocorrelation D-W Statistic p-value
##    1     -0.08017586      2.150686   0.132
##  Alternative hypothesis: rho != 0

8.4 Panel Diagnóstico Completo

par(mfrow = c(2, 2))
plot(modelo)

par(mfrow = c(1, 1))

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.

9 Detección de Anomalías en el Precio

9.1 Método 1 — Residuos Estandarizados (|z| > 2)

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)")
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()

9.2 Método 2 — IQR sobre el Precio (anomalías univariadas)

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
cat("Límite superior IQR:", round(limite_sup, 2), "\n")
## 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()


10 Análisis Geoespacial — Propiedades Caras por Ubicación

10.1 Mapa Interactivo

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

10.2 Top 10 Propiedades más Caras

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")
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.


11 Observaciones Influyentes

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)")
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()


12 Tratamiento de Anomalías — Comparación de Modelos

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")
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

13 Modelo de Regresión Múltiple

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
# Verificar multicolinealidad
vif(modelo_multi)
##    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")
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.


14 Validación del Modelo — Train / Test Split

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%)")
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()


15 Interpretación General

cat("## Resumen de Resultados\n\n")

15.1 Resumen de Resultados

cat("### 1. Correlaciones\n")

15.1.1 1. Correlaciones

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("### 2. Modelo Simple\n")

15.1.2 2. Modelo Simple

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("### 3. Modelo Múltiple\n")

15.1.3 3. Modelo Múltiple

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("### 4. Anomalías detectadas\n")

15.1.4 4. Anomalías detectadas

cat("- Por residuos estandarizados (|z| > 2):", sum(RE.num$outlier == "Atípico"), "observaciones.\n")
  • Por residuos estandarizados (|z| > 2): 17 observaciones.
cat("- Por método IQR:", sum(RE.num$anomalia_iqr == "Anomalía"), "observaciones.\n")
  • Por método IQR: 3 observaciones.
cat("- Observaciones influyentes (Cook > 4/n):", sum(RE.num$influyente == "Influyente"), ".\n\n")
  • Observaciones influyentes (Cook > 4/n): 18 .
cat("### 5. Propiedades caras por ubicación\n")

15.1.5 5. Propiedades caras por ubicació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.

cat("### 6. Validación Train/Test\n")

15.1.6 6. Validación Train/Test

cat("RMSE en test (modelo simple):", round(RMSE_test, 4), "\n")

RMSE en test (modelo simple): 13.0187

cat("RMSE en test (modelo múltiple):", round(RMSE_multi_test, 4), "\n")

RMSE en test (modelo múltiple): 11.8275

cat("El modelo múltiple generaliza mejor al conjunto de prueba.\n")

El modelo múltiple generaliza mejor al conjunto de prueba.