R Functional Programming
Functional programming treats functions as first-class objects — you pass them as arguments, return them from other functions, and store them in lists. R supports functional programming deeply through the purrr package and base R tools. This style produces shorter, more testable, and more composable code than imperative loops.
Core Functional Programming Concepts
Concept Description ────────────────────────────────────────────────────────────────── First-class functions Functions stored in variables, passed as args Higher-order functions Functions that take/return other functions Pure functions Same input → same output, no side effects Immutability Do not modify inputs; return new values Function composition Chain small functions into larger ones
map() — Apply a Function to Each Element
library(purrr) numbers <- list(1, 4, 9, 16, 25) map(numbers, sqrt) # returns list of results map_dbl(numbers, sqrt) # returns double vector: 1 2 3 4 5 map_chr(numbers, as.character) # returns character vector map_lgl(numbers, \(x) x > 5) # returns logical vector
map() family: map() → always returns a list map_dbl() → returns numeric vector map_int() → returns integer vector map_chr() → returns character vector map_lgl() → returns logical vector map_df() → returns data frame (bind rows)
map2() and pmap() — Multiple Inputs
# map2: iterate over two vectors in parallel lengths <- c(4, 6, 8) widths <- c(3, 5, 2) map2_dbl(lengths, widths, \(l, w) l * w) # [1] 12 30 16 # pmap: iterate over any number of lists params <- list( mean = c(0, 5, 10), sd = c(1, 2, 3), n = c(100, 200, 150) ) pmap(params, rnorm) # generates random samples for each parameter set
reduce() — Combine Elements Cumulatively
# Reduce a list to a single value reduce(c(1,2,3,4,5), `+`) # 15 (like sum) reduce(c(2,3,4), `*`) # 24 (like prod) reduce(list(df1, df2, df3), rbind) # stack data frames # With accumulate — see intermediate steps accumulate(c(1,2,3,4,5), `+`) # [1] 1 3 6 10 15
Filter Functions
data <- list(4, -2, 9, -7, 0, 15, -3) keep(data, \(x) x > 0) # keep positive: 4 9 15 discard(data, \(x) x > 0) # discard positive: -2 -7 0 -3 some(data, \(x) x > 10) # TRUE (any > 10?) every(data, \(x) x > 0) # FALSE (all positive?) detect(data, \(x) x > 8) # 9 (first match)
Function Factories (Closures)
# Returns a new function customized by the argument
make_power <- function(n) {
function(x) x^n
}
square <- make_power(2)
cube <- make_power(3)
square(4) # 16
cube(3) # 27
# Practical: make a percentage formatter
make_formatter <- function(prefix="", suffix="", digits=1) {
function(x) paste0(prefix, round(x, digits), suffix)
}
pct_format <- make_formatter(suffix="%")
inr_format <- make_formatter(prefix="₹", digits=0)
pct_format(87.534) # "87.5%"
inr_format(45678) # "₹45678"
Function Composition
library(purrr)
# compose() combines functions right-to-left
clean_text <- compose(trimws, tolower)
clean_text(" HELLO WORLD ") # "hello world"
# Chain with pipes instead
" HELLO WORLD " |> tolower() |> trimws()
walk() — Side Effects Without Return
# Like map() but used when you care about side effects, not the return
files <- c("report1.csv", "report2.csv", "report3.csv")
walk(files, function(f) {
cat("Processing:", f, "\n")
# ... process each file
})
Practical: Process Multiple Datasets
datasets <- list(
sales = data.frame(region="N", rev=c(100,200,150)),
returns = data.frame(region="S", rev=c(50,80,60)),
refunds = data.frame(region="E", rev=c(30,40,35))
)
# Compute summary stats for each dataset
summaries <- map(datasets, function(df) {
list(
total = sum(df$rev),
mean = mean(df$rev),
rows = nrow(df)
)
})
map_dbl(summaries, "total") # extract "total" from each
# sales returns refunds
# 450 190 105
Functional programming makes complex data pipelines from simple, reusable building blocks. The purrr package brings consistent, predictable map-reduce patterns to R. As your analyses grow in complexity, the ability to compose small pure functions into powerful pipelines becomes one of R's biggest advantages over spreadsheet-based workflows.
