Data Quality Checks Every Data Scientist Should Know
Data Quality Checks Every Data Scientist Should Know
A model trained on data with 500 null values, 20 duplicates, and 15 outliers will produce unreliable results. The problem is not the algorithm — it is the data. And data quality problems are invisible until you look for them.
This article covers the seven data quality checks every data scientist should run, explains what each catches, and provides a complete Python validator you can use immediately.
Featured image: articles/084/featured-image.svg
Quality checks matrix: articles/084/quality-checks-matrix.svg
The Seven Essential Checks
| # | Check | What It Catches | Severity |
|---|---|---|---|
| 1 | Missing | null / NaN / empty values | critical |
| 2 | Duplicates | identical rows, key collisions | error |
| 3 | Outliers | extreme values, anomalies | warning |
| 4 | Schema | missing / extra columns | critical |
| 5 | Types | wrong dtype, mixed types | error |
| 6 | Range | values outside expected bounds | error |
| 7 | Uniqueness | duplicate primary keys | critical |
Let's examine each one.
1. Missing Values
Null values are the most common data quality problem. They cause NaN propagation, silent type coercion, and model failures.
def check_missing(df, required_columns=None):
"""Check for null/NaN values."""
total_nulls = df.isna().sum().sum()
col_nulls = df.isna().sum()
cols_with_nulls = col_nulls[col_nulls > 0]
if required_columns:
missing_required = {c: df[c].isna().sum()
for c in required_columns if df[c].isna().sum() > 0}
if missing_required:
return {'passed': False, 'critical': True,
'message': f'Required columns have nulls: {missing_required}'}
if total_nulls == 0:
return {'passed': True, 'message': 'No null values'}
return {'passed': False, 'message':
f'{total_nulls} nulls across {len(cols_with_nulls)} columns'}
Why it matters:
df.mean()silently ignores NaN — your mean is wrongdf.merge()produces unexpected row counts with null keys- ML models either crash or silently ignore rows with NaN
2. Duplicates
Duplicate rows bias statistics, inflate counts, and create false patterns.
def check_duplicates(df, subset=None):
"""Check for duplicate rows."""
if subset:
dupes = df.duplicated(subset=subset).sum()
label = f' (subset: {subset})'
else:
dupes = df.duplicated().sum()
label = ''
if dupes == 0:
return {'passed': True, 'message': 'No duplicates'}
return {'passed': False, 'message':
f'{dupes} duplicate rows{label}'}
Types of duplicates:
| Type | Detection | Impact |
|---|---|---|
| Exact row copies | df.duplicated() | Inflated statistics |
| Key collisions | df.duplicated(subset=['id']) | Join explosions |
| Near-duplicates | Fuzzy matching | Phantom records |
3. Outliers
Outliers are extreme values that distort statistics and models. Detection depends on context — what is an outlier in one dataset is normal in another.
def check_outliers(df, columns, method='iqr', threshold=1.5):
"""Detect outliers using IQR method."""
outlier_report = {}
for col in columns:
series = df[col].dropna()
q1, q3 = series.quantile(0.25), series.quantile(0.75)
iqr = q3 - q1
lower, upper = q1 - threshold * iqr, q3 + threshold * iqr
outliers = ((series < lower) | (series > upper)).sum()
if outliers > 0:
outlier_report[col] = outliers
return outlier_report
Two detection methods:
| Method | How It Works | Best For |
|---|---|---|
| IQR | 1.5x interquartile range | Most numerical data |
| Z-score | 3 standard deviations | Normally distributed data |
Important: do not automatically remove outliers. Investigate them first — they may be real, important data points.
4. Schema Validation
Schema checks verify that expected columns exist. This catches upstream pipeline changes, file format mismatches, and API changes.
def check_schema(df, expected_columns):
"""Validate column existence."""
missing = [c for c in expected_columns if c not in df.columns]
extra = [c for c in df.columns if c not in expected_columns]
if missing:
return {'passed': False, 'critical': True,
'message': f'Missing columns: {missing}'}
if extra:
return {'passed': True, 'warning': f'Extra columns: {extra}'}
return {'passed': True, 'message': 'All expected columns present'}
Schema validation is a critical check — missing columns cause immediate crashes downstream.
5. Type Validation
Wrong data types cause silent failures. A numeric column loaded as string, or a date loaded as object, breaks downstream operations.
def check_types(df, expected_types):
"""Validate column data types."""
mismatches = {}
for col, expected in expected_types.items():
if col not in df.columns:
continue
actual = str(df[col].dtype)
if expected.lower() not in actual.lower():
mismatches[col] = {'expected': expected, 'actual': actual}
return mismatches
Common type problems:
| Problem | Symptom | Fix |
|---|---|---|
| Number loaded as string | mean() fails | pd.to_numeric() |
| Date loaded as object | Date operations fail | pd.to_datetime() |
| Integer loaded as float | NaN in column | Handle nulls first |
| Mixed types in column | Unexpected dtype | Investigate source |
6. Range Validation
Range checks verify that values fall within expected bounds. A negative quantity, a price of -100, or an age of 500 are range violations.
def check_range(df, ranges):
"""Validate values within expected ranges."""
issues = {}
for col, (low, high) in ranges.items():
if col not in df.columns:
continue
series = df[col].dropna()
below = (series < low).sum()
above = (series > high).sum()
if below > 0 or above > 0:
issues[col] = {'below': below, 'above': above}
return issues
Define ranges based on domain knowledge:
| Field | Expected Range | Why |
|---|---|---|
| quantity | [1, 10000] | Cannot order 0 or negative items |
| unit_price | [0.01, 100000] | Prices must be positive |
| age | [0, 120] | Human age bounds |
| percentage | [0, 100] | Cannot exceed 100% |
7. Uniqueness Validation
Uniqueness checks verify that primary key columns contain unique values. Duplicate keys break joins, cause row explosions, and corrupt aggregations.
def check_uniqueness(df, columns):
"""Validate primary key uniqueness."""
issues = {}
for col in columns:
if col not in df.columns:
continue
total = len(df)
unique = df[col].nunique()
dupes = total - unique
if dupes > 0:
issues[col] = dupes
return issues
The test is simple: nunique() should equal len() for primary key columns.
Running All 7 Checks
Here is the complete validator class that runs all checks and generates a quality report:
class DataQualityValidator:
def __init__(self, df, name='dataset'):
self.df = df
self.name = name
self.results = []
def check_missing(self, required_columns=None):
# ... (see full code above)
pass
def check_duplicates(self, subset=None):
# ... (see full code above)
pass
def check_outliers(self, columns=None, method='iqr'):
# ... (see full code above)
pass
def check_schema(self, expected_columns):
# ... (see full code above)
pass
def check_types(self, expected_types):
# ... (see full code above)
pass
def check_range(self, ranges):
# ... (see full code above)
pass
def check_uniqueness(self, columns):
# ... (see full code above)
pass
def summary(self):
total = len(self.results)
passed = sum(1 for r in self.results if r['passed'])
score = (passed / total * 100) if total > 0 else 0
grade = 'A' if score >= 90 else 'B' if score >= 80 else 'C' if score >= 70 else 'F'
print(f'Quality score: {score:.0f}% (Grade: {grade})')
return {'score': score, 'grade': grade, 'passed': passed, 'total': total}
Real Validator Output
When we ran the validator on 5,001 synthetic sales rows with injected issues:
| Check | Result | Details |
|---|---|---|
| MISSING | FAIL | 490 nulls in required column 'product' |
| DUPLICATES | FAIL | 5 rows with duplicate customer+date |
| OUTLIERS | FAIL | quantity=99999 detected via IQR |
| SCHEMA | PASS | All 9 expected columns present |
| TYPES | FAIL | order_date: expected object, got str |
| RANGE | FAIL | quantity: 1 below, 1 above [1, 1000] |
| UNIQUENESS | FAIL | order_id: 1 duplicate |
Quality Score: 12% (Grade: F) — 2 critical, 2 error, 3 warning. Total validation time: 19.0 ms.
When to Run Each Check
| Check | When to Run | Frequency |
|---|---|---|
| Missing | After every extraction | Every run |
| Duplicates | After extraction + after joins | Every run |
| Outliers | Before model training | Each training cycle |
| Schema | First time + when sources change | On schema change |
| Types | After every extraction | Every run |
| Range | After transformation | Every run |
| Uniqueness | Before joins and aggregations | Before critical ops |
Common Mistakes
- Ignoring missing values — NaN propagates silently through calculations
- Auto-removing outliers — investigate before deleting
- Skipping schema validation — crashes downstream with unhelpful errors
- Trusting inferred types — always verify dtypes explicitly
- No quality report — if you don't measure it, you can't improve it
Best Practices
- Run quality checks before every analysis and model training
- Set severity levels — know what must stop the pipeline vs. what is a warning
- Generate a quality report — store it with your outputs
- Define expected ranges using domain knowledge, not arbitrary numbers
- Treat quality checks as code — version them, test them, maintain them
- Log quality metrics over time — track whether data quality is improving or degrading
Further Reading
- Build Your First Python Data Pipeline — quality checks as part of the ETL pipeline
- Data Leakage in ML — quality issues that cause leakage
- Train/Val/Test Sets — quality-aware data splitting
- Why ML Models Fail in Production — data quality as a root cause
- Model Drift Explained — quality degradation over time
- Parquet vs CSV — Parquet preserves types that CSV loses
- Feature Engineering in the Age of AI — quality features start with quality data
Conclusion
Data quality is not a one-time task — it is a discipline. Every dataset has problems. The question is whether you find them before they affect your results, or after.
Seven checks cover the most common issues: missing values, duplicates, outliers, schema, types, range, and uniqueness. Running them takes milliseconds. The insights they provide are worth far more.
💬 Discuss this topic
Have questions or insights about Data Quality Checks Every Data Scientist Should Know? Join the BestWordz Community.
📚 Related Articles
Data Leakage in Machine Learning: 10 Mistakes That Destroy Your Model
Key Takeaway --> Data leakage occurs when your model accidentally uses information that wouldn't b…
CybersecurityWhat Is Prompt Engineering?
Key Takeaway Prompt Engineering is the skill of communicating effectively with AI models. It is not…
AI & Machine LearningData Contracts Explained: Making Data Pipelines More Reliable
A data contract is a formal agreement between a data producer and a data consumer that defines the …
AI & Machine LearningDuckDB: SQL on Your Laptop for Modern Data Science
DuckDB lets you run SQL queries directly on CSV, Parquet, and JSON files — without loading them int…
CybersecurityThe 10-Stage Data Science Roadmap
Data science in 2026 spans far beyond machine learning. A complete data scientist needs Python, sta…
AI & Machine LearningParquet vs CSV: Why Data Scientists Should Care
For analytical and data science workflows, Apache Parquet is usually superior to CSV in storage eff…
🔧 Related Tools
Standard Deviation Calculator
Compute the standard deviation of a data set — sample or population — with variance, mean, and coun…
Try it now →HTML Entity Encoder
Encode and decode HTML Entity data, entirely in your browser.
Try it now →URL Encoder
Encode and decode URL data, entirely in your browser.
Try it now →Recall Calculator
Compute recall (sensitivity) — the share of actual positives the model managed to find.
Try it now →💬 Discuss on BestWordz Community
Join the conversation about Python, Rust, Data Science on the BestWordz Community forum.
Visit Forum →