Wenden Sie in einem Aufruf mehrere Zusammenfassungsfunktionen auf mehrere Variablen pro Gruppe an

91

Ich habe den folgenden Datenrahmen

x <- read.table(text = "  id1 id2 val1 val2
1   a   x    1    9
2   a   x    2    4
3   a   y    3    5
4   a   y    4    9
5   b   x    1    7
6   b   y    4    4
7   b   x    3    9
8   b   y    2    8", header = TRUE)

Ich möchte den Mittelwert von val1 und val2 berechnen, gruppiert nach id1 und id2, und gleichzeitig die Anzahl der Zeilen für jede Kombination aus id1 und id2 zählen. Ich kann jede Berechnung separat durchführen:

# calculate mean
aggregate(. ~ id1 + id2, data = x, FUN = mean)

# count rows
aggregate(. ~ id1 + id2, data = x, FUN = length)

Um beide Berechnungen in einem Aufruf durchzuführen, habe ich es versucht

do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x))))

Ich erhalte jedoch eine verstümmelte Ausgabe zusammen mit einer Warnung:

#     m   n
# id1 1   2
# id2 1   1
#     1.5 2
#     2   2
#     3.5 2
#     3   2
#     6.5 2
#     8   2
#     7   2
#     6   2
# Warning message:
#   In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( :
#   number of columns of result is not a multiple of vector length (arg 1)

Ich könnte das Plyr-Paket verwenden, aber mein Datensatz ist ziemlich groß und Plyr ist sehr langsam (fast unbrauchbar), wenn die Größe des Datensatzes zunimmt.

Wie kann ich aggregateoder andere Funktionen verwenden, um mehrere Berechnungen in einem Aufruf durchzuführen?

Brokkoli
quelle
Neben den aggregatein den Antworten erwähnten gibt es auch byund tapply.
Roman Luštrik

Antworten:

152

Sie können alles in einem Schritt erledigen und die richtige Kennzeichnung erhalten:

> aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
#   id1 id2 val1.mn val1.n val2.mn val2.n
# 1   a   x     1.5    2.0     6.5    2.0
# 2   b   x     2.0    2.0     8.0    2.0
# 3   a   y     3.5    2.0     7.0    2.0
# 4   b   y     3.0    2.0     6.0    2.0

Dadurch wird ein Datenrahmen mit zwei ID-Spalten und zwei Matrixspalten erstellt:

str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) )
'data.frame':   4 obs. of  4 variables:
 $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"
 $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : NULL
  .. ..$ : chr  "mn" "n"

Wie von @ lord.garbage unten ausgeführt, kann dies mithilfe von in einen Datenrahmen mit "einfachen" Spalten konvertiert werden do.call(data.frame, ...)

str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 
    )
'data.frame':   4 obs. of  6 variables:
 $ id1    : Factor w/ 2 levels "a","b": 1 2 1 2
 $ id2    : Factor w/ 2 levels "x","y": 1 1 2 2
 $ val1.mn: num  1.5 2 3.5 3
 $ val1.n : num  2 2 2 2
 $ val2.mn: num  6.5 8 7 6
 $ val2.n : num  2 2 2 2

Dies ist die Syntax für mehrere Variablen in der LHS:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
IRTFM
quelle
1
Vielen Dank. Wie bekomme ich ein Aggregat, um nur eine Spalte zusammenzufassen? Wenn ich mehrere numerische Spalten habe, möchte ich nicht, dass Spalten summiert werden, die ich nicht möchte. Ich könnte die Spalten natürlich wegwerfen, nachdem die Aggregation abgeschlossen ist, aber die CPU-Zyklen wären dann bereits verbraucht.
Brokkoli
Sie geben nur die zu gruppierenden Faktoren und die zu aggregierenden Spalten an. Verwenden Sie möglicherweise eine negative Spaltenindizierung in Daten oder setzen Sie die gewünschten Spalten in die linke Zeile der Formel. (Siehe Bearbeiten.)
IRTFM
2
Ich bin auf den Fehler gestoßen, den user2659402 in seinem Update bei der Verwendung von RStudio 0.98.1014 auf einem Windows 7-Computer erwähnt hat. Wenn Sie den Datenrahmen wie gezeigt an die Konsole ausgeben, erscheint er normal. Wenn Sie ihn jedoch in d speichern und dann versuchen, auf d $ val1.mn zuzugreifen, wird NULL zurückgegeben. d erscheint auch fehlerhaft, wenn Sie Ansicht (d) ausführen. Die Verwendung des Codes im Update hat das Problem behoben.
JHowIX
4
Der Grund, warum Sie Schwierigkeiten haben, ist, dass die "vals" als Matrizen mit jeweils zwei Spalten und nicht als normale Spalten zurückgegeben werden. Versuchen d$val1[ , ""mn"]Sie, die Struktur mit zu betrachten str.
IRTFM
5
Sie können die Spalten, die Matrizen enthalten, wieder in den Datenrahmen einbinden: agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))mithilfe von agg_df <- do.call(data.frame, agg). Siehe auch hier .
lord.garbage
30

Angesichts dessen in der Frage:

Ich könnte das Plyr-Paket verwenden, aber mein Datensatz ist ziemlich groß und Plyr ist sehr langsam (fast unbrauchbar), wenn die Größe des Datensatzes zunimmt.

Dann könnten Sie in data.table( 1.9.4+) versuchen:

> DT
   id1 id2 val1 val2
1:   a   x    1    9
2:   a   x    2    4
3:   a   y    3    5
4:   a   y    4    9
5:   b   x    1    7
6:   b   y    4    4
7:   b   x    3    9
8:   b   y    2    8

> DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)]   # simplest
   id1 id2  V1  V2 N
1:   a   x 1.5 6.5 2
2:   a   y 3.5 7.0 2
3:   b   x 2.0 8.0 2
4:   b   y 3.0 6.0 2

> DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)]  # named
   id1 id2 val1.m val2.m count
1:   a   x    1.5    6.5     2
2:   a   y    3.5    7.0     2
3:   b   x    2.0    8.0     2
4:   b   y    3.0    6.0     2

> DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)]   # mean over all columns
   id1 id2 val1 val2 count
1:   a   x  1.5  6.5     2
2:   a   y  3.5  7.0     2
3:   b   x  2.0  8.0     2
4:   b   y  3.0  6.0     2

Für Zeitvergleiche aggregate(in Frage und alle 3 anderen Antworten verwendet), data.tableum diesen Benchmark (die aggund agg.xFälle) zu sehen.

Matt Dowle
quelle
12

Sie können eine countSpalte hinzufügen , mit aggregieren sumund dann verkleinern, um Folgendes zu erhalten mean:

x$count <- 1
agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum)
agg
#   id1 id2 val1 val2 count
# 1   a   x    3   13     2
# 2   b   x    4   16     2
# 3   a   y    7   14     2
# 4   b   y    6   12     2

agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count
agg
#   id1 id2 val1 val2 count
# 1   a   x  1.5  6.5     2
# 2   b   x  2.0  8.0     2
# 3   a   y  3.5  7.0     2
# 4   b   y  3.0  6.0     2

Es hat den Vorteil, dass Sie Ihre Spaltennamen beibehalten und eine einzelne countSpalte erstellen .

flodel
quelle
12

Mit dem dplyrPaket können Sie dies erreichen, indem Sie verwenden summarise_all. Mit dieser Zusammenfassungsfunktion können Sie andere Funktionen (in diesem Fall meanund n()) auf jede der nicht gruppierenden Spalten anwenden :

x %>%
  group_by(id1, id2) %>%
  summarise_all(funs(mean, n()))

was gibt:

     id1    id2 val1_mean val2_mean val1_n val2_n
1      a      x       1.5       6.5      2      2
2      a      y       3.5       7.0      2      2
3      b      x       2.0       8.0      2      2
4      b      y       3.0       6.0      2      2

Wenn Sie die Funktion (en) nicht auf alle nicht gruppierten Spalten anwenden möchten, geben Sie die Spalten an, auf die sie angewendet werden sollen, oder indem Sie die nicht gewünschten mit einem Minus ausschließen, indem Sie die summarise_at()Funktion verwenden:

# inclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(val1, val2), funs(mean, n()))

# exclusion
x %>%
  group_by(id1, id2) %>%
  summarise_at(vars(-val2), funs(mean, n()))
Jaap
quelle
10

Vielleicht möchten Sie zusammenführen ?

x.mean <- aggregate(. ~ id1+id2, p, mean)
x.len  <- aggregate(. ~ id1+id2, p, length)

merge(x.mean, x.len, by = c("id1", "id2"))

  id1 id2 val1.x val2.x val1.y val2.y
1   a   x    1.5    6.5      2      2
2   a   y    3.5    7.0      2      2
3   b   x    2.0    8.0      2      2
4   b   y    3.0    6.0      2      2
neilfws
quelle
4

Sie können das auch verwenden plyr::each(), um mehrere Funktionen einzuführen:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = plyr::each(avg = mean, n = length))
heschmat
quelle
1

Eine weitere dplyrOption ist acrossTeil der aktuellen Entwicklerversion

#devtools::install_github("tidyverse/dplyr")
library(dplyr)

x %>% 
  group_by(id1, id2) %>% 
  summarise(across(starts_with("val"), list(mean = mean, n = length)))

Ergebnis

# A tibble: 4 x 4
# Groups:   id1 [2]
  id1   id2   mean$val1 $val2 n$val1 $val2
  <fct> <fct>     <dbl> <dbl>  <int> <int>
1 a     x           1.5   6.5      2     2
2 a     y           3.5   7        2     2
3 b     x           2     8        2     2
4 b     y           3     6        2     2

packageVersion("dplyr")
[1]0.8.99.9000
Markus
quelle