Группировать по нескольким столбцам в dplyr, используя строковый вектор

157

Я пытаюсь перенести свое понимание plyr в dplyr, но не могу понять, как группировать по нескольким столбцам.

# make data with weird column names that can't be hard coded
data = data.frame(
  asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

# plyr - works
ddply(data, columns, summarize, value=mean(value))

# dplyr - raises error
data %.%
  group_by(columns) %.%
  summarise(Value = mean(value))
#> Error in eval(expr, envir, enclos) : index out of bounds

Чего мне не хватает, чтобы перевести пример plyr в синтаксис dplyr-esque?

Редактировать 2017 : Dplyr был обновлен, поэтому доступно более простое решение. Смотрите текущий выбранный ответ.

sharoz
источник
3
Просто попал сюда, как это было топ Google. Вы можете использовать group_by_теперь объяснил вvignette("nse")
Джеймс Оуэрс
3
@kungfujam: Похоже, что сгруппировать только по первому столбцу, а не по паре столбцов
sharoz
1
Вам нужно использовать .dots. Вот решение, адаптированное из ответа @hadley ниже:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
Джеймс Оуэрс
1
Положите
1
Как кто-то указал в ответе на комментарий, цель состоит в том, чтобы не требовать жестко закодированных имен столбцов.
Шароз

Ответы:

52

Поскольку этот вопрос был опубликован, dplyr добавил версии с определенными областями group_by( документация здесь ). Это позволяет вам использовать те же функции, которые вы использовали бы select, например:

data = data.frame(
    asihckhdoydkhxiydfgfTgdsx = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkghc5cdsvxvyv0ja = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# get the columns we want to average within
columns = names(data)[-3]

library(dplyr)
df1 <- data %>%
  group_by_at(vars(one_of(columns))) %>%
  summarize(Value = mean(value))

#compare plyr for reference
df2 <- plyr::ddply(data, columns, plyr::summarize, value=mean(value))
table(df1 == df2, useNA = 'ifany')
## TRUE 
##  27 

Вывод вашего примера вопроса, как и ожидалось (см. Сравнение с plyr выше и вывод ниже):

# A tibble: 9 x 3
# Groups:   asihckhdoydkhxiydfgfTgdsx [?]
  asihckhdoydkhxiydfgfTgdsx a30mvxigxkghc5cdsvxvyv0ja       Value
                     <fctr>                    <fctr>       <dbl>
1                         A                         A  0.04095002
2                         A                         B  0.24943935
3                         A                         C -0.25783892
4                         B                         A  0.15161805
5                         B                         B  0.27189974
6                         B                         C  0.20858897
7                         C                         A  0.19502221
8                         C                         B  0.56837548
9                         C                         C -0.22682998

Обратите внимание, что, поскольку dplyr::summarizeза один раз удаляется только один слой группировки, в результирующем тибле все еще происходит группировка (которая может иногда застать людей врасплох). Если вы хотите быть абсолютно в безопасности от неожиданного поведения группирования, вы всегда можете добавить %>% ungroupв свой конвейер после подведения итогов.

Empiromancer
источник
обновление для 0.7.0того, чтобы сделать систему цитирования-цитаты доступной с несколькими столбцами?
Елена Чуклина
4
Вы можете также использовать .dotsаргументы group_by()как таковой: data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Поль Ружье
Призыв one_of()сделать что-нибудь здесь? Я думаю, что это избыточно в этом контексте, так как выражение обернуто в вызове vars().
ноу
@Khashir Да, этот ответ все еще работает @knowah Вы правы, one_of()в этом контексте призыв к избыточности
Empiromancer
2
@Sos Чтобы применить функцию по нескольким столбцам , используя selectсинтаксис, увидеть новую acrossфункцию: dplyr.tidyverse.org/reference/across.html В вашем случае, это будет выглядеть примерно такsummarize(across(all_of(c(''value_A", "value_B")), mean))
Empiromancer
102

Чтобы полностью написать код, вот обновление ответа Хэдли с новым синтаксисом:

library(dplyr)

df <-  data.frame(
    asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
    a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
    value = rnorm(100)
)

# Columns you want to group by
grp_cols <- names(df)[-3]

# Convert character vector to list of symbols
dots <- lapply(grp_cols, as.symbol)

# Perform frequency counts
df %>%
    group_by_(.dots=dots) %>%
    summarise(n = n())

вывод:

Source: local data frame [9 x 3]
Groups: asihckhdoydk

  asihckhdoydk a30mvxigxkgh  n
1            A            A 10
2            A            B 10
3            A            C 13
4            B            A 14
5            B            B 10
6            B            C 12
7            C            A  9
8            C            B 12
9            C            C 10
Джеймс Оверс
источник
1
Похоже, что это все еще жесткое кодирование имен столбцов, просто в формуле. Суть вопроса в том, как использовать строки, чтобы не приходилось печатать asihckhdoydk...
Грегор Томас
1
dots <- lapply(names(df)[-3], function(x) as.symbol(x)).dots
Обновили
4
попытка разобраться в этих ответах .dots=была решающим шагом. если кто-то знает, почему это требуется при group_byвызове, можете ли вы отредактировать этот ответ? сейчас это немного непостижимо.
Андрей
12
vignette("nse")указывает, что есть три способа цитирования, которые являются приемлемыми: формула, цитата и символ. Если вы не беспокоитесь о том, из какой среды он будет тянуться, вы, вероятно, можете сойти с рукgroup_by_(.dots=grp_cols)
Ари Б. Фридман
58

Поддержка этого в dplyr в настоящее время довольно слабая, в конце концов я думаю, что синтаксис будет примерно таким:

df %.% group_by(.groups = c("asdfgfTgdsx", "asdfk30v0ja"))

Но этого, вероятно, не будет некоторое время (потому что мне нужно продумать все последствия).

В то же время, вы можете использовать regroup(), который принимает список символов:

library(dplyr)

df <-  data.frame(
  asihckhdoydk = sample(LETTERS[1:3], 100, replace=TRUE),
  a30mvxigxkgh = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

df %.%
  regroup(list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %.%
  summarise(n = n())

Если у вас есть символьный вектор имен столбцов, вы можете преобразовать их в правильную структуру с помощью lapply()и as.symbol():

vars <- setdiff(names(df), "value")
vars2 <- lapply(vars, as.symbol)

df %.% regroup(vars2) %.% summarise(n = n())
Hadley
источник
6
as.symbolрешает это. Спасибо! В случае, если это помогает в разработке: этот сценарий действительно распространен для меня. Агрегируйте числовой результат по каждой комбинации других переменных.
Шароз
очевидно, это работает только для этого конкретного примера, а не для другого.
Пауло Э. Кардосо
3
Первоначально я пометил это как ответ, но обновления dplyr позволяют ответу kungfujam работать.
Шароз
regroupтакже не рекомендуется (по крайней мере, начиная с версии 0.4.3).
Берк У.
27

Строковая спецификация столбцов в dplyrтеперь поддерживается через варианты dplyrфункций с именами, заканчивающимися подчеркиванием. Например, соответствующая group_byфункции есть group_by_функция, которая может принимать строковые аргументы. Эта виньетка подробно описывает синтаксис этих функций.

Следующий фрагмент кода четко решает проблему, которую изначально поставил @sharoz (обратите внимание на необходимость выписать .dotsаргумент):

# Given data and columns from the OP

data %>%
    group_by_(.dots = columns) %>%
    summarise(Value = mean(value))

(Обратите внимание, что dplyr теперь использует %>%оператор и %.%не рекомендуется).

Эдвард
источник
17

Пока dplyr не получит полную поддержку строковых аргументов, возможно, эта суть полезна:

https://gist.github.com/skranz/9681509

Он содержит множество функций-оболочек, таких как s_group_by, s_mutate, s_filter и т. Д., Которые используют строковые аргументы. Вы можете смешивать их с обычными функциями dplyr. Например

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)
Себастьян Кранц
источник
11

Это работает, если вы передаете ему объекты (ну, вы не, но ...), а не как символьный вектор:

df %.%
    group_by(asdfgfTgdsx, asdfk30v0ja) %.%
    summarise(Value = mean(value))

> df %.%
+   group_by(asdfgfTgdsx, asdfk30v0ja) %.%
+   summarise(Value = mean(value))
Source: local data frame [9 x 3]
Groups: asdfgfTgdsx

  asdfgfTgdsx asdfk30v0ja        Value
1           A           C  0.046538002
2           C           B -0.286359899
3           B           A -0.305159419
4           C           A -0.004741504
5           B           B  0.520126476
6           C           C  0.086805492
7           B           C -0.052613078
8           A           A  0.368410146
9           A           B  0.088462212

где dfбыла ваша data.

?group_by говорит:

 ...: variables to group by. All tbls accept variable names, some
      will also accept functons of variables. Duplicated groups
      will be silently dropped.

который я интерпретирую, чтобы обозначать не символьные версии имен, а то, как вы бы на них ссылались foo$bar; barздесь не цитируется Или как вы бы ссылаться на переменные в формуле foo ~ bar.

@Arun также упоминает, что вы можете сделать:

df %.%
    group_by("asdfgfTgdsx", "asdfk30v0ja") %.%
    summarise(Value = mean(value))

Но вы не можете передать то, что не оценено , не является именем переменной в объекте данных.

Я предполагаю, что это связано с внутренними методами, которые Хэдли использует для поиска вещей, которые вы передаете через ...аргумент.

Гэвин Симпсон
источник
1
@Arun Спасибо за это. Я этого не заметил, но это тоже имеет смысл. Я добавил примечание по этому поводу со ссылкой на вас и ваш комментарий.
Гэвин Симпсон
4
К сожалению, я не могу полагаться на жесткое кодирование имен столбцов. Я пытаюсь сделать это без необходимости их указывать.
Шароз
4
data = data.frame(
  my.a = sample(LETTERS[1:3], 100, replace=TRUE),
  my.b = sample(LETTERS[1:3], 100, replace=TRUE),
  value = rnorm(100)
)

group_by(data,newcol=paste(my.a,my.b,sep="_")) %>% summarise(Value=mean(value))
Иордания
источник
4

Один (крошечный) случай, который отсутствует в ответах здесь, который я хотел бы сделать явным, - это когда переменные для группировки динамически генерируются в середине потока в конвейере:

library(wakefield)
df_foo = r_series(rnorm, 10, 1000)
df_foo %>% 
  # 1. create quantized versions of base variables
  mutate_each(
    funs(Quantized = . > 0)
  ) %>% 
  # 2. group_by the indicator variables
  group_by_(
    .dots = grep("Quantized", names(.), value = TRUE)
    ) %>% 
  # 3. summarize the base variables
  summarize_each(
    funs(sum(., na.rm = TRUE)), contains("X_")
  )

Это в основном показывает, как использовать grepв сочетании с group_by_(.dots = ...)для достижения этой цели.

tchakravarty
источник
3

Общий пример использования .dotsаргумента в качестве входного векторного символа для dplyr::group_byфункции:

iris %>% 
    group_by(.dots ="Species") %>% 
    summarise(meanpetallength = mean(Petal.Length))

Или без жестко закодированного имени для группирующей переменной (согласно запросу OP):

iris %>% 
    group_by(.dots = names(iris)[5]) %>% 
    summarise_at("Petal.Length", mean)

На примере ОП:

data %>% 
    group_by(.dots =names(data)[-3]) %>% 
    summarise_at("value", mean)

См. Также виньетка dplyr по программированию, которая объясняет местоимения, квази-цитаты, фразы и тидевал.

Поль Ружье
источник