Skip to content

R - Functions

A function is a reusable block of code that performs a specific task. In R, functions are first-class objects, meaning you can work with them just like any other variable. Using functions is essential for writing clean, efficient, and maintainable code.

You create an R function using the function keyword. The basic syntax is:

function_name <- function(argument_1, argument_2, ...) {
# Code to execute (the function body)
return_value # The last evaluated expression is returned automatically
}
  • Function Name: The name you use to call the function (e.g., function_name). Stored as an object in the R environment.
  • Arguments: Placeholders for the data you’ll pass into the function. They can have default values, making them optional.
  • Function Body: The set of R statements that define what the function does.
  • Return Value: The result of the function. In R, the value of the last expression evaluated inside the function is automatically returned. You can also use return() explicitly.

A pure function is a function that, for the same input, will always return the same output and has no observable side effects (like printing to the console or modifying a global variable). It’s a best practice to write pure functions whenever possible, as they are easier to test and reason about.

Let’s refactor the original example to follow this principle. Instead of printing the squares inside the loop (a side effect), our function will return a vector of the squared numbers.

# A user-defined function to calculate the squares of a sequence of numbers.
# This is a 'pure' function: it returns a value and has no side effects.
calculate_squares <- function(n) {
# Input validation: check if n is a single positive number
if (!is.numeric(n) || length(n) != 1 || n < 1) {
stop("Argument 'n' must be a single positive integer.")
}
# The 'sapply' function is a more idiomatic R way to apply a function over a vector
sapply(1:n, function(x) x^2)
}
# Call the function and store the result
squared_numbers <- calculate_squares(6)
# Now, print the result (the side effect is outside the function)
print(squared_numbers)

When we execute the above code, it produces a clean vector as output:

[1] 1 4 9 16 25 36

You can provide arguments to a function by their position or by explicitly naming them. Naming arguments is often clearer and less error-prone.

# A simple function with multiple arguments
calculate_result <- function(a, b, c) {
result <- a * b + c
return(result)
}
# Call by position (5*3 + 11)
print(calculate_result(5, 3, 11))
# Call by name (order doesn't matter)
print(calculate_result(c = 3, a = 11, b = 5))

This produces the following result:

[1] 26
[1] 58

You can provide default values for arguments, making them optional when the function is called.

# A function with default arguments
create_greeting <- function(name, greeting = "Hello") {
paste(greeting, name, sep = ", ")
}
# Call the function using the default greeting
print(create_greeting("Alice"))
# Call the function, overriding the default
print(create_greeting("Bob", greeting = "Goodbye"))

This produces the following result:

[1] "Hello, Alice"
[1] "Goodbye, Bob"

The pipe operator, %>% from the magrittr package (or the native |> in R 4.1+), is a powerful tool for making your code more readable. It takes the result of the expression on its left and passes it as the first argument to the function on its right.

library(magrittr) # or library(dplyr)
# Without the pipe
print(round(mean(1:10)))
# With the pipe: reads like a sentence "take 1:10, then find the mean, then round it"
1:10 %>%
mean() %>%
round() %>%
print()

Both expressions produce the same result, but the piped version is often easier to follow for complex chains of operations.

[1] 6
[1] 6

R is a great language for functional programming. The purrr package from the tidyverse provides a complete and consistent set of tools for working with functions and vectors. It’s a great next step after mastering the basics. For example, purrr::map() is a powerful alternative to loops or sapply().