Menu

Оператор пайпа в R: %>% и встроенный |>

Что означает %>%, как работает встроенный пайп базового R |>, правила и подстановочные символы каждого из них и какой выбирать в новом коде.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Какую проблему решают пайпы

Пайп берёт стоящее перед ним значение и подаёт его в следующий вызов функции — 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), а его подстановка . может стоять где угодно и сколько угодно раз. Для обычного случая с первым аргументом поведение идентично.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ