0
# Continuous Scores
1
2
Metrics for evaluating single-valued continuous forecasts including error measures, bias calculations, efficiency indices, and correlation coefficients. All functions support flexible dimension handling, optional weighting, and angular data processing.
3
4
## Capabilities
5
6
### Mean Squared Error (MSE)
7
8
Calculates the mean squared error between forecast and observations, the most fundamental error metric in forecast verification.
9
10
```python { .api }
11
def mse(
12
fcst: FlexibleArrayType,
13
obs: FlexibleArrayType,
14
*,
15
reduce_dims: Optional[FlexibleDimensionTypes] = None,
16
preserve_dims: Optional[FlexibleDimensionTypes] = None,
17
weights: Optional[xr.DataArray] = None,
18
is_angular: Optional[bool] = False,
19
) -> XarrayLike:
20
"""
21
Calculate Mean Squared Error.
22
23
Args:
24
fcst: Forecast data
25
obs: Observation data
26
reduce_dims: Dimensions to reduce (collapse)
27
preserve_dims: Dimensions to preserve (all others reduced)
28
weights: Optional weights for weighted MSE calculation
29
is_angular: Handle circular data (e.g. wind direction)
30
31
Returns:
32
Mean squared error values
33
34
Formula:
35
MSE = (1/n) * Σ(forecast_i - observed_i)²
36
"""
37
```
38
39
**Usage Example:**
40
41
```python
42
from scores.continuous import mse
43
import xarray as xr
44
45
# Basic MSE calculation
46
mse_value = mse(forecast, observations)
47
48
# MSE with dimension reduction
49
temporal_mse = mse(forecast, observations, reduce_dims="time")
50
51
# Weighted MSE (e.g. area weighting)
52
weighted_mse = mse(forecast, observations, weights=area_weights)
53
54
# Angular MSE for directional data
55
wind_dir_mse = mse(forecast_dir, observed_dir, is_angular=True)
56
```
57
58
### Root Mean Squared Error (RMSE)
59
60
Square root of MSE, providing error values in the same units as the original data.
61
62
```python { .api }
63
def rmse(
64
fcst: FlexibleArrayType,
65
obs: FlexibleArrayType,
66
*,
67
reduce_dims: Optional[FlexibleDimensionTypes] = None,
68
preserve_dims: Optional[FlexibleDimensionTypes] = None,
69
weights: Optional[xr.DataArray] = None,
70
is_angular: Optional[bool] = False,
71
) -> FlexibleArrayType:
72
"""
73
Calculate Root Mean Squared Error.
74
75
Args:
76
fcst: Forecast data
77
obs: Observation data
78
reduce_dims: Dimensions to reduce
79
preserve_dims: Dimensions to preserve
80
weights: Optional weights
81
is_angular: Handle circular data
82
83
Returns:
84
Root mean squared error values
85
86
Formula:
87
RMSE = √[(1/n) * Σ(forecast_i - observed_i)²]
88
"""
89
```
90
91
### Mean Absolute Error (MAE)
92
93
Mean absolute deviation between forecast and observations, less sensitive to outliers than MSE.
94
95
```python { .api }
96
def mae(
97
fcst: FlexibleArrayType,
98
obs: FlexibleArrayType,
99
*,
100
reduce_dims: Optional[FlexibleDimensionTypes] = None,
101
preserve_dims: Optional[FlexibleDimensionTypes] = None,
102
weights: Optional[xr.DataArray] = None,
103
is_angular: Optional[bool] = False,
104
) -> FlexibleArrayType:
105
"""
106
Calculate Mean Absolute Error.
107
108
Args:
109
fcst: Forecast data
110
obs: Observation data
111
reduce_dims: Dimensions to reduce
112
preserve_dims: Dimensions to preserve
113
weights: Optional weights
114
is_angular: Handle circular data
115
116
Returns:
117
Mean absolute error values
118
119
Formula:
120
MAE = (1/n) * Σ|forecast_i - observed_i|
121
"""
122
```
123
124
### Bias Metrics
125
126
#### Additive Bias (Mean Error)
127
128
Average difference between forecast and observations, indicating systematic over- or under-prediction.
129
130
```python { .api }
131
def additive_bias(
132
fcst: XarrayLike,
133
obs: XarrayLike,
134
*,
135
reduce_dims: Optional[FlexibleDimensionTypes] = None,
136
preserve_dims: Optional[FlexibleDimensionTypes] = None,
137
weights: Optional[XarrayLike] = None,
138
is_angular: Optional[bool] = False,
139
) -> XarrayLike:
140
"""
141
Calculate additive bias (mean error).
142
143
Args:
144
fcst: Forecast data
145
obs: Observation data
146
reduce_dims: Dimensions to reduce
147
preserve_dims: Dimensions to preserve
148
weights: Optional weights
149
is_angular: Handle circular data
150
151
Returns:
152
Additive bias values
153
154
Formula:
155
Bias = (1/n) * Σ(forecast_i - observed_i)
156
"""
157
158
# Alias for additive_bias
159
def mean_error(
160
fcst: XarrayLike,
161
obs: XarrayLike,
162
*,
163
reduce_dims: Optional[FlexibleDimensionTypes] = None,
164
preserve_dims: Optional[FlexibleDimensionTypes] = None,
165
weights: Optional[XarrayLike] = None,
166
is_angular: Optional[bool] = False,
167
) -> XarrayLike:
168
"""Alias for additive_bias."""
169
```
170
171
#### Multiplicative Bias
172
173
Ratio-based bias measure useful for positive-valued variables.
174
175
```python { .api }
176
def multiplicative_bias(
177
fcst: XarrayLike,
178
obs: XarrayLike,
179
*,
180
reduce_dims: Optional[FlexibleDimensionTypes] = None,
181
preserve_dims: Optional[FlexibleDimensionTypes] = None,
182
weights: Optional[XarrayLike] = None,
183
) -> XarrayLike:
184
"""
185
Calculate multiplicative bias.
186
187
Args:
188
fcst: Forecast data (positive values)
189
obs: Observation data (positive values)
190
reduce_dims: Dimensions to reduce
191
preserve_dims: Dimensions to preserve
192
weights: Optional weights
193
194
Returns:
195
Multiplicative bias values
196
197
Formula:
198
Multiplicative Bias = mean(forecast) / mean(observation)
199
200
Notes:
201
- Values > 1 indicate forecast over-prediction
202
- Values < 1 indicate forecast under-prediction
203
- Perfect forecast has multiplicative bias = 1
204
"""
205
```
206
207
#### Percent Bias (PBIAS)
208
209
Percentage-based bias calculation commonly used in hydrology.
210
211
```python { .api }
212
def pbias(
213
fcst: XarrayLike,
214
obs: XarrayLike,
215
*,
216
reduce_dims: Optional[FlexibleDimensionTypes] = None,
217
preserve_dims: Optional[FlexibleDimensionTypes] = None,
218
weights: Optional[XarrayLike] = None,
219
) -> XarrayLike:
220
"""
221
Calculate percent bias.
222
223
Args:
224
fcst: Forecast data
225
obs: Observation data
226
reduce_dims: Dimensions to reduce
227
preserve_dims: Dimensions to preserve
228
weights: Optional weights
229
230
Returns:
231
Percent bias values
232
233
Formula:
234
PBIAS = 100 * Σ(forecast_i - observed_i) / Σ(observed_i)
235
236
Notes:
237
- Positive values indicate model overestimation bias
238
- Negative values indicate model underestimation bias
239
- Perfect forecast has PBIAS = 0
240
"""
241
```
242
243
### Efficiency Indices
244
245
#### Kling-Gupta Efficiency (KGE)
246
247
Decomposed performance metric combining correlation, variability ratio, and bias ratio.
248
249
```python { .api }
250
def kge(
251
fcst: xr.DataArray,
252
obs: xr.DataArray,
253
*,
254
reduce_dims: Optional[FlexibleDimensionTypes] = None,
255
preserve_dims: Optional[FlexibleDimensionTypes] = None,
256
scaling_factors: Optional[Union[list[float], np.ndarray]] = None,
257
include_components: Optional[bool] = False,
258
) -> XarrayLike:
259
"""
260
Calculate Kling-Gupta Efficiency.
261
262
Args:
263
fcst: Forecast data
264
obs: Observation data
265
reduce_dims: Dimensions to reduce
266
preserve_dims: Dimensions to preserve
267
scaling_factors: Component scaling factors [ρ, α, β]
268
include_components: Return individual components
269
270
Returns:
271
KGE efficiency values (and components if requested)
272
273
Formula:
274
KGE = 1 - √[(s_ρ*(ρ-1))² + (s_α*(α-1))² + (s_β*(β-1))²]
275
276
Components:
277
- ρ: Correlation coefficient
278
- α: Variability ratio (σ_forecast/σ_observation)
279
- β: Bias ratio (μ_forecast/μ_observation)
280
- s_ρ, s_α, s_β: Scaling factors (default: [1,1,1])
281
282
Notes:
283
- Perfect forecast has KGE = 1
284
- Default benchmark (observation mean) has KGE ≈ -0.41
285
"""
286
```
287
288
#### Nash-Sutcliffe Efficiency (NSE)
289
290
Normalized measure of model performance relative to observation mean.
291
292
```python { .api }
293
def nse(
294
fcst: XarrayLike,
295
obs: XarrayLike,
296
*,
297
reduce_dims: Optional[FlexibleDimensionTypes] = None,
298
preserve_dims: Optional[FlexibleDimensionTypes] = None,
299
weights: Optional[XarrayLike] = None,
300
is_angular: Optional[bool] = False,
301
) -> XarrayLike:
302
"""
303
Calculate Nash-Sutcliffe Efficiency.
304
305
Args:
306
fcst: Forecast data
307
obs: Observation data
308
reduce_dims: Dimensions to reduce
309
preserve_dims: Dimensions to preserve
310
weights: Optional weights
311
is_angular: Handle circular data
312
313
Returns:
314
Nash-Sutcliffe efficiency values
315
316
Formula:
317
NSE = 1 - Σ(observed_i - forecast_i)² / Σ(observed_i - observed_mean)²
318
319
Notes:
320
- Perfect forecast has NSE = 1
321
- Forecast as good as observation mean has NSE = 0
322
- NSE can be negative (worse than observation mean)
323
- Range: (-∞, 1]
324
"""
325
```
326
327
### Correlation Coefficients
328
329
#### Pearson Correlation
330
331
Linear correlation coefficient measuring strength of linear relationship.
332
333
```python { .api }
334
def pearsonr(
335
fcst: XarrayLike,
336
obs: XarrayLike,
337
*,
338
reduce_dims: Optional[FlexibleDimensionTypes] = None,
339
preserve_dims: Optional[FlexibleDimensionTypes] = None,
340
) -> XarrayLike:
341
"""
342
Calculate Pearson correlation coefficient.
343
344
Args:
345
fcst: Forecast data
346
obs: Observation data
347
reduce_dims: Dimensions to reduce
348
preserve_dims: Dimensions to preserve
349
350
Returns:
351
Pearson correlation coefficients
352
353
Notes:
354
- Range: [-1, 1]
355
- Measures linear association strength
356
- Perfect positive correlation: r = 1
357
- Perfect negative correlation: r = -1
358
- No linear relationship: r = 0
359
- This function doesn't support weights
360
"""
361
```
362
363
#### Spearman Rank Correlation
364
365
Non-parametric correlation based on rank ordering.
366
367
```python { .api }
368
def spearmanr(
369
fcst: XarrayLike,
370
obs: XarrayLike,
371
*,
372
reduce_dims: Optional[FlexibleDimensionTypes] = None,
373
preserve_dims: Optional[FlexibleDimensionTypes] = None,
374
) -> XarrayLike:
375
"""
376
Calculate Spearman rank correlation coefficient.
377
378
Args:
379
fcst: Forecast data
380
obs: Observation data
381
reduce_dims: Dimensions to reduce
382
preserve_dims: Dimensions to preserve
383
384
Returns:
385
Spearman rank correlation coefficients
386
387
Notes:
388
- Range: [-1, 1]
389
- Measures monotonic association strength
390
- Less sensitive to outliers than Pearson
391
- This function doesn't support weights
392
- Based on rank ordering rather than actual values
393
"""
394
```
395
396
### Advanced Scoring Functions
397
398
#### Quantile Score
399
400
Asymmetric loss function for evaluating quantile forecasts.
401
402
```python { .api }
403
def quantile_score(
404
fcst: FlexibleArrayType,
405
obs: FlexibleArrayType,
406
alpha: float,
407
*,
408
reduce_dims: Optional[FlexibleDimensionTypes] = None,
409
preserve_dims: Optional[FlexibleDimensionTypes] = None,
410
weights: Optional[xr.DataArray] = None,
411
) -> FlexibleArrayType:
412
"""
413
Calculate quantile score for quantile forecasts.
414
415
Args:
416
fcst: Quantile forecast values
417
obs: Observation values
418
alpha: Quantile level (e.g., 0.1 for 10th percentile)
419
reduce_dims: Dimensions to reduce
420
preserve_dims: Dimensions to preserve
421
weights: Optional weights
422
423
Returns:
424
Quantile scores
425
426
Formula:
427
QS = Σ[(α - I(obs_i < fcst_i)) * (fcst_i - obs_i)]
428
429
Notes:
430
- Proper scoring rule for quantile forecasts
431
- Alpha should match the quantile level being forecast
432
- Lower scores indicate better performance
433
"""
434
```
435
436
#### Flip-Flop Index
437
438
Measures forecast consistency by detecting oscillatory behavior.
439
440
```python { .api }
441
def flip_flop_index(
442
fcst: xr.DataArray,
443
obs: xr.DataArray,
444
*,
445
reduce_dims: Optional[FlexibleDimensionTypes] = None,
446
preserve_dims: Optional[FlexibleDimensionTypes] = None,
447
) -> xr.DataArray:
448
"""
449
Calculate Flip-Flop Index for forecast consistency.
450
451
Args:
452
fcst: Forecast data (must have time dimension)
453
obs: Observation data
454
reduce_dims: Dimensions to reduce
455
preserve_dims: Dimensions to preserve
456
457
Returns:
458
Flip-flop index values
459
460
Notes:
461
- Measures forecast oscillatory behavior
462
- Higher values indicate more inconsistent forecasts
463
- Requires time series data
464
- Used to detect forecast instability
465
"""
466
467
def flip_flop_index_proportion_exceeding(
468
fcst: xr.DataArray,
469
obs: xr.DataArray,
470
threshold: float,
471
*,
472
reduce_dims: Optional[FlexibleDimensionTypes] = None,
473
preserve_dims: Optional[FlexibleDimensionTypes] = None,
474
) -> xr.DataArray:
475
"""
476
Calculate proportion of flip-flop indices exceeding threshold.
477
478
Args:
479
fcst: Forecast data
480
obs: Observation data
481
threshold: Threshold value for comparison
482
reduce_dims: Dimensions to reduce
483
preserve_dims: Dimensions to preserve
484
485
Returns:
486
Proportion values [0, 1]
487
"""
488
```
489
490
## Usage Patterns
491
492
### Standard Workflow
493
494
```python
495
from scores.continuous import mse, rmse, mae, kge, pearsonr
496
from scores.sample_data import continuous_forecast, continuous_observations
497
498
# Load data
499
forecast = continuous_forecast()
500
observations = continuous_observations()
501
502
# Calculate multiple metrics
503
scores_dict = {
504
'MSE': mse(forecast, observations),
505
'RMSE': rmse(forecast, observations),
506
'MAE': mae(forecast, observations),
507
'KGE': kge(forecast, observations),
508
'Correlation': pearsonr(forecast, observations)
509
}
510
511
# Display results
512
for metric, value in scores_dict.items():
513
print(f"{metric}: {value.values:.4f}")
514
```
515
516
### Dimension-aware Scoring
517
518
```python
519
# Multi-dimensional data (time, lat, lon)
520
forecast_3d = forecast.expand_dims({"lat": 10, "lon": 15})
521
observations_3d = observations.expand_dims({"lat": 10, "lon": 15})
522
523
# Compute temporal scores at each grid point
524
spatial_temporal_mse = mse(forecast_3d, observations_3d, reduce_dims="time")
525
526
# Compute spatial scores for each time step
527
temporal_spatial_mse = mse(forecast_3d, observations_3d, reduce_dims=["lat", "lon"])
528
529
# Overall score (all dimensions)
530
overall_mse = mse(forecast_3d, observations_3d)
531
```
532
533
### Weighted Scoring
534
535
```python
536
from scores.functions import create_latitude_weights
537
import numpy as np
538
539
# Create latitude weights for area weighting
540
lats = np.linspace(-90, 90, forecast_3d.sizes["lat"])
541
area_weights = create_latitude_weights(lats)
542
543
# Calculate area-weighted scores
544
weighted_mse = mse(forecast_3d, observations_3d,
545
reduce_dims=["time", "lat", "lon"],
546
weights=area_weights)
547
```