Learn
Concept·

Data Cleaning & Preprocessing

One customer showed up in the data as four

You group revenue by city and Seoul comes back four times: "Seoul", "seoul", "SEOUL", and "Seoul " with a trailing space. To the machine these are four different strings, and GROUP BY dutifully returns four rows.

Member A · Seoul · 12,000

Member A · seoul · 12,000  ← the same person, entered twice

Member B · SEOUL · 8,000

Member C · "Seoul " (trailing space) · 5,000

Two steps fix this: de-duplicate and normalise the spelling. Run them in one order and then the other, and the answers part company.

Order of stepsRows dropped as duplicatesRows leftSeoul revenue
De-duplicate → normalise0437,000
Normalise → de-duplicate1325,000

Suppose you had run de-duplication first. It would have caught nothing. Two rows for the same person still differ if the city is spelled differently, so they are not identical rows. Normalise the spelling afterwards and those rows become identical — but de-duplication has already been and gone, so they stay in the table permanently.

Data cleaning is this kind of work. Not one idea in it is hard, but getting the order wrong fails silently and leaves no trace. There are simply a few more rows than there should be.

The key question

If I run this step now, will the next step be able to see what it needs to see?

1

Concept

What you are fixing, and what you are not

Cleaning and preprocessing are different jobs

The two get named in one breath, which blurs them. Cleaning restores data to what it should have been all along. Preprocessing changes it into something a model can use.

CleaningPreprocessing
GoalCorrect what is wrongReshape into a usable form
ExamplesDe-duplication, type casting, spellingScaling, encoding, derived features
JudgementThere is a right answerDepends on the model
Learns parametersNoYes — means, maxima, category stats
Relative to the train/test splitFine before itMust come after it

That last row is where real projects go wrong. Fixing a typo using the whole dataset harms nothing. But the moment you compute a mean in order to fill blanks, that mean has to come from the training rows alone.

The four things cleaning deals with

Duplicates

the same row twice

Exact copies, and copies that only match after cleaning.

Types

numbers stored as text
"2025-1-9"2025-01-09

Dates, numbers and booleans arriving as strings.

Consistency

one value, four spellings
SeoulseoulSEOULSeoul␣

Whitespace, casing and naming variants of one category.

Sentinels

missing in disguise
999

999 and −1 are values to the computer, blanks to you.

  • Duplicates — rows that are already identical, and rows that only become identical after cleaning.
  • Types — dates and numbers that arrived as strings. Comparison and sorting both go wrong.
  • Consistency — one value fragmented by whitespace, casing or naming.
  • Sentinels — 999 or −1 standing in for missing. They corrupt statistics without a warning.

Never edit the source

Open the file and fix it by hand, and the work evaporates. Nobody knows what you changed, next month's extract needs the whole job doing again, and when a result looks odd there is nothing to trace back through.

  • The raw file is read-only and never overwritten
  • Every fix is code — one readable line at a time
  • Rerunning it has to produce exactly the same table
  • Log the row count after each step
  • Comment why the fix was made, not just what it does

Why the row counts are worth logging

Print the surviving row count at every step and the question "you said fifty thousand, why is this forty-eight?" takes one line to answer. Without that log, the same question eats a day.

2

Why It Matters

Get the order wrong and the duplicates survive

The steps do not commute

Cleaning steps look independent, which is why nobody thinks about ordering them. In fact each one creates the state the next one needs in order to see anything.

How order changes the answer

Normalise → de-duplicate: 12 rows → 8 rows

De-duplicate → normalise: 12 rows → 11 rows

Same two steps, different result. In the second case three pairs survive and get counted as separate customers in every aggregate from then on.

For the same reason, type casting comes first almost always. While a date is text, 2025-10-01 sorts before 2025-2-01, and every duration calculation and range filter downstream is wrong.

Sentinels corrupt statistics quietly

An age column averaging 100 because of a few 999s gets noticed. The dangerous case is the less extreme one: a handful of −1s in "purchase count" pulls the mean down just slightly, and nobody ever looks twice.

ValueWhere it shows upOriginally meant
999, 9999, 99999Age, count, code fields"Not recorded", from fixed-width forms
−1Counts and durations"Unknown", chosen because counts cannot be negative
0AnywhereSometimes a real zero, sometimes an unfilled default
1900-01-01, 1970-01-01Date fieldsThe epoch, or a database default
"N/A", "NULL", "-"Text fieldsMissing, typed by hand in three ways
99.9, 9.99MeasurementsAn out-of-range flag from an instrument

Finding them is easy. For each numeric column, look at the minimum, the maximum and the most frequent values. If 999 is a genuine measurement there will be one of it; if it is a sentinel there will be dozens, all identical.

How preprocessing leaks

Cleaning on the full dataset is broadly safe. Anything that learns a parameter is not. These steps extract a number from the data and remember it, and if that number carries information from the test rows, you have leakage.

StepWhat it memorisesIf fitted on everything
Mean imputationThe column meanThe test set mean bleeds into training
StandardisationMean and standard deviationSame problem
Min-max scalingThe minimum and maximumTest-set extremes leak in
Target encodingTarget mean per categoryThe worst case — the answer leaks directly
Feature selectionWhich columns surviveYou picked them by looking at test performance
Outlier thresholdsColumn quantilesThe test rows helped set the cut-off

The symptom never varies

Leakage makes validation scores better than the real thing. Performance drops once it ships. Which is why a model that comes in better than expected is not cause for celebration but cause for an audit.

3

How It Works

Seven steps, each in the only place it works

1. Seven steps, in this order

The sequence below is not a preference. Each step sits where it does because it needs what the previous step produced.

Table 1 The pipeline, and why each step belongs where it is
#StepWhy it goes here
1Fix typesNothing downstream can compare values it cannot parse
2Normalise textTurns near-duplicates into exact ones
3Convert sentinels to missing999 must stop being a number before any statistic runs
4Drop duplicatesOnly now do duplicates actually look identical
5Apply range rulesImpossible values are easier to spot in a clean frame
6Split train and testEverything after this point must be fitted on train alone
7Impute, scale, encodeThese learn parameters, so they come after the split

Step 6 is the dividing line. Above it the work has a right answer, so the full dataset is fine. Below it the work learns numbers from the data, so it may only ever see the training rows.

2. Sort the duplicates by kind first

Five quite different situations hide under the phrase "drop duplicates". Delete indiscriminately and you lose real records.

Kind of duplicateHow to find itWhat to do
Every column identicalA plain duplicate checkSafe to drop — but find out why it happened
Same key, different valuesGroup by the key, count > 1Decide which record wins, and record the rule
Same entity, different spellingNormalise first, then check againFuzzy match only if you must, and review it
Duplicated by a joinRow count jumped after a mergeThe join key is not unique — fix the join
Legitimately repeatedSame person, two real purchasesKeep both — this is not a duplicate

The last row is the one to remember. If the same customer paid the same amount twice on the same day, those are two transactions, not a duplicate. Dropping them because the rows match deletes revenue.

3. A standard order for text normalisation

Strings that look identical to a reader are frequently different to a machine. Working through these in order clears up most of it.

StepApplies toExample
Trim leading and trailing spaceNearly every text column"NY " → "NY"
Collapse repeated spacesNames and addresses"New York" → "New York"
Fold caseCodes and categories"PRO" → "Pro"
Unicode normalisationAccents and composed characters"José" in two encodings
Apply an alias tablePlaces and company names"NYC" → "New York"
Unify missing markers"", "N/A", "unknown"All become null

Fuzzy matching is a last resort

Merging similar values by edit distance is powerful and fails silently."Manchester" and "Winchester" are close. Exhaust the rules you can write down first, then take whatever is left as a list for a human to reviewrather than a merge to run automatically.

4. Write the range rules down

Rules that come from the domain — "age is between 0 and 120" — cost one line of code and then catch violations in next month's extract automatically. Kept in your head, they get missed every time.

  • Numeric: the physically possible minimum and maximum
  • Dates: nothing before launch, nothing after today
  • Ordering: signup ≤ first purchase ≤ last purchase
  • Totals: the parts must not exceed the whole
  • Categories: flag anything outside the allowed list
4

Example

How 50,000 rows became 47,904

In practice: a cleaning log for 50,000 rows

Here is the log from cleaning a subscription membership table. Recording the row count at every step means any later question about the numbers has an answer.

StepRows leftChangeNote
Loaded raw50,000Straight from the CSV
Cast three date columns50,000012 unparseable values set to null
Normalised text50,0000plan: 11 distinct values → 4
−1 and 9999 → missing50,0000812 in tenure, 47 in age
Dropped exact duplicates48,317−1,683The export job ran twice
Resolved key duplicates48,102−215Kept the most recent record
Removed range violations48,061−41age > 120, tenure < 0
Dropped rows with no label47,904−157Churn status unknown

The total loss is 2,096 rows, or 4.2%. But 1,683 of those came from an export job that ran twice — which is not a data problem, it is a pipeline problem. Deleting the rows and moving on is the wrong response; telling the data team is the right one.

The lesson here

A cleaning log is not a record of what you patched. It is a report on what is broken upstream. Those 1,683 duplicates will be back next month, and every month after, until somebody fixes the cause.

In practice: the rows that survived because someone asked

In the same table, 41 rows had a negative tenure_days. It looked like an obvious error and was about to be deleted. Asking why turned up a reason.

Where the negatives came from

Signup date was account creation time; first payment was the processor's authorisation time.

For pre-order customers the payment cleared before the account existed.

So the 41 rows were not errors, they were pre-order customers — and the segment with the lowest churn rate in the whole table. Deleting them would have thrown out the best customers in the business.

The whole difference is between "this looks wrong, delete it" and "this looks wrong, find out why". Odd values are usually information about how the data came to exist.

Common misunderstandings

Misconception 1

❌ Identical rows can always be dropped.

If the same customer paid the same amount on the same day twice, both are real. On a table with no unique transaction ID, identical rows can be perfectly correct — so establish what the table's key is before deleting anything.

Misconception 2

❌ Fill the blanks with the mean and get started.

Mean imputation shrinks variance and distorts correlations, and the mean it uses has to come from the training rows only. The question that comes before filling anything is "why is this blank?"

Misconception 3

❌ Cleaning is a one-off task.

The data arrives again next month. Fix it by hand and you will fix it by hand every month — and slightly differently each time. That is the real argument for writing it as code.

Misconception 4

❌ Might as well scale while you are cleaning.

Scaling learns a mean and a standard deviation from the data. Do it before the split and the test rows contribute to those numbers, which makes validation look better than the model actually is.

5

Interactive

Run the same steps in different orders

Run the same steps in different orders

Twelve rows. The four steps are applied in the order you click them, and the badge on each button shows its position. Turn all four on and the surviving row count still depends on the sequence.

Click the steps in the order you would run them. The number on each button is its place in the pipeline.

Rows

12

Distinct cities

10

Mean spend

192.8

namecityjoinedspend
Kim MinjunSeoul2025-03-04120
kim minjun␣seoul2025-03-04120
Lee SoyeonBusan2025-1-9340
Lee Soyeonbusan␣2025-1-9340
Park JihoSEOUL2025-07-21-1
Choi EunjiIncheon2025-11-285
Choi EunjiIncheon2025-11-285
Jung HaerinSeoul␣2025-05-13210
Yoon DoyunDaegu2025-08-30-1
Kang SenaBUSAN2025-02-17460
Kang Sena␣Busan2025-02-17460
Oh TaeminIncheon␣2025-12-195

Twelve rows as delivered. Three of them are the same three people entered twice, but no two of those rows are byte-identical yet.

Cleaning steps do not commute. The order is part of the method.

What to look for

  • With nothing enabled, why are there ten distinct cities?
  • Click "Trim & case" then "Drop duplicates" — how many rows remain?
  • Now the other way round. How many remain then?
  • How far does the mean spend move once −1 becomes missing?
  • Casting dates changes no row counts — so why bother?

Learning points

De-duplication recognises identical rows only — near-matches are invisible to it.

Which is why normalising the spelling has to come strictly before it.

Getting the order wrong raises no error. There are just a few more rows than there should be.

Key takeaways

Order matters

The steps do not commute

  • Normalise before de-duplicating
  • Cast types before comparing
  • Split before anything that learns

Never edit in place

Cleaning is code, not handiwork

  • The raw file stays untouched
  • Every fix is a line someone can read
  • Rerunning it gives the same table

Ask before you fix

A defect is also information

  • Duplicates point at a broken pipeline
  • Sentinels reveal how data was collected
  • Deleting hides the cause

The question is not "what should I fix?"

It is "will the next step be able to see what it needs after this one runs?" Cleaning is not a checklist of tasks but an ordered pipeline.

How does this apply to real data?

Cleaning is not a thing you do once before analysing. It is code that reruns every time the data refreshes. In SKARI you can check the following alongside.

Duplicates and quality

Duplicate Check, Key Validation, Range Rules

Text tidying

Trim, Case Fold, Category Mapping

Missing and outliers

Missing Map, Sentinel Scan, Outlier Fence

Preprocessing

Scaling, Encoding, Train/Test Split

Once this clicks, you can answer questions like these.

  • Where in the pipeline does this step belong?
  • Is this duplicate an error or a real record?
  • Does this step learn anything from the data?
  • Did I log how many rows each step removed?
  • Will this same code run unchanged on next month's extract?
Now try it on real dataOpen in Lab

Go Deeper

Missing Values