【问题标题】:Dynamically create dataframe and variable names in R similar to macros in SAS and STATA在 R 中动态创建数据框和变量名称,类似于 SAS 和 STATA 中的宏
【发布时间】:2020-10-13 23:09:48
【问题描述】:

一年多来,我一直在努力寻找解决方案,并决定写一篇关于它的帖子。任何帮助,将不胜感激。这是我可以在 Stata 和 SAS 中轻松完成但我不知道如何在 R 中完成的伪代码。{} 是今年引入 dplyr 的类似胶水的运算符, 所以我将它用作使伪代码工作的运算符的占位符。

library(tidyverse)
var <- "mpg"
df_name <- "mtcars"

{df_name} %>% count({var})

{df_name}_1 <- {df_name} %>% mutate(., {var}_1={var}/2)

length({df_name}_1)

应该导致

library(tidyverse)
var <- "mpg"
df_name <- "mtcars"

mtcars %>% count(mpg)

mtcars_1 <- mtcars %>% mutate(., mpg_1=mpg/2)

length(mtcars_1)

在 Stata 中,我可以轻松地使用这样的本地或全局宏:

local df_name "mtcars"

然后将其引用为 `df_name'

在 SAS 中,我可以使用这样的全局宏:

 %LET df_name=mtcars;

然后像 &df_name 一样引用它。

请注意引用这些值在视觉上是多么容易——没有赋值、获取、括号、mgets等。

这两种方法都允许在数据集名称、函数、变量等中使用它们。极大地简化了我的代码并为我节省了大量时间。如何在 R 中使用视觉简洁性来做到这一点?我的代码对于熟悉 Stata/SAS 的人来说应该是可读的(dplyr 在这方面很棒!),太多的 eval,将所有东西包装在函数中,用括号赋值只会让他们放弃项目或迫使我改回SAS/Stata。

我尝试了 {{}}、!!、enquo、sym 和 NSE 的所有组合,但仍然不知道如何以一种视觉上简单的方式使其工作。在 dplyr 管道中,变量名终于有了一些解决方法,但数据帧和基础 R 没有。

我非常感谢您在这件事上的任何帮助!我在 2009 年使用 R 时遇到了这个问题,并放弃了 R,直到我不得不在 2019 年回来,但仍然找不到解决这个问题的简单方法。

【问题讨论】:

  • df_name 是否需要存储文本变量?如果您想以类似 SAS 的方式将其保存在一个简单的脚本中,为什么不将数据集的名称分配给该对象呢? df_name &lt;- mtcars 那么你可以做df_name %&gt;% count(!!var)length(df_name[[var]]) 吗?
  • 我认为 R 中没有类似的东西(尽管也许有人会展示一些做得很好的包)。我不认为你会喜欢这个,但基本上每当你想这样做时,我认为你应该使用函数。这本质上就是函数参数的工作方式。当我有foo &lt;- rnorm(16) 并且我有mean(foo) 时,mean 函数在内部将foo 引用为x。当我执行lm(mpg ~ hp, data = mtcars) 时,lm 函数仅在内部使用data,名称或参数。 R 是一种函数式语言,R 实现这些目的的方式是通过函数。
  • 是的,将所有内容都封装在函数中会使所有内容都很难使用和调试。这都是学术研究,不是生产就绪的东西。
  • 尊敬的,这是一种范式转变。生产代码不使用函数来使事情难以使用和调试。生产代码使用函数(最好是许多小函数,而不是一个大函数),因为它更容易测试、调试和使用。举个例子,你正在为此苦苦挣扎,但如果你使用函数,这不是问题。如果你从滑雪转向单板滑雪,你也必须在某个时候放开杆子。
  • @thelatemail 在 SAS 中,每个 DATA STEP 都可以生成一个 &df_name._1 新文件,这样前一个文件和新文件就存在并且可以进行比较、调试等等。我如何使用您的方法执行 {df_name}_1 % mutate(., {var}_1={var}/2) ?我实际上不想在环境中只有 mtcars_* 的 df_name 命名数据框。感谢您的答复!我需要绝对确定它不能完成。

标签: r dataframe dplyr tidyverse nse


【解决方案1】:

R 是一种函数式编程语言。您将使用元编程来创建代码,而不是使用像 SAS 这样的宏样式文本替换来创建新功能。 R 中的变量名是符号,而不是字符串。试图强制这两种不同的编程范式看起来相同通常不是一个好主意,并且会导致两种语言中的一种非常不习惯。

使用 R 最好跟踪数据本身而不是数据的名称,并使用符号跟踪要使用的列。

library(tidyverse)

df <- mtcars
var <- rlang::sym("mpg")

那你就可以了

df %>% count(!!var)

或者创建一个函数

get_counts <- function(data, x) {
    data %>% count({{x}})
}
get_counts(df, mpg)   #use actual column name
get_counts(df, !!var) #or use name from variable with !!

您可以使用 := 和一些带有 dplyr 的 glue 样式 sytnax 命名新变量

df %>% mutate(., "{var}_1" := !!var/2)

您也可以将其放入函数中

create_new_var <- function(data, x) {
  data %>% mutate(., "{{x}}_1":={{x}}/2)
}
create_new_var(df, mpg)
create_new_var(df, !!var)

然后,您无需使用名称中的数字索引创建变量,而是使用管道

df %>% 
   create_new_var(!!var) %>%
   length()

或者,如果您要处理多个值,则将值保存在命名列表中。然后你可以在列表上映射函数。例如

df_name <- "mtcars"
data <- mget(df_name, inherits = TRUE)
fixed <- map(data, ~create_new_var(., !!var))
lens <- map(fixed, ~length(.))
lens$mtcars
# [1] 12
lens[[df_name]]
# [1] 12

如果您有多个 data.frames 存储在 data 列表中,这也可以工作

我强烈反对你使用这样的东西,但你可以定义一种新的赋值类型,允许字符串行变量名。例如

`%<-%` <-  function(x, value) {
  varname <- glue::glue(x, .envir = parent.frame())
  invisible(assign(varname, value, envir = parent.frame()))
}

df_name <- "mtcars"
"{df_name}_1" %<-% { get(df_name) %>% create_new_var(!!var) }

在这里,我们定义了%&lt;-% 而不是&lt;-,它将采用类似字符串的名称并将它们扩展并将它们转换为新变量。我们使用get() 来获取带有字符串的变量的值。请注意,我们需要在要分配给新值的表达式周围使用{},因为我们无法控制新运算符的优先级。它与管道运算符具有相同的优先级,因此默认情况下一切都会从左到右。不过,这不是一个适当的 R 编程可能会做的事情。

【讨论】:

  • 这最后一段代码是唯一解决动态 LHS 数据名分配的代码!这是非常不合理的,我理解其中的含义,但这是答案第一次真正击中靶心!谢谢!我们不能在 df %>% mutate(., "{var}_1" := !!var/2) 中使用 {} 的根本原因是什么,但是对于 &lt;- 的 LHS,就像你在上一个所做的那样阻止?
【解决方案2】:

我不认为需要在 R 中使用 get 而不是在 SA 中使用 LET 更困难。将命名字符值转换为 R 名称或“语言”表达式的 dplyr 机制是 !! 运算符,因此这似乎是您请求的第一部分的精确实现:

 library(tidyverse)
 var <- "mpg"
 df_name <- get("mtcars")  # this will create a separate copy
 
 
 df_name %>% count(!!var)
#  "mpg"  n
#1   mpg 32

不过,您需要对下一部分使用assign 操作。我不认为 dplyr 已经尝试模仿 SAS 的宏特性,大多数有经验的 R 用户会认为这是可憎的。大约 10 年前,一位 R-greats 曾努力说明 R 如何进行宏处理,但我认为他这样做更多是为了说明为什么不应该这样做,而不是出于任何意图R 更像 SAS。 (我现在出去找文章。)

这是document.,它接近 20 年前。转到第 11 页。

【讨论】:

  • 感谢 bang bang 最近引入的可变部分现在可以工作了。数据框部分仍然造成麻烦。所以只有 assign 与 datframe 名称一起工作?
猜你喜欢
  • 2020-10-03
  • 2019-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-04
  • 2020-09-29
相关资源
最近更新 更多