Environments

Se você utiliza o R regularmente, com certeza já se deparou com o termo environment. Ele aparece como um painel do RStudio, quando acessamos o código de uma função e (implicitamente) quando carregamos pacotes. Neste post, vamos tentar responder as três perguntas básicas sobre qualquer coisa no R: 1. o que é? 2. para que serve? e 3. como NÃO usar?

O que é?

Definindo de uma maneira bem simples, environments são locais onde objetos são armazenados, isto é, conjuntos de ligações entre símbolos e valores. Por exemplo, quando fazemos a atribuição abaixo,

a <- 4

estamos criando uma associação do símbolo a ao valor 4, que, por padrão, é guardada dentro do global environment.

ls(globalenv())
## [1] "a"

Assim, quando rodarmos o símbolo a, o R, por padrão, vai procurar dentro desse environment um valor para devolver. No caso, o valor 4.

a
## [1] 4

Mais formalmente, environments podem ser definidos como a junção de duas coisas: um conjunto de pares (símbolo, valor); e um ponteiro para um outro environment. Quando o R não encontra um valor para um símbolo no environment em que está procurando, ele passa a procurar no próximo, o environment para qual o primeiro está apontando, chamado de environment pai. Assim, os environments se estruturam como uma árvore, cuja raiz é um environment vazio.

emptyenv()
## <environment: R_EmptyEnv>

O que faz?

É possível criar novos environments com a função new.env()

magrathea <- new.env()

e criar objetos dentro desse environments com a função assign()

assign("a", 8, envir = magrathea)
ls(magrathea)
## [1] "a"

Agora temos um objeto chamado a no global environment e no magrathea, que nós criamos. Note que o R inicia a busca no global environment.

a
## [1] 4

Vamos agora criar outro objeto dentro de magrathea.

assign("b", 15, envir = magrathea)

Observe que se procurarmos simplesmente por b, o R não vai encontrar um valor para associar.

b

Acontece que magrathea é um environment “abaixo” do global na hierarquia, e o R só estende a sua busca para environments acima (sim, estou pensando numa árvore de ponta-cabeça).

parent.env(magrathea)
## <environment: R_GlobalEnv>

Se criarmos agora um objeto no global

c <- 16

e usarmos a função get() para procurá-lo no environment que criamos, o R irá encontrá-lo porque o global é o environment pai de magrathea.

get("c", envir = magrathea)
## [1] 16

Essa estrutura é muito útil na hora de utilizar funções. Sempre que uma função é chamada, um novo environment é criado, o environment de avaliação, que contém os objetos usados como argumento da função, os objetos criados dentro da função e aponta para o environment onde a função foi criada (geralmente o global).

f <- function(a, b) {

  c <- a + b

  return(c)

}

environment(f)
## <environment: R_GlobalEnv>

Esse comportamento nos permite fazer duas coisas. Primeiro, os cálculos realizados dentro das funções não modificam os objetos do global.

f(23, 42)
## [1] 65

c
## [1] 16

Segundo, podemos utilizar objetos dentro da função sem defini-los lá dentro.

f <- function(b) {

  return(a + b)

}

f(108)
## [1] 112

Neste caso, como o R não encontrou o símbolo a dentro do environment de avaliação, ele foi buscar no pai, o global.

Como não usar?

Agora que temos uma visão ao menos superficial da estrutura de environments, podemos entender melhor porque usar a função attach() é uma prática não recomendada ao programar em R.

Se utilizarmos a função search(), ela nos devolverá o “caminho” de environments, começando do global (magrathea não será exibido).

search()
## [1] ".GlobalEnv"        "package:stats"     "package:graphics" 
## [4] "package:grDevices" "package:utils"     "package:datasets" 
## [7] "package:methods"   "Autoloads"         "package:base"

Repare que os pacotes carregados geram um novo environment na árvore.

library(ggplot2)
search()
##  [1] ".GlobalEnv"        "package:ggplot2"   "package:stats"    
##  [4] "package:graphics"  "package:grDevices" "package:utils"    
##  [7] "package:datasets"  "package:methods"   "Autoloads"        
## [10] "package:base"

É por isso que, ao carregar um pacote, podemos utilizar as suas funções sem a necessidade de escrever coisas do tipo ggplot2::geom_point(). Agora, veja o que acontece quando usamos a função attach()

mighty <- list("Jason" = "vermelho", "Zach" = "Preto", "Billy" = "Azul",
     "Trini" = "Amarela", "Kimberly" = "Rosa", "Thomas" = "Verde")

attach(mighty)
search()
##  [1] ".GlobalEnv"        "mighty"            "package:ggplot2"  
##  [4] "package:stats"     "package:graphics"  "package:grDevices"
##  [7] "package:utils"     "package:datasets"  "package:methods"  
## [10] "Autoloads"         "package:base"

Um novo environment mighty é criado acima do global! Isso quer dizer que se você não tiver total conhecimento dos objetos que estão sendo anexados, você estará criando uma lista de objetos “invisíveis” que podem ser avaliados mesmo dentro de funções. E veja o que acontece quando carregamos mais pacotes

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
search()
##  [1] ".GlobalEnv"        "package:dplyr"     "mighty"           
##  [4] "package:ggplot2"   "package:stats"     "package:graphics" 
##  [7] "package:grDevices" "package:utils"     "package:datasets" 
## [10] "package:methods"   "Autoloads"         "package:base"

O environment do pacote dplyr aparece antes do mighty. Isso quer dizer que os objetos do mighty podem ser mascarados por todos os pacotes que você carregar a seguir. Veja um simples exemplo de como as coisas podem dar errado.

dados <- tibble::tibble(paciente = 1:30,
                        cancer = rbinom(30, size = 1, prob = 0.5))

attach(dados)
cancer
##  [1] 1 0 0 1 0 0 1 1 1 0 0 1 1 0 1 1 1 0 1 0 0 0 1 0 1 1 0 1 0 1

Com o código acima, criamos um banco de dados representando 30 pacientes com (1) ou sem (0) um certo tipo de câncer. As variáveis paciente e cancer foram anexadas ao rodarmos attach(dados).

Agora, imagine se esse banco de dados tiver informações de tempo até a remissão do câncer e quisermos rodar modelos de sobrevivência. Um passo natural seria carregar a biblioteca survival.

library(survival)
## 
## Attaching package: 'survival'
## The following object is masked from 'dados':
## 
##     cancer
search()
##  [1] ".GlobalEnv"        "package:survival"  "dados"            
##  [4] "package:dplyr"     "mighty"            "package:ggplot2"  
##  [7] "package:stats"     "package:graphics"  "package:grDevices"
## [10] "package:utils"     "package:datasets"  "package:methods"  
## [13] "Autoloads"         "package:base"

O pacote survival também tem um objeto chamado cancer. Assim, ao carregá-lo, o environment survival ficará na frente do environment dados na árvore e, se não prestarmos atenção com o warning, esse será o nosso novo objeto cancer.

head(cancer)
##   inst time status age sex ph.ecog ph.karno pat.karno meal.cal wt.loss
## 1    3  306      2  74   1       1       90       100     1175      NA
## 2    3  455      2  68   1       0       90        90     1225      15
## 3    3 1010      1  56   1       0       90        90       NA      15
## 4    5  210      2  57   1       1       90        60     1150      11
## 5    1  883      2  60   1       0      100        90       NA       0
## 6   12 1022      1  74   1       1       50        80      513       0

Assim, se for utilizar a função attach() é preciso ter muito cuidado com o que se está fazendo. E a melhor dica é não use.

Esse post foi apenas uma introdução sobre como os environments funcionam. Ainda existe muito mais coisa por trás, como o conceito de namespaces. Se você quiser saber mais, recomendo como primeira parada esse post, do qual tirei boa parte das informações passadas aqui. Também vale a pena dar uma olhada nas definições nesse link.

Sugestões, dúvidas e críticas, deixe um comentário!

comments powered by Disqus