R语言 如何根据变量名动态创建case_when()表达式?

eni9jsuy  于 2023-07-31  发布在  其他
关注(0)|答案(2)|浏览(119)

我有一个数据框架,其中包含一组收入变量和一组销售变量。我想创建一个新的变量集,它接受revenue变量的值,并检查它与哪个sales变量匹配,如果匹配,则返回相应的年份。
下面是一个简单的例子,说明我所拥有的和我正在努力实现的目标:

library(tidyverse)
library(rlang)

# Creating an example dataframe
df <- tibble(
  revenues_1 = c(1, 3, 4),
  revenues_2 = c(2, 4, 5),
  sales_2005 = c(1, 2, 3),
  sales_2006 = c(2, 3, 4),
  sales_2007 = c(3, 4, 5)
)

# What I want to do:
# df <- df |> 
#  rowwise() |>
#  mutate(
#    year_1 = case_when(
#      revenues_1 == sales_2005 ~ 2005,
#      revenues_1 == sales_2006 ~ 2006,
#      revenues_1 == sales_2007 ~ 2007,
#      .default = NA
#    ),
#    year_2 = case_when(
#      revenues_2 == sales_2005 ~ 2005,
#      revenues_2 == sales_2006 ~ 2006,
#      revenues_2 == sales_2007 ~ 2007,
#      .default = NA
#    )
#  )

字符串
然而,如果我有很多年和很多收入变量,这是不可扩展的。我怎样才能使这更有效率?
一个想法是(不工作):

# Create a list of expressions for each year
years_exprs <- map(2005:2007, ~parse_expr(paste0("var == 'sales_", .x, "' ~ ", .x)))

# Use this list in a case_when statement
df <- df |> 
  rowwise() |>
  mutate(across(starts_with("revenues_"), function(var) {
    case_when(
      !!!years_exprs,
      .default = NA
    )
  }, .names = "year_{.col}")) |> 
  ungroup()

5anewei6

5anewei61#

如果我理解正确的话,使用现有的代码,您可以使用dplyrmutateacrossstarts_with,然后将函数放在命名列表中以创建新的变量。
为了减少多年来创建单个case_when逻辑的工作量,您可以首先将rlang::parse_exprs()paste0一起使用:

yrs <- 2005:2007
years_exprs <- rlang::parse_exprs(paste0(".x == sales_", yrs, " ~ ", yrs))

字符串
然后使用!!!进行计算:

library(dplyr)

df %>% mutate(across(starts_with("revenues_"), 
                     list(year = ~case_when(!!!years_exprs))))


输出:

revenues_1 revenues_2 sales_2005 sales_2006 sales_2007 revenues_1_year revenues_2_year
       <dbl>      <dbl>      <dbl>      <dbl>      <dbl>           <dbl>           <dbl>
1          1          2          1          2          3            2005            2006
2          3          4          2          3          4            2006            2007
3          4          5          3          4          5            2006            2007


注意:如果你的变量不一定都以“revenue”开头,你可以创建一个包含所需列的向量,不管它们是否共享相同的模式(这里是mut_vars),然后使用all_of()

mut_vars <- c("revenues_1", "revenues_2")
df %>% mutate(across(all_of(mut_vars), 
                     list(year = ~case_when(!!!years_exprs))))

fafcakar

fafcakar2#

如果你绝对需要生成表达式,你可以这样做:

library(glue)
library(tidyverse)

years <- 2005:2007
idx <- 1:2
years_exprs <- map_chr(idx, ~ {
  str_flatten_comma(c(glue("revenues_{.x} == sales_{years} ~ {years}"),
                      ".default = NA")) %>% 
    str_c("case_when(", ., ")")
                    }) |>
  set_names(paste0("year_", idx)) |>
  parse_exprs()

df |>
  rowwise() |>
  mutate(!!! years_exprs) |>
  ungroup()

字符串
如果要使用分割切片操作符!!!,则需要为mutate提供 named 表达式。这些名称将成为变量名称。
你可以看到这个表达式在使用rlang::qq_show注入后是如何解析的:

qq_show(
  df |>
    rowwise() |>
    mutate(!!! years_exprs) |>
    ungroup()
)

ungroup(mutate(rowwise(df), year_1 = case_when(revenues_1 == sales_2005 ~ 2005,
revenues_1 == sales_2006 ~ 2006, revenues_1 == sales_2007 ~ 2007, .default = NA),
year_2 = case_when(revenues_2 == sales_2005 ~ 2005, revenues_2 == sales_2006 ~
  2006, revenues_2 == sales_2007 ~ 2007, .default = NA)))


话虽如此,如果你能找到一种方法来实现@jpsmith提供的解决方案,那将是一个更好的实践。根据我的经验,当人们有宽格式的数据时,他们会以这种方式使用表达式。通常,转向更长的格式或使用可用的工具(如across)更容易。

输出

revenues_1 revenues_2 sales_2005 sales_2006 sales_2007 year_1 year_2
       <dbl>      <dbl>      <dbl>      <dbl>      <dbl>  <dbl>  <dbl>
1          1          2          1          2          3   2005   2006
2          3          4          2          3          4   2006   2007
3          4          5          3          4          5   2006   2007

相关问题