【问题标题】:dplyr mutate specific columns by evaluating lookup cell valuedplyr 通过评估查找单元格值来改变特定列
【发布时间】:2019-05-05 02:19:56
【问题描述】:

我已经探索了使用 quosures、符号和评估的各种选项,但我似乎无法获得正确的语法。这是一个示例数据框。

data.frame("A" = letters[1:4], "B" = letters[26:23], "C" = letters[c(1,3,5,7)], "D" = letters[c(2,4,6,8)], "pastecols" = c("B, C","B, D", "B, C, D", NA))
  A B C D pastecols
1 a z a b      B, C
2 b y c d      B, D
3 c x e f   B, C, D
4 d w g h      <NA>

现在假设我想根据 pastecols 中的查找字符串粘贴来自不同列的值,并且我总是想包含 A 列。这是我想要的结果:

  A B C D pastecols  result
1 a z a b      B, C   a z a
2 b y c d      B, D   b y d
3 c x e f   B, C, D c x e f
4 d w g h      <NA>       d

理想情况下,这可以在 dplyr 中完成。这是我得到的最接近的:

x %>% mutate(result = lapply(lapply(str_split(pastecols, ", "), c, "A"), na.omit))
  A B C D pastecols     result
1 a z a b      B, C    B, C, A
2 b y c d      B, D    B, D, A
3 c x e f   B, C, D B, C, D, A
4 d w g h      <NA>          A

【问题讨论】:

    标签: r dplyr eval


    【解决方案1】:

    这是使用pmap 做类似事情的一种方法。 pmap 可用于通过将每一行捕获为命名向量来有效地逐行处理数据帧;然后,您可以通过使用["pastecols"] 选择它们来获取所需的列名以作为cols 进行索引。

    大多数匿名函数语法不是tidyverse 的东西,而只是基本的R 东西。走过它:

    1. 将数据框作为列表传递给pmap_chr.l 参数。请记住,数据框是列的列表!
    2. 使用c(...) 捕获所有... 参数。基本上我们将数据帧的每一行作为函数的参数调用;现在row 是包含该行的命名向量。请注意,如果您有列表列,这将中断,(但这里还有很多其他的东西,所以我假设没有......)
    3. 我们可以从row["pastecols"] 中获得我们想要的row 的值,但是我们需要将(比如)"B, C" 转换为c("A", "B", "C") 才能做到这一点。下一行只是添加"A",用"A" 替换缺失值,如果有的话,将其分成几部分,然后向下索引到列表中。 [[ 部分就是您在管道链中执行list[[1]]" 的方式,它是运算符的前缀形式。你需要这个,因为str_split 返回一个列表,而我们只需要向量。
    4. 使用此 cols 向量从 row 获取所需值并将其返回,折叠成长度为 1 的字符向量!
    library(tidyverse)
    tbl <- tibble("A" = letters[1:4], "B" = letters[26:23], "C" = letters[c(1,3,5,7)], "D" = letters[c(2,4,6,8)], "pastecols" = c("B, C","B, D", "B, C, D", NA))
    
    tbl %>%
      mutate(result = pmap_chr(
        .l = .,
        .f = function(...){
          row <-  c(...)
          cols <- row["pastecols"] %>% str_c("A, ", .) %>% replace_na("A") %>% str_split(", ") %>% `[[`(1)
          vals <- row[cols] %>% str_c(collapse = ", ")
          return(vals)
        }
      ))
    #> # A tibble: 4 x 6
    #>   A     B     C     D     pastecols result    
    #>   <chr> <chr> <chr> <chr> <chr>     <chr>     
    #> 1 a     z     a     b     B, C      a, z, a   
    #> 2 b     y     c     d     B, D      b, y, d   
    #> 3 c     x     e     f     B, C, D   c, x, e, f
    #> 4 d     w     g     h     <NA>      d
    

    reprex package (v0.2.0) 于 2018 年 12 月 3 日创建。

    【讨论】:

    • 这种方法似乎比您使用收集的建议快 5 倍。加分是因为我一直在尝试学习 purrr 中的新功能!您介意解释 pmap_chr 中的函数定义中发生了什么吗?具体来说,function(...)、c(...) 和 %>% [[(1)。
    • 添加了一些解释。我怀疑如果它目前仍然太慢,可以通过在pmap 之外执行cols 部分来加快速度,因为str_cstr_split 是矢量化的,但它需要一些代码改组。跨度>
    • 感谢您的更新。我对列表运算符很熟悉,我只是从未在这样的管道链中见过它。至于省略号,我只在函数文档中看到过,很高兴我现在知道如何有效地使用它!
    【解决方案2】:

    不是最优雅的解决方案,但只需使用基本 R 即可完成工作。如果列 A 从未出现在 pastecols 中,您可以从代码中删除 unique()

    for(r in seq_len(nrow(df))) {
      df$result[r] <- paste(
                        df[r, na.omit(unique(c("A", unlist(strsplit(df$pastecols[r], ", ")))))],
                        collapse = " "
                      )
    }
    df
    
      A B C D pastecols  result
    1 a z a b      B, C   a z a
    2 b y c d      B, D   b y d
    3 c x e f   B, C, D c x e f
    4 d w g h      <NA>       d
    

    数据 -

    df <- data.frame(
      "A" = letters[1:4], 
      "B" = letters[26:23], 
      "C" = letters[c(1,3,5,7)], 
      "D" = letters[c(2,4,6,8)], 
      "pastecols" = c("B, C","B, D", "B, C, D", NA), stringsAsFactors = F
    )
    

    【讨论】:

      【解决方案3】:

      这是一种不同的方法,它不依赖于 applymap 系列中的迭代函数,如果您希望避免它们,并尝试利用 tidyversetidyr 侧。该方法基本上是将带有gatherseparate_rows 的数据框扩展为pastecols 和实际列的每个组合,然后是filter,因此我们只保留与每个rowid 匹配的那些。一旦我们有了它,我们可以group_bysummarise 将它带回每rowid 的一行。有一堆家务来处理你总是有列 A 的事实,请注意我在输出 pastecols 中留下了 A,但如果你愿意,你可以删除它。

      library(tidyverse)
      tbl <- tibble("A" = letters[1:4], "B" = letters[26:23], "C" = letters[c(1,3,5,7)], "D" = letters[c(2,4,6,8)], "pastecols" = c("B, C","B, D", "B, C, D", NA))
      
      tbl %>%
        rowid_to_column() %>%
        mutate(
          pastecols = str_c("A, ", pastecols),
          pastecols = if_else(is.na(pastecols), "A", pastecols)
        ) %>%
        gather(colname, value, -pastecols, -rowid) %>%
        separate_rows(pastecols) %>%
        filter(pastecols == colname) %>%
        group_by(rowid) %>%
        summarise(
          pastecols = str_c(pastecols, collapse = ", "),
          result = str_c(value, collapse = ", ")
        )
      #> # A tibble: 4 x 3
      #>   rowid pastecols  result    
      #>   <int> <chr>      <chr>     
      #> 1     1 A, B, C    a, z, a   
      #> 2     2 A, B, D    b, y, d   
      #> 3     3 A, B, C, D c, x, e, f
      #> 4     4 A          d
      

      reprex package (v0.2.0) 于 2018 年 12 月 3 日创建。

      【讨论】:

      • 我喜欢这个例子坚持事物的 tidyverse 方面。我想我试图在 quosures 等方面变得太有创意了,当 collect 和 separate_rows 非常适合这项工作时。我确实担心我将如何在实践中应用它的计算时间。玩具示例很简单,但我将应用它的数据有 15 个“pastecols”和约 6M 行。我必须尝试一下,但我想即使我使用的是强大的服务器,我也可能会遇到一些内存溢出错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多