【发布时间】:2021-09-30 00:31:28
【问题描述】:
有没有办法在 dplyr 中以不同的列名和/或不同数量的条件动态/以编程方式生成 case_when 条件?我有一个交互式脚本,我正在尝试将其转换为函数。 case_when 语句中有很多重复的代码,我想知道它是否可以以某种方式自动化,而无需我一次又一次地从头开始编写所有内容。
这是一个虚拟数据集:
test_df = tibble(low_A=c(5, 15, NA),
low_TOT=c(NA, 10, NA),
low_B=c(20, 25, 30),
high_A=c(NA, NA, 10),
high_TOT=c(NA, 40, NA),
high_B=c(60, 20, NA))
expected_df = tibble(low_A=c(5, 15, NA),
low_TOT=c(NA, 10, NA),
low_B=c(20, 25, 30),
ans_low=c(5, 10, 30),
high_A=c(NA, NA, 10),
high_TOT=c(NA, 40, NA),
high_B=c(60, 20, NA),
ans_high=c(60, 40, 10))
> expected_df
# A tibble: 3 x 8
low_A low_TOT low_B ans_low high_A high_TOT high_B ans_high
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 5 NA 20 5 NA NA 60 60
2 15 10 25 10 NA 40 20 40
3 NA NA 30 30 10 NA NA 10
我想要的逻辑是,如果._TOT 列有值,请使用它。如果不是,则尝试列._A,如果不是,则尝试列._B。请注意,我故意没有将._TOT 作为组的第一列。在这种情况下,我可以只使用 coalesce(),但我想要一个通用的解决方案,而不考虑列顺序。
当然,这一切都可以通过几个case_when 语句轻松完成。我的问题是:
- 我正在尝试制作一个通用函数,因此不想要交互式/整洁的评估。
- 我有一大堆这样的专栏。都以
_TOT, _A, _B之一结尾,但前缀不同(例如low_TOT, low_A, low_B, high_TOT, high_A, high_B,.....,我不想一次又一次地重写一堆case_when函数。
我现在拥有的看起来像这样(我为每个前缀写了一个case_when):
def my_function = function(df) {
df %>% mutate(
# If a total low doesn't exist, use A (if exists) or B (if exists)
"ans_low" := case_when(
!is.na(.data[["low_TOT"]]) ~ .data[["low_TOT"]],
!is.na(.data[["low_A"]]) ~ .data[["low_A"]],
!is.na(.data[["low_B"]]) ~ .data[["low_B"]],
),
# If a total high doesn't exist, use A (if exists) or B (if exists)
"ans_high" := case_when(
!is.na(.data[["high_TOT"]]) ~ .data[["high_TOT"]],
!is.na(.data[["high_A"]]) ~ .data[["high_R"]],
!is.na(.data[["high_B"]]) ~ .data[["high_B"]],
# Plus a whole bunch of similar case_when functions...
}
我想要的是理想地获得一种方法来动态生成具有不同条件的case_when 函数,这样我就不会每次都通过利用以下事实编写新的case_when:
- 所有三个条件都具有相同的通用形式和相同的变量名称结构,但前缀不同(
high_、low_等)。 - 它们具有
!is.na( .data[[ . ]]) ~ .data[[ . ]]形式的相同公式,其中点(.) 是动态生成的列名称。
我想要的是这样的:
def my_function = function(df) {
df %>% mutate(
"ans_low" := some_func(prefix="Low"),
"ans_high" := some_func(prefix="High")
}
我尝试创建自己的case_when 生成器来替换标准case_when,如下所示,但出现错误。我猜那是因为 .data 在 tidyverse 函数之外并不能真正工作?
some_func = function(prefix) {
case_when(
!is.na(.data[[ sprintf("%s_TOT", prefix) ]]) ~ .data[[ sprintf("%s_TOT", prefix) ]],
!is.na(.data[[ sprintf("%s_A", prefix) ]]) ~ .data[[ sprintf("%s_A", prefix) ]],
!is.na(.data[[ sprintf("%s_B", prefix) ]]) ~ .data[[ sprintf("%s_B", prefix) ]]
)
}
我很好奇的另一件事是制作一个更通用的case_when 生成器。在到目前为止的示例中,只有列的名称(前缀)发生了变化。如果我想怎么办
- 更改后缀的数量和名称(例如,
high_W, high_X, high_Y, high_Z, low_W, low_X, low_Y, low_Z, .......),然后将后缀的字符向量作为some_func的参数 - 更改公式的形式。现在,它的所有条件都是
!is.na(.data[[ . ]]) ~ .data[[ . ]]的形式,但是如果我想让它成为some_func的参数呢?例如,!is.na(.data[[ . ]]) ~ sprintf("%s is missing", .)
我很乐意让它与不同的前缀一起工作,但了解我如何使用任意(但常见的)后缀和任意公式实现更通用的东西会非常酷,这样我就可以做到@ 987654353@.
【问题讨论】:
-
请展示一个可重现的小例子
-
如果您包含一个简单的reproducible example,其中包含可用于测试和验证可能解决方案的示例输入和所需输出,则更容易为您提供帮助。如果您只是想获取第一个非 NA 值,则像
coalesce()这样的函数可能更合适。 -
立即查看。我添加了一个简单的数据集作为示例,并将问题重写为更清晰、更简短。这仍然有点长,因为,真的,我要问 3 个问题,关于我想要的越来越普遍的水平(并看看这是否可能,首先)。
-
A
coalesce()可能是一个潜在的答案,但我对动态生成条件更感兴趣(is.na只是这里的特定示例,coalesce还需要特定的列顺序)。我真的很想了解如何更好地使用 dplyr 编程并实现更高级别的抽象/通用性。 -
我也刚刚尝试了
coalese()对列进行了先前的重新排序,但它给出了同样的主要问题:我现在必须编写一大堆coalesce语句。我想利用列组的公共前缀,这样我就不必编写 10 个不同的case_when或coalese语句。