Использование агрегата для применения нескольких функций по нескольким переменным в одном вызове
У меня есть следующий фрейм данных
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
для выполнения нескольких вычислений в одном вызове?
Ответы
Ответ 1
Вы можете сделать все за один шаг и получить правильную маркировку:
> 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
Это создает фрейм данных с двумя столбцами id и двумя столбцами матрицы:
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) ) )
Ответ 2
Учитывая это в вопросе:
Я мог бы использовать пакет plyr, но мой набор данных довольно большой, и plyr очень медленный (почти непригодный), когда размер набора данных растет.
Затем в 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
(используемого в вопросе и всех трех других ответов) до data.table
см.
этот тест (случаи agg
и agg.x
).
Ответ 3
Вы можете добавить столбец 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
.
Ответ 4
Возможно, вы хотите объединиться?
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
Ответ 5
Используя пакет dplyr
, вы можете достичь этого, используя либо summarise_each
, либо summarise_all
, которые дают вам тот же результат. С помощью этих обобщающих функций вы можете применять другие функции (в данном случае mean
и n()
) к каждому из негрупповых столбцов:
x %>% group_by(id1, id2) %>%
summarise_each(funs(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
Если вы не хотите применять функции ко всем негрупповым столбцам, вы указываете столбцы, к которым они должны применяться, или исключая ненужные с минусом:
# inclusion
x %>% group_by(id1, id2) %>%
summarise_each(funs(mean, n()), val1, val2)
# exclusion
x %>% group_by(id1, id2) %>%
summarise_each(funs(mean, n()), -val2)
Ответ 6
Вы также можете использовать plyr::each()
для ввода нескольких функций:
aggregate(cbind(val1, val2) ~ id1 + id2, data = x, FUN = plyr::each(avg = mean, n = length))