dplyr group_by над элементами двух столбцов - PullRequest
0 голосов
/ 26 февраля 2019

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

df <- data.frame(buyer = c("A","C","B"),
                 seller = c("B","D","E"),
                 amount = c(1,2,3))

Я ищу предпочтительно решение dplyr для достижения следующего.

buyer          seller       amount
  A              B           1
  C              D           2
  B              E           3

должно привести кгрупповое резюме для каждого агента (A, B, C, D, E)

output
agent     total_amount
  A        1
  B        4 #(=1+3)
  C        2
  D        2

Я мог бы group_by для покупателя и продавца, а затем добавить результаты, но это не элегантно и несколько громоздко.

library(dplyr)
res_b <- df %>%
      group_by(buyer) %>%
      summarise(total_amount=sum(amount))
res_s <- df %>%
      group_by(seller) %>%
      summarise(total_amount=sum(amount))

Любая помощь приветствуется.Другие решения (кроме Tidyverse), очевидно, тоже приветствуются.

Редактировать: следовало бы сказать, что мой исходный набор данных составляет около 60 миллионов наблюдений.

Ответы [ 4 ]

0 голосов
/ 27 февраля 2019

Доступ к строкам дважды и группировка по c(buyer, seller):

# setup
library(data.table)
setDT(df)
df[, c("buyer", "seller") := .(as.character(buyer), as.character(seller))] 

# aggregate
df[rep(1:.N, 2), .(total = sum(amount)), by=.(agent = c(df$buyer, df$seller))]

   agent total
1:     A     1
2:     C     2
3:     B     4
4:     D     2
5:     E     3

Материал df$ необходим из-за агрессивного анализа NSE, я думаю.Я не уверен, что by= или keyby= должны быть здесь быстрее.

Сравнительный анализ : я попробовал это с данными zx8 и обнаружил, что примерно в два раза медленнее, чем rbind, еслиЯ переформулирую, чтобы ...

dt_big[, data.table(agent = c(buyer,seller), v = amount)][, sum(v), by=agent]
# 7.4 seconds vs 4.0 for dt_rbind with n = 10^8

Наконец, еще один быстрый, но многословный вариант:

groupingsets(dt_big, 
  by=c("buyer", "seller"), 
  sets = list("buyer", "seller"), 
  j = sum(amount))[is.na(buyer), buyer := seller][, sum(V1), by=buyer])
# 4.2 seconds
0 голосов
/ 27 февраля 2019

Бенчмаркинг

library(bench)

bnch <- 
  press(
    n = 10^c(5, 6, 7, 8),{
      set.seed(1);df_big <- data.frame(buyer = sample(LETTERS, n, replace = TRUE), seller = sample(LETTERS, n, replace = TRUE), amount = sample(1:10, n, replace = TRUE))
      set.seed(1);dt_big <- data.table(buyer = sample(LETTERS, n, replace = TRUE), seller = sample(LETTERS, n, replace = TRUE), amount = sample(1:10, n, replace = TRUE))
      mark(
        dplyr = {
          df_big %>% 
            gather(var, agent, -amount) %>% 
            group_by(agent) %>% 
            summarise(total_amount = sum(amount))}, 
        dt_melt = {
          melt(dt_big, measure.vars = c('buyer', 'seller'), id.vars = 'amount')[
            , .(total_amount = sum(amount)), by = .(agent = value) ][order(agent), ]},
        dt_rbind = {
          rbind(dt_big[ , .(x = sum(amount)), by = .(agent = buyer) ],
                dt_big[ , .(x = sum(amount)), by = .(agent = seller) ])[
                  order(agent), .(total_amount = sum(x)), by = agent]}
        )})

bnch
# # A tibble: 12 x 15
#    expression      n      min     mean   median      max `itr/sec` mem_alloc  n_gc n_itr
#    <chr>       <dbl> <bch:tm> <bch:tm> <bch:tm> <bch:tm>     <dbl> <bch:byt> <dbl> <int>
#  1 dplyr      1.00e5  15.75ms   16.4ms  15.85ms   22.7ms   61.0       6.88MB     0    31
#  2 dt_melt    1.00e5   6.34ms   8.39ms   8.48ms    9.2ms  119.        7.01MB     1    53
#  3 dt_rbind   1.00e5   7.45ms   7.82ms   7.75ms    8.9ms  128.        4.06MB     0    64
#  4 dplyr      1.00e6 149.07ms 159.32ms 160.07ms 168.06ms    6.28     68.68MB     0     4
#  5 dt_melt    1.00e6  49.85ms  58.88ms  60.52ms  62.58ms   17.0      69.34MB     1     7
#  6 dt_rbind   1.00e6  35.73ms  38.05ms  38.61ms  40.01ms   26.3      39.09MB     1    12
#  7 dplyr      1.00e7    1.78s    1.78s    1.78s    1.78s    0.560   686.66MB     2     1
#  8 dt_melt    1.00e7 648.77ms 648.77ms 648.77ms 648.77ms    1.54    692.61MB     1     1
#  9 dt_rbind   1.00e7 389.32ms 390.37ms 390.37ms 391.41ms    2.56    387.54MB     3     2
# 10 dplyr      1.00e8   18.73s   18.73s   18.73s   18.73s    0.0534    6.71GB     3     1
# 11 dt_melt    1.00e8    8.18s    8.18s    8.18s    8.18s    0.122     6.76GB     2     1
# 12 dt_rbind   1.00e8    4.15s    4.15s    4.15s    4.15s    0.241     3.78GB     1     1

ggplot2::autoplot(bnch)

enter image description here

0 голосов
/ 27 февраля 2019

Как вы упомянули "60 million observations", вот еще одно решение, использующее data.table, rbind вместо melt :

library(data.table)

setDT(df)
rbind(df[ , .(x = sum(amount)), by = .(agent = buyer) ],
      df[ , .(x = sum(amount)), by = .(agent = seller) ])[
        , .(total_amount = sum(x)), by = agent]

#    agent total_amount
# 1:     A            1
# 2:     C            2
# 3:     B            4
# 4:     D            2
# 5:     E            3
0 голосов
/ 26 февраля 2019

Мы можем сначала преобразовать в длинный формат и выполнить простое агрегирование, например

library(tidyverse)

df %>% 
 gather(var, agent, -amount) %>% 
 group_by(agent) %>% 
 summarise(total_amount = sum(amount))

, что дает

# A tibble: 5 x 2
  agent   total_amount
  <chr>          <dbl>
1 A                1
2 B                4
3 C                2
4 D                2
5 E                3

Вы можете попробовать data.tableдля большей эффективности.Вот прямой перевод кода tidyverse выше

1012 *
...