首页 文章

在每行数据帧上调用类似apply的函数,每行包含多个参数

提问于
浏览
128

我有一个包含多列的数据框 . 对于数据框中的每一行,我想在该行上调用一个函数,并且该函数的输入使用该行中的多个列 . 例如,假设我有这个数据,这个testFunc接受两个args:

> df <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
> df
  x y z
1 1 3 5
2 2 4 6
> testFunc <- function(a, b) a + b

假设我想将此testFunc应用于列x和z . 因此,对于第1行,我想要1 5,而对于第2行,我想要2 6.有没有办法在不编写for循环的情况下执行此操作,可能使用apply函数系列?

我试过这个:

> df[,c('x','z')]
  x z
1 1 5
2 2 6
> lapply(df[,c('x','z')], testFunc)
Error in a + b : 'b' is missing

但得到错误,任何想法?

EDIT: 我想要调用的实际函数不是简单的总和,而是power.t.test . 我使用b只是为了举例 . 最终目标是能够做这样的事情(用伪代码编写):

df = data.frame(
    delta=c(delta_values), 
    power=c(power_values), 
    sig.level=c(sig.level_values)
)

lapply(df, power.t.test(delta_from_each_row_of_df, 
                        power_from_each_row_of_df, 
                        sig.level_from_each_row_of_df
))

其中结果是每行df的power.t.test的输出向量 .

10 回答

  • 15

    您可以将 apply 应用于原始数据的子集 .

    dat <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
     apply(dat[,c('x','z')], 1, function(x) sum(x) )
    

    或者如果你的函数只是sum使用矢量化版本:

    rowSums(dat[,c('x','z')])
    [1] 6 8
    

    如果你想使用 testFunc

    testFunc <- function(a, b) a + b
     apply(dat[,c('x','z')], 1, function(x) testFunc(x[1],x[2]))
    

    EDIT 要按名称访问列而不是索引,您可以执行以下操作:

    testFunc <- function(a, b) a + b
     apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
    
  • 104

    data.framelist ,所以......

    对于 vectorized functions do.call 通常是一个不错的选择 . 但论证的名称开始发挥作用 . 这里使用args x和y来调用 testFunc 来代替a和b . ... 允许传递不相关的args而不会导致错误:

    do.call( function(x,z,...) testFunc(x,z), df )
    

    对于 non-vectorized functionsmapply 将起作用,但您需要匹配args的顺序或明确命名它们:

    mapply(testFunc, df$x, df$z)
    

    有时 apply 会起作用 - 因为当所有args属于同一类型时,将 data.frame 强制转换为矩阵不会因更改数据类型而导致问题 . 你的例子是这种 .

    如果要在其中传递参数的另一个函数中调用函数,则有一个比这些更明智的方法 . 如果你想走这条路,研究 lm() 身体的第一行 .

  • 0

    使用 mapply

    > df <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
    > df
      x y z
    1 1 3 5
    2 2 4 6
    > mapply(function(x,y) x+y, df$x, df$z)
    [1] 6 8
    
    > cbind(df,f = mapply(function(x,y) x+y, df$x, df$z) )
      x y z f
    1 1 3 5 6
    2 2 4 6 8
    
  • 9

    使用dplyr包的新答案

    如果要应用的函数是矢量化的,则可以使用 dplyr 包中的 mutate 函数:

    > library(dplyr)
    > myf <- function(tens, ones) { 10 * tens + ones }
    > x <- data.frame(hundreds = 7:9, tens = 1:3, ones = 4:6)
    > mutate(x, value = myf(tens, ones))
      hundreds tens ones value
    1        7    1    4    14
    2        8    2    5    25
    3        9    3    6    36
    

    与plyr包的旧答案

    在我的拙见中,最适合该任务的工具是 plyr 来自 plyr 包 .

    例:

    > library(plyr)
    > x <- data.frame(tens = 1:3, ones = 4:6)
    > mdply(x, function(tens, ones) { 10 * tens + ones })
      tens ones V1
    1    1    4 14
    2    2    5 25
    3    3    6 36
    

    不幸的是,正如Bertjan Broeksema指出的那样,如果你不在 mdply 调用中使用数据帧的所有列,这种方法就会失败 . 例如,

    > library(plyr)
    > x <- data.frame(hundreds = 7:9, tens = 1:3, ones = 4:6)
    > mdply(x, function(tens, ones) { 10 * tens + ones })
    Error in (function (tens, ones)  : unused argument (hundreds = 7)
    
  • 3

    许多函数已经是向量化,因此不需要任何迭代( for 循环或 *pply 函数) . 你的 testFunc 就是这样一个例子 . 你可以简单地打电话:

    testFunc(df[, "x"], df[, "z"])
    

    一般来说,我建议首先尝试这种矢量化方法,看看它们是否能得到你想要的结果 .


    或者,如果您需要将多个参数传递给未向量化的函数, mapply 可能就是您要查找的内容:

    mapply(power.t.test, df[, "x"], df[, "z"])
    
  • 26

    其他人已正确指出 mapply 是为此目的而制作的,但(为了完整起见)概念上更简单的方法就是使用 for 循环 .

    for (row in 1:nrow(df)) { 
        df$newvar[row] <- testFunc(df$x[row], df$z[row]) 
    }
    
  • 9

    这是另一种方法 . 它更直观 .

    我觉得有些答案没有考虑的一个关键方面,我指出后代,是apply()让你轻松进行行计算,但只适用于矩阵(所有数字)数据

    对于数据帧,仍然可以对列进行操作:

    as.data.frame(lapply(df, myFunctionForColumn()))
    

    要对行进行操作,我们首先进行转置 .

    tdf<-as.data.frame(t(df))
    as.data.frame(lapply(tdf, myFunctionForRow()))
    

    缺点是我相信R会复制你的数据表 . 这可能是一个记忆问题 . (这真的很难过,因为在程序上简单的tdf只是原始df的迭代器,因此节省了内存,但R不允许指针或迭代器引用 . )

    另外,一个相关的问题是如何对数据帧中的每个单独的单元进行操作 .

    newdf <- as.data.frame(lapply(df, function(x) {sapply(x, myFunctionForEachCell()}))
    
  • 97

    我来这里寻找tidyverse功能名称 - 我知道它存在 . 为(我的)未来参考和 tidyverse 爱好者添加此项: purrrlyr:invoke_rows (旧版本中为 purrr:invoke_rows ) .

    通过连接到原始问题中的标准统计方法,broom包可能会有所帮助 .

  • 4

    @ user20877984的答案很棒 . 由于他们总结得比我之前的答案好得多,所以这是我(可能仍然是粗制滥造)尝试应用这个概念:

    以基本方式使用 do.call

    powvalues <- list(power=0.9,delta=2)
    do.call(power.t.test,powvalues)
    

    处理完整的数据集:

    # get the example data
    df <- data.frame(delta=c(1,1,2,2), power=c(.90,.85,.75,.45))
    
    #> df
    #  delta power
    #1     1  0.90
    #2     1  0.85
    #3     2  0.75
    #4     2  0.45
    

    lapply power.t.test 函数指向每行的指定值:

    result <- lapply(
      split(df,1:nrow(df)),
      function(x) do.call(power.t.test,x)
    )
    
    > str(result)
    List of 4
     $ 1:List of 8
      ..$ n          : num 22
      ..$ delta      : num 1
      ..$ sd         : num 1
      ..$ sig.level  : num 0.05
      ..$ power      : num 0.9
      ..$ alternative: chr "two.sided"
      ..$ note       : chr "n is number in *each* group"
      ..$ method     : chr "Two-sample t test power calculation"
      ..- attr(*, "class")= chr "power.htest"
     $ 2:List of 8
      ..$ n          : num 19
      ..$ delta      : num 1
      ..$ sd         : num 1
      ..$ sig.level  : num 0.05
      ..$ power      : num 0.85
    ... ...
    
  • 1

    如果data.frame列是不同的类型, apply() 有问题 . 关于行迭代的一个微妙之处是当列是不同的类型时 apply(a.data.frame, 1, ...) 如何对字符类型进行隐式类型转换;例如 . 因子和数字列 . 这是一个使用因子的例子一列修改数字列:

    mean.height = list(BOY=69.5, GIRL=64.0)
    
    subjects = data.frame(gender = factor(c("BOY", "GIRL", "GIRL", "BOY"))
             , height = c(71.0, 59.3, 62.1, 62.1))
    
    apply(height, 1, function(x) x[2] - mean.height[[x[1]]])
    

    减法失败,因为列被转换为字符类型 .

    一种解决方法是将第二列反向转换为数字:

    apply(subjects, 1, function(x) as.numeric(x[2]) - mean.height[[x[1]]])
    

    但是可以通过将列保持分离并使用 mapply() 来避免转换:

    mapply(function(x,y) y - mean.height[[x]], subjects$gender, subjects$height)
    

    mapply() 是必需的,因为 [[ ]] 不接受矢量参数 . 因此,通过将一个向量传递给 [] ,通过更丑陋的代码,可以在减法之前完成列迭代:

    subjects$height - unlist(mean.height[subjects$gender])
    

相关问题