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
149: Building an A/B Testing Analysis Pipeline
I've seen this specific mistake more times than I care to admit. A junior analyst comes to me with a "successful" A/B test result, but when I look at the code, the math is fundamentally skewed because they've fallen into the global mean trap.
# The "Broken" Analysis
# df contains 'group' (A or B) and 'converted' (0 or 1)
conversion_b <- mean(df$converted[df$group == "B"])
overall_mean <- mean(df$converted)
# Calculating lift
lift <- conversion_b - overall_mean
print(paste("The lift for Group B is:", lift))
The "Global Mean" Trap
At first glance, this looks fine. The analyst is trying to see how much better Group B performed compared to the average. But here is the problem: overall_mean includes the data from Group B. By subtracting the global mean from Group B's mean, you aren't measuring the lift relative to the control (Group A); you're measuring the lift relative to a weighted average of both groups. This shrinks the perceived effect and gives you a mathematically incorrect lift value.
Isolating the Control Baseline
To fix this, we have to be explicit. In A/B testing, the control group is your ground truth. Everything must be measured relative to Group A, not the dataset as a whole. Here is how we actually handle that logic:
# The Fixed Analysis
conv_a <- mean(df$converted[df$group == "A"])
conv_b <- mean(df$converted[df$group == "B"])
# True lift: (Treatment - Control) / Control
lift <- (conv_b - conv_a) / conv_a
print(paste("The true relative lift is:", round(lift * 100, 2), "%"))
Now we're comparing apples to apples. I prefer calculating relative lift (the percentage increase) over absolute lift because it provides a much better sense of the business impact, regardless of whether your baseline conversion is 1% or 20%.
Modularizing the Analysis into a Pipeline
Doing this manually for one metric is easy. Doing it for five metrics across ten different experiments is a nightmare. To make this sustainable, we need a pipeline. I usually build this as a function that takes a raw dataframe and a metric column name, returning a clean summary. This prevents "copy-paste errors" where you accidentally reference conv_a in a conv_b calculation.
analyze_ab_test <- function(data, metric_col) {
# Extract values for each group
group_a <- data[[metric_col]][data$group == "A"]
group_b <- data[[metric_col]][data$group == "B"]
# Basic stats
m_a <- mean(group_a)
m_b <- mean(group_b)
lift <- (m_b - m_a) / m_a
# Statistical significance using a proportion test
# prop.test expects counts of successes and total trials
test_result <- prop.test(x = c(sum(group_a), sum(group_b)),
n = c(length(group_a), length(group_b)))
return(data.frame(
metric = metric_col,
control_rate = m_a,
treatment_rate = m_b,
lift = lift,
p_value = test_result$p.value,
significant = test_result$p.value < 0.05
))
}
# Running the pipeline on multiple metrics
metrics <- c("converted", "clicked_upsell", "completed_profile")
results <- do.call(rbind, lapply(metrics, function(m) analyze_ab_test(df, m)))
Adding Rigor with Confidence Intervals
A p-value tells you if there is an effect, but it doesn't tell you the magnitude of that effect. If you tell a stakeholder "it's significant," their first question will be "by how much?"
I always append the confidence interval of the difference to my pipeline. Since we are dealing with proportions, we can extract the conf.int from the prop.test object. If the interval is very wide, it means your sample size is likely too small, and you shouldn't trust the lift value even if the p-value looks promising. If the interval is tight and stays above zero, you've got a winner.
📋 Practical Task
Building a Multi-Metric Conversion Pipeline for E-commerce
You have been handed a dataset called ecommerce_data with the following columns: user_id, group (either "A" or "B"), purchased (0 or 1), and signed_up (0 or 1).
Your task is to create a robust analysis pipeline. Write a function called run_ab_pipeline that:
- Accepts the dataframe and a vector of metric column names.
- Calculates the conversion rate for both groups for every metric provided.
- Calculates the relative lift
(Treatment - Control) / Control. - Performs a
prop.testto determine the p-value for each metric. - Returns a single combined dataframe containing the metric name, the lift, and the p-value.
Test your function by passing c("purchased", "signed_up") as the metrics. Ensure your output is a tidy dataframe where each row represents a different metric.
There are no comments for now.