在每行数据框中调用类似于 application 的函数,并在每行中使用多个参数

我有一个包含多个列的数据框架。对于数据框架中的每一行,我希望调用该行上的一个函数,该函数的输入使用该行的多个列。例如,假设我有这个数据和这个 testFunc,它接受两个参数:

> 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 循环就可以实现的方法,比如使用 application 函数族?

我试过了:

> 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

但是出错了,有什么想法吗?

我要调用的实际函数 编辑:不是一个简单的求和函数,而是 power.t.test。我用 a + 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 的输出向量。

305847 次浏览

您可以将 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) )

或者如果你的函数只是求和,使用向量化版本:

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

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

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

一般来说,我建议首先尝试这种向量化方法,看看它们是否能得到预期的结果。


或者,如果您需要向一个未向量化的函数传递多个参数,那么 mapply可能就是您要寻找的:

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

使用 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

@ 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

对指定值的每一行执行 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
... ...

data.framelist所以..。

对于 向量化函数向量化函数来说,do.call通常是一个不错的选择。但是参数的名称起了作用。在这里,你的 testFunc用参数 x 和 y 代替 a 和 b 来调用。...允许传递不相关的参数而不会导致错误:

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

对于 非向量化函数非向量化函数mapply将工作,但是您需要匹配参数的顺序或者显式地命名它们:

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

有时候 apply会工作-当所有的参数都是相同类型的时候,因此强制 data.frame到一个矩阵不会因为改变数据类型而造成问题。你的例子就是这样的。

如果要在另一个函数中调用函数,这个函数的所有参数都要传递到这个函数中,那么还有一个比这个更灵活的方法。如果你想走那条路线,研究 lm()主体的第一行。

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

for (row in 1:nrow(df)) {
df$newvar[row] <- testFunc(df$x[row], df$z[row])
}
如果 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)
因为 [[ ]]不接受向量参数,所以需要使用 < p > mapply() 迭代可以在减法之前通过向 []传递一个向量来完成, 更加丑陋的代码:

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

新的答案与 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包中的 mdply

例如:

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

这里有一个替代方法,它更直观。

有一个关键的方面我觉得有些答案没有考虑到,这是我为后代指出的,那就是 application ()可以让您轻松地进行行计算,但只能对矩阵(所有数字)数据进行计算

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

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

我来这里寻找 整体宇宙函数名-我知道它的存在。为(我的)未来的参考和 tidyverse爱好者添加这个: purrrlyr:invoke_rows(旧版本的 purrr:invoke_rows)。

与原始问题中的标准统计方法相关联,扫帚包可能会有所帮助。

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

一个非常好的函数是 plyr中的 adply,特别是如果您想将结果追加到原始数据帧中。这个函数和它的表亲 ddply已经帮我省去了很多麻烦和代码行!

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

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

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