dplyr 通过评估查找单元格值来改变特定列

dplyr mutate specific columns by evaluating lookup cell value

我已经探索了使用 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 即可完成工作。如果列 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
)

这是使用 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 return 是一个列表,我们只需要向量。
  4. 使用此 cols 向量从 row 和 return 中获取所需的值,折叠成长度为 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-03。

这是一种不依赖于 applymap 系列中的迭代函数的不同方式,如果您希望避免它们,并尝试利用 tidyr tidyverse 的一侧。该方法基本上是将 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-03。