R Functions — Reusable Code Building Blocks

R BasicsFunctionsFree Lesson

Advertisement

R Functions — Reusable Code Building Blocks

Learning Objectives

By the end of this tutorial, you will be able to:

  • Define functions with single and multiple arguments
  • Use default values, variable arguments, and named arguments
  • Create anonymous and recursive functions
  • Apply functional programming concepts with Map(), Reduce(), and Filter()
  • Write clean, well-structured functions

Creating Functions

Basic Syntax

# Simple function
greet <- function(name) {
  paste("Hello,", name, "!")
}

greet("Alice")
# [1] "Hello, Alice !"

# Single expression (implicit return)
square <- function(x) x^2
square(5)  # [1] 25

Multiple Arguments

# Multiple arguments
add <- function(a, b) {
  a + b
}
add(3, 5)  # [1] 8

# Named arguments
add(a = 3, b = 5)  # [1] 8
add(b = 5, a = 3)  # [1] 8 (order doesn't matter)

Return Values

# Explicit return
divide <- function(a, b) {
  if (b == 0) {
    return("Cannot divide by zero")
  }
  a / b
}

divide(10, 3)   # [1] 3.333333
divide(10, 0)   # [1] "Cannot divide by zero"

# Multiple return values (as list)
get_stats <- function(x) {
  list(
    mean = mean(x),
    sd = sd(x),
    n = length(x)
  )
}

stats <- get_stats(1:10)
stats$mean   # [1] 5.5
stats$sd     # [1] 3.02765

Default Arguments

# Default values
greet <- function(name, greeting = "Hello") {
  paste(greeting, name, "!")
}

greet("Alice")           # [1] "Hello Alice !"
greet("Alice", "Hi")     # [1] "Hi Alice !"
greet("Alice", "Good morning")  # [1] "Good morning Alice !"

# Multiple defaults
power <- function(x, base = 2, times = 1) {
  (x^base) * times
}

power(3)          # [1] 9
power(3, base = 3) # [1] 27
power(3, times = 5) # [1] 45

Variable Arguments

... (Ellipsis)

# Pass arguments to another function
summary_stats <- function(x, ...) {
  list(
    mean = mean(x, ...),
    sd = sd(x, ...)
  )
}

summary_stats(c(1, 2, NA, 4, 5))
# $mean: NA (because of NA)

summary_stats(c(1, 2, NA, 4, 5), na.rm = TRUE)
# $mean: 3
# $sd: 1.732051

# Flexible arguments
add_all <- function(...) {
  sum(...)
}
add_all(1, 2, 3, 4, 5)  # [1] 15

# Capture arguments
log_args <- function(...) {
  args <- list(...)
  cat("Arguments received:", paste(names(args), collapse = ", "), "\n")
}
log_args(x = 1, y = 2, z = 3)
# Arguments received: x y z

Argument Matching

R matches arguments in three ways:

# 1. Exact name matching
f <- function(x, y, z) {
  list(x = x, y = y, z = z)
}
f(x = 1, y = 2, z = 3)

# 2. Partial name matching (unique prefix)
f(x = 1, y = 2, z = 3)
f(x = 1, y = 2, z = 3)

# 3. Positional matching (after named args)
f(1, 2, 3)
f(z = 3, x = 1, 2)  # y matched by position

# Best practice: always use full names
f(x = 1, y = 2, z = 3)

Scope and Environments

# Global vs local
x <- 10

my_func <- function() {
  x <- 20  # Local x
  cat("Inside:", x, "\n")
}

my_func()   # Inside: 20
cat("Outside:", x, "\n")  # Outside: 10

# Lexical scoping — functions see their parent environment
outer <- function() {
  x <- "outer"
  inner <- function() {
    cat(x, "\n")  # Can see outer's x
  }
  inner()
}
outer()  # outer

# Global assignment
x <- 10
modify <- function() {
  x <<- 20  # Modifies global x
}
modify()
x  # [1] 20

Anonymous Functions

# Lambda functions (no name)
function(x) x^2

# Assign to variable
square <- function(x) x^2
square(5)  # [1] 25

# Use inline with apply family
sapply(1:5, function(x) x^2)
# [1]  1  4  9 16 25

# Short form (R 4.1+)
sapply(1:5, \(x) x^2)
# [1]  1  4  9 16 25

# In pipes
1:5 |>
  sapply(\(x) x^2) |>
  sum()
# [1] 55

Higher-Order Functions

lapply, sapply, vapply

# lapply — always returns list
lapply(1:5, function(x) x^2)

# sapply — simplified (vector when possible)
sapply(1:5, function(x) x^2)
# [1]  1  4  9 16 25

# vapply — type-safe sapply
vapply(1:5, function(x) x^2, numeric(1))
# [1]  1  4  9 16 25

Map, Reduce, Filter

# Map — apply function to multiple vectors
Map("+", c(1, 2, 3), c(10, 20, 30))
# [[1]]
# [1] 11
# [[2]]
# [1] 22
# [[3]]
# [1] 33

# Reduce — fold/accumulate
Reduce("+", c(1, 2, 3, 4, 5))  # [1] 15

# Filter
Filter(function(x) x > 3, c(1, 2, 3, 4, 5))
# [1] 4 5

# Find
Find(function(x) x > 3, c(1, 2, 3, 4, 5))
# [1] 4

# Position
Position(function(x) x > 3, c(1, 2, 3, 4, 5))
# [1] 4

Purrr (tidyverse)

library(purrr)

# map — like lapply
map(1:5, \(x) x^2)

# map_dbl — like vapply for doubles
map_dbl(1:5, \(x) x^2)
# [1]  1  4  9 16 25

# map_chr, map_lgl, map_int — type-safe variants
map_chr(1:5, \(x) paste("item", x))

# map2 — two inputs
map2(c(1, 2, 3), c(10, 20, 30), \(x, y) x + y)

# pmap — multiple inputs
pmap(list(c(1, 2, 3), c(10, 20, 30), c(100, 200, 300)), \(x, y, z) x + y + z)

# keep and discard
keep(1:10, \(x) x > 5)   # [1]  6  7  8  9 10
discard(1:10, \(x) x > 5) # [1] 1 2 3 4 5

Recursive Functions

# Factorial
factorial_r <- function(n) {
  if (n <= 1) return(1)
  n * factorial_r(n - 1)
}

factorial_r(5)  # [1] 120

# Fibonacci
fib <- function(n) {
  if (n <= 1) return(n)
  fib(n - 1) + fib(n - 2)
}

sapply(0:10, fib)
# [1]  0  1  1  2  3  5  8 13 21 34 55

# Binary search
binary_search <- function(arr, target, low = 1, high = length(arr)) {
  if (low > high) return(-1)
  mid <- (low + high) %/% 2
  if (arr[mid] == target) return(mid)
  if (arr[mid] < target) {
    binary_search(arr, target, mid + 1, high)
  } else {
    binary_search(arr, target, low, mid - 1)
  }
}

binary_search(1:100, 42)  # [1] 42

Practice Exercises

Exercise 1: BMI Calculator

Write a function that calculates BMI from weight (kg) and height (cm), and returns both the BMI value and the category.

Solution

bmi_calculator <- function(weight_kg, height_cm) {
  height_m <- height_cm / 100
  bmi <- weight_kg / (height_m ^ 2)

  category <- case_when(
    bmi < 18.5 ~ "Underweight",
    bmi < 25 ~ "Normal weight",
    bmi < 30 ~ "Overweight",
    TRUE ~ "Obese"
  )

  list(
    bmi = round(bmi, 1),
    category = category
  )
}

bmi_calculator(70, 175)
# $bmi: 22.9
# $category: "Normal weight"

Exercise 2: Custom Summary

Write a function that takes a numeric vector and returns a list with mean, median, sd, min, max, and IQR.

Solution

custom_summary <- function(x) {
  list(
    mean = mean(x, na.rm = TRUE),
    median = median(x, na.rm = TRUE),
    sd = sd(x, na.rm = TRUE),
    min = min(x, na.rm = TRUE),
    max = max(x, na.rm = TRUE),
    iqr = IQR(x, na.rm = TRUE)
  )
}

custom_summary(1:100)

Key Takeaways

  • Functions are first-class objects in R — can be assigned, passed, and returned
  • Use default arguments for flexibility
  • ... passes extra arguments to inner functions
  • Anonymous functions are useful with apply family
  • Lexical scoping — functions see their parent environment
  • Use lapply()/sapply() instead of loops for cleaner code
  • purrr provides type-safe map functions
  • Recursive functions are elegant for tree-like problems

Next: Learn about R Apply Family — the powerful apply functions.

Advertisement

Need Expert R Programming Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement