Proyecto Final: Competencia de Kaggle

Nombre: Francisco Gonzalez

Carnet: 24002914

Carga de librerias

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(readr)
library(ggplot2)
library(corrplot)
## corrplot 0.95 loaded
library(tidyr)

Carga del archivo train

# 1. Cargar los datos
df <- read_csv("train.csv")
## Rows: 14447 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): ocean_proximity
## dbl (10): id, longitude, latitude, housing_median_age, total_rooms, total_be...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

EDA

# 2. Descripción general del dataset
cat("Dimensiones del dataset:\n")
## Dimensiones del dataset:
dim(df)
## [1] 14447    11
cat("\nPrimeras filas:\n")
## 
## Primeras filas:
print(head(df))
## # A tibble: 6 × 11
##      id longitude latitude housing_median_age total_rooms total_bedrooms
##   <dbl>     <dbl>    <dbl>              <dbl>       <dbl>          <dbl>
## 1  9744     -122.     36.8                 15        2191            358
## 2 13893     -116.     34.1                 37         452            109
## 3 18277     -122.     37.3                 35        1172            184
## 4 16176     -122.     37.7                 52         126             24
## 5  8843     -118.     34.1                 28        4001           1352
## 6  7653     -118.     33.8                 28        2152            415
## # ℹ 5 more variables: population <dbl>, households <dbl>, median_income <dbl>,
## #   median_house_value <dbl>, ocean_proximity <chr>
cat("\nResumen estadístico:\n")
## 
## Resumen estadístico:
print(summary(df))
##        id          longitude         latitude     housing_median_age
##  Min.   :    1   Min.   :-124.3   Min.   :32.54   Min.   : 1.00     
##  1st Qu.: 5140   1st Qu.:-121.8   1st Qu.:33.93   1st Qu.:18.00     
##  Median :10210   Median :-118.5   Median :34.26   Median :29.00     
##  Mean   :10275   Mean   :-119.6   Mean   :35.64   Mean   :28.85     
##  3rd Qu.:15449   3rd Qu.:-118.0   3rd Qu.:37.72   3rd Qu.:37.00     
##  Max.   :20640   Max.   :-114.3   Max.   :41.95   Max.   :52.00     
##                                                                     
##   total_rooms    total_bedrooms     population      households    
##  Min.   :    2   Min.   :   1.0   Min.   :    6   Min.   :   1.0  
##  1st Qu.: 1444   1st Qu.: 295.0   1st Qu.:  786   1st Qu.: 280.0  
##  Median : 2121   Median : 433.0   Median : 1163   Median : 408.0  
##  Mean   : 2635   Mean   : 537.8   Mean   : 1425   Mean   : 500.1  
##  3rd Qu.: 3138   3rd Qu.: 647.0   3rd Qu.: 1722   3rd Qu.: 604.5  
##  Max.   :39320   Max.   :6445.0   Max.   :28566   Max.   :6082.0  
##                  NA's   :137                                      
##  median_income     median_house_value ocean_proximity   
##  Min.   : 0.4999   Min.   : 14999     Length:14447      
##  1st Qu.: 2.5671   1st Qu.:119600     Class :character  
##  Median : 3.5350   Median :179700     Mode  :character  
##  Mean   : 3.8639   Mean   :206874                       
##  3rd Qu.: 4.7229   3rd Qu.:264600                       
##  Max.   :15.0001   Max.   :500001                       
## 
cat("\nTipos de datos:\n")
## 
## Tipos de datos:
str(df)
## spc_tbl_ [14,447 × 11] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id                : num [1:14447] 9744 13893 18277 16176 8843 ...
##  $ longitude         : num [1:14447] -122 -116 -122 -122 -118 ...
##  $ latitude          : num [1:14447] 36.8 34.1 37.3 37.7 34.1 ...
##  $ housing_median_age: num [1:14447] 15 37 35 52 28 28 23 40 18 52 ...
##  $ total_rooms       : num [1:14447] 2191 452 1172 126 4001 ...
##  $ total_bedrooms    : num [1:14447] 358 109 184 24 1352 ...
##  $ population        : num [1:14447] 1150 184 512 37 1799 ...
##  $ households        : num [1:14447] 330 59 175 27 1220 429 1130 110 439 53 ...
##  $ median_income     : num [1:14447] 4.8 3.73 7.36 10.23 2.58 ...
##  $ median_house_value: num [1:14447] 227500 65800 500001 225000 272900 ...
##  $ ocean_proximity   : chr [1:14447] "<1H OCEAN" "INLAND" "<1H OCEAN" "NEAR BAY" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   id = col_double(),
##   ..   longitude = col_double(),
##   ..   latitude = col_double(),
##   ..   housing_median_age = col_double(),
##   ..   total_rooms = col_double(),
##   ..   total_bedrooms = col_double(),
##   ..   population = col_double(),
##   ..   households = col_double(),
##   ..   median_income = col_double(),
##   ..   median_house_value = col_double(),
##   ..   ocean_proximity = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>
# 3. Revisión de valores nulos
cat("\nConteo de valores NA por variable:\n")
## 
## Conteo de valores NA por variable:
print(colSums(is.na(df)))
##                 id          longitude           latitude housing_median_age 
##                  0                  0                  0                  0 
##        total_rooms     total_bedrooms         population         households 
##                  0                137                  0                  0 
##      median_income median_house_value    ocean_proximity 
##                  0                  0                  0
# 4. Distribución de variables numéricas
num_vars <- df %>% select(where(is.numeric)) %>% select(-id)

for (col in names(num_vars)) {
  print(
    ggplot(df, aes_string(x = col)) +
      geom_histogram(bins = 30, fill = "steelblue", color = "white") +
      labs(title = paste("Histograma de", col), x = col, y = "Frecuencia")
  )
}
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

## Warning: Removed 137 rows containing non-finite outside the scale range
## (`stat_bin()`).

# 5. Distribución de variable categórica (ocean_proximity)
print(
  ggplot(df, aes(x = ocean_proximity)) +
    geom_bar(fill = "coral") +
    labs(title = "Conteo por categoría: ocean_proximity", x = "ocean_proximity", y = "Frecuencia") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
)

# 6. Correlación entre variables numéricas
corr <- cor(num_vars, use = "complete.obs")
print(corrplot(corr, method = "color", tl.cex = 0.7, number.cex = 0.7))

## $corr
##                      longitude     latitude housing_median_age total_rooms
## longitude           1.00000000 -0.925276861       -0.108724857  0.04308386
## latitude           -0.92527686  1.000000000        0.009975766 -0.03415305
## housing_median_age -0.10872486  0.009975766        1.000000000 -0.36043960
## total_rooms         0.04308386 -0.034153051       -0.360439596  1.00000000
## total_bedrooms      0.06747626 -0.064580439       -0.322601658  0.93081258
## population          0.10004100 -0.106998172       -0.303472841  0.87057074
## households          0.05586774 -0.069709284       -0.305525485  0.92130168
## median_income      -0.01342858 -0.081630247       -0.112757577  0.20178604
## median_house_value -0.04848349 -0.143927728        0.112052285  0.13692854
##                    total_bedrooms  population  households median_income
## longitude            0.0674762647  0.10004100  0.05586774 -0.0134285771
## latitude            -0.0645804386 -0.10699817 -0.06970928 -0.0816302471
## housing_median_age  -0.3226016579 -0.30347284 -0.30552549 -0.1127575767
## total_rooms          0.9308125825  0.87057074  0.92130168  0.2017860449
## total_bedrooms       1.0000000000  0.89006702  0.98176657 -0.0006637225
## population           0.8900670221  1.00000000  0.91589957  0.0120161690
## households           0.9817665656  0.91589957  1.00000000  0.0190061583
## median_income       -0.0006637225  0.01201617  0.01900616  1.0000000000
## median_house_value   0.0564098147 -0.01988276  0.07063649  0.6876749992
##                    median_house_value
## longitude                 -0.04848349
## latitude                  -0.14392773
## housing_median_age         0.11205229
## total_rooms                0.13692854
## total_bedrooms             0.05640981
## population                -0.01988276
## households                 0.07063649
## median_income              0.68767500
## median_house_value         1.00000000
## 
## $corrPos
##                 xName              yName x y          corr
## 1           longitude          longitude 1 9  1.0000000000
## 2           longitude           latitude 1 8 -0.9252768614
## 3           longitude housing_median_age 1 7 -0.1087248574
## 4           longitude        total_rooms 1 6  0.0430838641
## 5           longitude     total_bedrooms 1 5  0.0674762647
## 6           longitude         population 1 4  0.1000409986
## 7           longitude         households 1 3  0.0558677414
## 8           longitude      median_income 1 2 -0.0134285771
## 9           longitude median_house_value 1 1 -0.0484834914
## 10           latitude          longitude 2 9 -0.9252768614
## 11           latitude           latitude 2 8  1.0000000000
## 12           latitude housing_median_age 2 7  0.0099757660
## 13           latitude        total_rooms 2 6 -0.0341530507
## 14           latitude     total_bedrooms 2 5 -0.0645804386
## 15           latitude         population 2 4 -0.1069981718
## 16           latitude         households 2 3 -0.0697092844
## 17           latitude      median_income 2 2 -0.0816302471
## 18           latitude median_house_value 2 1 -0.1439277280
## 19 housing_median_age          longitude 3 9 -0.1087248574
## 20 housing_median_age           latitude 3 8  0.0099757660
## 21 housing_median_age housing_median_age 3 7  1.0000000000
## 22 housing_median_age        total_rooms 3 6 -0.3604395957
## 23 housing_median_age     total_bedrooms 3 5 -0.3226016579
## 24 housing_median_age         population 3 4 -0.3034728414
## 25 housing_median_age         households 3 3 -0.3055254853
## 26 housing_median_age      median_income 3 2 -0.1127575767
## 27 housing_median_age median_house_value 3 1  0.1120522851
## 28        total_rooms          longitude 4 9  0.0430838641
## 29        total_rooms           latitude 4 8 -0.0341530507
## 30        total_rooms housing_median_age 4 7 -0.3604395957
## 31        total_rooms        total_rooms 4 6  1.0000000000
## 32        total_rooms     total_bedrooms 4 5  0.9308125825
## 33        total_rooms         population 4 4  0.8705707393
## 34        total_rooms         households 4 3  0.9213016832
## 35        total_rooms      median_income 4 2  0.2017860449
## 36        total_rooms median_house_value 4 1  0.1369285409
## 37     total_bedrooms          longitude 5 9  0.0674762647
## 38     total_bedrooms           latitude 5 8 -0.0645804386
## 39     total_bedrooms housing_median_age 5 7 -0.3226016579
## 40     total_bedrooms        total_rooms 5 6  0.9308125825
## 41     total_bedrooms     total_bedrooms 5 5  1.0000000000
## 42     total_bedrooms         population 5 4  0.8900670221
## 43     total_bedrooms         households 5 3  0.9817665656
## 44     total_bedrooms      median_income 5 2 -0.0006637225
## 45     total_bedrooms median_house_value 5 1  0.0564098147
## 46         population          longitude 6 9  0.1000409986
## 47         population           latitude 6 8 -0.1069981718
## 48         population housing_median_age 6 7 -0.3034728414
## 49         population        total_rooms 6 6  0.8705707393
## 50         population     total_bedrooms 6 5  0.8900670221
## 51         population         population 6 4  1.0000000000
## 52         population         households 6 3  0.9158995656
## 53         population      median_income 6 2  0.0120161690
## 54         population median_house_value 6 1 -0.0198827622
## 55         households          longitude 7 9  0.0558677414
## 56         households           latitude 7 8 -0.0697092844
## 57         households housing_median_age 7 7 -0.3055254853
## 58         households        total_rooms 7 6  0.9213016832
## 59         households     total_bedrooms 7 5  0.9817665656
## 60         households         population 7 4  0.9158995656
## 61         households         households 7 3  1.0000000000
## 62         households      median_income 7 2  0.0190061583
## 63         households median_house_value 7 1  0.0706364872
## 64      median_income          longitude 8 9 -0.0134285771
## 65      median_income           latitude 8 8 -0.0816302471
## 66      median_income housing_median_age 8 7 -0.1127575767
## 67      median_income        total_rooms 8 6  0.2017860449
## 68      median_income     total_bedrooms 8 5 -0.0006637225
## 69      median_income         population 8 4  0.0120161690
## 70      median_income         households 8 3  0.0190061583
## 71      median_income      median_income 8 2  1.0000000000
## 72      median_income median_house_value 8 1  0.6876749992
## 73 median_house_value          longitude 9 9 -0.0484834914
## 74 median_house_value           latitude 9 8 -0.1439277280
## 75 median_house_value housing_median_age 9 7  0.1120522851
## 76 median_house_value        total_rooms 9 6  0.1369285409
## 77 median_house_value     total_bedrooms 9 5  0.0564098147
## 78 median_house_value         population 9 4 -0.0198827622
## 79 median_house_value         households 9 3  0.0706364872
## 80 median_house_value      median_income 9 2  0.6876749992
## 81 median_house_value median_house_value 9 1  1.0000000000
## 
## $arg
## $arg$type
## [1] "full"
# 7. Boxplots de precio de vivienda vs. proximidad al océano
print(
  ggplot(df, aes(x = ocean_proximity, y = median_house_value)) +
    geom_boxplot(fill = "lightgreen") +
    labs(title = "Distribución de precio de vivienda por proximidad al océano",
         x = "ocean_proximity", y = "median_house_value") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))
)

# 8. Gráfico de dispersión: median_income vs. median_house_value
print(
  ggplot(df, aes(x = median_income, y = median_house_value, color = ocean_proximity)) +
    geom_point(alpha = 0.6) +
    labs(title = "Ingreso medio vs. precio de vivienda",
         x = "median_income", y = "median_house_value")
)

# 9. Mapa de localización: latitude vs longitude coloreado por precio de vivienda
library(ggplot2)         # Por si no está cargado
library(ggmap)          # Por si no está cargado
## ℹ Google's Terms of Service: <https://mapsplatform.google.com>
##   Stadia Maps' Terms of Service: <https://stadiamaps.com/terms-of-service>
##   OpenStreetMap's Tile Usage Policy: <https://operations.osmfoundation.org/policies/tiles>
## ℹ Please cite ggmap if you use it! Use `citation("ggmap")` for details.
# Puedes usar el centro geográfico (centro de tus datos)
# Definir el centro de tus datos
centro <- c(lon = median(df$longitude), lat = median(df$latitude)+median(df$latitude)*.10)

ggmap::register_google(key = Sys.getenv("GOOGLE_API_KEY"))

ggmap::has_google_key()
## [1] TRUE
map <- get_googlemap(
  center = centro,
  zoom = 6,
  size = c(640, 640),
  scale = 1,
  maptype = "terrain"
)
## ℹ <https://maps.googleapis.com/maps/api/staticmap?center=37.686,-118.5&zoom=6&size=640x640&scale=1&maptype=terrain&key=xxx-8Pzey8_I5kAHk>
# Crear un dataframe solo con las filas completas para las columnas relevantes
df_mapa <- df %>% 
  filter(!is.na(longitude), !is.na(latitude), !is.na(median_house_value))

# Usar este dataframe limpio en tu gráfico
ggmap(map) +
  geom_point(
    data = df_mapa,
    aes(x = longitude, y = latitude, color = median_house_value),
    alpha = 0.7, size = 2
  ) +
  scale_color_viridis_c() +
  labs(
    title = "Valor medio de vivienda por ubicación geográfica en California",
    x = "Longitud", y = "Latitud",
    color = "Median House Value"
  ) +
  theme(legend.position = "right")

Conclusiones EDA

1. Boxplot: Precio de vivienda según proximidad al océano

  • Los precios más altos de vivienda están en las categorías ISLAND, NEAR BAY y NEAR OCEAN.

  • La categoría INLAND tiene los precios más bajos y además más dispersión (muchos valores bajos, algunos altos como outliers).

  • Vivir cerca de la costa o la bahía está claramente asociado a un mayor valor de las viviendas.

2. Dispersión: Ingreso medio vs. precio de vivienda

  • Hay una fuerte correlación positiva: a mayor ingreso medio, mayor valor medio de la vivienda.

  • Existe un “tope” aparente en el precio de la vivienda (muchos puntos pegados en $500,000), lo que indica un capping (probablemente un límite superior en el dataset original).

  • Las viviendas cerca del océano y bahía suelen estar asociadas tanto a mayores ingresos como a mayores valores de vivienda.

3. Mapa geográfico: Precio de vivienda en California

  • Las áreas costeras, especialmente el área de la bahía de San Francisco y zonas de Los Ángeles, muestran valores de vivienda mucho más altos (zonas amarillas y verdes claros).

  • El interior del estado (INLAND) tiene predominantemente valores bajos (púrpuras y azules).

  • Patrón geográfico claro: el precio aumenta al acercarse al océano y áreas urbanas principales.

4. Matriz de correlación

  • La mayor correlación positiva es entre ingreso medio y valor de vivienda.

  • Otras variables con alta correlación son total_rooms, households, population, pero con menos impacto directo en el precio.

  • Latitude y longitude muestran cierta relación geográfica pero no directa con el precio (son proxies de ubicación).

Conclusiones generales

  • La variable más predictiva para el valor de la vivienda es el ingreso medio del área.

  • La proximidad al océano y bahías tiene un gran impacto positivo en el precio de la vivienda.

  • Existe un límite superior artificial en el valor de la vivienda, que puede distorsionar modelos predictivos si no se considera.

  • El patrón espacial es clave: las zonas urbanas costeras concentran los precios más altos.

  • Las variables relacionadas con tamaño de la vivienda y número de hogares/población tienen relación entre sí pero menor impacto directo en el precio.

Entrenamiento inicial de modelos (Reg.Lineal, Arbol de Decision, Random Forest)

library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.3.0 ──
## ✔ broom        1.0.8     ✔ rsample      1.3.0
## ✔ dials        1.4.0     ✔ tibble       3.2.1
## ✔ infer        1.0.8     ✔ tune         1.3.0
## ✔ modeldata    1.4.0     ✔ workflows    1.2.0
## ✔ parsnip      1.3.2     ✔ workflowsets 1.1.1
## ✔ purrr        1.0.4     ✔ yardstick    1.3.2
## ✔ recipes      1.3.0
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ purrr::discard()  masks scales::discard()
## ✖ dplyr::filter()   masks stats::filter()
## ✖ dplyr::lag()      masks stats::lag()
## ✖ yardstick::spec() masks readr::spec()
## ✖ recipes::step()   masks stats::step()
train <- df
# 2. Dividir en entrenamiento (80%) y prueba (20%)
set.seed(42)
split <- initial_split(train, prop = 0.8, strata = median_house_value)
train_split <- training(split)
test_split  <- testing(split)
# 3. Preprocesamiento
receta <- recipe(median_house_value ~ ., data = train_split) %>%
  update_role(id, new_role = "ID") %>%
  step_impute_median(all_numeric_predictors()) %>%    # 1. Imputar primero
  step_impute_mode(all_nominal_predictors()) %>%
  step_mutate_at(
    all_numeric_predictors(),                         # 2. Winsorización 
    fn = ~scales::squish(., quantile(., 0.01, na.rm = TRUE), quantile(., 0.99))
  ) %>%
  step_dummy(all_nominal_predictors()) %>%            # 3. Dummies después
  step_zv(all_predictors())
# 3. Preparar receta en train, aplicar en test
prep_receta <- prep(receta, training = train_split)
train_prep <- bake(prep_receta, new_data = train_split)
test_prep  <- bake(prep_receta, new_data = test_split)
colSums(is.na(train_prep))
##                         id                  longitude 
##                          0                          0 
##                   latitude         housing_median_age 
##                          0                          0 
##                total_rooms             total_bedrooms 
##                          0                          0 
##                 population                 households 
##                          0                          0 
##              median_income         median_house_value 
##                          0                          0 
##     ocean_proximity_INLAND     ocean_proximity_ISLAND 
##                          0                          0 
##   ocean_proximity_NEAR.BAY ocean_proximity_NEAR.OCEAN 
##                          0                          0
colSums(is.na(test_prep))
##                         id                  longitude 
##                          0                          0 
##                   latitude         housing_median_age 
##                          0                          0 
##                total_rooms             total_bedrooms 
##                          0                          0 
##                 population                 households 
##                          0                          0 
##              median_income         median_house_value 
##                          0                          0 
##     ocean_proximity_INLAND     ocean_proximity_ISLAND 
##                          0                          0 
##   ocean_proximity_NEAR.BAY ocean_proximity_NEAR.OCEAN 
##                          0                          0
# 4. Especificación de modelos

# a) Regresión Lineal
modelo_lm <- linear_reg() %>% set_engine("lm")

# b) Árbol de Decisión
modelo_tree <- decision_tree() %>%
  set_engine("rpart") %>%
  set_mode("regression")

# c) Random Forest
modelo_rf <- rand_forest(trees = 500) %>%
  set_engine("ranger", importance = "impurity") %>%
  set_mode("regression")
# 5. Entrenamiento
# Regresión Lineal
ajuste_lm <- fit(modelo_lm, median_house_value ~ ., data = train_prep)
pred_lm <- predict(ajuste_lm, new_data = test_prep) %>%
  bind_cols(test_prep)
rmse_lm <- yardstick::rmse(pred_lm, truth = median_house_value, estimate = .pred)

# Árbol de decisión
ajuste_tree <- fit(modelo_tree, median_house_value ~ ., data = train_prep)
pred_tree <- predict(ajuste_tree, new_data = test_prep) %>%
  bind_cols(test_prep)
rmse_tree <- yardstick::rmse(pred_tree, truth = median_house_value, estimate = .pred)

# Random Forest
ajuste_rf <- fit(modelo_rf, median_house_value ~ ., data = train_prep)
pred_rf <- predict(ajuste_rf, new_data = test_prep) %>%
  bind_cols(test_prep)
rmse_rf <- yardstick::rmse(pred_rf, truth = median_house_value, estimate = .pred)
# Comparar resultados
resultados <- tibble(
  Modelo = c("Regresión Lineal", "Árbol de Decisión", "Random Forest"),
  RMSE = c(rmse_lm$.estimate, rmse_tree$.estimate, rmse_rf$.estimate)
)
print(resultados)
## # A tibble: 3 × 2
##   Modelo              RMSE
##   <chr>              <dbl>
## 1 Regresión Lineal  67805.
## 2 Árbol de Decisión 73502.
## 3 Random Forest     47403.
evaluacion_grafica_regresion <- function(modelo, df_pred, target = "median_house_value", num_features = 15) {
  library(ggplot2)
  library(performance)
  library(vip)
  library(yardstick)
  # library(DALEX) # Opcional, si tienes DALEX y quieres interpretación avanzada

  # 1. Real vs Predicho
  p1 <- ggplot(df_pred, aes_string(x = target, y = ".pred")) +
    geom_point(alpha = 0.3, color = "blue") +
    geom_abline(slope = 1, intercept = 0, color = "red") +
    labs(title = "Real vs Predicho", x = "Valor real", y = "Valor predicho")

  # 2. Residuos vs Predicción
  df_pred$residuals <- df_pred$.pred - df_pred[[target]]
  p2 <- ggplot(df_pred, aes(x = .pred, y = residuals)) +
    geom_point(alpha = 0.3) +
    geom_hline(yintercept = 0, color = "red") +
    labs(title = "Residuos vs Predicción", x = "Predicción", y = "Residuo")

  # 3. Histograma de residuos
  p3 <- ggplot(df_pred, aes(x = residuals)) +
    geom_histogram(bins = 30, fill = "darkgreen", alpha = 0.7) +
    labs(title = "Distribución de residuos", x = "Residuo", y = "Frecuencia")

  # 4. Métricas
  met <- yardstick::metrics(df_pred, truth = !!sym(target), estimate = .pred)
  print(met)

  # 5. Importancia de variables (si aplica)
  if("fit" %in% names(modelo)) {
    print(vip::vip(modelo$fit, num_features = num_features) + ggtitle("Importancia de variables"))
  } else {
    message("No se puede calcular la importancia de variables para este tipo de modelo.")
  }

  # 6. Chequeo de regresión con performance (modelo lm auxiliar)
  mod_check <- lm(as.formula(paste(target, "~ .pred")), data = df_pred)
  performance::check_model(mod_check)

  # 7. Mostrar los plots
  print(p1)
  print(p2)
  print(p3)

  invisible(list(
    metricas = met,
    p_real_vs_pred = p1,
    p_residuos = p2,
    p_hist_residuos = p3
  ))
}
evaluacion_grafica_regresion(ajuste_rf, pred_rf, target = "median_house_value")
## 
## Attaching package: 'performance'
## The following objects are masked from 'package:yardstick':
## 
##     mae, rmse
## 
## Attaching package: 'vip'
## The following object is masked from 'package:utils':
## 
##     vi
## # A tibble: 3 × 3
##   .metric .estimator .estimate
##   <chr>   <chr>          <dbl>
## 1 rmse    standard   47403.   
## 2 rsq     standard       0.839
## 3 mae     standard   32233.

Conclusiones sobre los modelos

Se compararon tres modelos principales para la predicción del valor de viviendas:

Modelo RMSE
Regresión Lineal 67,805
Árbol de Decisión 73,502
Random Forest 47,403
  • El modelo Random Forest obtuvo el mejor desempeño con un RMSE de 47,403, significativamente menor que los otros dos modelos. Esto indica que tiene mayor capacidad para capturar las relaciones no lineales y complejas presentes en los datos.

  • La regresión lineal y el árbol de decisión presentan errores más elevados, lo que evidencia que la relación entre las variables explicativas y el valor de la vivienda no es completamente lineal ni puede ser explicada por reglas simples.

Conclusiones de los gráficos del mejor modelo (Random Forest)

1. Importancia de Variables

  • La variable más importante es median_income, lo que sugiere que el ingreso mediano de los hogares es el factor más determinante para predecir el valor de la vivienda.

  • Otras variables relevantes son la proximidad al interior (ocean_proximity_INLAND), la localización geográfica (longitude y latitude) y, en menor medida, características estructurales como total_rooms y population.

2. Gráfico Real vs Predicho

  • Hay una fuerte correlación positiva entre los valores reales y predichos, lo que muestra que el modelo logra capturar correctamente la tendencia general de los datos.

  • Sin embargo, se observa cierta dispersión, especialmente en los valores altos, donde el modelo tiende a subestimar (los puntos se agrupan debajo de la línea roja).

3. Residuos vs Predicción

  • Los residuos están mayoritariamente dispersos alrededor de cero, lo que indica que el modelo no presenta sesgo sistemático importante.

  • Hay una mayor dispersión de residuos en los valores más altos de predicción, lo que sugiere que el modelo tiene mayor dificultad para predecir correctamente las viviendas de mayor valor.

4. Distribución de residuos

  • La distribución de los residuos tiene forma aproximadamente simétrica y centrada en cero, lo que es un buen indicio de que el modelo no está sistemáticamente sobreestimando ni subestimando el valor de las viviendas.

  • Existen algunos valores atípicos (outliers) en los extremos, lo cual es esperable en modelos aplicados a datos reales, especialmente con valores extremos de la variable objetivo.

Entrenamiento de Modelos(XGBosst, Lasso/Rige, KKNN, SVM

modelo_xgb <- boost_tree(trees = 500, learn_rate = 0.05) %>%
  set_engine("xgboost") %>%
  set_mode("regression")

modelo_enet <- linear_reg(penalty = 0.1, mixture = 0.5) %>% # Cambia penalty/mixture para probar lasso/ridge
  set_engine("glmnet")

modelo_svm <- svm_rbf(cost = 1, rbf_sigma = 0.1) %>%
  set_engine("kernlab") %>%
  set_mode("regression")

modelo_knn <- nearest_neighbor(neighbors = 10) %>%
  set_engine("kknn") %>%
  set_mode("regression")
# XGBoost

ajuste_xgb <- fit(modelo_xgb, median_house_value ~ ., data = train_prep)
pred_xgb  <- predict(ajuste_xgb,  new_data = test_prep) %>% bind_cols(test_prep)
rmse_xgb  <- yardstick::rmse(pred_xgb, truth = median_house_value, estimate = .pred)

# Lasso/Ridge/Elastic Net (Elastic Net general)

ajuste_enet <- fit(modelo_enet, median_house_value ~ ., data = train_prep)
pred_enet <- predict(ajuste_enet, new_data = test_prep) %>% bind_cols(test_prep)
rmse_enet <- yardstick::rmse(pred_enet, truth = median_house_value, estimate = .pred)

# Support Vector Machine (SVM)

ajuste_svm <- fit(modelo_svm, median_house_value ~ ., data = train_prep)
pred_svm  <- predict(ajuste_svm,  new_data = test_prep) %>% bind_cols(test_prep)
rmse_svm  <- yardstick::rmse(pred_svm, truth = median_house_value, estimate = .pred)

# KNN

ajuste_knn <- fit(modelo_knn, median_house_value ~ ., data = train_prep)
pred_knn  <- predict(ajuste_knn,  new_data = test_prep) %>% bind_cols(test_prep)
rmse_knn  <- yardstick::rmse(pred_knn, truth = median_house_value, estimate = .pred)
# 4. Comparación de resultados
resultados <- tibble(
  Modelo = c("XGBoost", "Elastic Net", "SVM", "KNN"),
  RMSE = c(rmse_xgb$.estimate, rmse_enet$.estimate, rmse_svm$.estimate, rmse_knn$.estimate)
)
print(resultados)
## # A tibble: 4 × 2
##   Modelo        RMSE
##   <chr>        <dbl>
## 1 XGBoost     45280.
## 2 Elastic Net 67825.
## 3 SVM         53376.
## 4 KNN         56773.
# 5. Visualización real vs predicho para el mejor modelo (ejemplo con XGBoost)
evaluacion_grafica_regresion(ajuste_xgb, pred_xgb, target = "median_house_value")
## # A tibble: 3 × 3
##   .metric .estimator .estimate
##   <chr>   <chr>          <dbl>
## 1 rmse    standard   45280.   
## 2 rsq     standard       0.848
## 3 mae     standard   29820.

Comparación de modelos

Modelo RMSE
XGBoost 45,280
Elastic Net 67,825
SVM 53,376
KNN 56,773
  • XGBoost obtuvo el menor RMSE, superando a los demás modelos en capacidad predictiva. Es el modelo que mejor se ajusta a la estructura y complejidad de los datos, aprovechando su potencia para capturar relaciones no lineales y complejas.

  • Los modelos SVM y KNN también presentan resultados relativamente buenos, aunque no logran igualar el rendimiento de XGBoost.

  • Elastic Net, al igual que la regresión lineal, tiene un desempeño considerablemente inferior, evidenciando que la relación entre variables no es simplemente lineal ni puede ser explicada solo por regularización.

Conclusiones sobre los gráficos de XGBoost

1. Importancia de variables

  • La variable median_income sigue siendo la más relevante por lejos, confirmando que el nivel de ingresos es el principal factor en el valor de la vivienda.

  • La variable ocean_proximity_INLAND también es significativa, seguida de aspectos geográficos (longitude, latitude) y de identificación (id), aunque estos últimos en menor medida.

  • Variables estructurales y otras categorías de proximidad al océano tienen un peso mucho menor.

2. Real vs Predicho

  • El gráfico muestra una buena correlación entre los valores reales y los predichos. La mayor parte de los puntos sigue la diagonal, lo que indica una predicción precisa en la mayoría de los casos.

  • Se observa dispersión, especialmente en los valores altos, donde hay una ligera tendencia del modelo a subestimar los precios más elevados.

3. Residuos vs Predicción

  • Los residuos se concentran cerca de cero en todo el rango de predicciones, lo cual es positivo y habla de un modelo bien ajustado.

  • Sin embargo, en los valores más altos de predicción, se ve mayor variabilidad y la presencia de algunos outliers, lo que indica que para las viviendas más caras el modelo puede cometer errores mayores.

  • No se observa un patrón claro de sesgo sistemático.

4. Distribución de residuos

  • La distribución es simétrica y centrada en cero, lo que indica que el modelo no tiende a sobrestimar ni subestimar sistemáticamente.

  • La presencia de algunos valores extremos es normal en problemas de este tipo y sugiere la existencia de datos atípicos o difíciles de predecir.

Generar archivo de salida

# 1. Cargar el test de Kaggle y prepararlo igual que test_prep_clean
test_kaggle <- readr::read_csv("test.csv")
## Rows: 6193 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): ocean_proximity
## dbl (9): id, longitude, latitude, housing_median_age, total_rooms, total_bed...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# 2. Preprocesar test_kaggle exactamente igual que tu test interno
test_kaggle_prep <- bake(prep_receta, new_data = test_kaggle) %>% drop_na()

# 3. Predecir con el modelo entrenado en train_prep
predicciones_kaggle <- predict(ajuste_xgb, new_data = test_kaggle_prep)

# 4. Recuperar el id de test original para el archivo de submission
# Asumiendo que 'id' NO fue eliminado en la preparación y sigue el mismo orden
# Recuperar los ids válidos (correspondientes a test_kaggle_prep)
ids_validos <- test_kaggle$id[as.numeric(rownames(test_kaggle_prep))]

# 5. Crear el archivo de salida
submission <- tibble(
  id = ids_validos,
  median_house_value = predicciones_kaggle$.pred
)

# 6. Guardar archivo CSV
readr::write_csv(submission, "submission.csv")