Functional programming elevates your R code from one‐off scripts to robust, maintainable workflows. In this post, you’ll learn how to:
Apply purrr’s mapping functions (
map(),map_df(),map2(), andpmap()) for clean iterationWrite custom R functions with clear inputs, outputs, and documentation
Handle errors gracefully using tryCatch() and purrr’s safe variants
Debug aggressively with browser(), traceback(), and related tools
By mastering these patterns and utilities, you’ll build code that scales, adapts, and surprises you with its reliability.
1. Mapping with purrr
Instead of writing explicit for loops, purrr promotes a declarative style where you “map” functions over lists or vectors. This yields shorter, more readable code.
install.packages("purrr")
library(purrr)
1.1 map(): Apply a function to each element
# Square each number in a vector
nums <- list(1, 2, 3, 4, 5)
squares <- map(nums, ~ .x ^ 2)
# list(1, 4, 9, 16, 25)
map()always returns a list.Use anonymous functions with
~for brevity.
1.2 map_df(): Return a data frame
When each iteration produces a data frame with identical columns, combine them into one:
# Simulate fetching stats for multiple groups
groups <- list("A", "B", "C")
stats <- map_df(groups, function(g) {
tibble(
group = g,
mean = rnorm(1, mean = as.numeric(charToRaw(g))),
sd = runif(1)
)
})
1.3 map2() and pmap(): Multiple inputs
map2(x, y, f) iterates over two vectors in parallel.
pmap(list(...), f) handles multiple arguments packaged as a list.
# Pairwise pasting
chars <- c("A", "B", "C")
nums <- c(1, 2, 3)
pairs <- map2(chars, nums, ~ paste(.x, .y, sep = "-"))
# list("A-1", "B-2", "C-3")
# pmap for three inputs
params <- list(
mean = c(0, 5, 10),
sd = c(1, 2, 3),
n = c(5, 5, 5)
)
samples <- pmap(params, ~ rnorm(..3, ..1, ..2))
1.4 safely(), possibly(), quietly()
Wrap your functions to handle errors without breaking the pipeline:
safe_log <- safely(log)
results <- map(list(10, "x", -5), safe_log)
# Each element is a list(result = x, error = NULL or error object)
possibly(f, otherwise) returns a default value on error.
quietly(f) suppresses printed output and returns messages/information.
2. Writing Custom Functions
Well‐designed functions encapsulate logic, making your code modular and testable.
2.1 Function skeleton
my_summary <- function(df, group_var, value_var) {
# Input validation
stopifnot(is.data.frame(df))
stopifnot(is.character(group_var), length(group_var) == 1)
stopifnot(is.character(value_var), length(value_var) == 1)
# Core logic
df %>%
group_by(.data[[group_var]]) %>%
summarize(
count = n(),
mean = mean(.data[[value_var]], na.rm = TRUE)
) %>%
ungroup()
}
2.2 Best practices
Clear inputs and outputs: Document each parameter’s type and purpose.
Error checking: Use
stop(),stopifnot(), or rlang’sabort()for meaningful messages.Return consistent types: Always return the same data structure (e.g., a tibble).
Roxygen documentation: Annotate functions with
#'tags for automatic help pages.
#' Summarize grouped statistics
#'
#' @param df A data frame or tibble.
#' @param group_var A string naming the grouping column.
#' @param value_var A string naming the numeric column to summarize.
#' @return A tibble with group counts and means.
#' @export
my_summary <- function(df, group_var, value_var) { ... }
3. Handling Errors Gracefully
Production pipelines must anticipate failures. Use tryCatch() to intercept errors and either recover or log them.
fetch_data <- function(url) {
tryCatch({
read_csv(url)
}, error = function(e) {
message("Failed to fetch: ", url, "\n Error: ", e$message)
return(NULL)
})
}
Alternatively, purrr’s possibly() wraps a function:
safe_fetch <- possibly(read_csv, otherwise = tibble(), quiet = FALSE)
data_list <- map(urls, safe_fetch)
4. Debugging Tools
Even well‐written functions may misbehave. R provides interactive debugging utilities to inspect state at runtime.
4.1 traceback()
After an error, call traceback() to view the call stack:
> my_summary(df, "group", "value")
Error: object 'value' not found
> traceback()
3: my_summary(df, "group", "value")
2: summarize(...)
1: my_summary(df, "group", "value")
4.2 browser()
Insert browser() inside your function to pause execution and inspect variables:
faulty <- function(x) {
browser()
x + y # y is undefined
}
# Run faulty(10), execution stops at browser prompt
Inside the browser prompt you can type variable names, call functions, or n to step to next line.
4.3 debug() and recover()
debug(func)flags a function to enter browser on each call.debugonce(func)for a single debugging session.options(error = recover)enables selection of any frame on error.
options(error = recover)
# Then run code that errors; you'll get a menu to inspect frames.
5. Putting It All Together: A Workflow Example
Imagine you need to process multiple CSV URLs, compute summary statistics by group, and consolidate results—while handling missing data gracefully and debugging unexpected failures.
library(tidyverse)
# 1. Define helpers
safe_read <- possibly(read_csv, tibble())
summarize_group <- function(data, grp, var) {
browser() # inspect a problematic dataset if needed
data %>%
drop_na(.data[[var]]) %>%
group_by(.data[[grp]]) %>%
summarize(count = n(), avg = mean(.data[[var]]), .groups = "drop")
}
# 2. Pipeline
urls <- c("data1.csv", "data2.csv", "broken.csv", "data3.csv")
results <- urls %>%
map(safe_read) %>%
map(~ summarize_group(.x, "category", "value")) %>%
compact() %>% # remove NULL or empty tibbles
bind_rows()
# 3. Inspect any errors
traceback() # if an error bubbled up
This workflow shows:
Purrr mapping for iteration
safe/pure functions that either return valid data or a placeholder
browser() for in‐depth inspection of edge cases
compact() and bind_rows() to merge only successful outputs
6. Conclusion and Next Steps
You’ve now embraced functional programming with purrr, learned to build and document reusable functions, tackled errors proactively, and debugged with R’s interactive tools. These skills are the backbone of professional R development.
In our next chapter, we’ll explore Reproducible Workflows: mastering R Markdown, version control with Git, and packaging code for seamless collaboration and deployment. If you have favorite purrr patterns, custom function tips, or debugging tricks, share them in the comments below!

Comments
Post a Comment