我有一个包含多列的数据框。对于数据框中的每一行,我想在该行上调用一个函数,并且该函数的输入使用该行中的多个列。例如,假设我有这个数据和这个接受两个参数的 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 循环的情况下做到这一点,也许使用 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
但是有错误,有什么想法吗?
编辑:我要调用的实际函数不是简单的求和,而是 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 的输出向量。
dplyr
方式。
您可以将 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']))
data.frame
是 list
,所以...
对于矢量化函数,do.call
通常是一个不错的选择。但是参数的名称开始发挥作用。在这里,您的 testFunc
用参数 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()
正文的第一行。
Vectorize
作为 mapply
的包装器以矢量化函数
使用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
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)
dplyr::mutate_each
。例如:iris %>% mutate_each(funs(half = . / 2),-Species)
。
其他人正确地指出 mapply
是为此目的而制作的,但是(为了完整起见)概念上更简单的方法就是使用 for
循环。
for (row in 1:nrow(df)) {
df$newvar[row] <- testFunc(df$x[row], df$z[row])
}
许多函数已经矢量化,因此不需要任何迭代(for
循环或 *pply
函数)。您的 testFunc
就是这样一个例子。您可以简单地调用:
testFunc(df[, "x"], df[, "z"])
一般来说,我建议先尝试这种矢量化方法,看看它们是否能得到你想要的结果。
或者,如果您需要将多个参数传递给未矢量化的函数,则 mapply
可能是您正在寻找的:
mapply(power.t.test, df[, "x"], df[, "z"])
这是另一种方法。它更直观。
我觉得一些答案没有考虑到的一个关键方面,我为后代指出的是 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()}))
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
df[,new_column := Vectorize(sample_fxn)(A,B,C)]
@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
... ...
2
上应用,为什么不直接在 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])
一个非常好的函数是来自 plyr
的 adply
,特别是如果您想将结果附加到原始数据帧。这个函数和它的表兄弟 ddply
为我省去了很多麻烦和代码行!
df_appended <- adply(df, 1, mutate, sum=x+z)
或者,您可以调用所需的函数。
df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))
apply
,它会复制整个对象(转换为矩阵)。如果 data.frame 中有不同的类对象,这也会导致问题。