Згрупуйте по декількох стовпцях у 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 було оновлено, тож є більш просте рішення. Дивіться поточно обрану відповідь.


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
Поставте повний код у відповідь нижче
James Owers

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до свого конвеєра після того, як підсумуєте.


чи оновлюється, щоб 0.7.0система цитата цитування цитати також була доступна з кількома стовпцями?
JelenaČuklina

4
Ви можете також використовувати .dotsаргументи group_by()як такої: data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Пол Рудьо

Чи є заклик one_of()зробити щось тут? Я вважаю, що це зайве в цьому контексті, оскільки вираз обгортається закликом до vars().
knowah

@Khashir так, ця відповідь все ще працює @knowah Ви маєте рацію, заклик до one_of()цього зайвий у цьому контексті
Empiromancer

1
@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())

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 = ...)для досягнення.


3

Загальний приклад використання .dotsаргументу як символьного введення dplyr::group_byфункції функції:

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

Або без жорстко кодованої назви змінної угруповання (на запитання ОП):

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

На прикладі ОП:

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

Дивіться також віньетку dplyr про програмування, яка пояснює займенники, квазіквітацію, quosures та tidyeval.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.