【问题标题】:Apply multiple functions to each row of a dataframe将多个函数应用于数据框的每一行
【发布时间】:2011-11-02 16:27:52
【问题描述】:

每当我认为自己理解了使用向量时,一个看似简单的问题就会让我的头脑彻底清醒。在这种情况下,大量阅读和尝试不同的例子并没有帮助。请在这里用勺子喂我...

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

# 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 库和更基础的方法获得答案会很棒。两者都将有助于我的理解。当然,如果很明显,请指出我要去哪里。 ;-)

现在回到我的帮助文件!

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

【问题讨论】:

    标签: r transform rows dataframe apply


    【解决方案1】:

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

    两个应用调用版本

    使用全基 R 函数的前两个单独的应用调用:

    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
    

    【讨论】:

    • @LookLeft - 关于您的编辑,我相当肯定 Gavin 的矢量化 pmin,pmax 解决方案将处理具有任意列数和名称的数据框。但我只是在猜测您在这里所说的“多元”是什么意思。
    • +1 以获得精彩的描述。使用 do.call 的循环问题和一般解决方案非常有见地。我开始更多地了解向量以及 R 函数处理它们的方式。我将继续使用每个示例。
    • @joran。是的,编辑是为了回应 Gavin 的评论和回答。他发现了限制并提供了一个很好的答案。
    【解决方案2】:

    我展示了三个替代的单行代码:

    • 使用plyreach函数
    • plyr each 函数与基础R 一起使用
    • 使用矢量化的pminpmax 函数

    解决方案 1:plyr 和 each

    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
    

    【讨论】:

    • 只是展示了这一点。您在 pmin/max colls 中获得 0 的额外奖励积分。我在df1 中允许任意数量的列获得额外奖励积分:P
    • @GavinSimpson 我修改后的答案显示了解决问题的三种替代(单线)方法,其中两种允许任意数量的列。
    • 现在你只是在炫耀! ;-) 好的。解决方案 1 和 2 会很慢(在大问题上会很慢),我们可能不应该鼓励使用非矢量化解决方案而不是矢量化解决方案。但尚不清楚 OP 是否需要针对多变量应用的通用解决方案或针对此特定问题的解决方案。所以我会让无偿的 plyr 用法通过一次;-)
    • 我想要一个多元解决方案。哇,请原谅我在消化所有这些惊人的帮助,我会尽快回复大家。
    • +1 用于完成 plyr() 方面的事情。错误谢谢!我已经尝试了所有三个,加文是正确的。使用我的实际数据(100c x 23000r)和一个稍微复杂一点的过程,通过增量添加列来循环,每个的速度是:do.call(Gavin)是29s,解决方案2是105秒,解决方案1......仍在等待.所以 plyr() 库在这种情况下并不是最好的解决方案。我会继续测试。
    【解决方案3】:

    这里有很多很好的答案。我是在 Gavin Simpson 编辑时开始的,所以我们涵盖了一些类似的领域。并行最小值和最大值(pmin 和 pmax)的作用几乎正是您编写函数的目的。 0 在 pmax(0, Bar1, Bar2) 中的作用可能有点不透明,但本质上 0 会被回收,所以就像在做

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

    这将获取所传递的三件事中的每一项,并找出其中的最大值。因此,如果它是负数并且完成了 ifelse 语句所做的大部分工作,则最大值将为 0。您可以重写,以便获得向量并将事物与您所做的类似的功能结合起来,这可能会使它更加透明。在这种情况下,我们只需将数据帧传递给一个新的并行且快速的 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)]

    可比较的 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

    一些提示

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

    此外,如果您要将内容放入循环或应用语句中,请尽可能少地放在那里。例如,在您的情况下,确实需要将 ifelse() 移出循环并随后应用于整个 MFE 结果。

    【讨论】:

    • 我接受了挑战,并在我修改后的答案中提供了使用plyr正常 minmax 函数的单行解决方案。
    • 谢谢约翰。额外的 0 列的描述很有用,解释 ifelse() 的最佳用途仍在消化中;即循环与整体。我希望有一天能回报大家的恩惠或“将它们传递下去”。
    【解决方案4】:

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

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

    (未测试 - 它应该返回一个包含两个(我认为是命名的)列和与 data.frame 一样多的行的数组)。现在你可以这样做了:

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

    非常恶心。请听从 Gavin 的建议。

    【讨论】:

    • 谢谢,我会坚持 Gavin 的建议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 2014-12-24
    • 1970-01-01
    • 2018-10-29
    相关资源
    最近更新 更多