Read the full writeup →

HTML report · a method test on simulated data

Ranking the Marathon Majors by course difficulty

How much time does a marathon course cost a runner compared with Berlin? Race results have no true answer to check a method against, so this project builds one. src/build_data.py simulates a top-100 elite field for the six World Marathon Majors from 2015 to 2024 and adds a fixed penalty to every finish on each course: Berlin 0, Chicago 12, Tokyo 28, London 35, New York 85 and Boston 105 seconds. Three ranking methods then try to recover those numbers.

The within-runner paired comparison recovers every planted penalty to within 7.3 seconds: Boston +101.0, New York +77.7, London +30.7, Tokyo +21.0, Chicago +11.4. The weather-normalized top-10 average is within 11.1 seconds. The grade-adjusted energy-cost model, which reads only the course elevation profiles, puts Boston 89 seconds faster than Berlin.

+101.0 s
Boston vs Berlin, paired method (planted: 105)
7.3 s
Largest paired miss, New York (77.7 vs 85)
−89.4 s
Boston vs Berlin, Minetti elevation model
42,567
Same-athlete course pairs within 18 months

1. The simulated field

The build script draws 400 men and 400 women with a fixed flat-course ability, sends 150 of each to every race (weighted toward the faster athletes), and gives each a finish time of ability plus course penalty plus a weather penalty plus normal noise with a 90-second standard deviation. The top 100 per gender are kept: 10,800 results over 54 editions, with 2020 left out. Race-day weather is a hand-entered table of temperature, humidity and wind per edition. Athlete names are combinations of real elite first and last names with a number attached; they are not real people's results.

2. The three methods

Grade-adjusted (Minetti)

Integrates the Minetti et al. (2002) energy cost of running over each course, treated as 40% climbing at its average up-grade, 40% descending and 20% flat, plus small penalties for grades over 1.5% and for many turns. It uses no race results. Boston's 136 m net descent makes it score faster than flat.

Within-runner paired

For every athlete who ran two different Majors within 18 months, divide each time by its race's weather multiplier and take the difference. Least squares fits one offset per course with Berlin fixed at zero; 2,000 bootstrap resamples give 95% intervals of about ±2 seconds.

Weather-normalized top-10 average

Each edition's top-10 average per gender, divided by a heat, humidity and wind multiplier after Maughan (2010) and El Helou et al. (2012), averaged over years and anchored to Berlin.

3. What the methods found

Box plots of simulated top-100 finish times per Major; Boston and New York sit highest, Berlin and Chicago lowest.
Figure 1. Simulated top-100 finish times per Major, 2015 to 2024, before any adjustment.
Forest plot of mean same-athlete time differences for the 15 course pairs with bootstrap intervals; pairs involving Boston and New York show the largest gaps.
Figure 2. Mean same-athlete delta for each of the 15 course pairs, with 95% bootstrap intervals; 42,567 pairs.

The paired estimate is below the planted value on every course, by 0.6 s (Chicago) to 7.3 s (New York). Only Chicago's interval contains its planted value, so the shortfall is a bias in the method on this data. The code doesn't test where it comes from.

Lines of weather-adjusted men's winning time by year for each Major; Boston and New York sit above the other four.
Figure 3. Weather-adjusted men's winning time by Major and year.

4. The composite index

Each method's offset becomes a factor on a 7,800-second (2:10) reference, and the three are weighted:

CDI = 0.15 × grade-adjusted + 0.50 × paired + 0.35 × weather-normalized
Bar chart of the composite Course Difficulty Index, Berlin at 1.000 and Boston and New York both near 1.009.
Figure 4. Course Difficulty Index, Berlin = 1.000. Intervals from the paired bootstrap scaled by its 0.50 weight.
RankCoursePlanted (s)Grade-adj. (s)Paired (s)Top-10 (s)CDICDI as s
1 Boston 105 −89.4 +101.0 +100.1 1.0092 +72.1
2 New York 85 +15.4 +77.7 +80.3 1.0089 +69.3
3 London 35 +3.6 +30.7 +29.7 1.0034 +26.3
4 Tokyo 28 −16.7 +21.0 +16.9 1.0018 +13.9
5 Chicago 12 −3.5 +11.4 +11.4 1.0012 +9.2
6 Berlin 0 0 0 0 1.0000 0

The composite puts Boston (1.0092) barely ahead of New York (1.0089). The grade-adjusted method's negative Boston value pulls Boston's composite down to 72 seconds over Berlin, against 101 from the paired fit alone.

Heatmap of difficulty factors by course and method; Boston's grade-adjusted cell, 0.989, is the lowest on the chart while its other two cells are the highest.
Figure 5. Difficulty factor by course and method. Boston's grade-adjusted cell is the one that disagrees.

5. Sensitivity

Three variations: drop the grade-adjusted method, restrict the paired fit to men's pairs with both times under 2:10, and replace Minetti with a simpler gain-and-loss elevation model. The first two keep the order. The third moves New York to 1.0166, above Boston at 1.0131.

Grouped bars of the index under four assumption sets; New York rises above Boston under the gain-and-loss elevation model.
Figure 6. The index under the baseline and three alternative assumptions.

6. Sydney and Cape Town

The two extra courses have elevation profiles but no simulated results, so only the grade-adjusted method scores them.

Ranking extended to Sydney and Cape Town as hatched bars scored by the elevation model only.
Figure 7. Ranking extended to Sydney and Cape Town (hatched, grade-adjusted only).

7. What this doesn't show

None of these numbers measures an actual course. The test is also easier than race data would be: the weather correction uses the same curve that generated the times, and the simulation has no pacing strategy, no fatigue tied to where the hills fall, and no difference in who races where. The simulation has no elevation term, so the gap between the grade-adjusted column and the planted one says nothing about whether the Minetti model is right about Boston itself. Course profiles are hand-entered, and the 40/40/20 split is a rough stand-in for a segment-by-segment integration.

Code, data and the notebook are at github.com/lyhjeremy/marathon-majors-course-difficulty. python src/build_data.py then python src/analysis.py reproduce every number here.