Подмножьте data.frame по списку и примените функцию к каждой части по строкам
Вопрос
Это может показаться типичным plyr
проблема, но у меня на уме кое-что другое.Вот функция, которую я хочу оптимизировать (пропустите for
цикл).
# 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)
}
А теперь короткая демонстрация...на самом деле, я собираюсь объяснить, что я в первую очередь намеревался сделать.Я хотел подмножество a data.frame
по векторам , собранным в list
объект.Поскольку это часть кода функции, которая сопровождает манипулирование данными в психологических исследованиях, вы можете рассмотреть m
по результатам личностного опросника (10 субъектов, 20 vars).Векторы в списке содержат индексы столбцов, которые определяют подшкалы вопросника (напримерличностные черты).Каждая подшкала определяется несколькими элементами (столбцами в data.frame
).Если мы предположим, что оценка по каждой подшкале - это не что иное, как sum
(или какой-либо другой функции) значений строк (результатов по этой части вопросника для каждого субъекта), вы могли бы выполнить:
> 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
Я взглянул на эту функцию и должен признать, что этот небольшой цикл нисколько не портит код...НО, если есть более простой / эффективный способ сделать это, пожалуйста, дайте мне знать!
Решение
Я бы выбрал другой подход и сохранил все в виде фреймов данных, чтобы вы могли использовать merge и ddply.Я думаю, вы обнаружите, что этот подход немного более общий, и с ним легче проверить, что каждый шаг выполнен правильно.
# 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))
Другие советы
после загрузки пакета plyr замените
subs <- list()
for (i in 1:length(lst)) {
# apply function on each part, by row
subs[[i]] <- apply(dt[ , lst[[i]]], 1, fun)
}
с
subs <- llply(lst,function(x) apply(dt[,x],1,fun))
@Hadley, я проверил ваш ответ, поскольку он довольно прост в ведении бухгалтерского учета (помимо того факта, что это более универсальное решение).Однако вот мой не очень длинный скрипт, который выполняет это и требует только base
пакет (который является тривиальным, поскольку я устанавливаю plyr
и reshape
сразу после установки R).Итак, вот источник:
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)
}
Для вашего конкретного примера однострочное решение является sapply(lst,function(x) rowSums(m[,x]))
(хотя вы могли бы добавить еще несколько строк, чтобы проверить правильность ввода и ввести имена столбцов).
Есть ли у вас на примете другие, более общие приложения?Или, возможно, это случай ЯГНИ?