TIDYVERSE 3

Danilo Verdugo (dynageo@gmail.com)

ORDENAR

Paquete dplyr de Tidyverse es la gramática para la manipulación de datos.

Un conjunto consistente de verbos que ayuda a solucionar los retos de procesamiento de datos más comunes.

Los principales “verbos” (funciones) de esta gramática son:

  • select(): selecciona columnas (variables) con base en sus nombres.
  • filter(): selecciona filas (observaciones) con base en sus valores.
  • arrange(): cambia el orden de las filas.
  • mutate(): crea nuevas columnas, las cuales se expresan como funciones de columnas existentes.
  • summarize(): agrupa y resume valores.

1. Inspección

glimpse(datos)
Rows: 6
Columns: 5
$ `Student ID`   <dbl> 1, 2, 3, 4, 5, 6
$ `Full Name`    <chr> "Sunil Huffmann", "Barclay Lynn", "Jayendra Lyne", "Leo…
$ favourite.food <chr> "Strawberry yoghurt", "French fries", "N/A", "Anchovies…
$ mealPlan       <chr> "Lunch only", "Lunch only", "Breakfast and lunch", "Lun…
$ AGE            <chr> "4", "5", "7", NA, "five", "6"

2. Manejo de Variables (columnas)

datos |> select(AGE, mealPlan)
datos |> select(Nombre = `Full Name`, edad = AGE)

3. Filtrado (filas)

Seleccionar observaciones (filas) por condición lógica.

datos |> filter(AGE == 6)
datos |> filter(AGE == 6 & mealPlan=="Lunch only")
datos |> filter(AGE == 6 | AGE == 4)

Seleccionar observaciones (filas) por posición.

datos |> slice(2)
datos |> slice(2:5)
datos |> slice((nrow(datos)-3):nrow(datos))

Ordenar filas.

datos |> arrange(favourite.food)

En piping…

datos |> slice(1:4) |> arrange(desc(favourite.food))
datos |> slice((nrow(datos)-3):nrow(datos)) |> arrange(favourite.food)

Crear nuevas columnas

mutate permite crear una nueva columna o variable, usar transmute para dejar solo la nueva.

datos |>
  mutate(edad = paste(AGE, "años", sep = " "))
# A tibble: 6 × 6
  `Student ID` `Full Name`      favourite.food     mealPlan          AGE   edad 
         <dbl> <chr>            <chr>              <chr>             <chr> <chr>
1            1 Sunil Huffmann   Strawberry yoghurt Lunch only        4     4 añ…
2            2 Barclay Lynn     French fries       Lunch only        5     5 añ…
3            3 Jayendra Lyne    N/A                Breakfast and lu… 7     7 añ…
4            4 Leon Rossini     Anchovies          Lunch only        <NA>  NA a…
5            5 Chidiegwu Dunkel Pizza              Breakfast and lu… five  five…
6            6 Güvenç Attila    Ice cream          Lunch only        6     6 añ…
datos |>
  transmute(edad = paste(AGE, "años", sep = " "), 
            `edad cuadrada` = as.numeric(AGE) ^ 2)
# A tibble: 6 × 2
  edad      `edad cuadrada`
  <chr>               <dbl>
1 4 años                 16
2 5 años                 25
3 7 años                 49
4 NA años                NA
5 five años              NA
6 6 años                 36

Resumen

datos |>
   summarise(
     rows = n(),
     alimentos = n_distinct(mealPlan))
# A tibble: 1 × 2
   rows alimentos
  <int>     <int>
1     6         2
datos |>
   summarise(
     rows = n(),
     `edad mínima` = min(AGE, na.rm = T))
# A tibble: 1 × 2
   rows `edad mínima`
  <int> <chr>        
1     6 4            

Operaciones en mapa

Los datos de GADM suelen traer muchas columnas de códigos que no necesitamos (GID_0, GID_1, ISO…). Con sf, si usas select, la columna geometry se mantiene automáticamente (a diferencia de los dataframes normales donde desaparece si no la seleccionas).

glimpse(mapa_tidy)
Rows: 24
Columns: 12
$ GID_1     <chr> "ARG.1_1", "ARG.2_1", "ARG.3_1", "ARG.4_1", "ARG.5_1", "ARG.…
$ GID_0     <chr> "ARG", "ARG", "ARG", "ARG", "ARG", "ARG", "ARG", "ARG", "ARG…
$ COUNTRY   <chr> "Argentina", "Argentina", "Argentina", "Argentina", "Argenti…
$ NAME_1    <chr> "Buenos Aires", "Catamarca", "Chaco", "Chubut", "Ciudad de B…
$ VARNAME_1 <chr> "Baires|Buenos Ayres", NA, "El Chaco|Presidente Juan Peron",…
$ NL_NAME_1 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ TYPE_1    <chr> "Provincia", "Provincia", "Provincia", "Provincia", "Distrit…
$ ENGTYPE_1 <chr> "Province", "Province", "Province", "Province", "Federal Dis…
$ CC_1      <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ HASC_1    <chr> "AR.BA", "AR.CT", "AR.CC", "AR.CH", "AR.DF", "AR.CB", "AR.CN…
$ ISO_1     <chr> "AR-B", "AR-K", "AR-H", "AR-U", NA, NA, "AR-W", NA, "AR-P", …
$ geometry  <MULTIPOLYGON [°]> MULTIPOLYGON (((-62.10181 -..., MULTIPOLYGON ((…
 mapa_tidy |>   
  select(nombre_region = NAME_1, tipo = TYPE_1)    
# Nota: No selecciono 'geometry', pero R sí sabe que debe conservarla. 
mapa_tidy |>   
  rename(nombre_region = NAME_1, tipo = TYPE_1)    
Simple feature collection with 24 features and 11 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.56056 ymin: -55.06153 xmax: -53.59184 ymax: -21.78137
Geodetic CRS:  WGS 84
First 10 features:
      GID_1 GID_0   COUNTRY          nombre_region
1   ARG.1_1   ARG Argentina           Buenos Aires
2   ARG.2_1   ARG Argentina              Catamarca
3   ARG.3_1   ARG Argentina                  Chaco
4   ARG.4_1   ARG Argentina                 Chubut
5   ARG.5_1   ARG Argentina Ciudad de Buenos Aires
6   ARG.6_1   ARG Argentina                Córdoba
7   ARG.7_1   ARG Argentina             Corrientes
8   ARG.8_1   ARG Argentina             Entre Ríos
9   ARG.9_1   ARG Argentina                Formosa
10 ARG.10_1   ARG Argentina                  Jujuy
                          VARNAME_1 NL_NAME_1             tipo        ENGTYPE_1
1               Baires|Buenos Ayres      <NA>        Provincia         Province
2                              <NA>      <NA>        Provincia         Province
3    El Chaco|Presidente Juan Peron      <NA>        Provincia         Province
4                              <NA>      <NA>        Provincia         Province
5  BUENOS AIRES D.F.|Capital Federa      <NA> Distrito Federal Federal District
6                           Cordova      <NA>        Provincia         Province
7                              <NA>      <NA>        Provincia         Province
8                        Entre-Rios      <NA>        Provincia         Province
9                              <NA>      <NA>        Provincia         Province
10                             <NA>      <NA>        Provincia         Province
   CC_1 HASC_1 ISO_1                       geometry
1  <NA>  AR.BA  AR-B MULTIPOLYGON (((-62.10181 -...
2  <NA>  AR.CT  AR-K MULTIPOLYGON (((-65.14583 -...
3  <NA>  AR.CC  AR-H MULTIPOLYGON (((-60.11628 -...
4  <NA>  AR.CH  AR-U MULTIPOLYGON (((-66.99986 -...
5  <NA>  AR.DF  <NA> MULTIPOLYGON (((-58.34292 -...
6  <NA>  AR.CB  <NA> MULTIPOLYGON (((-63.04897 -...
7  <NA>  AR.CN  AR-W MULTIPOLYGON (((-57.64509 -...
8  <NA>  AR.ER  <NA> MULTIPOLYGON (((-59.1991 -3...
9  <NA>  AR.FM  AR-P MULTIPOLYGON (((-58.36386 -...
10 <NA>  AR.JY  AR-Y MULTIPOLYGON (((-64.44048 -...
# Nota: No selecciono 'geometry', pero R sí sabe que debe conservarla. 

Podemos filtrar registros específicos tal como filtraríamos filas en una tabla.

mapa_tidy |>   
  filter(NAME_1 != "Chaco") 
Simple feature collection with 23 features and 11 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.56056 ymin: -55.06153 xmax: -53.59184 ymax: -21.78137
Geodetic CRS:  WGS 84
First 10 features:
      GID_1 GID_0   COUNTRY                 NAME_1
1   ARG.1_1   ARG Argentina           Buenos Aires
2   ARG.2_1   ARG Argentina              Catamarca
3   ARG.4_1   ARG Argentina                 Chubut
4   ARG.5_1   ARG Argentina Ciudad de Buenos Aires
5   ARG.6_1   ARG Argentina                Córdoba
6   ARG.7_1   ARG Argentina             Corrientes
7   ARG.8_1   ARG Argentina             Entre Ríos
8   ARG.9_1   ARG Argentina                Formosa
9  ARG.10_1   ARG Argentina                  Jujuy
10 ARG.11_1   ARG Argentina               La Pampa
                          VARNAME_1 NL_NAME_1           TYPE_1        ENGTYPE_1
1               Baires|Buenos Ayres      <NA>        Provincia         Province
2                              <NA>      <NA>        Provincia         Province
3                              <NA>      <NA>        Provincia         Province
4  BUENOS AIRES D.F.|Capital Federa      <NA> Distrito Federal Federal District
5                           Cordova      <NA>        Provincia         Province
6                              <NA>      <NA>        Provincia         Province
7                        Entre-Rios      <NA>        Provincia         Province
8                              <NA>      <NA>        Provincia         Province
9                              <NA>      <NA>        Provincia         Province
10               El Pampa|Eva Perón      <NA>        Provincia         Province
   CC_1 HASC_1 ISO_1                       geometry
1  <NA>  AR.BA  AR-B MULTIPOLYGON (((-62.10181 -...
2  <NA>  AR.CT  AR-K MULTIPOLYGON (((-65.14583 -...
3  <NA>  AR.CH  AR-U MULTIPOLYGON (((-66.99986 -...
4  <NA>  AR.DF  <NA> MULTIPOLYGON (((-58.34292 -...
5  <NA>  AR.CB  <NA> MULTIPOLYGON (((-63.04897 -...
6  <NA>  AR.CN  AR-W MULTIPOLYGON (((-57.64509 -...
7  <NA>  AR.ER  <NA> MULTIPOLYGON (((-59.1991 -3...
8  <NA>  AR.FM  AR-P MULTIPOLYGON (((-58.36386 -...
9  <NA>  AR.JY  AR-Y MULTIPOLYGON (((-64.44048 -...
10 <NA>  AR.LP  AR-L MULTIPOLYGON (((-63.38943 -...
mapa_tidy |> 
  mutate(area = st_area(geometry)/1e6, 
         perimetro = st_perimeter(geometry))
Simple feature collection with 24 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.56056 ymin: -55.06153 xmax: -53.59184 ymax: -21.78137
Geodetic CRS:  WGS 84
First 10 features:
      GID_1 GID_0   COUNTRY                 NAME_1
1   ARG.1_1   ARG Argentina           Buenos Aires
2   ARG.2_1   ARG Argentina              Catamarca
3   ARG.3_1   ARG Argentina                  Chaco
4   ARG.4_1   ARG Argentina                 Chubut
5   ARG.5_1   ARG Argentina Ciudad de Buenos Aires
6   ARG.6_1   ARG Argentina                Córdoba
7   ARG.7_1   ARG Argentina             Corrientes
8   ARG.8_1   ARG Argentina             Entre Ríos
9   ARG.9_1   ARG Argentina                Formosa
10 ARG.10_1   ARG Argentina                  Jujuy
                          VARNAME_1 NL_NAME_1           TYPE_1        ENGTYPE_1
1               Baires|Buenos Ayres      <NA>        Provincia         Province
2                              <NA>      <NA>        Provincia         Province
3    El Chaco|Presidente Juan Peron      <NA>        Provincia         Province
4                              <NA>      <NA>        Provincia         Province
5  BUENOS AIRES D.F.|Capital Federa      <NA> Distrito Federal Federal District
6                           Cordova      <NA>        Provincia         Province
7                              <NA>      <NA>        Provincia         Province
8                        Entre-Rios      <NA>        Provincia         Province
9                              <NA>      <NA>        Provincia         Province
10                             <NA>      <NA>        Provincia         Province
   CC_1 HASC_1 ISO_1                       geometry              area
1  <NA>  AR.BA  AR-B MULTIPOLYGON (((-62.10181 -... 306914.0395 [m^2]
2  <NA>  AR.CT  AR-K MULTIPOLYGON (((-65.14583 -... 101492.8035 [m^2]
3  <NA>  AR.CC  AR-H MULTIPOLYGON (((-60.11628 -...  99947.2871 [m^2]
4  <NA>  AR.CH  AR-U MULTIPOLYGON (((-66.99986 -... 223924.1731 [m^2]
5  <NA>  AR.DF  <NA> MULTIPOLYGON (((-58.34292 -...    211.0445 [m^2]
6  <NA>  AR.CB  <NA> MULTIPOLYGON (((-63.04897 -... 164777.3006 [m^2]
7  <NA>  AR.CN  AR-W MULTIPOLYGON (((-57.64509 -...  89081.0892 [m^2]
8  <NA>  AR.ER  <NA> MULTIPOLYGON (((-59.1991 -3...  78063.2731 [m^2]
9  <NA>  AR.FM  AR-P MULTIPOLYGON (((-58.36386 -...  75752.0870 [m^2]
10 <NA>  AR.JY  AR-Y MULTIPOLYGON (((-64.44048 -...  53251.9399 [m^2]
        perimetro
1  6542171.14 [m]
2  2143864.14 [m]
3  1834622.31 [m]
4  4067283.16 [m]
5    75032.26 [m]
6  1900377.91 [m]
7  1548144.09 [m]
8  1392999.67 [m]
9  1889339.55 [m]
10 1412705.96 [m]

Noten que aparece un [m] o [km^2], significa que el valor posee un formato especial, se refiere a unidades, es mejor limpiar y convertirlo a número.

mapa_tidy |> 
  mutate(area = as.numeric(st_area(geometry)/1e6), 
         perimetro = st_perimeter(geometry))
Simple feature collection with 24 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -73.56056 ymin: -55.06153 xmax: -53.59184 ymax: -21.78137
Geodetic CRS:  WGS 84
First 10 features:
      GID_1 GID_0   COUNTRY                 NAME_1
1   ARG.1_1   ARG Argentina           Buenos Aires
2   ARG.2_1   ARG Argentina              Catamarca
3   ARG.3_1   ARG Argentina                  Chaco
4   ARG.4_1   ARG Argentina                 Chubut
5   ARG.5_1   ARG Argentina Ciudad de Buenos Aires
6   ARG.6_1   ARG Argentina                Córdoba
7   ARG.7_1   ARG Argentina             Corrientes
8   ARG.8_1   ARG Argentina             Entre Ríos
9   ARG.9_1   ARG Argentina                Formosa
10 ARG.10_1   ARG Argentina                  Jujuy
                          VARNAME_1 NL_NAME_1           TYPE_1        ENGTYPE_1
1               Baires|Buenos Ayres      <NA>        Provincia         Province
2                              <NA>      <NA>        Provincia         Province
3    El Chaco|Presidente Juan Peron      <NA>        Provincia         Province
4                              <NA>      <NA>        Provincia         Province
5  BUENOS AIRES D.F.|Capital Federa      <NA> Distrito Federal Federal District
6                           Cordova      <NA>        Provincia         Province
7                              <NA>      <NA>        Provincia         Province
8                        Entre-Rios      <NA>        Provincia         Province
9                              <NA>      <NA>        Provincia         Province
10                             <NA>      <NA>        Provincia         Province
   CC_1 HASC_1 ISO_1                       geometry        area      perimetro
1  <NA>  AR.BA  AR-B MULTIPOLYGON (((-62.10181 -... 306914.0395 6542171.14 [m]
2  <NA>  AR.CT  AR-K MULTIPOLYGON (((-65.14583 -... 101492.8035 2143864.14 [m]
3  <NA>  AR.CC  AR-H MULTIPOLYGON (((-60.11628 -...  99947.2871 1834622.31 [m]
4  <NA>  AR.CH  AR-U MULTIPOLYGON (((-66.99986 -... 223924.1731 4067283.16 [m]
5  <NA>  AR.DF  <NA> MULTIPOLYGON (((-58.34292 -...    211.0445   75032.26 [m]
6  <NA>  AR.CB  <NA> MULTIPOLYGON (((-63.04897 -... 164777.3006 1900377.91 [m]
7  <NA>  AR.CN  AR-W MULTIPOLYGON (((-57.64509 -...  89081.0892 1548144.09 [m]
8  <NA>  AR.ER  <NA> MULTIPOLYGON (((-59.1991 -3...  78063.2731 1392999.67 [m]
9  <NA>  AR.FM  AR-P MULTIPOLYGON (((-58.36386 -...  75752.0870 1889339.55 [m]
10 <NA>  AR.JY  AR-Y MULTIPOLYGON (((-64.44048 -...  53251.9399 1412705.96 [m]
library(ggplot2)

Visualización rápida (ggplot2)

Al ser un objeto tidy, entra directo al flujo de ggplot.

  1. Solamente el lienzo donde se trazará.
  1. Agrego la geometría.
mapa_tidy |>   
  ggplot() +   
  geom_sf()

Propiedades de todo el mapa ‘single symbol’.

mapa_tidy |>   
  ggplot() +   
  geom_sf(
    fill = 'white',
    color = 'darkblue',
    lwd = 0.5
    )

Estética o Mapping. Usar una variable para colorear.

mapa_tidy |>   
  ggplot() +   
  geom_sf(aes(fill = NAME_1)) +
  ggtitle("Estados de Argentina")
mapa_tidy |>   
  ggplot() +   
  geom_sf(aes(fill = NAME_1), 
          show.legend = FALSE) +  
  #scale_fill_viridis_d(option = "cividis") + 
  scale_fill_discrete() + 
  #scale_fill_brewer(palette = "Paired") + 
  geom_sf_label(data=mapa_tidy, 
                aes(label=NAME_1),
                text.color = "red",
                border.color="orange",
                size= 2, 
                alpha=0.1) +
  theme_minimal() +   
  labs(title = "Estados Argentinos", 
       subtitle = "Datos de GADM procesados con Tidyverse") 
mapa_tidy |>   
  ggplot() +   
  geom_sf(aes(fill = NAME_1), 
          show.legend = FALSE,
          color ="white",alpha=0.8) +  
  scale_fill_viridis_d(option = "cividis") + 
  #scale_fill_discrete() + 
  #scale_fill_brewer(palette = "Paired") + 
  geom_sf_label(data=mapa_tidy, 
                aes(label=NAME_1),
                text.color = "red",
                border.color="orange",
                size= 2, 
                alpha=0.1) +
  labs(title = "Estados Argentinos", 
       subtitle = "División Política",
       caption = "Fuente: GADM 2025 | Proyección: WGS84",
       x= NULL, y= NULL) +
  theme_minimal() +   
  theme(
    plot.title = element_text(face="bold", size = 16)
  )

Agregando una capa ráster al mapa. Necesitamos el paquete tidyterra. Para agregar la capacidad de agregar un objeto ráster directo al proceso de dibujo.

library(tidyterra)

Notas:

  • Internamente evita convertir las imágenes a matrices de datos. Liberando memoria y acelerando el resultado.

  • Usamos una escala de colores con el sufijo _c para datos contínuos (ver caso de polígonos!).

#library(tidyterra) # ¡Indispensable para esto!
ggplot() +
  # Esta es la geometría mágica para rasters
  geom_spatraster(data = dtm) +
  # Colores automáticos de hipstometría (estilo atlas)
  scale_fill_hypso_tint_c(
    palette = "dem_poster", # Paleta clásica de Wikipedia
    name = "Altitud (m)"
  ) +
  theme_void() # Quita ejes y fondo gris

GGPLOT2 funciona en “capas”, cada elemento a dibujar ocupa una posición en un stack o pila, el orden influye en su visibilidad.

Nota: geom_contour() genera isolíneas y se pueden configurar sus propiedades de dibujo.

#library(tidyterra) # ¡Indispensable para esto!
mapa_tidy |>   
  ggplot() +   
  geom_spatraster(data = dtm, alpha= 0.4) + #Capa ráster, alpha IMPORTANTE!
  geom_contour(
    data = dtm, aes(x = x, y = y, z = elevacion),
    binwidth = 500, color = "grey80", size = 0.2
  ) +
  geom_contour(
    data = dtm, aes(x = x, y = y, z = elevacion),
    binwidth = 1000, color = "grey60", size = 0.4
  ) +
  scale_fill_hypso_tint_c(
    palette = "dem_poster", # Paleta clásica de Wikipedia
    name = "Altitud (m)"
  ) +
  geom_sf(color="red", lwd=0.1) + #Capa vectorial
  theme_void() # Quita ejes y fondo gris

Resumen de la Estructura de Datos

Característica Objeto geodata (SpatVector) Objeto sf (sf / data.frame)
Filosofía Rendimiento puro (C++) Legibilidad y compatibilidad Tidy
Visualización plot() base ggplot() + geom_sf()
Manipulación Funciones específicas de terra Verbos de dplyr (filter, mutate)
Acción clave Descarga Análisis

Lámina calidad producción.

Preproceso: Generando capas adicionales

message("Calculando terreno y sombras...")

# Calculamos pendiente (slope) y orientación (aspect)
slope <- terra::terrain(dtm, "slope", unit = "radians")
aspect <- terra::terrain(dtm, "aspect", unit = "radians")

# Calculamos el Hillshade
# angle = 45 (altura del sol), direction = 315 (luz desde el Noroeste - estándar cartográfico)
hillshade <- terra::shade(slope, aspect, angle = 45, direction = 315)

# Normalizamos nombres para facilitar el plot
names(dtm) <- "elevacion"
names(hillshade) <- "sombra"

Carga paquetes adicionales necesarios.

library(ggspatial)
library(ggnewscale)

Versión de código completo.

message("Generando Mapas...")

ggplot() +
geom_spatraster(data = hillshade, show.legend = FALSE) +
  scale_fill_gradient(low = "black", high = "white", na.value = NA) +
  new_scale_fill() + # Reiniciamos la escala de relleno para poder usar otra paleta
  geom_spatraster(data = dtm, alpha = 0.6) + # Jugar con alpha (0.5 a 0.7)
  scale_fill_hypso_tint_c(
    palette = "wiki-2.0",
    name = "Elevación (m)",
    breaks = c(0, 3000,  7000),
    guide = guide_colorbar(
      direction = "horizontal",
      barwidth = 15,
      barheight = 0.5,
      title.position = "top"
    )
  ) +
  geom_sf(data = mapa_tidy, fill = NA, color = "black", lwd = 0.25) +
  annotation_scale(
    location = "br",
    width_hint = 0.2,
    style = "ticks",
    line_col = "black",
    text_col = "black"
  ) +
  annotation_north_arrow(
    location = "tr",
    which_north = "true",
    height = unit(1.5, "cm"),
    width = unit(1.5, "cm"),
    style = north_arrow_minimal(
      fill = "black",
    #style = north_arrow_nautical(
    #style = north_arrow_fancy_orienteering(
      #fill = c("white", "black"),
      line_col = "grey20"
    )
  ) +
  labs(
    title = "ARGENTINA",
    subtitle = "Div. Política, Topografía y Relieve",
    caption = "Fuente: SRTM (Elevatr) & GADM | Procesado en R con tidyterra"
  ) +
  theme_minimal(base_family = "serif") + # Fuente Serif da toque "Atlas Antiguo"
  theme(
    plot.background = element_rect(fill = "white", color = NA),
    panel.background = element_rect(fill = "#eef7fa", color = NA), # Fondo azulito agua pálido
    plot.title = element_text(size = 20, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 14, hjust = 0.5, color = "grey40"),
    axis.text = element_blank(), # Sin coordenadas en los ejes
    axis.title = element_blank(),
    legend.position = "bottom",
    legend.background = element_rect(fill = "white", color = "grey90"),
    panel.grid = element_blank() # Sin grilla para look limpio
  ) +
  coord_sf(expand = FALSE)
# Guardar en alta resolución para impresión
ggsave("data/mapa_Atlas_argentina.png", width = 10, height = 12, dpi = 300)
message("Generando Mapas...")

ggplot() +
  # ----------------------------------------------------------------------------
# CAPA 1 (FONDO): EL SOMBREADO (HILLSHADE)
# Usamos geom_spatraster de tidyterra.
# Truco: show.legend = FALSE porque nadie necesita la leyenda de la sombra.
# ----------------------------------------------------------------------------
geom_spatraster(data = hillshade, show.legend = FALSE) +
  scale_fill_gradient(low = "black", high = "white", na.value = NA) +
# ----------------------------------------------------------------------------
# CAPA 2 (MEDIO): LA ELEVACIÓN CON COLOR (HYPSOMETRIC TINT)
# Esta capa va ENCIMA de la sombra.
# EL TRUCO CLAVE: alpha = 0.7 (Transparencia).
# Esto permite que la textura de la sombra de abajo "atraviese" el color.
# ----------------------------------------------------------------------------
new_scale_fill() + # Reiniciamos la escala de relleno para poder usar otra paleta
  geom_spatraster(data = dtm, alpha = 0.6) + # Jugar con alpha (0.5 a 0.7)
  # paleta de "Wiki" (estilo Wikipedia/Atlas físico)
  # tidyterra trae escalas preciosas listas para usar.
  scale_fill_hypso_tint_c(
    palette = "wiki-2.0",
    name = "Elevación (m)",
    breaks = c(0, 3000,  7000),
    guide = guide_colorbar(
      direction = "horizontal",
      barwidth = 15,
      barheight = 0.5,
      title.position = "top"
    )
  ) +
# ----------------------------------------------------------------------------
# CAPA 3 (ARRIBA): VECTORES Y CONTEXTO
# El límite administrativo va encima de todo.
# ----------------------------------------------------------------------------
geom_sf(data = mapa_tidy, fill = NA, color = "black", lwd = 0.25) +
# ----------------------------------------------------------------------------
# CAPA 4: ELEMENTOS CARTOGRÁFICOS Y ESTÉTICA (Atlas Style)
# ----------------------------------------------------------------------------
# Escala
annotation_scale(
  location = "br",
  width_hint = 0.2,
  style = "ticks",
  line_col = "black",
  text_col = "black"
) +
  # Norte (Estilo clásico)
  annotation_north_arrow(
    location = "tr",
    which_north = "true",
    height = unit(1.5, "cm"),
    width = unit(1.5, "cm"),
    style = north_arrow_minimal(
      fill = "black",
    #style = north_arrow_nautical(
    #style = north_arrow_fancy_orienteering(
      #fill = c("white", "black"),
      line_col = "grey20"
    )
  ) +
  # Etiquetas y Títulos
  labs(
    title = "ARGENTINA",
    subtitle = "Div. Política, Topografía y Relieve",
    caption = "Fuente: SRTM (Elevatr) & GADM | Procesado en R con tidyterra"
  ) +

  # Tema personalizado "Limpio"
  theme_minimal(base_family = "serif") + # Fuente Serif da toque "Atlas Antiguo"
  theme(
    plot.background = element_rect(fill = "white", color = NA),
    panel.background = element_rect(fill = "#eef7fa", color = NA), # Fondo azulito agua pálido
    plot.title = element_text(size = 20, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 14, hjust = 0.5, color = "grey40"),
    axis.text = element_blank(), # Sin coordenadas en los ejes
    axis.title = element_blank(),
    legend.position = "bottom",
    legend.background = element_rect(fill = "white", color = "grey90"),
    panel.grid = element_blank() # Sin grilla para look limpio
  ) +

  # Recorte final (Zoom exacto al vector)
  coord_sf(expand = FALSE)