Áp dụng một số hàm tóm tắt trên một số biến theo nhóm trong một cuộc gọi


91

Tôi có khung dữ liệu sau

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)

Tôi muốn tính giá trị trung bình của val1 và val2 được nhóm theo id1 và id2, đồng thời đếm số hàng cho mỗi kết hợp id1-id2. Tôi có thể thực hiện từng phép tính riêng biệt:

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

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

Để thực hiện cả hai phép tính trong một lần gọi, tôi đã thử

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

Tuy nhiên, tôi nhận được một đầu ra bị cắt xén cùng với một cảnh báo:

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

Tôi có thể sử dụng gói plyr, nhưng tập dữ liệu của tôi khá lớn và plyr rất chậm (gần như không sử dụng được) khi kích thước của tập dữ liệu lớn lên.

Làm cách nào để sử dụng aggregatehoặc các hàm khác để thực hiện một số phép tính trong một cuộc gọi?


Bên cạnh aggregateđược đề cập trong các câu trả lời còn có bytapply.
Roman Luštrik

Câu trả lời:


152

Bạn có thể thực hiện tất cả trong một bước và được dán nhãn thích hợp:

> 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

Điều này tạo ra một khung dữ liệu với hai cột id và hai cột ma trận:

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"

Như được chỉ ra bởi @ords.garbage bên dưới, điều này có thể được chuyển đổi thành khung dữ liệu với các cột "đơn giản" bằng cách sử dụng 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

Đây là cú pháp cho nhiều biến trên LHS:

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

1
Cảm ơn nhiều. Xin lưu ý thêm, làm cách nào để lấy tổng hợp để chỉ tổng hợp một cột. Nếu tôi có một số cột số, tôi không muốn nó tổng các cột mà tôi không muốn. Tất nhiên, tôi có thể vứt bỏ các cột sau khi tổng hợp xong, nhưng các chu kỳ CPU sẽ được sử dụng sau đó.
bông cải xanh

Bạn chỉ cung cấp cho nó các yếu tố được nhóm vào và các cột được tổng hợp. Có thể sử dụng lập chỉ mục cột phủ định trong dữ liệu hoặc đặt các cột bạn muốn vào LHS của công thức. (Xem chỉnh sửa.)
IRTFM

2
Tôi đã gặp phải lỗi mà user2659402 đã đề cập trong bản cập nhật của anh ấy khi sử dụng RStudio 0.98.1014 trên máy tính chạy windows 7. Nếu bạn xuất khung dữ liệu ra bảng điều khiển như được hiển thị, nó xuất hiện bình thường, tuy nhiên nếu bạn lưu nó vào d, rồi cố gắng truy cập d $ val1.mn, nó sẽ trả về NULL. d cũng xuất hiện không đúng định dạng nếu bạn chạy chế độ xem (d). Sử dụng mã trong bản cập nhật đã sửa nó.
JHowIX

4
Lý do bạn gặp khó khăn là "vals" đang được trả về dưới dạng ma trận với hai cột mỗi cột, thay vì các cột thông thường. Hãy thử d$val1[ , ""mn"]và xem cấu trúc với str.
IRTFM

5
Bạn có thể liên kết các cột chứa ma trận trở lại khung dữ liệu: agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))bằng cách sử dụng agg_df <- do.call(data.frame, agg). Xem thêm tại đây .
Lord.garbage

30

Đưa ra điều này trong câu hỏi:

Tôi có thể sử dụng gói plyr, nhưng tập dữ liệu của tôi khá lớn và plyr rất chậm (gần như không sử dụng được) khi kích thước của tập dữ liệu lớn lên.

Sau đó, trong data.table( 1.9.4+) bạn có thể thử:

> 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

Để so sánh thời gian aggregate(được sử dụng trong câu hỏi và tất cả 3 câu trả lời khác) để data.tablexem điểm chuẩn này ( trường hợp aggagg.x).


12

Bạn có thể thêm một countcột, tổng hợp sum, sau đó chia tỷ lệ lại để có được 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

Nó có ưu điểm là giữ nguyên tên cột của bạn và tạo một countcột duy nhất .


12

Sử dụng dplyrgói bạn có thể đạt được điều này bằng cách sử dụng summarise_all. Với hàm tóm tắt này, bạn có thể áp dụng các hàm khác (trong trường hợp này là meann()) cho từng cột không phân nhóm:

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

mang lại:

     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

Nếu bạn không muốn áp dụng (các) hàm cho tất cả các cột không phân nhóm, bạn chỉ định các cột mà chúng sẽ được áp dụng hoặc bằng cách loại trừ cột không muốn bằng dấu trừ bằng cách sử dụng summarise_at()hàm:

# 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

Có lẽ bạn muốn hợp nhất ?

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

Bạn cũng có thể sử dụng plyr::each()để giới thiệu nhiều chức năng:

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

1

Một dplyrtùy chọn khác acrosslà một phần của phiên bản nhà phát triển hiện tại

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

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

Kết quả

# 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.