1: Gathering & Cleaning · 2: Cleaning & the Model · 3: Transformation · 4: Cube Operations · 5: Dashboard
Python and SQL. You need the file Practice 1 produced.
Practice 1 gave you one file with 72,437 rows:
build/consolidated/consolidated_long.csv
It looked finished. It is not. In Part 1 you will find real mistakes in it. In Part 2 you clean the descriptions, not the numbers. In Part 3 we name the pieces of the model you will build next week.
dwhdemo.vedara.net/docs/etl/ — extract.py, extract_multi.py, clean_consolidate.py, make_source_fixtures.py. Download them into a folder called etl/ next to your data.A key is one country, one indicator, one year. The same source should never give two values for one key. Count how often it does:
import csv
from collections import Counter
rows = list(csv.DictReader(open("build/consolidated/consolidated_long.csv")))
seen = Counter((r["source_id"], r["iso3"], r["indicator_cd"], r["year"])
for r in rows)
dupes = [k for k, n in seen.items() if n > 1]
print(len(dupes), "keys appear twice from the same source")
print(Counter(k[0] for k in dupes))You should find 909, all from Eurostat.
Print both values for one of them:
k = dupes[0]
for r in rows:
if (r["source_id"], r["iso3"], r["indicator_cd"], r["year"]) == k:
print(r["value"], r["unit"], r["flag"])For Albania in 2000 you get 2120.0 and 8.2. One is euros per person, the other is a percentage change. They are two different measures that our cleaner put under one name.
Now look at the raw file staging_multi/s4_eurostat.tsv. The first column holds four values joined by commas. The second one is the unit:
A,CLV20_EUR_HAB,B1GQ,AL <- euros per person A,CLV_PCH_PRE_HAB,B1GQ,AL <- percent change
The Practice 1 cleaner only read the country code and ignored the unit. Fix it yourself: in clean_s4_eurostat, skip any row whose unit is not CLV20_EUR_HAB. Re-run and check the 909 are gone; Eurostat should drop from 1,854 rows to 923, and the total from 72,437 to 71,506.
A corrected copy is published as docs/etl/clean_consolidate_fixed.py — compare it with yours after you have tried, and use it from Practice 3 on.
Check each indicator against what it can physically be:
for r in rows:
v = float(r["value"])
if r["indicator_cd"] == "UNEMPLOYMENT" and not 0 <= v <= 100:
print("impossible unemployment:", r)
if r["indicator_cd"] in ("GDP_CUR_USD", "POPULATION") and v < 0:
print("negative:", r)You should find none. That is a useful result: it means the number parsing in Practice 1 worked.
Print the ten largest inflation values:
infl = [r for r in rows if r["indicator_cd"] == "INFLATION_CPI"]
infl.sort(key=lambda r: -float(r["value"]))
for r in infl[:10]:
print(r["iso3"], r["year"], float(r["value"]))The top one is Venezuela in 2018 at 65,374%. Zimbabwe 2024 is 736%. Your first instinct is that these are broken. They are not — that is what hyperinflation looks like.
So far you cleaned numbers. The names, codes and groupings that go with the numbers are also dirty, and nobody has looked at them yet.
Each source writes country names its own way:
| Code | World Bank | Excel workbook | SQL agency |
|---|---|---|---|
| USA | United States | United States of America | USA |
| GBR | United Kingdom | United Kingdom of Great Britain | GBR |
| DEU | Germany | Germany (Fed. Rep.) | DEU |
| KOR | Korea, Rep. | Korea, Rep. of | KOR |
Pick one name per country and say why. This is the same kind of decision as choosing between two numbers, except nobody can check you with arithmetic. Do not join on the name — join on the code, which all three agree on.
Every fact should belong to a country you have a description for. Check:
import json
# the country list Practice 1 downloaded
countries = json.load(open("staging/countries.json"))
known = {c["id"] for c in countries}
orphans = {r["iso3"] for r in rows} - known
print(len(orphans), "codes have facts but no description")
print(sorted(orphans))You will find 25 codes carrying 2,466 facts, all from the IMF. They include TWN (Taiwan), UVK (Kosovo) and WBG (West Bank and Gaza) — places the World Bank list does not cover.
You have to choose, and either answer is defensible:
| Option | What it costs |
|---|---|
| Drop the facts | You lose 2,466 real observations and Taiwan disappears from your warehouse. |
| Add the missing rows by hand | You are now a publisher of country data, and must keep it up to date. |
Whichever you pick, write it down. Someone will ask why Taiwan is missing.
Nothing yet records where a number came from. Build a small table by hand with one row per source: its id, its name, and the format you read it from (JSON, CSV, TSV, HTML, Excel, SQL). Seven rows. You will need it next week.
The entity list holds 217 countries and 78 aggregates — things like “World”, “Euro area” and “High income”. They sit in the same list because the World Bank publishes them together.
Add up the GDP of everything in the list for 2024, then compare it with the row called World. What do you get, and why?
Next week you build the database. These are the four words you need first. No code this week — just be able to point at each one in your own data.
A measurement. “Germany, GDP, 2024, 4.69 trillion dollars.” Facts are numbers you add up, average, or rank. You have about 57,000 of them.
A description you filter or group by. “Germany is in Europe and is a high-income country.” Dimensions are text, they are few, and they change rarely. Your four are: country, indicator, year, source.
The test: if you would put it after WHERE or GROUP BY, it is a dimension. If you would put it inside SUM(), it is a fact.
What one row means. Ours is one country, one indicator, one year. Decide this before anything else: it fixes what questions you can answer. If you had kept months, the grain would be finer and the table much bigger.
A plain number standing in for a real-world code. Instead of writing "DEU" in 57,000 fact rows, you write 57, and keep one row elsewhere saying 57 means Germany. It is smaller, faster to join, and when a country is renamed you change one row instead of thousands.
Take the seven columns of your clean file and put each one in a box: fact, dimension, or neither. Then write one sentence saying what the grain of your file is. Bring both next week.
Transformation. Building the star schema from the pieces you just named: the dimension tables, the fact table, and the queries they make easy.