ML Systems Lab Open interactive version →
Foundational 35 min read data qualityprofilingmissing valuesoutliers

Data Quality Audit

Understand what makes data "dirty" and why auditing before modeling is non-negotiable.

A vendor hands you a shiny 2-million-row dataset with 47 columns. Before you write a single line of modeling code, you spend 20 minutes just *looking* at the data — a data quality audit — and here is what you find. The `income` column is stored as text, not numbers. `date_of_birth` has values from 1800 to 2300. `transaction_amount` is 12% missing, and the missing ones are suspiciously the *large* transactions. And 30,000 rows are exact duplicates. Twenty minutes of looking just saved you a week of chasing a broken model in production.

Here is why this step is non-negotiable, and it is subtler than "garbage in, garbage out." A powerful model does not choke on bad data — it *learns from it, confidently*. Feed a gradient-boosted tree rows where `age = -3` and it will happily decide that negative ages predict something. Your training metrics look fine. The model ships. It assigns high confidence to wrong answers and never once flags that anything is off. The real slogan is "garbage in, *confident* garbage out," and the bill arrives weeks later when the true labels do.


What to actually check

A good audit sweeps seven things:

- Schema — are the column types right, and are all the expected columns present? - Missing values — how much is null per column, and is the missingness random or *patterned*? - Duplicates — exact repeated rows, or near-duplicates on a key? - Distribution — outliers, strange skew, two humps where you expected one? - Target — is the label balanced, and are there noisy or impossible label/feature combos? - Leakage — does any feature secretly use information you would not have at prediction time? - Coverage — does this data actually resemble the population you will deploy against?


Outliers are not the same as impossible values

These two get lumped together, but they need different treatment. An outlier is a data point that is extreme but potentially *valid* — a $50,000 transaction is unusual, not wrong. An impossible value violates a hard constraint and is *always* an error — the `age = -3` row from a moment ago is not "an unusual customer," it is broken data, and it gets nullified before anything else runs. Confusing the two is dangerous in both directions: nullifying a legitimate extreme value throws away real signal, while treating a constraint violation as "just an outlier" lets broken data quietly train the model.

And "outlier" itself is not automatically noise to be removed. In fraud or anomaly detection, the fraud rows *are* the outliers — rare and extreme by definition, relative to the mass of normal transactions. A blanket "remove the outliers" step, applied without asking what the outliers *are* in this dataset, strips out most of the positive class before the model ever sees it, leaving a detector that has learned to predict "not fraud" almost every time.


The sneakiest failure: rows that silently vanish

One failure deserves special mention because it hides so well. Join two tables on customer ID, and if some IDs in one table have no match in the other, those rows simply *disappear* — no error, the pipeline reports success, and 120,000 training examples are gone. And they are almost never a random 120,000: they tend to be a specific group (older customers, one region), which is now missing entirely from training. Your model quietly learns nothing about them.


Make the checks automatic

The fix is to turn the whole checklist into *executable assertions* that run on every new batch of data — "column income must be a float between 0 and 10 million with under 5% nulls." Tools like Great Expectations do exactly this. When an assertion fails you get a loud error on day one instead of a mysterious performance drop six weeks later. And re-run these at *every* retrain, not just once: a column that was 2% null in January and 18% null in June is a data-collection problem, and you will never notice it without looking again. A pipeline running without crashing is not the same thing as the data being good.


Missingness has a taxonomy — and it changes what you can do

"How much is missing" is only half the question; "*why* is it missing" decides the fix. MCAR (missing completely at random): the missingness is unrelated to anything, so dropping or simple imputation is safe. MAR (missing at random): the missingness depends on *other observed* features (income missing more often for a certain age group) — you can impute using those features. MNAR (missing not at random): the missingness depends on the *missing value itself* (high earners refuse to state income) — and this is the dangerous one, because the missing values carry signal or bias that no ordinary imputation recovers. During the audit, don't just count nulls; look at *what else is true* about the missing rows.


Drift has metrics, not just eyeballs

"Does this batch resemble training?" can be measured. For continuous features, PSI (population stability index) is the industry-standard drift score (below 0.1 stable, above 0.2 significant), the KS test measures the largest gap between two distributions, and Wasserstein distance captures how far mass moved. For distributions generally, KL and Jensen-Shannon divergence quantify how different two are (JS is symmetric and bounded). For categorical features, compare category frequencies (chi-squared, or PSI on the buckets) and watch for brand-new categories. Wire these into the audit so drift is a *number that crosses a threshold*, not a vibe.


Audit the labels, not just the features

Dirty labels quietly cap model quality more than dirty features. Check for noisy labels (wrong ground truth), conflicting labels (identical rows labeled differently), annotation disagreement (measure inter-annotator agreement), delayed labels (the truth arrives weeks later, so recent rows are under-labeled), label leakage (a feature encodes the label), and label-definition drift (what counts as "fraud" changed between last year and this year). A feature audit that ignores label quality misses the ceiling on how good any model can get.


Train-serving skew and feature freshness

Some quality problems only exist *between* environments. Train-serving skew is when a feature is computed one way offline (SQL over full history) and another way online (a real-time approximation) — the model then scores on values it never trained on. Related is staleness: a feature that's fresh offline may be hours old at serving time. So the audit must track *timestamps*: the event time (when it happened), the ingestion time (when it landed), and the feature-computation time — and enforce a maximum feature age. A value that's correct but stale is still wrong at decision time.


Data contracts and a severity policy

Mature pipelines formalise this with data contracts: the producing team commits to a schema, types, value ranges, cardinality, null-rate ceilings, and a freshness SLA, with clear ownership so a breaking change upstream is caught at the boundary, not six weeks downstream. And each failed check needs a severity/action policy decided in advance: does this failure block training, quarantine the batch, fall back to a previous model, fire a warning only, or route to human review? "The check failed" is useless without "…and therefore we do X."


Audit sample coverage, not just column values

Finally, check *who* is in the data. A dataset can be clean on every column yet systematically under-represent a segment — a geography, a device type, an acquisition channel, a cohort, or the rare high-value population you most care about. Break the audit down by these dimensions and confirm each important segment has enough coverage, because a model trained on a skewed sample is confidently wrong exactly where the data was thin.

Key points

Takeaway

Data quality failures surface as production incidents, not training errors — because models train confidently on garbage and only reveal the problem when ground-truth labels arrive weeks later.

Recap

Check your understanding

Q1. A colleague argues that outlier rows should simply be removed before training to keep the model clean. What breaks if you follow this advice blindly on a fraud detection dataset?

Q2. You join two tables on a customer ID and your training set shrinks from 500,000 to 380,000 rows without any error. What likely happened and why does it matter?

Q3. What is the difference between an outlier and an impossible value, and why should they be handled differently?

Q4. You run a data profile on training set in January and model performs well. You retrain in June without re-profiling and performance drops. What data quality issue is most likely responsible?

Q5. A column has 55% null values. A colleague says to impute with the median. What should you do before accepting that advice?

Q6. Two features are each 20% missing. In feature A, the missingness is unrelated to anything; in feature B, high earners systematically decline to report the value. How do you classify each, and why does it change your handling?

Q7. Your feature pipeline is clean on every column, but the model underperforms badly for one region and one device type. Aggregate metrics look fine. Which TWO of the following are true about what went wrong and how to catch it earlier?

Try it interactively

ML Systems Lab is a free interview-prep platform for ML engineers — work through the full interactive module, quizzes, and drills.

Open ML Systems Lab →