Объединение агрегированных значений обратно в исходный фрейм данных
Один из шаблонов проектирования, которые я использую снова и снова, выполняет "группу за" или "разделять, применять, комбинировать (SAC)" в кадре данных, а затем присоединять агрегированные данные к исходным данным. Это полезно, например, при расчете каждого отклонения графства от среднего значения состояния в кадре данных со многими штатами и округами. Редко мой агрегатный расчет - простое среднее, но это хороший пример. Я часто решаю эту проблему следующим образом:
require(plyr)
set.seed(1)
## set up some data
group1 <- rep(1:3, 4)
group2 <- sample(c("A","B","C"), 12, rep=TRUE)
values <- rnorm(12)
df <- data.frame(group1, group2, values)
## got some data, so let aggregate
group1Mean <- ddply( df, "group1", function(x)
data.frame( meanValue = mean(x$values) ) )
df <- merge( df, group1Mean )
df
Что создает хорошие агрегированные данные, такие как:
> df
group1 group2 values meanValue
1 1 A 0.48743 -0.121033
2 1 A -0.04493 -0.121033
3 1 C -0.62124 -0.121033
4 1 C -0.30539 -0.121033
5 2 A 1.51178 0.004804
6 2 B 0.73832 0.004804
7 2 A -0.01619 0.004804
8 2 B -2.21470 0.004804
9 3 B 1.12493 0.758598
10 3 C 0.38984 0.758598
11 3 B 0.57578 0.758598
12 3 A 0.94384 0.758598
Это работает, но есть ли альтернативные способы сделать это, которые улучшают читаемость, производительность и т.д.?
Ответы
Ответ 1
Одна строка кода выполняет трюк:
new <- ddply( df, "group1", transform, numcolwise(mean))
new
group1 group2 values meanValue
1 1 A 0.48742905 -0.121033381
2 1 A -0.04493361 -0.121033381
3 1 C -0.62124058 -0.121033381
4 1 C -0.30538839 -0.121033381
5 2 A 1.51178117 0.004803931
6 2 B 0.73832471 0.004803931
7 2 A -0.01619026 0.004803931
8 2 B -2.21469989 0.004803931
9 3 B 1.12493092 0.758597929
10 3 C 0.38984324 0.758597929
11 3 B 0.57578135 0.758597929
12 3 A 0.94383621 0.758597929
identical(df, new)
[1] TRUE
Ответ 2
Я думаю, что ave()
более полезен здесь, чем вызов plyr, который вы показываете (я недостаточно знаком с plyr, чтобы узнать, можете ли вы делать то, что хотите, с plyr напрямую или нет, я был бы удивлен, если вы сможете " t!) или другие базовые R альтернативы (aggregate()
, tapply()
).:
> with(df, ave(values, group1, FUN = mean))
[1] -0.121033381 0.004803931 0.758597929 -0.121033381 0.004803931
[6] 0.758597929 -0.121033381 0.004803931 0.758597929 -0.121033381
[11] 0.004803931 0.758597929
Вы можете использовать within()
или transform()
, чтобы вставить этот результат непосредственно в df
:
> df2 <- within(df, meanValue <- ave(values, group1, FUN = mean))
> head(df2)
group1 group2 values meanValue
1 1 A 0.4874291 -0.121033381
2 2 B 0.7383247 0.004803931
3 3 B 0.5757814 0.758597929
4 1 C -0.3053884 -0.121033381
5 2 A 1.5117812 0.004803931
6 3 C 0.3898432 0.758597929
> df3 <- transform(df, meanValue = ave(values, group1, FUN = mean))
> all.equal(df2,df3)
[1] TRUE
И если важно упорядочение:
> head(df2[order(df2$group1, df2$group2), ])
group1 group2 values meanValue
1 1 A 0.48742905 -0.121033381
10 1 A -0.04493361 -0.121033381
4 1 C -0.30538839 -0.121033381
7 1 C -0.62124058 -0.121033381
5 2 A 1.51178117 0.004803931
11 2 A -0.01619026 0.004803931
Ответ 3
С точки зрения производительности вы можете выполнять такую же операцию, используя data.table
пакет, который встроен в агрегацию и очень быстро благодаря индексам и реализации на основе C. Например, данный df
уже существует из вашего примера:
library("data.table")
dt<-as.data.table(df)
setkey(dt,group1)
dt<-dt[,list(group2,values,meanValue=mean(values)),by=group1]
dt
group1 group2 values meanValue
[1,] 1 A 0.82122120 0.18810771
[2,] 1 C 0.78213630 0.18810771
[3,] 1 C 0.61982575 0.18810771
[4,] 1 A -1.47075238 0.18810771
[5,] 2 B 0.59390132 0.03354688
[6,] 2 A 0.07456498 0.03354688
[7,] 2 B -0.05612874 0.03354688
[8,] 2 A -0.47815006 0.03354688
[9,] 3 B 0.91897737 -0.20205707
[10,] 3 C -1.98935170 -0.20205707
[11,] 3 B -0.15579551 -0.20205707
[12,] 3 A 0.41794156 -0.20205707
Я не сравнивал это, но по моему опыту это намного быстрее.
Если вы решите спуститься по дороге data.table, которую, я думаю, стоит изучить, если вы работаете с большими наборами данных, вам действительно нужно прочитать документы, потому что есть некоторые отличия от фрейма данных, которые могут вас укусить, если вы не знают о них. Однако, в частности, data.table обычно работает с любой функцией, ожидающей кадр данных, поскольку таблица данных будет требовать, чтобы ее тип был фреймом данных (таблица данных наследуется от фрейма данных).
[Фев. 2011]
[Август 2012] Обновление от Матфея:
Новое в v1.8.2, выпущенное в CRAN в июле 2012 года, :=
по группам. Это очень похоже на ответ выше, но добавляет новый столбец со ссылкой на dt
, поэтому нет копии и нет необходимости в шаге слияния или повторении существующих столбцов для возврата вместе с агрегатом. Вначале нет необходимости setkey
, и он справляется с несмежными группами (т.е. Группами, которые не сгруппированы вместе).
Это значительно быстрее для больших наборов данных и имеет простой и короткий синтаксис:
dt <- as.data.table(df)
dt[, meanValue := mean(values), by = group1]
Ответ 4
Не можете ли вы добавить x
к функции, которую вы передаете в ddply
?
df <- ddply( df, "group1", function(x)
data.frame( x, meanValue = mean(x$values) ) )
Ответ 5
A dplyr
возможность:
library(dplyr)
df %>%
group_by(group1) %>%
mutate(meanValue = mean(values))
Возвращает фрейм данных в исходном порядке. Добавьте arrange(group1)
в трубку, если вы хотите заказать "group1".