TIDYVERSE 1

Danilo Verdugo (dynageo@gmail.com)

Ciencia de Datos

La ciencia de datos es una disciplina que permite transformar datos sin procesar (o crudos) en comprensión, perspectiva y conocimiento.

El Ciclo Básico

IMPORTAR > ORDENAR > TRANSFORMAR > VISUALIZAR > COMUNICAR

R para ciencia de Datos

Tidyverse es una colección de paquetes diseñados para la ciencia de datos. Todos los paquetes comparten:

  • Filosofía de diseño

  • Gramática

  • Estructuras de datos subyacentes

Para instalar el tidyverse completo:

install.packages("tidyverse")
library(tidyverse)

Datos tidy

Los paquetes de Tidyverse trabajan con datos tidy: ordenados, organizados.

los datos tidy deben cumplir con tres características:

  • Cada variable debe tener su propia columna.
  • Cada observación debe tener su propia fila.
  • Cada valor debe tener su propia celda.
head(mpg)
# A tibble: 6 × 11
  manufacturer model displ  year   cyl trans      drv     cty   hwy fl    class 
  <chr>        <chr> <dbl> <int> <int> <chr>      <chr> <int> <int> <chr> <chr> 
1 audi         a4      1.8  1999     4 auto(l5)   f        18    29 p     compa…
2 audi         a4      1.8  1999     4 manual(m5) f        21    29 p     compa…
3 audi         a4      2    2008     4 manual(m6) f        20    31 p     compa…
4 audi         a4      2    2008     4 auto(av)   f        21    30 p     compa…
5 audi         a4      2.8  1999     6 auto(l5)   f        16    26 p     compa…
6 audi         a4      2.8  1999     6 manual(m5) f        18    26 p     compa…

Pipes

Las funciones de Tidyverse pueden encadenarse a través del operador pipe (tubo), ya sea el del paquete magrittr (%>%) o el del paquete base de R (|>). Los procesos se enlazan con pipes para formar pipelines (tuberías).

mpg |> select(manufacturer)

Data Espacial

La integración de la filosofía “Tidyverse” con los datos espaciales ha dado lugar a lo que a veces se llama “Tidy Spatial Data Science”.

Paquete sf (Simple Features)

Durante años, el paquete estándar en R fue sp.

Potente, pero complejo: trataba los datos espaciales como objetos extraños y difíciles de manipular.

El paquete sf (Edzer Pebesma) cambió las reglas del juego al adoptar la filosofía tidy.

Claves

  • Dataframes como base: En sf, un mapa es simplemente un dataframe (una tabla).

  • La columna geometry: Es una “columna lista” especial (generalmente llamada geometry) que contiene las coordenadas.

  • Compatibilidad total: Como es un dataframe, puedes usar todos los verbos de dplyr (select, filter, mutate, group_by) directamente sobre tus mapas.

Clave: “Los datos espaciales no son especiales; son solo datos con atributos geométricos”.

El Ecosistema Tidy Espacial

La filosofía tidy no viene sola; trae consigo una familia de herramientas diseñadas para trabajar en armonía:

Área Paquete Principal Descripción Tidy
Vectores sf Maneja puntos, líneas y polígonos. Permite usar el operador “pipe” (%>% o |>
Rasters stars / terra stars Cubos de datos espaciotemporales. (terra muy rápido, se integra bien).
Área Paquete Principal Descripción Tidy
Visualización ggplot2 Incluye geom_sf(), detecta automáticamente la geometría y proyección, permitiendo crear mapas complejos con la gramática de gráficos habitual.
Área Paquete Principal Descripción Tidy
Mapas Interactivos tmap / leaflet tmap (Thematic Maps) utiliza una sintaxis por capas muy similar a ggplot2, ideal para la filosofía tidy.

Ventajas de la filosofía “Tidy” en lo espacial

  1. Legibilidad: El código se lee como una oración.

    • Antes: Tenías que extraer coordenadas, unir tablas con IDs complejos y luego plotear.

    • Ahora: leer_datos() |> filtrar(region == "X") |> calcular_area() |> plotear()

  1. Manipulación de atributos: Puedes hacer joins espaciales (unir datos por posición) con la misma facilidad que un left_join normal.
  1. Consistencia: No tienes que aprender una sintaxis para tus datos estadísticos y otra diferente para tus datos geográficos.

Ejemplo

Se necesita filtrar un mapa de ciudades para dejar solo las grandes y luego dibujarlas.

Sin filosofía Tidy (Estilo antiguo/base):

# Requería indexación compleja y gestión de objetos S4 
ciudades_grandes <- ciudades[ciudades$poblacion > 100000, ] 
plot(ciudades_grandes) 

Con filosofía Tidy (sf + dplyr + ggplot2):

ciudades %>%   
filter(poblacion > 100000) %>%   
ggplot()

El futuro: La evolución continúa

Actualmente, el ecosistema está madurando hacia el manejo de Big Data espacial (usando paquetes como arrow junto con sf) y la integración fluida con bases de datos espaciales (PostGIS) sin salir del entorno de RStudio.

IMPORTAR

Carga de datos.

CSV

#archivo local
datos <- read_csv("data/students.txt")
#desde una URL
datos <- read_csv("https://pos.it/r4ds-students-csv")
datos <- read_csv("data/students.txt", na = c("N/A", ""))

Escritura

write_csv(datos, "/data/datos-1.csv")

VECTORIAL: Geodata

El paquete geodata es el sucesor moderno para descargar datos (reemplazando al antiguo getData de raster). Las fuentes de datos para el paquete se encuentran en Fuentes de geodata.

Sin embargo, geodata descarga los objetos en formato SpatVector (del paquete terra), que es muy rápido pero no sigue estrictamente la lógica de dataframe que necesita el tidyverse.

Por tanto, el paso crucial es la conversión.

Paso 1: Cargar las librerías necesarias

Necesitas tres paquetes:

  1. geodata: Para descargar los mapas de GADM.

  2. sf: Para convertir el mapa a formato tidy (Simple Features).

  3. tidyverse: Para manipular los datos (dplyr, ggplot2).

library(geodata)   # Descarga de datos 
library(sf)        # Manejo espacial tidy 
library(tidyverse) # Manipulación y gráficos 
library(tidyterra) # Métodos y gráficos para objetos 'terra'

Paso 2: Descargar datos con gadm()

El proyecto gadm.org se encarga de organizar y distribuir los esfuerzos para mantener una base de datos actualizada y completa de archivos digitales vectoriales de los límites internacionales y político administrativos:

La función gadm() descarga los límites. Sus argumentos clave son:

  • country: El código ISO del país (ej. “ESP” para España, “CHL” para Chile, “ARG” para Argentina).
  • level: El nivel de detalle administrativo.

    • 0: Límite país.

    • 1: Estados/Regiones/Comunidades Autónomas.

    • 2: Provincias/Municipios (depende del país).

    • 3: Distrito/Comuna (depende del país).

    • 4: Canton (ej. Francia).

    • 5: Commune (ej. Francia).

  • path: Carpeta donde se guardarán los archivos (para no descargarlos cada vez).

# Descargamos nivel 1 (Regiones/Estados) de un país
# Esto devuelve un objeto "SpatVector" (no es tidy aún) 
mapa_crudo <- gadm(country = "ARG", 
                   level = 1, 
                   path = tempdir()) 

Un paquete muy potente y útil es rvest para facilitar la descarga y luego manipulación de HTML y XML

Códigos Iso

library(rvest)

html = read_html('https://www.iban.com/country-codes')
isos = html |> 
  html_elements("tr") |> 
  html_elements("td") |> 
  html_text() |>
  matrix(ncol = 4, byrow = TRUE) |>
  as_tibble()

Paso 3: El Puente a Tidy (st_as_sf)

Aquí ocurre la magia. Transformamos el objeto opaco de terra en un objeto sf (que es un dataframe con geometría).

# Convertimos a Simple Feature (sf) 
mapa_tidy <- st_as_sf(mapa_crudo)  
# Podemos ver que es una tabla 
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  <GEOMETRY [°]> MULTIPOLYGON (((-62.10181 -..., POLYGON ((-65.14583…

Sistemas coordenados

Hasta ahora, todos los datos se encuentran en coordenadas geográficas (latitud/longitud) expresadas en valores angulares.

Si intentamos, por ej. hacer un buffer de \(500\) metros sobre un mapa que está en grados, cometemos uno de los errores más comunes y frustrantes en R: el resultado suele ser deforme o minúsculo porque R interpreta \(500\) como grados, no como metros.

En la filosofía Tidyverse/sf, manejar proyecciones es explícito y fluye a través de las “tuberías” (pipes).

¿Por qué convertir?

Los datos de geodata (GADM) vienen por defecto en un sistema geográfico (WGS84).

  • Unidades: Grados decimales (Latitud/Longitud).
  • Forma: Esferoide (el mundo curvo).
  • Problema: No puedes medir distancias planas (metros) ni áreas con precisión (ni facilidad!) en una esfera deformada.

Necesitamos aplanar el mapa proyectándolo a un sistema métrico (como UTM o proyecciones nacionales).

Código: EPSG y st_transform()

En el mundo sf, usamos códigos EPSG (un número corto que identifica la proyección) y la función st_transform().

  • EPSG:4326: WGS84 (El estándar mundial de GPS/GADM). Unidad: Grados.
  • EPSG:3857: Pseudo-Mercator (Google Maps). Unidad: Metros (pero distorsiona áreas).
  • EPSG Locales: Depende del país (ej. ETRS89 para España, MAGNA para Colombia, SIRGAS para Chile). Unidad: Metros (alta precisión).

Preparemos nuestros datos para cálculos geométricos.

PASO 1. Recuperamos los datos y consultamos su “ADN” espacial. (st_crs)

#Conversión a Tidy (sf)
mapa_sf <- st_as_sf(mapa_crudo)

#Diagnóstico: ¿En qué sistema estamos?
st_crs(mapa_sf)$input
[1] "WGS 84"
# Debería decir "WGS 84" o algo similar.
# Nota importante: Si miras el dataframe, la columna 'geometry' muestra números como -77.7, -6.9 (Grados).
plot(mapa_sf$geometry)

PASO 2. La Transformación (st_transform)

Supongamos que queremos calcular áreas en metros cuadrados. Necesitamos pasar a una proyección por ej. UTM.

Aquí es donde brilla el estilo tidy: la transformación es solo un paso más en la cadena.

mapa_metros <- mapa_sf |>
  # Seleccionamos columnas de interés para limpiar
  select(nombre = NAME_1) |>
  # TRANSFORMACIÓN: Aquí ocurre la magia
  st_transform(crs = 32717) 

# Verificamos el cambio
glimpse(mapa_metros)
Rows: 24
Columns: 2
$ nombre   <chr> "Buenos Aires", "Catamarca", "Chaco", "Chubut", "Ciudad de Bu…
$ geometry <GEOMETRY [m]> MULTIPOLYGON (((2104164 533..., POLYGON ((2059871 67…
# Mira la columna 'geometry': ¡Ahora los números son gigantes! 
# (ej. 2059871 6723807). Son metros, no grados.

Una vez que tienes mapa en tidy, olvida que estás trabajando con mapas complejos. Piensa que es un Excel.

Escritura

st_write(mapa_metros, "data/mapa_metros.shp", append = FALSE)
Deleting layer `mapa_metros' using driver `ESRI Shapefile'
Writing layer `mapa_metros' to data source 
  `data/mapa_metros.shp' using driver `ESRI Shapefile'
Writing 24 features with 1 fields and geometry type Unknown (any).

RÁSTER: Elevación

Para mantener la filosofía Tidyverse mientras trabajamos con rasters (ej. datos de elevación), utilizaremos el paquete terra como motor de cálculo, ya que es mucho más rápido y moderno que el antiguo paquete raster, y se integra mejor con este flujo.

Necesitamos cargar elevatr para descargar el DEM (Modelo Digital de Elevación) y terra para procesarlo.

library(elevatr)
library(terra)

La fuente de datos para elevación, se encuentran detalladas en:

Fuente de Datos

El Flujo Tidy: Descarga y Procesamiento

Vamos a encadenar las operaciones. Nota cómo usamos rast() inmediatamente después de descargar para convertir el objeto al formato moderno de terra.

NOTA: usamos el objeto proyectado!!

# z = zoom (1 a 14). Un z=6 es ligero y bueno para ejemplos nacionales.
mapa_dtm = get_elev_raster(locations = mapa_metros, 
                           z = 6, 
                           src = "aws", 
                           clip = "locations", 
                           tmp_dir = tempdir(), 
                           ncpu = ifelse(future::availableCores() > 2, 2, 1)) |>
  rast()
mapa_dtm
class       : SpatRaster 
size        : 3866, 2351, 1  (nrow, ncol, nlyr)
resolution  : 964.2861, 964.2861  (x, y)
extent      : 1033587, 3300624, 3788232, 7516162  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=utm +zone=17 +south +datum=WGS84 +units=m +no_defs 
source(s)   : memory
name        : file1350e17302c3 
min value   :             -106 
max value   :             6615 
# Verificamos nombres (importante para el extract más adelante)
names(mapa_dtm) <- "elevacion"
pen=terrain(mapa_dtm)
asp=terrain(mapa_dtm,"aspect")
hill=shade(pen,asp)
plot(hill,col=grey.colors(255))

Transformación de un objeto ráster:

mapa_geo <- mapa_dtm |>
  project("+proj=longlat +datum=WGS84")

plot(mapa_geo)

Escritura

writeRaster(mapa_geo, "data/mapa_geo.tif", overwrite = T, NAflag=255)