1. Courses
  2. Data
  3. Data Analysis with Excel, Python & AI
  4. Stop 2

35 minBeginner

Cleaning data in a spreadsheet

An export almost never arrives ready to analyse. This lesson takes the messy East city sales export from Mangrove Market and turns it into a table you can trust: the raw file kept untouched, every change written down, and the row counts and totals checked before anyone reads a number.

By the end you can

  • Keep the raw export untouched and do every change in a working copy with helper columns
  • Reshape a table so that it is tidy: one row per observation, one column per variable
  • Find and fix numbers stored as text, dates read the wrong way, stray spaces and inconsistent labels
  • Find duplicates with COUNTIFS before you remove them, and add store details with XLOOKUP
  • Keep a cleaning log and prove the result with row counts and a total that matches another source

The everyday picture

A grocery delivery arrives at a restaurant kitchen. Before anyone cooks, the chef checks the crates against the delivery note. Two crates of tomatoes are listed once but delivered twice. One box is labelled "toms", another "Tomatoes", another "TOMATO". A bag of onions has no label at all. The chef counts, sorts, relabels and sets aside what is wrong.

Two habits make this work. The chef keeps the supplier's delivery note in a drawer, unchanged, so any question later can be checked against it. And the chef writes on the note what was sent back and why. If the numbers at the end of the night don't add up, the trail is there.

Cleaning data is the same job. The raw export is the delivery note: you never write on it. The working copy is the kitchen. The cleaning log is the note of what you changed. And counting the crates is the row count you check before and after every step.

Raw stays raw, and the table should be tidy

In Asking a question data can answer you wrote an analysis plan for "East city sales are down. Why?" The plan named a dataset: daily sales by store and category for East city's 54 stores, for the 8 weeks to Sunday 18 October 2026. Tomás, Mangrove's data analyst, now sends you that as a CSV file. Before you look at a single number, you set up your workbook in three tabs.

  • raw: the export, exactly as it arrived. You never type in it, sort it or delete from it. If you break something later, you start again from here.
  • work: helper columns with formulas that read from raw and produce clean values next to the original ones, so you can compare them row by row.
  • clean: the final table, pasted as values from work, plus a log tab that lists every change.

Why so careful? Because a spreadsheet does not remember what you did. If you overwrite a column and save, the old values are gone, and nobody, including you next week, can tell what the file said before. A formula next to the original keeps both.

The clean table should be . Hadley Wickham's 2014 paper Tidy Data gives three rules: each variable is a column, each observation is a row, and each type of thing you observe is its own table. For our question, one is one store's sales of one category on one day. That is the table's , the word you met in da-01. The variables are store, date, category, sales and transactions.

Many files are built for reading on screen. A store manager's weekly report has a column per week, total rows and merged headers. A pivot table or a formula cannot use it: the week, a variable, is hidden in the column headers, and the total rows are a different kind of thing mixed in with the observations.

Wide: a column per week store Wk 41 Wk 42 201 95,210 93,880 202 88,400 87,950 Total 183,610 181,830 unpivot, drop total Tidy: row per store-week store week sales 201 Wk 41 95,210 201 Wk 42 93,880 202 Wk 41 88,400 202 Wk 42 87,950
The wide table hides a variable (the week) in its headers and mixes a total row with the stores. The tidy table has one row per store and week, so every tool can filter, sum and pivot it, and totals are computed when you need them.

Turning columns into rows is called . In Excel, Power Query has an Unpivot Columns command; in any tool you can also rebuild the table with formulas, and later in this course you will do it in a few lines of Python (The same analysis in Python). Our East export is already long, one row per store, date and category, but it has a grand total row at the bottom. A total row breaks tidiness the same way: sum the sales column and you count every sale twice.

Quick check

You need to fix a wrong store name in the export. Where do you make the change?

Types: numbers stored as text, and dates read the wrong way

Every cell has a . A number can be added, a date can be grouped by week, and text can only be compared and searched. The most common cleaning problem is a value that looks like a number or a date but is stored as text. Formulas then skip it without any warning.

Numbers stored as text. SUM and AVERAGE add only real numbers. If a range holds "USD 1204.50" as text, SUM leaves that cell out and still shows a confident total. Four quick ways to spot the problem:

  • Alignment: with default formatting, numbers sit on the right of the cell and text on the left.
  • Excel marks many numbers stored as text with a small green triangle in the corner of the cell.
  • =ISNUMBER(E2) returns TRUE for a real number and FALSE for text.
  • Compare =COUNT(E2:E24666), which counts numbers only, with =COUNTA(E2:E24666), which counts every non-empty cell. The difference is the number of text cells in the column.

The fix is a helper column that removes the extra characters and converts the rest: =VALUE(SUBSTITUTE(TRIM(E2),"USD ","")). TRIM removes spaces at the start and end and turns runs of spaces inside the text into one. SUBSTITUTE swaps one piece of text for another, here "USD " for nothing. VALUE turns the text that is left into a number.

Watch the and the decimal mark. One thousand two hundred and four dollars fifty is written 1,204.50 in some countries and 1.204,50 in others. Spreadsheets read numbers using the of your computer or file, so the same CSV can give numbers on one laptop and text on another. Before you convert, look at a few raw values and decide which convention the export uses.

Dates. A spreadsheet stores a real date as a number of days, which is why you can subtract two dates or group them by week. In Google Sheets, and in Excel with its default date system, 18 October 2026 is stored as 46313. Text that only looks like a date gets none of this.

The trap is regional date order. Mangrove's sales system writes dates day first: 03/10/2026 means 3 October. When you open a CSV, the spreadsheet reads each date using your locale. On a laptop set to month first, 03/10/2026 becomes 10 March 2026: a real date, in the wrong month, with nothing to warn you. And 18/10/2026 cannot be read at all (there is no month 18), so it stays as text.

In the CSV What the sheet holds 03/10/2026 Day first: 3 Oct Month first: 10 Mar 18/10/2026 Day first: 18 Oct Month first: text 10 Mar looks real Text dates sort last
Opened on a month-first laptop, a day-first export splits in two. Days 1 to 12 turn into real but wrong dates; days 13 to 31 stay as text. In our 8 weeks, 24 of the 56 days flip and 32 stay as text.

Don't repair flipped dates cell by cell: you cannot tell a flipped 10 March from a real one. Go back to the raw file and import it again so that the date column arrives as text. In Google Sheets, File > Import has a "Convert text to numbers, dates, and formulas" box that you can untick. In Excel, Data > From Text/CSV opens a preview; choose Transform Data and change the date column's type to Text before you load it. Then build the date yourself from its parts:

Output
=DATE(VALUE(RIGHT(C2,4)), VALUE(MID(C2,4,2)), VALUE(LEFT(C2,2)))
   year = last 4 characters, month = characters 4-5, day = first 2

The lasting fix is upstream. Ask for exports with dates, written 2026-10-18. Year, month, day can only be read one way.

Quick check

In the sales column, =COUNTA gives 24,649 and =COUNT gives 19,273. What does that tell you?

Spaces, labels, duplicates and blanks

Invisible characters. "Bakery" and "Bakery " (with a space at the end) look the same on screen, but to a formula or a pivot table they are two different labels. TRIM removes the extra spaces. CLEAN removes non-printing characters such as line breaks that sometimes come along from other systems. Excel's TRIM does not remove the non-breaking space that web pages use; =SUBSTITUTE(A2,CHAR(160)," ") turns it into a normal space first.

Inconsistent labels. The same category can arrive as "Fresh produce", "fresh produce", "Fresh Produce " and "Produce". LOWER, UPPER and PROPER fix the case, but they cannot know that "Produce" and "Fruit & veg" mean the same thing. For that, build a small : one column with every raw label you found (lower case, trimmed), one with the standard name. Then look each row up in it. A row whose label is missing from the map shows "UNMAPPED", so new spellings cannot slip through.

This matters for the analysis. If fresh produce sales are split across four labels, a pivot table shows four small rows instead of one big one, and the drop you are looking for is spread thin.

Lookups from master tables. The export has store IDs but no city and no store type. Those live in a : Mangrove's site table, with one row per store or DC. XLOOKUP finds a value in one column and returns the matching value from another:

Output
=XLOOKUP(A2, stores!$A$2:$A$400, stores!$C$2:$C$400, "NOT FOUND")
   look for the store ID in A2 in the master's ID column,
   return the city from the same row, or NOT FOUND if there is no match

Older versions of Excel: =VLOOKUP(A2, stores!$A$2:$C$400, 3, FALSE)

The dollar signs fix the ranges so they don't move when you copy the formula down 24,665 rows. The fourth argument is what XLOOKUP returns when nothing matches; without it you get #N/A. Count the misses with =COUNTIF(E2:E24666,"NOT FOUND"). A lookup also has a type trap: the number 207 and the text "207" do not match. If one side has IDs stored as text, every row for those stores comes back NOT FOUND.

Duplicates. A is two rows for the same observation: here, the same store, date and category. Exports get them when a store's data is uploaded twice. Find them before you delete anything. A helper column counts how many rows share the row's key:

Output
=COUNTIFS($A$2:$A$24666, A2, $B$2:$B$24666, B2, $C$2:$C$24666, C2)
   how many rows have this store, this date and this category?
   1 = fine, 2 or more = the key appears more than once

Filter that column to values above 1 and look at the rows. If the copies are identical, they are true duplicates. If the key matches but the sales differ, it's a conflict: one of the two numbers is wrong, and you need to ask which before you keep either. Only then use Remove Duplicates (Data > Remove Duplicates in Excel, Data > Data cleanup > Remove duplicates in Google Sheets), choosing the key columns. Both keep the first row of each group, so the conflicts must be settled first.

Blanks and zeros. A blank and a zero mean different things. Zero says "we know the store sold nothing": it was closed. A blank says "we don't know": the upload failed. AVERAGE skips blank cells but counts zeros, so filling blanks with 0 drags averages down and makes a missing day look like a terrible one. Keep blanks blank, add a note column that says why, and count them with =COUNTBLANK(D2:D24666).

Quick check

A store's sales are blank for one day because the till upload failed. Its manager asks you to "put a zero in so the average works". What do you do?

The cleaning log and the checks that prove it

A is a list of every change: what was wrong, how many rows it touched, what you did, and the row count after. It takes ten minutes. When the head of stores asks "are you sure?", the log is your answer, and when the same export comes next month, it is your recipe.

Three checks turn "I cleaned it" into "it is right":

  1. Row counts before and after every step. Write down the count after each change and explain every difference. If you removed 24 duplicates, the count must fall by exactly 24.
  2. The expected shape. You know how many rows a complete table should have: stores × days × categories. Every date should have the same number of rows, and so should every store. =COUNTIFS per date or per store shows any gap.
  3. Reconcile to another source. To is to compare your total with a number from somewhere else, such as finance's weekly sales report, and explain the gap. A small, explained gap is fine. An unexplained one means a step is still wrong.
Raw: 24,665 rows -1 grand total row 24,664 rows -24 duplicates 24,640 rows -448 West store rows Clean: 24,192 rows 54 stores x 56 days x 8 categories = 24,192
Every step changes the row count by an amount you can explain, and the final count matches the shape the table must have: 54 stores, 56 days and 8 categories. A count that doesn't match tells you a step is still wrong.

Fixing types and labels does not change the row count, so check those steps another way: after the fix, the count of text cells, UNMAPPED labels and NOT FOUND lookups should all be zero, and each category should have the same number of rows.

Do it

Clean a sales export into a tidy table with a log and checks. Use the East export or any export from your own work.

  1. Protect the raw data

    Import the CSV into a tab called raw, with the date column as text. Write down the row count straight away: =COUNTA(raw!A2:A24666). Never edit this tab.

  2. Profile every column

    For each column, check the type (COUNT against COUNTA), the blanks (COUNTBLANK), the distinct values of label columns, and the smallest and largest date. Write every surprise in the log before you fix anything.

  3. Build helper columns in the work tab

    One formula per column, each reading from raw. Fill them down to the last row.

    Output
    A store_id   =VALUE(TRIM(raw!A2))
    B date       =DATE(VALUE(RIGHT(raw!C2,4)), VALUE(MID(raw!C2,4,2)), VALUE(LEFT(raw!C2,2)))
    C category   =XLOOKUP(LOWER(TRIM(CLEAN(raw!D2))), map!$A$2:$A$20, map!$B$2:$B$20, "UNMAPPED")
    D sales      =IF(TRIM(raw!E2)="", "", VALUE(SUBSTITUTE(TRIM(raw!E2), "USD ", "")))
    E city       =XLOOKUP(A2, stores!$A$2:$A$400, stores!$C$2:$C$400, "NOT FOUND")
    F dup_count  =COUNTIFS($A$2:$A$24666, A2, $B$2:$B$24666, B2, $C$2:$C$24666, C2)

    The TOTAL row gives an error in column A, because "TOTAL" is not a number. That is useful: it flags the row for removal.

  4. Settle duplicates and out-of-scope rows

    Filter dup_count above 1 and compare the copies. Filter city for anything other than East and for NOT FOUND. Fix the cause of each, then copy the work tab and paste it as values into the clean tab, run Remove Duplicates on store_id, date and category, and delete the rows that are out of scope.

  5. Check and log

    Fill in the log and the checks. The analysis starts only when every check passes or has a written explanation.

    Markdown
    # Cleaning log: East city sales, 24 Aug to 18 Oct 2026
    Source: east_sales export from Tomás, received Tue 20 Oct 2026, 24,665 data rows
    Raw file kept unchanged in tab "raw"
    
    | Step | Problem                      | Rows   | Fix                          | Rows after |
    |------|------------------------------|--------|------------------------------|------------|
    | 1    | Grand total row              | 1      | removed                      | 24,664     |
    | 2    | Sales as text "USD 1204.50"  | 5,376  | SUBSTITUTE + VALUE           | 24,664     |
    | ...  |                              |        |                              |            |
    
    ## Checks
    - Expected rows: 54 stores x 56 days x 8 categories = 24,192   Actual: 24,192
    - Rows per date: all 432          Rows per category: all 3,024
    - Text cells in sales: 0    UNMAPPED: 0    NOT FOUND: 0    Blanks: 16 (explained)
    - Total sales: $42.33M   Finance report: $42.36M   Gap: $27k (0.06%), blank days
    
    ## Open questions
    - Stores 233 and 247: sales for the missing days requested again from store systems

Bring it to your team

Ask these before anyone builds a chart or a pivot table from a new export.

  • Where is the raw file, and has anyone edited it?
  • What is one row of this table, and how many rows should there be?
  • Does our total match finance's number for the same period, and if not, what explains the gap?
  • How were the dates read, and does every date have the same number of rows?
  • Which rows did we remove, and is each removal in the log?
  • Can the export send ISO dates and plain numbers next time?

Red flags

  • A cleaned file with no raw copy and no log.
  • Blanks filled with zeros "to make the formulas work".
  • Duplicates removed before anyone looked at them.
  • A total that has never been compared with another source.
  • A category or store list that is longer than the business actually has.

Practice

Clean and total the export in SQL

The same cleaning works in SQL. The table raw_export(row_no, store_id, sale_date, category, sales) holds a small piece of the East export, every column stored as text, the way it arrived. The table site(id, name, city, kind) is the store master. Clean the export and return one row per category for East city stores only, with the columns category (trimmed, in initcap form such as Fresh Produce), row_count (rows after removing duplicates) and sales_cents (total sales in integer cents). Watch for spaces, mixed case, a $ sign, thousands commas, a duplicated row, a West store and a TOTAL row. A duplicate is a row with the same store, date, category and sales after cleaning; the same sales on a different day is not a duplicate.

SQL exerciseRuns in a real PostgreSQL inside your browser
-- Clean the export, then total East city sales per category
WITH cleaned AS (
  SELECT store_id,        -- TODO: trim(store_id)
         sale_date,
         category,        -- TODO: initcap(trim(category))
         sales            -- TODO: drop ',' and '$' with replace(), then ::numeric
  FROM raw_export         -- TODO: SELECT DISTINCT, so cleaned duplicates collapse to one row
)
SELECT category,
       count(*) AS row_count,
       0 AS sales_cents   -- TODO: sum(sales * 100)::int
FROM cleaned              -- TODO: JOIN site ON site.id::text = cleaned.store_id, keep city = 'East'
GROUP BY category;
Ctrl + Enter runs
Show a solution
SQL
WITH cleaned AS (
  SELECT DISTINCT                                     -- after cleaning, copies are identical
         trim(store_id)           AS store_id,        -- '202 ' becomes '202'
         sale_date,
         initcap(trim(category))  AS category,        -- ' fresh produce' becomes 'Fresh Produce'
         replace(replace(trim(sales), ',', ''), '$', '')::numeric AS sales  -- '1,204.50' becomes 1204.50
  FROM raw_export
)
SELECT c.category,
       count(*)                 AS row_count,
       sum(c.sales * 100)::int  AS sales_cents       -- money as whole cents
FROM cleaned c
JOIN site s ON s.id::text = c.store_id               -- compare as text: 'TOTAL' is not a number
WHERE s.city = 'East'                                -- drops store 318 (West)
GROUP BY c.category;

The order matters: clean first, then remove duplicates. Row 9 only becomes a copy of row 4 after trimming. Row 10 has the same sales as row 2 but on another day, so it stays. The join to the store master does two jobs at once: it drops the TOTAL row, which matches no store, and the West store. Comparing s.id::text with the text ID avoids an error, because 'TOTAL'::int would fail.

Pass the exercise to finish this task.

Find the problems and write the log

A store manager sends you this extract. Your laptop reads dates month first. The full file should have 8 categories per store per day. List every problem you can see, say how you would find it across 24,000 rows with a formula, and write the first four lines of your cleaning log.

Output
store_id  store_name     date        category        sales        transactions
207       Lakeview       05/10/2026  Fresh produce   1,180.40     212
207       Lakeview       05/10/2026  Fresh Produce   1,180.40     212
207       Lakeview       14/10/2026  Dairy           USD 640.00   150
"212"     North Gate     14/10/2026  Produce         905.10       171
219       Canal Street   07/09/2026  Bakery          0            0
233       Mill Lane      12/10/2026  Bakery
TOTAL                                                412,880.55
  • You spot the duplicate on row 2 (same store, date and sales once the label is cleaned) and name COUNTIFS on the key as the way to find all of them
  • You explain that 05/10/2026 will turn into 10 May and 14/10/2026 will stay text, and that the fix is to re-import the dates as text and rebuild them
  • You catch the text sales "USD 640.00" (COUNT against COUNTA, or ISNUMBER) and the store ID stored as text that will break the lookup
  • You map "Produce" to Fresh produce with a mapping table, keep the zero for Canal Street (closed) apart from the blank for Mill Lane (unknown), and remove the TOTAL row
  • Your log lines each have the problem, the number of rows, the fix and the row count after
Show a model answer

Problems: row 2 duplicates row 1 (the labels differ only in case); 05/10/2026 will become 10 May and 14/10/2026 will stay as text on a month-first laptop; "USD 640.00" is text and SUM will skip it; store "212" is text and XLOOKUP against numeric IDs will return NOT FOUND; "Produce" is another spelling of Fresh produce; Canal Street's 0 is a real zero if the store was closed, but Mill Lane's blank is unknown and must stay blank; the TOTAL row would double the sum.

Formulas to find them in the full file: COUNTIFS on store, date and category for duplicates; COUNT against COUNTA and ISNUMBER for text numbers; a mapping table with "UNMAPPED" for new labels; XLOOKUP with "NOT FOUND" for IDs; COUNTBLANK for blanks; COUNTIFS per date to show missing days.

Output
Step  Problem                         Rows   Fix                                Rows after
1     TOTAL row                       1      removed                            n - 1
2     Dates day first, read wrongly   all    re-imported as text, DATE()        n - 1
3     Sales as text "USD ..."         ?      SUBSTITUTE + VALUE, COUNT = COUNTA  n - 1
4     Duplicate store-date-category   ?      checked copies, Remove Duplicates  n - 1 - dups

Check yourself

After cleaning, your East table has 24,190 rows. You expected 54 stores × 56 days × 8 categories = 24,192. What is the best next step?

A pivot of the East data by date shows 24 real dates, all on the 9th or 10th of a month from January to December 2026, plus 32 entries that are text, such as 18/10/2026. What happened?

COUNTIFS shows 48 rows with a count of 2 on store, date and category. In 8 of those pairs, the two rows have different sales values. What should you do?

Flashcards

Open my review

Key takeaways

  • Keep the raw export untouched; clean in helper columns and a separate clean tab, and log every change.
  • Make the table tidy: one row per observation at a clear grain, one column per variable, no total rows.
  • Check types first: numbers stored as text and dates read in the wrong order break formulas without any warning.
  • Fix labels with TRIM, CLEAN and a mapping table, add details from master tables with XLOOKUP, and look at duplicates with COUNTIFS before removing them.
  • A cleaned table is ready when its row count matches the expected shape and its total reconciles to another source.

Sources and further reading

Episode transcriptThe seventy-seven million dollar sum · about 10 minutes · tap any line to play from there
  1. NadiaImran, what would you say if I told you East city sold seventy-seven million dollars in eight weeks?
  2. ImranI'd say that's impossible. East has fifty-four stores. A typical store does about ninety-five thousand dollars a week. That's nowhere near seventy-seven million.
  3. NadiaRight. Finance says about forty-two point four million. But seventy-seven million is what the first SUM of the sales export said on Tuesday morning.
  4. ImranSo the data was wrong before anyone even started the analysis.
  5. NadiaThe data was messy. That's normal. Today is about turning a messy export into a table you can trust, and proving that you did.
  6. NadiaStart with a kitchen. A grocery delivery arrives at a restaurant. Before anyone cooks, the chef checks the crates against the delivery note.
  7. ImranAnd finds tomatoes delivered twice, one box labelled toms, another TOMATO, and a bag of onions with no label at all.
  8. NadiaExactly. And the chef has two habits. The supplier's delivery note goes in a drawer, unchanged. And the chef writes on a separate sheet what was sent back and why.
  9. ImranSo the raw export is the delivery note. You never write on it.
  10. NadiaNever. You work in a copy. In the spreadsheet that means three tabs. Raw, exactly as it arrived. Work, with helper columns that read from raw. And clean, the final table, plus a log of every change.
  11. ImranWhy not just fix the cells in place? It's faster.
  12. NadiaBecause a spreadsheet doesn't remember what you did. Overwrite a column, save, and the old values are gone. Nobody can tell what the system sent, including you next week.
  13. ImranFair. Then what does the clean table look like?
  14. NadiaTidy. That's from a twenty fourteen paper by Hadley Wickham. Each variable is a column, each observation is a row, and each type of thing is its own table.
  15. ImranAnd an observation here is one store's sales of one category on one day.
  16. NadiaThat's the grain. The opposite is a store manager's weekly report, with a column per week and a total row under each group. Nice to read. Useless for a pivot table, because the week is hidden in the headers.
  17. ImranAnd our export had a total row too.
  18. NadiaAt the very bottom. A grand total. Sum the column and every sale is counted twice. That's most of the seventy-seven million.
  19. ImranMost of it? What's the rest?
  20. NadiaThe rest goes the other way. Twelve stores are on older tills, and their export writes sales like USD twelve hundred and four fifty. That's text. SUM skips text cells and still shows a confident total.
  21. ImranNo warning at all?
  22. NadiaNone. So you test for it. Count the column with COUNT, which counts only numbers, and with COUNTA, which counts every filled cell. The difference is the text cells. Here it was five thousand three hundred and seventy-six.
  23. ImranTwelve stores, fifty-six days, eight categories. That's exactly the old-till stores.
  24. NadiaNice arithmetic. The fix is a helper column. TRIM the spaces, SUBSTITUTE away the USD, and VALUE turns what's left into a real number.
  25. ImranI've also seen numbers with a comma for thousands on one laptop and a comma for decimals on another.
  26. NadiaYes. The spreadsheet reads numbers using your locale, your regional settings. So look at a few raw values first and decide which convention the export uses.
  27. NadiaNow the nastiest one. Dates. If you can see the screen, the second diagram shows two dates going into a spreadsheet. If not, picture this.
  28. NadiaMangrove's sales system writes dates day first. So the third of October is written zero three, then ten, then twenty twenty-six. Your laptop is set to read month first.
  29. ImranSo it reads the third of October as the tenth of March.
  30. NadiaA real date, in the wrong month, and nothing looks odd. And the eighteenth of October can't be read at all, because there's no month eighteen. So it stays as text.
  31. ImranSo in our eight weeks, any day from the first to the twelfth flips, and anything after the twelfth stays text.
  32. NadiaTwenty-four of the fifty-six days flipped, and thirty-two stayed text.
  33. ImranCan I fix the flipped ones by hand?
  34. NadiaNo, and this is the trap. You can't tell a flipped tenth of March from a real one. Go back to the raw file, import the date column as text, and build the date from its parts with the DATE function.
  35. ImranAnd next time, ask for the export in year, month, day order.
  36. NadiaISO dates. Twenty twenty-six, ten, eighteen. Every tool reads them one way only.
  37. ImranWhat about the labels? I bet fresh produce came in five spellings.
  38. NadiaFourteen spellings for eight categories. Fresh produce, fresh produce with a space at the end, Produce, Fruit and veg. A pivot table shows them as separate rows.
  39. ImranWhich spreads the fresh sales thin, and that's the category we care about.
  40. NadiaExactly. TRIM and LOWER fix spaces and case. For different words you build a small mapping table, raw label on the left, standard name on the right, and look each row up in it. Anything missing shows UNMAPPED.
  41. ImranAnd the city? The export only has store IDs.
  42. NadiaXLOOKUP against the store master. Give it a fourth argument, NOT FOUND, so misses are easy to count. Our first try returned NOT FOUND for two thousand six hundred and eighty-eight rows.
  43. ImranMissing stores?
  44. NadiaNo. Six store IDs in the master had been pasted in as text. The number two hundred and seven doesn't match the text two hundred and seven. Convert them with VALUE and the misses drop to zero.
  45. ImranNow tell me about the duplicates, because that's where I'd have pressed Remove Duplicates straight away.
  46. NadiaMost people do. Look first. A helper column with COUNTIFS counts how many rows share the same store, date and category. Forty-eight rows showed two.
  47. ImranSo twenty-four pairs. Three store days uploaded twice, eight categories each.
  48. NadiaAnd the copies were identical, so they were safe to remove. They'd added about forty-one thousand dollars. But if a pair has the same key and different sales, that's a conflict, not a duplicate.
  49. ImranBecause Remove Duplicates keeps the first row, and the first might be the wrong one.
  50. NadiaRight. Ask the source which value is true before you keep either.
  51. ImranLast one. Two stores had blank sales for a day. I'd put a zero so the average works.
  52. NadiaThat's the classic mistake, Imran. A zero says the store sold nothing. A blank says we don't know. The upload failed. AVERAGE already skips blanks but counts zeros, so a zero turns a lost upload into a terrible day.
  53. ImranSo keep it blank, write why, and ask for the figures again.
  54. NadiaThen you prove it. The third diagram is a ladder of row counts. Twenty-four thousand six hundred and sixty-five raw rows. Minus one total row. Minus twenty-four duplicates. Minus four hundred and forty-eight rows for a West store with the wrong region tag.
  55. ImranWhich leaves twenty-four thousand one hundred and ninety-two.
  56. NadiaAnd that's exactly fifty-four stores times fifty-six days times eight categories. The shape you expected. Every date has four hundred and thirty-two rows. Every category has three thousand and twenty-four.
  57. ImranAnd the total?
  58. NadiaForty-two point three three million against finance's forty-two point three six. The gap is about twenty-seven thousand dollars, roughly six hundredths of a percent, and it's the two blank store days. Explained.
  59. ImranSo that's reconciling. Your total against an independent number, with every gap explained.
  60. NadiaThat's it. The Do it section walks you through it. Import the raw file and count its rows. Profile every column. Build helper columns for store, date, category, sales, city and a duplicate count. Settle duplicates and out-of-scope rows. Paste as values, then fill in the log and the checks.
  61. ImranAnd the practice has the same cleaning in SQL. Trim, fix the case, strip the dollar signs and commas, remove duplicates, and join the store master.
  62. NadiaWith one twist. Clean first, then remove duplicates. Two rows only look identical after you trim them.
  63. ImranSo when someone puts a new export in front of my team, I ask: where's the raw file, what is one row, how many rows should there be, and does the total match finance?
  64. NadiaGood list. Here's one to think about. Your cleaned table has twenty-four thousand one hundred and ninety rows. You expected twenty-four thousand one hundred and ninety-two. Two rows short. What do you do? Take a second.
  65. ImranI'd count rows per date and per store with COUNTIFS, find exactly where the two are missing, and write it in the log.
  66. NadiaYes. The expected shape tells you where to look. Adding two zero rows to make the count match would be inventing data.
  67. ImranLet me sum up. One, the raw export stays untouched, and every change goes in a log. Two, check types first, because text numbers and flipped dates break formulas silently. Three, a clean table matches the shape you expect and reconciles to another source.
  68. NadiaAnd one nuance on the second point. Look before you delete. Count duplicates with COUNTIFS, read them, and only then remove them.
  69. ImranAnd next time?
  70. NadiaNext time we pivot the clean table, and the fresh sales in nine East stores start to stand out.
Finished reading?Mark it complete to fill in its stop on your course line.

Use it at work

Clean the supplier price file

Mixed SKU formats, currencies as text, duplicate rows.

Open in the Data Lab
Next stopPivot tables and summaries
Was this lesson helpful?
Questions and discussion

Loading the discussion…