我不明白传递
dplyr::arrange_at
参数时 .funs
在做什么。
例如,假设我们创建一个数据框
Z
:
library(dplyr)
Z <- expand.grid(A = c(1:2, NA), B = c(1:2, NA))
并假设我们想按
A
(用 NA
first)排序,然后按 B
排序。那么我们可以尝试下面的target
或current
:
all_equal(
target = Z %>% arrange(!is.na(A), A, B),
current = Z %>% arrange_at(.vars = c("A", "A", "B"),
.funs = list(function(x)!is.na(x), identity, identity)),
ignore_row_order = FALSE)
返回“相同的行值,但顺序不同”。第一个版本(
target
)是我所期望的,但第二个版本(current
)令人费解。我期望的是.funs
中的每个函数都将应用于.var
中的相应列,然后它会像arrange()
一样排序。
最终我想以一种非常动态的方式进行排序,因此想要
arrange_at
的全部力量。
正如 @akrun 在评论中所说,
_at
dplyr
函数系列创建所有 .vars
和所有 .funs
的笛卡尔积。因此,我需要的是一个 arrange_parallel_at
函数,它期望 .vars
和 .funs
具有相同的长度,并且在名称为 .vars
中的第 k 个条目的列上计算第 k 个函数(并且仅该列) 。然后所有这些列以相同的顺序成为 arrange
的参数。
以下是对我自己的问题的an回答。虽然它有效(特别是我在更新中要求的),但它几乎肯定不是最佳的,因为我怀疑有基于tidy eval的更好的解决方案。 所以我不愿意接受。
library(tidyverse)
arrange_parallel_at <- function(.data, .vars, .funs) {
stopifnot(length(.vars) == length(.funs), is.character(.vars), is.list(.funs))
tmp_cols <- paste0('.tmp', seq_along(.vars))
for (i in seq_along(.vars)) {
.data[[tmp_cols[i]]] <- .funs[[i]](.data[[.vars[i]]])
}
.data <- arrange_at(.data, tmp_cols)
.data[tmp_cols] <- NULL
.data
}
下面是一些测试代码。
tibble(A = c(1:2, NA)) %>%
crossing(B = c(1:2, NA)) ->
Z
na_first <- function(x) !is.na(x)
all_equal(
Z %>% arrange(!is.na(A), A, !is.na(B), desc(B)),
Z %>% arrange_parallel_at( c( 'A', 'A', 'B', 'B'),
list(na_first, identity, na_first, desc)),
ignore_row_order = FALSE) # returns TRUE