Skip to Content
Course content

240: Practice Exercise: Merging Multiple Messy Data Sources

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

I've spent more hours than I'd like to admit staring at a dataframe that should have 1,000 rows after a join, only to find it has zero. When you're merging messy data sources—especially when those sources come from different departments or legacy systems—the code usually looks correct, but the data is lying to you.

Take a look at this snippet. I'm trying to merge a customer list with their recent purchase history using left_join from dplyr.

# The setup
customers <- data.frame(cust_id = c("C101", "C102", "C103"), name = c("Alice", "Bob", "Charlie"))
purchases <- data.frame(customer_id = c("c101", "C102 ", "C103"), amount = c(50, 100, 150))

# The join
merged_data <- customers %>% 
  left_join(purchases, by = c("cust_id" = "customer_id"))

print(merged_data)
# Result: Alice has NA, Bob has NA, Charlie has 150.
# Wait... why did Alice and Bob disappear?

The Invisible Character Trap

If you look closely at the purchases dataframe, you'll see the culprit. Alice's ID is lowercase ("c101") and Bob's ID has a trailing space ("C102 "). To R, these are entirely different strings. This is the most common "bug" when merging messy sources: the keys look identical to the human eye, but they are mathematically different.

When this happens, don't just guess. I always use an anti_join to see exactly which rows are failing to match. It's the fastest way to diagnose the mismatch.

# Diagnosing the gap
mismatches <- customers %>% 
  anti_join(purchases, by = c("cust_id" = "customer_id"))

print(mismatches) 
# This shows us Alice and Bob are the ones not matching.

Sanitizing Keys Before the Merge

The fix isn't to manually edit the CSV files. You need to build a "cleaning pipeline" that standardizes your keys before they ever hit the join function. I typically use stringr for this because it's explicit and readable.

library(dplyr)
library(stringr)

# Clean both sources to a common standard: trimmed and uppercase
clean_customers <- customers %>% 
  mutate(cust_id = str_trim(str_to_upper(cust_id)))

clean_purchases <- purchases %>% 
  mutate(customer_id = str_trim(str_to_upper(customer_id)))

# Now the join actually works
merged_data <- clean_customers %>% 
  left_join(clean_purchases, by = c("cust_id" = "customer_id"))

Scaling to Multiple Messy Files

In the real world, you aren't usually merging two dataframes; you're merging twenty. Writing twenty left_join statements is a recipe for a copy-paste error. Instead, I use purrr::reduce. It allows you to take a list of dataframes and "collapse" them into one using a specific function.

The trick here is ensuring every single dataframe in your list has the key column named identically. If one is called cust_id and another is CustomerID, reduce will fail. I recommend a quick rename step inside a map call before you start the reduction process.

library(purrr)

# Imagine a list of 5 messy dataframes
all_dfs <- list(df1, df2, df3, df4, df5)

# 1. Standardize column names and clean keys across all DFs
standardized_dfs <- all_dfs %>% 
  map(~ .x %>% 
        rename(join_key = 1) %>% # Assume the first column is always the key
        mutate(join_key = str_trim(str_to_upper(join_key))))

# 2. Reduce them all into one master dataframe
final_df <- standardized_dfs %>% 
  reduce(full_join, by = "join_key")

By using full_join here, I ensure that I don't lose any data from any of the sources, even if some customers only appear in the fifth file. I can always filter out the NA rows later if they aren't needed.




📋 Practical Task

Exercise: Consolidating Fragmented Q3 Regional Sales Reports

You have been handed three different dataframes representing sales from three different regions. Each is "messy" in its own way: some have inconsistent casing, some have leading/trailing whitespace, and the ID columns have different names.

The Data:

north_sales <- data.frame(id = c("A101", "A102", "A103"), revenue = c(1000, 1500, 1200))
south_sales <- data.frame(SaleID = c("a101 ", "A102", "A104"), revenue = c(800, 900, 1100))
east_sales <- data.frame(ClientID = c("A101", " a103", "A105"), revenue = c(700, 600, 1300))

Your Task:

  • Create a list containing these three dataframes.
  • Use a map function to rename the ID column in each dataframe to standard_id and clean the values (remove whitespace and convert to uppercase).
  • Use reduce with a full_join to merge all three datasets into one master dataframe called master_sales.
  • Verify that master_sales contains 5 unique IDs (A101 through A105).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.