【问题标题】:Apply function to heterogeneous rows of data.frame将函数应用于 data.frame 的异构行
【发布时间】:2017-01-16 14:45:34
【问题描述】:

假设我有一些(非矢量化)函数foo

foo <- function (bar, baz, frobozz, frotz = 42) {
    if (frobozz) {
        frotz
    }
    else {
        bar * nchar(baz)
    }
}

毫无疑问,这是一个愚蠢的功能,但出于这个问题的目的,将其视为给定的。 (IOW,基于修改 foo 的答案超出范围。)

另外,假设我有data.framedf,如下图:

> df
  frobozz bar baz
1    TRUE   1   a
2   FALSE   2   b
3    TRUE   3   c
4   FALSE   4   d
5    TRUE   5   e

现在,df 的每一行都可以看作是一个异构命名列表(我以后将其缩写为record)。

事实上,将df 的任何行转换为这样的记录并不难:

> df[1, , drop = TRUE]
$frobozz
[1] TRUE

$bar
[1] 1

$baz
[1] "a"

此外,任何命名槽的此类记录中的值都属于适合作为foo签名中同名参数的类型。

这意味着我可以使用do.callfoo 应用于df 的任何行:

> do.call(foo, df[1, , drop = TRUE])
[1] 42
> do.call(foo, df[2, , drop = TRUE])
[1] 2

(请注意,即使df 的列的顺序和foo 所需参数的顺序不匹配,这仍然有效。)

现在,我想通过将foo 应用于df每一行 来创建一个新列。

我曾希望apply 能够胜任这项任务,但它失败了:

> apply(df, 1, foo)
Error in FUN(newX[, i], ...) : 
  argument "frobozz" is missing, with no default

当然,我可以诉诸这样的事情:

sapply(1:nrow(df), function (i) { do.call(foo, df[i, , drop = TRUE]) })

有没有一种看起来不那么无知的方法来实现这一点?


这是这个问题的一个变体,可能更容易处理。

考虑函数foo_wrapper

foo_wrapper <- function ( record ) {
    foo( record$bar, record$baz, record$frobozz )
}

此函数比foo 更灵活,因为它只需要它的参数record 具有名为barbazfrobozz 的元素;它不关心它可能具有的任何其他元素。此外,可以将foo_wrapper 直接应用到df 的行,而不必求助于do.call

> foo_wrapper(df[4, , drop = TRUE])
[1] 4

不幸的是,apply 也以 foo_wrapper 失败:

> apply(df, 1, foo_wrapper)
Error in record$frobozz : $ operator is invalid for atomic vectors

【问题讨论】:

  • ?mapply。试试do.call(mapply,c(foo,df))
  • 我猜this 可能有用。请参阅 ?mapply 或其包装器 ?Vectorize -- do.call(Vectorize(foo), df)
  • @Hack-R baz 列必须是字符才能按foo 的顺序工作。
  • @nicola:谢谢!我在发帖之前尝试过mapply,但毫无头绪:例如mapply(foo, df)mapply(foo, df[, c("bar", "baz", "frobozz")]),导致出现的错误类似于我在使用 apply 时遇到的错误。一百万年后,我不会想到通过do.call 做到这一点。它当然工作得很好,但我仍然对这个结构有点迷惑。您介意发表您的评论及其背后的原因吗?

标签: r


【解决方案1】:

您可以只 Vectorize 您的函数,然后使用 with() 访问变量。例如您的示例数据...

dd <- read.table(text="frobozz bar baz
1    TRUE   1   a
2   FALSE   2   b
3    TRUE   3   c
4   FALSE   4   d
5    TRUE   5   e", header=T, stringsAsFactors=F)

然后就可以运行了

with(dd, Vectorize(foo)(frobozz=frobozz, bar=bar, baz=baz))
# [1] 42  2 42  4 42

【讨论】:

  • 非常优雅。 +1!
猜你喜欢
  • 2021-06-30
  • 2015-01-18
  • 2011-12-28
  • 2018-07-17
  • 1970-01-01
  • 2012-05-25
  • 2015-02-23
  • 2016-03-20
  • 2017-09-02
相关资源
最近更新 更多