如何在R函数中评估字符向量元素以动态创建数据变量
生成水果虚拟变量的问题解决
问题背景
给定如下R数据集:
library(tibble) df <- data.frame( basket = c("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8"), fruits = c("orange, apple", "apple", "pineapple, banana", "strawberry, apple, watermelon", "blueberry, pear", "apple, papaya", "apple", "strawberry") )
其中basket为篮子名称,fruits是每个篮子的水果列表(逗号分隔)。需要为指定水果(apple、strawberry、pineapple)生成虚拟变量,标记每个篮子是否包含对应水果(1=包含,0=不包含)。
用户尝试编写自定义函数批量生成变量,但遇到错误:
fruits_list <- c("apple", "strawberry", "papaya") create_ind_vars <- function(df, num) { df %>% mutate("ind_{{num}}" := if_else( charges == !! rlang::sym(paste0(num)) | str_starts(charges, !! rlang::sym(paste0(num, ","))) | str_detect(charges, !! rlang::sym(paste0(",", num, ","))) | str_ends(charges, !! rlang::sym(paste0(",", num))), 1, 0 )) } testdat <- create_ind_vars(df, fruits_list)
报错信息:
Error in `rlang::sym()`: ! Can't convert a character vector to a symbol. Run `rlang::last_trace()` to see where the error occurred.
错误原因
- 变量名误写:函数中
charges应为数据集里的fruits列; - 语法误用:
rlang::sym()用于转换单个字符为符号,但传入的是字符向量,且无需用该函数——直接字符串匹配即可; - 函数逻辑缺陷:设计为处理单个水果,但直接传入向量导致批量处理失败;
- 变量命名错误:
"ind_{{num}}"的写法不符合动态命名语法,需结合glue风格语法与:=使用。
解决方案
方法1:使用dplyr::across(推荐,简洁高效)
利用across批量处理指定水果,结合stringr::str_detect精准匹配字符串,同时用glue语法生成变量名:
library(dplyr) library(stringr) target_fruits <- c("apple", "strawberry", "pineapple") df_result <- df %>% mutate( across( all_of(target_fruits), ~as.integer(str_detect(fruits, regex(paste0("\\b", .x, "\\b"), ignore_case = FALSE))), .names = "{.col}" ) ) print(df_result)
说明:
\\b是单词边界,避免匹配pineapple时误判包含apple;as.integer将逻辑值(TRUE/FALSE)转换为1/0;.names = "{.col}"直接用水果名作为新变量名,符合需求格式。
方法2:修正自定义函数+循环迭代
若坚持使用自定义函数,调整为处理单个水果,再循环遍历目标列表:
library(dplyr) library(stringr) create_ind_var <- function(df, fruit) { df %>% mutate( !!fruit := as.integer( str_detect(fruits, regex(paste0("\\b", fruit, "\\b"))) ) ) } target_fruits <- c("apple", "strawberry", "pineapple") df_result <- df for (fruit in target_fruits) { df_result <- create_ind_var(df_result, fruit) } print(df_result)
也可使用purrr::reduce简化循环:
library(purrr) df_result <- reduce(target_fruits, create_ind_var, .init = df)
方法3:用tidyr拆分后计数
先把水果拆分成单独行,统计每个篮子的水果出现情况,再合并回原数据:
library(tidyr) library(dplyr) target_fruits <- c("apple", "strawberry", "pineapple") df_result <- df %>% left_join( df %>% separate_rows(fruits, sep = ",\\s*") %>% filter(fruits %in% target_fruits) %>% mutate(value = 1) %>% pivot_wider( id_cols = basket, names_from = fruits, values_from = value, values_fill = 0 ), by = "basket" ) %>% mutate(across(all_of(target_fruits), ~replace_na(.x, 0))) print(df_result)
说明:
separate_rows将逗号分隔的水果拆分为单独行;pivot_wider将行转列为虚拟变量;replace_na把未匹配到目标水果的篮子填充为0。
内容的提问来源于stack exchange,提问作者Andrew Skelton
相关产品推荐
相关产品推荐

