Какую проблему решают пайпы
Пайп берёт стоящее перед ним значение и подаёт его в следующий вызов функции — x |> f() означает f(x). Это звучит как тривиальная перезапись, пока вы не сцепите три-четыре шага. Без пайпа многошаговые преобразования вкладываются друг в друга, а вложенные вызовы читаются задом наперёд: то, что происходит первым, находится в самой внутренней позиции.
Чтобы прочитать round(sqrt(x), 1), вы начинаете с середины (x), двигаетесь наружу (sqrt, затем round) и держите в голове, какая завершающая , 1) какому вызову принадлежит. На трёх уровнях это раздражает; на пяти, с глаголами по датафреймам и несколькими аргументами у каждого, — по-настоящему трудно уследить. Альтернатива, к которой прибегают, — сохранять каждый промежуточный шаг в tmp1, tmp2, tmp3 — засоряет рабочее пространство именами, которые никому не нужны.
Пайп переписывает цепочку в том порядке, в каком шаги происходят на самом деле: взять x, затем извлечь корень, затем округлить.
Встроенный пайп |>
Начиная с R 4.1, пайп встроен в сам язык — пакет не нужен. |> вставляет предыдущее значение первым аргументом следующего вызова:
c(1, 4, 9, 16) |> sqrt() |> round(1) — это ровно round(sqrt(c(1, 4, 9, 16)), 1): R буквально переписывает одно в другое на этапе разбора, поэтому накладных расходов во время выполнения нет вообще. Но теперь код читается как рецепт, сверху вниз: данные, а затем каждое преобразование по порядку. Это работает с любой функцией, а не только с инструментами для данных: mtcars |> head(3) — от начала и до конца обычный базовый R.
Обратите внимание, как хорошо пайпы ложатся на функции, чей первый параметр — «данные»: head, sort, unique, summary и каждый глагол dplyr спроектированы именно так, поэтому конвейеры в R ощущаются столь естественно.
Правила |>
Встроенный пайп намеренно строг. Три правила покрывают почти всё:
1. Следующий шаг должен быть вызовом, со скобками. x |> sqrt — синтаксическая ошибка; нужно писать x |> sqrt():
c(1, 4, 9) |> sqrt # Error: The pipe operator requires a function call as RHS
c(1, 4, 9) |> sqrt() # correct
2. Значение попадает в позицию первого аргумента. Дополнительные аргументы, которые вы написали, остаются на своих местах после него — x |> round(1) это round(x, 1).
3. Чтобы значение попало куда-то ещё, используйте подстановку _ с именованным аргументом (R 4.2+). Классический случай: lm принимает сначала формулу, а данные вторыми, поэтому подача датафрейма в неё требует data = _:
_ может встретиться один раз и только как именованный аргумент. Когда это слишком ограничивает, подавайте в анонимную функцию — она может поставить значение куда угодно:
Лямбда обёрнута в скобки и затем вызвана через () — так соблюдается правило 1. Если вы часто делаете так в одном конвейере, этот шаг, вероятно, просится стать именованной функцией.
Пайп magrittr %>%
До того как пайп появился в языке, его предоставлял пакет magrittr, и %>% стал визитной карточкой кода tidyverse — загрузка dplyr даёт его вам автоматически. Вы встретите его почти в каждом учебнике по dplyr и почти в каждом скрипте, написанном за последнее десятилетие:
library(dplyr)
mtcars %>%
filter(mpg > 30) %>%
select(mpg, wt)
Поскольку %>% приходит из пакета, эти примеры нельзя запустить в чистой сессии R — Error: could not find function "%>%" это ровно то, что вы получите, забыв library(dplyr) (или library(magrittr)).
В обычном случае поведение идентично |>: подать предыдущее значение первым аргументом следующего вызова. Но magrittr более снисходителен:
c(1, 4, 9) %>% sqrt # bare function name - no parentheses needed
mtcars %>% lm(mpg ~ wt, data = .) # . placeholder, usable anywhere
c(-2, 3) %>% { max(abs(.)) } # . can even appear several times, in nested calls
|> против %>%: различия, которые имеют значение
Для конвейера по первому аргументу — а это подавляющее большинство реального кода — они взаимозаменяемы. Различия живут по краям:
- Доступность:
|>— базовый R 4.1+;%>%требует magrittr (обычно через dplyr). - Скобки:
|>требуетsqrt();%>%принимает голоеsqrt. - Подстановка:
|>использует_один раз и только как именованный аргумент (4.2+);%>%использует.где угодно, сколько угодно раз, в том числе внутри вложенных вызовов. - Скорость:
|>переписывается на этапе разбора во вложенный вызов — нулевая стоимость.%>%— обычный вызов функции с небольшими (на практике пренебрежимыми) накладными расходами.
Ничто из этого не делает %>% неправильным — он проверен боем и чуть гибче. Но всё, что он умеет сверх |>, можно сделать через лямбду, а встроенному пайпу не нужна зависимость.
Какой использовать?
Для нового кода: используйте |>. Он часть языка, работает везде, где работает R 4.1+, не требует пакета, а его строгость читается как достоинство — конвейеры остаются простыми или рефакторятся в именованные функции.
Но вы обязаны свободно читать %>%, потому что кодовые базы tidyverse, ответы на Stack Overflow и большинство книг по R, написанных с 2014 года, им полны. Команды с устоявшимся стилем dplyr часто сохраняют %>% ради единообразия, и это разумное решение — худший выбор это смешивать оба оператора в одном файле. Что бы вы ни писали, одно соглашение переносится всегда: в длинных конвейерах ставьте каждый шаг на отдельную строку с пайпом в конце строки, чтобы рецепт читался как список шагов.
Здесь работает тот же вкус, что и с семейством apply: пайпы предназначены для линейных пошаговых преобразований. Когда вычисление ветвится или промежуточный результат нужен дважды, лучше присвоить его переменной с хорошим именем, а не проталкивать всё через одну цепочку.
Что вы уносите с собой
- Пайп подаёт предыдущее значение в следующий вызов:
x |> f() |> g()этоg(f(x)), записанное в порядке происходящего. |>— базовый R (4.1+): требует скобок, целится в первый аргумент, подстановка_с именованным аргументом (4.2+).%>%— magrittr/tidyverse: допускает голые имена, подстановка.где угодно — но нужен пакет.- Предпочитайте
|>в новом коде; читайте%>%повсюду не моргнув глазом. - Пайпы подходят линейным рецептам; ветвящаяся логика заслуживает именованных переменных.
Дальше: пакеты — откуда на самом деле берутся %>%, dplyr и остальные 20 000 расширений R.
Часто задаваемые вопросы
Что означает %>% в R?
%>% — это пайп из magrittr: он берёт стоящее перед ним значение и подаёт его первым аргументом в следующий вызов, так что x %>% f() %>% g() означает g(f(x)). Он приходит из пакета magrittr и загружается автоматически вместе с dplyr и tidyverse — частью базового R он не является.
Нужен ли пакет, чтобы использовать |> в R?
Нет. |> — встроенный пайп, входящий в базовый R начиная с версии 4.1: он работает в обычной сессии R без каких-либо установок. Пакет нужен как раз для %>% (magrittr или что-то, что его реэкспортирует, например dplyr).
В чём разница между |> и %>% в R?
Оба подают предыдущее значение в следующий вызов. |> относится к базовому R, требует явных скобок (x |> sqrt()) и использует подстановку _ только с именованным аргументом. %>% требует magrittr, принимает голые имена функций (x %>% sqrt), а его подстановка . может стоять где угодно и сколько угодно раз. Для обычного случая с первым аргументом поведение идентично.