【问题标题】:Mutating columns of a data frame based on a predicate function (dplyr::mutate_if)基于谓词函数(dplyr::mutate_if)改变数据帧的列
【发布时间】:2016-11-10 04:23:57
【问题描述】:

我想使用 dplyr 的 mutate_if() 函数将 list-columns 转换为 data-frame-columns,但是当我尝试这样做时遇到了一个令人费解的错误。我正在使用 dplyr 0.5.0、purrr 0.2.2、R 3.3.0。

基本设置如下:我有一个数据框d,其中一些列是列表:

d <- dplyr::data_frame(
  A = list(
    list(list(x = "a", y = 1), list(x = "b", y = 2)),
    list(list(x = "c", y = 3), list(x = "d", y = 4))
  ),
  B = LETTERS[1:2]
)

我想使用以下函数将列表列(在本例中为 d$A)转换为数据框列:

tblfy <- function(x) {
  x %>%
    purrr::transpose() %>%
    purrr::simplify_all() %>%
    dplyr::as_data_frame()
}

也就是说,我希望将列表列d$A 替换为列表lapply(d$A, tblfy),即

[[1]]
#  A tibble: 2 x 2
      x     y
  <chr> <dbl>
1     a     1
2     b     2

[[2]]
# A tibble: 2 x 2
      x     y
  <chr> <dbl>
1     c     3
2     d     4

当然,在这个简单的情况下,我可以做一个简单的重新分配。然而,关键是我想以编程方式执行此操作,理想情况下使用 dplyr,以一种可以处理任意数量的列表列的普遍适用的方式。

这是我绊倒的地方:当我尝试使用以下应用程序将列表列转换为数据框列时

d %>% dplyr::mutate_if(is.list, funs(tblfy))

我收到一条我不知道如何解释的错误消息:

Error: Each variable must be named.
Problem variables: 1, 2

为什么mutate_if() 会失败?如何正确应用它以获得所需的结果?

备注

一位评论者指出函数tblfy() 应该被向量化。这是一个合理的建议。但是——除非我向量化不正确——这似乎不是问题的根源。插入tblfy() 的矢量化版本,

tblfy_vec <- Vectorize(tblfy)

进入mutate_if() 失败并出现错误

Error: wrong result size (4), expected 2 or 1

更新

在获得一些 purrr 经验后,我现在发现以下方法很自然,虽然有些冗长:

d %>%
  map_if(is.list, ~ map(., ~ map_df(., identity))) %>%
  as_data_frame()

这与下面@alistaire 的解决方案或多或少相同,但分别使用map_if()map(),代替mutate_if(),分别。 Vectorize().

【问题讨论】:

  • 那么预期的输出究竟是什么?您想将 A 从列表列表更改为小标题列表吗?
  • 你的函数没有向量化,它只接受一个列表。看看tblfy(d$A)。因为d$A 中有两个列表,所以出现错误。你不是在比较苹果和苹果。在您的lapply(d$A, tblfy) 中,您一次为您的函数提供一个列表,这就是它起作用的原因。 tblfy(d$A[[1]])tblfy(d$A[[2]])。在您的 dplyr 函数中,您提供了两个列表。更改 tblfy 以接受多个列表,或更改 dplyr 调用。或者正如 MrFlick 所要求的,更广泛地考虑您正在构建的内容。
  • @MrFlick 我已经编辑了问题,以明确所需的输出。现在清楚了吗?
  • @PierreLafortune 好点。我已经尝试过矢量化,但仍然失败。请参阅已编辑的问题。大概我矢量化不正确。但是怎么做?奇怪的是,当我将tblfy_vec() 直接应用到d$A 时,我得到了一个 4 的列表,这与我的理解完全不同,即矢量化创建了一个对列表(或向量)组件进行操作的函数。
  • 尝试插入一个应用函数。地图或 lapply

标签: r dplyr purrr


【解决方案1】:

原来的 tblfy 函数对我来说出错了(即使它的元素直接链接),所以让我们重新构建它,同时添加矢量化,这样我们就可以避免之前必要的 rowwise() 调用:

tblfy <- Vectorize(function(x){x %>% purrr::map_df(identity) %>% list()})

现在我们可以很好地使用mutate_if

d %>% mutate_if(purrr::is_list, tblfy)
## Source: local data frame [2 x 2]
## 
##                A     B
##           <list> <chr>
## 1 <tbl_df [2,2]>     A
## 2 <tbl_df [2,2]>     B

...如果我们想看看那里有什么,

d %>% mutate_if(purrr::is_list, tblfy) %>% tidyr::unnest()
## Source: local data frame [4 x 3]
## 
##       B     x     y
##   <chr> <chr> <dbl>
## 1     A     a     1
## 2     A     b     2
## 3     B     c     3
## 4     B     d     4

几个注意事项:

  • map_df(identity) 在构建 tibble 方面似乎比任何替代配方都更有效。我知道identity 调用似乎没有必要,但其他大多数情况都会中断。
  • 我不确定tblfy 的用途会有多广泛,因为它在某种程度上取决于列表列中的列表结构,这可能会有很大差异。如果你有很多类似的结构,我想它很有用。
  • 可能有一种方法可以使用 pmap 而不是 Vectorize,但我无法通过一些粗略的尝试来实现它。

【讨论】:

  • 谢谢,就这样!您使用map_df()tblfy() 版本比我的更简洁。没想到会那样做。事实上,查看map_df() 的源代码可以解释为什么您的解决方案有效,特别是为什么需要list()(这让我一开始很困惑):因为map_df 实际上是map,后面跟着bind_rows,省略 list() 将导致数据框尺寸为 4 x 2。
【解决方案2】:

无需任何复制的就地转换:

library(data.table)

for (col in d) if (is.list(col)) lapply(col, setDF)

d
#Source: local data frame [2 x 2]
#
#                A B
#1 <S3:data.frame> A
#2 <S3:data.frame> B

【讨论】:

  • 不回答关于mutate_if 的原始问题,但对基本问题来说是一个非常好的替代解决方案。 data.table 对我来说是新的。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-13
  • 2013-07-28
  • 1970-01-01
  • 2018-09-20
相关资源
最近更新 更多