Поместить data.frame в список и применить функцию к каждой части по строкам - PullRequest
2 голосов
/ 28 февраля 2010

Это может показаться типичной проблемой 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)
}

А теперь короткая демонстрация ... на самом деле, я собираюсь объяснить, что я в первую очередь собирался сделать. Я хотел установить data.frame для векторов, собранных в list объекте. Поскольку это часть кода функции, которая сопровождает манипулирование данными в психологических исследованиях, вы можете рассматривать m как результаты анкетного опроса личности (10 предметов, 20 переменных). Векторы в списке содержат индексы столбцов, которые определяют подшкалы вопросника (например, черты личности). Каждая подшкала определяется несколькими элементами (столбцы в 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

Я взглянул на эту функцию и должен признать, что этот маленький цикл совсем не портит код ... НО, если есть более простой / эффективный способ сделать это, пожалуйста, дайте мне знать!

Ответы [ 4 ]

7 голосов
/ 28 февраля 2010

Я бы выбрал другой подход и сохранил бы все как фреймы данных, чтобы вы могли использовать слияние и 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))
2 голосов
/ 28 февраля 2010

после загрузки пакета 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))
0 голосов
/ 04 августа 2013

В вашем конкретном примере однострочное решение - sapply(lst,function(x) rowSums(m[,x])) (хотя вы можете добавить еще несколько строк для проверки правильности ввода и вставить имена столбцов).

Имеете ли вы в виду другие, более общие приложения? Или это может быть случай YAGNI ?

0 голосов
/ 13 марта 2010

@ Хэдли, я проверил твой ответ, так как он довольно прост и легок для бухгалтерии (кроме того, что это более универсальное решение). Тем не менее, вот мой не очень длинный скрипт, который делает это и требует только 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)
}
...