Агрегировать по категориям, которые содержат NA с ddply и lapply?
Я хотел бы объединить data.frame по 3 категориям, причем одна из них варьируется. К сожалению, эта одна изменяющаяся категория содержит NA (на самом деле это причина, по которой она должна меняться). Таким образом, я создал список data.frames
, Каждый data.frame в этом списке содержит только полные случаи по трем переменным (только одна из которых изменяется).
Давайте воспроизведем это:
library(plyr)
mydata <- warpbreaks
names(mydata) <- c("someValue","group","size")
mydata$category <- c(1,2,3)
mydata$categoryA <- c("A","A","X","X","Z","Z")
# add some NA
mydata$category[c(8,10,19)] <- NA
mydata$categoryA[c(14,1,20)] <- NA
# create a list of dfs that contains TRUE FALSE
noNAList <- function(vec){
res <- !is.na(vec)
return(res)
}
testTF <- lapply(mydata[,c("category","categoryA")],noNAList)
# create a list of data.frames
selectDF <- function(TFvec){
res <- mydata[TFvec,]
return(res)
}
# check x and see that it may contain NAs as long
# as it's not in one of the 3 categories I want to aggregate over
x <-lapply(testTF,selectDF)
## let's ddply get to work
doddply <- function(df){
ddply(df,.(group,size),summarize,sumTest = sum(someValue))
}
y <- lapply(x, doddply);y
y
подходит очень близко к тому, что я хочу получить
$category
group size sumTest
1 A L 375
2 A M 198
3 A H 185
4 B L 254
5 B M 259
6 B H 169
$categoryA
group size sumTest
1 A L 375
2 A M 204
3 A H 200
4 B L 254
5 B M 259
6 B H 169
Но мне нужно реализовать агрегацию по третьей переменной, которая в данном случае category
а также categoryA
, Как:
group size category sumTest sumTestTotal
1 A H 1 46 221
2 A H 2 46 221
3 A H 3 93 221
и так далее. Как я могу добавить имена (x) для lapply, или мне нужен цикл или среда здесь?
РЕДАКТИРОВАТЬ: Обратите внимание, что я хочу, чтобы ЛЮБАЯ категория ИЛИ категория A добавлялась в микс На самом деле у меня есть около 15 взаимоисключающих категориальных переменных.
4 ответа
Я знаю, что вопрос явно требует ddply()/lapply()
решение.
Но... если вы готовы перейти на темную сторону, вот data.table()
функция, которая должна сделать свое дело:
# Convert mydata to a data.table
library(data.table)
dt <- data.table(mydata, key = c("group", "size"))
# Define workhorse function
myfunction <- function(dt, VAR) {
E <- as.name(substitute(VAR))
dt[i = !is.na(eval(E)),
j = {n <- sum(.SD[,someValue])
.SD[, list(sumTest = sum(someValue),
sumTestTotal = n,
share = sum(someValue)/n),
by = VAR]
},
by = key(dt)]
}
# Test it out
s1 <- myfunction(dt, "category")
s2 <- myfunction(dt, "categoryA")
ДОБАВЛЕНО В РЕДАКТИРОВАНИЕ
Вот как вы можете запустить это для вектора разных категориальных переменных:
catVars <- c("category", "categoryA")
ll <- lapply(catVars,
FUN = function(X) {
do.call(myfunction, list(dt, X))
})
names(ll) <- catVars
lapply(ll, head, 3)
# $category
# group size category sumTest sumTestTotal share
# [1,] A H 2 46 185 0.2486486
# [2,] A H 3 93 185 0.5027027
# [3,] A H 1 46 185 0.2486486
#
# $categoryA
# group size categoryA sumTest sumTestTotal share
# [1,] A H A 79 200 0.395
# [2,] A H X 68 200 0.340
# [3,] A H Z 53 200 0.265
Я думаю, что вы, возможно, сделаете это действительно тяжело для себя, если я правильно понимаю ваш вопрос.
Если вы хотите объединить data.frame 'myData' по трем (или четырем) переменным, вы просто сделаете это:
aggregate(someValue ~ group + size + category + categoryA, sum, data=mydata)
group size category categoryA someValue
1 A L 1 A 51
2 B L 1 A 19
3 A M 1 A 17
4 B M 1 A 63
aggregate
автоматически удалит строки, которые включают NA
в любой из категорий. Если someValue иногда NA
, затем вы можете добавить параметр na.rm=T.
Я также отметил, что вы помещаете много ненужного кода в функции. Например:
# create a list of data.frames
selectDF <- function(TFvec){
res <- mydata[TFvec,]
return(res)
}
Может быть написано как:
selectDF <- function(TFvec) mydata[TFvec,]
Кроме того, используя lapply
создать список из двух фреймов данных без NA
это перебор. Попробуйте этот код:
x = list(mydata[!is.na(mydata$category),],mydata[!is.na(mydata$categoryA),])
Наконец, я нашел решение, которое может быть не таким гладким, как у Джоша, но оно работает без темных сил (data.table). Вы можете смеяться - вот мой воспроизводимый пример, использующий те же примеры данных, что и в вопросе.
qual <- c("category","categoryA")
# get T / F vectors
noNAList <- function(vec){
res <- !is.na(vec)
return(res)
}
selectDF <- function(TFvec) mydata[TFvec,]
NAcheck <- lapply(mydata[,qual],noNAList)
# create a list of data.frames
listOfDf <- lapply(NAcheck,selectDF)
workhorse <- function(charVec,listOfDf){
dfs <- list2env(listOfDf)
# create expression list
exlist <- list()
for(i in 1:length(qual)){
exlist[[qual[i]]] <- parse(text=paste("ddply(",qual[i],
",.(group,size,",qual[i],"),summarize,sumTest = sum(someValue))",
sep=""))
}
res <- lapply(exlist,eval,envir=dfs)
return(res)
}
Это больше похоже на то, что вы имеете в виду? Я считаю ваш пример чрезвычайно сложным для понимания. В приведенном ниже коде метод может взять любой столбец, а затем агрегировать его. Может возвращать несколько функций агрегации someValue. Затем я нахожу все имена столбцов, по которым вы хотите агрегировать, и затем применяю функцию к этому вектору.
# Build a method to aggregate by column.
agg.by.col = function (column) {
by.list=list(mydata$group,mydata$size,mydata[,column])
names(by.list) = c('group','size',column)
aggregate(mydata$someValue, by=by.list, function(x) c(sum=sum(x),mean=mean(x)))
}
# Find all the column names you want to aggregate by
cols = names(mydata)[!(names(mydata) %in% c('someValue','group','size'))]
# Apply the method to each column name.
lapply (cols, agg.by.col)