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
242: Practice Exercise: Automating Data Validation Rules
I’ve spent a lot of my career dealing with "dirty" data. Usually, it starts with a colleague saying, "The data is clean," and then five minutes into my analysis, I find a temperature reading of 4,000 degrees Celsius or a patient's age listed as -12. You can write a few filter() statements to catch these, but when you have fifty columns of sensor data, writing fifty manual checks is a recipe for a headache.
The Manual Slog
Let's start where I usually do—the manual way. I've got a small dummy dataset of industrial sensor readings. I want to make sure the temperature is between -50 and 150 and the pressure is between 0 and 500.
# Our messy sensor data
sensors <- data.frame(
sensor_id = 1:5,
temp = c(22.5, 180.1, 21.0, -60.5, 23.2),
pressure = c(101.3, 105.2, -10.1, 102.4, 600.1),
humidity = c(45, 47, 42, 110, 44)
)
# The manual check
bad_temp <- sensors$temp < -50 | sensors$temp > 150
bad_press <- sensors$pressure < 0 | sensors$pressure > 500
print(bad_temp)
print(bad_press)
This works for two columns. But look at that code. I'm repeating the same logic: value < min | value > max. If I add ten more sensors, I'm just copy-pasting lines and hoping I don't make a typo. That's where we stop being engineers and start being data entry clerks. I hate that.
Defining a Rulebook
I want to separate the logic of validation from the definition of the rules. What if I store the rules in a list? That way, if the hardware specs change and the temperature ceiling moves to 200, I only change one number in one place.
# A list where names match the column names
validation_rules <- list(
temp = list(min = -50, max = 150),
pressure = list(min = 0, max = 500),
humidity = list(min = 0, max = 100)
)
Now I have a "rulebook." But how do I actually apply it? I could write a for loop, but in R, looping over columns often feels clunky. Let's try to write a small helper function that takes a vector and a rule, and returns a logical vector of "failures."
check_range <- function(x, rule) {
return(x < rule$min | x > rule$max)
}
# Let's test it on one column
check_range(sensors$temp, validation_rules$temp)
Scaling the Validation
Okay, the function works. Now I need to apply this function to every column mentioned in my rulebook. This is where purrr::map or lapply comes in. I'll try lapply since it's base R, but the logic is the same: iterate through the rules, find the corresponding column in the data, and run the check.
# I'll use lapply to iterate over the names of the rules
results <- lapply(names(validation_rules), function(col_name) {
rule <- validation_rules[[col_name]]
data_vec <- sensors[[col_name]]
# We'll return a named vector of indices that failed
which(check_range(data_vec, rule))
})
names(results) <- names(validation_rules)
print(results)
Wait, this is better, but the output is a bit disjointed. It tells me which rows failed, but it doesn't give me a clean report. If I'm handing this to a QA team, they don't want a list of indices; they want to see the actual bad data. Let's adjust the function to return a data frame of the violations.
Building a Validation Report
Let's refine this. Instead of just returning the index, I'll create a function that returns a "violation" data frame containing the row ID, the column name, and the offending value. This is how you actually build production-grade validation.
validate_data <- function(df, rules) {
all_violations <- list()
for (col_name in names(rules)) {
rule <- rules[[col_name]]
vals <- df[[col_name]]
# Find where the rule is violated
fails <- which(vals < rule$min | vals > rule$max)
if (length(fails) > 0) {
all_violations[[col_name]] <- data.frame(
row = fails,
column = col_name,
value = vals[fails],
reason = paste0("Outside range [", rule$min, ",", rule$max, "]")
)
}
}
# Stack all the little data frames into one big report
do.call(rbind, all_violations)
}
report <- validate_data(sensors, validation_rules)
print(report)
Now we're talking. Instead of hunting through the original data, I have a concise table of every single rule breach. If the report is empty, the data is clean. If it's not, I have a checklist of exactly what needs fixing. This pattern—defining rules in a configuration object and applying them via a wrapper function—is the gold standard for automating data hygiene.
📋 Practical Task
Exercise: Automating Quality Control for a Clinical Trial Dataset
You have been handed a dataset containing blood pressure readings from a clinical trial. The data is noisy, and you need to automate the validation process to identify "impossible" readings before the analysis begins.
The Dataset:
trial_data <- data.frame(
patient_id = 1:6,
systolic = c(120, 115, 300, 122, 40, 118),
diastolic = c(80, 82, 85, 150, 78, -10),
heart_rate = c(72, 75, 70, 210, 68, 74)
)
The Rules:
systolic: Must be between 70 and 200.diastolic: Must be between 40 and 120.heart_rate: Must be between 40 and 160.
Your Task:
- Create a named list called
clinical_rulesthat stores the min and max for each of the three columns. - Write a function called
generate_qc_reportthat takes a data frame and a rules list as arguments. - The function should iterate through the rules and return a single data frame containing the
rownumber, thecolumnname, and theoffending_valuefor every instance where a rule is violated. - Run your function on
trial_dataand print the resulting report.
There are no comments for now.