with
with(data, expr, ...) The with() function evaluates an R expression inside a local environment constructed from a data frame, list, or environment. Names in data are looked up before anything in the caller’s environment, which lets you refer to them by short name instead of writing df$col over and over. It is one of the oldest pieces of non-standard evaluation in base R and ships in every supported version.
Signature and arguments
with(data, expr, ...)
data— an environment, list, data frame, or integer used bysys.call()-style helpers. Its names become local bindings inside the constructed environment.expr— the expression to evaluate. Use a{ ... }block when you need more than one statement, since;only binds the first piece....— present for method extensions; the default method does not consume it.
with() returns the value of expr and never modifies data. There is also a default method plus methods for data.frame and array; the list method behaves the same way as the default.
How it works
with() builds a fresh environment whose parent is the caller’s environment. The expression is evaluated inside that constructed environment, so names in data are found first. Anything in the caller’s environment, including functions and global objects, stays reachable through the parent. When the call returns, the constructed environment is discarded along with any assignments made inside it.
That is why a line like with(df, x <- x + 1) does not change df. The new x is created in the temporary environment and thrown away when with() returns. If you want the change to stick, reach for within() instead.
Basic examples
Run a summary inside a data frame:
with(mtcars, mean(mpg))
#> [1] 20.09062
The same call written out by hand returns the identical value, but it makes the data source explicit and is the form you want in package code or anywhere a reader is not already staring at mtcars:
mean(mtcars$mpg)
#> [1] 20.09062
The two forms are equivalent for atomic columns. The with() form is shorter; the explicit form does not depend on lookup order and is easier to grep for.
Filter rows without typing the data frame name twice:
with(airquality, Ozone[Temp > 80])
#> [1] 39 36 44 45 36 44 46 30 ...
This is the canonical “with” pattern: build a logical or numeric condition, then index. The same code without with() would repeat airquality$ in two places and is easy to mistype.
Run a compound model on a fresh data frame without ever naming the columns:
with(
data.frame(
u = c(5, 10, 15, 20, 30, 40, 60, 80, 100),
lot1 = c(118, 58, 42, 35, 27, 25, 21, 19, 18),
lot2 = c(69, 35, 26, 21, 18, 16, 13, 12, 12)
),
list(
summary(glm(lot1 ~ log(u), family = Gamma)),
summary(glm(lot2 ~ log(u), family = Gamma))
)
)
The columns u, lot1, and lot2 are reachable inside the model formulas because the data frame is the constructed environment. The example is lifted straight from the help page and shows the pattern at its purest: an anonymous data frame drives an anonymous analysis.
Using a block expression
For more than one statement, wrap the body in { ... }. Without braces, with() only sees the first expression because of how ; binds:
with(mtcars, {
v8 <- cyl == 8
sum(v8)
})
#> [1] 15
Inside the block, every new binding lives only in the temporary environment. After the call, v8 is gone from your workspace. If you want the helper to survive, assign the whole call: flag <- with(mtcars, cyl == 8).
A common pattern is to drive a plot straight from the columns of a data set, where the boxplot formula len ~ dose reads naturally because both names resolve in the constructed environment:
with(ToothGrowth, {
boxplot(
len ~ dose, boxwex = 0.25, at = 1:3 - 0.2,
subset = (supp == "VC"), col = "yellow",
main = "Guinea Pigs' Tooth Growth",
xlab = "Vitamin C dose mg", ylab = "tooth length",
ylim = c(0, 35)
)
})
For modeling and graphics, prefer the data argument of the function when it has one. boxplot(len ~ dose, data = ToothGrowth, ...) is the same plot with the source of len and dose right there in the call.
Creating modified copies with within()
within() shares the same shape but inspects the local environment after evaluating expr and copies any new or changed bindings back into data. It is the right tool when you want to derive new columns and keep them:
aq <- within(airquality, {
lOzone <- log(Ozone)
Month <- factor(month.abb[Month])
cTemp <- round((Temp - 32) * 5 / 9, 1)
S.cT <- Solar.R / cTemp
rm(Day, Temp)
})
head(aq)
within() is the safer replacement for the older transform(). It works on any data frame, evaluates each new binding sequentially, and lets you rm() columns you no longer need. The result is a fresh data frame; the input is left alone.
For lists, the list method adds a keepAttrs argument. Setting it to FALSE skips the input’s attributes and name reordering, which is faster when you do not need either:
lst <- list(a = 1, b = 2)
within(lst, { c <- a + b })
#> $a
#> [1] 1
#> $b
#> [1] 2
#> $c
#> [1] 3
When with() shines, and when it does not
Reach for with() for short interactive sessions, plotting, and one-off summaries where the data is in scope and the expression is brief. The official R help page says it plainly: with() is meant for interactive use, and using it inside a function or library can silently shadow local variables in the caller.
For production code, prefer the data argument of modeling and graphics functions, or write df$col explicitly. Both make the source of every name obvious, and neither relies on the constructed-environment trick. attach() is a related but older alternative that puts the data frame on the search path; with() is almost always a better choice because the binding only exists for the duration of the call.
Common gotchas
<-does not mutate the data. Usewithin()if you want the change to stick.- Masking can bite you. If
datahas a column namedxand your caller also has anx, the column wins. - Single-expression parsing.
with(data, a <- 1; a + 1)is parsed asa + 1because of operator precedence; reach for{ ... }whenever the body has more than one statement. - Data frames only take atomic columns, so
within(df, tmp <- list(...))will fail. with()does not replace thedataargument oflm,glm,boxplot, and friends. Wrap a call inwith()only for short scripts; use thedataargument for anything that ships.
For programming, prefer explicit df$col references or pass data to the modeling function. The tidyverse equivalent of within() for column derivation is dplyr::mutate().
See also
subset()— filter a data frame by a condition.data.frame— the most commondataargument.list— using a list asdata, including thekeepAttrsargument.