【问题标题】:R: row-wise dplyr::mutate using function that takes a data frame row and returns an integerR:逐行 dplyr::mutate 使用接受数据框行并返回整数的函数
【发布时间】:2017-05-30 14:03:01
【问题描述】:

我正在尝试使用自定义函数来使用 pipe mutate 语句。我看起来有点像SO post,但徒劳无功。 假设我有一个这样的数据框(其中blob 是一些与特定任务无关的变量,但它是整个数据的一部分):

df <- 
  data.frame(exclude=c('B','B','D'), 
             B=c(1,0,0), 
             C=c(3,4,9), 
             D=c(1,1,0), 
             blob=c('fd', 'fs', 'sa'), 
             stringsAsFactors = F)

我有一个使用变量名称的函数,因此请根据exclude 列中的值选择一些,例如计算 exclude 中未指定的变量的总和(始终为单个字符)。

FUN <- function(df){
  sum(df[c('B', 'C', 'D')] [!names(df[c('B', 'C', 'D')]) %in% df['exclude']] )
}

当我给FUN 提供单行(第1 行)时,我得到CDexclude 未提及的那些)的预期总和,即 4:

FUN(df[1,])

如何在带有 mutate 的管道中进行类似操作(将结果添加到变量 s)。这两个尝试都不起作用:

df %>% mutate(s=FUN(.))
df %>% group_by(1:n()) %>% mutate(s=FUN(.))

更新 这也不能按预期工作:

df %>% rowwise(.) %>% mutate(s=FUN(.))

这是有原因的,但不在 dplyr 的变异(和管道)内:

df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))

【问题讨论】:

  • 你能举个例子吗?这不起作用:df %&gt;% rowwise(.) %&gt;% mutate(s=FUN(.))。 @konvas 的回答显得有些笨拙(请参阅我对@konvas 建议的评论)
  • 对不起,我误会了。你可以df %&gt;% rowwise(.) %&gt;% mutate(s=FUN(data.frame(exclude = exclude, B = B, C = C, D = D))),或df %&gt;% rowwise() %&gt;% nest(exclude:D) %&gt;% mutate(s = map_dbl(data, FUN)) %&gt;% unnest()。问题是dplyr 不会自然地对子数据帧进行操作,而是对列(即向量)进行操作。所以需要一些额外的技巧。
  • 这就是我所追求的! (备案:tidyr::nest()purrr::map_dbl() )。

标签: r row dplyr


【解决方案1】:

如果您想使用dplyr,您可以使用rowwise 和您的函数FUN

df %>% 
    rowwise %>% 
    do({
        result = as_data_frame(.)
        result$s = FUN(result)
        result
    })

同样可以使用group_by 而不是rowwise 来实现(就像你已经尝试过的那样)但是使用do 而不是mutate

df %>% 
    group_by(1:n()) %>% 
    do({
        result = as_data_frame(.)
        result$s = FUN(result)
        result
    })

mutate 在这种情况下不起作用的原因是您将整个 tibble 传递给它,所以这就像调用 FUN(df)

一种更有效的方法是做同样的事情,只是制作一个包含列的矩阵,然后使用rowSums

cols <- c('B', 'C', 'D')
include_mat <- outer(function(x, y) x != y, X = df$exclude, Y = cols)
# or outer(`!=`, X = df$exclude, Y = cols) if it's more readable to you
df$s <- rowSums(df[cols] * include_mat)

【讨论】:

  • 是的,谢谢,但我上面的例子(总和)只是一个玩具例子。我的目标是了解如何在 dplyr 管道 (%&gt;%) 中使用带有 dplyr::mutate 的(ny) 自定义函数。 do() 语句中的建议似乎非常(如您所说)效率低下且有状态 - 有很多更简单的方法。我也可以做得更有效率(但这不是 dplyr mutate 和管道):df$s &lt;- sapply(1:nrow(df), function(x) FUN(df[x,])) .
【解决方案2】:

purrr 接近

为此,我们可以使用nestmap_dbl 的组合:

library(tidyverse)
df %>% 
  rowwise %>% 
  nest(-blob) %>% 
  mutate(s = map_dbl(data, FUN)) %>% 
  unnest

让我们稍微分解一下。首先,rowwise 允许我们应用每个后续函数来支持需要应用于每一行的任意复杂操作。

接下来,nest 将创建一个新列,该列是我们要输入到FUN 的数据列表(tibbles 与 data.frames 之美!)。因为我们正在应用这个rowwise,所以每一行都包含一个exclude:D 的单行小标题。

最后,我们使用map_dblFUN 映射到每个小标题。 map_dbl 用于其他 map_* 函数系列,因为我们的预期输出是数字(即双精度)。

unnest 将我们的 tibble 返回到更标准的结构中。

purrrlyr接近

虽然purrrlyr 可能不像其父级dplyrpurrr 那样“受欢迎”,但它的by_row 函数在这里有一些实用性。

在您上面的示例中,我们将通过以下方式使用您的数据框df 和用户定义的函数FUN

df %>% 
  by_row(..f = FUN, .to = "s", .collate = "cols")

就是这样!给你:

# tibble [3 x 6]
  exclude     B     C     D  blob     s
    <chr> <dbl> <dbl> <dbl> <chr> <dbl>
1       B     1     3     1    fd     4
2       B     0     4     1    fs     5
3       D     0     9     0    sa     9

诚然,语法有点奇怪,但它是这样分解的:

  • ..f = 应用于每一行的函数
  • .to = 输出列的名称,在本例中为 s
  • .collate = 按列表、行或列排列结果的方式。由于FUN 只有一个输出,我们可以使用"cols""rows"

有关使用purrrlyr 的更多信息,请参阅here...


性能

预警,虽然我喜欢by_row 的功能,但它并不总是最好的性能方法! purrr 更直观,但速度损失也相当大。见下面microbenchmark 测试:

library(microbenchmark)
mbm <- microbenchmark(
  purrr.test = df %>% rowwise %>% nest(-blob) %>% 
    mutate(s = map_dbl(data, FUN)) %>% unnest,
  purrrlyr.test = df %>% by_row(..f = FUN, .to = "s", .collate = "cols"),
  rowwise.test = df %>% 
    rowwise %>% 
    do({
      result = as_tibble(.)
      result$s = FUN(result)
      result
    }),
  group_by.test = df %>% 
    group_by(1:n()) %>% 
    do({
      result = as_tibble(.)
      result$s = FUN(result)
      result
    }),
  sapply.test = {df$s <- sapply(1:nrow(df), function(x) FUN(df[x,]))}, 
  times = 1000
)
autoplot(mbm)

您可以看到purrrlyr 方法比使用dorowwisegroup_by(1:n()) 组合的方法更快(请参阅@konvas 答案),并且与sapply 方法相当.但是,该软件包无疑不是最直观的。标准的purrr 方法似乎是最慢的,但也可能更容易使用。不同的用户自定义函数可能会改变速度顺序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    相关资源
    最近更新 更多