El operador pipe en R: %>% y |> explicados

Introducción a R Conceptos básicos de R
El operador pipe de magrittr y el pipe nativo en R

El operador pipe toma el resultado de la expresión de su izquierda y lo pasa como primer argumento de la función de su derecha, de modo que x %>% f(y) equivale a f(x, y). En R hay dos pipes: %>% del paquete magrittr (que cargan dplyr y el tidyverse) y el pipe nativo |>, disponible en R base desde la versión 4.1. En este tutorial aprenderás cómo funcionan ambos, en qué se diferencian, cómo enviar el resultado a otro argumento y cómo solucionar los errores más habituales.

¿Qué significa %>% en R?

El pipe se puede leer como “y luego”. Toma el valor de la izquierda y lo inserta como primer argumento de la llamada a la función de la derecha. Cualquier otro argumento que escribas dentro de la llamada se coloca a continuación:

library(magrittr) # También se carga con library(dplyr)

x <- c(1, 5, 12, 20, 33)

head(x, 3)
x %>% head(3) # Equivale a head(x, 3)
x |> head(3)  # Equivale a head(x, 3)
[1]  1  5 12
[1]  1  5 12
[1]  1  5 12

Las tres líneas devuelven exactamente lo mismo. El pipe no añade ninguna funcionalidad nueva, solo cambia la forma de escribir el código. Tienes una breve introducción al pipe junto con el resto de operadores en el tutorial de operadores en R.

¿Por qué usar el pipe? Código anidado frente a encadenado

Supón que quieres calcular la raíz cuadrada de x, después la media del resultado y finalmente redondearla a dos decimales. Sin pipes tendrás que anidar las llamadas a las funciones, por lo que el código se lee de dentro hacia fuera: la primera operación (sqrt) es la última que lees.

x <- c(1, 5, 12, 20, 33)

# Llamadas anidadas
round(mean(sqrt(x)), 2)

# Con el pipe: se lee de izquierda a derecha
x %>% sqrt() %>% mean() %>% round(2)
[1] 3.38
[1] 3.38

Con el pipe las operaciones aparecen en el mismo orden en el que se ejecutan y los argumentos adicionales (el 2 de round) quedan junto a su función. La alternativa es guardar cada paso en un objeto intermedio, lo cual funciona pero llena tu espacio de trabajo de variables que no volverás a usar.

La ventaja es más clara en cadenas largas. Cuando el código tiene varios pasos, escribe cada uno en su propia línea, con el pipe al final de la línea:

x %>%
  sqrt() %>%
  mean() %>%
  round(2)

Pipe de magrittr %>% frente al pipe nativo |>

El operador %>% proviene del paquete magrittr. Puedes usarlo después de cargar magrittr, dplyr o el tidyverse. Desde R 4.1.0, R base también incluye el pipe nativo |>, que no necesita ningún paquete. En la mayoría de los casos ambos se usan de la misma forma:

x %>% sqrt() %>% mean() %>% round(2)

x |> sqrt() |> mean() |> round(2)
[1] 3.38
[1] 3.38

Sin embargo, hay algunas diferencias que conviene conocer:

%>% de magrittr |> nativo
Requiere magrittr, dplyr o tidyverse R 4.1.0 o posterior
Marcador de posición . _ (R 4.2.0 o posterior)
Marcador en un argumento sin nombre Sí No, debe tener nombre
Función sin paréntesis (x %>% sqrt) Sí No, da error
Marcador dentro de llamadas anidadas Sí No
Pipes adicionales (%<>%, %T>%, %$%) Sí No
Velocidad Pequeño coste adicional Sin coste adicional

El pipe nativo no es una función. R reescribe el código al analizarlo, por lo que x |> f(y) se convierte en f(x, y) antes de ejecutarse. Puedes comprobarlo con quote:

quote(x |> sqrt() |> mean() |> round(2))
round(mean(sqrt(x)), 2)

Por eso |> es tan rápido como el código anidado. En cambio, %>% es una función normal que se ejecuta cada vez que la llamas. La diferencia se mide en microsegundos, como puedes ver con el paquete microbenchmark:

library(microbenchmark)

microbenchmark(
  anidado  = round(mean(sqrt(x)), 2),
  nativo   = x |> sqrt() |> mean() |> round(2),
  magrittr = x %>% sqrt() %>% mean() %>% round(2),
  times = 10000
)
Unit: microseconds
     expr min  lq    mean median  uq    max neval cld
  anidado 2.6 2.8 3.42749    3.0 3.5 1207.9 10000  a
   nativo 2.6 2.8 3.33783    3.0 3.5  501.1 10000  a
 magrittr 3.8 4.1 5.01452    4.3 5.2  739.8 10000   b

Los tiempos exactos dependerán de tu ordenador, pero fíjate en la mediana: el código anidado y el pipe nativo tardan lo mismo, mientras que el pipe de magrittr tarda algo más de 1 microsegundo extra por llamada (la columna cld marca con letras distintas las expresiones que difieren). En la práctica esto no importa, salvo que llames a un pipe millones de veces dentro de un bucle.

Entonces, ¿cuál deberías usar? Si tu código requiere R 4.1 o posterior, el pipe nativo |> es la opción por defecto: no depende de ningún paquete y es el que se utiliza en la documentación reciente de dplyr y de R. Quédate con %>% si tu código tiene que funcionar en versiones antiguas de R o si necesitas sus funcionalidades extra, como el marcador . dentro de llamadas anidadas.

Cómo escribir el pipe en RStudio

En RStudio puedes insertar el pipe con el atajo de teclado Ctrl + Shift + M (Cmd + Shift + M en macOS). Por defecto inserta %>%. Si prefieres el pipe nativo, ve a Tools → Global Options → Code y marca la opción Use native pipe operator, |> (requires R 4.1+). A partir de ese momento, el mismo atajo insertará |>.

Pasar el resultado a un argumento distinto del primero

El pipe siempre utiliza el primer argumento, pero muchas funciones esperan los datos en otra posición. Por ejemplo, el primer argumento de lm es la fórmula y los datos van en el argumento data. En estos casos necesitas un marcador de posición (placeholder) que indique dónde debe ir el lado izquierdo: . con %>% y _ con |>.

# magrittr: el punto
mtcars %>% lm(mpg ~ wt, data = .) %>% coef()

# Pipe nativo: el guion bajo (R >= 4.2.0)
mtcars |> lm(mpg ~ wt, data = _) |> coef()
(Intercept)          wt
  37.285126   -5.344472
(Intercept)          wt
  37.285126   -5.344472

El marcador nativo _ tiene algunas restricciones: tiene que pasarse a un argumento con nombre y solo puede aparecer una vez. Desde R 4.3.0 también puede usarse al principio de una extracción, como mtcars |> _$mpg. Si necesitas algo más flexible con |>, puedes encadenar una función anónima, escrita como \(z) (forma abreviada de function(z)) y seguida de ():

x |> (\(z) z[z > 15])()
[1] 20 33

Consulta el tutorial sobre cómo crear funciones en R para saber más sobre funciones.

El punto dentro de llamadas anidadas

Con %>% hay una regla que sorprende a muchos usuarios. Si el . solo aparece dentro de otra llamada a una función, magrittr sigue pasando el lado izquierdo como primer argumento. En el siguiente ejemplo la intención es pegar la longitud de x, pero x también se usa como primer argumento de paste:

x %>% paste(length(.))
[1] "1 5"  "5 5"  "12 5" "20 5" "33 5"

Para evitarlo, encierra el lado derecho entre llaves. Así el lado izquierdo solo se utiliza donde está el .:

x %>% {paste(length(.))}
[1] "5"

Pipes con dplyr

El pipe es especialmente popular con dplyr porque todas sus funciones reciben un data frame como primer argumento y devuelven un data frame. Esto te permite encadenar operaciones como filter, group_by, summarise y arrange. El siguiente código se queda con los coches de más de 100 caballos, los agrupa por número de cilindros, calcula el número de coches y el consumo medio, y ordena el resultado:

library(dplyr)

mtcars %>%
  filter(hp > 100) %>%
  group_by(cyl) %>%
  summarise(n = n(), mean_mpg = mean(mpg)) %>%
  arrange(desc(mean_mpg))
# A tibble: 3 × 3
    cyl     n mean_mpg
  <dbl> <int>    <dbl>
1     4     2     25.9
2     6     7     19.7
3     8    14     15.1

Ahora compáralo con el mismo código sin pipes. El resultado es idéntico, pero es mucho más difícil ver qué argumentos pertenecen a cada función:

arrange(summarise(group_by(filter(mtcars, hp > 100), cyl),
                  n = n(), mean_mpg = mean(mpg)), desc(mean_mpg))

Puedes sustituir cada %>% por |> en la cadena anterior y obtendrás la misma salida.

Otros pipes de magrittr

El paquete magrittr incluye otros tres pipes. dplyr no los carga, así que tendrás que llamar a library(magrittr) para usarlos.

  • Pipe de asignación %<>%: pasa el objeto por el pipe y le asigna el resultado. y %<>% sqrt() equivale a y <- y %>% sqrt().
  • Pipe en T %T>%: devuelve el lado izquierdo en lugar del resultado de la función. Es útil para funciones que se llaman por sus efectos secundarios, como gráficos o print, en mitad de una cadena.
  • Pipe de exposición %$%: expone las columnas de un data frame a la función, como with. Resulta práctico con funciones que no tienen argumento data, como cor.
library(magrittr)

# Pipe de asignación
y <- c(1, 5, 12, 20, 33)
y %<>% sqrt()
y

# Pipe en T: dibuja un histograma y después devuelve el resumen
mtcars$mpg %T>% hist() %>% summary()

# Pipe de exposición: equivale a cor(mtcars$mpg, mtcars$wt)
mtcars %$% cor(mpg, wt)
[1] 1.000000 2.236068 3.464102 4.472136 5.744563

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  10.40   15.43   19.20   20.09   22.80   33.90

[1] -0.8676594

Sin el pipe en T, summary recibiría la salida de hist (un objeto de clase histogram) en lugar de los datos.

El pipe de asignación %<>% sobrescribe el objeto original sin preguntar. Muchas guías de estilo recomiendan evitarlo y escribir la asignación de forma explícita con <-, ya que el código resulta más fácil de leer.

Cuándo no usar pipes y cómo depurarlos

Los pipes son ideales para una secuencia lineal de transformaciones sobre un único objeto, pero no siempre son la mejor opción:

  • Cadenas muy largas (más de diez pasos, aproximadamente) son difíciles de depurar. Divídelas guardando objetos intermedios con nombres claros.
  • Varias entradas o salidas. Si un paso necesita dos objetos diferentes o tienes que conservar resultados intermedios, utiliza asignaciones normales.
  • Funciones que no reciben los datos como primer argumento y necesitan marcadores complejos. A veces una llamada normal a la función es más legible.

Cuando una cadena devuelve un resultado inesperado, ejecútala paso a paso. En RStudio, selecciona el código hasta el final de una línea (sin el último pipe) y ejecútalo con Ctrl + Enter para ver el resultado intermedio. También puedes guardar parte de la cadena e inspeccionarla:

paso1 <- mtcars %>% filter(hp > 100)
nrow(paso1)

paso1 %>% count(cyl)
[1] 23
  cyl  n
1   4  2
2   6  7
3   8 14

Errores comunes con el pipe

Error: could not find function “%>%”

Es el error más frecuente. Significa que el paquete que proporciona %>% no está cargado en la sesión actual:

Error in x %>% sqrt() : could not find function "%>%"

Para solucionarlo, carga magrittr o dplyr con library(dplyr) antes de usar el pipe (instálalo primero con install.packages("dplyr") si es necesario, como se explica en cómo instalar paquetes en R), o utiliza el pipe nativo |>, que no necesita ningún paquete.

Usar el punto con el pipe nativo

El pipe nativo no entiende el marcador . de magrittr. Cuando sustituyes %>% por |> en código antiguo, el . se trata como un objeto normal que no existe:

mtcars |> lm(mpg ~ wt, data = .)
Error in eval(mf, parent.frame()) : object '.' not found

Utiliza data = _ en su lugar. Ten en cuenta que el guion bajo debe ir en un argumento con nombre; si no, obtendrás el siguiente error:

x |> paste("a", _)
Error in paste("a", "_") :
  pipe placeholder can only be used as a named argument (<input>:1:6)

Función sin paréntesis

Con %>% puedes escribir x %>% sqrt, pero el pipe nativo requiere una llamada a una función en el lado derecho, es decir, el nombre de la función seguido de paréntesis:

x |> sqrt
Error in sqrt :
  The pipe operator requires a function call as RHS (<input>:1:6)

Escribe x |> sqrt() en su lugar.

El pipe al principio de una línea

R evalúa una línea en cuanto forma una expresión completa. Si colocas el pipe al principio de la línea siguiente, la primera línea se ejecuta por separado y la segunda produce un error:

x
  %>% sqrt()
[1]  1  5 12 20 33
Error: unexpected SPECIAL in "  %>%"

Con el pipe nativo el mensaje es unexpected '|>'. En ambos casos, coloca el pipe al final de la línea, como en todos los ejemplos de este tutorial.

ggplot2 utiliza + en lugar de un pipe para añadir capas. Un error frecuente es escribir %>% entre capas de ggplot2 o + entre verbos de dplyr. Puedes pasar un data frame a ggplot() con un pipe, pero a partir de esa llamada tendrás que usar +.