ToolsPower BI · Power Query · DAX
Data300K+ athlete-event records, 1896–2022
ModelStar schema, 4 tables
RoleEnd-to-end (solo)
View code & .pbix on GitHub

An interactive, three-page Power BI dashboard built on 120+ years of Olympic data. The goal wasn’t only to visualise — it was to build a clean end-to-end pipeline (ingest → clean → model → measure → present → drill through) and to debug the real problems that surfaced along the way.

01 Demo

Interactive drill-through — pick a season, then drill into any country’s medal story.

02 Three questions

  • How has gender equality at the Olympics evolved over time?
  • Which countries dominate the medal table — and how does that shift between Summer and Winter Games?
  • How do athlete body types vary across sports?

03 How it was built

Data cleaning (Power Query)

  • Mixed date formats in born — some full dates, some year-only. Rather than forcing a date type (which errors the whole column), I extracted the trailing 4-digit birth year into a clean integer.
  • Text in height/weight — converted types in Power Query, then Replace Errors → null, the standard two-step for messy numeric columns. Bad values become null and drop out at the measure level.
  • Nulls in medal → “None” so medal status is explicit and countable; season derived from edition to power the slicer. No blind dedup — uniqueness is athlete_id + result_id.

Data modeling

A star schema with the results fact table at the centre, with the athlete-bio and event-results dimensions joining in. The medal-tally table is intentionally left unrelated — it sits at a different grain (country × edition) than the fact table (athlete × event), so it’s kept purely for validation.

Star-schema model in Power BI.
Star-schema model in Power BI.

DAX measures

The core measures use FILTER so filter contexts stack correctly (see the debugging notes), plus a dynamic drill-through title built with SELECTEDVALUE:

Total Medals =
COUNTROWS(FILTER('Olympic_Athlete_Event_Results',
    'Olympic_Athlete_Event_Results'[medal] <> "None"))

Female % =
DIVIDE(
    CALCULATE([Total Athletes], 'Olympic_Athlete_Bio'[sex] = "Female"),
    [Total Athletes])

Detail Title =
"Medal Breakdown — " &
SELECTEDVALUE('Olympic_Athlete_Event_Results'[country_noc], "All Countries")

04 The dashboard

Page 1 — Overview

KPI cards, a line chart of female participation over time (the core narrative), a Top-15 countries bar chart, and a season slicer — all cross-filtered. Switching to Winter reshuffles the leaderboard entirely.

Overview — Summer Games.
Overview — Summer Games.
Same page, Winter season — a completely different leaderboard.
Same page, Winter season — a completely different leaderboard.

Page 2 — Athlete Physique

A scatter of average height vs. weight by sport, revealing a clear positive correlation and distinct clusters — tall, heavy sports like rowing and basketball vs. compact ones like gymnastics.

Height vs. weight by sport.
Height vs. weight by sport.

Page 3 — Country Detail (drill-through)

Right-click any country → Drill through → a detail page of that country’s medals by sport and its gold/silver/bronze breakdown (coloured to match the medals). A dynamic title built with SELECTEDVALUE names the selected country, and the season context carries through.

Medals by sport for the selected country.
Medals by sport for the selected country.
Gold / silver / bronze breakdown.
Gold / silver / bronze breakdown.
The drill-through respects the Winter season context.
The drill-through respects the Winter season context.

05 Debugging notes (the interesting part)

Several real problems surfaced — diagnosed and fixed:

01

Gold Medals equalled Total Medals

The original measure wrapped [Total Medals] in CALCULATE with a medal filter, but the inner CALCULATE overrode the outer context. I verified with a DAX query, then rewrote both measures using FILTER so the filters stack.

02

Scatter plot collapsed to a single point

sport (fact) couldn’t slice height/weight (bio) because the cross-filter direction was single. A SUMMARIZECOLUMNS query confirmed identical averages everywhere; setting the relationship to bi-directional fixed it.

03

A bad replace overwrote the whole medal column

An early medal replacement wrote “None” over everything. Caught it by listing distinct values via a DAX query, reverted the step, and used a safer replacement.

06 Key insights

  • Female participation rose from under 2% in the early Games to roughly 45–50% in recent editions.
  • Medal dominance concentrates in a handful of nations, and the leaderboard reshuffles entirely between Summer (USA, GBR, URS) and Winter (CAN, NOR, GER).
  • Athlete physique clusters tightly by sport — a clean, interpretable signal.

The full write-up, DAX, and the .pbix file are on GitHub.

View on GitHub