Мне было интересно, есть ли более элегантное решение, чем мой подход ниже. У меня есть фрейм данных, и я хотел бы получить среднее значение для каждого столбца на основе верхних значений из каждой группы.
set.seed(123)
df <- data.frame(
A = sample(c("A","B","C"), 20, replace=TRUE),
B = rnorm(60, 5, 2),
C = rnorm(60, 0, 2),
D = rnorm(60, 10, 2))
library("dplyr")
top <- 5
top.B <- df %>% group_by(A) %>% top_n(n=top, wt=B) %>% summarize(top.A=mean(B))
top.C <- df %>% group_by(A) %>% top_n(n=-top, wt=C) %>% summarize(top.C=mean(C))
top.D <- df %>% group_by(A) %>% top_n(n=top, wt=D) %>% summarize(top.D=mean(D))
top5 <- merge(top.B, top.C, by = "A")
top5 <- merge(top5, top.D, by = "A")
Я могу сделать это, объединив кадры данных. И результат выглядит так:
A top.A top.C top.D
1 A 7.663078 -1.986632 12.62946
2 B 6.926882 -2.186245 13.18132
3 C 7.548887 -2.255001 12.15677
Интересно, можно ли это сделать без создания этого нового фрейма данных. Обратите внимание, что в столбце C среднее значение берется из нижних значений или верхних с использованием убывающего порядка.
Спасибо.
Вот один из вариантов с map
library(tidyverse)
map(names(df)[-1], ~
df %>%
select(A, .x) %>%
group_by(A) %>%
top_n(n = top, wt = !! rlang::sym(.x)) %>%
summarise(!! str_c('top.', .x) := mean(!! rlang::sym(.x)))) %>%
reduce(inner_join, by = 'A')
# A tibble: 3 x 4
# A top.B top.C top.D
# <fct> <dbl> <dbl> <dbl>
#1 A 6.10 3.20 12.8
#2 B 7.94 2.17 12.3
#3 C 8.19 1.18 12.9
Или используя frank
from data.table
с summarise_all
(аналогично варианту в посте @tmfmnk)
library(data.table)
df %>%
group_by(A) %>%
summarise_all(list( ~ mean(.[frank(-.) <= 5])))
# A tibble: 3 x 4
# A B C D
# <fct> <dbl> <dbl> <dbl>
#1 A 6.10 3.20 12.8
#2 B 7.94 2.17 12.3
#3 C 8.19 1.18 12.9
Или с помощью order
df %>%
group_by(A) %>%
summarise_all(list(~ mean(.x[order(-.)][1:5])))
# A tibble: 3 x 4
# A B C D
# <fct> <dbl> <dbl> <dbl>
#1 A 6.10 3.20 12.8
#2 B 7.94 2.17 12.3
#3 C 8.19 1.18 12.9
Почему-то я получаю другие значения, чем вы, но этот подход должен работать
library(dplyr)
df %>%
gather(key, value, -A) %>%
group_by(A, key) %>%
top_n(5, value) %>%
summarise(m = mean(value)) %>%
ungroup() %>%
spread(key, m)
# A tibble: 3 x 4
A B C D
<fct> <dbl> <dbl> <dbl>
1 A 6.10 3.20 12.8
2 B 7.94 2.17 12.3
3 C 8.19 1.18 12.9
Вот данные:
set.seed(123)
df <- data.frame(
A = sample(c("A","B","C"), 20, replace=TRUE),
B = rnorm(60, 5, 2),
C = rnorm(60, 0, 2),
D = rnorm(60, 10, 2))
Одна dplyr
возможность может быть:
df %>%
group_by(A) %>%
summarise_all(list(~ mean(.[dense_rank(desc(.)) <= 5])))
A B C D
<fct> <dbl> <dbl> <dbl>
1 A 7.66 2.16 12.6
2 B 6.93 1.79 13.2
3 C 7.55 2.23 12.2
Если вам нужны нижние 5 наблюдений для столбца C:
df %>%
group_by(A) %>%
summarise(B = mean(B[dense_rank(desc(B)) <= 5]),
C = mean(C[dense_rank(C) <= 5]),
D = mean(D[dense_rank(desc(D)) <= 5]))
A B C D
<fct> <dbl> <dbl> <dbl>
1 A 7.66 -1.99 12.6
2 B 6.93 -2.19 13.2
3 C 7.55 -2.26 12.2
Однако он теряет свою элегантность, пожалуйста, смотрите обновленный пост.
Это намного лучше, чем создавать фреймы данных и объединять их. Это работает. Спасибо.
Если вы считаете ответ полезным, примите его :)
Вариант data.table
:
Чтобы получить среднее из топ-5
get_mean_top5 <- function(x) -mean(sort(-x, partial = 1:5)[1:5])
df[, lapply(.SD, get_mean_top5), keyby = A, .SDcols = c("B", "D")]
# A B D
# 1: A 6.097723 12.75887
# 2: B 7.942064 12.33379
# 3: C 8.190137 12.93201
Среднее, если последние 5:
get_mean_bot5 <- function(x) mean(sort(x, partial = 1:5)[1:5])
df[, lapply(.SD, get_mean_bot5), keyby = A, .SDcols = c("C")]
Чтобы получить полную таблицу за один шаг:
setDT(df, key = "A")
df[, lapply(.SD, get_mean_top5), keyby = A, .SDcols = c("B", "D")
][df[, lapply(.SD, get_mean_bot5), keyby = A, .SDcols = c("C")]]
Это было здорово, но мне нужны 5 верхних значений для столбцов B и D, а нижние для столбца C. Вот почему я использовал -top, чтобы получить обратный порядок:
r top_n(n=-top, wt=C)