Learn

How to measure a rain forecast

Count what it got right and what it got wrong, against a truth it never saw, on days it was not tuned on.

Hits, misses and false alarms

Pick a rain threshold, say 1 mm/h. Lay the forecast over what really happened. Every place and time falls in one of four boxes:

  • hit: rain forecast, and it rained;
  • miss: it rained, but no rain was forecast;
  • false alarm: rain forecast, and it stayed dry;
  • correct dry: no rain forecast, none fell.

Correct dry cases are left out of most rain scores on purpose: on most days most of Europe is dry, and a forecast of “no rain anywhere” would look excellent.

The scores built from them

ScoreFormulaReads as
Hit score (CSI)hits / (hits + misses + false alarms)Share of the rain events caught, counting both kinds of error. 1 is perfect.
Detection (POD)hits / (hits + misses)Share of the rain that fell which was forecast. Ignores false alarms.
Success ratiohits / (hits + false alarms)Share of the rain forecast that fell. Ignores misses.
F-score2 × hits / (2 × hits + misses + false alarms)Like the hit score, a little kinder; used by the Weather Index to rank providers at airports.
Bias(hits + false alarms) / (hits + misses)How often rain was forecast against how often it fell. 1 is right; below 1 too little rain.
Near-miss score (FSS)compares the share of rainy squares within a neighbourhoodRain put a few km off still counts. Fairer to sharp forecasts.

Read the hit score and the bias together. A forecast can raise its detection by spreading rain everywhere (more hits, more false alarms), or look safe by forecasting rain rarely. A bias far from 1 tells you which trick is at work.

One picture for all of them

The performance diagram puts these scores on one chart. Across: the success ratio. Up: detection. A perfect forecast sits in the top right corner. The curved lines are equal hit scores, the dashed lines through the origin equal bias. Since hit score and bias fix the other two, any published pair of them can be placed on it.

# from CSI and bias to detection and success ratio
pod = csi * (1 + bias) / (1 + csi)
sr = pod / bias
plt.plot(sr, pod, marker="o")  # one point per time ahead
Performance diagram, success ratio across and detection up, with curves of equal hit score and dashed lines of equal bias. Nimbo and rain held still run along the bias 1 line toward the origin as time ahead grows, Nimbo higher. pySTEPS runs along the bias 2 line: high detection, low success ratio. Performance diagram, success ratio across and detection up, with curves of equal hit score and dashed lines of equal bias. Nimbo and rain held still run along the bias 1 line toward the origin as time ahead grows, Nimbo higher. pySTEPS runs along the bias 2 line: high detection, low success ratio.
Figure 1. Rain of 1 mm/h or more against radar, on the scoreboard’s 39 rainy days; one point per time ahead, from 15 minutes to 3 hours. pySTEPS here runs on the radar’s reflectivity turned into rain with the classic Marshall–Palmer relation (see radar coverage).

The three forecasts slide toward the bottom left as they look further ahead. Nimbo and the still radar image stay on the bias 1 line: they forecast rain about as often as it falls. pySTEPS sits on the bias 2 line: it forecasts rain of 1 mm/h twice as often as it falls, so it catches most of the rain (detection 0.82 at 15 minutes) at the price of about three false alarms for every two hits. Its hit score alone would not tell you that.

Near misses: the neighbourhood score

Rain forecast 4 km east of where it fell counts twice against a forecast square by square: once as a miss, once as a false alarm. A forecast that blurs its rain into a wide grey area suffers less from this than a sharp one, which is not what users want. The fractions skill score (FSS) answers it: in a window around each square, compare the share of rainy squares forecast with the share observed.

FSS = 1 − Σ(F − O)² / (ΣF² + ΣO²), where F and O are the forecast and observed shares in each window. With a window of one square, it is exactly the F-score. A common rule of thumb calls a forecast useful at the scale where FSS passes 0.5 + f/2, with f the share of the map that is rainy.

def fss(fcst, obs, thr, size):
    f = uniform_filter((fcst >= thr) * 1.0, size)
    o = uniform_filter((obs >= thr) * 1.0, size)
    return 1 - ((f - o)**2).sum() / ((f**2).sum() + (o**2).sum())
Line chart of the near-miss score against the neighbourhood width, 2 to 50 km, one line per time ahead. At 15 minutes the score is 0.61 at 2 km and 0.94 at 50 km. At 1 hour it passes the useful line at about 6 km, at 2 hours at about 26 km, and at 4 hours it stays under it, reaching 0.36 at 50 km. Line chart of the near-miss score against the neighbourhood width, 2 to 50 km, one line per time ahead. At 15 minutes the score is 0.61 at 2 km and 0.94 at 50 km. At 1 hour it passes the useful line at about 6 km, at 2 hours at about 26 km, and at 4 hours it stays under it, reaching 0.36 at 50 km.
Figure 2. Near-miss score of a plain extrapolation nowcast (pySTEPS) for rain of 1 mm/h or more, on 40 rainy days in Spain and Portugal, against the width of the window. At 2 km (one square) it is the F-score. Dashed: the “useful” level 0.5 + f/2.

Read across the dashed line: one hour ahead, this forecast is useful at about 6 km; two hours ahead, at about 26 km; four hours ahead, not even at 50 km. The scale at which a forecast can be trusted grows with time ahead, which is a more honest summary than any single score. Nimbo’s scoreboard reports the near-miss score at 10 km.

Score against a truth the forecast never saw

There are three common truths, and they are not equal:

  • Radar covers every square, but it is also the forecast’s input. A forecast scored on the radar it was built from is partly marking its own homework.
  • Rain gauges measure the water that actually fell, hour by hour, at thousands of points. No radar nowcast uses them as input, which makes them the fairest check.
  • Airport reports say whether it is raining every half hour or so at hundreds of airports. Coarse, but independent and worldwide.

Nimbo’s scoreboard uses all three: radar, 5,369 gauges in 10 national networks, and 757 airports.

Every day is different

A score pooled over many days hides how much it moves from one day to the next. Figure 3 takes the pooled comparison apart: one dot per rainy day, Nimbo’s hit score minus pySTEPS’s, one hour ahead, on each truth.

for truth in ["radar", "gauges"]:
    days = pd.DataFrame(bench["by_day"][truth])
    diff = days["product"] - days["sprog_raw"]
    print(truth, (diff > 0).sum(), "of", len(diff))
Dot chart of the daily hit score difference, Nimbo minus pySTEPS. Against radar, all 39 days are positive, between +0.03 and +0.13. Against rain gauges the dots spread from −0.07 to +0.26, and Nimbo is ahead on 30 of 39 days. Dot chart of the daily hit score difference, Nimbo minus pySTEPS. Against radar, all 39 days are positive, between +0.03 and +0.13. Against rain gauges the dots spread from −0.07 to +0.26, and Nimbo is ahead on 30 of 39 days.
Figure 3. One dot per rainy day of the scoreboard: hit score for any rain one hour ahead, Nimbo minus pySTEPS, against radar (whole map) and against rain gauges (rain in the hour). bench is the public file of figure 3 of “What is nowcasting?”.

Against radar the gap is steady. Against gauges, a far smaller sample of points, it swings from day to day, and on 9 of 39 days pySTEPS did better. This is why a difference between two forecasts needs an interval: resample the days many times, recompute the gap each time, and report the range it moves in. The scoreboard does this with whole blocks of neighbouring days, since days a day or two apart often share the same weather system.

The traps

  • Days the model was trained or tuned on. A model scored on days it learned from will look better than it is. Only days set aside before training count.
  • Different events for different forecasts. Two forecasts must be scored on exactly the same places and times, or the comparison measures the weather, not the forecasts.
  • Dry days. Scores on days with little rain swing wildly. Choose rainy days, and say how they were chosen.
  • One number. A forecast can be good one hour ahead and poor at three, good for drizzle and poor for downpours. Look at skill against time ahead and by rain threshold.
  • No error bars. With a few dozen days, small differences are noise. Resample the days to see how much a score could move.
  • Blurring to win. Smoothing a forecast can raise some scores while making the map useless. A gain should hold on independent truths, not only on the score it was tuned for.

A worked example

On the scoreboard’s rainy days, Nimbo’s hit score for a wet hour (1 mm or more) in rain gauges is about 0.47 one hour ahead, 0.38 two hours ahead and 0.31 three hours ahead. pySTEPS, scored on the same gauges and hours, reaches about 0.40, 0.31 and 0.25. Both fall with time; the gap between them is the measure that matters, and it comes with an interval from resampling the days.

Next: Radar coverage in Europe