Capítulo 14 Listas

Hasta ahora hemos hecho un resumen de los elementos básicos de R:

  • vectores: colección de elementos de igual tipo. Recuerda que un elemento individual, por ejemplo x <- 1, es en realidad un vector (de longitud 1). Pueden ser números, caracteres, fechas o valores lógicos, entre otros.

  • matrices: colección BIDIMENSIONAL de elementos de igual tipo e igual longitud.

  • data.frame: colección BIDIMENSIONAL de elementos de igual longitud pero de cualquier tipo, lo más parecido a lo que conocemos como una tabla en Excel.

Además hemos visto una serie de herramientas para tratar esos datos:

  • estructuras condicionalesif-else
  • bucles para repetir trozos de código.
  • organizar el código en proyectos para facilitar nuestro trabajo
  • funciones

Con todos estos ingredientes estamos preparados/as para ver el que probablemente sea el tipo de dato más importante en R: las listas.

Las listas son colecciones de variables de diferente tipo pero que pueden ser también de diferente longitud, con estructuras totalmente heterógeneas, de ahí que sea el formato de salida de muchísimas funciones de R que te devuelven a la vez un cadena de texto, un vector de números o una tabla, todo guardado en la misma variable (incluso una lista puede tener dentro a su vez otra lista).

Vamos a crear nuestra primera lista con tres elementos: el nombre de nuestros padres/madres, nuestro lugar de nacimiento y edades de nuestros hermanos.

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
## $progenitores
## [1] "Paloma"   "Gregorio"
## 
## $lugar_nacimiento
## [1] "Madrid"
## 
## $edades_hermanos
## [1] 25 30 26
length(lista)
## [1] 3

Si observas el objeto que hemos definido como lista, su longitud del objeto es de 3: 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 de longitudes dispares).

dim(lista) # devolverá NULL al no tener dos dimensiones
## NULL
length(lista)
## [1] 3
class(lista) # de tipo lista
## [1] "list"

Si los juntásemos con un data.frame, al tener distinta longitud, obtendríamos un error: arguments imply differing number of rows.

data.frame("progenitores" = variable_1,
           "lugar_nacimiento" = variable_2,
           "edades_hermanos" = variable_3)
## Error in data.frame(progenitores = variable_1, lugar_nacimiento = variable_2, : arguments imply differing number of rows: 2, 1, 3

Para acceder a un elemento de la lista 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

# Accedemos por índice
lista[[1]]
## [1] "Paloma"   "Gregorio"
# Accedemos por nombre
lista$progenitores
## [1] "Paloma"   "Gregorio"

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 acceder a varios elementos a la vez)

# Varios elementos
lista[1:2]
## $progenitores
## [1] "Paloma"   "Gregorio"
## 
## $lugar_nacimiento
## [1] "Madrid"

Las listas nos dan tanta flexibilidad que es el formato de dato natural para guardar datos que no están estructurados, 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.
# 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
## $nacimiento
## [1] "1989-09-10"
## 
## $notas_insti
##                 biología física matemáticas
## primer_parcial         5      4         8.0
## segundo_parcial        7      5         9.5
## 
## $teléfonos
## [1] "914719567" "617920765" "716505013"
## 
## $nombre_padres
## [1] "Juan"  "Julia"
names(datos)
## [1] "nacimiento"    "notas_insti"   "teléfonos"     "nombre_padres"
length(datos)
## [1] 4

Hemos creado una lista algo más compleja de 4 elementos, a los cuales podemos acceder por índice o nombre.

datos[[1]]
## [1] "1989-09-10"
datos$nacimiento
## [1] "1989-09-10"
datos[[2]]
##                 biología física matemáticas
## primer_parcial         5      4         8.0
## segundo_parcial        7      5         9.5
datos$notas_insti
##                 biología física matemáticas
## primer_parcial         5      4         8.0
## segundo_parcial        7      5         9.5

Como hemos comentado, también podemos aplicar la recursividad y hacer listas con otras listas dentro, de forma que para acceder a cada nivel deberemos usar el operador [[]].

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
## [1] "lista_1" "lista_2"
names(lista_de_listas[[1]]) # Nombres de los elementos guardados en el primer elemento, que es a su vez una lista
## [1] "teléfonos"     "nombre_padres"
lista_de_listas[[1]][[1]] # Elemento 1 de la lista guardada como elemento 1 de la lista superior
## [1] "914719567" "617920765" "716505013"

¡Nos permiten guardar «datos n-dimensionales»!.

Es un formato muy habitual para devolver argumentos en funciones. Imagina que la función igualdad_nombres que hemos definido en el Ejercicio 4

# Sin importar mayúsculas
igualdad_nombres <- function(persona_1, persona_2) {
  
  return(toupper(persona_1) == toupper(persona_2))
}
igualdad_nombres("Javi", "javi")
## [1] TRUE
igualdad_nombres("Javi", "Lucía")
## [1] FALSE

Vamos a reescribirla haciendo que devuelva no solo si es igual o distinto, sino que devuelva los dos nombres de entrada. Al ser un tipo de distinto de dato (una variable lógica, y un vector de longitud de caracteres), lo más práctico es devolverlo en forma de lista.

# Sin importar mayúsculas
igualdad_nombres <- function(persona_1, persona_2) {
  
  return(list("son_iguales" = toupper(persona_1) == toupper(persona_2),
              "nombres" = c(persona_1, persona_2)))
}
resultado <- igualdad_nombres("Javi", "Lucía")
resultado$son_iguales
## [1] FALSE
resultado$nombres
## [1] "Javi"  "Lucía"

 

WARNING: operaciones aritméticas con listas

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).

datos <- list("a" = 1:5, "b" = 10:20)
datos / 2
## Error in datos/2: argumento no-numérico para operador binario
lapply(datos, FUN = function(x) { x / 2})
## $a
## [1] 0.5 1.0 1.5 2.0 2.5
## 
## $b
##  [1]  5.0  5.5  6.0  6.5  7.0  7.5  8.0  8.5  9.0  9.5 10.0

 

14.1 📝 Ejercicios

Ejercicio 1: 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).

  • Solución:
# 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
## $nombre
## [1] "Javier"
## 
## $cp
## [1] 28019
## 
## $soltero
## [1] TRUE
## 
## $notas
##   mates lengua
## 1   7.5     10
## 2   8.0      5
## 3   9.0      6
# Longitud
length(lista_ejemplo)
## [1] 4
# Accedemos al elemento dos
lista_ejemplo[[2]]
## [1] 28019

 

Ejercicio 2: accede a los elementos que ocupan los lugares 1 y 4 de la lista definida anteriormente.

  • Solución:
# Accedemos al 1 y al 4
lista_ejemplo[c(1, 4)]
## $nombre
## [1] "Javier"
## 
## $notas
##   mates lengua
## 1   7.5     10
## 2   8.0      5
## 3   9.0      6

Otra opción es acceder con los nombres

# Accedemos al 1 y al 4
lista_ejemplo$nombre
## [1] "Javier"
lista_ejemplo$notas
##   mates lengua
## 1   7.5     10
## 2   8.0      5
## 3   9.0      6
lista_ejemplo[c("nombre", "notas")]
## $nombre
## [1] "Javier"
## 
## $notas
##   mates lengua
## 1   7.5     10
## 2   8.0      5
## 3   9.0      6

 

Ejercicio 3: define una lista de 4 elementos que contenga, en una sola variable, tu nombre, apellido, edad y si estás soltero/a.

  • Solución:
library(lubridate)
# 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
## $nombre
## [1] "Javier"
## 
## $apellidos
## [1] "Álvarez Liébana"
## 
## $edad
## [1] 32
## 
## $soltero
## [1] TRUE