如何在未实现tidyselect选择助手的函数中使用:等选择工具?
如何在不支持tidyselect选择助手的函数中使用这些工具?
你可能遇到过这种情况:有些dplyr函数(比如distinct())并没有实现tidyselect的选择助手语法(比如用:选择连续列范围),直接使用会抛出错误,比如:
library(dplyr) mtcars %>% distinct(vs:gear, .keep_all = TRUE) #> Warning in vs:gear: numerical expression has 32 elements: only the first used #> Warning in vs:gear: numerical expression has 32 elements: only the first used #> Error: Column `vs:gear` must be length 32 (the number of rows) or one, not 5
下面我们来一步步优化解决方案,找到更简洁优雅的实现方式:
方法1:借助select()间接获取列名
这是你最初尝试的方案,利用select()原生支持选择助手的特性,先拿到目标列的名称,再传递给distinct():
mtcars %>% distinct(!!! syms(names(select(., vs:gear))), .keep_all = TRUE)
这个方法能生效,但写法偏冗长,复用性也不强。
方法2:直接调用tidyselect底层解析函数
通过tidyselect::eval_select()直接解析选择表达式,拿到目标列的位置与名称,再传递给distinct():
# 需要tidyselect >= 1.0版本 mtcars %>% distinct(!!! syms(names(tidyselect::eval_select(quote(vs:gear), .))), .keep_all = TRUE)
这个方法本质和方法1类似,但依赖tidyselect的底层函数,可读性并没有提升。
方法3:自定义可复用的包装函数(最优解)
如果需要多次使用这类功能,最优雅的方式是给distinct()写一个支持tidyselect的包装函数,后续调用就和其他dplyr函数一样便捷:
distinct2 <- function(.data, ..., .keep_all = FALSE) { # 捕获传入的选择表达式 expr <- rlang::expr(c(...)) # 用tidyselect解析表达式,得到目标列的位置信息 pos <- tidyselect::eval_select(expr, data = .data) # 将解析后的列名传递给原生distinct()函数 dplyr::distinct(.data = .data, .keep_all = .keep_all, !!! syms(names(pos))) } # 调用方式和原生函数几乎一致,还支持所有tidyselect选择助手 mtcars %>% distinct2(vs:gear, .keep_all = TRUE)
这个包装函数的优势很明显:
- 复用性强,一次定义就能反复使用
- 调用语法和原生
distinct()完全兼容,几乎没有学习成本 - 支持所有tidyselect选择助手(比如
starts_with()、contains()等),不止是:的范围选择
运行效果
执行上述代码后,就能得到正确的去重结果:
#> mpg cyl disp hp drat wt qsec vs am gear carb #> Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 #> Mazda RX4 Wag 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 #> Datsun 710 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 #> Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 #> Merc 240D 24.4 4 147 62 3.69 3.190 20.00 1 0 4 2 #> Porsche 914-2 26.0 4 120 91 4.43 2.140 16.70 0 1 5 2 #> Lotus Europa 30.4 4 95 113 3.77 1.513 16.90 1 1 5 2
内容的提问来源于stack exchange,提问作者Aurèle
相关产品推荐
相关产品推荐

