Grupowanie przez wiele kolumn w dplyr, przy użyciu wprowadzania wektora łańcuchowego

Próbuję przenieść moje zrozumienie plyr do dplyr, ale nie mogę wymyślić, jak grupować po wielu kolumnach.

# 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

Czego mi brakuje, aby przetłumaczyć przykład plyr na składnię dplyr?

Edit 2017: Dplyr został zaktualizowany, więc dostępne jest prostsze rozwiązanie. Zobacz aktualnie wybraną odpowiedź.

 131
Author: sharoz, 2014-01-18

8 answers

Ponieważ to pytanie zostało opublikowane, dplyr dodał wersje scoped group_by (dokumentacja tutaj ). Pozwala to na korzystanie z tych samych funkcji, z których można korzystać z select, Jak tak:

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 

Wynik z przykładowego pytania jest zgodny z oczekiwaniami (Zobacz porównanie do plyr powyżej i wynik poniżej):

# 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

Zauważ, że ponieważ dplyr::summarize usuwa tylko jedną warstwę grupowania na raz, nadal masz pewne grupowanie w wynikowym tibble (które może czasami złapać ludzi przez zaskoczenie później w dół linii). Jeśli chcesz być całkowicie bezpieczny przed nieoczekiwanym zachowaniem grupowania, zawsze możesz dodać %>% ungroup do potoku po podsumowaniu.

 30
Author: Empiromancer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-17 19:27:55

Aby napisać kod w całości, oto aktualizacja odpowiedzi Hadleya z nową składnią:

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

Wyjście:

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
 97
Author: kungfujam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-28 18:08:05

Wsparcie dla tego w dplyr jest obecnie dość słabe, w końcu myślę, że składnia będzie coś w stylu:

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

Ale tego pewnie nie będzie przez jakiś czas (bo muszę przemyśleć wszystkie konsekwencje).

W międzyczasie możesz użyć regroup(), która pobiera listę symboli:

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

Jeśli masz wektor znaków nazw kolumn, możesz przekonwertować je na właściwą strukturę za pomocą lapply() i as.symbol():

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

df %.% regroup(vars2) %.% summarise(n = n())
 55
Author: hadley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-20 20:42:30

String Specyfikacja kolumn w dplyr jest teraz obsługiwana przez warianty funkcji dplyr z nazwami kończącymi się podkreśleniem. Na przykład, odpowiadająca funkcji group_by istnieje funkcja group_by_, która może przyjmować argumenty łańcuchowe. ta winieta szczegółowo opisuje składnię tych funkcji.

Poniższy fragment rozwiązuje problem, który pierwotnie postawił @sharoz (zwróć uwagę na konieczność wypisania argumentu .dots):

# Given data and columns from the OP

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

(zauważ, że dplyr używa teraz operatora %>% i %.% jest przestarzały).

 23
Author: edward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-19 15:59:56

Dopóki dplyr nie ma pełnego wsparcia dla argumentów łańcuchowych, być może ten gist jest przydatny:

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

Zawiera kilka funkcji wrappera, takich jak s_group_by, s_mutate, s_filter, itp., które używają argumentów łańcuchowych. Można je mieszać z normalnymi funkcjami dplyr. Na przykład

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)
 16
Author: Sebastian Kranz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-21 08:04:42

To działa, jeśli przekażesz go obiektom (cóż, nie jesteś, ale...) zamiast jako wektor postaci:

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

Gdzie df było twoje data.

?group_by says:

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

Które interpretuję nie jako wersje znaków imion, ale jak byś się do nich odnosił w foo$bar; bar nie jest tu cytowany. Albo jak odnosisz się do zmiennych we wzorze: foo ~ bar.

@Arun wspomina również, że możesz zrobić:

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

Ale nie można przekazać czegoś, co unevaluated {[22] } nie jest nazwą zmiennej w obiekcie data.

Zakładam, że jest to spowodowane wewnętrznymi metodami, których używa Hadley do wyszukiwania rzeczy, które przekazujesz za pomocą argumentu ....

 10
Author: Gavin Simpson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 20:00:44
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))
 3
Author: Jordan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-24 16:53:22

Jeden (malutki) przypadek, którego brakuje w odpowiedziach tutaj, że chciałem wyraźnie powiedzieć, jest wtedy, gdy zmienne do grupy przez są generowane dynamicznie w połowie strumienia w potoku:

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_")
  )

To w zasadzie pokazuje, jak używać grep w połączeniu z group_by_(.dots = ...), aby to osiągnąć.

 2
Author: tchakravarty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-07 07:40:12