2015-06-11 68 views
6

Tôi có một khung dữ liệu mà trông như sau:dplyr: giá trị tối đa trong một nhóm, không bao gồm giá trị trong mỗi hàng?

> df <- data_frame(g = c('A', 'A', 'B', 'B', 'B', 'C'), x = c(7, 3, 5, 9, 2, 4)) 
> df 
Source: local data frame [6 x 2] 

    g x 
1 A 7 
2 A 3 
3 B 5 
4 B 9 
5 B 2 
6 C 4 

tôi biết làm thế nào để thêm một cột với giá trị tối đa x đối với từng nhóm g:

> df %>% group_by(g) %>% mutate(x_max = max(x)) 
Source: local data frame [6 x 3] 
Groups: g 

    g x x_max 
1 A 7  7 
2 A 3  7 
3 B 5  9 
4 B 9  9 
5 B 2  9 
6 C 4  4 

Nhưng những gì tôi muốn là để nhận được là giá trị tối đa x cho mỗi nhóm g, không bao gồm giá trị x trong mỗi hàng.

Đối với ví dụ được đưa ra, các đầu ra mong muốn sẽ trông như thế này:

Source: local data frame [6 x 3] 
Groups: g 

    g x x_max x_max_exclude 
1 A 7  7    3 
2 A 3  7    7 
3 B 5  9    9 
4 B 9  9    5 
5 B 2  9    9 
6 C 4  4   NA 

tôi nghĩ rằng tôi có thể có thể sử dụng row_number() để loại bỏ các yếu tố đặc biệt và lấy tối đa là những gì còn lại, nhưng nhấn thông điệp cảnh báo và có sai -Inf đầu ra:

> df %>% group_by(g) %>% mutate(x_max = max(x), r = row_number(), x_max_exclude = max(x[-r])) 
Source: local data frame [6 x 5] 
Groups: g 

    g x x_max r x_max_exclude 
1 A 7  7 1   -Inf 
2 A 3  7 2   -Inf 
3 B 5  9 1   -Inf 
4 B 9  9 2   -Inf 
5 B 2  9 3   -Inf 
6 C 4  4 1   -Inf 
Warning messages: 
1: In max(c(4, 9, 2)[-1:3]) : 
    no non-missing arguments to max; returning -Inf 
2: In max(c(4, 9, 2)[-1:3]) : 
    no non-missing arguments to max; returning -Inf 
3: In max(c(4, 9, 2)[-1:3]) : 
    no non-missing arguments to max; returning -Inf 

các {có thể đọc được, ngắn gọn, hiệu quả} cách mà hầu hết để có được kết quả này trong dplyr là gì? Bất kỳ hiểu biết nào về lý do tại sao nỗ lực của tôi sử dụng row_number() không hoạt động cũng sẽ được đánh giá cao. Cảm ơn đã giúp đỡ.

+0

là mã này: tóm tắt (group_by (df, g), max.x = max (x))? –

+0

Cảm ơn, @Shenglin Chen, nhưng điều đó không khớp với kết quả mong muốn trong ví dụ trên. Điều đó mang lại cho tôi giá trị 'x' tối đa cho mỗi nhóm (trả về một data_frame với 3 hàng). Nhưng những gì tôi muốn là một data_frame với cùng số hàng như bảng đầu vào, trong đó giá trị tại hàng 'r' cho giá trị' x' tối đa trong nhóm 'g', không bao gồm hàng' r'. Xem "đầu ra mong muốn" ở trên cho một ví dụ cụ thể. – Eric

Trả lời

4

Bạn có thể thử:

df %>% 
    group_by(g) %>% 
    arrange(desc(x)) %>% 
    mutate(max = ifelse(x == max(x), x[2], max(x))) 

Mà cho:

#Source: local data frame [6 x 3] 
#Groups: g 
# 
# g x max 
#1 A 7 3 
#2 A 3 7 
#3 B 9 5 
#4 B 5 9 
#5 B 2 9 
#6 C 4 NA 

Benchmark

Tôi đã thử các giải pháp cho đến nay trên benchma rk:

df <- data.frame(g = sample(LETTERS, 10e5, replace = TRUE), 
       x = sample(1:10, 10e5, replace = TRUE)) 

library(microbenchmark) 

mbm <- microbenchmark(
    steven = df %>% 
    group_by(g) %>% 
    arrange(desc(x)) %>% 
    mutate(max = ifelse(x == max(x), x[2], max(x))), 
    eric = df %>% 
    group_by(g) %>% 
    mutate(x_max = max(x), 
      x_max2 = sort(x, decreasing = TRUE)[2], 
      x_max_exclude = ifelse(x == x_max, x_max2, x_max)) %>% 
    select(-x_max2), 
    arun = setDT(df)[order(x), x_max_exclude := c(rep(x[.N], .N-1L), x[.N-1L]), by=g], 
    times = 50 
) 

@ giải pháp data.table Arun là nhanh nhất:

# Unit: milliseconds 
# expr  min  lq  mean median  uq  max neval cld 
# steven 158.58083 163.82669 197.28946 210.54179 212.1517 260.1448 50 b 
# eric 223.37877 228.98313 262.01623 274.74702 277.1431 284.5170 50 c 
# arun 44.48639 46.17961 54.65824 47.74142 48.9884 102.3830 50 a 

enter image description here

3

Sự cố thú vị. Dưới đây là một trong những cách sử dụng data.table:

require(data.table) 
setDT(df)[order(x), x_max_exclude := c(rep(x[.N], .N-1L), x[.N-1L]), by=g] 

Ý tưởng là để trật tự theo cột x và trên những chỉ số, chúng tôi nhóm bởi g. Vì chúng tôi đã có chỉ số đặt hàng, cho các hàng .N-1 đầu tiên, giá trị tối đa là giá trị tại .N. Và đối với hàng thứ, đó là giá trị tại hàng số .N-1.

.N là một biến đặc biệt chứa số lượng quan sát trong mỗi nhóm.

Tôi sẽ để nó cho bạn và/hoặc dplyr các chuyên gia dịch này (hoặc trả lời bằng cách tiếp cận khác).

+0

Cảm ơn phiên bản data.table, @Arun. Tôi nghĩ rằng đó là tương tự trong tinh thần để giải pháp dplyr tốt nhất của tôi cho đến nay (mà tôi cũng chỉ cần đăng), mặc dù tôi không biết data.table của tôi cũng đủ để nói cho dù họ là giống hệt nhau. – Eric

+2

Eric, ý tưởng tương tự, nhưng không phải là thực hiện. Bạn đang gọi 'sort()' cho mỗi nhóm, và sau đó có 'ifelse()' ... – Arun

2

Đây là điều tốt nhất tôi đã đưa ra cho đến nay. Không chắc chắn nếu có một cách tốt hơn.

df %>% 
    group_by(g) %>% 
    mutate(x_max = max(x), 
     x_max2 = sort(x, decreasing = TRUE)[2], 
     x_max_exclude = ifelse(x == x_max, x_max2, x_max)) %>% 
    select(-x_max2) 
+0

Bạn có thể đơn giản hóa thành: 'group_by (df, g)%>% biến (max = ifelse (x ==) max (x), sắp xếp (x, giảm = TRUE) [2], max (x))) ' –

0

Một cách khác với một chức năng:

df %>% group_by(g) %>% mutate(x_max_exclude = max_exclude(x)) 
Source: local data frame [6 x 3] 
Groups: g 

    g x x_max_exclude 
1 A 7    3 
2 A 3    7 
3 B 5    9 
4 B 9    5 
5 B 2    9 
6 C 4   NA 

Chúng tôi viết một hàm gọi là max_exclude mà không hoạt động mà bạn mô tả.

max_exclude <- function(v) { 
    res <- c() 
    for(i in seq_along(v)) { 
    res[i] <- suppressWarnings(max(v[-i])) 
    } 
    res <- ifelse(!is.finite(res), NA, res) 
    as.numeric(res) 
} 

Nó hoạt động với base R quá:

df$x_max_exclude <- with(df, ave(x, g, FUN=max_exclude)) 
Source: local data frame [6 x 3] 

    g x x_max_exclude 
1 A 7    3 
2 A 3    7 
3 B 5    9 
4 B 9    5 
5 B 2    9 
6 C 4   NA 

Benchmark

Dưới đây là một con bài học, hãy cẩn thận của cho vòng!

big.df <- data.frame(g=rep(LETTERS[1:4], each=1e3), x=sample(10, 4e3, replace=T)) 


microbenchmark(
    plafort_dplyr = big.df %>% group_by(g) %>% mutate(x_max_exclude = max_exclude(x)), 
    plafort_ave = big.df$x_max_exclude <- with(big.df, ave(x, g, FUN=max_exclude)), 
    StevenB = (big.df %>% 
    group_by(g) %>% 
    mutate(max = ifelse(row_number(desc(x)) == 1, x[row_number(desc(x)) == 2], max(x))) 
    ), 
    Eric = df %>% 
    group_by(g) %>% 
    mutate(x_max = max(x), 
      x_max2 = sort(x, decreasing = TRUE)[2], 
      x_max_exclude = ifelse(x == x_max, x_max2, x_max)) %>% 
    select(-x_max2), 
    Arun = setDT(df)[order(x), x_max_exclude := c(rep(x[.N], .N-1L), x[.N-1L]), by=g] 
) 

Unit: milliseconds 
      expr  min  lq  mean median  uq  max neval 
plafort_dplyr 75.219042 85.207442 89.247409 88.203225 90.627663 179.553166 100 
    plafort_ave 75.907798 84.604180 87.136122 86.961251 89.431884 104.884294 100 
     StevenB 4.436973 4.699226 5.207548 4.931484 5.364242 11.893306 100 
      Eric 7.233057 8.034092 8.921904 8.414720 9.060488 15.946281 100 
      Arun 1.789097 2.037235 2.410915 2.226988 2.423638 9.326272 100 
+0

Điều này có vẻ khá tốn kém. Bạn không chắc chắn điều này có thể mở rộng cho tập dữ liệu lớn hơn. –

+1

@ StevenBeaupré nó có thể được. Đó chỉ là một ý tưởng khác. –

+1

@ StevenBeaupré Tôi đã kiểm tra tốc độ. Xấu hổ chậm. –

Các vấn đề liên quan