Skip to Content
Course content

32: Functional Programming Concepts in R

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running a high-end commercial kitchen. You have a set of standard recipes—these are your functions. But as the head chef, you don't just follow recipes; you create systems. You might have a "Prep Station" whose only job is to take any recipe and apply it to every single ingredient in a crate. The Prep Station doesn't care if the recipe is "dice," "peel," or "sear"; it just knows how to repeat the process across a collection of items. Then, you might have a "Customizer" who takes a base recipe (like a vinaigrette) and, based on a customer's request for "extra spicy," hands back a modified version of that recipe for the line cooks to use.

In R, this is the heart of functional programming. We stop thinking of functions as just "tasks to perform" and start treating them as first-class citizens. This means you can pass a function into another function as an argument, or even have a function return another function as its result. It sounds meta, and it is, but it's how you move from writing repetitive scripts to building truly flexible software.

The Power of the Prep Station (Higher-Order Functions)

You've probably used lapply or sapply before, but I want you to look at them through this lens. They are Higher-Order Functions (HOFs) because they take another function as an input. I personally find that once you embrace this, you'll almost never write a for loop for data transformation again.

Let's look at a real scenario. Suppose you have a list of numeric vectors representing stock prices over a week, and you want to apply different types of summaries to them.

# A list of weekly prices for three stocks
stocks <- list(
  AAPL = c(150, 152, 149, 155, 153),
  GOOG = c(2800, 2810, 2790, 2820, 2815),
  MSFT = c(300, 305, 302, 308, 307)
)

# Instead of writing a loop, we pass the 'mean' function directly into lapply
avg_prices <- lapply(stocks, mean)
print(avg_prices)

In this snippet, mean isn't being called (notice there are no parentheses like mean()); it's being passed as an object. The lapply function acts as our Prep Station, taking the "mean recipe" and applying it to every element in the stocks list.

Building a Function Factory (Closures)

This is where things get interesting. A "closure" happens when a function returns another function, and that inner function remembers the environment in which it was created. I like to call these "Function Factories."

Say you need to scale your data by different factors depending on the currency exchange rate. Instead of writing five different scaling functions, you write one factory that creates them for you.

# The Factory: it takes a multiplier and returns a NEW function
make_scaler <- function(multiplier) {
  function(x) {
    return(x * multiplier)
  }
}

# Create specific tools using the factory
usd_to_eur <- make_scaler(0.92)
usd_to_gbp <- make_scaler(0.79)

# Now use the tools
price_usd <- 100
print(usd_to_eur(price_usd)) # 92
print(usd_to_gbp(price_usd)) # 79

Notice that usd_to_eur is now a function itself. It "remembered" that the multiplier was 0.92 even after make_scaler finished executing. This is an incredibly powerful way to encapsulate logic and avoid cluttering your global environment with dozens of nearly identical functions.

Staying Pure to Avoid Headaches

To really master this style, you have to strive for "Pure Functions." A function is pure if it always produces the same output for the same input and has no side effects. A side effect is anything that changes the state of the world outside the function—like modifying a global variable or printing to the console.

I've seen too many R scripts crash because a function deep in the code modified a variable in the global environment (the .GlobalEnv), and the programmer spent three hours wondering why their data changed. If you keep your functions pure, your code becomes predictable, easier to test, and much simpler to debug.




📋 Practical Task

Build a Dynamic Data Normalizer

Your task is to create a flexible system for normalizing numeric data. Instead of writing a single function with a long list of if/else statements, you will use the functional concepts learned in this lesson.

Requirements:

  • Create a "factory" function called make_normalizer. This factory should take a string argument method (either "minmax" or "zscore").
  • Based on the method, the factory should return a new function.
    • The "minmax" function should scale a vector to a range between 0 and 1: (x - min(x)) / (max(x) - min(x)).
    • The "zscore" function should scale a vector by subtracting the mean and dividing by the standard deviation: (x - mean(x)) / sd(x).
  • Create a list of three different numeric vectors (e.g., list(a = c(1, 2, 3), b = c(10, 20, 30), c = c(5, 15, 25))).
  • Use a higher-order function (like lapply) to apply a "zscore" normalizer (created by your factory) to every vector in that list.

Expected Output: A list where every numeric vector has been transformed into its Z-score equivalent.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.