Cleaning without profiling is editing by anecdote. A few strange rows may be real edge cases, while a quiet formatting difference may affect half the dataset. Profiling establishes the shape of the data before a repair changes the evidence.
Start with grain and identity
Before counting nulls, write one sentence describing a row:
One row represents one menu record as catalogued by the source.
Then identify the candidate key and test it:
SELECT id, COUNT(*) AS n
FROM menu
GROUP BY id
HAVING COUNT(*) > 1;
If the supposed key is duplicated, every later count, join, and repair may be ambiguous.
A minimum useful profile
For each column, collect:
- row count, null count, blank count, and distinct count;
- minimum and maximum for numeric or temporal values;
- length distribution for text;
- most common values and their frequencies;
- values that fail an expected pattern or domain;
- cross-column rules relevant to the intended use.
SELECT
COUNT(*) AS rows,
SUM(place IS NULL) AS null_places,
SUM(TRIM(COALESCE(place, '')) = '') AS blank_places,
COUNT(DISTINCT TRIM(place)) AS distinct_places
FROM menu;
Null and blank are counted separately because they may come from different source behaviors. Sentinel values such as ?, N/A, or 0001-01-01 require their own counts.
Classify the defect before choosing a tool
| Class | Example | Useful technique |
|---|---|---|
| Syntactic | Several date formats | Regex, parser, normalization |
| Domain | Year outside 1850–2012 | Range constraint |
| Relational | Author references no paper | Foreign-key check |
| Semantic | Ship name stored as a city | Rules plus context |
| Duplicate | Same entity under variants | Blocking and entity resolution |
| Quantitative | Extreme value in a distribution | Statistical method |
A regex can detect a shape; it cannot establish that a plausible-looking city is geographically correct. Match the technique to the defect class.
Profile combinations, not only columns
Many defects appear only across attributes:
end_date < start_date;- a resolved city with no source rule;
- the same location mapping confidently to several cities;
- a country code incompatible with a postal-code pattern.
SELECT location, COUNT(DISTINCT canonical_city) AS cities
FROM observations
WHERE canonical_city IS NOT NULL
GROUP BY location
HAVING COUNT(DISTINCT canonical_city) > 1;
This query discovers ambiguous mappings that a per-column profile cannot see.
Preserve a baseline
A profile is most useful when it can be compared before and after cleaning. Store machine-readable counts, the query or code that produced them, and the dataset fingerprint. A repair should explain each change in terms of:
before violations - repaired + newly exposed = after violations
If row count falls, say which rejection rule removed rows. If cardinality collapses from 3,708 raw place strings to 233 cities, retain the mapping and unresolved set.
Review card
- State row grain and verify identity first.
- Count null, blank, sentinel, distinct, range, and frequency separately.
- Choose a technique after classifying the defect.
- Cross-column and cross-row profiles reveal semantic problems.
- Preserve the pre-cleaning profile as evidence.
- Every changed, rejected, and unresolved row should be reconcilable.
沒有 profiling 就開始 cleaning,等於依 anecdote 編輯資料。幾筆奇怪 row 可能只是正常 edge case;一個不顯眼的 format 差異反而可能影響半份 dataset。Profiling 的用途,是在 repair 改變證據前先保存資料原貌。
先寫 grain 與 identity
計算 null 前,先用一句話定義 row:
一列代表 source catalog 中的一份 menu record。
接著找 candidate key 並驗證:
SELECT id, COUNT(*) AS n
FROM menu
GROUP BY id
HAVING COUNT(*) > 1;
如果預期 key 已重複,後面的 count、join 與 repair 都可能含糊。
最小但有用的 profile
每個 column 至少收集:
- row、null、blank、distinct count;
- numeric/temporal min、max;
- text length distribution;
- 最常見 value 與 frequency;
- 不符合 pattern/domain 的值;
- intended use 需要的 cross-column rule。
SELECT
COUNT(*) AS rows,
SUM(place IS NULL) AS null_places,
SUM(TRIM(COALESCE(place, '')) = '') AS blank_places,
COUNT(DISTINCT TRIM(place)) AS distinct_places
FROM menu;
Null 與 blank 要分開算,因為它們可能來自不同 source behavior。?、N/A、0001-01-01 等 sentinel 也要獨立計數。
先分類 defect,再選工具
| 類別 | 例子 | 適合技術 |
|---|---|---|
| Syntactic | 多種 date format | Regex、parser、normalization |
| Domain | Year 不在 1850–2012 | Range constraint |
| Relational | Author 指向不存在的 paper | Foreign-key check |
| Semantic | Ship name 被當成 city | Rule 加 context |
| Duplicate | 同一 entity 有多種寫法 | Blocking、entity resolution |
| Quantitative | Distribution 中的 extreme value | Statistical method |
Regex 能判斷形狀,不能證明一個看似合理的 city 在地理上正確。工具要跟 defect class 對上。
不只 profile 單欄,也看組合
很多問題只會出現在 attribute 之間:
end_date < start_date;- resolved city 沒有 source rule;
- 同一 location 高信心映射到多個 city;
- country code 與 postal-code pattern 不相容。
SELECT location, COUNT(DISTINCT canonical_city) AS cities
FROM observations
WHERE canonical_city IS NOT NULL
GROUP BY location
HAVING COUNT(DISTINCT canonical_city) > 1;
這能找到單欄 profile 看不見的 ambiguous mapping。
保留 baseline
Profile 能做 before/after 比較時才最有價值。保存 machine-readable count、產生它的 query/code,以及 dataset fingerprint。Repair 應能解釋:
before violations - repaired + newly exposed = after violations
Row count 減少,就說清楚哪條 rejection rule 移除了哪些 row;cardinality 從 3,708 個 raw place 變 233 個 city,就保留 mapping 與 unresolved set。
複習卡
- 先定義 row grain,再驗證 identity。
- Null、blank、sentinel、distinct、range、frequency 分開算。
- 分類 defect 後再選工具。
- Cross-column/cross-row profile 才看得到 semantic problem。
- Pre-cleaning profile 是後續比較的證據。
- Changed、rejected、unresolved row 都應能 reconcile。