Gruppieren nach mehreren Spalten in dplyr unter Verwendung der Eingabe eines Zeichenfolgenvektors

157

Ich versuche, mein Verständnis von Plyr in Dplyr zu übertragen, kann aber nicht herausfinden, wie ich nach mehreren Spalten gruppieren soll.

# 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

Was fehlt mir, um das Plyr-Beispiel in eine dplyr-artige Syntax zu übersetzen?

Edit 2017 : Dplyr wurde aktualisiert, sodass eine einfachere Lösung verfügbar ist. Siehe die aktuell ausgewählte Antwort.

sharoz
quelle
3
Ich bin gerade hier angekommen, da es Top-Google war. Sie können group_by_jetzt erklärt invignette("nse")
James Owers
3
@kungfujam: Das scheint nur nach der ersten Spalte zu gruppieren, nicht nach dem
Spaltenpaar
1
Sie müssen verwenden .dots. Hier ist die Lösung, die aus der folgenden Antwort von @hadley übernommen wurde:df %>% group_by_(.dots=list(quote(asihckhdoydk), quote(a30mvxigxkgh))) %>% summarise(n = n())
James Owers
1
Habe den vollständigen Code in eine Antwort unten
eingegeben
1
Wie jemand in einer Antwort auf den Kommentar betonte, besteht das Ziel darin, keine fest codierten Spaltennamen zu benötigen.
Sharoz

Antworten:

52

Da diese Frage gestellt wurde, fügte dplyr Versionen mit Gültigkeitsbereich von group_by( Dokumentation hier ) hinzu. Auf diese Weise können Sie dieselben Funktionen verwenden, mit denen Sie selectFolgendes tun würden:

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 

Die Ausgabe Ihrer Beispielfrage ist wie erwartet (siehe Vergleich mit Plyr oben und Ausgabe unten):

# 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

Beachten Sie, dass, da immer dplyr::summarizenur eine Gruppierungsebene gleichzeitig entfernt wird, in der resultierenden Tabelle immer noch eine Gruppierung stattfindet (die manchmal später die Leute überraschen kann). Wenn Sie vor unerwartetem Gruppierungsverhalten absolut sicher sein möchten, können Sie %>% ungroupIhre Pipeline nach dem Zusammenfassen jederzeit erweitern .

Empiromant
quelle
0.7.0Wird aktualisiert, um das Quote-Unquote-System auch mit mehreren Spalten verfügbar zu machen?
JelenaČuklina
4
Sie können die .dotsArgumente auch group_by()als solche verwenden : data %>% group_by(.dots = columns) %>% summarize(value = mean(value)).
Paul Rougieux
Hat der Anruf one_of()hier etwas zu tun? Ich denke, es ist in diesem Zusammenhang überflüssig, da der Ausdruck in einen Aufruf von eingeschlossen ist vars().
Knowah
@ Khashir ja, diese Antwort funktioniert immer noch @knowah Sie haben Recht, der Anruf an one_of()ist in diesem Zusammenhang überflüssig
Empiromancer
Wie würden Sie diese Lösung anwenden, wenn Sie den Mittelwert mehrerer Spalten wünschen? Angenommen, Sie haben value_Aund value_Bin Ihrer Eingabe, und Sie möchten einen Mittelwert für jede der Spalten basierend auf haben group_by? könntest du es einfach tun ...summarize(Mean_A = value_A, Mean_B = value_B)?
Sos
102

Um den Code vollständig zu schreiben, hier ein Update zu Hadleys Antwort mit der neuen Syntax:

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

Ausgabe:

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
James Owers
quelle
1
Dies scheint immer noch die Spaltennamen fest zu codieren, nur in einer Formel. Der Punkt der Frage ist, wie man Strings verwendet, um nicht tippen zu müssen asihckhdoydk...
Gregor Thomas
1
Habe die Lösung aktualisiert dots <- lapply(names(df)[-3], function(x) as.symbol(x)), um das .dotsArgument zu erstellen
James Owers
4
Der Versuch, diese Antworten zu sortieren, .dots=war der entscheidende Schritt. Können group_bySie diese Antwort bearbeiten, wenn jemand genau weiß, warum dies für den Anruf erforderlich ist ? im Moment ist es ein bisschen unergründlich.
Andrew
12
vignette("nse")gibt an, dass drei Möglichkeiten zum Zitieren akzeptabel sind: Formel, Zitat und Zeichen. Es sei denn , Sie , die Umwelt besorgt sind sie aus ziehen wird, können Sie wahrscheinlich mit wegkommengroup_by_(.dots=grp_cols)
Ari B. Friedman
58

Die Unterstützung dafür in dplyr ist derzeit ziemlich schwach, irgendwann denke ich, dass die Syntax ungefähr so ​​aussehen wird:

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

Aber das wird wahrscheinlich eine Weile nicht da sein (weil ich alle Konsequenzen durchdenken muss).

In der Zwischenzeit können Sie Folgendes verwenden regroup(): Eine Liste mit Symbolen:

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

Wenn Sie ein Zeichen Vektor von Spaltennamen haben, können Sie sie an die richtige Struktur zu konvertieren , lapply()und as.symbol():

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

df %.% regroup(vars2) %.% summarise(n = n())
Hadley
quelle
6
as.symbollöst es. Vielen Dank! Falls es bei der Entwicklung hilft: Dieses Szenario ist für mich sehr verbreitet. Aggregieren Sie ein numerisches Ergebnis über jede Kombination der anderen Variablen.
Sharoz
anscheinend funktioniert dies nur für dieses spezielle Beispiel und kein anderes.
Paulo E. Cardoso
3
Ich habe dies ursprünglich als Antwort markiert, aber durch Aktualisierungen von dplyr kann die Antwort von kungfujam funktionieren.
Sharoz
regroupist ebenfalls veraltet (mindestens ab Version 0.4.3).
Berk U.
27

Die Zeichenfolgenspezifikation von Spalten in dplyrwird jetzt durch Varianten der dplyrFunktionen unterstützt, deren Namen in einem Unterstrich enden. Entsprechend der group_byFunktion gibt es beispielsweise eine group_by_Funktion, die Zeichenfolgenargumente annehmen kann. Diese Vignette beschreibt die Syntax dieser Funktionen im Detail.

Das folgende Snippet löst das Problem, das @sharoz ursprünglich gestellt hat, sauber (beachten Sie, dass das .dotsArgument ausgeschrieben werden muss):

# Given data and columns from the OP

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

(Beachten Sie, dass dplyr jetzt den %>%Operator verwendet und %.%veraltet ist).

Edward
quelle
17

Bis dplyr die volle Unterstützung für Zeichenfolgenargumente hat, ist dieser Kern vielleicht nützlich:

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

Es enthält eine Reihe von Wrapper-Funktionen wie s_group_by, s_mutate, s_filter usw., die Zeichenfolgenargumente verwenden. Sie können sie mit den normalen dplyr-Funktionen mischen. Beispielsweise

cols = c("cyl","gear")
mtcars %.%
  s_group_by(cols) %.%  
  s_summarise("avdisp=mean(disp), max(disp)") %.%
  arrange(avdisp)
Sebastian Kranz
quelle
11

Es funktioniert, wenn Sie die Objekte übergeben (naja, Sie sind es nicht, aber ...) und nicht als Zeichenvektor:

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

wo dfwar Ihr data.

?group_by sagt:

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

was ich so interpretiere, dass es nicht die Zeichenversionen der Namen bedeutet, sondern wie Sie sich darauf beziehen würden foo$bar; barwird hier nicht zitiert. Oder wie Sie auf Variablen in einer Formel verweisen würden : foo ~ bar.

@Arun erwähnt auch, dass Sie tun können:

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

Sie können jedoch nicht etwas übergeben, das nicht bewertet ist und kein Name einer Variablen im Datenobjekt ist.

Ich nehme an, dass dies auf die internen Methoden zurückzuführen ist, mit denen Hadley die Dinge nachschlägt, die Sie über das ...Argument übergeben.

Gavin Simpson
quelle
1
@Arun Danke dafür. Ich hatte das nicht bemerkt, aber es macht auch Sinn. Ich habe diesbezüglich eine Notiz hinzugefügt, in der ich Sie und Ihren Kommentar zitiere.
Gavin Simpson
4
Leider kann ich mich nicht darauf verlassen, die Spaltennamen hart zu codieren. Ich versuche dies zu tun, ohne sie angeben zu müssen.
Sharoz
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))
Jordanien
quelle
4

Ein (winziger) Fall, der in den Antworten fehlt, den ich explizit machen wollte, ist, wenn die Variablen, nach denen gruppiert werden soll, dynamisch in einer Pipeline generiert werden:

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

Dies zeigt im Wesentlichen, wie grepin Verbindung mit verwendet werden group_by_(.dots = ...), um dies zu erreichen.

tchakravarty
quelle
3

Allgemeines Beispiel für die Verwendung des .dotsArguments als Zeichenvektoreingabe für die dplyr::group_byFunktion:

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

Oder ohne einen fest codierten Namen für die Gruppierungsvariable (wie vom OP angefordert):

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

Am Beispiel des OP:

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

Siehe auch die dplyr-Vignette zur Programmierung, in der Pronomen, Quasiquotation, Quosures und Tidyeval erklärt werden.

Paul Rougieux
quelle