-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path03-atributos.Rmd
772 lines (607 loc) · 46.4 KB
/
03-atributos.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# Operaciones de datos de atributos {#attr}
## Prerrequisitos {-}
- Este capítulo requiere que se instalen y adjunten los siguientes paquetes:
```{r 03-attribute-operations-1, message=FALSE}
library(sf) # paquete de datos vectoriales introducido en el capítulo 2
library(terra) # paquete de datos raster introducido en el capítulo 2
library(dplyr) # paquete tidyverse para la manipulación de marcos de datos
```
- También depende de **spData**, que carga los conjuntos de datos utilizados en los ejemplos de código de este capítulo:
```{r 03-attribute-operations-2, results='hide'}
library(spData) # paquete de datos espaciales introducido en el capítulo 2
```
## Introducción
Los datos de atributos son la información no espacial asociada a los datos geográficos (geométricos).
Un ejemplo sencillo es el de una parada de autobús: su posición suele estar representada por coordenadas de latitud y longitud (datos geométricos), además de su nombre.
La parada [Elephant & Castle / New Kent Road](https://www.openstreetmap.org/relation/6610626) de Londres, por ejemplo, tiene unas coordenadas de -0,098 grados de longitud y 51,495 grados de latitud que pueden representarse como `POINT (-0,098 51,495)` en la representación `sfc` descrita en el capítulo \@ref(spatial-class).
Los atributos, como el nombre *atributo*\index{attribute} de la función POINT (por utilizar la terminología de Simple Features) son el tema de este capítulo.
```{r, eval=FALSE, echo=FALSE}
# Objetivo: encontrar una parada de autobús en el centro de Londres
library(osmdata)
london_coords = c(-0.1, 51.5)
london_bb = c(-0.11, 51.49, -0.09, 51.51)
bb = tmaptools::bb(london_bb)
osm_data = opq(bbox = london_bb) %>%
add_osm_feature(key = "highway", value = "bus_stop") %>%
osmdata_sf()
osm_data_points = osm_data$osm_points
osm_data_points[4, ]
point_vector = round(sf::st_coordinates(osm_data_points[4, ]), 3)
point_df = data.frame(name = "London bus stop", point_vector)
point_sf = sf::st_as_sf(point_df, coords = c("X", "Y"))
```
Otro ejemplo es el valor de elevación (atributo) de una celda específica de la cuadrícula en los datos raster.
A diferencia del modelo de datos vectoriales, el modelo de datos raster almacena la coordenada de la celda de la cuadrícula de forma indirecta, lo cual significa que la distinción entre atributo e información espacial es menos clara.
Para ilustrar este punto, piensa en un píxel en la 3ª fila y la 4ª columna de una matriz raster.
Su ubicación espacial está definida por su índice en la matriz: se mueve desde el origen cuatro celdas en la dirección x (normalmente este y derecha en los mapas) y tres celdas en la dirección y (normalmente sur y abajo).
La *resolución* del raster define la distancia para cada paso en x e y que se especifica en un *cabezal*.
La cabecera es un componente vital de los conjuntos de datos raster que especifica cómo se relacionan los píxeles con las coordenadas geográficas (véase también el capítulo \@ref(spatial-operations)).
Esto muestra cómo manipular objetos geográficos basados en atributos como los nombres de las paradas de autobús en un conjunto de datos vectoriales y las elevaciones de los píxeles en un conjunto de datos rasterizados.
En el caso de los datos vectoriales, esto implica técnicas como crear subconjuntos y o agregaciones (véanse las secciones \@ref(vector-attribute-subsetting) y \@ref(vector-attribute-aggregation)).
Las secciones \@ref(vector-attribute-joining) y \@ref(vec-attr-creation) demuestran cómo unir datos en objetos de características simples utilizando un ID compartido y cómo crear nuevas variables, respectivamente.
Cada una de estas operaciones tiene un equivalente espacial:
el operador `[` en R básico, por ejemplo, funciona igualmente para subconjuntar objetos basados en su atributo y objetos espaciales; también se pueden unir atributos en dos conjuntos de datos geográficos utilizando uniones espaciales.
Esto es una buena noticia: las habilidades desarrolladas en este capítulo son transferibles.
El capítulo \@ref(spatial-operations) extiende los métodos presentados aquí al mundo espacial.
Después de una inmersión profunda en varios tipos de operaciones de atributos *vectoriales* en la siguiente sección, las operaciones de datos de atributos *raster* se cubren en la Sección \ref(manipulando-objetos-raster), que demuestra cómo crear capas raster que contienen atributos continuos y categóricos y cómo extraer valores de celdas de una o más capas (subconjunto raster).
La sección \@ref(summarizing-raster-objects) proporciona una visión general de las operaciones ráster "globales" que pueden utilizarse para resumir conjuntos de datos raster completos.
## Manipulación de atributos vectoriales
Los conjuntos de datos vectoriales geográficos están bien soportados en R gracias a la clase `sf`, que extiende la clase `data.frame` de R.
Al igual que los marcos de datos, los objetos `sf` tienen una columna por variable de atributo (como 'nombre') y una fila por observación o *característica* (por ejemplo, por estación de autobuses).
Los objetos `sf` se diferencian de los marcos de datos básicos porque tienen una columna de `geometría` de la clase `sfc` que puede contener una serie de entidades geográficas (uno o 'múltiples' puntos, líneas y polígonos) por fila.
Esto se describió en el capítulo \@ref(spatial-class), donde se demostró cómo *los métodos genéricos* como `plot()` y `summary()` funcionan con los objetos `sf`.
**sf** también proporciona genéricos que permiten que los objetos `sf` se comporten como marcos de datos normales, como se muestra al imprimir los métodos de la clase:
```{r 03-attribute-operations-3, eval=FALSE}
methods(class = "sf") # métodos para objetos sf, se muestran los 12 primeros
```
```{r 03-attribute-operations-4}
#> [1] aggregate cbind coerce
#> [4] initialize merge plot
#> [7] print rbind [
#> [10] [[<- $<- show
```
```{r 03-attribute-operations-5, eval=FALSE, echo=FALSE}
# Otra forma de mostrar los métodos de sf:
attributes(methods(class = "sf"))$info %>%
filter(!visible)
```
Muchos de ellos (`aggregate()`, `cbind()`, `merge()`, `rbind()` y `[`) sirven para manejar marcos de datos.
Por ejemplo, `rbind()` une dos marcos de datos, uno "sobre" el otro.
`$<-` crea nuevas columnas.
Una característica clave de los objetos `sf` es que almacenan datos espaciales y no espaciales de la misma manera, como columnas en un `data.frame`.
```{block2 03-attribute-operations-6, type = 'rmdnote'}
La columna de geometría de los objetos `sf` suele llamarse `geometría` o `geom`, pero puede utilizarse cualquier nombre.
El siguiente comando, por ejemplo, crea una columna de geometría llamada `g`:
`st_sf(data.frame(n = world$name_long), g = world$geom)`
Esto permite que las geometrías importadas de las bases de datos espaciales tengan varios nombres, como `wkb_geometry` y `the_geom`.
```
Los objetos `sf` también pueden extender las clases `tidyverse` para marcos de datos, `tibble` y `tbl`.
\index{tidyverse (package)}.
Por lo tanto, **sf** permite dar rienda suelta a toda la potencia de las capacidades de análisis de datos de R en los datos geográficos, tanto si se utiliza la base de R como las funciones de tidyverse para el análisis de datos.
\index{tibble}
(Ver [`Rdatatable/data.table#2273`](https://github.com/Rdatatable/data.table/issues/2273) para ver la compatibilidad entre los objetos `sf` y el paquete `data.table`).
Antes de utilizar estas capacidades, merece la pena repasar cómo descubrir las propiedades básicas de los objetos de datos vectoriales.
Empecemos por utilizar las funciones básicas de R para conocer el conjunto de datos `world` del paquete **spData**:
```{r 03-attribute-operations-7}
class(world) # es un objeto sf y un marco de datos (ordenado)
dim(world) # es un objeto bidimensional, con 177 filas y 11 columnas
```
`world` contiene diez columnas no geográficas (y una columna que contiene una lista de geometrías) con casi 200 filas que representan los países del mundo.
La función `st_drop_geometry()` mantiene sólo los datos de los atributos de un objeto `sf`, es decir, elimina su geometría:
```{r 03-attribute-operations-8}
world_df = st_drop_geometry(world)
class(world_df)
ncol(world_df)
```
La eliminación de la columna de geometría antes de trabajar con los datos de atributos puede ser útil; los procesos de manipulación de datos pueden ejecutarse más rápido cuando trabajan sólo con los datos de atributos y las columnas de geometría no siempre son necesarias.
En la mayoría de los casos, sin embargo, tiene sentido mantener la columna de geometría, lo que explica que la columna sea "pegajosa" (permanece después de la mayoría de las operaciones de atributos a menos que se elimine específicamente).
Las operaciones de datos no espaciales sobre objetos `sf` sólo cambian la geometría de un objeto cuando es apropiado (por ejemplo, disolviendo los bordes entre polígonos adyacentes tras una agregación).
Convertirse en un experto en la manipulación de datos de atributos geográficos significa convertirse en un experto en la manipulación de marcos de datos.
Para muchas aplicaciones, el paquete tidyverse \index{tidyverse (package)} **dplyr** ofrece un enfoque eficaz para trabajar con marcos de datos.
La compatibilidad con Tidyverse es una ventaja de **sf** frente a su predecesor **sp**, pero hay que evitar algunos inconvenientes (véase la viñeta complementaria `tidyverse-pitfalls` en [geocompr.github.io](https://geocompr.github.io/geocompkg/articles/tidyverse-pitfalls.html) para más detalles).
### Subconjuntos de atributos vectoriales {#vector-attribute-subsetting}
Los métodos de subconjunto de R base incluyen el operador `[` y la función `subset()`.
Las funciones clave para manejar y crear subconjuntos de **dplyr** son `filter()` y `slice()` para crear subconjuntos de filas, y `select()` para crear subconjuntos de columnas.
Ambos planteamientos conservan los componentes espaciales de los datos de atributos en los objetos `sf`, mientras que si se utiliza el operador `$` o la función **dplyr** `pull()` para devolver una única columna de atributos como vector se perderán los datos de atributos, tal y como veremos.
\index{attribute!subsetting}
Esta sección se centra en el subconjunto de marcos de datos `sf`; para más detalles sobre el subconjunto de vectores y marcos de datos no geográficos recomendamos leer la sección [2.7](https://cran.r-project.org/doc/manuals/r-release/R-intro.html#Index-vectors) de An Introduction to R [@rcoreteam_introduction_2021] y el Capítulo [4](https://adv-r.hadley.nz/subsetting.html) de Advanced R Programming [@wickham_advanced_2019], respectivamente.
El operador `[` puede dividir tanto filas como columnas.
Los índices colocados dentro de los corchetes situados directamente después del nombre de un objeto de marco de datos especifican los elementos que se quieren conservar.
El comando `object[i, j]` significa 'devolver las filas representadas por `i` y las columnas representadas por `j`, donde `i` y `j` suelen contener enteros o `TRUE` y `FALSE` (los índices también pueden ser caracteres, indicando los nombres de las filas o las columnas).
`object[5, 1:3]`, por ejemplo, significa `devolver datos que contengan la 5ª fila y las columnas 1 a 3`: el resultado debería ser un marco de datos con sólo 1 fila y 3 columnas, y una cuarta columna de geometría si es un objeto `sf`.
Si se deja `i` o `j` vacía se devuelven todas las filas o columnas, por lo que `world[1:5, ]` devuelve las cinco primeras filas y las 11 columnas que componen el marco de datos.
Los ejemplos que hay a continuación demuestran la creación de subconjunto con R base.
Adivina el número de filas y columnas de los marcos de datos `sf` devueltos por cada comando y comprueba los resultados en tu propio ordenador (consulta el final del capítulo para ver más ejercicios):
```{r 03-attribute-operations-9, eval=FALSE}
world[1:6, ] # Subconjunto de las filas 1 a 6
world[, 1:3] # Subconjunto de las columnas 1 a 3
world[1:6, 1:3] # Subconjunto de las filas 1 a 6 y las columnas 1 a 3
world[, c("name_long", "pop")] # Columnas por nombre
world[, c(T, T, F, F, F, F, F, T, T, F, F)] # Selección de columnas por índices lógicos
world[, 888] # Índice representando una columna no existenete
```
```{r, eval=FALSE, echo=FALSE}
# Estas fallan
world[c(1, 5), c(T, T)]
world[c(1, 5), c(T, T, F, F, F, F, F, T, T, F, F, F)]
```
Una demostración de la utilidad de utilizar vectores `lógicos` para el subconjunto se muestra en el fragmento de código siguiente.
Esto crea un nuevo objeto, `small_countries`, que contiene las naciones cuya superficie es inferior a 10.000 km^2^:
```{r 03-attribute-operations-10}
i_small = world$area_km2 < 10000
summary(i_small) # un vector lógico
small_countries = world[i_small, ]
```
El objeto `i_small` (abreviatura de "índice" que representa a los países pequeños) es un vector lógico que se puede utilizar para agrupar los siete países más pequeños del `mundo` por su superficie.
Un comando más conciso, que omita el objeto intermediario (`i_small`), genera el mismo resultado:
```{r 03-attribute-operations-11}
small_countries = world[world$area_km2 < 10000, ]
```
La función base de R `subset()` proporciona otra forma de conseguir el mismo resultado:
```{r 03-attribute-operations-12, eval=FALSE}
small_countries = subset(world, area_km2 < 10000)
```
Las funciones de R base son maduras, estables y ampliamente utilizadas, lo que las convierte en una opción sólida, especialmente en contextos en los que la reproducibilidad y la fiabilidad son fundamentales.
Las funciones de **dplyr** permiten flujos de trabajo "ordenados" que algunas personas (incluidos los autores de este libro) encuentran intuitivos y productivos para el análisis interactivo de datos, especialmente cuando se combinan con editores de código como RStudio que permiten [autocompletar](https://support.rstudio.com/hc/en-us/articles/205273297-Code-Completion-in-the-RStudio-IDE) los nombres de las columnas.
A continuación se muestran las funciones clave para el subconjunto de marcos de datos (incluidos los marcos de datos `sf`) con las funciones **dplyr**.
<!-- La frase que sigue parece no ser cierta según el punto de referencia que se indica a continuación. -->
<!-- `dplyr` también es más rápido que R base para algunas operaciones, debido a su backend C++\index{C++}. -->
<!-- ¿Algo sobre dbplyr? Nunca he visto a nadie usarlo regularmente para datos espaciales 'en el campo' así que omitiremos la parte de la integración con dbs por ahora (RL 2021-10) -->
<!-- Las principales funciones de **dplyr** para crear subgrupos son `select()`, `slice()`, `filter()` y `pull()`. -->
```{r, echo=FALSE, eval=FALSE}
# Objetivo: benchmark base vs dplyr subsetting
# ¿Podría trasladarse a otro lugar?
i = sample(nrow(world), size = 10)
benchmark_subset = bench::mark(
world[i, ],
world %>% slice(i)
)
benchmark_subset[c("expression", "itr/sec", "mem_alloc")]
# # October 2021 on laptop with CRAN version of dplyr:
# # A tibble: 2 × 3
# expression `itr/sec` mem_alloc
# <bch:expr> <dbl> <bch:byt>
# 1 world[i, ] 1744. 5.55KB
# 2 world %>% slice(i) 671. 4.45KB
```
`select()` selecciona las columnas por nombre o posición.
Por ejemplo, podrías seleccionar sólo dos columnas, `name_long` y `pop`, con el siguiente comando:
```{r 03-attribute-operations-14}
world1 = dplyr::select(world, name_long, pop)
names(world1)
```
Nota: al igual que con el comando equivalente en R base (`world[, c("name_long", "pop")]`), la columna `geom` permanece.
`select()` también permite seleccionar un rango de columnas con la ayuda del operador `:`:
```{r 03-attribute-operations-15}
# Selecciona todas las columnas entre name_long y pop (incluidas)
world2 = dplyr::select(world, name_long:pop)
```
Puedes eliminar columnas específicas con el operador `-`:
```{r 03-attribute-operations-16}
# Muestra todas las columnas excepto subregion y area_km2
world3 = dplyr::select(world, -subregion, -area_km2)
```
Crear subconjuntos y renombrar columnas al mismo tiempo con la sintaxis `nuevo_nombre = antiguo_nombre`:
```{r 03-attribute-operations-17}
world4 = dplyr::select(world, name_long, population = pop)
```
Cabe destacar que el comando anterior es más conciso que el equivalente en R base, el cual requiere dos líneas de código:
```{r 03-attribute-operations-18, eval=FALSE}
world5 = world[, c("name_long", "pop")] # subagrupar las columnas por nombre
names(world5)[names(world5) == "pop"] = "population" # renombrar la columna manualmente
```
`select()` también funciona con "funciones de ayuda" para operaciones más avanzadas, como `contains()`, `starts_with()` y `num_range()` (véase la página de ayuda con `?select` para más detalles).
La mayoría de los verbos de **dplyr** devuelven un marco de datos, pero se puede extraer una sola columna como vector con `pull()`.
<!-- Note: I have commented out the statement below because it is not true for `sf` objects, it's a bit confusing that the behaviour differs between data frames and `sf` objects. -->
<!-- The subsetting operator in base R (see `?[`), by contrast, tries to return objects in the lowest possible dimension. -->
<!-- This means selecting a single column returns a vector in base R as demonstrated in code chunk below which returns a numeric vector representing the population of countries in the `world`: -->
Puede obtener el mismo resultado en R base con los operadores de subconjunto de listas `$` y `[[`, los tres comandos siguientes devuelven el mismo vector numérico:
```{r 03-attribute-operations-21, eval = FALSE}
pull(world, pop)
world$pop
world[["pop"]]
```
<!-- Commenting out the following because it's confusing and covered better in other places (RL, 2021-10) -->
<!-- To turn off this behavior, set the `drop` argument to `FALSE`, -->
```{r 03-attribute-operations-19, eval=FALSE, echo=FALSE}
# crear un marco de datos deshechable
d = data.frame(pop = 1:10, area = 1:10)
# retornar una sola columna (pop) en forma de marco de datos
d[, "pop", drop = FALSE] # equivalente a d["pop"]
select(d, pop)
# retornar un vector al seleccionar una sola columna
d[, "pop"]
pull(d, pop)
```
```{r 03-attribute-operations-20, echo=FALSE, eval=FALSE}
x1 = d[, "pop", drop = FALSE] # equivalente a d["pop"]
x2 = d["pop"]
identical(x1, x2)
```
`slice()` es el equivalente de fila de `select()`.
El siguiente fragmento de código, por ejemplo, selecciona las filas 1 a 6:
```{r 03-attribute-operations-22, eval=FALSE}
slice(world, 1:6)
```
`filter()` es el equivalente de **dplyr** a la función `subset()` de R base.
Mantiene sólo las filas que coinciden según los criterios dados, por ejemplo, sólo los países con un área por debajo de un determinado umbral, o con un promedio alto de esperanza de vida, como se muestra en los siguientes ejemplos:
```{r 03-attribute-operations-23, eval=FALSE}
world7 = filter(world ,area_km2 < 10000) # países con un área menor a 10.000km2
world7 = filter(world, lifeExp > 82) # con una esperanza de vida superior a 82 años
```
El conjunto estándar de operadores de comparación se puede utilizar en la función `filter()`, como se ilustra en la Tabla \@ref(tab:operators):
```{r operators0, echo=FALSE}
if (knitr::is_html_output()){
operators = c("`==`", "`!=`", "`>`, `<`", "`>=`, `<=`", "`&`, <code>|</code>, `!`")
} else {
operators = c("==", "!=", ">, <", ">=, <=", "&, |, !")
}
```
```{r operators, echo=FALSE}
operators_exp = c("Igual a", "Distinto a", "Mayor/menor que",
"Mayor/menor que o igual",
"Operadores lógicos: Y, O, No (And, Or, Not)")
knitr::kable(tibble(Symbol = operators, Name = operators_exp),
caption = paste("Operadores de comparación que retornan Booleanos",
"(TRUE/FALSE)."),
caption.short = "Operadores de comparación que retornan Booleanos.",
booktabs = TRUE)
```
### Encadenamiento de comandos con el operador pipe
Una de las claves para el flujo de trabajo con las funciones de **dplyr** es el operador ['pipe'](http://r4ds.had.co.nz/pipes.html) `%>%` (y desde R `4.1.0` el pipe nativo `|>`), el cual toma su nombre del pipe de Unix `|` [@grolemund_r_2016].
Los pipes permiten un código expresivo: el resultado de una función anterior se convierte en el primer argumento de la siguiente función, lo que permite *encadenar*.
Esto se ilustra a continuación, en el que sólo se filtran los países de Asia del conjunto de datos `world`, a continuación se seleccionan dos columnas para crear un subconjunto (`name_long` y `continent`) y las cinco primeras filas (resultado no mostrado).
```{r 03-attribute-operations-24}
world7 = world %>%
filter(continent == "Asia") %>%
dplyr::select(name_long, continent) %>%
slice(1:5)
```
El fragmento anterior muestra cómo el operador pipe permite escribir los comandos en un orden claro:
los anteriores van de arriba a abajo (línea por línea) y de izquierda a derecha.
La alternativa a `%>%` son las llamadas a funciones 'anidadas', que son más difíciles de leer:
```{r 03-attribute-operations-25}
world8 = slice(
dplyr::select(
filter(world, continent == "Asia"),
name_long, continent),
1:5)
```
### Agregación de atributos vectoriales {#vector-attribute-aggregation}
\index{attribute!aggregation}
\index{aggregation}
La agregación implica resumir los datos con una o más "variables de agrupación", normalmente a partir de columnas del marco de datos que se va a agregar (la agregación geográfica se trata en el siguiente capítulo).
Un ejemplo de agregación de atributos es el cálculo del número de personas por continente a partir de los datos a nivel de país (una fila por país).
El conjunto de datos `world` contiene los ingredientes necesarios: las columnas `pop` y `continent`, la población y la variable de agrupación, respectivamente.
El objetivo es encontrar la suma `sum()` de las poblaciones de los países para cada continente, lo que resulta en un marco de datos más pequeño (la agregación es una forma de reducción de datos y puede ser un paso inicial útil cuando se trabaja con grandes conjuntos de datos).
Esto se puede hacer con la función básica de R `agregate()` de la siguiente manera:
```{r 03-attribute-operations-26}
world_agg1 = aggregate(pop ~ continent, FUN = sum, data = world, na.rm = TRUE)
class(world_agg1)
```
El resultado es un marco de datos no espacial con seis filas, una por continente, y dos columnas que informan del nombre y la población de cada continente (véase la tabla \@ref(tab:continents) con los resultados de los 3 continentes más poblados).
`aggregate()` es una [función genérica](https://adv-r.hadley.nz/s3.html#s3-methods) lo que significa que se comporta de forma diferente en función de lo que se le añada.
**sf** proporciona el método `aggregate.sf()` que se activa automáticamente cuando `x` es un objeto `sf` al que se le proporciona un argumento `by`:
```{r 03-attribute-operations-27}
world_agg2 = aggregate(world["pop"], list(world$continent), FUN = sum, na.rm = TRUE)
class(world_agg2)
nrow(world_agg2)
```
El resultado es un objeto espacial `world_agg2` que contiene 8 características que representan los continentes del mundo (y el océano abierto).
`group_by() %>% summarize()` es el equivalente en **dplyr** de la función `aggregate()`, con el nombre de la variable proporcionado en la función `group_by()` especificando la variable de agrupación y la información sobre lo que se va a resumir al pasarle la función `summarize()`, como se muestra a continuación:
```{r 03-attribute-operations-28}
world_agg3 = world %>%
group_by(continent) %>%
summarize(pop = sum(pop, na.rm = TRUE))
```
El enfoque puede parecer más complejo, pero tiene ventajas: flexibilidad, legibilidad y control sobre los nuevos nombres de las columnas.
Esta flexibilidad se ilustra en el siguiente comando, que calcula no sólo la población, sino también la superficie y el número de países de cada continente:
```{r 03-attribute-operations-29}
world_agg4 = world %>%
group_by(continent) %>%
summarize(pop = sum(pop, na.rm = TRUE), `area (sqkm)` = sum(area_km2), n = n())
```
En el fragmento de código anterior `pop`, `area (sqkm)` y `n` son nombres de columnas en el resultado, y `sum()` y `n()` eran las funciones de agregación.
Estas funciones de agregación devuelven objetos `sf` con filas que representan los continentes y geometrías que contienen múltiples polígonos que representan cada masa de tierra e islas asociadas (esto funciona gracias a la operación geométrica "unión", como se explica en la sección \@ref(geometry-unions)).
Combinemos lo que hemos aprendido hasta ahora sobre las funciones de **dplyr**, encadenando varios comandos para resumir datos de atributos sobre todos los países por continente.
El siguiente comando calcula la densidad de población (con `mutate()`), ordena los continentes por el número de países que contienen (con `dplyr::arrange()`), y mantiene sólo los 3 continentes más poblados (con `top_n()`), cuyo resultado se presenta en la Tabla \@ref(tab:continents)):
```{r 03-attribute-operations-30}
world_agg5 = world %>%
st_drop_geometry() %>% # aisla la geometría para ganar velocidad
dplyr::select(pop, continent, area_km2) %>% # crea un subconjunto con las columnas de interés
group_by(continent) %>% # agrupa por continente y resume los datos
summarize(Pop = sum(pop, na.rm = TRUE), Area = sum(area_km2), N = n()) %>%
mutate(Density = round(Pop / Area)) %>% # calcula la densidad de población
top_n(n = 3, wt = Pop) %>% # muestra sólo las 3 primeras filas
arrange(desc(N)) # ordenar los 3 continentes en orden descendiente
```
```{r continents, echo=FALSE}
options(scipen = 999)
knitr::kable(
world_agg5,
caption = "Los 3 continentes más poblados ordenados por densidad de población (personas por km2).",
caption.short = "Los 3 continentes más poblados.",
booktabs = TRUE
)
```
```{block2 03-attribute-operations-31, type='rmdnote'}
Se puede encontrar más información en las páginas de ayuda (a las que se puede acceder a través de `?summarize` y `vignette(package = "dplyr")` y en el capítulo 5 de [R for Data Science](http://r4ds.had.co.nz/transform.html#grouped-summaries-with-summarize).
```
### Unión de atributos vectoriales {#vector-attribute-joining}
Combinar datos de diferentes fuentes es una tarea frecuente en la preparación de datos.
Las uniones hacen esto combinando tablas basadas en una variable "clave" compartida.
**dplyr** tiene múltiples funciones de unión incluyendo `left_join()` y `inner_join()` --- véase `vignette("two-table")` para una lista completa.
Estos nombres de funciones siguen las convenciones utilizadas en el lenguaje de las bases de datos [SQL](http://r4ds.had.co.nz/relational-data.html) [@grolemund_r_2016, Chapter 13]; utilizándolos para unir conjuntos de datos no espaciales a objetos `sf`.
El objetivo de esta sección es aprender a realizar este tipo de unión.
Las funciones de unión de **dplyr** funcionan igual en los marcos de datos y en los objetos `sf`, la única diferencia importante es la columna de la lista `geometría`.
El resultado de las uniones de datos puede ser tanto un objeto `sf` como un objeto `data.frame`.
El tipo más común de unión de atributos en datos espaciales toma un objeto `sf` como primer argumento y le añade columnas de un `data.frame` especificado como segundo argumento.
\index{join}
\index{attribute!join}
Para demostrar las uniones, combinaremos los datos sobre la producción de café con el conjunto de datos `world`.
Los datos sobre el café se encuentran en un marco de datos llamado `coffee_data` del paquete **spData** (véase `?coffee_data` para más detalles).
`coffee_data` tiene 3 columnas:
`name_long` nombra las principales naciones productoras de café; y `coffee_production_2016` y `coffee_production_2017` contienen valores estimados de la producción de café en unidades de sacos de 60 kg para cada año.
Un "left join", que conserva el primer conjunto de datos, combina "world" con "coffee_data":
```{r 03-attribute-operations-32, warning=FALSE}
world_coffee = left_join(world, coffee_data)
class(world_coffee)
```
Dado que los conjuntos de datos de entrada comparten una "variable clave" (`name_long`), la unión ha funcionado sin utilizar el argumento `by` (véase `?left_join` para más detalles).
El resultado es un objeto `sf` idéntico al objeto original `world` pero con dos nuevas variables sobre la producción de café.
Esto puede ser representado como un mapa, tal y como se ilustra en la Figura \@ref(fig:coffeemap), generada con la función `plot()` a continuación:
```{r coffeemap, fig.cap="Producción mundial de café (en miles de sacos de 60 kg) por país, 2017. Fuente: Organización Internacional del Café.", fig.scap="Producción mundial de café por países."}
names(world_coffee)
plot(world_coffee["coffee_production_2017"])
```
Para que la unión funcione, se debe proporcionar una "variable clave" en ambos conjuntos de datos.
Por defecto **dplyr** utiliza todas las variables con nombres coincidentes.
En este caso, ambos objetos `world_coffee` y `world` contenían una variable llamada `name_long`, lo que explica el mensaje `Joining, by = "name_long"`.
En la mayoría de los casos en que los nombres de las variables no son iguales. En esos casos tienes dos opciones:
1. Cambiar el nombre de la variable clave en uno de los objetos para que coincidan.
2. Utilizar el argumento `by` para especificar las variables de unión.
Este último enfoque se demuestra a continuación en una versión renombrada de `coffee_data`:
```{r 03-attribute-operations-33, warning=FALSE}
coffee_renamed = rename(coffee_data, nm = name_long)
world_coffee2 = left_join(world, coffee_renamed, by = c(name_long = "nm"))
```
```{r 03-attribute-operations-34, eval=FALSE, echo=FALSE}
identical(world_coffee, world_coffee2)
nrow(world)
nrow(world_coffee)
```
Obsérvese que se mantiene el nombre del objeto original, lo que significa que `world_coffee` y el nuevo objeto `world_coffee2` son idénticos.
Otra característica del resultado es que tiene el mismo número de filas que el conjunto de datos original.
Aunque sólo hay 47 filas de datos en `coffee_data`, los 177 registros de países se mantienen intactos en `world_coffee` y `world_coffee2`:
Las filas del conjunto de datos original que no coinciden se les asignan valores "NA" para las nuevas variables de producción de café.
¿Y si sólo queremos conservar los países que coinciden con la variable clave?
En ese caso se puede utilizar `inner join()`:
```{r 03-attribute-operations-35, warning=FALSE}
world_coffee_inner = inner_join(world, coffee_data)
nrow(world_coffee_inner)
```
Fíjate en que el resultado de `inner_join()` sólo tiene 45 filas frente a las 47 de `coffee_data`.
¿Qué ha ocurrido con las filas restantes?
Podemos identificar las filas que no coinciden utilizando la función `setdiff()` de la siguiente manera:
```{r 03-attribute-operations-36}
setdiff(coffee_data$name_long, world$name_long)
```
El resultado muestra que `Others` representa una fila que no está presente en el conjunto de datos `world` y en el caso de la "República Democrática del Congo" el nombre
ha sido abreviado, lo que hace que la unión no lo tenga en cuenta.
El siguiente comando utiliza una función de concordancia de palabras (regex) del paquete **stringr** para confirmar qué debería ser `Congo, Dem. Rep. of`:
```{r 03-attribute-operations-37}
(drc = stringr::str_subset(world$name_long, "Dem*.+Congo"))
```
```{r, echo=FALSE, eval=FALSE}
world$name_long[grepl(pattern = "Dem*.+Congo", world$name_long)] # base R
```
```{r 03-attribute-operations-38, eval=FALSE, echo=FALSE}
# objetivo: probar los nombres en los objetos coffee_data y world
str_subset(coffee_data$name_long, "Ivo|Congo,")
.Last.value %in% str_subset(world$name_long, "Ivo|Dem*.+Congo")
```
Para solucionar este problema, crearemos una nueva versión de `coffee_data` y actualizaremos el nombre.
Si se une el marco de datos actualizado con `inner_join()`, se obtiene un resultado con las 46 naciones productoras de café:
```{r 03-attribute-operations-39, warning=FALSE}
coffee_data$name_long[grepl("Congo,", coffee_data$name_long)] = drc
world_coffee_match = inner_join(world, coffee_data)
nrow(world_coffee_match)
```
También es posible unir en la dirección contraria: empezar con un conjunto de datos no espaciales y añadir variables de un objeto Simple Features (sf).
Esto se demuestra a continuación, se comienza con el objeto `coffee_data` y se le añaden variables del conjunto de datos original `world`.
A diferencia de las uniones anteriores, el resultado *no* es otro objeto Simple Feature, sino un marco de datos en forma de tibble de **tidyverse**:
El resultado de una unión tiende a coincidir con su primer argumento:
```{r 03-attribute-operations-40, warning=FALSE}
coffee_world = left_join(coffee_data, world)
class(coffee_world)
```
```{block2 03-attribute-operations-41, type='rmdnote'}
En la mayoría de los casos, la columna de geometría sólo es útil en un objeto `sf`.
La columna de geometría sólo puede utilizarse para crear mapas y operaciones espaciales si R "sabe" que es un objeto espacial, definido por un paquete espacial como **sf**.
Afortunadamente, los marcos de datos no espaciales con una columna de lista de geometría (como `coffee_world`) pueden ser convertidos en un objeto `sf` de la siguiente manera: `st_as_sf(coffee_world)`.
```
Esta sección cubre la mayoría de los casos de uso de `join`.
Para más información, recomendamos @grolemund_r_2016, la ['viñeta' de join](https://geocompr.github.io/geocompkg/articles/join.html) en el paquete **geocompkg** que acompaña este libro, y la documentación del paquete **data.table**.^[
**data.table** es un paquete de procesamiento de datos de alto rendimiento.
Su aplicación a los datos geográficos se trata en la siguiente entrada de blog: r-spatial.org/r/2017/11/13/perp-performance.html.
]
Otro tipo de unión es la unión espacial, que se trata en el siguiente capítulo (Sección \@ref(spatial-joining)).
### Creando atributos y eliminando información espacial {#vec-attr-creation}
A menudo, queremos crear una nueva columna basada en columnas ya existentes.
Por ejemplo, queremos calcular la densidad de población de cada país.
Para ello necesitamos dividir una columna de población, aquí `pop`, por una columna de área, aquí `area_km2`.
Usando R base, podemos escribir:
```{r 03-attribute-operations-42}
world_new = world # no sobreescribe nuestros datos originales
world_new$pop_dens = world_new$pop / world_new$area_km2
```
Alternativamente, podemos utilizar una de las funciones de **dplyr** - `mutate()` o `transmute()`.
La función `mutate()` añade nuevas columnas en la penúltima posición del objeto `sf` (la última se reserva para la geometría):
```{r 03-attribute-operations-43, eval=FALSE}
world %>%
mutate(pop_dens = pop / area_km2)
```
La diferencia entre `mutate()` y `transmute()` es que esta última elimina todas las demás columnas existentes (excepto la columna de geometría fijada):
```{r 03-attribute-operations-44, eval=FALSE}
world %>%
transmute(pop_dens = pop / area_km2)
```
La función `unite()` del paquete **tidyr** (que proporciona muchas funciones útiles para remodelar conjuntos de datos, como `pivot_longer()`) pega las columnas existentes.
Por ejemplo, queremos combinar las columnas `continent` y `region_un` en una nueva columna llamada `con_reg`.
Además, podemos definir un separador (aquí: dos puntos `:`) que define cómo se deben unir los valores de las columnas de entrada, y si se deben eliminar las columnas originales (aquí: `TRUE`):
```{r 03-attribute-operations-45, eval=FALSE}
world_unite = world %>%
unite("con_reg", continent:region_un, sep = ":", remove = TRUE)
```
La función `separate()` hace lo contrario de `unite()`: divide una columna en varias columnas utilizando una expresión regular o posiciones de caracteres.
Esta función también proviene del paquete **tidyr**.
```{r 03-attribute-operations-46, eval=FALSE}
world_separate = world_unite %>%
separate(con_reg, c("continent", "region_un"), sep = ":")
```
```{r 03-attribute-operations-47, echo=FALSE, eval=FALSE}
identical(world, world_separate)
```
La función de **dplyr** `rename()` y la función base de R `setNames()` son útiles para renombrar columnas.
La primera sustituye un nombre antiguo por uno nuevo.
El siguiente comando, por ejemplo, renombra la larga columna `nombre_long` a simplemente `name`:
```{r 03-attribute-operations-48, eval=FALSE}
world %>%
rename(name = name_long)
```
`setNames()` cambia todos los nombres de las columnas a la vez, y requiere un vector de caracteres con un nombre que coincida con cada columna.
Esto se ilustra a continuación, lo cual produce el mismo objeto `world`, pero con nombres mucho más cortos:
```{r 03-attribute-operations-49, eval=FALSE, echo=FALSE}
abbreviate(names(world), minlength = 1) %>% dput()
```
```{r 03-attribute-operations-50, eval=FALSE}
new_names = c("i", "n", "c", "r", "s", "t", "a", "p", "l", "gP", "geom")
world %>%
setNames(new_names)
```
Es importante señalar que las operaciones de datos de atributos conservan la geometría de simple features.
Como se ha mencionado al principio del capítulo, puede ser útil eliminar la geometría.
Para ello, hay que eliminarla explícitamente.
Por lo tanto, un enfoque como `select(world, -geom)` no tendrá éxito y en su lugar debe utilizar `st_drop_geometry()`.^[`st_geometry(world_st) = NULL` también funciona para eliminar la geometría de `world`, pero sobreescribe el objeto original.
]
```{r 03-attribute-operations-51}
world_data = world %>% st_drop_geometry()
class(world_data)
```
## Manipulando objetos raster
<!--jn-->
A diferencia del modelo de datos vectoriales subyacente a simple features (que representa puntos, líneas y polígonos como entidades discretas en el espacio), los datos rasterizados representan superficies continuas.
Esta sección muestra cómo funcionan los objetos raster, creándolos *desde cero*, basándose en la sección \@ref(an-introduction-to-terra).
Debido a su estructura única, el subconjunto y otras operaciones con conjuntos de datos raster funcionan de manera diferente, como se demuestra en la sección \@ref(raster-subsetting).
\index{raster!manipulation}
El siguiente código recrea el conjunto de datos raster utilizados en la sección \@ref(raster-classes), cuyo resultado se ilustra en la figura \@ref(fig:cont-raster).
Esto demuestra cómo funciona la función `rast()` para crear un raster de ejemplo llamado `elev` (que representa elevaciones).
```{r 03-attribute-operations-52, message=FALSE, eval = FALSE}
elev = rast(nrows = 6, ncols = 6, resolution = 0.5,
xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5,
vals = 1:36)
```
El resultado es un objeto raster con 6 filas y 6 columnas (especificadas por los argumentos `nrow` y `ncol`), y una extensión espacial mínima y máxima en dirección x e y (`xmin`, `xmax`, `ymin`, `ymax`).
El argumento `vals` establece los valores que contiene cada celda: datos numéricos que van de 1 a 36 en este caso.
Los objetos raster también pueden contener valores categóricos de clase `lógica` o variables `factoriales` en R.
El siguiente código crea un raster que representa el tamaño de los granos de café (Figura \@ref(fig:cont-raster)):
```{r 03-attribute-operations-53, eval = FALSE}
grain_order = c("clay", "silt", "sand")
grain_char = sample(grain_order, 36, replace = TRUE)
grain_fact = factor(grain_char, levels = grain_order)
grain = rast(nrows = 6, ncols = 6, resolution = 0.5,
xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5,
vals = grain_fact)
```
```{r 03-attribute-operations-54, include = FALSE}
elev = rast(system.file("raster/elev.tif", package = "spData"))
grain = rast(system.file("raster/grain.tif", package = "spData"))
```
El objeto raster almacena la correspondiente tabla de búsqueda o "Raster Attribute Table" (RAT) como una lista de marcos de datos, los cuales pueden ser visualizados con `cats(grain)` ( véase `?cats()` para más información).
Cada elemento de esta lista es una capa del raster.
También es posible utilizar la función `levels()` para recuperar y añadir nuevos niveles de factores o sustituir los existentes:
```{r 03-attribute-operations-56}
levels(grain)[[1]] = c(levels(grain)[[1]], wetness = c("wet", "moist", "dry"))
levels(grain)
```
```{r cont-raster, echo = FALSE, message = FALSE, fig.asp=0.5, fig.cap = "Conjuntos de datos rasterizados con valores numéricos (izquierda) y categóricos (derecha).", fig.scap="Conjuntos de datos rasterizados con valores numéricos y categóricos.", warning=FALSE}
source("https://github.com/Robinlovelace/geocompr/raw/main/code/03-cont-raster-plot.R", print.eval = TRUE)
```
```{block2 coltab, type='rmdnote'}
Los objetos raster categóricos también pueden almacenar información sobre los colores asociados a cada valor utilizando una tabla de colores.
La tabla de colores es un marco de datos con tres (rojo, verde, azul) o cuatro (alfa) columnas, donde cada fila se refiere a un valor.
Las tablas de colores en **terra** se pueden ver o establecer con la función `coltab()` ( véase `?coltab`).
Es importante señalar que al guardar un objeto rasterizado con una tabla de colores en un archivo (por ejemplo, GeoTIFF) también se guardará la información de color.
```
### Subconjuntos de rásteres
Los subconjuntos de rásteres se realizan con el operador base de R `[`, que acepta varios tipos de entradas:
\index{raster!subsetting}
- Indexación de filas y columnas
- IDs de celdas
- Coordenadas (Véase la sección \@ref(spatial-raster-subsetting))
- Otros objetos espaciales (Véase la sección \@ref(spatial-raster-subsetting))
Aquí sólo mostramos las dos primeras opciones, ya que pueden considerarse operaciones no espaciales.
Si necesitamos un objeto espacial para crear otro subconjunto o la salida es un objeto espacial, nos referimos a esto como subconjunto espacial.
Por lo tanto, las dos últimas opciones se mostrarán en el próximo capítulo (véase la sección \@ref(spatial-raster-subsetting)).
Las dos primeras opciones de subconjunto se demuestran en los comandos siguientes ---
ambos devuelven el valor del píxel superior izquierdo en el objeto raster `elev` (los resultados no se muestran):
```{r 03-attribute-operations-58, eval = FALSE}
# fila 1, columna 1
elev[1, 1]
# ID de la celda 1
elev[1]
```
El subconjunto de los objetos raster de varias capas devolverá el valor de la(s) celda(s) de cada capa.
Por ejemplo, `c(elev, grain)[1]` devuelve un marco de datos con una fila y dos columnas --- una para cada capa.
Para extraer todos los valores o filas completas, también puedes utilizar `values()`.
Los valores de las celdas pueden modificarse sobrescribiendo los valores existentes junto con una operación de subconjunto.
El siguiente fragmento de código, por ejemplo, establece la celda superior izquierda de `elev` a 0 (los resultados no se muestran):
```{r 03-attribute-operations-60, results='hide'}
elev[1, 1] = 0
elev[]
```
Dejar los corchetes vacíos es una versión abreviada de `values()` para recuperar todos los valores de un raster.
También se pueden modificar múltiples celdas de esta manera:
```{r 03-attribute-operations-61}
elev[1, c(1, 2)] = 0
```
Reemplazar los valores de rásteres multicapa puede hacerse con una matriz, con tantas columnas como capas queramos y filas como celdas reemplazables (resultados no mostrados):
```{r 03-attribute-operations-61b, eval=FALSE}
two_layers = c(grain, elev)
two_layers[1] = cbind(c(0), c(4))
two_layers[]
```
### Sintetizando objetos rasterizados {#summarizing-raster-objects}
**terra** contiene funciones para extraer estadísticas descriptivas\index{statistics} para rásteres enteros.
La impresión de un objeto raster en la consola al escribir su nombre devuelve sus valores mínimos y máximos.
`summary()` proporciona estadísticas descriptivas\index{statistics} comunes. -- mínimo, máximo, cuartiles y número de `NA`s para los rásteres continuos y un número de celdas de cada clase para los rásteres categóricos.
Otras operaciones de síntesis, como la desviación estándar (véase más adelante) o estadísticas de síntesis personalizadas, pueden calcularse con `global()`.
\index{raster!summarizing}
```{r 03-attribute-operations-62, eval = FALSE}
global(elev, sd)
```
```{block2 03-attribute-operations-63, type='rmdnote'}
Si aplicas las funciones `summary()` y `global()` a un objeto raster de varias capas, éstas resumirán cada capa por separado, como se puede ilustrar ejecutando: `summary(c(elev, grain))`.
```
Además, la función `freq()` permite obtener la tabla de frecuencias de los valores categóricos.
Las estadísticas de los valores raster pueden visualizarse de distintas maneras.
Funciones específicas como `boxplot()`, `density()`, `hist()` y `pairs()` funcionan también con objetos raster, como se demuestra en el histograma creado con el comando siguiente (no mostrado):
```{r 03-attribute-operations-64, eval=FALSE}
hist(elev)
```
En caso de que la función de visualización deseada no funcione con objetos raster, se pueden extraer los datos raster para representarlos con la ayuda de `values()` (Sección \@ref(raster-subsetting)).
\index{raster!values}
Las estadísticas raster descriptivas pertenecen a las llamadas operaciones raster globales.
Estas y otras operaciones típicas del procesamiento raster forman parte del esquema del álgebra de mapas, que se tratan en el siguiente capítulo (Sección \@ref(map-algebra)).
```{block 03-attribute-operations-65, type='rmdnote'}
Algunos nombres de funciones chocan entre paquetes (por ejemplo, una función con el nombre `extract()` existe en ambos paquetes **terra** y **tidyr**).
Además de no cargar los paquetes haciendo referencia a las funciones de forma verbosa (por ejemplo, `tidyr::extract()`), otra forma de evitar los choques de nombres de funciones es descargando el paquete que genere este choque de nombres con `detach()`.
El siguiente comando, por ejemplo, descarga el paquete **terra** (esto también puede hacerse en la pestaña *paquete* (Package) que reside por defecto en el panel inferior derecho de RStudio): `detach("paquete:terra", unload = TRUE, force = TRUE)`.
El argumento `force` asegura que el paquete se desprenderá incluso si otros paquetes dependen de él.
Esto, sin embargo, puede conducir a una usabilidad restringida de los paquetes que dependen del paquete desprendido, y por lo tanto no se recomienda.
```
## Ejercicios
```{r, echo=FALSE, results='asis'}
res = knitr::knit_child('_03-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE))
cat(res, sep = '\n')
```