2015-06-11 3 views
6

У меня есть кадр данных, который выглядит следующим образом:dplyr: максимальное значение в группе, исключая значение в каждой строке?

> 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 

Я знаю, как добавить столбец с максимальным x значения для каждой группы 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 

Но то, что я хотел бы, чтобы get - максимальное значение x для каждой группы g, , за исключением значения x в каждой строке.

Для данного примера, желаемый результат будет выглядеть следующим образом:

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 

Я думал, что я мог бы быть в состоянии использовать row_number(), чтобы удалить отдельные элементы и взять максимум из того, что осталось, но удар предупредительных сообщений и получил неправильный -Inf выход:

> 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 

Что является наиболее {читаемый, краткий, эффективный} способа получить этот вывод в dplyr? Любое понимание того, почему моя попытка с использованием row_number() не работает, также будет высоко оценена. Спасибо за помощь.

+0

Является ли этот код: обобщать (group_by (ДФ, г), max.x = тах (х))? –

+0

Спасибо, @Shenglin Chen, но это не соответствует желаемому результату в приведенном выше примере. Это дает мне максимальное значение «x» для каждой группы (возвращает data_frame с 3 строками). Но я хочу, чтобы data_frame с тем же количеством строк, что и входная таблица, где значение в строке 'r' дает максимальное значение' x' в группе 'g', исключая строку' r'. См. «Желаемый результат» выше для конкретного примера. – Eric

ответ

4

Вы можете попробовать:

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

Что дает:

#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

Я попытался решения до сих пор на benchma гк:

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 
) 

@ data.table решение Аруна является самым быстрым:

# 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

Интересная проблема. Вот один из способов использования data.table:

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

Идея заключается в том, чтобы порядок по столбцам x и на этих показателях, мы группа по g. Поскольку у нас есть упорядоченные индексы, для первых строк .N-1 максимальное значение - это значение в .N. А для .N-й строки это значение в .N-1-й строке.

.N - специальная переменная, которая содержит количество наблюдений в каждой группе.

Я оставлю это вам и/или экспертам dplyr, чтобы перевести это (или ответить другим путем).

+0

Спасибо за версию data.table, @Arun. Я думаю, что по-моему это похоже на мое лучшее решение dplyr (которое я только что опубликовал), хотя я не знаю своих данных. Таблицу достаточно, чтобы сказать, идентичны ли они. – Eric

+2

Эрик, аналогичная идея, но не реализация. Вы вызываете 'sort()' для каждой группы, а затем есть 'ifelse()' ... – Arun

2

Это лучшее, что я придумал до сих пор. Не уверен, есть ли лучший способ.

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

Вы можете упростить: 'group_by (df, g)%>% mutate (max = ifelse (x ==) max (x), sort (x, убывающий = TRUE) [2], max (x))) ' –

0

Другой способ с функционалом:

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 

Напишем функцию, называемую max_exclude, что делает операцию, вы описываете.

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

Он работает с base R тоже:

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

Вот урок дети, остерегайтесь для петель!

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

Это кажется довольно дорогим. Не уверен, что это может масштабироваться для большего набора данных. –

+1

@ StevenBeaupré это может быть. Это была еще одна идея. –

+1

@ StevenBeaupré Я тестировал скорость. Смущающе медленно. –

Смежные вопросы