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

在最近与同学们的交谈中,我一直主张避免使用全局variables,除非存储常量。 这是一种典型的应用统计types的程序,每个人都编写自己的代码,项目规模很小,所以人们很难看到由于习惯不稳而造成的麻烦。

在谈到避免全局variables时,我将重点放在全局variables可能会引起麻烦的以下原因上 ,但是我想在R和/或Stata中有一些例子来遵循这些原则(以及其他您可能认为重要的原则),而我很难想出可信的。

  • 非局域性:全局variables使得debugging变得更加困难,因为他们更难理解代码stream
  • 隐式耦合:全局通过允许遥远的代码段之间的复杂交互来打破函数式编程的简单性
  • 命名空间冲突:通用名(x,i等)被重用,导致命名空间冲突

对这个问题的一个有用的答案将是一个可重复和独立的代码片段,其中全局性导致特定types的麻烦,理想情况下与其中纠正该问题的另一个代码片段。 如果需要,我可以生成更正的解决scheme,所以问题的例子更重要。

相关链接

全局variables不好

全局variables是不好的?

对于没有编程经验的本科生来说,我也很乐意教授R。 我发现的问题是,大多数时候全局变差的例子都相当简单,并没有真正理解。

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

  1. 我请全class写下他们认为i最终的价值:

     i = 10 for(i in 1:5) i = i + 1 i 

    有些课堂猜测正确。 那么我问你是否应该写这样的代码?

    从某种意义上说, i是一个正在改变的全局variables。

  2. 下面这段代码返回的是什么:

     x = 5:10 x[x=1] 

    问题是我们所说的x是什么意思

  3. 以下函数是否返回全局或局部variables:

      z = 0 f = function() { if(runif(1) < 0.5) z = 1 return(z) } 

    答:都是。 再次讨论为什么这是不好的。

哦,全球的美妙气味 …

在这篇文章中的所有答案都给出了R例子,OP也想要一些Stata例子。 所以,让我和这些一起合作。

与R不同,Stata确实关心其本地macros( local命令创build的那些)的局部性,所以问题是“这是一个全局z还是一个正在返回的本地z? 永远不会出现。 (Gosh …如果地方没有被强制执行,R怎么能写任何代码?)Stata有一个不同的怪癖,即一个不存在的本地或全局macros被评估为一个空string,可能会也可能不需要。

我看到全局使用有几个主要原因:

  1. 全局variables经常用作variables列表的快捷方式,如

     sysuse auto, clear regress price $myvars 

    我怀疑这样的构造的主要用途是对于在交互式input和在do-file中存储代码的人来说,他们尝试了多个规范。 假设他们尝试使用同质标准差,异方差标准差和中值回归进行回归:

     regress price mpg foreign regress price mpg foreign, robust qreg price mpg foreign 

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

     regress price $myvars regress price $myvars, robust qreg price $myvars exit 

    配上适当的全球macros观背景。 到现在为止还挺好; 片段

     global myvars mpg foreign do myreg 

    产生理想的结果。 现在让我们说,他们发送电子邮件他们着名的do-file声称产生非常好的回归结果的合作者,并指示他们打字

     do myreg 

    他们的合作者会看到什么? 在最好的情况下,如果他们开始一个Stata的新实例(失败的耦合: myreg.do真的不知道你打算用非空variables列表运行这个), mpg的平均值和中位数。 但是,如果合作者有一些东西在作品中,也有一个全球myvars定义(名称相撞)…人,那会是一场灾难。

  2. 全局用于目录或文件名称,如下所示:

     use $mydir\data1, clear 

    上帝只知道会装什么。 不过在大型项目中,它确实来得方便。 你可能想在你的主文件中的某个地方定义global mydir ,甚至可能是

     global mydir `c(pwd)' 
  3. 全局可以用来存储一个不可预知的废话,就像一个完整的命令:

     capture $RunThis 

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

  4. Stata自己使用全局variables是为了上帝的设置,就像I型错误的概率/置信水平一样:全局的$S_level总是被定义的(你必须是一个完全白痴的人来重新定义这个全局$S_level ,当然这在技术上是可行的)。 然而,这大多是版本5及以下版本(粗略)的遗留问题,因为可以从不太脆弱的系统常量获得相同的信息:

     set level 90 display $S_level display c(level) 

值得庆幸的是,全局variables在Stata中非常明确,因此很容易debugging和删除。 在上面的一些情况中,当然在第一种情况下,你想要传递参数给do-file, inside the do-file. Instead of using globals in the看作是本地的“0” inside the do-file. Instead of using globals in the inside the do-file. Instead of using globals in the myreg.do`文件inside the do-file. Instead of using globals in the ,我可能会将其编码为

  unab varlist : `0' regress price `varlist' regress price `varlist', robust qreg price `varlist' exit 

unab东西将作为一个保护元素:如果input不是合法的varlist,程序将停止并显示一条错误消息。

在我看到的最坏的情况下,全球被定义后只用了一次。

在某些情况下,当你想要使用全局variables的时候,因为否则你必须把这个血腥的事情传递给其他的文件或程序。 一个例子,我发现这个全局variables是不可避免的,那就是编码一个最大似然估计量,在那里我不知道我会有多less个方程和参数。 Stata坚持(用户提供的)可能性评估器将有特定的方程。 所以我必须在全局variables中积累我的方程,然后在Stata需要parsing的语法描述中用全局variables来呼叫我的求值程序:

 args lf $parameters 

其中lf是目标函数(对数似然)。 我在正常混合包( denormix )和validation性因子分析软件包( confa )中遇到过至less两次这样的问题。 当然,你可以findit他们两个。

一个划分意见的全局variables的例子是stringsAsFactors将数据读入R或创build数据框的问题。

 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 

由于在R中实现了选项的方式,这不能真正被纠正 – 任何事情都可能在你不知道它的情况下改变它们,因此同一块代码不能保证返回完全相同的对象。 约翰·钱伯斯(John Chambers)在他最近的书中哀叹这个特征

R中的病态示例是使用R中的全局variablespi来计算圆的面积。

 > 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 

当然,可以通过强制使用base::pi防御性地写函数foo() ,但是除非打包并使用NAMESPACE否则这种方式在普通用户代码中可能不可用:

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

这突出了你可以通过依赖任何不属于你的函数范围或明确作为参数传入的东西。

这里有一个有趣的病理例子,涉及全局和局部定义的replace函数,全局赋值和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 

replace函数使用由is.na(x)的本地值返回的索引,而不是返回[1] 1 0 0 0 1 0 1 0 ,即使分配给x的全局值。 此行为在R语言定义中进行了说明。

R中的一个简单而有说服力的例子就是像这样运行:

 .Random.seed <- 'normal' 

我select了“正常”作为某人可能select的东西,但是你可以在那里使用任何东西。

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

 rnorm(10) 

那么你可以指出,任何全局variables都可能发生同样的事情。

我也用这个例子:

 x <- 27 z <- somefunctionthatusesglobals(5) 

然后问学生x的价值是多less; 答案是我们不知道。

通过试验和错误,我已经了解到,我需要非常明确地命名我的函数参数(并确保在开始和沿着函数时进行足够的检查),使所有内容尽可能健壮。 如果variables存储在全局环境中,那么尤其如此,但是您尝试使用自定义贵重物品来debugging函数 – 而不是加起来! 这是一个简单的例子,结合了错误的检查和调用全局variables。

 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 

今天试图教导这个例子的草图。 具体来说,本文着重于试图直观地说明为什么全局可以导致问题,所以它尽可能地抽象出来,试图说明从代码中可以或不能得出什么结果(将函数作为黑盒子)。

设置

这是一些代码。 根据给定的标准决定是否返回错误。

代码

 stopifnot( all( x!=0 ) ) y <- f(x) 5/x 

标准

情况1: f()是一个正常行为的函数,只使用局部variables。

情况2: f()不一定是一个正常行为的函数,可能会使用全局赋值。

答案

情况1:代码不会返回错误,因为第一行检查x不等于零,第三行除以x

情况2:代码可能会返回一个错误,因为f()可以例如从x减去1并将其分配给父环境中的x ,其中任何x等于1的元素可以被设置为零,并且第三行会通过零错误返回一个除法。

这是一个对统计types有意义的答案的尝试。

  • 命名空间冲突:通用名(x,i等)被重用,导致命名空间冲突

首先我们定义一个对数似然函数,

 logLik <- function(x) { y <<- x^2+2 return(sum(sqrt(y+7))) } 

现在我们编写一个不相关的函数来返回input的平方和。 因为我们懒惰,我们会这样做,把它作为一个全局variables传递给它,

 sumSq <- function() { return(sum(y^2)) } y <<- seq(5) sumSq() [1] 55 

我们的对数似然函数似乎performance得和我们预期的完全一样,拿一个参数并返回一个值,

 > logLik(seq(12)) [1] 88.40761 

但是,我们的其他function呢?

 > sumSq() [1] 633538 

当然,这是一个微不足道的例子,在复杂的程序中不会有任何例子。 但是希望这会引发一场关于跟踪全局而不是本地更难的讨论。

R中,你也可以尝试向他们展示,通常不需要使用全局variables,因为你可以通过改变环境来访问函数 内部定义的variables。 例如下面的代码

 zz="aaa" x = function(y) { zz="bbb" cat("value of zz from within the function: \n") cat(zz , "\n") cat("value of zz from the function scope: \n") with(environment(x),cat(zz,"\n")) }