R和Stata中全局变量危险的例子

Ari*_*man 44 scope r global-variables stata

在最近与同学的对话中,我一直主张避免全局,除了存储常量.这是一种典型的应用统计类型程序,每个人编写自己的代码和项目大小都很小,所以人们很难看到由于草率的习惯造成的麻烦.

在谈论避免使用全局变量时,我主要关注全局变量可能导致问题的以下原因,但我想在R和/或Stata中使用一些示例来遵循原则(以及您可能认为重要的任何其他原则) ),我很难想出可信的人.

  • 非本地化:Globals使调试更难,因为它们更难理解代码流
  • 隐式耦合:Globals通过允许远程代码段之间的复杂交互来打破函数式编程的简单性
  • 命名空间冲突:重用通用名称(x,i等),导致命名空间冲突

对这个问题的一个有用的答案是一个可重现的,自包含的代码片段,其中全局变量会导致特定类型的麻烦,理想情况下是另一个代码片段,其中问题得到纠正.如有必要,我可以生成更正的解决方案,因此问题的示例更为重要.

相关链接:

全局变量很糟糕

全球变量是否不好?

csg*_*pie 28

我也很乐意为那些没有编程经验的本科生教授R. 我发现的问题是大多数全局变量都很糟糕的例子,相当简单,并没有真正说明问题.

相反,我试图说明最不惊讶的原则.我用一些例子来弄清楚发生了什么.这里有些例子:

  1. 我要求全班写下他们认为最终价值的i内容:

    i = 10
    for(i in 1:5)
        i = i + 1
    i
    
    Run Code Online (Sandbox Code Playgroud)

    有些人猜对了.然后我问你应该写这样的代码吗?

    在某种意义上,i是一个正在改变的全局变量.

  2. 以下代码返回什么:

    x = 5:10
    x[x=1]
    
    Run Code Online (Sandbox Code Playgroud)

    问题是我们究竟是什么意思 x

  3. 以下函数是返回全局变量还是局部变量:

     z = 0
     f = function() {
         if(runif(1) < 0.5)
              z = 1
         return(z)
      }
    
    Run Code Online (Sandbox Code Playgroud)

    答案:两者.再次讨论为什么这很糟糕.

  • 这些都很聪明,但我真的很喜欢这里的#3,因为它为范围和返回值的更广泛讨论提供了一个很好的主题. (6认同)
  • 一个有趣的修改#2:`x <-5:10`; x [x <-1]`返回5,但在基础环境中`x`现在是1. (4认同)

Sta*_*asK 16

哦,全球的美妙气味 ......

本文中的所有答案都给出了R示例,OP也想要一些Stata示例.所以让我来谈谈这些.

与R不同,Stata确实关注其本地宏(您使用local命令创建的本地宏)的位置,因此问题是"这是一个全局z还是正在返回的本地z?" 永远不会出现.(天哪......如果没有强制执行地点,你怎么能写任何代码?)Stata有一个不同的怪癖,即不存在的本地或全局宏被评估为空字符串,可能是也可能不是.

我看过使用全局变量有几个主要原因:

  1. 全局变量通常用作变量列表的快捷方式,如

    sysuse auto, clear
    regress price $myvars
    
    Run Code Online (Sandbox Code Playgroud)

    我怀疑这种构造的主要用途是在交互式打字和将代码存储在do-file中的人之间进行切换,因为他们尝试了多种规范.假设他们尝试使用同性恋标准误差,异方差标准误差和中位数回归进行回归:

    regress price mpg foreign
    regress price mpg foreign, robust
    qreg    price mpg foreign
    
    Run Code Online (Sandbox Code Playgroud)

    然后他们用另一组变量运行这些回归,然后用另一组变量运行,最后他们放弃并将其设置为do-file myreg.dowith a file

    regress price $myvars
    regress price $myvars, robust
    qreg    price $myvars
    exit
    
    Run Code Online (Sandbox Code Playgroud)

    伴随着适当的全球宏观背景.到现在为止还挺好; 片段

    global myvars mpg foreign
    do myreg
    
    Run Code Online (Sandbox Code Playgroud)

    产生理想的结果.现在让我们说他们通过电子邮件发送他们着名的do文件,声称可以为协作者提供非常好的回归结果,并指示他们输入

    do myreg
    
    Run Code Online (Sandbox Code Playgroud)

    他们的合作者会看到什么?在最好的情况下,mpg如果他们开始一个新的Stata实例的平均值和中位数(失败的耦合:myreg.do真的不知道你打算用非空变量列表来运行它).但是,如果合作者在工作中有某些东西,并且也有全局myvars定义(名称冲突)...男人,那将是一场灾难.

  2. Globals用于目录或文件名,如:

    use $mydir\data1, clear
    
    Run Code Online (Sandbox Code Playgroud)

    上帝只知道将要装载什么.但是,在大型项目中,它确实很方便.您可能希望global mydir在主文件中定义某个位置,甚至可以定义

    global mydir `c(pwd)'
    
    Run Code Online (Sandbox Code Playgroud)
  3. Globals可用于存储不可预测的垃圾,就像一个完整的命令:

    capture $RunThis
    
    Run Code Online (Sandbox Code Playgroud)

    上帝只知道将要执行什么.这是隐式强耦合的最坏情况,但由于我甚至不确定RunThis它将包含任何有意义的东西,我capture在它前面放了一个,并准备处理非零返回代码_rc.(但是,请参阅下面的示例.)

  4. Stata自己使用全局变量是为了神的设置,比如I型错误概率/置信度:全局$S_level总是定义的(你必须是一个完全重新定义这个全局的白痴,尽管它在技术上是可行的).然而,这主要是版本5及以下(大致)代码的遗留问题,因为可以从不太脆弱的系统常量获得相同的信息:

    set level 90
    display $S_level
    display c(level)
    
    Run Code Online (Sandbox Code Playgroud)

值得庆幸的是,全局变量在Stata中非常明确,因此很容易调试和删除.在上述某些情况中,当然在第一种情况下,您需要将参数传递给do文件,这些文件在do文件中被视为本地`0'文件.myreg.do我可能不会在文件中使用全局变量,而是将其编码为

    unab varlist : `0'
    regress price `varlist'
    regress price `varlist', robust
    qreg    price `varlist'
    exit
Run Code Online (Sandbox Code Playgroud)

unab东西将作为保护元素:如果输入不是合法的varlist,程序将停止并显示错误消息.

在我见过的最糟糕的情况下,全局在定义之后只使用了一次.

有时候你想要使用全局变量,因为否则你必须将血腥的东西传递给其他所有的文件或程序.我发现全局变量几乎不可避免的一个例子是编码最大似然估计器,我事先并不知道有多少方程和参数.Stata坚持认为(用户提供的)可能性评估器将具有特定的方程式.所以我不得不在全局变量中累积我的方程式,然后在Stata需要解析的语法描述中用全局变量调用我的赋值器:

args lf $parameters
Run Code Online (Sandbox Code Playgroud)

lf目标函数(对数似然)在哪里.我在正常混合物包装(denormix)和验证性因子分析包(confa)中遇到过这种情况至少两次; findit当然,你们两个都可以.

  • 美丽的答案StasK.谢谢.在你的解释中,我认识到了很多Stata的痛苦.就个人而言,特别是在Stata中,宏可以包含半命令和其他精神错乱,我尝试坚持代码完成建议并仅使用它们来代替常量. (3认同)

Rei*_*son 11

分析意见的全局变量的一个R示例是将stringsAsFactors数据读入R或创建数据帧的问题.

set.seed(1)
str(data.frame(A = sample(LETTERS, 100, replace = TRUE),
               DATES = as.character(seq(Sys.Date(), length = 100, by = "days"))))
options("stringsAsFactors" = FALSE)
set.seed(1)
str(data.frame(A = sample(LETTERS, 100, replace = TRUE),
               DATES = as.character(seq(Sys.Date(), length = 100, by = "days"))))
options("stringsAsFactors" = TRUE) ## reset
Run Code Online (Sandbox Code Playgroud)

由于在R中实现选项的方式,这无法真正得到纠正 - 任何事情都可能在您不知情的情况下改变它们,因此不能保证相同的代码块返回完全相同的对象.约翰·钱伯斯在他最近的书中哀叹这一特征.


Rei*_*son 8

R中的病理示例是使用R中可用的一个全局变量pi来计算圆的面积.

> r <- 3
> pi * r^2
[1] 28.27433
> 
> pi <- 2
> pi * r^2
[1] 18
> 
> foo <- function(r) {
+     pi * r^2
+ }
> foo(r)
[1] 18
> 
> rm(pi)
> foo(r)
[1] 28.27433
> pi * r^2
[1] 28.27433
Run Code Online (Sandbox Code Playgroud)

当然,人们可以foo()通过强制使用来防御性地编写该功能,base::pi但是这种追索可能在普通用户代码中不可用,除非打包并使用NAMESPACE:

> foo <- function(r) {
+     base::pi * r^2
+ }
> foo(r = 3)
[1] 28.27433
> pi <- 2
> foo(r = 3)
[1] 28.27433
> rm(pi)
Run Code Online (Sandbox Code Playgroud)

这突出了您可以通过依赖于不仅仅在函数范围内或作为参数明确传递的任何内容而陷入的混乱.


Mic*_*ael 8

这是一个有趣的病理学例子,包括替换函数,全局赋值,以及全局和局部定义的x ......

x <- c(1,NA,NA,NA,1,NA,1,NA)

local({

    #some other code involving some other x begin
    x <- c(NA,2,3,4)
    #some other code involving some other x end

    #now you want to replace NAs in the the global/parent frame x with 0s
    x[is.na(x)] <<- 0
})
x
[1]  0 NA NA NA  0 NA  1 NA
Run Code Online (Sandbox Code Playgroud)

[1] 1 0 0 0 1 0 1 0替换函数使用由本地值返回的索引,而不是返回,is.na(x)即使您正在分配x的全局值.R语言定义中记录了此行为.


Gre*_*now 5

R中一个快速但令人信服的例子是运行这样的行:

.Random.seed <- 'normal'
Run Code Online (Sandbox Code Playgroud)

我选择"正常"作为某人可能选择的东西,但你可以在那里使用任何东西.

现在运行使用生成的随机数的任何代码,例如:

rnorm(10)
Run Code Online (Sandbox Code Playgroud)

然后你可以指出任何全局变量都可能发生同样的事情.

我也用例子:

x <- 27
z <- somefunctionthatusesglobals(5)
Run Code Online (Sandbox Code Playgroud)

然后问学生有什么价值x; 答案是我们不知道.


Rom*_*rik 5

通过反复试验,我了解到我需要非常明确地命名我的函数参数(并确保在开始和函数中进行足够的检查),以使一切尽可能健壮.如果您将变量存储在全局环境中,但是您尝试使用自定义贵重物品调试函数,则尤其如此 - 并且某些内容无法添加!这是一个结合不良检查和调用全局变量的简单示例.

glob.arg <- "snake"
customFunction <- function(arg1) {
    if (is.numeric(arg1)) {
        glob.arg <- "elephant"
    }

    return(strsplit(glob.arg, "n"))
}

customFunction(arg1 = 1) #argument correct, expected results
customFunction(arg1 = "rubble") #works, but may have unexpected results
Run Code Online (Sandbox Code Playgroud)