R Environment and Scope

An environment is a container where R stores variable names and their values. Scope defines which variables are visible from which locations in your code. Understanding how R finds variables — by searching environments in a specific order — prevents many subtle bugs.

What Is an Environment?

Think of environments as nested rooms:

  ┌─────────────────────────────────────┐
  │ Global Environment (.GlobalEnv)     │
  │  x <- 10                            │
  │  my_func <- function() { ... }      │
  │                                     │
  │  ┌──────────────────────────────┐   │
  │  │ Function Environment         │   │
  │  │  (created when func runs)    │   │
  │  │  y <- 20  (local only)       │   │
  │  └──────────────────────────────┘   │
  └─────────────────────────────────────┘
         ↓ parent
  ┌─────────────────────────────────────┐
  │ Base Environment                    │
  │  (built-in R functions: mean, etc.) │
  └─────────────────────────────────────┘

Global vs Local Scope

x <- 100    # global variable

my_func <- function() {
  y <- 200            # local variable (only inside function)
  cat("Inside function — x:", x, "y:", y, "\n")
}

my_func()
# Inside function — x: 100  y: 200

cat("Outside function — x:", x, "\n")
# Outside function — x: 100
# cat("y:", y)  → ERROR: object 'y' not found

Variable Lookup Chain

When R looks for a variable inside a function:
  1. Check the function's own environment
  2. Check the parent environment (where function was defined)
  3. Check the global environment
  4. Check package environments
  5. Check the base environment
  6. ERROR: object not found

This chain is called the lexical scoping rule.

Local Variable Shadows Global

x <- "global"

demo <- function() {
  x <- "local"       # creates a NEW local x — global x unchanged
  cat("Inside:", x, "\n")
}

demo()
# Inside: local

cat("Outside:", x, "\n")
# Outside: global   ← global x is unchanged

The <<- Operator — Global Assignment from Inside a Function

counter <- 0

increment <- function() {
  counter <<- counter + 1    # modifies the GLOBAL counter
}

increment()
increment()
increment()
print(counter)   # 3

Use <<- cautiously. Functions that modify global state are harder to test and debug. Prefer returning values and reassigning outside the function when possible.

Environment Functions

environment()          # current environment
globalenv()            # the global environment object
baseenv()              # the base environment
emptyenv()             # the empty environment (top of chain)
parent.env(e)          # parent of environment e
ls(envir=globalenv())  # list variables in global env
exists("x")            # check if variable exists
get("x")               # get value of variable by name (string)
assign("x", 42)        # assign by name (string)

Creating Isolated Environments

# Create a new environment
my_env <- new.env()

# Assign variables in that environment
my_env$score <- 95
my_env$name  <- "Asha"

# Access them
my_env$score        # 95
ls(my_env)          # "name" "score"

# Evaluate expression in a specific environment
eval(quote(score + 5), envir=my_env)   # 100

Function Closures

# A closure captures its enclosing environment
make_adder <- function(n) {
  function(x) x + n    # inner function captures 'n'
}

add5  <- make_adder(5)
add10 <- make_adder(10)

add5(3)    # 8
add10(3)   # 13

# add5 remembers n=5 even after make_adder() finished running

Checking Variable Environments

x <- 42
f <- function() { x }

environment(f)           # the environment where f was defined
environmentName(globalenv())   # "R_GlobalEnv"

Scope and environments underpin every R program. Most bugs related to "variable not found" or "wrong value being used" come from misunderstanding which environment R is looking in. Functions, closures, and R6 classes all use environments as their core mechanism — understanding this topic unlocks a much deeper level of R mastery.

Leave a Comment

Your email address will not be published. Required fields are marked *