将多个函数应用于数据框的每一行

时间:2011-08-24 10:23:22

标签: r transform rows dataframe apply

每当我想到我理解使用向量时,似乎是一个简单的问题就会让我内心深处。很多阅读和尝试不同的例子在这个场合没有帮助。请把勺子喂我......

我想将两个自定义函数应用于数据帧的每一行,并将结果添加为两个新列。这是我的示例代码:

# Required packages:
library(plyr)

FindMFE <- function(x) {
    MFE <- max(x, na.rm = TRUE) 
    MFE <- ifelse(is.infinite(MFE ) | (MFE  < 0), 0, MFE)
    return(MFE)
}

FindMAE <- function(x) {
    MAE <- min(x, na.rm = TRUE) 
    MAE <- ifelse(is.infinite(MAE) | (MAE> 0), 0, MAE)
    return(MAE)
}

FindMAEandMFE <- function(x){
        # I know this next line is wrong...
    z <- apply(x, 1, FindMFE, FindMFE)
        return(z)
}

df1 <- data.frame(Bar1=c(1,2,3,-3,-2,-1),Bar2=c(3,1,3,-2,-3,-1))

df1 = transform(df1, 
    FindMAEandMFE(df1)  
)

#DF1 should end up with the following data...
#Bar1   Bar2    MFE MAE
#1      3       3   0
#2      1       2   0
#3      3       3   0
#-3     -2      0   -3
#-2     -3      0   -3
#-1     -1      0   -1

使用plyr库和类似基础的方法获得答案会很棒。两者都有助于我的理解。当然,如果显而易见,请指出我出错的地方。 ; - )

现在回到我的帮助文件中!

编辑:我想要一个多变量解决方案,因为列名可能会随着时间的推移而改变和扩展。它还允许将来重用代码。

4 个答案:

答案 0 :(得分:19)

我认为你这里的想法太复杂了。两个单独的apply()电话有什么问题?然而,有一个更好的方法来做你在这里做的事情,不涉及循环/应用调用。我会单独处理这些,但第二种解决方案更可取,因为它是真正的矢量化。

两个应用呼叫版本

使用all-Base R函数进行前两次单独的apply调用:

df1 <- data.frame(Bar1=c(1,2,3,-3,-2,-1),Bar2=c(3,1,3,-2,-3,-1))
df1 <- transform(df1, MFE = apply(df1, 1, FindMFE), MAE = apply(df1, 1, FindMAE))
df1

给出了:

> df1
  Bar1 Bar2 MFE MAE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

好的,循环两次df1行可能效率不高,但即使遇到大问题,你也花了更多时间思考在一次通过中巧妙地做到这一点通过这样的方式比你节省的。

使用向量化函数pmax()pmin()

更好的方法是注意pmax()pmin()函数,并意识到他们可以执行apply(df1, 1, FindFOO()调用所执行的操作。例如:

> (tmp <- with(df1, pmax(0, Bar1, Bar2, na.rm = TRUE)))
[1] 3 2 3 0 0 0

将是您问题中的MFE。如果您有两列,并且它们是Bar1Bar2df1的前两列,则此操作非常简单。但它不是很一般;如果你想要计算多个列,等等怎么办? pmax(df1[, 1:2], na.rm = TRUE)不会做我们想要的事情:

> pmax(df1[, 1:2], na.rm = TRUE)
  Bar1 Bar2
1    1    3
2    2    1
3    3    3
4   -3   -2
5   -2   -3
6   -1   -1

使用pmax()pmin()获取常规解决方案的诀窍是使用do.call()为我们安排对这两个函数的调用。更新您的功能以使用我们的想法:

FindMFE2 <- function(x) {
   MFE <- do.call(pmax, c(as.list(x), 0, na.rm = TRUE))
   MFE[is.infinite(MFE)] <- 0
   MFE
}

FindMAE2 <- function(x) {
   MAE <- do.call(pmin, c(as.list(x), 0, na.rm = TRUE))
   MAE[is.infinite(MAE)] <- 0
   MAE
}

给出:

> transform(df1, MFE = FindMFE2(df1), MAE = FindMAE2(df1))
  Bar1 Bar2 MFE MAE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

而不是apply()。如果您想在一个步骤中完成此操作,现在可以更轻松地进行包装:

FindMAEandMFE2 <- function(x){
    cbind(MFE = FindMFE2(x), MAE = FindMAE2(x))
}

可以用作:

> cbind(df1, FindMAEandMFE2(df1))
  Bar1 Bar2 MFE MAE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

答案 1 :(得分:19)

我展示了三种替代单行:

  • 使用each
  • plyr功能
  • plyr each函数与基础R
  • 一起使用
  • 使用vectorise的pminpmax函数

解决方案1:plyr和每个

plyr包定义了each函数,它可以执行您想要的操作。从?each将多个函数聚合到一个函数中。这意味着您可以使用单行解决问题:

library(plyr)
adply(df1, 1, each(MAE=function(x)max(x, 0), MFE=function(x)min(x, 0)))

  Bar1 Bar2 MAE MFE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

溶液2:各自和碱基R

当然,您可以将each与基本功能结合使用。以下是apply使用它的方法 - 请注意,在添加到原始data.frame之前必须转置结果。

library(plyr)
data.frame(df1, 
  t(apply(df1, 1, each(MAE=function(x)max(x, 0), MFE=function(x)min(x, 0)))))

  Bar1 Bar2 MAE MFE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

解决方案3:使用向量化函数

使用向量化函数pminpmax,您可以使用这个单行程序:

transform(df1, MFE=pmax(0, Bar1, Bar2), MAE=pmin(0, Bar1, Bar2))

  Bar1 Bar2 MFE MAE
1    1    3   3   0
2    2    1   2   0
3    3    3   3   0
4   -3   -2   0  -3
5   -2   -3   0  -3
6   -1   -1   0  -1

答案 2 :(得分:6)

这里有很多好的答案。我是在Gavin Simpson编辑的时候开始的,所以我们介绍了一些相似的内容。并行最小值和最大值(pmin和pmax)几乎就是你正在编写函数的内容。它在pmax(0,Bar1,Bar2)中的0可能有点不透明,但基本上0会被回收,这就像做

pmax(c(0,0,0,0,0,0), Bar1, Bar2)

这将通过三件事中的每一项并找到它们的最大值。因此,如果它是负数,则max将为0,并完成ifelse语句所做的大部分操作。你可以重写,这样你就可以得到向量,并将事物与你正在做的事情相结合,这可能会使它更加透明。在这种情况下,我们只需将数据帧传递给一个新的并行快速findMFE函数,该函数可以处理任何数值数据帧并得到一个向量。

findMFE <- function(dataf){
    MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
}

MFE <- findMFE(df1)

这个函数的作用是在传递的数据帧中添加一个额外的0列,然后调用pmax传递df1的每个单独列,就像它是一个列表一样(数据帧是列表所以这很容易)。

现在,我注意到您实际上想要更正数据中不属于您的示例的Inf值...我们可以为您的函数添加额外的行...

findMFE <- function(dataf){
    MFE <- do.call( pmax, c(dataf, 0, na.rm = TRUE))
    ifelse(is.infinite(MFE), 0, MFE)
}

现在,正确使用向量上的ifelse()函数。我这样做是为了你的例子,但Gavin Simpson使用MFE [is.infinite(MFE)]&lt; - 0更有效率。请注意,此findMFE函数未在循环中使用,它只是传递整个数据帧。

可比较的findMAE是......

findMAE <- function(dataf){
    MAE <- do.call( pmin, c(dataf, 0, na.rm = TRUE))
    ifelse(is.infinite(MAE), 0, MAE)
}

,组合功能就是......

findMFEandMAE <- function(dataf){
    MFE <- findMFE(dataf)
    MAE <- findMAE(dataf)
    return(data.frame(MFE, MAE))
}

MFEandMAE&lt; - findMFEandMAE(df1) df1&lt; - cbind(df1,MFEandMAE)

一些提示

如果你有一个标量if语句不使用ifelse(),请使用if()else。它在标量情况下要快得多。并且,您的函数是标量,并且您正在尝试对它们进行矢量化。 ifelse()已经被向量化并且在使用时以非常快的速度运行,但在使用标量时比if()其他要慢得多。

另外,如果你要把东西放在一个循环中或者应用尽可能少的语句。例如,在你的情况下,ifelse()确实需要从循环中取出并随后应用于整个MFE结果。

答案 3 :(得分:1)

如果你真的想要它,你可以:

FindMAEandMFE <- function(x){
    t(apply(x, 1, function(currow){c(MAE=FindMAE(currow), MFE=FindMFE(currow))}))
}

(未经测试 - 它应返回一个包含两个(我认为是名称)列的数组,以及与data.frame一样多的行)。现在你可以做到:

df1<-cbind(df1, FindMAEandMFE(df1))

非常icky。请听取加文的建议。