Capítulo16 Pipes
Fecha de la última revisión
## [1] "2026-09-09"
El tema proviene de los siguientes sitios.
English: https://r4ds.had.co.nz/pipes.html
Español: https://es.r4ds.hadley.nz/
16.1 Temas: El paquete “magrittr” y sus funciones
El operador pipe %>% fue creado por Stefan Milton Bache en el paquete magrittr (2014); el nombre juega con el cuadro de Magritte «Ceci n’est pas une pipe». Fue tan popular que en 2021 R incorporó su propio pipe nativo |>. La idea: leer el código de izquierda a derecha, como una receta de pasos.
- Qué problema resuelve el pipe
- Cómo funciona
%>%: el resultado de la izquierda entra por la derecha - El marcador de posición
. - El pipe nativo
|>y en qué se diferencia de%>% - Los pipes especiales de magrittr:
%$%,%<>%,%T>% %in%, que no es un pipe- Cuándo no conviene usar un pipe
16.2 ¿Qué problema resuelve el pipe?
Supón que quieres hacer cuatro cosas seguidas con una tabla: filtrarla, agruparla, resumirla y ordenar el resultado. Sin pipe hay dos maneras de escribirlo, y las dos son incómodas.
La primera es anidar las funciones, una dentro de otra:
arrange(summarise(group_by(filter(tabla, ...), ...), ...), ...). Se lee de
adentro hacia afuera, al revés del orden en que ocurren las cosas, y basta un
paréntesis mal puesto para perder media hora.
La segunda es guardar pasos intermedios: tabla1 <- filter(...),
tabla2 <- group_by(tabla1, ...), y así. Se lee mejor, pero acaba llenando la
memoria de objetos que no interesan a nadie, con nombres como tabla2 que en
dos semanas no significan nada.
El pipe resuelve las dos cosas a la vez: pone los pasos en el orden en que ocurren y no deja basura en el camino. El código empieza a leerse como una receta: toma la tabla, luego filtra, luego agrupa, luego resume.
Los operadores que vamos a evaluar son los siguientes. Los cinco primeros sí son pipes; el último no lo es, aunque se escriba igual:
%>%“Pipe”: pasa el resultado de la izquierda como primer argumento de la derecha. Es el que se usa el 95 % de las veces.|>“Pipe nativo”: lo mismo, pero viene con R y no necesita ningún paquete.%!>%“Eager pipe”, pipe impaciente: igual que%>%, pero calcula cada paso en el momento en vez de esperar. Solo importa cuando los pasos tienen efectos secundarios.%$%“Exposition pipe”, pipe de exposición: expone las columnas de la tabla para poder nombrarlas sueltas, como encor(x, y).%<>%“Assignment pipe”, pipe de asignación: pasa el objeto y guarda el resultado encima del original. Peligroso, ver más abajo.%T>%“Tee pipe”, pipe en T: hace un paso de lado (dibujar, imprimir, guardar) y sigue pasando el objeto que entró, no el resultado.
Y el que no es un pipe:
%in%no es un pipe. Es el operador de pertenencia: contesta “¿este valor está en esta lista?” y devuelveTRUEoFALSE. Está aquí porque se escribe con signos de porcentaje a los lados, igual que los pipes, y por eso se confunde. Tiene su propia sección más abajo.
En R, cualquier operador escrito entre signos de porcentaje (%algo%) es un
operador definido por alguien, no necesariamente un pipe. Ya conoces otros dos que
no tienen nada que ver con encadenar: %% (el residuo de una división) y %/%
(la división entera), del capítulo de fechas y horas. Los signos de porcentaje
son solo la manera que tiene R de escribir operadores propios.
Los pipes de magrittr: encadenar operaciones.
%>%/|>: pasa el resultado de la izquierda como primer argumento de la derecha.%$%: expone las columnas para usarlas por nombre (p. ej.cor(x, y)).%<>%: aplica y reasigna al objeto original.%T>%: ejecuta un paso “lateral” (comoplot) y sigue pasando el objeto original.
16.2.1 Cómo funciona, exactamente
La regla es de una línea: x %>% f(y) es lo mismo que f(x, y). Lo que está
a la izquierda del pipe entra como primer argumento de la función que está a
la derecha.
Eso funciona porque casi todas las funciones del tidyverse fueron diseñadas
pensándolo: filter(), mutate(), group_by(), summarise() y arrange()
reciben la tabla como primer argumento. Por eso encadenan tan bien.
¿Y cuando la función no recibe el dato en primer lugar? Para eso está el
marcador de posición, el punto ., que dice “ponlo aquí y no al principio”:
# sin marcador: la tabla entra como primer argumento
paises %>% filter(anio == 2007)
# con marcador: lm() quiere la fórmula primero y los datos después
paises %>% lm(esperanza_de_vida ~ pib_per_capita, data = .)Lee el %>% en voz alta como “y luego”. datos %>% filter(...) %>% summarise(...) se lee “toma los datos, y luego filtra, y luego resume”. Si la
frase no tiene sentido en voz alta, el código tampoco lo va a tener.
16.2.2 %>% y |>: cuál usar
Desde la versión 4.1, R trae su propio pipe, |>, que no necesita ningún
paquete. Para el 95 % de lo que hacemos en el curso los dos son intercambiables.
Las diferencias que sí importan:
- Necesita un paquete:
%>%sí (magrittr, que viene contidyverse); el nativo no, es parte de R. - Marcador de posición:
%>%usa el punto.en cualquier posición; el nativo usa el guion bajo_y solo en un argumento con nombre. - Función sin paréntesis:
x %>% sqrtse permite; con el nativo hay que escribir siempresqrt(). - Pipes especiales (
%$%,%<>%,%T>%): existen solo del lado demagrittr.
En la práctica: usa |> cuando puedas, porque es parte de R y siempre va a estar
ahí; usa %>% cuando necesites el punto o alguno de los pipes especiales de este
capítulo. En este libro vas a ver los dos, a veces en el mismo bloque, y no es un
error.
16.2.3 Un primer ejemplo: con pipe y sin pipe
Los ejemplos que siguen usan los conjuntos en inglés que vienen con R y con
ggplot2 (diamonds, mtcars, iris), no las versiones en español del paquete
datos. Es a propósito: casi toda la documentación de magrittr que vas a
encontrar en internet está escrita con estos mismos datos, y conviene que los
reconozcas.
Primero, la manera de siempre, sin pipe: se crea una columna nueva escribiendo el nombre de la tabla tres veces en la misma línea.
Función básica para calcular el precio básico de diamantes por quilates
## # A tibble: 3 × 10
## carat cut color clarity depth table price x y z
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
## # A tibble: 3 × 11
## carat cut color clarity depth table price x y z Precio_q
## <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl>
## 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43 1417.
## 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31 1552.
## 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31 1422.
Ahora lo mismo con pipe y mutate(). El nombre de la tabla aparece una sola
vez, y la línea dice lo que hace:
dplyr::mutate() con los dos puntos dobles significa “la función mutate() del
paquete dplyr”. No hace falta si ya activaste tidyverse, pero se usa cuando
dos paquetes tienen funciones con el mismo nombre y se quiere dejar claro cuál es.
Calcular el promedio de precio por la calidad de los diamantes usando pipes
Desde la versión 4.1, R trae un pipe nativo |> que no requiere paquetes. El %>% proviene de magrittr y añade funciones extra como el marcador de posición ..
## # A tibble: 5 × 2
## cut Precio_color
## <ord> <dbl>
## 1 Fair 4359.
## 2 Good 3929.
## 3 Very Good 3982.
## 4 Premium 4584.
## 5 Ideal 3458.
Calcular el promedio de precio por color de los diamantes usando pipes
16.3 El ejemplo clásico de magrittr
Los tres bloques que siguen hacen exactamente lo mismo con mtcars y están aquí
para comparar. Vale la pena leerlos despacio, porque es el ejemplo con el que
magrittr se presentó al mundo.
Un ejemplo básico del pipe
car_data <-
mtcars %>%
subset(hp > 100) %>%
aggregate(. ~ cyl, data = ., FUN = . %>% mean %>% round(2)) %>%
transform(kpl = mpg %>% multiply_by(0.4251)) %>%
print## cyl mpg disp hp drat wt qsec vs am gear carb kpl
## 1 4 25.90 108.05 111.00 3.94 2.15 17.75 1.00 1.00 4.50 2.00 11.010090
## 2 6 19.74 183.31 122.29 3.59 3.12 17.98 0.57 0.43 3.86 3.43 8.391474
## 3 8 15.10 353.10 209.21 3.23 4.00 16.77 0.00 0.14 3.29 3.50 6.419010
Fíjate en dos cosas. Primero, data = .: aggregate() no recibe los datos en
primer lugar, así que hace falta el marcador de posición. Segundo,
FUN = . %>% mean %>% round(2): una cadena de pipes que empieza con un punto no
es un cálculo, es una función anónima, una receta guardada para aplicarla
después a lo que llegue.
Ahora la misma cuenta escrita a la antigua, con las funciones anidadas:
Un ejemplo sin usar el pipe para calcular la misma información
car_data <-
transform(aggregate(. ~ cyl,
data = subset(mtcars, hp > 100),
FUN = function(x) round(mean(x), 2)),
kpl = mpg*0.4251)
car_data## cyl mpg disp hp drat wt qsec vs am gear carb kpl
## 1 4 25.90 108.05 111.00 3.94 2.15 17.75 1.00 1.00 4.50 2.00 11.010090
## 2 6 19.74 183.31 122.29 3.59 3.12 17.98 0.57 0.43 3.86 3.43 8.391474
## 3 8 15.10 353.10 209.21 3.23 4.00 16.77 0.00 0.14 3.29 3.50 6.419010
Las dos dan el mismo resultado. La diferencia está en cómo se leen: la primera se
lee de arriba abajo en el orden en que ocurren las cosas; la segunda hay que
leerla de adentro hacia afuera, empezando por subset(), que está enterrado en
el centro.
Y una tercera versión, con las funciones de dplyr en vez de las de base R:
car_data <-
mtcars %>%
subset(hp > 100) %>%
group_by(cyl) %>%
summarise(across(everything(), list(mean))) %>%
transform(kpl = mpg_1 %>% multiply_by(0.4251)) %>%
print## cyl mpg_1 disp_1 hp_1 drat_1 wt_1 qsec_1 vs_1 am_1
## 1 4 25.90000 108.0500 111.0000 3.940000 2.146500 17.75000 1.0000000 1.0000000
## 2 6 19.74286 183.3143 122.2857 3.585714 3.117143 17.97714 0.5714286 0.4285714
## 3 8 15.10000 353.1000 209.2143 3.229286 3.999214 16.77214 0.0000000 0.1428571
## gear_1 carb_1 kpl
## 1 4.500000 2.000000 11.010090
## 2 3.857143 3.428571 8.392689
## 3 3.285714 3.500000 6.419010
16.4 Alternativas a los pipes
El pipe no es obligatorio y no siempre es la mejor opción. Las dos alternativas son:
- Pasos intermedios. Guardar cada paso en un objeto con nombre
(
vuelos_limpios,resumen_por_mes). Ocupa más líneas, pero deja el resultado de cada paso disponible para revisarlo, y eso es exactamente lo que hace falta cuando algo sale mal en el medio de una cadena larga. - Sobre escribir el original. Reasignar sobre el mismo nombre
(
tabla <- filter(tabla, ...), y otra vez, y otra vez). Ahorra nombres, pero es peligroso: si corres una línea dos veces por accidente, ya no sabes en qué estado quedó la tabla, y hay que volver a leer los datos desde el principio.
Cuándo no usar el pipe: cuando la cadena pasa de unos diez pasos (parte en dos con un objeto intermedio bien nombrado); cuando hay varias entradas o varias salidas, porque el pipe supone un solo objeto que viaja; y cuando estás depurando, porque un objeto intermedio con nombre se puede mirar y un paso en el medio de una cadena no.
16.5 %$%: el pipe de exposición
Algunas funciones no reciben una tabla: reciben vectores sueltos. cor() es
el ejemplo típico: quiere cor(x, y), dos columnas, no un data frame. Con el
pipe normal eso no funciona, porque cor() recibiría la tabla entera.
El pipe de exposición %$% resuelve justo eso: expone las columnas de la
tabla para que se puedan nombrar directamente, sin escribir tabla$columna cada
vez.
%$% (magrittr): expone las columnas de la tabla como si fueran objetos sueltos.
tabla %$% cor(a, b)equivale acor(tabla$a, tabla$b).- Para qué sirve: usar funciones de base R que esperan vectores (
cor(),t.test(),table(),ts.plot()) dentro de una cadena de pipes. - Ojo: solo funciona con
%>%; el pipe nativo|>no tiene equivalente.
En el bloque siguiente, las líneas comentadas son las que no funcionan, y
están ahí a propósito para que veas el contraste: con el pipe nativo cor()
recibiría la tabla completa y fallaría.
library(magrittr)
#iris %>%
# subset(Sepal.Length > mean(Sepal.Length)) |>
# cor(Sepal.Length, Sepal.Width)
iris %>%
subset(Sepal.Length > mean(Sepal.Length)) %$%
cor(Sepal.Length, Sepal.Width)## [1] 0.3361992
iris %>%
subset(Sepal.Length > mean(Sepal.Length)) |>
summarize(corr= cor(Sepal.Length, Sepal.Width))## corr
## 1 0.3361992
La segunda parte del bloque enseña la alternativa tidyverse: si en vez de cor()
usas summarize(), ya no hace falta exponer nada, porque summarize() sí sabe
buscar las columnas dentro de la tabla. Casi siempre hay una salida tidyverse que
evita %$%.
16.5.1 Otro ejemplo de Pipe: %$%
ts.plot() es una función de base R que dibuja una serie de tiempo y espera un
vector, no una tabla: otro caso donde %$% es lo que hace falta. Después del
ejemplo viene la versión tidyverse de la misma gráfica, con ggplot(), donde
todo se resuelve dentro de la tabla.

#data.frame(z = rnorm(100)) |>
# ts.plot(z)
#Para hacerlo la misma grafico con Tidyverse
z1=data.frame(z = rnorm(100))
library(data.table)
z1=data.table(z1)
z1$order= c(1:100)
z1|>
ggplot(aes(order, z))+
geom_line()
16.6 %<>%: el pipe de asignación
Este pipe hace dos cosas de un golpe: pasa el objeto a la función y guarda el
resultado encima del objeto original. Es decir, x %<>% sqrt es lo mismo que
escribir x <- x %>% sqrt.
lhs %<>% rhs
lhs: el objeto, que sirve a la vez de valor inicial y de destino.rhs: la llamada a la función, con la sintaxis de magrittr.
%<>% modifica el objeto original y no hay manera de deshacerlo. En el
Ejemplo 1 de abajo, la columna Sepal.Length de iris queda cambiada de verdad
en tu sesión: si vuelves a correr el bloque, le sacarás la raíz cuadrada otra vez,
y luego otra, sin ningún aviso. Por eso casi nunca se usa en un análisis de
verdad: rompe la regla de que correr el mismo script dos veces dé el mismo
resultado. Si necesitas el valor transformado, guárdalo en una columna nueva
con mutate().
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 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.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
## [1] 2.258318 2.213594 2.167948 2.144761 2.236068 2.323790
## [1] 1.361612477 -0.189292059 0.462969745 -0.007385678 2.689108440
## [6] 1.459546809 -1.653300235 -0.203078277 0.618497216 0.597135379
## [1] 0.007385678 0.029096523 0.030999873 0.035387591 0.040816992 0.054441440
## [7] 0.069795203 0.081581732 0.087776321 0.110958305 0.119075263 0.120358116
## [13] 0.120542457 0.131861167 0.133266112 0.145019780 0.145731942 0.150293091
## [19] 0.155538095 0.164714712
16.7 %in%: esto no es un pipe
%in% aparece en la lista del principio porque se escribe con signos de
porcentaje a los lados, igual que los pipes, pero no tiene nada que ver con
ellos. No pasa nada de la izquierda a la derecha: es un operador de
pertenencia, que contesta la pregunta “¿este valor está en esta lista?” y
devuelve TRUE o FALSE.
- Sirve para filtrar por varios valores específicos de una variable a la vez.
%in% (base R): pertenencia a un conjunto.
x %in% c("a", "b")devuelveTRUEpara cada elemento dexque sea"a"o"b".- Para qué sirve: sustituye a una cadena larga de
|. En vez defilter(d, sp == "a" | sp == "b" | sp == "c"), se escribefilter(d, sp %in% c("a", "b", "c")). - Ojo con los
NA:NA %in% c(1, 2)devuelveFALSE, noNA, que es lo contrario de lo que hacen casi todas las comparaciones en R.
En el ejemplo se filtran dos especies de Iris, Iris setosa e Iris virginica, dejando fuera la tercera.
## [1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 2.258318 3.5 1.4 0.2 setosa
## 2 2.213594 3.0 1.4 0.2 setosa
## 3 2.167948 3.2 1.3 0.2 setosa
## 4 2.144761 3.1 1.5 0.2 setosa
## 5 2.236068 3.6 1.4 0.2 setosa
## 6 2.323790 3.9 1.7 0.4 setosa
16.8 %T>%: el pipe en T
El pipe en T, %T>%, sirve cuando en medio de una cadena hay una función que
no devuelve nada, como plot(), print() o write_csv(). Con un pipe
normal la cadena se rompe ahí, porque el paso siguiente recibiría el NULL que
esa función devuelve.
%T>% hace el paso “de lado”, como el palo corto de una T: ejecuta la función y
sigue pasando el objeto que entró, no el resultado.
%T>% (magrittr): paso lateral dentro de una cadena.
x %T>% plot() %>% colSums(): dibujaxy después le calcula las sumas de columna, todo en una cadena.- Cuándo se usa: para dibujar, imprimir o guardar en medio de un análisis, sin cortarlo en dos.
- Sin él habría que guardar el objeto en una variable, dibujarlo, y volver a usarlo.
Referencia: https://stackoverflow.com/questions/61196304/magrittr-tee-pipe-t-equivalent

## [1] 1.395464 5.606604
16.9 %!>%: el pipe impaciente
Aparece en la lista del principio y casi nunca hace falta, pero conviene saber
que existe. %>% es perezoso: no calcula un paso hasta que alguien necesita
su resultado. %!>%, añadido en magrittr 2.0, es impaciente: obliga a
calcular cada paso en el momento en que se escribe.
La diferencia solo se nota cuando los pasos tienen efectos secundarios, es
decir, cuando además de calcular hacen algo más: imprimir un mensaje, escribir un
archivo, avanzar un número al azar. Ahí el orden en que ocurren las cosas importa,
y %!>% lo garantiza. En un análisis normal, %>% y %!>% dan el mismo
resultado.
16.10 Ejercicios del capítulo
Sobre los ejercicios de este capítulo. Cada ejercicio se entrega con tres
cosas: el script, el resultado (que el bloque corra) y una explicación
en palabras. Los ejercicios usan millas y paises del paquete datos, en
español, y no mtcars, iris ni diamonds, que son los del capítulo. La hoja
completa para entregar está en Ejercicios/Ejercicios_Capitulo_16_pipes.Rmd.
16.10.1 Ejercicio 1. De adentro hacia afuera, a de arriba hacia abajo
Este código anidado funciona pero es ilegible:
arrange(summarise(group_by(filter(paises, anio == 2007), continente), esperanza_media = mean(esperanza_de_vida)), desc(esperanza_media))
Reescríbelo con pipes, un paso por línea.
Verificación: tu tabla debe tener 5 filas, una por continente, y el mismo orden que produce la versión anidada.
En palabras: ¿en qué orden hay que leer la versión anidada para entenderla? ¿Y la tuya?
16.10.2 Ejercicio 2. El marcador de posición
Datos: paises del año 2007. Ajusta un modelo lineal de esperanza_de_vida en
función de pib_per_capita dentro de una cadena de pipes, usando lm().
lm() quiere la fórmula primero, así que vas a necesitar data = ..
Verificación: el coeficiente del pib_per_capita debe salir positivo.
En palabras: ¿qué habría pasado si escribieras paises2007 %>% lm(...) sin
el punto? Explica qué habría recibido lm() como primer argumento.
16.10.3 Ejercicio 3. %>% y |> lado a lado
Datos: millas. Escribe la misma cadena dos veces, una con %>% y otra con
|>: filtra los vehículos de 2008, agrupa por clase, calcula el promedio de
autopista y ordena de mayor a menor. Después intenta escribir millas %>% nrow
y millas |> nrow (sin paréntesis) y mira qué pasa.
Verificación: las dos cadenas deben dar exactamente la misma tabla; una de
las dos versiones de nrow sin paréntesis da error.
En palabras: ¿cuál de las dos falló y por qué? ¿Cuál de los dos pipes usarías por defecto, y en qué caso cambiarías de opinión?
16.10.4 Ejercicio 4. %$% con una función que quiere vectores
Datos: millas. Calcula la correlación entre cilindrada y autopista de tres
maneras: con cor(millas$cilindrada, millas$autopista), con %$%, y con
summarise() dentro de una cadena normal. Prueba también qué pasa si usas %>%
en vez de %$%.
Verificación: las tres maneras que funcionan dan el mismo número, negativo y con valor absoluto mayor que 0.7.
En palabras: ¿qué mensaje da la versión con %>% y por qué? ¿Cuál de las
tres versiones que sí funcionan usarías en un análisis tuyo?
16.10.5 Ejercicio 5. Por qué %<>% es peligroso
Haz una copia: millas_copia <- millas. Aplícale
millas_copia$autopista %<>% sqrt y mira el resultado. Después corre la misma
línea otra vez y vuelve a mirar.
Verificación: millas sin tocar sigue igual, pero los valores de
millas_copia cambiaron las dos veces.
En palabras: explica por qué esto rompe la reproducibilidad de un script.
Escribe debajo la versión con mutate() que hace lo mismo sin ese problema.
16.10.6 Ejercicio 6. %T>%, el paso de lado
Datos: millas. En una sola cadena: toma millas, quédate con cilindrada
y autopista, dibuja el par con plot() en el medio de la cadena usando
%T>%, y termina calculando el promedio de las dos columnas con colMeans().
Prueba primero con %>% en vez de %T>% para ver el error.
Verificación: con %T>% sale la gráfica y un vector de dos promedios.
En palabras: ¿qué recibió colMeans() cuando usaste %>%? ¿Por qué?
16.10.7 Ejercicio 7 (BONO). %in% no es un pipe
Datos: paises del año 2007. Filtra los países de tres continentes de dos
maneras: con | repetido tres veces, y con %in%. Después contesta con código:
¿qué devuelve NA %in% c(1, 2) y qué devuelve NA == 1?
Verificación: las dos maneras de filtrar dan la misma tabla.
En palabras: %in% se escribe como un pipe pero no lo es. Explica en una
oración qué hace realmente, y por qué la diferencia entre NA %in% ... y
NA == ... puede cambiar el resultado de un filtro.