r - 使用带有 a 的 pmap 将不同的正则表达式应用于 tibble 中的不同变量?
问题描述
这个问题非常类似于Using pmap to apply different regular expressions to different variables in a tibble? ,但有所不同,因为我意识到我的示例不足以描述我的问题。
我正在尝试将不同的正则表达式应用于小标题中的不同变量。例如,我制作了一个 tibble 列表 1) 我要修改的变量名,2) 我要匹配的正则表达式,以及 3) 替换字符串。我想将正则表达式/替换应用于不同数据框中的变量。请注意,目标 tibble 中可能有我不想修改的变量,并且我的“配置”tibble 中的行顺序可能与我的“目标”tibble 中的列/变量顺序不对应。
所以我的“配置”标题可能如下所示:
test_config <- dplyr::tibble(
string_col = c("col1", "col2", "col4", "col3"),
pattern = c("^\\.$", "^NA$", "^$", "^NULL$"),
replacement = c("","","", "")
)
我想将此应用于目标小标题:
test_target <- dplyr::tibble(
col1 = c("Foo", "bar", ".", "NA", "NULL"),
col2 = c("Foo", "bar", ".", "NA", "NULL"),
col3 = c("Foo", "bar", ".", "NA", "NULL"),
col4 = c("NULL", "NA", "Foo", ".", "bar"),
col5 = c("I", "am", "not", "changing", ".")
)
因此,目标是在 test_target 的用户指定列/变量中用空字符串替换不同的字符串。
结果应该是这样的:
result <- dplyr::tibble(
col1 = c("Foo", "bar", "", "NA", "NULL"),
col2 = c("Foo", "bar", ".", "", "NULL"),
col3 = c("Foo", "bar", ".", "NA", ""),
col4 = c("NULL", "NA", "Foo", ".", "bar"),
col5 = c("I", "am", "not", "changing", ".")
)
我可以用 for 循环做我想做的事,如下所示:
for (i in seq(nrow(test_config))) {
test_target <- dplyr::mutate_at(test_target,
.vars = dplyr::vars(
tidyselect::matches(test_config$string_col[[i]])),
.funs = dplyr::funs(
stringr::str_replace_all(
., test_config$pattern[[i]],
test_config$replacement[[i]]))
)
}
相反,有没有更整洁的方式来做我想做的事?到目前为止,我认为这purrr::pmap
是完成这项工作的工具,我制作了一个函数,它接受数据框、变量名称、正则表达式和替换值,并返回修改了单个变量的数据框。它的行为符合预期:
testFun <- function(df, colName, regex, repVal){
colName <- dplyr::enquo(colName)
df <- dplyr::mutate_at(df,
.vars = dplyr::vars(
tidyselect::matches(!!colName)),
.funs = dplyr::funs(
stringr::str_replace_all(., regex, repVal))
)
}
# try with example
out <- testFun(test_target,
test_config$string_col[[1]],
test_config$pattern[[1]],
"")
但是,当我尝试将该函数与 一起使用时pmap
,我遇到了几个问题:1) 有没有比这更好的方法来构建 pmap 调用的列表?
purrr::pmap(
list(test_target,
test_config$string_col,
test_config$pattern,
test_config$replacement),
testFun
)
2)当我打电话时pmap
,我收到一个错误:
Error: Element 2 has length 4, not 1 or 5.
所以pmap
我不高兴我试图将长度为 5 的 tibble 作为其他元素长度为 4 的列表的元素传递(我认为它会回收 tibble)。
另请注意,以前,当我pmap
使用 4-row tibble 调用时,我得到了一个不同的错误,
Error in UseMethod("tbl_vars") :
no applicable method for 'tbl_vars' applied to an object of class "character"
Called from: tbl_vars(tbl)
你们中的任何人都可以提出一种使用 pmap 来做我想做的事情的方法,或者是否有不同或更好的 tidyverse 方法来解决这个问题?
谢谢!
解决方案
这里有两种tidyverse
方法。一个类似于data.table
答案,因为它涉及重塑数据,将其与配置连接,并重新整形回宽。另一种是purrr
基于 - 的,在我看来,这是一种奇怪的方法。我推荐第一个,因为它感觉更直观。
用于tidyr::gather
使数据长形,然后dplyr::left_join
确保来自的每个文本值test_target
都有相应的模式和替换——即使是没有模式的情况(col5)也将通过使用左连接来保留。
library(tidyverse)
...
test_target %>%
gather(key = col, value = text) %>%
left_join(test_config, by = c("col" = "string_col"))
#> # A tibble: 25 x 4
#> col text pattern replacement
#> <chr> <chr> <chr> <chr>
#> 1 col1 Foo "^\\.$" ""
#> 2 col1 bar "^\\.$" ""
#> 3 col1 . "^\\.$" ""
#> 4 col1 NA "^\\.$" ""
#> 5 col1 NULL "^\\.$" ""
#> 6 col2 Foo ^NA$ ""
#> 7 col2 bar ^NA$ ""
#> 8 col2 . ^NA$ ""
#> 9 col2 NA ^NA$ ""
#> 10 col2 NULL ^NA$ ""
#> # ... with 15 more rows
使用ifelse
替换模式存在的模式,或者如果模式不存在则保留原始文本。只保留必要的模式,添加行号,因为spread
需要唯一的 ID,并再次使数据变宽。
test_target %>%
gather(key = col, value = text) %>%
left_join(test_config, by = c("col" = "string_col")) %>%
mutate(new_text = ifelse(is.na(pattern), text, str_replace(text, pattern, replacement))) %>%
select(col, new_text) %>%
group_by(col) %>%
mutate(row = row_number()) %>%
spread(key = col, value = new_text) %>%
select(-row)
#> # A tibble: 5 x 5
#> col1 col2 col3 col4 col5
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Foo Foo Foo NULL I
#> 2 bar bar bar NA am
#> 3 "" . . Foo not
#> 4 NA "" NA . changing
#> 5 NULL NULL "" bar .
第二种方法是制作一个仅包含列名的小标题,将其与配置连接,然后拆分为列表列表。然后purrr::map2_dfc
映射到您创建的这个列表和 的列,并通过ingtest_target
返回一个数据框。cbind
这样做的原因是数据框在技术上是列的列表,因此如果您映射到数据框,您会将每一列视为列表项。我无法ifelse
在这里工作——逻辑中的某些东西只有单个字符串而不是整个向量返回。
tibble(all_cols = names(test_target)) %>%
left_join(test_config, by = c("all_cols" = "string_col")) %>%
split(.$all_cols) %>%
map(as.list) %>%
map2_dfc(test_target, function(info, text) {
if (is.na(info$pattern)) {
text
} else {
str_replace(text, info$pattern, info$replacement)
}
})
#> # A tibble: 5 x 5
#> col1 col2 col3 col4 col5
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Foo Foo Foo NULL I
#> 2 bar bar bar NA am
#> 3 "" . . Foo not
#> 4 NA "" NA . changing
#> 5 NULL NULL "" bar .
由reprex 包(v0.2.1)于 2018 年 10 月 30 日创建
推荐阅读
- c# - Unity如何正确获取字符宽度
- python - 如果我们将整数作为输入,为什么 set 中的 pop 函数会弹出第一个元素?
- firebase - 通过 cmd $flutter pub add firebase_auth 添加 firesbase 时,文件 generated_plugin_registrant.dart 显示错误(红色)
- python - 创建唯一组名的请求
- ssas - 在表格立方体模型的 MDX 查询中使用计算成员中的日期范围
- javascript - 如何在保持堆栈导航屏幕使用的同时将类更改为函数?
- mysql - 无法通过 MySQL Workbench 本地连接 MySQL
- django - 通过 Ajax 调用将 ManyToMany Queryset 数据传递给 django 模板时遇到问题
- javascript - 如何使用 React js 更改元掩码中的网络
- python - 在python中自动化长列表