Ответ 1
Я думаю, что Хэдли будет лучшим человеком, который тебе объяснит, но я дам ему шанс.
%.%
- это двоичный оператор, называемый цепным оператором. В R
вы можете в значительной степени определить любой собственный двоичный оператор со специальным символом %
. Из того, что мне кажется, мы в значительной степени используем его, чтобы упростить "цепочечные" синтаксисы (например, x+y
, намного лучше, чем sum(x,y)
). Вы можете сделать действительно классный материал с ними, см. Этот классный пример здесь.
Какова цель %.%
в dplyr
? Чтобы облегчить вам выражение себя, уменьшая разрыв между тем, что вы хотите сделать, и тем, как вы его выражаете.
Взяв пример из введение в dplyr, пусть предположим, что вы хотите группировать полеты по годам, месяцам и дням, выберите эти переменные плюс задержки в прибытии и отъезде, суммируйте их по среднему значению и затем фильтруйте только те задержки за 30. Если бы не было %.%
, вам нужно было бы написать вот так:
filter(
summarise(
select(
group_by(hflights, Year, Month, DayofMonth),
Year:DayofMonth, ArrDelay, DepDelay
),
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE)
),
arr > 30 | dep > 30
)
Он выполняет эту работу. Но довольно сложно выразить себя и прочитать. Теперь вы можете написать то же самое с более дружественным синтаксисом, используя оператор цепочки %.%
:
hflights %.%
group_by(Year, Month, DayofMonth) %.%
select(Year:DayofMonth, ArrDelay, DepDelay) %.%
summarise(
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE)
) %.%
filter(arr > 30 | dep > 30)
Легче писать и читать!
И как это работает?
Посмотрим на определения. Сначала для %.%
:
function (x, y)
{
chain_q(list(substitute(x), substitute(y)), env = parent.frame())
}
Он использует другую функцию, называемую chain_q
. Поэтому давайте посмотрим на это:
function (calls, env = parent.frame())
{
if (length(calls) == 0)
return()
if (length(calls) == 1)
return(eval(calls[[1]], env))
e <- new.env(parent = env)
e$`__prev` <- eval(calls[[1]], env)
for (call in calls[-1]) {
new_call <- as.call(c(call[[1]], quote(`__prev`), as.list(call[-1])))
e$`__prev` <- eval(new_call, e)
}
e$`__prev`
}
Что это делает?
Чтобы упростить ситуацию, позвольте предположить, что вы вызвали: group_by(hflights,Year, Month, DayofMonth) %.% select(Year:DayofMonth, ArrDelay, DepDelay)
.
Ваши вызовы x
и y
будут тогда group_by(hflights,Year, Month, DayofMonth)
и select(Year:DayofMonth, ArrDelay, DepDelay)
. Таким образом, функция создает новую среду под названием e
(e <- new.env(parent = env)
) и сохраняет объект с именем __prev
с оценкой первого вызова (e$'__prev' <- eval(calls[[1]], env)
). Затем для каждого другого вызова он создает другой вызов, первым аргументом которого является предыдущий вызов - это __prev
- в нашем случае это будет select('__prev', Year:DayofMonth, ArrDelay, DepDelay)
- поэтому он "цепочки" вызовов внутри цикла.
Поскольку вы можете использовать бинарные операторы друг над другом, вы действительно можете использовать этот синтаксис для очень сложных операций с выражением.