Third in an occasional series porting specific methods from Tony Ladson’s blog to Python — his explanation and the underlying method are his; I’m just providing a translation for readers who know Python better than R. Sources: Visualising Hydrologic Data and the concrete worked example, Better line graphs for hydrologic data, R in his gist.

Six creeks, six unrelated peak-flow-vs-duration lines on one chart. Ladson’s example (invented creek names, made-up numbers — a teaching example, not a real gauge record) makes two points at once.

Point one: sequential palettes are for ordered data

blues = plt.cm.Blues(np.linspace(0.35, 0.95, len(creeks)))  # mimics brewer.pal(8,'Blues')[3:8]

fig, ax = plt.subplots(figsize=(7, 5))
for (name, flows), color in zip(creeks.items(), blues):
    ax.plot(durations, flows, marker='o', color=color, label=name)
ax.legend(fontsize=8)
Line graph of six unrelated creeks using a sequential Blues colour palette, showing poor contrast between similarly-valued lines
Sequential palette on unordered categories. Rocky, Reedy and Waterhole Creeks — the three lines closest together in value, exactly where the reader most needs contrast — are also the three closest together in colour.

A sequential palette encodes order — light to dark implies low to high. These six creeks have no natural order; picking a sequential palette to tell them apart imports a ranking that isn’t there, and (worse, practically) the mid-range blues used for the middle of the palette are genuinely hard to tell apart at a glance.

Point two: a legend makes the reader do the work

The fix is two changes at once: a qualitative palette built for categorical data (Dark2 — matplotlib ships the identical ColorBrewer palette Ladson uses in R), and labelling each line directly instead of routing identification through a legend the reader has to look back and forth to.

dark2 = plt.cm.Dark2(np.linspace(0, 1, len(creeks)))

fig, ax = plt.subplots(figsize=(8, 5.5))
for (name, flows), color in zip(creeks.items(), dark2):
    ax.plot(durations, flows, marker='o', color=color)
    ax.annotate(name, xy=(durations[0], flows[0]), xytext=(-8, 0),
                textcoords='offset points', ha='right', va='center', fontsize=8, color=color)
    ax.annotate(name, xy=(durations[-1], flows[-1]), xytext=(8, 0),
                textcoords='offset points', ha='left', va='center', fontsize=8, color=color)
for spine in ['top', 'right']:
    ax.spines[spine].set_visible(False)
The same six creeks using a qualitative Dark2 colour palette with direct line labels instead of a legend
Qualitative palette, labelled directly at both ends, minimal frame. No legend to decode; each creek reads as itself.

Not perfect — where lines start close together (Reedy, Rocky and Waterhole Creeks all begin within about 150 units of each other), the left-hand labels still sit tight against one another. That’s an honest limitation of direct labelling when the data itself is genuinely bunched, not something the technique fixes for free — it shows up in Ladson’s original R version too, for the same reason.


Companion notebook: notebooks/10_better_line_graphs/

Source: Ladson, A.R. (2018). Better line graphs for hydrologic data; companion page Visualising Hydrologic Data. R source: gist.github.com/TonyLadson/732e3dbcb8aeaf76fd25c04f6ff246b7.

Updated: