simulate_series <- function(phi, sp, m0 = 3, n = 6000, seed = 303) {
set.seed(seed); x <- numeric(n); x[1] <- m0
for (t in 2:n) x[t] <- m0 + phi * (x[t - 1] - m0) + rnorm(1, 0, sp)
x
}
x <- simulate_series(phi = 0.85, sp = 0.5) # strongly autocorrelated seriesForecast skill and the baseline
The CRPS post ended on a loose thread: a score of 0.66 is a number with no meaning until you compare it to something. That comparison is the skill score, and it turns a raw score into a statement of the form “my forecast beats a simple baseline by this much”. The catch, and the whole point of this post, is that the baseline is a choice, and the same forecast can look skilful or worthless depending on which baseline you pick. This is not a technicality; it is the most common way an ecological forecast is oversold.
Skill is a comparison, not a property
A skill score rescales your score against a reference forecast: one minus your score divided by the reference’s score. Positive means you beat the reference; zero means you tied it; negative means the reference was better. Two references are standard in ecology (Dietze et al. 2018; Harris et al. 2018). Climatology predicts next year from the long-run distribution: next year looks like a random past year. Persistence predicts next year from this year: next year looks like the last observation. They are easy to beat in different situations, and that is where the trouble starts.
Take a population whose log-abundance follows an order-one autoregressive process: each year reverts partway toward a long-run mean, with environmental noise on top. The strength of that year-to-year memory is the autocorrelation, and it controls which baseline is hard to beat. When memory is strong, this year is a great guess for next year, so persistence is tough and climatology is weak. When memory is weak, the reverse holds.
The same forecast, opposite verdicts
Now fix one forecast and score it against both baselines. The forecast is a reasonable but imperfect model: it uses the autocorrelation, but underestimates it (it assumes weaker memory than the series really has). Its CRPS, and the two baselines’ CRPS, are computed on the same series.
score_forecasts <- function(phi_true, sp = 0.5, phi_model = 0.4, m0 = 3) {
x <- simulate_series(phi_true, sp, m0); n <- length(x); idx <- 2:n
smarg <- sp / sqrt(1 - phi_true^2) # stationary marginal sd
mu_m <- m0 + phi_model * (x[idx - 1] - m0) # the model (under-damped)
s_m <- smarg * sqrt(1 - phi_model^2)
sdiff <- sp * sqrt(2 / (1 + phi_true)) # sd of the year-to-year change
y <- x[idx]
c(model = mean(crps_norm(mu_m, s_m, y)),
clim = mean(crps_norm(rep(m0, length(idx)), rep(smarg, length(idx)), y)), # climatology
pers = mean(crps_norm(x[idx - 1], rep(sdiff, length(idx)), y))) # persistence
}
skill <- function(s_fore, s_ref) 1 - s_fore / s_ref
S <- score_forecasts(phi_true = 0.85)
c(S, skill_vs_clim = skill(S["model"], S["clim"]),
skill_vs_pers = skill(S["model"], S["pers"])) model clim pers skill_vs_clim.model
0.3757964 0.5276830 0.2927406 0.2878368
skill_vs_pers.model
-0.2837181
Read the two skill numbers. Against climatology the model has skill +0.288: clearly positive, a useful forecast. Against persistence the skill is -0.284: negative, worse than simply repeating last year’s value. This is one forecast, one series, one CRPS. The verdict flipped because the reference changed. A paper reporting only the climatology comparison would call this model skilful; a reviewer who asked for persistence would call it a failure. Both are looking at the same forecast.
The mechanism is not subtle once you sweep the autocorrelation. Climatology is hard to beat when there is little memory to exploit; persistence is hard to beat when there is a lot. The model sits in between, so its skill against each baseline crosses zero at a different point, and there is only a window of autocorrelation where it beats both.
phis <- seq(0.05, 0.95, by = 0.05)
sk <- t(sapply(phis, function(ph) {
s <- score_forecasts(ph)
c(vs_clim = skill(s["model"], s["clim"]), vs_pers = skill(s["model"], s["pers"]))
}))
head(round(sk, 3)) vs_clim.model vs_pers.model
[1,] -0.063 0.230
[2,] -0.043 0.224
[3,] -0.023 0.217
[4,] -0.002 0.209
[5,] 0.019 0.200
[6,] 0.039 0.190
The climatology curve is negative until about 0.2 and strongly positive after; the persistence curve is positive until about 0.7 and strongly negative after. At the dashed line (0.85) they land on opposite sides of zero, which is the split we just computed.
The honest limit
Skill is not accuracy. It is a ranking relative to a reference, and a good skill score against a weak reference says more about the reference than about your model. The practical rules follow directly. Report skill against a baseline that is genuinely hard for your system, not one chosen because you beat it: for a strongly autocorrelated population that means persistence, not climatology. Report the baseline’s own score too, so a reader can see whether you cleared a low bar or a high one. And when you compare two forecasts, hold the baseline fixed; a change of reference can reverse the ordering without either forecast changing at all.
Where to go next
The window where a forecast beats every reasonable baseline is real, but it can still hide problems: a forecast can post a good score while being badly calibrated, or the score difference between two models can be noise. The last post in this cluster is the checking step, three diagnostics that catch the ways a forecast evaluation misleads even when the headline number looks fine.
References
Dietze MC, Fox A, Beck-Johnson LM, et al. 2018. Proceedings of the National Academy of Sciences 115(7):1424-1432 (10.1073/pnas.1710231115)
Harris DJ, Taylor SD, White EP 2018. PeerJ 6:e4278 (10.7717/peerj.4278)
Dietze MC 2017. Ecological Forecasting. Princeton University Press (ISBN 978-1-4008-8545-9)