20

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

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

# 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

4 回答 4

19

我认为你在这里想得太复杂了。两个单独的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()andpmin()函数,并意识到它们可以做每个apply(df1, 1, FindFOO()调用正在做的事情。例如:

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

将是您的问题中的 MFE。Bar1如果您有两列并且它们是和Bar2或 的前 2 列,这很容易使用df1。但这不是很笼统;如果你有多个列要计算它等等怎么办?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()使用and获得通用解决方案的诀窍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
于 2011-08-24T10:30:36.610 回答
19

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

  • 使用的each功能plyr
  • 使用plyr each带有基础 R 的函数
  • 使用向量化的pminandpmax函数

解决方案 1:plyr 和 each

plyr包定义了each执行您想要的功能。From ?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
于 2011-08-24T10:54:14.433 回答
6

这里有很多很好的答案。我是在 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)] <- 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 <- findMFEandMAE(df1) df1 <- cbind(df1, MFEandMAE)

一些技巧

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

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

于 2011-08-24T11:39:08.330 回答
1

如果您真的非常想要它,您可以:

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

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

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

很恶心。请听从加文的建议。

于 2011-08-24T10:49:24.280 回答