Subconjunto a data.frame por list e aplicar função em cada parte, por linhas
Pergunta
Isso pode parecer um típico plyr
Problema, mas tenho algo diferente em mente. Aqui está a função que eu quero otimizar (pule o for
ciclo).
# dummy data
set.seed(1985)
lst <- list(a=1:10, b=11:15, c=16:20)
m <- matrix(round(runif(200, 1, 7)), 10)
m <- as.data.frame(m)
dfsub <- function(dt, lst, fun) {
# check whether dt is `data.frame`
stopifnot (is.data.frame(dt))
# check if vectors in lst are "whole" / integer
# vector elements should be column indexes
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol
# fall if any non-integers in list
idx <- rapply(lst, is.wholenumber)
stopifnot(idx)
# check for list length
stopifnot(ncol(dt) == length(idx))
# subset the data
subs <- list()
for (i in 1:length(lst)) {
# apply function on each part, by row
subs[[i]] <- apply(dt[ , lst[[i]]], 1, fun)
}
# preserve names
names(subs) <- names(lst)
# convert to data.frame
subs <- as.data.frame(subs)
# guess what =)
return(subs)
}
E agora uma breve demonstração ... na verdade, estou prestes a explicar o que pretendia fazer principalmente. Eu queria subconjuntar um data.frame
por vetores reunidos em list
objeto. Como isso faz parte do código de uma função que acompanha a manipulação de dados em pesquisa psicológica, você pode considerar m
como um questionário de personalidade (10 indivíduos, 20 vars). Vetores na lista Hold Índices de coluna que definem subescalas de questionários (por exemplo, traços de personalidade). Cada subescala é definida por vários itens (colunas em data.frame
). Se pressupunciarmos que a pontuação em cada subescala não passasse sum
(ou alguma outra função) dos valores da linha (resultados nessa parte do questionário para cada sujeito), você pode executar:
> dfsub(m, lst, sum)
a b c
1 46 20 24
2 41 24 21
3 41 13 12
4 37 14 18
5 57 18 25
6 27 18 18
7 28 17 20
8 31 18 23
9 38 14 15
10 41 14 22
Eu dei uma olhada nessa função e devo admitir que esse pequeno loop não está estragando o código ... mas, se houver uma maneira mais fácil/eficiente de fazer isso, por favor, me avise!
Solução
Eu adotaria uma abordagem diferente e manteria tudo como quadros de dados para que você possa usar a mesclagem e a DDPLY. Eu acho que você encontrará essa abordagem um pouco mais geral e é mais fácil verificar se cada etapa é executada corretamente.
# Convert everything to long data frames
m$id <- 1:nrow(m)
library(reshape)
obs <- melt(m, id = "id")
obs$variable <- as.numeric(gsub("V", "", obs$variable))
varinfo <- melt(lst)
names(varinfo) <- c("variable", "scale")
# Merge and summarise
obs <- merge(obs, varinfo, by = "variable")
ddply(obs, c("id", "scale"), summarise,
mean = mean(value),
sum = sum(value))
Outras dicas
Depois de carregar o pacote Plyr, substitua
subs <- list()
for (i in 1:length(lst)) {
# apply function on each part, by row
subs[[i]] <- apply(dt[ , lst[[i]]], 1, fun)
}
com
subs <- llply(lst,function(x) apply(dt[,x],1,fun))
@Hadley, verifiquei sua resposta, pois é bastante direto e fácil para a contabilidade (além do fato de ser uma solução mais geral). No entanto, aqui está o meu script não tão de longo de longa que faz a coisa e exige apenas base
pacote (que é trivial desde que eu instalo plyr
e reshape
logo após a instalação de R). Agora, aqui está a fonte:
dfsub <- function(dt, lst, fun) {
# check whether dt is `data.frame`
stopifnot (is.data.frame(dt))
# convert data.frame factors to numeric
dt <- as.data.frame(lapply(dt, as.numeric))
# check if vectors in lst are "whole" / integer
# vector elements should be column indexes
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) abs(x - round(x)) < tol
# fall if any non-integers in list
idx <- rapply(lst, is.wholenumber)
stopifnot(idx)
# check for list length
stopifnot(ncol(dt) == length(idx))
# subset the data
subs <- list()
for (i in 1:length(lst)) {
# apply function on each part, by row
subs[[i]] <- apply(dt[ , lst[[i]]], 1, fun)
}
names(subs) <- names(lst)
# convert to data.frame
subs <- as.data.frame(subs)
# guess what =)
return(subs)
}
Para o seu exemplo específico, uma solução de uma linha é sapply(lst,function(x) rowSums(m[,x]))
(Embora você possa adicionar mais algumas linhas para verificar a entrada válida e colocar os nomes das colunas).
Você tem outras aplicações mais gerais em mente? Ou isso é possivelmente um caso de Yagni?