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
16: Data Cleaning Basics
I want to show you a snippet of code that I actually wrote a few years ago. I had just imported a CSV of sales data, and I wanted to find the average order value. It looked straightforward, but R gave me a result that made zero sense.
# My broken code
sales_data <- read.csv("orders.csv")
avg_sale <- mean(sales_data$amount)
print(avg_sale)
# Output: [1] NA
Now, I looked at the CSV in Excel, and the amount column was clearly full of numbers. Why was R returning NA? This is a classic "dirty data" trap. When I ran str(sales_data), I realized the amount column wasn't a numeric type; it was a character vector. Why? Because the CSV had dollar signs and commas in the values (e.g., "$1,200.50"). R saw that "$" and decided the whole column was text.
The Mystery of the NA Average
The problem here is that R won't implicitly guess that "$1,200.50" should be treated as a number. If you try to run a math function on a character string, it fails. In the case of mean(), if there's even one non-numeric value (or if the whole column is the wrong type), you often end up with NA.
To fix this, we have to explicitly clean the string before we can cast it to a number. We need to strip out everything that isn't a digit or a decimal point.
Stripping Strings to Recover Numbers
Here is how I fixed it. I used gsub() to replace the unwanted characters with nothing, and then wrapped that in as.numeric().
# The fix
sales_data$amount <- gsub("[$,]", "", sales_data$amount) # Remove $ and ,
sales_data$amount <- as.numeric(sales_data$amount) # Convert to number
avg_sale <- mean(sales_data$amount, na.rm = TRUE)
print(avg_sale)
# Output: [1] 1240.50
Notice the na.rm = TRUE inside the mean() function. That's my second "pro tip" for this lesson. Even after you fix the data types, real-world data almost always has missing values. By default, R is conservative: if there's one NA in your vector, the result of the calculation is NA. Adding na.rm = TRUE tells R to just ignore the blanks and calculate based on the available data.
Handling Inconsistent Text Casing
Cleaning isn't just about numbers. I've spent way too many hours debugging scripts only to realize that "New York", "new york", and "NEW YORK" were being treated as three different cities in my group-by summaries. This is a nightmare for data aggregation.
The easiest way to handle this is to force everything to one case immediately after importing. I usually go with lowercase because it's less jarring to look at in logs.
# Using the base R tolower function
sales_data$city <- tolower(sales_data$city)
If you're using the tidyverse, you can do this inside a mutate() call, but the principle is the same: standardize your categories before you start analyzing them, or your counts will be wrong.
Dealing with Whitespace Ghosts
One of the most frustrating bugs in R is the "invisible space." You'll try to filter for a value—filter(df, status == "Complete")—and it returns zero rows, even though you can clearly see "Complete" in the data. Often, that cell actually contains "Complete " (with a trailing space).
I always recommend running a trimws() (trim white space) on any character column that you plan to use for filtering or joining.
# Remove leading and trailing whitespace
sales_data$status <- trimws(sales_data$status)
Once you've handled the types, the NAs, the casing, and the whitespace, you actually have a dataset you can trust. Do that first, or you'll spend the rest of your project chasing ghosts.
📋 Practical Task
Cleaning the Messy Warehouse Inventory List
You've been handed a messy dataframe representing a warehouse inventory. The data is a disaster: the price column has currency symbols, the category column has inconsistent casing, and some stock_count values are missing.
# Run this code to create the messy dataset
inventory <- data.frame(
item = c("Widget A", "Widget B", "Gadget X", "Gadget Y", "Sprocket Z"),
category = c("Tools", "tools", "Electronics", "ELECTRONICS", "Tools "),
price = c("$10.00", "$15.50", "$100.00", "$120.00", "$5.00"),
stock_count = c(50, 20, NA, 10, 100),
stringsAsFactors = FALSE
)
Your Goal: Write a script to clean this dataframe so that:
- The
pricecolumn is converted to anumerictype. - The
categorycolumn is standardized to lowercase and has no trailing/leading whitespace. - You calculate the total value of the inventory (Price * Stock Count), ensuring that the missing
stock_countdoesn't break your calculation.
Print the final cleaned dataframe and the total inventory value to the console.
There are no comments for now.