Profiling columns
Before you can defend data quality you have to measure it: null rates, distinct ratios, ranges, and distributions — the fingerprint of every column.
You cannot write good quality checks for data you haven't measured. Profiling is the measurement step: for every column, compute a small statistical fingerprint — and suddenly a table you 'know' becomes a table you know. Every serious data-quality practice, and every data-observability product including our own Vigil, starts exactly here.
The fingerprint
- Null rate — share of missing values. The single most informative number about a column. A null rate that moves matters more than its absolute level:
emailgoing from 30% to 55% null overnight means an upstream form broke, even though both numbers 'look bad.' - Distinct ratio — distinct values ÷ rows. Near 1.0 → the column behaves like an identifier. Near 0.0 → a category. An 'identifier' at 0.97 is a smell: something is duplicating. This one ratio is how tools infer which columns should be unique.
- Range & percentiles — min, max, p50, p95 for numbers and dates.
unit_pricemax of 9,999 says someone fat-fingered;ordered_atmax in the future says a timezone bug. - Top values — the five most frequent values and their shares. For categorical columns this is the distribution:
statusshould be roughly 80/10/10 across completed/cancelled/refunded. A new value ('complete', no 'd') appearing at 2% is a contract change nobody announced.
SELECT
count(*) AS rows,
count(*) - count(email) AS nulls,
round(1.0 - count(email) / count(*), 3) AS null_rate,
round(count(DISTINCT email) * 1.0 / count(*),3) AS distinct_ratio
FROM stg_pos_customers;
-- DuckDB shortcut for the whole table at once: SUMMARIZE stg_pos_customers;Profile per day, not just per table
A single profile tells you what the data is; a daily profile series tells you what the data is doing. Bucket the fact table by order_date and compute the fingerprint per day, and quality stops being a snapshot and becomes a signal: row counts per day form a volume baseline, daily null rates expose the exact date a source started misbehaving, daily top-values expose category drift. Module 3's lab stores profiles in a table for precisely this reason — and it's the same design choice inside Vigil.
The professional move when inheriting any dataset: profile first, promise second. Twenty minutes of profiling tells you which quality guarantees you can actually make — and hands you the thresholds for the checks you'll write in the next lesson, based on evidence instead of optimism.