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


168

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


Answers:


137

您可以将其应用于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,那行得通!您知道是否可以通过名称而不是通过索引指定args吗?因此,对于testFunc,类似于apply(dat [,c('x','z')],1,[pseudocode] testFunc(a = x,b = y))吗?原因是我以这种方式调用power.t.test,并且我希望能够按名称引用delta,power,sig.level参数,而不是将它们粘贴到具有预定位置的数组中,然后引用这些位置,原因是更健壮。无论如何,非常感谢!
vasek1

抱歉,之前的评论,请在输入完毕之前按Enter键:)将其删除并发布完整版本。
vasek1

21
不要apply在大数据上使用框架,它会复制整个对象(转换为矩阵)。如果您在data.frame中有不同的类对象,这也会引起问题。
mnel

105

A data.frame是一个list,所以...

对于矢量化函数 do.call,通常是一个不错的选择。但是争论的名字开始起作用。在这里,testFunc用args x和y代替a和b来调用your 。在...允许无关的参数传递给没有导致错误进行传递:

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

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

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

有时apply会起作用-就像所有args都是相同类型时一样,因此强制data.frame转换为矩阵不会通过更改数据类型引起问题。您的示例就是这种情况。

如果要在另一个所有参数都传入其中的函数中调用您的函数,则比这些函数要精巧得多。lm()如果您想走那条路线,请研究其身体的第一行。


8
如果可以的话,+ 10。欢迎来到SO。好的答案-值得一提的Vectorizemapply向量化函数的包装器
mnel

哇,真滑。我使用的原始函数未进行矢量化处理(在power.t.test之上是自定义扩展名),但我想我将对其进行矢量化处理并使用do.call(...)。谢谢!
vasek1

3
只是重申一下,这个答案已经说了apply(df,1,function(row)...)可能很糟糕,因为apply将df转换为矩阵!!!这可能很糟糕,并导致大量的头发拉扯。非常需要替代方案!
科林D

非常感谢您对矢量化与非矢量化之间的区别,这绝对是我一直在寻找的答案
User632716

31

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

20

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

在我的愚见,该工具最适合的任务是mdplyplyr包装。

例:

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

1
当您只有少量的列时,这很好。我试图做类似的事情:mdply(df,function(col1,col3){})和mdply失败了,抱怨col2未使用。现在,如果您有几十甚至几百列,这种方法就不是很吸引人。
Bertjan Broeksema 2015年

1
@BertjanBroeksema可以修改很多列,可以使用dplyr::mutate_each。例如:iris %>% mutate_each(funs(half = . / 2),-Species)
Paul Rougieux

您不能只将省略号或数百个传递给函数而不使用它吗?那应该解决那个错误?
肖恩

11

其他人正确地指出了mapply为此目的而设计的方法,但是(出于完整性考虑)从概念上讲更简单的方法就是使用for循环。

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

1
你是对的。为了有效地使用mapply,我认为您必须了解这只是幕后的“ for”循环,尤其是如果您来自C ++或C#等程序编程背景。
Contango 2014年

10

许多函数已经被矢量化了,因此不需要任何迭代(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'],...)之类的东西?
vasek1 2013年

1
是的,这就是您所拥有的!;)
Ricardo Saporta

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

4

我来这里寻找tidyverse函数名称-我知道它存在。添加此为(我的)将来参考和tidyverse爱好者:purrrlyr:invoke_rowspurrr:invoke_rows旧版本)。

通过连接到原始问题中的标准stats方法,扫帚软件包可能会有所帮助。


3

@ 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
里卡多·萨波特塔

3

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

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

1

一个非常好的函数是adplyfrom plyr,特别是如果您要将结果附加到原始数据帧时。这个函数及其表亲为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的其他列吗?
val
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.