187

I have a dataframe with multiple columns. For each row in the dataframe, I want to call a function on the row, and the input of the function is using multiple columns from that row. For example, let's say I have this data and this testFunc which accepts two 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

Let's say I want to apply this testFunc to columns x and z. So, for row 1 I want 1+5, and for row 2 I want 2 + 6. Is there a way to do this without writing a for loop, maybe with the apply function family?

I tried this:

> 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

But got error, any ideas?

EDIT: the actual function I want to call is not a simple sum, but it is power.t.test. I used a+b just for example purposes. The end goal is to be able to do something like this (written in pseudocode):

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

where the result is a vector of outputs for power.t.test for each row of df.

4

12 回答 12

150

您可以应用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]))

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

 testFunc <- function(a, b) a + b
 apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
于 2013-02-25T02:18:02.340 回答
115

Adata.framelist, 所以 ...

对于矢量化函数 do.call,通常是一个不错的选择。但是参数的名称开始发挥作用。在这里,您testFunc用 args x 和 y 代替 a 和 b 调用。...允许传递不相关的参数而不会导致错误:

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

对于非向量化函数mapply可以使用,但您需要匹配 args 的顺序或明确命名它们:

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

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

如果您的函数要在参数全部传递到的另一个函数中调用,则有比这些更巧妙的方法。lm()如果你想走那条路,研究一下身体的第一行。

于 2013-02-25T02:47:38.677 回答
35

利用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
于 2013-02-25T02:31:04.640 回答
24

dplyr包的新答案

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

> 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包的旧答案

在我看来,最适合该任务的工具mdply来自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)
于 2014-11-05T21:53:31.870 回答
15

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

for (row in 1:nrow(df)) { 
    df$newvar[row] <- testFunc(df$x[row], df$z[row]) 
}
于 2014-01-16T00:21:37.010 回答
12

许多函数已经矢量化,因此不需要任何迭代(for循环或*pply函数)。你testFunc就是这样一个例子。您可以简单地调用:

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

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


或者,如果您需要将多个参数传递给未矢量化的函数,则mapply可能是您正在寻找的:

  mapply(power.t.test, df[, "x"], df[, "z"])
于 2013-02-25T02:25:37.983 回答
5

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

我觉得一些答案没有考虑到的一个关键方面,我为后代指出的是 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()}))
于 2016-06-02T18:53:41.533 回答
5

data.table也有一种非常直观的方法:

library(data.table)

sample_fxn = function(x,y,z){
    return((x+y)*z)
}

df = data.table(A = 1:5,B=seq(2,10,2),C = 6:10)
> df
   A  B  C
1: 1  2  6
2: 2  4  7
3: 3  6  8
4: 4  8  9
5: 5 10 10

:=可以在括号内调用运算符以使用函数添加新列

df[,new_column := sample_fxn(A,B,C)]
> df
   A  B  C new_column
1: 1  2  6         18
2: 2  4  7         42
3: 3  6  8         72
4: 4  8  9        108
5: 5 10 10        150

使用此方法也很容易接受常量作为参数:

df[,new_column2 := sample_fxn(A,B,2)]

> df
   A  B  C new_column new_column2
1: 1  2  6         18           6
2: 2  4  7         42          12
3: 3  6  8         72          18
4: 4  8  9        108          24
5: 5 10 10        150          30
于 2019-01-15T20:58:06.247 回答
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

lapplypower.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
... ...
于 2013-02-25T02:42:56.577 回答
4

我来这里是为了寻找tidyverse函数名称——我知道它存在。添加此内容以供(我的)未来参考和tidyverse爱好者:(purrrlyr:invoke_rowspurrr:invoke_rows旧版本中)。

通过与原始问题中的标准统计方法的连接,扫帚包可能会有所帮助。

于 2017-03-05T15:04:42.323 回答
3

如果 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])
于 2014-07-11T19:28:22.997 回答
3

一个非常好的函数是adplyfrom plyr,特别是如果你想将结果附加到原始数据帧。这个函数和它的表弟ddply为我省去了很多麻烦和代码行!

df_appended <- adply(df, 1, mutate, sum=x+z)

或者,您可以调用所需的函数。

df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))
于 2019-02-08T20:42:47.260 回答