R:Tidyverse 选择语义 tidyselect::eval_select 将数字附加到重复项

如何解决R:Tidyverse 选择语义 tidyselect::eval_select 将数字附加到重复项

我尝试了解 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,"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 年 5 月 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

要获得想要的输出,我们需要做两件事:

  1. 添加uniquely_named = NULL作为参数并在调用函数时用FALSE指定
  2. 指定现有参数 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,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,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,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,uniquely_named = FALSE)
  names <- names(.data)
  list(names(loc),names[loc])
}

# test
data %>%
  test(foo = 1,"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 年 5 月 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),"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 年 6 月 3 日创建

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?