- Blog
- How to Call Functions in
R
798 words4 min read
Simple:
fun(args)
Blog post done.
In a sense, this tongue-in-cheek response is completely accurate, but the nuance which has been buzzing about my head is that args bit—so, a slightly more descriptive and less inflammatory title for this piece may be: “How to Pass Arguments to Functions in R” but whatever. So what’s the nuance?
Well, the main thing I’ve been thinking about is when you can use arguments by position, instead of by name—i.e. when is it ok to do mean(1:10, TRUE) vs mean(x = 1:10, na.rm = TRUE). I’ve landed on some loose heuristics which I think are helpful, but I think the problem is sufficiently ill-specified that there cannot be any catch-all solutions.
Position
I think its ok to pass arguments by position when you own the function you are calling, and are scripting. I.e., if you write a function for your data processing pipeline (as you should!), you can call that function naively. That function is yours, that function isn’t likely to change, and the calling code probably doesn’t do too much with the function anyway; the function is probably rather bespoke.
It is also ok to pass arguments by position when the calling code is stable, and the arguments standard. I never name my arguments to mean() and friends because they’re in base, and there is no way R Core would do a rug pull and change the formals of these integral functions. Similarly, but somewhat softer, are functions featured in the tidyverse. Any function which takes data as its first argument is probably not going to change that in a long time, and certainly not without a deprecation cycle, so I would never pass data by name. Same deal with, say, aesthetics in a ggplot2 object—I’m not liable to write mapping = aes(..). These are arguments which are integral to functions which are integral to their respective packages which are integral to modern R. These are rock solid, just like mean and other base friends.
In short: pass arguments by position when you own the callee and the caller is in a non-critical, typically scripting, environment. Also pass arguments by position when those arguments are stable.
Name
Obviously, in all other scenarios, but as that is not constructive, let’s give a few examples.
Pass arguments by name when you are using experimental stuff. For example, mutate() and some friends support an argument called .by1 which is experimental at time of writing. If you want to use it, you definitely should mention it by name—who knows when the API might change. You should pass args by name when you don’t own the function, especially if you are building a package instead of scripting. Additionally, naming arguments to a function can be useful as a mean sof auto-explaining what that code is doing, especially if you’re using an argument which isn’t super common2:
pnorm(1)
#> [1] 0.8413447
pnorm(1, 0, 1, TRUE, TRUE)
#> [1] -0.1727538
pnorm(1, 0, 1, lower.tail = TRUE, log = TRUE)
#> [1] -0.1727538
Don’t use T and F! These are unprotected variables, and could technically be anything at runtime.
T <- FALSE
T
#> [1] FALSE
TRUE <- FALSE
#> Error in TRUE <- FALSE: invalid (do_set) left-hand side to assignment
See! Evil!
Devs can (and sometimes should!) force users to pass arguments by name—functions can use ... to force certain arguments be passed by name, lest they be eaten by the dots. For example:
my_func <- \(x, ..., cool_thing = identity) x^2 |> cool_thing()
my_func(1:5)
#> [1] 1 4 9 16 25
my_func(1:5, stop("ERROR"))
#> [1] 1 4 9 16 25
my_func(1:5, mean)
#> [1] 1 4 9 16 25
my_func(1:5, cool_thing = mean)
#> [1] 11
The design of this trivial function forces cool_thing to be specified by name and means users can’t rely on it being in a stable position. If I were writing the function for actual use, I’d probably guard against the dots somehow, if the function doesn’t need them—though the name is escaping me, I’ve definitely used a few packages which do this.
Conclusion
Like I said at the onset, this whole endeavour is loosey-goosey; there is no One Ring. That being said, I know I can be better about naming arguments sometimes, especially when building packages, and it is probably something worth thinking about next time you’re writing code.
Code output created on 2025-11-21 with reprex v2.1.1 and R 4.5.2.
Footnotes
-
“Optionally, a [tidy select] selection of columns to group by for just this operation, functioning as an alternative to
group_by().” Back to reference 1 -
What constitutes “not common” of course is variable in some sense, but note that code is usually read more than written and be verbose to a degree which aligns with the expected familiarity of devs who will read your code. For pedagogical purposes, naming (almost) everything usually doesn’t really hurt. Back to reference 2

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.