R语言mutate()中依据某列值动态查找对应列值的方案咨询
问题:如何根据tibble内部某列的取值,动态提取对应名称的列的值?
我现在需要在data frame/tibble中,根据var列的取值,匹配提取对应名称的列的取值。目前通过硬编码case_when()可以实现,代码如下:
library(tidyverse) set.seed(1) ds <- tibble(var = paste0("x", sample(1:3, 10, replace = T)), x1 = 0:9, x2 = 100:109, x3 = 1000:1009) ds %>% mutate(result = case_when(var == "x1" ~ x1, var == "x2" ~ x2, var == "x3" ~ x3)) #> # A tibble: 10 x 5 #> var x1 x2 x3 result #> <chr> <int> <int> <int> <int> #> 1 x1 0 100 1000 0 #> 2 x3 1 101 1001 1001 #> 3 x1 2 102 1002 2 #> 4 x2 3 103 1003 103 #> 5 x1 4 104 1004 4 #> 6 x3 5 105 1005 1005 #> 7 x3 6 106 1006 1006 #> 8 x2 7 107 1007 107 #> 9 x2 8 108 1008 108 #> 10 x3 9 109 1009 1009
但如果存在大量xn格式的列,硬编码方式完全不适用。我发现如下写法可以处理外部变量的查找场景:
y <- "x2" ds %>% mutate(result = !!sym(y)) #> # A tibble: 10 x 5 #> var x1 x2 x3 result #> <chr> <int> <int> <int> <int> #> 1 x1 0 100 1000 100 #> 2 x3 1 101 1001 101 #> 3 x1 2 102 1002 102 #> 4 x2 3 103 1003 103 #> 5 x1 4 104 1004 104 #> 6 x3 5 105 1005 105 #> 7 x3 6 106 1006 106 #> 8 x2 7 107 1007 107 #> 9 x2 8 108 1008 108 #> 10 x3 9 109 1009 109
但该方法直接调用tibble内部的var列时会报错:
ds %>% mutate(result = !!sym(var)) #> Error: Only strings can be converted to symbols
以上示例由reprex包 (v2.0.0)在2021-05-24创建。
请问如何实现基于tibble内部列值动态查找对应列值的需求?
解答
首先解释报错原因:!! 属于rlang的元编程语法,是在表达式提交给dplyr执行前就完成求值的,这时候var还是完整的列向量,sym()无法把向量转为单个符号,自然报错。这种语法只适合处理全局固定的外部变量,没法处理逐行变化的内部列值。
下面给两种常用的实现方案:
方案1:逐行处理(逻辑直观,适合中小数据量)
用rowwise()将数据按行拆分后,逐行读取var的值提取对应列即可:
ds %>% rowwise() %>% mutate(result = c_across(all_of(var))) %>% ungroup()
如果你的dplyr版本比较旧,也可以用get()配合当前数据环境取值:
ds %>% rowwise() %>% mutate(result = get(var, cur_data())) %>% ungroup()
方案2:矩阵索引(效率极高,适合大数据量)
直接通过「行号+列位置」的矩阵索引定位取值,不需要逐行遍历,性能远高于逐行处理方案:
ds %>% mutate(result = .[cbind(1:nrow(.), match(var, colnames(.)))])
两种方案输出结果和你硬编码case_when的结果完全一致。
内容的提问来源于stack exchange,提问作者AGeographer
相关产品推荐
相关产品推荐

