在每一行数据帧上调用类似应用的函数,每行有多个参数

Posted

技术标签:

【中文标题】在每一行数据帧上调用类似应用的函数,每行有多个参数【英文标题】:Call apply-like function on each row of dataframe with multiple arguments from each row 【发布时间】:2013-02-10 03:08:15 【问题描述】:

我有一个包含多列的数据框。对于数据框中的每一行,我想在该行上调用一个函数,并且该函数的输入使用该行中的多个列。例如,假设我有这个数据和这个接受两个参数的 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 的输出向量。

【问题讨论】:

另见***.com/a/24728107/946850dplyr方式。 【参考方案1】:

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

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

或者,你可以调用你想要的函数。

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

【讨论】:

adply() 可以处理返回列表或数据帧的函数吗?例如,如果 testFunc() 返回一个列表怎么办?是否会使用 unnest() 将其变异为 df_appened 的其他列?【参考方案2】:

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

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

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

我觉得一些答案没有考虑到一个关键方面,我为后代指出,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()))

【讨论】:

【参考方案5】:

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)

【讨论】:

只有少数列时很好。我试图做类似的事情: mdply(df, function(col1, col3) ) 并且 mdply 退出,抱怨 col2 未使用。现在,如果你有几十甚至几百列,这种方法就不是很有吸引力了。 @BertjanBroeksema 修改了很多列,可以使用dplyr::mutate_each。例如:iris %&gt;% mutate_each(funs(half = . / 2),-Species). 你不能只将省略号或数百个传递给函数而不使用它吗?那应该可以解决这个错误吗?【参考方案6】:

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

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

【讨论】:

你是对的。要有效地使用 mapply,我认为您必须了解它只是一个幕后的“for”循环,特别是如果您具有 C++ 或 C# 等过程编程背景。【参考方案7】:

如果 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])

【讨论】:

【参考方案8】:

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

【讨论】:

感谢@agstudy,这行得通!您知道是否有任何方法可以按名称而不是按索引指定参数?因此,对于 testFunc,类似于 apply(dat[,c('x','z')], 1, [pseudocode] testFunc(a=x, b=y))?原因是我以这种方式调用 power.t.test,我希望能够通过名称引用 delta、power、sig.level 参数,而不是将它们粘贴到具有预先指定位置的数组中,然后引用这些位置,因为它更健壮。无论如何,非常感谢! 抱歉之前的评论,在输入完之前按回车:) 删除它并发布完整版。 不要在大数据帧上使用apply,它将复制整个对象(转换为矩阵)。如果 data.frame 中有不同的类对象,这也会导致问题。【参考方案9】:

@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
... ...

【讨论】:

哈哈,也许有点复杂? ;) 你为什么使用 t() 并申请 2,为什么不直接申请 1【参考方案10】:

data.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 会起作用 - 因为当所有参数都属于同一类型时,因此将data.frame 强制转换为矩阵不会因更改数据类型而导致问题。你的例子就是这样的。

如果要在另一个函数中调用您的函数,并将所有参数都传递给该函数,那么还有比这些更巧妙的方法。如果你想走这条路,请研究lm() 正文的第一行。

【讨论】:

如果可以的话,+10。欢迎来到 SO。很好的答案 - 可能值得一提 Vectorize 作为 mapply 的包装器以矢量化函数 哇,这太棒了。我使用的原始函数没有矢量化(power.t.test 之上的自定义扩展),但我想我会将它矢量化并使用 do.call(...)。谢谢! 只是重申这个答案已经说明 apply(df, 1, function(row) ...) 可能不好,因为 apply 将 df 转换为矩阵!!!!这可能很糟糕,并导致大量的头发拉扯。非常需要申请的替代方案! 非常感谢您区分矢量化/非矢量化,这绝对是我一直在寻找的答案【参考方案11】:

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

【讨论】:

【参考方案12】:

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

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

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


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

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

【讨论】:

哦,亲爱的。你知道是否有办法在 mapply 中按名称指定参数?即类似 [pseudocode] mapply(power.t.test, delta=df[,'delta'], power=df[,'power'], ...) ? 是的,它就是你所拥有的! ;)

以上是关于在每一行数据帧上调用类似应用的函数,每行有多个参数的主要内容,如果未能解决你的问题,请参考以下文章

Pandas:np.where 在数据帧上有多个条件

在列表中的多个数据帧上应用 lapply,R

如何在聚合的 pandas 数据帧上运行多个函数

Vim,如何在每一行下面 添加一个或多个空白行。

R中带有for循环的多个数据帧上的行名

如何在 R 中的单个数据帧上迭代地应用函数?