Data Cleaning and Validation
Turn raw, messy data into reliable ML training sets. Schema validation, duplicate detection, type coercion, outlier handling, and automated rules that catch problems before they reach your model.
Garbage in, garbage out — and the garbage is invisible until your model ships.
A 2020 survey by Anaconda found data scientists spend 45% of their time cleaning data. That number hasn't changed much since. But the more dangerous problem isn't the time it takes — it's the errors that slip through uncleaned and silently corrupt a model that looks fine in evaluation but behaves wrong in production.
Consider what happens at DoorDash. The orders table has negative distances from data entry errors. Delivery times of 0 minutes from cancelled orders never removed. Duplicate records from a retry bug in the mobile app. City names spelled three different ways — "Seattle", "seattle", "SEATTLE". Star ratings of 6 from a frontend validation bug that was fixed three months ago. None of these cause your training script to crash. They all silently degrade your model.
This module gives you a systematic process — not a one-time cleaning script, but a validation framework that runs automatically every time new data arrives and catches problems before they reach training.
What this module covers:
The messy DoorDash dataset used throughout this module
Run this block once to create a realistic messy dataset with deliberate data quality problems. All sections in this module clean and validate it.
Data quality audit — know exactly what you are dealing with
The first rule of data cleaning: audit before you touch anything. Running a comprehensive quality report on a new dataset takes five minutes and reveals every problem you'll spend hours debugging if you skip it. It also gives you a baseline so you can prove the data got better after cleaning.
Schema validation — define what valid data looks like
A schema is a contract: a precise description of what each column should contain. Validating against a schema answers: are the right columns present? Are they the right types? Are values in the expected ranges? Are required columns non-null? Schema validation is the first gate every new dataset should pass before any further processing.
Duplicate detection — exact and near-duplicate removal
Duplicates are more than a storage problem. In ML, duplicate training examples cause the model to overweight those records — whatever pattern they represent gets amplified. A duplicate rate of 5% can meaningfully skew a model trained on imbalanced data. There are two kinds of duplicates: exact copies and near-duplicates (same record, slightly different values from a retry or data merging issue).
Type coercion — columns stored as the wrong dtype
Type errors are the most common data quality problem after nulls. A price column stored as a string because someone entered "N/A" once. A boolean column stored as integers 0 and 1 — but also containing 2. A date column stored as a free-text string with three different formats. These cause silent failures when you call .values or fit().
String cleaning — normalise free text and categoricals
String columns are the messiest part of any real dataset. "Seattle", "seattle", "Seatle", "SEATTLE", "seattle " — these are five representations of the same city, and they will be treated as five separate categories by any ML model. String cleaning must be systematic, not case-by-case.
Outlier detection and treatment
Not all outliers are errors. Some are genuine extreme values — a restaurant that genuinely takes 90 minutes to prepare food, or an order delivered in 8 minutes because it was around the corner. The first question is always: is this an error or a real edge case? Only then do you decide what to do with it.
IQR method — the standard robust outlier detector
Consistency checks — rules that span multiple columns
Individual column validation catches per-column problems. But some data quality issues only appear when you look at two columns together. A delivery time of 10 minutes for a distance of 15km is physically impossible. A 5-star rating with a note "worst experience ever" is inconsistent. These cross-column rules are called consistency checks and they catch a class of errors that column-level validation misses entirely.
Schema drift — catch when upstream data changes silently
Schema drift is when the data coming from an upstream source changes in a way nobody told you about. A new column appears. An existing column is renamed. A categorical column gains a new value. A numeric column suddenly contains nulls. Any of these will silently break a downstream ML pipeline — the training runs, metrics look plausible, but the model has learned from corrupted data.
Great Expectations — automated validation at scale
Great Expectations (GX) is the standard open-source library for data validation in production ML pipelines. Instead of writing custom validation code, you define "expectations" — declarative statements about what your data should look like. GX runs them against your data and produces a detailed HTML report. It integrates with Airflow, dbt, Spark, and every major data platform.
The complete cleaning pipeline — one class, all steps
Every common data cleaning error — explained and fixed
Where cleaning actually happens in the pipeline — and who owns it
In a tutorial, one person writes a script that both cleans the data and trains the model five minutes later. In a real company, cleaning is split across an organisational boundary that most tutorials never show, and knowing which side of that boundary you are standing on changes what "cleaning" even means for the code you are about to write.
Many data teams organise pipelines around a layered model — often called bronze, silver, and gold, or raw, validated, and curated. Data engineering typically owns the boundary between bronze and silver: enforcing schema, deduplicating on the business key, coercing types, and catching schema drift, exactly like the SchemaValidator and drift detector built earlier in this module. That layer is deliberately model-agnostic — the same validated orders table feeds the ETA model, the fraud model, and a quarterly business dashboard, so it cannot bake in decisions specific to any one of them.
ML engineering typically owns the boundary between silver and gold: the outlier treatment decisions, the choice of which rows to drop versus clip versus flag, and anything that depends on what a specific model needs rather than what is universally true about the data. Whether a 90-minute delivery gets clipped to the 99th percentile or kept as a genuine signal is a modeling decision, not a data engineering one — it depends on what the ETA model is trying to learn, and a different model consuming the same silver table might make the opposite choice.
Looking back at the cleaner class built earlier in this module: coerce_types, deduplicate, and clean_strings are silver-layer work — model-agnostic, correct regardless of which model eventually consumes the table. handle_outliers and drop_unusable are gold-layer decisions — the specific percentile chosen, and the specific columns required to be non-null, both depend on what the downstream model needs. In a real team these two halves are often literally two different pipelines, owned by two different people, with a validated silver table as the contract between them.
Five things people get wrong about data cleaning
A model trained on a cleaned snapshot from three months ago is being fed fresh, uncleaned data every time it scores a new prediction in production. The whole point of building cleaning as a validation framework — the SchemaValidator, the DoorDashDataCleaner class, the schema drift detector — rather than a one-off notebook cell is that the same checks need to run automatically on every new batch, indefinitely, for as long as the model stays in production. Treating cleaning as a step you finish once is exactly how a silent upstream schema change goes unnoticed for weeks.
Great Expectations and similar frameworks only catch what you told them to check for. They will faithfully verify that delivery_time stays under 180 minutes forever, but they will not notice that a genuinely new, legitimate delivery pattern (say, a new long-distance delivery tier the business just launched) is quietly failing that same expectation every day because nobody updated the range after the product changed. Automated validation is excellent at catching known failure modes reliably and cheaply — it is not a substitute for a human periodically asking whether the expectations themselves are still correct.
The boundary is far blurrier in practice than the two-module structure of this track suggests. Deciding whether to clip an outlier to the 99th percentile or leave it, and deciding whether to add an is_outlier flag column, are simultaneously a cleaning decision and the creation of a new feature. Target encoding a cleaned categorical column, or computing a group aggregate on cleaned data, blends directly into feature engineering with no hard line between where one stops and the other starts. Thinking of them as two genuinely separate phases, rather than one continuous spectrum of data transformation decisions, tends to produce pipelines with duplicated logic on both sides of an artificial divide.
IQR and Z-score outlier detection identify statistically unusual values — they say nothing about whether those values are errors or genuine rare events. A 90-minute delivery during a snowstorm is a real, informative data point about how the system behaves under stress; a negative distance or a zero-minute delivery time is a provable data entry error. Treating every statistical outlier as automatically safe to remove throws away exactly the tail behaviour a model most needs to learn to avoid being blindsided by it in production.
Whether a value is missing is frequently itself informative — a star_rating that is null because the customer never left a review is a meaningfully different situation from a customer who rated the order 1 star, and a model can benefit from an explicit "was_missing" indicator column even when the underlying value cannot be recovered. Dropping a 40 percent-null column outright discards that signal along with the missing values; imputing it silently can be just as costly if the missingness itself correlates with the target. The right response depends on why the value is missing, not on the null percentage alone.
Data cleaning — 5 questions interviewers actually ask
I would first find out why the values are missing, since that changes the right strategy more than the percentage does. If missing is random and unrelated to the target, median or mean imputation combined with a boolean was_missing flag is usually safe and preserves the fact that imputation happened. If missingness itself correlates with the outcome — for example, star ratings are disproportionately missing for orders customers never bothered to review because they were unremarkable — imputing to the mean would actively erase that signal, and I would prefer to keep the missing indicator as a feature in its own right, or in tree-based models let the missingness be handled natively rather than filled at all.
I start from domain plausibility rather than statistics alone: a negative distance or a zero-minute delivery time is provably impossible regardless of how rare it is, so it gets treated as an error unconditionally. A 90-minute delivery during a known weather event, by contrast, is statistically unusual but physically possible, so I would not remove it — I would clip it if its magnitude alone would destabilise a linear model, but I would add a flag column so the information that this was an extreme case is not lost. The statistical outlier tests (IQR, Z-score, isolation forest) are useful for finding candidates to investigate; they should never be the sole basis for the remove-or-keep decision.
I flag-and-keep whenever the underlying value might carry real signal and I am not confident the row is actually wrong — extreme-but-plausible values, categories with very low frequency, or missingness that might be informative. I clean away or correct only values that are provably impossible given the domain, like a negative distance or a 5-star boundary violated by a rating of 7. The general principle: cleaning should remove information that is definitely noise, and flagging should preserve information that might be signal — when genuinely unsure which case applies, flagging is the safer default because it is reversible and the model can learn to weight the flag appropriately, whereas deleting a row is not reversible.
I tie the severity of the response to the blast radius of being wrong. A schema violation on a required, non-nullable column, or a swing in null rate on a feature the model depends on heavily, should hard-fail the pipeline the way the SchemaValidator and drift detector in this module do — better to block training on bad data than silently ship a degraded model. A small number of rows violating a soft consistency rule, like a handful of rows where delivery_time is slightly below restaurant_prep, is worth logging and monitoring as a trend but rarely worth blocking an entire pipeline run over, since the cost of a false alarm at that severity outweighs the benefit.
I would avoid fixing spellings one at a time as I discover them, since that never actually terminates. Instead I would normalise case and whitespace first, then build an explicit canonical mapping from the observed variants to the correct value, and run fuzzy matching as a fallback only for whatever the explicit mapping does not cover — logging anything fuzzy matching still cannot resolve confidently rather than silently guessing. Critically, I would keep that mapping as a reusable, versioned piece of code, not a one-off notebook fix, since the same typos and variants reliably reappear in every new batch of data from the same upstream source.
You now have clean, validated data. It's time to build features from it.
Cleaning removes what is wrong. Validation catches new problems automatically. Together they ensure that the data reaching your model is trustworthy. The next module — Feature Engineering — takes clean data and transforms it into the representations that make ML models learn fastest and generalise best. Distance becomes log-distance. Timestamps become hour-of-day, day-of-week, and cyclical encodings. Categorical columns become embeddings or one-hot vectors. This transformation step consistently produces larger improvements than changing the model architecture.
Transform raw columns into powerful model inputs — log transforms, interaction features, target encoding, embeddings, and the feature engineering techniques that consistently outperform model tuning.
🎯 Key Takeaways
- ✓Always audit before fixing. Run a comprehensive quality report first — count nulls, check ranges, list unique values, detect duplicates. You cannot clean systematically what you have not measured.
- ✓Schema validation is a contract. Define every column's type, nullability, allowed values, and range as explicit code. Run validation on every new data batch — not just once during development.
- ✓Deduplicate on the business key (order_id), not all columns. Full-row deduplication is slow and misses near-duplicates. Hash stable fields to catch near-duplicates from retry bugs and data merge issues.
- ✓Type errors are silent killers. Use pd.to_numeric(errors="coerce") for numeric columns and pd.to_datetime(format="mixed", errors="coerce") for dates. Check how many values became NaN after coercion — a large number signals a serious upstream problem.
- ✓Three outlier strategies: remove provably impossible values (delivery_time=0), clip extreme-but-real values to 99th percentile AND add a flag column, or keep outliers and let the model handle them. Never clip without flagging — you lose information silently.
- ✓Consistency checks span multiple columns. delivery_time < distance_km/1.0 + 5 is physically impossible. is_late != (delivery_time > 45) is a label error. These cross-column rules catch a class of errors that column-level validation misses entirely.
- ✓Schema drift detection is not optional for production pipelines. Capture a reference schema fingerprint (dtypes, null rates, value ranges, allowed categories) and compare every new batch against it. A 35% shift in mean delivery time or a new city value should trigger an alert before the data reaches your model.
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.