Applica diverse funzioni di riepilogo su più variabili per gruppo in una chiamata


91

Ho il seguente data frame

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)

Voglio calcolare la media di val1 e val2 raggruppate per id1 e id2 e contare simultaneamente il numero di righe per ciascuna combinazione id1-id2. Posso eseguire ogni calcolo separatamente:

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

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

Per fare entrambi i calcoli in una chiamata, ho provato

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

Tuttavia, ottengo un output confuso insieme a un avviso:

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

Potrei usare il pacchetto plyr, ma il mio set di dati è abbastanza grande e plyr è molto lento (quasi inutilizzabile) quando la dimensione del set di dati aumenta.

Come posso utilizzare aggregateo altre funzioni per eseguire più calcoli in una chiamata?


Oltre a aggregatemenzionati nelle risposte ci sono anche bye tapply.
Roman Luštrik

Risposte:


152

Puoi fare tutto in un unico passaggio e ottenere un'etichettatura adeguata:

> 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

Questo crea un dataframe con due colonne id e due colonne matrice:

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"

Come sottolineato da @ lord.garbage di seguito, questo può essere convertito in un dataframe con colonne "semplici" utilizzando 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

Questa è la sintassi per più variabili su LHS:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )

1
Grazie mille Come nota a margine, come ottengo aggregato per riassumere solo una colonna. Se ho diverse colonne numeriche, non voglio che sommino le colonne che non voglio. Potrei ovviamente buttare via le colonne dopo che l'aggregazione è terminata, ma i cicli della CPU sarebbero già stati spesi allora.
broccoli

Gli dai solo i fattori su cui raggruppare e le colonne da aggregare. Eventualmente utilizzare l'indicizzazione negativa delle colonne nei dati o inserire le colonne desiderate nell'LHS della formula. (Vedi modifica)
IRTFM

2
Ho riscontrato il bug menzionato da user2659402 nel suo aggiornamento durante l'utilizzo di RStudio 0.98.1014 su una macchina Windows 7. Se invii il data frame alla console come mostrato, sembra normale, tuttavia se lo salvi in ​​d, e poi provi ad accedere a d $ val1.mn, restituisce NULL. d appare anche in formato non corretto se si esegue view (d). L'utilizzo del codice nell'aggiornamento lo ha risolto.
JHowIX

4
Il motivo per cui hai difficoltà è che i "vals" vengono restituiti come matrici con due colonne ciascuna, piuttosto che come colonne ordinarie. Prova a d$val1[ , ""mn"]guardare la struttura con str.
IRTFM

5
È possibile associare le colonne che contengono matrici nel frame di dati: agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))utilizzando agg_df <- do.call(data.frame, agg). Vedi anche qui .
lord

30

Dato questo nella domanda:

Potrei usare il pacchetto plyr, ma il mio set di dati è abbastanza grande e plyr è molto lento (quasi inutilizzabile) quando la dimensione del set di dati aumenta.

Quindi in data.table( 1.9.4+) potresti provare:

> 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

Per il confronto dei tempi aggregate(utilizzato nella domanda e tutte e 3 le altre risposte) per data.tablevedere questo benchmark (i casi agge agg.x).


12

Puoi aggiungere una countcolonna, aggregare con sum, quindi ridimensionare per ottenere 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

Ha il vantaggio di preservare i nomi delle colonne e di creare una singola countcolonna.


12

Usando il dplyrpacchetto puoi ottenere questo risultato usando summarise_all. Con questa funzione di riepilogo puoi applicare altre funzioni (in questo caso meane n()) a ciascuna delle colonne non raggruppanti:

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

che dà:

     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

Se non si desidera applicare le funzioni a tutte le colonne non raggruppate, specificare le colonne a cui devono essere applicate o escludendo quelle non desiderate con un segno meno utilizzando la summarise_at()funzione:

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

10

Forse vuoi unire ?

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

4

Puoi anche usare il plyr::each()per introdurre più funzioni:

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

1

Un'altra dplyropzione è acrossche fa parte dell'attuale versione di sviluppo

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

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

Risultato

# 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.