Когда использовать функцию "Do" в dplyr

Я узнал, что функция Do используется, когда вы хотите применить функцию к каждой группе.

например, если я хочу вывести верхние 2 строки из категорий "A", "C" и "I" переменной Index, можно использовать следующий синтаксис.

t <- mydata %>% filter(Index %in% c("A", "C", "I")) %>% group_by(Index) %>% do(head(.,2))

Я понимаю, что после группировки по индексу функция Do используется для вычисления head (., 2) для каждой группы.

Однако в некоторых случаях Do не используется вообще. Например, Чтобы вычислить среднее значение переменной Y2014, сгруппированное по переменной Index, я подумал, что следует использовать следующий код.

t <- mydata %>% group_by(Index) %>% do(summarise(Mean_2014 = mean(Y2014)))

однако выше синтаксис возвращает ошибку

Error in mean(Y2014) : object 'Y2014' not found

Но если я удалю Do из синтаксиса, он вернет то, что я точно хотел.

t <- mydata %>% group_by(Index) %>% summarise(Mean_2014 = mean(Y2014))

Я действительно запутался в использовании функции Do в dplyr. Мне кажется непоследовательным. Когда следует использовать и не использовать функцию Do? Почему я должен использовать Do в первом случае, а не во втором случае?

Ответ 1

В комментариях к этому вопросу обсуждается, что во многих случаях вы можете найти альтернативу в dplyr или связанных пакетах, которые избегают использования do, и примеры в вопросе относятся к этому типу; однако, чтобы ответить на вопрос напрямую, а не на альтернативы:

Различия между использованием do и не использованием

В контексте кадров данных ключевыми различиями между использованием do и без использования do являются:

  • Автоматическая установка точки. Код внутри do не будет автоматически вставлен в первый аргумент. Например, вместо кода do(summarise(Mean_2014 = mean(Y2014))) в вопросе нужно было бы написать do(summarise(., Mean_2014 = mean(Y2014))) с точкой, поскольку точка не будет автоматически вставлена. Это является следствием того, что do является правой частью функции %>%, а не summarize. Хотя это важно понимать так, чтобы мы вставляли точку, когда это было необходимо, если целью было просто избежать автоматической вставки точки в первый аргумент, мы могли бы попеременно использовать скобки для получения этого эффекта: whatever %>% { myfun(arg1, arg2) } также не будет автоматически вставлять точку в качестве первый аргумент вызова myfun.

  • respecting group_by Только функции, специально написанные для соблюдения group_by, сделают это. Здесь есть два вопроса. (1) Только функции, специально написанные для оценки group_by, будут выполняться один раз для каждой группы. mutate, summarize и do являются примерами функций, которые запускаются один раз для каждой группы (есть и другие). (2) Даже если функция запускается один раз для каждой группы, возникает вопрос о том, как обрабатывается точка. Мы фокусируемся на двух случаях (не полный список): (i) если do не используется, то, если точка используется в вызове функции в выражении для аргумента, она будет ссылаться на весь вход, игнорируя group_by. Предположительно это является следствием правил замены магритт-точек и ничего не знает о group_by. С другой стороны, (ii) в пределах do точка всегда относится к строкам текущей группы. Например, сравните вывод этих двух и обратите внимание, что точка относится к 3 строкам в первом случае, когда используется do, и все 6 строк во втором, где это не так. Это несмотря на то, что summarize соответствует group_by тем, что он выполняется один раз для каждой группы.

    BOD$g <- c(1, 1, 1, 2, 2, 2)
    BOD %>% group_by(g) %>% do(summarize(., nr = nrow(.)))
    ## # A tibble: 2 x 2
    ## # Groups: g [2]
    ##       g    nr
    ##   <dbl> <int>
    ## 1  1.00     3
    ## 2  2.00     3
    
    BOD %>% group_by(g) %>% summarize(nr = nrow(.))
    ## # A tibble: 2 x 2
    ##       g    nr
    ##   <dbl> <int>
    ## 1  1.00     6
    ## 2  2.00     6
    

Подробнее см. ?do.

Код из вопроса

Теперь мы рассмотрим код в вопросе. Поскольку mydata никогда не определялся в вопросе, мы используем первую строку кода ниже, чтобы определить ее для облегчения конкретных примеров.

mydata <- data.frame(Index = rep(c("A", "C", "I"), each = 3), Y2014 = 1)

mydata %>% 
       filter(Index %in% c("A", "C", "I")) %>% 
       group_by(Index) %>% 
       do(head(., 2))

## # A tibble: 6 x 2
## # Groups: Index [3]
##   Index  Y2014
##   <fctr> <dbl>
## 1 A       1.00
## 2 A       1.00
## 3 C       1.00
## 4 C       1.00
## 5 I       1.00
## 6 I       1.00

В приведенном выше коде создается 2 строки для каждой из 3 групп, дающих 6 строк. Если бы мы опустили do, тогда он проигнорировал бы group_by и произвел только две строки с точкой, считающейся целыми 9 строками ввода, а не только каждой группой за раз. (В этом конкретном случае dplyr предоставляет свою собственную альтернативу head, которая позволяет избежать этих проблем, но для иллюстрации общей точки мы придерживаемся кода в вопросе.)

Следующий код из вопроса генерирует ошибку, потому что вставка do не выполняется, и поэтому должен быть первый аргумент суммирования, т.е. ввод кадра данных:

mydata %>% 
       group_by(Index) %>% 
       do(summarise(Mean_2014 = mean(Y2014)))
## Error in mean(Y2014) : object 'Y2014' not found

Если мы удалим do в приведенном выше коде, как в последней строке кода в вопросе, то он работает, поскольку выполняется вставка точки. Альтернативно, если мы добавим точку do(summarise(., Mean_2014 = mean(Y2014))), она также будет работать, хотя do действительно кажется излишним в этом случае, поскольку summarize уже относится к group_by, поэтому нет необходимости обертывать его в do.

mydata %>% 
       group_by(Index) %>% 
       summarise(Mean_2014 = mean(Y2014))

## # A tibble: 3 x 2
##   Index  Mean_2014
##   <fctr>     <dbl>
## 1 A           1.00
## 2 C           1.00
## 3 I           1.00