Más que estadística. Un entorno de software libre diseñado específicamente para el análisis de datos y la creación de gráficos de alta calidad.
Un SIG de código abierto. A través de sus paquetes (como sf, terra o lidR), R se transforma en un Sistema de Información Geográfica extremadamente potente, automatizable y reproducible.
Orientado a la comunidad. Tiene una de las comunidades de usuarios más activas del mundo, cualquier problema espacial que enfrentes, alguien más ya lo resolvió y documentó.
R (El Motor): Hace todo el cálculo pesado por detrás, pero su interfaz por defecto es muy básica (una consola de texto).
RStudio (El Tablero de Control): Es un Entorno de Desarrollo Integrado (IDE). Es la interfaz gráfica amable que usamos para escribir el código, ver los mapas, gestionar los archivos y revisar las variables, todo en una sola pantalla.
Paso 1: Instalar el “Motor” (R) cran.r-project.org. Descargar e instalar la versión para tu sistema operativo (Windows, Mac o Linux). Dejar todas las opciones por defecto durante la instalación.
Paso 2: Instalar el “Tablero” (RStudio) posit.co/download/rstudio-desktop/. Descargar e instalar RStudio Desktop (versión gratuita).
Paso 3: Instalar las “Herramientas” (Paquetes) Abrir RStudio por primera vez y usar la consola para instalar nuestro ecosistema básico.
Objetivo: Lograr fluidez en la manipulación y limpieza de grandes volúmenes de datos, una habilidad crucial para manejar las extensas tablas de atributos de los archivos .las.
Para instalar el tidyverse completo:
Los paquetes de Tidyverse trabajan con datos tidy: ordenados, organizados.
los datos tidy deben cumplir con tres características:
# 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…
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).
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”.
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.
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”.
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 |
Puntos, líneas y polígonos. “pipe” %>% o |> |
| Rasters | terra |
Muy rápido, se integra bien. |
| LiDAR | lidR |
Nubes puntos. Filtrar, segmentar y analizar. |
| Á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. |
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()
Se necesita filtrar un mapa de ciudades para dejar solo las grandes y luego dibujarlas.
Sin filosofía Tidy (Estilo antiguo/base):
Con filosofía Tidy (sf + dplyr + ggplot2):
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.
Carga de datos.
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:
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"
Seleccionar observaciones (filas) por condición lógica.
Seleccionar observaciones (filas) por posición.
Ordenar filas.
En piping…
mutate permite crear una nueva columna o variable, usar transmute para dejar solo la nueva.
# 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ñ…
Necesitas tres paquetes:
geodata: Para descargar los mapas de GADM.
sf: Para convertir el mapa a formato tidy (Simple Features).
tidyverse: Para manipular los datos (dplyr, ggplot2).
lidR: Para trabajar con nubes de puntos LiDAR.
Para aplicar todo lo que aprendimos de dplyr, vamos a extraer la tabla de datos (“payload”) que vive dentro del objeto espacial y la convertiremos en un tibble.
Rows: 122,028
Columns: 16
$ X <dbl> 308983.3, 308983.4, 308983.3, 308983.2, 308983.4, 30…
$ Y <dbl> 6047094, 6047094, 6047094, 6047094, 6047094, 6047094…
$ Z <dbl> 576.149, 579.129, 578.819, 577.649, 582.309, 578.909…
$ gpstime <dbl> 136000.3, 136000.3, 136000.3, 136000.3, 136000.3, 13…
$ Intensity <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ ReturnNumber <int> 2, 2, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2…
$ NumberOfReturns <int> 2, 3, 3, 3, 2, 3, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 3…
$ ScanDirectionFlag <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ EdgeOfFlightline <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ Classification <int> 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1…
$ Synthetic_flag <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
$ Keypoint_flag <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
$ Withheld_flag <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
$ ScanAngleRank <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ UserData <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
$ PointSourceID <int> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5…
Ahora podemos responder preguntas sobre nuestra nube de puntos usando tuberías (%>% o |>) y verbos clásicos.
group_by + summarise)¿Cuántos puntos tenemos por cada clase y cuál es la altura máxima en cada una? (Recordemos: la clase 2 suele ser el terreno, la clase 5 vegetación alta, etc.)
resumen_clases <- datos_lidar |>
group_by(Classification) |>
summarise(
cantidad_puntos = n(),
altura_maxima = max(Z),
intensidad_media = mean(Intensity)
) |>
arrange(desc(cantidad_puntos))
glimpse(resumen_clases)Rows: 2
Columns: 4
$ Classification <int> 1, 2
$ cantidad_puntos <int> 112607, 9421
$ altura_maxima <dbl> 634.775, 621.555
$ intensidad_media <dbl> 1, 1
filter + select + mutate)Vamos a crear una nueva tabla que contenga solo los puntos del terreno (Classification == 2). Nos quedaremos solo con las coordenadas y, a modo de ejemplo, pasaremos la elevación de metros a centímetros.
terreno_limpio <- datos_lidar |>
filter(Classification == 2) |>
select(X, Y, Z) |>
mutate(Z_cm = Z * 100)
head(terreno_limpio)# A tibble: 6 × 4
X Y Z Z_cm
<dbl> <dbl> <dbl> <dbl>
1 308983. 6047094. 576. 57615.
2 308983. 6047095. 577. 57674.
3 308983. 6047095. 577. 57681.
4 308983. 6047096. 577. 57694.
5 308983. 6047095. 577. 57692.
6 308983. 6047095. 577. 57686.
# 4. Construimos el gráfico
ggplot(data = data_muestra, aes(x = X, y = Y, color = Z)) +
# Usamos puntos muy pequeños y algo de transparencia
geom_point(size = 0.1, alpha = 0.8) +
# Aplicamos una paleta de colores continua tipo topografía
scale_color_viridis_c(option = "turbo") +
# CRÍTICO: Forzamos la relación de aspecto 1:1.
coord_equal() +
# Limpiamos el fondo para que parezca un mapa y no un gráfico estadístico
theme_void() +
# Añadimos las etiquetas
labs(
title = "Vista en Planta (2.5D) de Nube de Puntos",
subtitle = "Renderizado nativo con ggplot2 (Muestra del 25%)",
color = "Elevación (m)"
)lidRsfPara delimitar un área de Interés (AOI) mediante polígonos. Poder de sf para crear geometrías y la función clip_roi() de lidR para aislar los datos.
# 1. Calcular el centro aproximado de nuestra nube de puntos
centro_x <- mean(nube_puntos@data$X)
centro_y <- mean(nube_puntos@data$Y)
# 2. Crear una geometría sf (un punto) y asignarle el CRS correcto
# CRÍTICO: Para intersectar, sf y lidR deben "hablar" en el mismo sistema de coordenadas
crs_nube <- st_crs(nube_puntos)aoi_punto <- st_point(c(centro_x, centro_y)) |>
st_sfc(crs = crs_nube)
# 3. Crear un buffer (área de influencia) de 50 metros alrededor del punto
aoi_poligono <- st_buffer(aoi_punto, dist = 50)
# 3a. Escribir un archivo shapefile para visualizarlo en QGIS o ArcGIS (opcional).
st_write(aoi_poligono, "data/aoi_poligono.shp")# 6. Visualización integrada: Polígono + Nube de Puntos
ggplot() +
# Capa base vectorial: Dibujamos nuestro polígono de recorte vacío y con borde rojo
geom_sf(data = aoi_poligono, fill = NA, color = "red", linewidth = 1) +
# Capa LiDAR: Ploteamos los puntos que sobrevivieron al recorte
geom_point(data = df_recorte, aes(x = X, y = Y, color = Z), size = 0.8, alpha = 0.9) +
# Estética del mapa
scale_color_viridis_c(option = "turbo") +
# coord_sf se encarga de mantener la proporción espacial y dibujar la grilla de coordenadas
coord_sf() +
theme_minimal() +
labs(
title = "Recorte Espacial (Clipping) de Nube LiDAR",
subtitle = "Intersección entre geometría sf (buffer de 50m) y objeto LAS",
color = "Elevación (m)",
x = "Longitud (X)",
y = "Latitud (Y)"
)