【问题标题】:Creating an R function that can accept both datasets and object names as well as those names as a string object创建一个可以接受数据集和对象名称以及这些名称作为字符串对象的 R 函数
【发布时间】:2020-02-20 02:24:57
【问题描述】:

我可以创建一个将对象名称作为参数的函数(这只是一个普通函数)。

我现在还可以创建一个函数,通过命名向量(使用 dataset <- eval(sym(dataset))date_col <- sym(date_col))获取其数据和列参数。

但是我想要一个可以处理这两种类型的输入的函数。

第一步是检测输入的类别。

对于数据集参数,这(下)适用于命名向量和实际对象名称。

 if (is.character(dataset)) {
    dataset <- eval(sym(dataset)) }

但是,我无法找出处理列参数的适当方法。

当我为它们使用命名向量元素时,该过程(如下)运行良好。

  if (is.character(date_col)) {
    date_col <- sym(date_col) } 

但我不确定如何处理实际的列名(即通过在上面添加else 组件)。 本质上我想把它变成一个符号,所以我仍然可以在函数的步骤中使用 {{}}(或 !!)。

这是我所追求的最小可复制示例。 如您所见,命名矢量版本有效,但实际数据集和列名无效。

我所追求的可能吗?一个函数可以以这种方式动态化还是我需要创建两个单独的函数?

[已编辑:根据评论做了一个更简单的例子]

library(dplyr)
library(rlang)


new_table <- tibble(
  Date = seq.Date(as.Date("2016-01-01"), as.Date("2019-12-31"), 1)) %>% 
  mutate(total_sales = rnorm(n()))


f_arguments <- c("dataset" = "new_table",
                        "date_col" = "Date",
                        "sales_col" = "total_sales")


f <- function(data, x, envir = parent.frame()) {

  if (is.character(data)) {
    data <- get(data, envir)}


  if (is.character(x)) {
    x <- sym(x) }

  data %>% 
    mutate(year_month = lubridate::floor_date(!!ensym(x), "months"),
           year = lubridate::year(!!ensym(x)))

}


# this (below) works per the above code, but not if I comment out 
# the if (is.character(x)) line

f(f_arguments[["dataset"]], 
  f_arguments[["date_col"]])


# this (below) does not work with the above code, but it will work if I comment out 
# the if (is.character(x)) line

f(new_table, Date)

【问题讨论】:

  • ps,已阅读 stackoverflow.com/questions/57376099/… 并且以我为例,他们的解决方案似乎都不起作用
  • 问题应该按照r标签页顶部的说明有minimal示例。上面有很多与问题无关的代码。
  • 已更新以获得更简单的最小示例

标签: r rlang


【解决方案1】:

已根据修改后的问题修改了示例。

library(dplr)
library(lubridate)

f <- function(data, x, envir = parent.frame()) {
  if (is.character(data)) data <- get(data, envir)
  x <- eval(substitute(x), data, envir)
  if (is.character(x)) x <- data[[x]]
  data %>% mutate(year = year(x)) %>% slice(1:2)
}

给予

f(f_arguments[["dataset"]], f_arguments[["date_col"]])  # test 1
## # A tibble: 2 x 3
##   Date       total_sales  year
##   <date>           <dbl> <dbl>
## 1 2016-01-01      -0.975  2016
## 2 2016-01-02       0.120  2016

f("new_table", "Date") # test 2
## # A tibble: 2 x 3
##   Date       total_sales  year
##   <date>           <dbl> <dbl>
## 1 2016-01-01      -0.975  2016
## 2 2016-01-02       0.120  2016

f(new_table, Date)  # test 3
## # A tibble: 2 x 3
##   Date       total_sales  year
##   <date>           <dbl> <dbl>
## 1 2016-01-01      -0.975  2016
## 2 2016-01-02       0.120  2016

f(new_table, f_arguments[["date_col"]]) # test 4
## # A tibble: 2 x 3
##   Date       total_sales  year
##   <date>           <dbl> <dbl>
## 1 2016-01-01      -0.975  2016
## 2 2016-01-02       0.120  2016

# test 5
g <- function(...) { new_tab <- new_table; f(...) }
g("new_tab", "Date") 
## # A tibble: 2 x 3
##   Date       total_sales  year
##   <date>           <dbl> <dbl>
## 1 2016-01-01      -0.975  2016
## 2 2016-01-02       0.120  2016

注意

就我个人而言,我不会这样做,并且会避免未评估的参数,并将第一个参数作为对象传递,第二个作为字符串传递。下面显示的两个示例仍然有效。

f2 <- function(data, x) {
  data %>% mutate(year = year(.[[x]])) %>% slice(1:2)
}

f2(new_table, "Date")
f2(get(f_arguments[["dataset"]]), f_arguments[["date_col"]])

或者允许使用 S3 将数据作为字符串传递:

f3 <- function(data, x, ...) UseMethod("f3")
f3.default <- function(data, x, ...)  {
  data %>% mutate(year = year(.[[x]])) %>% slice(1:2)
}
f3.character <- function(data, x, envir = parent.frame(), ...) {
   data <- get(data, envir)
   NextMethod()
}

f3(new_table, "Date")
f3(f_arguments[["dataset"]], f_arguments[["date_col"]])

【讨论】:

  • 感谢@Grothendieck,您的代码适用于您的示例,但不适用于我的示例。我已经简化了我最初的例子。我无法弄清楚关键的区别。关于我缺少什么的任何提示?
  • 已修改示例以更接近修改后的问题。
  • 感谢@G.Grotherndieck,已经发布了我自己问题的答案(这不是完成的事情吗?)。当然,您的回答引导了我很多...但是我认为我的新功能更简单...您认为如何?我不太明白您的 parent.frame() 论点在您的示例中做了什么..但是大多数 rlang 对我来说都是一个谜...目前正处于采用有效方法而不是巩固总体理解的阶段(希望稍后出现)。
  • parent.frame()代码是为了解决测试5等数据框不在全局环境中的情况。
【解决方案2】:

目标是拥有一个可以接受“字符串”参数或常规数据集和列引用的函数。并在对原始功能进行最小改动的情况下做到这一点。

即,它需要"data" 以及data。还有"column_x" 以及常规的column_x

挑战在于您需要在函数中包含 rlang !! 以便 “字符串”值可以工作,但这会阻止常规参数版本。

以下解决方案检测第一个(数据集)参数是否为字符串,然后对参数应用正确的转换,以便函数可以继续使用 rlang !!

f <- function(data, column_x) {

  if (is.character(data)) {
    data <- eval(sym(data))
    column_x <- sym(column_x) }

  data %>% 
    mutate(year_month = lubridate::floor_date(!! ensym(column_x), "months"),
           year = lubridate::year(!! ensym(column_x))) %>% 
    head(2)
}


# let's test

f(f_arguments[["dataset"]], 
  f_arguments[["date_col"]])


f(new_table, Date)

如果没有@G.Grothendieck 和@andrew_reece 的慷慨帮助(来自我之前的问题),我当然无法做到这一点。

【讨论】:

  • 此代码在我的答案中使用测试 4 和 5 运行时会出错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多