R:Tidyverse 选择语义 tidyselect::eval_select 将数字附加到重复项
R: Tidyverse selection semantics tidyselect::eval_select appending numbers to duplicates
我尝试了一段时间来了解 tidyverse 设计以及如何使用它进行编程。我试图编写一个使用 tidyselect 语义的函数,我发现 tidyselect::eval_select
将数字附加到 lhs 表达式。看到此语义用于列重命名,这并不奇怪。不幸的是,我的用于构建数据结构的函数不需要这种行为,它需要表达式的 lhs 中提供的常规名称(根据需要重复多次)。我还没有设法找出这种行为的来源;它似乎是 make.unique
但我找不到它的实现位置。如果你知道,我很好奇学习,如果不知道,解决我的问题不应该依赖它。
我想要的只是 lhs 名称没有附加数字,如示例所示:
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# Example
data %>%
tidyselect::eval_select(rlang::expr(c(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))), .)
#> foo bar1 bar2 bar3 foobar1 foobar2 foobar3 foobar4
#> 1 2 3 4 1 2 3 4
# Function
test <- function(.data, ...) {
loc <- tidyselect::eval_select(rlang::expr(c(...)), .data)
names <- names(.data)
list(names(loc), names[loc])
}
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar1" "bar2" "bar3" "foobar1" "foobar2" "foobar3"
#> [8] "foobar4"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v2.0.0)
创建于 2021-05-22
期望的输出:
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar"
#> [8] "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
非常感谢任何帮助。
问题是由一个名为 ensure_named
的函数引起的,该函数深深嵌套在 eval_select
的实现中。它是 vars_select_eval
函数的一部分。
ensure_named(pos, vars, uniquely_named, allow_rename)
好消息是我们只需要覆盖 uniquely_named
参数,这个参数是从第一个名为 eval_select_impl
的实现函数开始执行的,该函数由 eval_select
本身调用。所以我们需要做的就是重写tidyselect::eval_select
.
要获得想要的输出,我们需要做两件事:
- 添加
uniquely_named = NULL
作为参数,调用函数时用FALSE
指定
- 指定现有参数
name_spec = "{outer}"
。仅执行此步骤是不够的,除非 uniquely_named
设置为 FALSE
。
在实际代码之前,注意事项:
tidyselect::eval_select
故意不允许重复的列名。
对于初学者来说,无法轻松创建具有重复列名的 tibble
:
tibble(a = 1:3, b = 4:6, a = 7:9)
#> Error: Column name `a` must not be duplicated.
#> Use .name_repair to specify repair.
一种解决方法是使用带有 tibble::new_tibble
:
的列表
tibble::new_tibble(list(a = 1:3, b = 4:6, a = 7:9), nrow = 3)
#> # A tibble: 3 x 3
#> a b a
#> <int> <int> <int>
#> 1 1 4 7
#> 2 2 5 8
#> 3 3 6 9
对于 data.frame
,当 check.names
参数设置为 FALSE
:
时,只能创建非唯一名称
data.frame(a = 1:3, b = 4:6, a = 7:9, check.names = FALSE)
#> a b a
#> 1 1 4 7
#> 2 2 5 8
#> 3 3 6 9
但是当我们将此 data.frame
与常规 {dplyr} 动词一起使用时,会抛出一个错误,告诉我们不能转换具有重复名称的数据帧:
data.frame(a = 1:3, b = 4:6, a = 7:9, check.names = FALSE) %>%
mutate(c = 1:3)
#> Error: Can't transform a data frame with duplicate names.
因此我们可以假设不建议在 {tidyverse} 中使用具有重名的 data.frame
。这可能与整洁数据的概念相矛盾。
话虽这么说,下面是上面提到的解决这个问题的方法:
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# custom eval_select function
my_eval_select <- function(expr, data,
env = rlang::caller_env(),
..., include = NULL,
exclude = NULL, strict = TRUE,
name_spec = NULL,
uniquely_named = NULL, # this is the new argument
allow_rename = TRUE) {
ellipsis::check_dots_empty()
tidyselect:::eval_select_impl(data, names(data), rlang::as_quosure(expr, env),
include = include, exclude = exclude, strict = strict,
name_spec = name_spec, allow_rename = allow_rename,
uniquely_named = uniquely_named) # which we also add here
}
# example 1
data %>%
my_eval_select(rlang::expr(c(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))),
data = .,
name_spec = "{outer}", # we need to specify this
uniquely_named = FALSE) # and this
#> foo bar bar bar foobar foobar foobar foobar
#> 1 2 3 4 1 2 3 4
# example: custom function
test <- function(.data, ...) {
loc <- my_eval_select(rlang::expr(c(...)),
data = .data,
name_spec = "{outer}",
uniquely_named = FALSE)
names <- names(.data)
list(names(loc), names[loc])
}
# test
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar" "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v0.3.0)
于 2021-05-22 创建
再次感谢@TimTeaFan 的详尽回答。我会保留它作为“正确”答案,因为我发现它非常有用。
我很晚才发现 tidyverse 的变量重命名规则。外部名称根据以下规则传播到所选元素:(1) 对于数据框,将附加数字后缀,因为列必须唯一命名。 (2) 对于法向量,名称只是分配给所有选定的输入。
所以我发布这个作为我自己问题的答案,因为它更容易,并且为了我创建一个简单数据结构的函数的目的实现相同的结果。我不确定这是否有任何缺点,但我从测试中看不到任何缺点。
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# custom function
test <- function(.data, ...) {
data <- as.list(.data)
loc <- tidyselect::eval_rename(rlang::expr(c(...)), data)
names <- names(.data)
list(names(loc), names[loc])
}
# test
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar" "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v2.0.0)
创建于 2021-06-03
我尝试了一段时间来了解 tidyverse 设计以及如何使用它进行编程。我试图编写一个使用 tidyselect 语义的函数,我发现 tidyselect::eval_select
将数字附加到 lhs 表达式。看到此语义用于列重命名,这并不奇怪。不幸的是,我的用于构建数据结构的函数不需要这种行为,它需要表达式的 lhs 中提供的常规名称(根据需要重复多次)。我还没有设法找出这种行为的来源;它似乎是 make.unique
但我找不到它的实现位置。如果你知道,我很好奇学习,如果不知道,解决我的问题不应该依赖它。
我想要的只是 lhs 名称没有附加数字,如示例所示:
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# Example
data %>%
tidyselect::eval_select(rlang::expr(c(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))), .)
#> foo bar1 bar2 bar3 foobar1 foobar2 foobar3 foobar4
#> 1 2 3 4 1 2 3 4
# Function
test <- function(.data, ...) {
loc <- tidyselect::eval_select(rlang::expr(c(...)), .data)
names <- names(.data)
list(names(loc), names[loc])
}
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar1" "bar2" "bar3" "foobar1" "foobar2" "foobar3"
#> [8] "foobar4"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v2.0.0)
创建于 2021-05-22期望的输出:
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar"
#> [8] "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
非常感谢任何帮助。
问题是由一个名为 ensure_named
的函数引起的,该函数深深嵌套在 eval_select
的实现中。它是 vars_select_eval
函数的一部分。
ensure_named(pos, vars, uniquely_named, allow_rename)
好消息是我们只需要覆盖 uniquely_named
参数,这个参数是从第一个名为 eval_select_impl
的实现函数开始执行的,该函数由 eval_select
本身调用。所以我们需要做的就是重写tidyselect::eval_select
.
要获得想要的输出,我们需要做两件事:
- 添加
uniquely_named = NULL
作为参数,调用函数时用FALSE
指定 - 指定现有参数
name_spec = "{outer}"
。仅执行此步骤是不够的,除非uniquely_named
设置为FALSE
。
在实际代码之前,注意事项:
tidyselect::eval_select
故意不允许重复的列名。
对于初学者来说,无法轻松创建具有重复列名的 tibble
:
tibble(a = 1:3, b = 4:6, a = 7:9)
#> Error: Column name `a` must not be duplicated.
#> Use .name_repair to specify repair.
一种解决方法是使用带有 tibble::new_tibble
:
tibble::new_tibble(list(a = 1:3, b = 4:6, a = 7:9), nrow = 3)
#> # A tibble: 3 x 3
#> a b a
#> <int> <int> <int>
#> 1 1 4 7
#> 2 2 5 8
#> 3 3 6 9
对于 data.frame
,当 check.names
参数设置为 FALSE
:
data.frame(a = 1:3, b = 4:6, a = 7:9, check.names = FALSE)
#> a b a
#> 1 1 4 7
#> 2 2 5 8
#> 3 3 6 9
但是当我们将此 data.frame
与常规 {dplyr} 动词一起使用时,会抛出一个错误,告诉我们不能转换具有重复名称的数据帧:
data.frame(a = 1:3, b = 4:6, a = 7:9, check.names = FALSE) %>%
mutate(c = 1:3)
#> Error: Can't transform a data frame with duplicate names.
因此我们可以假设不建议在 {tidyverse} 中使用具有重名的 data.frame
。这可能与整洁数据的概念相矛盾。
话虽这么说,下面是上面提到的解决这个问题的方法:
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# custom eval_select function
my_eval_select <- function(expr, data,
env = rlang::caller_env(),
..., include = NULL,
exclude = NULL, strict = TRUE,
name_spec = NULL,
uniquely_named = NULL, # this is the new argument
allow_rename = TRUE) {
ellipsis::check_dots_empty()
tidyselect:::eval_select_impl(data, names(data), rlang::as_quosure(expr, env),
include = include, exclude = exclude, strict = strict,
name_spec = name_spec, allow_rename = allow_rename,
uniquely_named = uniquely_named) # which we also add here
}
# example 1
data %>%
my_eval_select(rlang::expr(c(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))),
data = .,
name_spec = "{outer}", # we need to specify this
uniquely_named = FALSE) # and this
#> foo bar bar bar foobar foobar foobar foobar
#> 1 2 3 4 1 2 3 4
# example: custom function
test <- function(.data, ...) {
loc <- my_eval_select(rlang::expr(c(...)),
data = .data,
name_spec = "{outer}",
uniquely_named = FALSE)
names <- names(.data)
list(names(loc), names[loc])
}
# test
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar" "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v0.3.0)
于 2021-05-22 创建再次感谢@TimTeaFan 的详尽回答。我会保留它作为“正确”答案,因为我发现它非常有用。 我很晚才发现 tidyverse 的变量重命名规则。外部名称根据以下规则传播到所选元素:(1) 对于数据框,将附加数字后缀,因为列必须唯一命名。 (2) 对于法向量,名称只是分配给所有选定的输入。
所以我发布这个作为我自己问题的答案,因为它更容易,并且为了我创建一个简单数据结构的函数的目的实现相同的结果。我不确定这是否有任何缺点,但我从测试中看不到任何缺点。
library(tidyverse)
# Data
data <- mtcars[, 8:11]
# custom function
test <- function(.data, ...) {
data <- as.list(.data)
loc <- tidyselect::eval_rename(rlang::expr(c(...)), data)
names <- names(.data)
list(names(loc), names[loc])
}
# test
data %>%
test(foo = 1, bar = c(2:4), foobar = c(1, "am", "gear", "carb"))
#> [[1]]
#> [1] "foo" "bar" "bar" "bar" "foobar" "foobar" "foobar" "foobar"
#>
#> [[2]]
#> [1] "vs" "am" "gear" "carb" "vs" "am" "gear" "carb"
由 reprex package (v2.0.0)
创建于 2021-06-03