У меня есть следующий фрейм данных
x <- read.table(text = " id1 id2 val1 val2
1 a x 1 9
2 a x 2 4
3 a y 3 5
4 a y 4 9
5 b x 1 7
6 b y 4 4
7 b x 3 9
8 b y 2 8", header = TRUE)
Я хочу вычислить среднее значение val1 и val2, сгруппированных по id1 и id2, и одновременно подсчитать количество строк для каждой комбинации id1-id2. Я могу выполнить каждый расчет отдельно:
# calculate mean
aggregate(. ~ id1 + id2, data = x, FUN = mean)
# count rows
aggregate(. ~ id1 + id2, data = x, FUN = length)
Чтобы выполнить оба расчета за один вызов, я попробовал
do.call("rbind", aggregate(. ~ id1 + id2, data = x, FUN = function(x) data.frame(m = mean(x), n = length(x))))
Однако я получаю искаженный вывод вместе с предупреждением:
# m n
# id1 1 2
# id2 1 1
# 1.5 2
# 2 2
# 3.5 2
# 3 2
# 6.5 2
# 8 2
# 7 2
# 6 2
# Warning message:
# In rbind(id1 = c(1L, 2L, 1L, 2L), id2 = c(1L, 1L, 2L, 2L), val1 = list( :
# number of columns of result is not a multiple of vector length (arg 1)
Я мог бы использовать пакет plyr, но мой набор данных довольно велик, а plyr работает очень медленно (почти непригодно), когда размер набора данных растет.
Как я могу использовать aggregate
или другие функции для выполнения нескольких вычислений за один вызов?
aggregate
упомянутых в ответах есть такжеby
иtapply
.Ответы:
Вы можете сделать все за один шаг и получить надлежащую маркировку:
> aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) # id1 id2 val1.mn val1.n val2.mn val2.n # 1 a x 1.5 2.0 6.5 2.0 # 2 b x 2.0 2.0 8.0 2.0 # 3 a y 3.5 2.0 7.0 2.0 # 4 b y 3.0 2.0 6.0 2.0
Это создает фрейм данных с двумя столбцами идентификаторов и двумя столбцами матрицы:
str( aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) 'data.frame': 4 obs. of 4 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1: num [1:4, 1:2] 1.5 2 3.5 3 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n" $ val2: num [1:4, 1:2] 6.5 8 7 6 2 2 2 2 ..- attr(*, "dimnames")=List of 2 .. ..$ : NULL .. ..$ : chr "mn" "n"
Как указано ниже в @ lord.garbage, его можно преобразовать в фрейм данных с "простыми" столбцами, используя
do.call(data.frame, ...)
str( do.call(data.frame, aggregate(. ~ id1+id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) ) ) ) 'data.frame': 4 obs. of 6 variables: $ id1 : Factor w/ 2 levels "a","b": 1 2 1 2 $ id2 : Factor w/ 2 levels "x","y": 1 1 2 2 $ val1.mn: num 1.5 2 3.5 3 $ val1.n : num 2 2 2 2 $ val2.mn: num 6.5 8 7 6 $ val2.n : num 2 2 2 2
Это синтаксис для нескольких переменных на LHS:
aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x) ) )
источник
d$val1[ , ""mn"]
и посмотрите на структуру с помощьюstr
.agg <- aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = function(x) c(mn = mean(x), n = length(x)))
с помощьюagg_df <- do.call(data.frame, agg)
. Смотрите также здесь .Учитывая это в вопросе:
Затем в
data.table
(1.9.4+
) вы можете попробовать:> DT id1 id2 val1 val2 1: a x 1 9 2: a x 2 4 3: a y 3 5 4: a y 4 9 5: b x 1 7 6: b y 4 4 7: b x 3 9 8: b y 2 8 > DT[ , .(mean(val1), mean(val2), .N), by = .(id1, id2)] # simplest id1 id2 V1 V2 N 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2 > DT[ , .(val1.m = mean(val1), val2.m = mean(val2), count = .N), by = .(id1, id2)] # named id1 id2 val1.m val2.m count 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2 > DT[ , c(lapply(.SD, mean), count = .N), by = .(id1, id2)] # mean over all columns id1 id2 val1 val2 count 1: a x 1.5 6.5 2 2: a y 3.5 7.0 2 3: b x 2.0 8.0 2 4: b y 3.0 6.0 2
Для сравнения моментов времени
aggregate
(используемый в вопросе и все другие ответы 3) , чтобыdata.table
увидеть этот эталон (вagg
иagg.x
случаях).источник
Вы можете добавить
count
столбец, агрегировать с нимsum
, а затем уменьшить масштаб, чтобы получитьmean
:x$count <- 1 agg <- aggregate(. ~ id1 + id2, data = x,FUN = sum) agg # id1 id2 val1 val2 count # 1 a x 3 13 2 # 2 b x 4 16 2 # 3 a y 7 14 2 # 4 b y 6 12 2 agg[c("val1", "val2")] <- agg[c("val1", "val2")] / agg$count agg # id1 id2 val1 val2 count # 1 a x 1.5 6.5 2 # 2 b x 2.0 8.0 2 # 3 a y 3.5 7.0 2 # 4 b y 3.0 6.0 2
Он имеет то преимущество, что сохраняет имена столбцов и создает один
count
столбец.источник
Используя
dplyr
пакет, вы можете добиться этого, используяsummarise_all
. С помощью этой функции суммирования вы можете применять другие функции (в данном случаеmean
иn()
) к каждому из столбцов без группировки:который дает:
id1 id2 val1_mean val2_mean val1_n val2_n 1 a x 1.5 6.5 2 2 2 a y 3.5 7.0 2 2 3 b x 2.0 8.0 2 2 4 b y 3.0 6.0 2 2
Если вы не хотите применять функцию (ы) ко всем столбцам, не являющимся группами, вы указываете столбцы, к которым они должны быть применены, или исключая нежелательные с минусом, используя
summarise_at()
функцию:# inclusion x %>% group_by(id1, id2) %>% summarise_at(vars(val1, val2), funs(mean, n())) # exclusion x %>% group_by(id1, id2) %>% summarise_at(vars(-val2), funs(mean, n()))
источник
Возможно, вы хотите слиться ?
x.mean <- aggregate(. ~ id1+id2, p, mean) x.len <- aggregate(. ~ id1+id2, p, length) merge(x.mean, x.len, by = c("id1", "id2")) id1 id2 val1.x val2.x val1.y val2.y 1 a x 1.5 6.5 2 2 2 a y 3.5 7.0 2 2 3 b x 2.0 8.0 2 2 4 b y 3.0 6.0 2 2
источник
Вы также можете использовать
plyr::each()
для введения нескольких функций:источник
Другой
dplyr
вариант -across
это часть текущей версии разработчика.#devtools::install_github("tidyverse/dplyr") library(dplyr) x %>% group_by(id1, id2) %>% summarise(across(starts_with("val"), list(mean = mean, n = length)))
Результат
# A tibble: 4 x 4 # Groups: id1 [2] id1 id2 mean$val1 $val2 n$val1 $val2 <fct> <fct> <dbl> <dbl> <int> <int> 1 a x 1.5 6.5 2 2 2 a y 3.5 7 2 2 3 b x 2 8 2 2 4 b y 3 6 2 2
packageVersion("dplyr") [1] ‘0.8.99.9000’
источник