-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
193: Practice Exercise: Building a Function Composition Pipeline
I've spent a lot of time reviewing R code from analysts who are brilliant at statistics but haven't quite grasped the ergonomics of the language. One of the most common things I see is what I call "The Onion." It's that habit of nesting functions so deeply that you have to read the code from the inside out to understand what's actually happening. When you're composing a series of transformations—cleaning data, transforming it, and then aggregating it—this approach quickly becomes a liability.
The Inside-Out Struggle
Imagine we're building a pipeline to process raw sensor data. We need to remove the NAs, normalize the scale, and then calculate a rolling average. The naive way to write this looks something like this:
# The "Onion" approach
final_data <- rolling_avg(normalize_scale(remove_outliers(raw_sensor_data)))
At first glance, it's compact. But as soon as you add a fourth or fifth step, it becomes a nightmare. If you want to check the output of remove_outliers to make sure it's working, you have to break the whole line apart and create a temporary variable. You're essentially reading the logic in reverse order of execution. It's mentally taxing, and it's where most bugs hide because it's so easy to misplace a closing parenthesis when you're five levels deep.
Now, some people try to fix this by creating a trail of temporary variables. They'll do data1 <- remove_outliers(raw_sensor_data), then data2 <- normalize_scale(data1), and so on. I've seen this in a lot of legacy scripts. While it's easier to debug, it litters your global environment with "junk" variables that serve no purpose other than to pass data to the next line. It's cluttered and makes the code feel like a grocery list rather than a logical process.
Thinking in Pipelines
The better way—and the way I want you to lean into—is using the native pipe operator |>. It allows you to write the code in the exact order the data moves through the system. It transforms the "Onion" into a conveyor belt.
# The pipeline approach
final_data <- raw_sensor_data |>
remove_outliers() |>
normalize_scale() |>
rolling_avg()
The trade-off here is purely about readability and maintainability. Notice how the logic now flows top-to-bottom, left-to-right. If I decide that normalize_scale should actually happen before remove_outliers, I don't have to carefully surgically extract a nested function call; I just swap two lines of code. If I need to debug a specific step, I can just comment out the rest of the pipeline and see exactly what that one function is emitting.
One thing to keep in mind: this only works if your functions are designed for it. For a pipeline to be clean, the first argument of each function should be the data it operates on. If you're using a function that expects the data as the second or third argument, you'll find yourself fighting the pipe. That's usually a signal that the function needs a wrapper or a rewrite to be "pipeline-friendly."
📋 Practical Task
Exercise: Building a Clean Stock Price Volatility Pipeline
You are tasked with creating a data processing pipeline for a set of stock price observations. You have three existing functions: clean_ticks() (removes zeros), calc_returns() (calculates percentage change), and filter_volatility() (keeps only returns above a certain threshold).
Below is a naive, nested implementation. Your goal is to refactor this into a clean, readable pipeline using the |> operator. Ensure that the data flows logically from the raw input stock_data to the final high_volatility_events variable.
# Existing functions (do not change these)
clean_ticks <- function(df) { df[df$price != 0, ] }
calc_returns <- function(df) { df$returns <- c(NA, diff(df$price)/df$price[-1]); df }
filter_volatility <- function(df, threshold = 0.02) { df[abs(df$returns) > threshold, ] }
# Raw data
stock_data <- data.frame(price = c(100, 101, 0, 102, 105, 104, 108))
# REFACTOR THIS LINE:
high_volatility_events <- filter_volatility(calc_returns(clean_ticks(stock_data)))
Refactor the final line into a pipeline. Then, add a fourth step to the pipeline using head(n = 5) to ensure you only ever retrieve the first five high-volatility events.
There are no comments for now.