Terapkan beberapa fungsi ringkasan pada beberapa variabel dengan grup dalam satu panggilan

92

Saya memiliki bingkai data berikut

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)

Saya ingin menghitung mean dari val1 dan val2 yang dikelompokkan berdasarkan id1 dan id2, dan sekaligus menghitung jumlah baris untuk setiap kombinasi id1-id2. Saya dapat melakukan setiap perhitungan secara terpisah:

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

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

Untuk melakukan kedua kalkulasi dalam satu panggilan, saya mencoba

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

Namun, saya mendapatkan hasil yang kacau bersama dengan peringatan:

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

Saya dapat menggunakan paket plyr, tetapi kumpulan data saya cukup besar dan plyr sangat lambat (hampir tidak dapat digunakan) ketika ukuran kumpulan data bertambah.

Bagaimana cara menggunakan aggregateatau fungsi lain untuk melakukan beberapa kalkulasi dalam satu panggilan?

Brokoli
sumber
Selain yang aggregatedisebutkan dalam jawaban ada juga bydan tapply.
Roman Luštrik

Jawaban:

154

Anda dapat melakukan semuanya dalam satu langkah dan mendapatkan pelabelan yang tepat:

> 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

Ini membuat kerangka data dengan dua kolom id dan dua kolom matriks:

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"

Seperti yang ditunjukkan oleh @ lord.garbage di bawah, ini dapat diubah menjadi kerangka data dengan kolom "sederhana" dengan menggunakan 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

Ini adalah sintaks untuk beberapa variabel di LHS:

aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
IRTFM
sumber
1
Terimakasih banyak. Sebagai catatan tambahan, bagaimana cara mengumpulkan agregat untuk menjumlahkan hanya satu kolom. Jika saya memiliki beberapa kolom numerik, saya tidak ingin menjumlahkan kolom yang tidak saya inginkan. Saya tentu saja bisa membuang kolom setelah agregasi selesai, tetapi siklus CPU sudah akan dihabiskan saat itu.
brokoli
Anda hanya memberikan faktor yang akan dikelompokkan dan kolom yang akan digabungkan. Mungkin menggunakan pengindeksan kolom negatif dalam data atau letakkan kolom yang Anda inginkan di kiri rumus. (Lihat edit.)
IRTFM
2
Saya menemukan bug yang disebutkan pengguna2659402 dalam pembaruannya saat menggunakan RStudio 0.98.1014 pada mesin windows 7. Jika Anda mengeluarkan bingkai data ke konsol seperti yang ditunjukkan, itu tampak normal, namun jika Anda menyimpannya ke d, dan kemudian mencoba mengakses d $ val1.mn, itu mengembalikan NULL. d juga tampak cacat jika Anda menjalankan tampilan (d). Menggunakan kode dalam pembaruan memperbaikinya.
JHowIX
4
Alasan Anda mengalami kesulitan adalah bahwa "vals" dikembalikan sebagai matriks dengan masing-masing dua kolom, bukan sebagai kolom biasa. Coba d$val1[ , ""mn"]lihat strukturnya dengan str.
IRTFM
5
Anda dapat mengikat kembali kolom yang berisi matriks ke dalam bingkai data: agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))dengan menggunakan agg_df <- do.call(data.frame, agg). Lihat juga disini .
lord.garbage
30

Diberikan ini dalam pertanyaan:

Saya dapat menggunakan paket plyr, tetapi kumpulan data saya cukup besar dan plyr sangat lambat (hampir tidak dapat digunakan) ketika ukuran kumpulan data bertambah.

Kemudian di data.table( 1.9.4+) Anda dapat mencoba:

> 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

Untuk membandingkan waktu aggregate(digunakan dalam pertanyaan dan ketiga jawaban lainnya) untuk data.tablemelihat tolok ukur ini ( kasus aggdan agg.x).

Matt Dowle
sumber
12

Anda dapat menambahkan countkolom, menggabungkan dengan sum, lalu menskalakan kembali untuk mendapatkan 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

Ini memiliki keuntungan untuk mempertahankan nama kolom Anda dan membuat satu countkolom.

flodel
sumber
12

Dengan menggunakan dplyrpaket Anda dapat mencapai ini dengan menggunakan summarise_all. Dengan fungsi ringkasan ini Anda dapat menerapkan fungsi lain (dalam kasus ini meandan n()) ke setiap kolom non-pengelompokan:

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

pemberian yang mana:

     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

Jika Anda tidak ingin menerapkan fungsi ke semua kolom non-pengelompokan, Anda menentukan kolom yang harus diterapkan atau dengan mengecualikan yang tidak diinginkan dengan minus menggunakan summarise_at()fungsi:

# 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
sumber
10

Mungkin Anda ingin bergabung ?

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
sumber
4

Anda juga dapat menggunakan plyr::each()untuk memperkenalkan beberapa fungsi:

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

dplyrPilihan lainnya adalah acrossyang merupakan bagian dari versi dev saat ini

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

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

Hasil

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