如何解决将屏蔽变量在“...”中传递给 group_by() 示例 1示例 2示例 3示例 4
动机
我正在创建一个自定义实用程序函数 streak_over()
,其目的是在语法上模仿 dplyr
动词 group_by()
。虽然 streak_over()
本质上包装了 group_by()
的分组功能,但这种分组是进一步操作的前兆。
在给定数据集的上下文中,streak_over()
的目的是为共享一个组的 连续 观察的每个“条纹”建立索引,其中预先建立了分组(通过先验 { {1}}) 或在 group_by()
本身中指定(通过 tidy evaluation)。
这是一个说明,其中分组变量分别为 streak_over()
和 x
:
y
详情
除了一个小问题外,我的所有工作都完全按预期工作。这是 x y group_id streak_index
<dbl> <chr> <int> <int>
1 1 a 1 1
2 1 a 1 1
3 2 b 4 2
4 2 b 4 2
5 1 a 1 3
6 2 a 3 4
7 1 b 2 5
8 2 b 4 6
的一般形式,它在 streak_over
中接受 ...
的所有参数,然后返回一个 group_by()
向量(如 integer
)连胜指数。 注意: streak_index
和 .start
是我自己定义的参数,用于定义“连续”的标准;除了它们作为函数头中的命名参数存在之外,它们与我的问题无关。
.min
一般来说,可用性是理想的。给定一个 streak_over <- function(...,.start = 1,.min = 2) {
return(
dplyr::group_by(...) # %>%
# ... %>% Further Operations %>% ...
)
}
像 data.frame
| x
来自上图
y
我们可以通过符合人体工程学的工作流程生成我们的条纹指数向量:
df <- data.frame(x = c(1,1,2,2),y = c("a","a","b","b"))
我们也可以像连续使用 df %>% group_by(x,y) %>% streak_over(.add = TRUE)
# [1] 1 1 2 2 3 4 5 6
df %>% group_by(x) %>% streak_over(y,.add = TRUE)
# [1] 1 1 2 2 3 4 5 6
df %>% streak_over(x,y)
# [1] 1 1 2 2 3 4 5 6
一样更改分组:
group_by()
最后,我们可以生成没有任何分组的索引:
df %>% group_by(x) %>% streak_over(y,.add = FALSE)
# [1] 1 1 2 2 3 3 4 4
df %>% group_by(x) %>% streak_over(y) # .add = FALSE by default
# [1] 1 1 2 2 3 3 4 4
df %>% streak_over(y)
# [1] 1 1 2 2 3 3 4 4
但是,我想更改一种默认行为。
问题
根据它包装的 df %>% streak_over() # no grouping given
# [1] 1 1 1 1 1 1 1 1
函数的当前默认值 (.add = FALSE
),当 group_by()
未另行指定时,streak_over()
当前会覆盖现有分组。虽然我通常确实喜欢这种行为,但在一种情况下,它违反直觉且不方便:
.add
这里已经存在一个分组并且可能很有用。此外,df %>% group_by(x,y) %>% streak_over()
# [1] 1 1 1 1 1 1 1 1
不包含进一步的分组变量,否则需要通过 streak_over()
进行消歧。 在这种特殊情况下,只需.add
保留现有分组会非常方便,而所有其他默认设置都遵循streak_over()
。
group_by()
这里列出了我想要的行为:
df %>% group_by(x,y) %>% streak_over()
# [1] 1 1 2 2 3 4 5 6
|
在 .add 调用中指定的分组变量 |
未指定分组变量 |
---|---|---|
(缺失) |
streak_over() 的当前默认行为 |
始终添加到现有分组 |
group_by() |
添加到现有分组 | 添加到现有分组 |
TRUE |
覆盖现有分组 | 覆盖现有分组 |
我还希望这种“延迟”是动态的:如果 R 团队使用新的默认设置(如 FALSE
)更新 dplyr::group_by()
,我希望 {{1} } 以与 mutate(.add = TRUE,
工作流程保持一致,而不是使用可能过时的硬编码默认值(如 streak_over()
)。
最后,为了美观和专业,我希望将 dplyr
的函数头保留为大致规范的形式:
streak_over(.add = TRUE,
尝试
都没有成功,我探索了很多方法。我当前的迭代
streak_over()
与许多其他人一样,由于似乎以下原因而失败:
- 尽管 this answer 支持我最初的直觉,但
streak_over <- function(...,.min = 2) { # ... } # Or... streak_over <- function(.data,...,.add,.drop,.min = 2) { # ... }
无法有效测试屏蔽变量的存在(例如streak_over <- function(.data,.min = 2) { if(rlang::is_missing(.add) && !length(list(...))) { .add <- TRUE if(!rlang::is_empty(dplyr::group_vars(.data))) { message("Existing groups will be kept. Discard with '.add = FALSE'.") } } return( dplyr::group_by(.data,.add = rlang::maybe_missing(.add),.drop = rlang::maybe_missing(.drop)) # %>% # ... %>% Further Operations %>% ... ) }
中的length(list(...))
在y
) 中,而df
似乎同样不可行。事实上,前一种方法给了我下面的错误;它似乎将streak_over(y)
解释为自身的对象,而不仅仅是match.call(expand.dots = FALSE) == match.call(expand.dots = TRUE)
中变量的符号:
y
- 似乎
.data
与rlang::maybe_missing()
的搭配效果不佳,其值应该模拟缺失的参数。由于df %>% group_by(x) %>% streak_over(y) # Error in streak_over(.,y) : object 'y' not found
无条件地将dplyr
强制转换为group_by()
,因此我收到以下错误:
.add
logical
- 即使我试图折叠函数头 (
streak_over <- function(.data,.min = 2) { # Condition REMOVED to progress beyond error above. return( dplyr::group_by(.data,.drop = rlang::maybe_missing(.drop)) # %>% # ... %>% Further Operations %>% ... ) }
),并让df %>% group_by(x) %>% streak_over(y) # Error in if (.add) { : argument is not interpretable as logical
“潜伏在阴影中”直到它被明确指定 (streak_over(...,.min = 2)
),我也不得不改变它的值以某种方式:有条件地.add
,然后是streak_over(.add = TRUE,
。不幸的是,如果用户确实明确指定了new_add <- TRUE
,R 会将它包含在group_by(...,.add = new_add)
旁边的.add
中,并且无法通过分配 {{ 1}} 在适当的地方。结果是(公认可预测的)错误:
...
.add = new_add
结论
我觉得必须有条件地将形式参数的默认值覆盖为包装函数的方法,即使它是包含掩码或整齐计算的神秘 new_add <- NULL
变量。然而,我怀疑这接近于 streak_over <- function(...,.min = 2) {
# Check if '.add' is present in '...'; and if no masked variables are present therein.
if(is.null(list(...)$.add) && all(names(list(...)) %in% c(".data",".add",".drop"))) {
new_add <- TRUE
} else {
new_add <- NULL
}
return(
dplyr::group_by(...,.add = new_add) # %>%
# ... %>% Further Operations %>% ...
)
}
领域,这是我绝对缺乏经验的 R 领域。
一如既往,感谢您的考虑以及您可能提供的任何帮助。
更新
感谢来自 hint 的 ktiu,在 Stack Overflow 上与 further research 合成,我拼凑了一个有点“hacky”的解决方案,它似乎满足了我最初的标准:>
df %>% group_by(x,y) %>% streak_over(.add = FALSE)
# Error in dplyr::group_by(...,.add = add) :
# formal argument ".add" matched by multiple actual arguments
我欢迎任何进一步的帮助:
- 提供更优雅的解决方案;可能(说)如建议的 here 和
default
包,如果安全实施,这看起来理想的手术......尽管我确实想知道掩码将正确翻译。 不幸的是,我的第一次尝试失败了:虽然一切看起来都很干净
...
并且打印输出表明对于本地 symbol
,streak_over <- function(.data,.min = 2) {
# Store the defaults for 'group_by()',in case they are needed.
gb_formals <- formals(dplyr::group_by)
# If neither '.add' nor masked variables in '...' were supplied to
# 'streak_over()',yet a grouping already exists in '.data',override the
# 'group_by()' default to intuitively preserve the grouping.
if(rlang::is_missing(.add) && !length(rlang::enquos(...)) &&
!rlang::is_empty(dplyr::group_vars(.data))) {
.add <- TRUE
message("Existing groups will be kept. Discard with '.add = FALSE'.")
}
return(
dplyr::group_by(.data,.add = rlang::maybe_missing(.add,gb_formals$.add),.drop = rlang::maybe_missing(.drop,gb_formals$.drop)) # %>%
# ... %>% Further Operations %>% ...
)
}
现在默认为 streak_over <- function(...,.min = 2) {
# Condition always TRUE here to illustrate the point.
if(TRUE) {
default::default(group_by) <- list(.add = TRUE)
}
# Print out default,to check if correctly updated.
default::default(group_by)
return(
group_by(...) # %>%
# ... %>% Further Operations %>% ...
)
}
,输出仍然表现为 .add
:
TRUE
- 改进我现有解决方案的技术(工具、结构、语法等);可能(比如)使用
group_by()
而不是.add = FALSE
。 - 稳定此解决方案的功能;特别是对
df %>% group_by(x,y) %>% streak_over() # - .data = [none] # - ... = [none] # * - .add = TRUE # - .drop = group_by_drop_default(.data) # [1] 1 1 1 1 1 1 1 1
的结构变化更加稳健,包括但不限于:- 更改
rlang::fn_fmls()
中参数的现有默认值 - 为之前没有默认值的
base::formals()
中的参数添加默认值 -
dplyr::group_by()
中现有参数的重命名 - 向
group_by()
添加新参数。
- 更改
除了满足原始标准的答案外,任何成功提供进一步帮助(同时满足原始标准)的答案都将收到我的赞成票和接受考虑。
奖金
我也很好奇以下哪个 group_by()
标头更规范:
请注意,此函数旨在模仿 streak_over(...,.min = 2)
语法,模仿 streak_over(.data,.min = 2)
。然而,在执行进一步的操作之前,dplyr
仍然包装另一个函数:在这种情况下,R 绝大多数将传递给包装函数的所有参数表示为包装头中的 dplyr
。再说一次,出于纯粹的功能目的,group_by(.data,.drop)
是传递给 streak_over()
的唯一形式参数,它需要在 ...
标头中的 .add
之外显式存在。
在权威参考资料的支持下,明确这一点可能会成为接受答案的“决胜局”。
再次感谢! — 格雷格
解决方法
这是一种方法
- 使用
rlang::enquos()
来解除...
中的函数参数, - 在以下情况下提供
.add = TRUE
-
.add
未明确传递 AND -
...
仅包含一个“非特殊点”参数(数据的名称,或管道中的.
)
-
- 使用这些变量调用
group_by()
的自定义包装器:
streak_over <- function(...,.start = 1,.min = 2) {
defused <- rlang::enquos(...)
if (any(".add" %in% names(defused),sum(! grepl("\\..+",names(defused))) > 1))
custom_wrapper(...)
else
custom_wrapper(...,.add = TRUE)
}
custom_wrapper <- function(...) {
# add custom logic here
dplyr::group_by(...)
}
请注意,我并没有特意匹配您指定的所有案例,但这可能是一个概念证明,您可以将其推向更成熟的解决方案。
尝试一下:
示例 1
library(dplyr)
df %>%
group_by(x) %>%
streak_over()
保持分组:
# A tibble: 8 x 2
# Groups: x [2]
x y
<dbl> <chr>
1 1 a
2 1 a
3 2 b
4 2 b
5 1 a
6 2 a
7 1 b
8 2 b
示例 2
df %>%
group_by(x) %>%
streak_over(y)
覆盖分组:
# A tibble: 8 x 2
# Groups: y [2]
x y
<dbl> <chr>
1 1 a
2 1 a
3 2 b
4 2 b
5 1 a
6 2 a
7 1 b
8 2 b
示例 3
df %>%
group_by(x) %>%
streak_over(.add = F)
删除分组:
# A tibble: 8 x 2
x y
<dbl> <chr>
1 1 a
2 1 a
3 2 b
4 2 b
5 1 a
6 2 a
7 1 b
8 2 b
示例 4
df %>%
group_by(x) %>%
streak_over(y,.add = T)
添加分组:
# A tibble: 8 x 2
# Groups: x,y [4]
x y
<dbl> <chr>
1 1 a
2 1 a
3 2 b
4 2 b
5 1 a
6 2 a
7 1 b
8 2 b
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。