Skip to main content

11 Functional Programming and Advanced Tools in R: purrr, Custom Functions, and Debugging

 


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(), and pmap()) for clean iteration

  • Write 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.

r
install.packages("purrr")
library(purrr)

1.1 map(): Apply a function to each element

r
# 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:

r
# 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.

r
# 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:

r
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

r
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’s abort() 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.

r
#' 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.

r
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:

r
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:

r
> 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:

r
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.

r
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.

r
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:

  1. Purrr mapping for iteration

  2. safe/pure functions that either return valid data or a placeholder

  3. browser() for in‐depth inspection of edge cases

  4. 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

Popular posts from this blog

Alfred Marshall – The Father of Modern Microeconomics

  Welcome back to the blog! Today we explore the life and legacy of Alfred Marshall (1842–1924) , the British economist who laid the foundations of modern microeconomics . His landmark book, Principles of Economics (1890), introduced core concepts like supply and demand , elasticity , and market equilibrium — ideas that continue to shape how we understand economics today. Who Was Alfred Marshall? Alfred Marshall was a professor at the University of Cambridge and a key figure in the development of neoclassical economics . He believed economics should be rigorous, mathematical, and practical , focusing on real-world issues like prices, wages, and consumer behavior. Marshall also emphasized that economics is ultimately about improving human well-being. Key Contributions 1. Supply and Demand Analysis Marshall was the first to clearly present supply and demand as intersecting curves on a graph. He showed how prices are determined by both what consumers are willing to pay (dem...

Part I: Getting Started with SQL Chapter 2: Setting Up Your Environment

  Chapter 2: Setting Up Your Environment Before you write your first query, it’s essential to install and configure a relational database on your workstation. A solid environment ensures you spend time learning SQL—not battling installation errors. In this chapter, we’ll: Compare four popular RDBMS options Walk through installation on Windows, macOS, and Linux Demonstrate how to connect via command-line and graphical tools Load a sample database so you can start querying immediately By the end, your machine will be a fully functional SQL playground. 1. Choosing an RDBMS: MySQL, PostgreSQL, SQLite, SQL Server Different relational database systems excel in different scenarios. Here’s a quick comparison to help you pick one: Feature MySQL PostgreSQL SQLite SQL Server Use case Web apps, LAMP stacks Analytics, GIS, advanced SQL Lightweight, embedded apps Enterprise .NET, Windows ecosystems Licensing GPL (Community) / Commercial (Enterprise) Open Source (PostgreSQL License) Public domain...

“This Sentence Is False”: The Liar Paradox, from Ancient Crete to Modern Code

 “All Cretans are liars,” said the Cretan Epimenides.  “This sentence is false,” echoes every logic textbook.  We’re still arguing 2,600 years later—and the paradox is winning.   _____________________________  /                             \ |   “THIS SENTENCE IS FALSE.”  |  \_____________________________/               |               |  self-reference               v    +---------------------------+    |  Truth flips back on     |    |  itself — paradox loop!  |    +---------------------------+ 1. Meet the Liar The classic one-liner: L: “This sentence is false.” If L is true, then what it asserts—its own falsity—must hold, so L is false. If L is false, then what it asserts isn’t the ca...