У меня есть df с оборотом для каждой подгруппы из основной. Теперь я хочу получить две основные группы с наибольшим оборотом.

df <- data.frame(
  grp = gl(5, 5, labels = c("A", "B", "C", "D", "E")),
  sub_grp = gl(5, 1),
  turnover = rnorm(25, mean = 100, sd = 15))
 > df
   grp sub_grp  turnover
1    A       1  98.14430
2    A       2 107.90811
3    A       3 103.93973
4    A       4  95.78222
5    A       5  63.19635
6    B       1  97.85688
7    B       2  92.65572
8    B       3  86.02872
9    B       4 101.88177
10   B       5 120.66959
11   C       1 125.93533
12   C       2  98.49771
13   C       3  77.28770
14   C       4 101.44822
15   C       5 107.08171
16   D       1  77.73252
17   D       2 107.49374
18   D       3  87.46436
19   D       4 101.49984
20   D       5  99.13047
21   E       1  91.48636
22   E       2 115.63716
23   E       3  99.34567
24   E       4 104.65408
25   E       5 121.41820

Я знаю, как получить две основные группы с наибольшим оборотом, но не знаю, как сохранить мои подгруппы и оборот по-прежнему разделенными на подгруппы.

df %>%
  group_by(grp) %>%
  summarise(total.turnover = sum(turnover)) %>%
  top_n(n = 2)

     grp total.turnover
  (fctr)          (dbl)
1      C       510.2507
2      E       532.5415

Результат хочу из этого примера.

   grp sub_grp  turnover
1    C       1 125.93533
2    C       2  98.49771
3    C       3  77.28770
4    C       4 101.44822
5    C       5 107.08171
6    E       1  91.48636
7    E       2 115.63716
8    E       3  99.34567
9    E       4 104.65408
10   E       5 121.41820
1
Tomas Ericsson 24 Апр 2017 в 13:19

2 ответа

Лучший ответ

Вот несколько разных подходов с dplyr.

Возвращение к исходному объекту

df %>%
    group_by(grp) %>%
    summarise(total.turnover = sum(turnover)) %>%
    top_n(n = 2) %>% 
    inner_join(df, by = "grp") %>% 
    select(grp, sub_grp, turnover)
# # A tibble: 10 × 3
#       grp sub_grp  turnover
#    <fctr>  <fctr>     <dbl>
# 1       A       1  91.59287
# 2       A       2  96.54734
# 3       A       3 123.38062
# 4       A       4 101.05763
# 5       A       5 101.93932
# 6       C       1 118.36123
# 7       C       2 105.39721
# 8       C       3 106.01157
# 9       C       4 101.66024
# 10      C       5  91.66238

Использование оконной функции (dense_rank)

df %>%
    group_by(grp) %>%
    mutate(total.turnover = sum(turnover)) %>%
    ungroup() %>%
    filter(dense_rank(desc(total.turnover)) < 3) %>%
    select(grp, sub_grp, turnover)
# # A tibble: 10 × 3
#       grp sub_grp  turnover
#    <fctr>  <fctr>     <dbl>
# 1       A       1  91.59287
# 2       A       2  96.54734
# 3       A       3 123.38062
# 4       A       4 101.05763
# 5       A       5 101.93932
# 6       C       1 118.36123
# 7       C       2 105.39721
# 8       C       3 106.01157
# 9       C       4 101.66024
# 10      C       5  91.66238

Использование data.table (аналогично подходу оконной функции dplyr)

library(data.table)
dt <- data.table(df)

dt[,total.turnover := sum(turnover), by = .(grp)
    ][,rank := frank(-total.turnover, ties.method = "dense")
    ][rank < 3, .(grp, sub_grp, turnover)]
#     grp sub_grp  turnover
#  1:   A       1  91.59287
#  2:   A       2  96.54734
#  3:   A       3 123.38062
#  4:   A       4 101.05763
#  5:   A       5 101.93932
#  6:   C       1 118.36123
#  7:   C       2 105.39721
#  8:   C       3 106.01157
#  9:   C       4 101.66024
# 10:   C       5  91.66238

library(dplyr)
set.seed(123)

df <- data.frame(
    grp = gl(5, 5, labels = c("A", "B", "C", "D", "E")),
    sub_grp = gl(5, 1),
    turnover = rnorm(25, mean = 100, sd = 15)
)
3
nrussell 24 Апр 2017 в 10:38

Один из вариантов - dplyr , где мы используем filter в обобщенном выходном объекте.

df %>% 
  filter(grp %in% df1$grp)

Где 'df1' - суммарный объект вывода


Или если бы мы хотели в одной цепочке

df %>% 
  group_by(grp) %>% 
  summarise(val = sum(turnover)) %>%
  top_n(2)  %>%
  semi_join(df, .) 
#   grp sub_grp  turnover
#1    C       1 125.93533
#2    C       2  98.49771
#3    C       3  77.28770
#4    C       4 101.44822
#5    C       5 107.08171
#6    E       1  91.48636
#7    E       2 115.63716
#8    E       3  99.34567
#9    E       4 104.65408
#10   E       5 121.41820

Или другой вариант в одну строку - data.table

library(data.table)
setDT(df)[grp %in% df[, sum(turnover), grp][order(-V1), head(grp, 2)]]
#      grp sub_grp  turnover
# 1:   C       1 125.93533
# 2:   C       2  98.49771
# 3:   C       3  77.28770
# 4:   C       4 101.44822
# 5:   C       5 107.08171
# 6:   E       1  91.48636
# 7:   E       2 115.63716
# 8:   E       3  99.34567
# 9:   E       4 104.65408
#10:   E       5 121.41820

Или мы можем сделать это легко с base R

subset(df, grp %in% names(tail(sort(xtabs(turnover~grp , df)),2)))
#   grp sub_grp  turnover
#11   C       1 125.93533
#12   C       2  98.49771
#13   C       3  77.28770
#14   C       4 101.44822
#15   C       5 107.08171
#21   E       1  91.48636
#22   E       2 115.63716
#23   E       3  99.34567
#24   E       4 104.65408
#25   E       5 121.41820

Данные

df <- structure(list(grp = c("A", "A", "A", "A", "A", "B", "B", "B", 
"B", "B", "C", "C", "C", "C", "C", "D", "D", "D", "D", "D", "E", 
"E", "E", "E", "E"), sub_grp = c(1L, 2L, 3L, 4L, 5L, 1L, 2L, 
3L, 4L, 5L, 1L, 2L, 3L, 4L, 5L, 1L, 2L, 3L, 4L, 5L, 1L, 2L, 3L, 
4L, 5L), turnover = c(98.1443, 107.90811, 103.93973, 95.78222, 
63.19635, 97.85688, 92.65572, 86.02872, 101.88177, 120.66959, 
125.93533, 98.49771, 77.2877, 101.44822, 107.08171, 77.73252, 
107.49374, 87.46436, 101.49984, 99.13047, 91.48636, 115.63716, 
99.34567, 104.65408, 121.4182)), .Names = c("grp", "sub_grp", 
"turnover"), 
 class = "data.frame", row.names = c(NA, -25L), 
 index = structure(integer(0), "`__grp`" = integer(0)))
1
akrun 24 Апр 2017 в 12:06