按行列出data.frame并按行在每个部分上应用函数

时间:2010-02-28 13:47:04

标签: list r apply dataframe lapply

这似乎是一个典型的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 个答案:

答案 0 :(得分:7)

我会采用不同的方法并将所有内容保存为数据框,以便您可以使用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))

答案 1 :(得分:2)

加载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))

答案 2 :(得分:0)

@Hadley,我已经检查了你的回复,因为它非常直接且易于记账(除了它是更通用的解决方案)。但是,这是我不太长的脚本,只需要base包(因为我在安装R之后安装plyrreshape这很简单)。现在,这是来源:

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)
}

答案 3 :(得分:0)

对于您的具体示例,单行解决方案是sapply(lst,function(x) rowSums(m[,x]))(尽管您可能会添加更多行来检查有效输入并放入列名称。)

您还有其他更通用的应用程序吗?或者这可能是YAGNI的情况?