10  Tibbles

10.1 Introducción

A lo largo de este libro trabajaremos con “tibbles” (que se pronuncia /tibls/) en lugar de los tradicionales data.frame de R. Los tibbles son data frames, pero modifican algunas características antiguas para hacernos la vida más fácil. R es un lenguaje viejo y algunas cosas que eran útiles hace 10 o 20 años actualmente pueden resultar inconvenientes. Es difícil modificar R base sin romper código existente, así que la mayor parte de la innovación ocurre a través de paquetes. Aquí describiremos el paquete tibble, que provee una versión de data frame que facilita el trabajo con el tidyverse. La mayoría de las veces usaremos el término tibble y data frame de manera indistinta; cuando queramos referirnos de manera particular al data frame que viene incluido en R lo llamaremos data.frame.

Si luego de leer este capítulo te quedas con ganas de aprender más sobre tibbles, quizás disfrutes vignette("tibble").

10.1.1 Prerequisitos

En este capítulo exploraremos el paquete tibble, parte de los paquetes principales del tidyverse. Para ejemplificar utilizaremos datasets incluidos en el paquete datos.

library(tidyverse)
library(datos)

10.2 Creando tibbles

La mayoría de las funciones que usarás en este libro producen tibbles, ya que son una de las características trasversales del tidyverse. La mayoría de los paquetes de R suelen usar data frames clásicos, así que algo que podrías querer hacer es convertir un data frame en un tibble. Esto lo puedes hacer con as_tibble():

as_tibble(flores)
# A tibble: 150 × 5
   Largo.Sepalo Ancho.Sepalo Largo.Petalo Ancho.Petalo Especie
          <dbl>        <dbl>        <dbl>        <dbl> <fct>  
 1          5.1          3.5          1.4          0.2 setosa 
 2          4.9          3            1.4          0.2 setosa 
 3          4.7          3.2          1.3          0.2 setosa 
 4          4.6          3.1          1.5          0.2 setosa 
 5          5            3.6          1.4          0.2 setosa 
 6          5.4          3.9          1.7          0.4 setosa 
 7          4.6          3.4          1.4          0.3 setosa 
 8          5            3.4          1.5          0.2 setosa 
 9          4.4          2.9          1.4          0.2 setosa 
10          4.9          3.1          1.5          0.1 setosa 
# ℹ 140 more rows

Puedes crear un nuevo tibble a partir de vectores individuales con tibble(). Esta función recicla vectores de longitud 1 automáticamente y te permite usar variables creadas dentro de la propia función, como se muestra abajo.

tibble(
  x = 1:5,
  y = 1,
  z = x^2 + y
)
# A tibble: 5 × 3
      x     y     z
  <int> <dbl> <dbl>
1     1     1     2
2     2     1     5
3     3     1    10
4     4     1    17
5     5     1    26

Si ya te has familiarizado con data.frame(), es importante que tomes en cuenta que tibble() hace menos cosas: nunca cambia el tipo de los inputs (p. ej., ¡nunca convierte caracteres en factores!), nunca cambia el nombre de las variables y nunca asigna nombres a las filas.

Un tibble puede usar nombres de columnas que no son nombres de variables válidos en R (también conocidos como nombres no sintácticos). Por ejemplo, pueden empezar con un caracter diferente a una letra o contener caracteres poco comunes, como espacios. Para referirse a estas variables, tienes que rodearlos de acentos graves, `:

tb <- tibble(
  `:)` = "sonrisa",
  ` ` = "espacio",
  `2000` = "número"
)
tb
# A tibble: 1 × 3
  `:)`    ` `     `2000`
  <chr>   <chr>   <chr> 
1 sonrisa espacio número

También necesitarás los acentos graves al trabajar con estas variables en otros paquetes, como ggplot2, dplyr y tidyr.

Otra forma de crear un tibble es con tribble(), que es una abreviación de tibble transpuesto. Esta función está pensada para realizar la entrada de datos en el código: los nombres de las columnas se definen con fórmulas (esto es, comienzan con ~) y cada entrada está separada por comas. Esto permite escribir pocos datos de manera legible.

tribble(
  ~x, ~y, ~z,
  #--|--|----
  "a", 2, 3.6,
  "b", 1, 8.5
)
# A tibble: 2 × 3
  x         y     z
  <chr> <dbl> <dbl>
1 a         2   3.6
2 b         1   8.5

Usualmente agregamos un comentario para dejar en claro cuál es el encabezado (esta línea debe empezar con #).

10.3 Tibbles vs. data.frame

Existen dos diferencias principales entre el uso de un tibble y un data.frame clásico: la impresión en la consola y la selección de los subconjuntos.

10.3.1 Impresión en la consola

Los tibbles tienen un método de impresión en la consola refinado: solo muestran las primeras 10 filas y solo aquellas columnas que entran en el ancho de la pantalla. Esto simplifica y facilita trabajar con bases de datos grandes. Además del nombre, cada columna muestra su tipo. Esto último es una gran característica tomada de str().

tibble(
  a = lubridate::now() + runif(1e3) * 86400,
  b = lubridate::today() + runif(1e3) * 30,
  c = 1:1e3,
  d = runif(1e3),
  e = sample(letters, 1e3, replace = TRUE)
)
# A tibble: 1,000 × 5
   a                   b              c      d e    
   <dttm>              <date>     <int>  <dbl> <chr>
 1 2023-08-09 21:38:58 2023-08-14     1 0.796  o    
 2 2023-08-10 11:09:40 2023-08-25     2 0.605  a    
 3 2023-08-09 20:51:33 2023-08-18     3 0.765  t    
 4 2023-08-10 13:51:47 2023-08-13     4 0.405  s    
 5 2023-08-09 22:13:39 2023-08-29     5 0.347  o    
 6 2023-08-10 05:43:15 2023-08-16     6 0.739  u    
 7 2023-08-10 13:57:00 2023-08-10     7 0.0154 n    
 8 2023-08-10 16:35:59 2023-08-23     8 0.405  m    
 9 2023-08-10 03:57:53 2023-08-29     9 0.122  i    
10 2023-08-10 04:13:10 2023-09-01    10 0.501  s    
# ℹ 990 more rows

Los tibbles están diseñados para no inundar tu consola accidentalmente al mirar data frames muy grandes. Sin embargo, a veces es necesario un output mayor que el que se obtiene por defecto. Existen algunas opciones que pueden ayudar.

Primero, puedes usar print() en el data frame y controlar el número de filas (n) y el ancho (width) mostrado. Por otro lado, width = Inf muestra todas las columnas:

datos::vuelos %>%
  print(n = 10, width = Inf)

También puedes controlar las características de impresión, modificando las opciones que están determinadas por default.

  • options(tibble.print_max = n, tibble.print_min = m): si hay más de n filas, mostrar solo m filas.

  • Usa options(tibble.print_min = Inf) para mostrar siempre todas las filas.

  • Usa options(tibble.width = Inf) para mostrar siempre todas las columnas sin importar el ancho de la pantalla.

Puedes ver una lista completa de opciones en la ayuda del paquete con package?tibble.

La opción final es usar el visualizador de datos de RStudio para obtener una versión interactiva del set de datos completo. Esto también es útil luego de realizar una larga cadena de manipulaciones.

datos::vuelos %>%
  View()

10.3.2 Selección de subconjuntos

Hasta ahora, todas las herramientas que aprendiste funcionan con el data frame completo. Si quieres recuperar una variable individual, necesitas algunas herramientas nuevas: $ y [[. Mientras que [[ permite extraer variables usando tanto su nombre como su posición, con $ sólo se puede extraer mediante el nombre. La única diferencia es que $ implica escribir un poco menos.

df <- tibble(
  x = runif(5),
  y = rnorm(5)
)

# Extraer usando el nombre
df$x
[1] 0.3130687 0.5759188 0.1918321 0.1014564 0.1586798
df[["x"]]
[1] 0.3130687 0.5759188 0.1918321 0.1014564 0.1586798
# Extraer indicando la posición
df[[1]]
[1] 0.3130687 0.5759188 0.1918321 0.1014564 0.1586798

Para usar estas herramientas dentro de un pipe, necesitarás usar el marcador de posición .:

df %>% .$x
[1] 0.3130687 0.5759188 0.1918321 0.1014564 0.1586798
df %>% .[["x"]]
[1] 0.3130687 0.5759188 0.1918321 0.1014564 0.1586798

En comparación a un data.frame, los tibbles son más estrictos: nunca funcionan con coincidencias parciales y generan una advertencia si la columna a la que intentas de acceder no existe.

10.4 Interactuando con código antiguo

Algunas funciones más antiguas no funcionan con los tibbles. Si te encuentras en uno de esos casos, usa as.data.frame() para convertir un tibble de nuevo en un data.frame:

class(as.data.frame(tb))
[1] "data.frame"

La principal razón de que algunas funciones previas no funcionen con tibbles es la función [. En este libro no usamos mucho [ porque dplyr::filter() y dplyr::select() resuelven los mismos problemas con un código más claro (aprenderás un poco sobre ello en el capítulo sobre subjoncuntos de vectores). Con los data frames de R base, [ a veces devuelve un data frame y a veces devuelve un vector. Con tibbles, [ siempre devuelve otro tibble.

10.5 Ejercicios

  1. ¿Cómo puedes saber si un objeto es un tibble? (Sugerencia: imprime mtautos en consola, que es un data frame clásico).

  2. Compara y contrasta las siguientes operaciones aplicadas a un data.frame y a un tibble equivalente. ¿Qué es diferente? ¿Por qué podría causarte problemas el comportamiento por defecto del data frame?

::: {.cell}

df <- data.frame(abc = 1, xyz = "a")
df$x
df[, "xyz"]
df[, c("abc", "xyz")]

:::

  1. Si tienes el nombre de una variable guardada en un objeto, p.e., var <- "mpg", ¿cómo puedes extraer esta variable de un tibble?

  2. Practica referenciar nombres no sintácticos en el siguiente data frame:

    1. Extrayendo la variable llamada 1.

    2. Generando un gráfico de dispersión de 1 vs 2.

    3. Creando una nueva columna llamada 3 que sea el resultado de la división de 2 por 1.

    4. Renombrando las columnas como uno, dos y tres.

molesto <- tibble(
  `1` = 1:10,
  `2` = `1` * 2 + rnorm(length(`1`))
)
  1. ¿Qué hace tibble::enframe()? ¿Cuándo lo usarías?

  2. ¿Qué opción controla cuántos nombres de columnas adicionales se muestran al pie de un tibble?