/
11-listas.Rmd
357 lines (234 loc) · 11.4 KB
/
11-listas.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
```{r echo = FALSE}
library(knitr)
# Color text
colorize <- function(x, color) {
if (knitr::is_latex_output()) {
sprintf("\\textcolor{%s}{%s}", color, x)
} else if (knitr::is_html_output()) {
sprintf("<span style='color: %s;'>%s</span>", color, x)
} else { x }
}
```
# Listas {#listas}
:::: {.blackbox data-latex=""}
Scripts usados:
* [**script10.R**](https://github.com/dadosdelaplace/courses-intro-R/blob/main/scripts/script10.R). Ver en <https://github.com/dadosdelaplace/courses-intro-R/blob/main/scripts/script10.R>
::::
Veamos un pequeño <mark>**resumen de los datos**</mark> que conocemos:
* <mark>**vectores**</mark>: colección de elementos de <mark>**igual tipo**</mark>. Pueden ser **números**, **caracteres** o **valores lógicos**, entre otros.
* <mark>**matrices**</mark>: colección <mark> **BIDIMENSIONAL**</mark> de elementos de <mark>**igual tipo**</mark> e <mark>**igual longitud**</mark>.
* <mark>**data.frame**</mark>: colección <mark> **BIDIMENSIONAL**</mark> de elementos de <mark>**igual longitud**</mark> pero <mark>**de cualquier tipo**</mark>, lo más parecido a lo que conocemos como una tabla en Excel.
Con todos estos ingredientes estamos preparados/as para ver el que probablemente sea el tipo de dato más importante en `R`: las <mark>**listas**</mark>.
Las <mark>**listas**</mark> son **colecciones** de variables de <mark>**diferente tipo**</mark> (ya lo teníamos con `data.frame`) pero además <mark>**también de diferente longitud**</mark>, con estructuras totalmente **heterógeneas**, todo guardado en la misma variable (incluso **una lista puede tener dentro a su vez otra lista**).
Vamos a <mark>**crear nuestra primera lista**</mark> con tres elementos: el nombre de nuestros padres/madres, nuestro lugar de nacimiento y edades de nuestros hermanos.
```{r}
variable_1 <- c("Paloma", "Gregorio")
variable_2 <- "Madrid"
variable_3 <- c(25, 30, 26)
lista <- list("progenitores" = variable_1,
"lugar_nacimiento" = variable_2,
"edades_hermanos" = variable_3)
lista
length(lista)
```
Si observas el objeto que hemos definido como `lista`, su <mark>**longitud**</mark> del es de 3 ya que tenemos guardados **tres elementos**
* un vector de caracteres (de longitud 2)
* un caracter (vector de longitud 1)
* un vector de números (de longitud 3)
Tenemos guardados elementos de distinto tipo (algo que ya podíamos con los `data.frame` pero, además, de longitudes dispares).
```{r}
dim(lista) # devolverá NULL al no tener dos dimensiones
length(lista)
class(lista) # de tipo lista
```
Si los juntásemos con un `data.frame`, al tener distinta longitud, obtendríamos un error: `arguments imply differing number of rows`.
```{r, error = TRUE}
data.frame("progenitores" = variable_1,
"lugar_nacimiento" = variable_2,
"edades_hermanos" = variable_3)
```
Para <mark>**acceder a un elemento de la lista**</mark> tenemos dos opciones:
- **Acceder por índice**: con el operador `[[i]]` accedemos al elemento i-ésimo de la lista.
- **Acceder por nombre**: con el operador `$nombre_elemento` accedemos al elemento por su nombre
```{r}
# Accedemos por índice
lista[[1]]
# Accedemos por nombre
lista$progenitores
```
Dada su heterogeneidad y flexibilidad, para acceder a un elemento particular, las listas tienen una forma peculiar de acceder (con el corchete doble, en contraposición con el corchete simple que nos permite <mark>**acceder a varios elementos a la vez**</mark>)
```{r seleccionar-varios}
# Varios elementos
lista[1:2]
```
Las listas nos dan tanta flexibilidad que es el formato de dato natural para <mark>**guardar datos que no están estructurados**</mark>, como pueden ser los datos almacenados en el registro de una persona.
Vamos a definir, por ejemplo, los **datos que tendría un instituto de un alumno**.
* `nacimiento`: una fecha.
* `notas_insti`: un `data.frame`.
* `teléfonos`: vector de números.
* `nombre_padres`: vector de texto.
```{r}
# Fecha de nacimiento
fecha_nacimiento <- as_date("1989-09-10")
# Notas de asignaturas en primer y segundo parcial
notas <- data.frame("biología" = c(5, 7), "física" = c(4, 5),
"matemáticas" = c(8, 9.5))
row.names(notas) <- # Nombre a las filas
c("primer_parcial", "segundo_parcial")
# Números de teléfono
tlf <- c("914719567", "617920765", "716505013")
# Nombres
padres <- c("Juan", "Julia")
# Guardamos TODO en una lista (con nombres de cada elemento)
datos <- list("nacimiento" = fecha_nacimiento,
"notas_insti" = notas, "teléfonos" = tlf,
"nombre_padres" = padres)
datos
```
Hemos creado una **lista algo más compleja de 4 elementos**, a los cuales podemos acceder por índice o por nombre.
```{r}
datos[[1]]
datos$nacimiento
datos[[2]]
datos$notas_insti
```
Como hemos comentado, también podemos hacer <mark>**listas con otras listas dentro**</mark>, de forma que para acceder a cada nivel deberemos usar el operador `[[]]`.
```{r}
lista_de_listas <- list("lista_1" = datos[3:4], "lista_2" = datos[1:2])
names(lista_de_listas) # Nombres de los elementos del primer nivel
names(lista_de_listas[[1]]) # Nombres de los elementos guardados en el primer elemento, que es a su vez una lista
lista_de_listas[[1]][[1]] # Elemento 1 de la lista guardada como elemento 1 de la lista superior
```
<mark>**¡Nos permiten guardar «datos n-dimensionales»!**</mark>.
Es un formato muy habitual para devolver argumentos en funciones. Imagina que la función `igualdad_nombres` que hemos definido en el Ejercicio 4
**`r colorize("WARNING: operaciones aritméticas con listas", "#dc3545")`**
Una **lista no se puede vectorizar de forma inmediata**, por lo cualquier operación aritmética aplicada a una lista dará error (para ello está disponible la función `lapply()`, o con las funciones del paquete `{purrr}`, cuyo uso avanzado corresponderá a otro manual pero que veremos a continuación una pequeña introducción).
```{r error = TRUE}
datos <- list("a" = 1:5, "b" = 10:20)
datos / 2
```
```{r lapply}
lapply(datos, FUN = function(x) { x / 2})
```
## Introducción a purrr
Dada su heterogeneidad, una <mark>**lista no se puede vectorizar**</mark> de forma inmediata, por lo cualquier operación aritmética aplicada a una lista dará **error**, como vemos a continuación con un ejemplo sencillo.
```{r error = TRUE}
datos <- list("a" = 1:5, "b" = 10:20)
datos / 2
```
Para operar con listas, una de las opciones más habituales es hacer uso de la familia `lapply()`, con un funcionamiento similar a la familia `apply()` que ya hemos visto con matrices. Dicha función `lapply()` necesita como primer argumento la lista a la que aplicar la operación, y como segundo argumento `FUN = ...` la <mark>**función que querramos aplicar a cada elemento**</mark> de la lista.
```{r}
lapply(datos, FUN = function(x) { x / 2})
```
Fíjate que la salida de `lapply()`, por defecto, siempre será <mark>**otra lista de igual longitud**</mark> (cada elemento será la función aplicada a cada elemento original de la lista).
Una opción más flexible y versatil de aparición «reciente» es hacer uso del paquete `{purrr}` del entorno `{tidyverse}`.
```{r}
# install.packages("purrr")
library(purrr)
```
Dicho paquete contiene diversa funciones que pretenden <mark>**imitar la programación funcional**</mark> de otros lenguajes como Scala o la **estrategia map-reduce de Hadoop** (de Google).
```{r echo = FALSE, out.width = "80%", fig.align = "left"}
knitr::include_graphics("./img/purrr.png")
```
La función más simple del paquete `{purrr}` es la función `map()`, que nos <mark>**aplica una función vectorizada**</mark> a cada uno de los elementos de una lista.
```{r}
library(microbenchmark)
x <- 1:1000
y <- sqrt(x) # vectorizado
# bucle
for (i in 1:1000) { y[i] <- sqrt(x[i]) }
microbenchmark(sqrt(x), for (i in 1:1000) { y[i] <- sqrt(x[i]) }, times = 1e3)
```
En vectores disponemos de una vectorización por defecto porque `R` realiza operaciones elemento a elemento. Con `map()` podemos <mark>**«mapear» cada lista**</mark> y aplicar la función **elemento a elemento** (si fuese el caso).
```{r}
library(purrr)
x <- rep(list(1:2), 3)
x
# purrr
map(x, sqrt)
# otro ejemplo
x <- list(rnorm(n = 1e3, mean = 0, sd = 1),
rnorm(n = 1e3, mean = 2, sd = 1))
map(x, mean)
```
```{r}
x <- rep(list(rnorm(n = 1e3, mean = 0, sd = 1)), 1000)
# Medimos tiempos entre map y lapply
microbenchmark(map(x, .f = function(x) { mean(x^2) }),
lapply(x, FUN = function(x) { mean(x^2) }),
times = 1e3)
```
Además de ser <mark>**más legible y eficiente**</mark>, el la vectorización de las listas con el paquete `{purrr}` nos permitirá <mark>**decidir el formato de salida**</mark> tras la operación (por ejemplo, en formato de vector con `map_dbl()` para números - en general - y `map_int()` para enteros), sin necesidad de hacer uso de `unlist()` (deshace el formato de lista original).
```{r}
# lista de 1000 valores de dos normales
x <- list(rnorm(n = 1e3, mean = 0, sd = 1),
rnorm(n = 1e3, mean = 2, sd = 1))
# media de cada una, devuelto en formato vector
map_dbl(x, mean)
```
Una de las opciones más habituales, y una de las principales ventajas, es pasar como argumento un <mark>**número**</mark> en lugar de una función, lo cual nos devolverá el **elemento i-ésimo de cada lista** de forma inmediata.
```{r}
c(x[[1]][3], x[[2]][3])
map_dbl(x, 3) # equivalente a lo anterior
```
Aunque no es el objetivo de este manual introductorio profundizar en dicho paquete (te lo recomiendo), mencionar que además nos permite la opción de pasar más de un argumento, realizando <mark>**operaciones binarias**</mark>, con la función `map2()`
```{r}
x <- list("a" = 1:3, "b" = 4:7)
y <- list("c" = c(-1, 4, 0), "b" = c(5, -4, -1, 2))
# dos listas como argumentos
map2(x, y, .f = function(x, y) { x^2 + y^2})
```
## 📝 Ejercicios
<details>
<summary><strong>Ejercicio 1</strong>: define una lista de 4 elementos de tipos distintos y accede al segundo de ellos (yo incluiré uno que sea un `data.frame` para que veas que en una lista cabe de todo).</summary>
<!-- toc -->
- Solución:
```{r}
# Ejemplo: lista con texto, numérico, lógico y un data.frame
lista_ejemplo <- list("nombre" = "Javier", "cp" = 28019,
"soltero" = TRUE,
"notas" = data.frame("mates" = c(7.5, 8, 9),
"lengua" = c(10, 5, 6)))
lista_ejemplo
# Longitud
length(lista_ejemplo)
# Accedemos al elemento dos
lista_ejemplo[[2]]
```
</details>
<details>
<summary><strong>Ejercicio 2</strong>: accede a los elementos que ocupan los lugares 1 y 4 de la lista definida anteriormente.</summary>
<!-- toc -->
- Solución:
```{r}
# Accedemos al 1 y al 4
lista_ejemplo[c(1, 4)]
```
Otra opción es acceder con los nombres
```{r}
# Accedemos al 1 y al 4
lista_ejemplo$nombre
lista_ejemplo$notas
lista_ejemplo[c("nombre", "notas")]
```
</details>
<details>
<summary><strong>Ejercicio 3</strong>: define una lista de 4 elementos que contenga, en una sola variable, tu nombre, apellido, edad y si estás soltero/a.</summary>
<!-- toc -->
- Solución:
```{r}
# Creamos lista: con lubridate calculamos la diferencia de años desde la fecha de nuestro nacimiento hasta hoy (sea cuando sea hoy)
lista_personal <- list("nombre" = "Javier",
"apellidos" = "Álvarez Liébana",
"edad" = 32,
"soltero" = TRUE)
lista_personal
```
</details>