R
Completed
-
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
34: Building a Data Cleaning Pipeline in R
I've just been handed a CSV file containing customer survey responses from three different regions. On the surface, it looks fine, but as soon as I load it, I can tell it's going to be a headache. We've got dates in three different formats, "Yes/No" answers written as "yes", "YES", and "Y", and a lot of trailing whitespace that's going to break my joins later.
The "Messy Room" Approach
My first instinct—and what most people do when they're in a rush—is to just start hacking away at the dataframe. Let's look at how I'd start this if I weren't thinking about a pipeline:
# The raw data
df <- read.csv("survey_data.csv")
# Fix the dates
df$date <- as.Date(df$date)
# Fix the casing
df$region <- toupper(df$region)
# Trim the whitespace
df$comments <- trimws(df$comments)
# Handle the NAs in ratings
df$rating[is.na(df$rating)] <- 0
This works for a one-off script, but it's a nightmare for a real project. Why? Because I'm mutating the object in place over and over. If I run the script twice by accident, or if I want to change the order of operations, I have to restart the whole R session to get back to the original df. Plus, if I get another file from the European team tomorrow, I have to copy-paste this whole block of code. It's fragile.
Stop the Variable Bloat
I want to move toward a flow where data enters at the top and comes out clean at the bottom. I'm going to use the native pipe operator |> (or %>% if you're still heavily tied to magrittr) to chain these operations. Instead of modifying df five times, I'll treat it as a sequence of transformations.
library(dplyr)
library(lubridate)
cleaned_data <- df |>
mutate(
date = ymd(date),
region = toupper(region),
comments = trimws(comments),
rating = coalesce(rating, 0)
)
This is much better. I can actually read the logic: "Take the data, then mutate these four columns." But we're still just applying this to one specific object. If I have ten files to process, I'm still just copy-pasting the pipe. That's not a pipeline; that's just a shorter script.
Turning the Logic into a Machine
To make this a true pipeline, I need to decouple the cleaning logic from the data itself. I'll wrap that pipe inside a function. This way, the function becomes the "cleaning machine," and I can feed any dataset into it.
clean_survey_data <- function(data) {
data |>
mutate(
date = ymd(date),
region = toupper(region),
comments = trimws(comments),
rating = coalesce(rating, 0)
) |>
filter(!is.na(date)) # I realized I can't trust rows with missing dates
}
# Now I can use it on any file
us_clean <- clean_survey_data(read.csv("us_survey.csv"))
eu_clean <- clean_survey_data(read.csv("eu_survey.csv"))
ap_clean <- clean_survey_data(read.csv("ap_survey.csv"))
I noticed something while testing this: the ymd() function from lubridate is great, but if the date is totally garbage, it just returns NA. By adding that filter(!is.na(date)) at the end of the pipe, I'm ensuring that the output of my pipeline is always "analysis-ready."
Handling the "What Ifs"
Here is where things usually break in production. What happens if the "region" column is missing entirely from one of the files? The whole function crashes. I don't want to write a dozen if statements; that ruins the elegance of the pipe. Instead, I'll add a quick check at the start of the function to ensure the required columns exist.
clean_survey_data <- function(data) {
required_cols <- c("date", "region", "comments", "rating")
if (!all(required_cols %in% colnames(data))) {
stop("The input data is missing one of the required columns: ",
paste(setdiff(required_cols, colnames(data)), collapse = ", "))
}
data |>
mutate(
date = ymd(date),
region = toupper(region),
comments = trimws(comments),
rating = coalesce(rating, 0)
) |>
filter(!is.na(date))
}
Now, if I feed it a malformed CSV, R tells me exactly which column is missing rather than throwing a cryptic "column not found" error in the middle of a mutate call. This is the difference between a script that "works on my machine" and a professional data pipeline.
📋 Practical Task
Build a Product Catalog Standardization Pipeline
You have been given a raw dataset of product listings from different vendors. The data is inconsistent: some prices have currency symbols, some product names are in all caps, and some SKU numbers have accidental leading spaces.
Your Task: Create a function called standardize_products that implements the following pipeline:
- Validation: Check that the columns
sku,product_name, andpriceall exist. If any are missing, usestop()to provide a clear error message. - SKU Cleaning: Remove all leading and trailing whitespace from the
skucolumn. - Name Normalization: Convert the
product_nameto title case (usetools::toTitleCase()or a similar method). - Price Conversion: The
pricecolumn is currently a character string (e.g., "$12.99"). Remove the "$" sign and convert the column to a numeric type. - Filtering: Remove any rows where the
priceresulted in anNA.
Test your function by creating a small dummy dataframe with at least one "dirty" row and one "missing column" scenario to ensure your validation logic works.
There are no comments for now.