Metrics Reference¶
Continuous Cell-Level Metrics¶
These metrics are computed at every grid cell for every timestep.
| Key | Name | Formula | Units |
|---|---|---|---|
ne |
Normalised Error | (test - source) / source * 100 |
percent |
ane |
Absolute Normalised Error | abs((test - source) / source) * 100 |
percent |
rse |
Root Squared Error | sqrt((test - source)^2) |
same as variable |
bias |
Mean Error (Bias) | test - source |
same as variable |
mae |
Mean Absolute Error | abs(test - source) |
same as variable |
Categorical Cell-Level Metrics¶
Require a threshold parameter. Events are defined as values exceeding the threshold.
| Key | Name | Formula | Range |
|---|---|---|---|
pod |
Probability of Detection | Hits / (Hits + Misses) |
0--1 |
far |
False Alarm Ratio | False Alarms / (Hits + False Alarms) |
0--1 |
csi |
Critical Success Index | Hits / (Hits + False Alarms + Misses) |
0--1 |
fbias |
Frequency Bias | (Hits + False Alarms) / (Hits + Misses) |
0--inf |
Domain-Aggregated Metrics¶
Computed over the spatial domain for each timestep.
| Key | Name | Description |
|---|---|---|
ne |
Normalised Error | NE computed from domain-summed values |
ane |
Absolute Normalised Error | ANE computed from domain-summed values |
rmse |
Root Mean Square Error | Spatially aggregated RMSE |
bias |
Mean Error | Spatially aggregated bias |
pearson |
Pearson Correlation | Spatial correlation coefficient (-1 to 1) |
pod |
Probability of Detection | From domain contingency table |
far |
False Alarm Ratio | From domain contingency table |
csi |
Critical Success Index | From domain contingency table |
gss |
Gilbert Skill Score | Equitable threat score from domain contingency table |
fbias |
Frequency Bias | From domain contingency table |
Station Metrics¶
Per-station time series metrics.
| Key | Name | Description |
|---|---|---|
bias |
Mean Bias | Mean difference (model - obs) |
mae |
Mean Absolute Error | Mean absolute difference |
rmse |
Root Mean Square Error | RMS of differences |
ne |
Normalised Error | Normalised by observed values |
ane |
Absolute Normalised Error | Absolute normalised difference |
pearson |
Pearson Correlation | Temporal correlation per station |
Wind Metrics¶
Vector wind evaluation from U/V components.
| Key | Name | Units | Description |
|---|---|---|---|
vector_rmse |
Vector RMSE | m/s | sqrt(mean(du^2 + dv^2)) |
speed_bias |
Wind Speed Bias | m/s | mean(test_speed - source_speed) |
direction_bias |
Wind Direction Bias | degrees | Mean directional difference (-180 to 180) |
Fractions Skill Score (FSS)¶
Spatial verification at multiple neighborhood scales (default: 1, 3, 5, 9, 17, 33, 65 grid cells).
| Value | Interpretation |
|---|---|
| 0.0 | No skill |
| 0.5 | Useful skill threshold |
| 1.0 | Perfect |
Requires a threshold parameter to binarize the fields before computing fractions.
Diurnal Metrics¶
Any of the following metrics grouped by hour-of-day (0--23):
bias, rmse, mae, pearson
Supports a utc_offset parameter for local time conversion.
API¶
Full signatures for every function in modverif.metrics, including
compute_residual_skill_score (residuals rather than model/obs pairs, and an RMSE-ratio
rather than the more common MSE-ratio skill score).
Standardized meteorological verification metrics.
ContingencyTable
¶
A class representing a 2x2 contingency table for binary events. Matches MET Grid-Stat/Point-Stat output categories.
bias()
¶
Frequency Bias. Bias = (Hits + False Alarms) / (Hits + Misses).
csi()
¶
Critical Success Index (Threat Score). CSI = Hits / (Hits + False Alarms + Misses).
far()
¶
False Alarm Ratio. FAR = False Alarms / (Hits + False Alarms).
from_data(source_data, test_data, threshold)
classmethod
¶
Create a contingency table from forecast and observation data using a threshold.
gss()
¶
Gilbert Skill Score (Equitable Threat Score). GSS = (Hits - Hits_random) / (Hits + False Alarms + Misses - Hits_random) where Hits_random = (Hits + Misses) * (Hits + False Alarms) / Total
pod()
¶
Probability of Detection (Hit Rate). POD = Hits / (Hits + Misses).
compute_ane(source_data, test_data, epsilon=1e-10)
¶
Compute absolute normalised error between source and test data, returning int16. ANE = |((test - source) / source)| * 100
compute_ane_1d(model, obs, epsilon=1e-10)
¶
Absolute normalised error for 1D paired arrays (as percentage).
compute_ane_domain(source_data, test_data, mask=None, epsilon=1e-10)
¶
Compute domain-aggregated absolute normalised error for each timestep.
compute_bias(source_data, test_data)
¶
Compute Mean Error (Bias) between source and test data. Bias = test - source
compute_bias_domain(source_data, test_data, mask=None)
¶
Compute domain-aggregated Mean Error (Bias) for each timestep.
compute_diurnal_stats(times, model, obs, metric='bias', utc_offset=0.0)
¶
Compute a metric grouped by hour-of-day.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray
|
Array of datetime64 values. |
required |
model
|
ndarray
|
1D model values (n_times,). |
required |
obs
|
ndarray
|
1D observation values (n_times,). |
required |
metric
|
str
|
One of 'bias', 'rmse', 'mae', 'pearson'. |
'bias'
|
utc_offset
|
float
|
Hours to add to UTC to get local time. |
0.0
|
Returns:
| Name | Type | Description |
|---|---|---|
hours |
ndarray
|
Integer array of shape (24,). |
values |
ndarray
|
Float64 array of shape (24,). |
compute_fraction_field(binary_field, neighborhood_size)
¶
Compute fraction of True cells within a square neighborhood.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
binary_field
|
ndarray
|
2D bool array (y, x). |
required |
neighborhood_size
|
int
|
Odd integer for neighborhood window size. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
2D float array of fractions. |
compute_fss(source_data, test_data, threshold, neighborhood_size, mask=None)
¶
Compute Fractions Skill Score for a single 2D field.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_data
|
ndarray
|
2D reference field (y, x). |
required |
test_data
|
ndarray
|
2D forecast/test field (y, x). |
required |
threshold
|
float
|
Binary event threshold. |
required |
neighborhood_size
|
int
|
Odd integer for neighborhood window size. |
required |
mask
|
ndarray
|
2D boolean mask. Only masked cells contribute. |
None
|
Returns:
| Type | Description |
|---|---|
float
|
FSS value in [0, 1]. |
compute_fss_multi_scale(source_data, test_data, threshold, neighborhood_sizes=None, mask=None)
¶
Compute FSS across multiple neighborhood sizes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_data
|
ndarray
|
2D reference field (y, x). |
required |
test_data
|
ndarray
|
2D forecast/test field (y, x). |
required |
threshold
|
float
|
Binary event threshold. |
required |
neighborhood_sizes
|
list[int]
|
Neighborhood sizes. Default: [1, 3, 5, 9, 17, 33, 65]. |
None
|
mask
|
ndarray
|
2D boolean mask. |
None
|
Returns:
| Type | Description |
|---|---|
dict[int, float]
|
Mapping neighborhood_size -> FSS. |
compute_lagged_correlation(model, obs, max_lag=None)
¶
Cross-correlation between model and observation time series at multiple lags.
A positive optimal lag means the model leads (event arrives early);
a negative optimal lag means the model lags (event arrives late).
See compute_xcorr_best_lag, whose convention is the OPPOSITE of this one.
WARNING: non-finite pairs are dropped before lagging, so the series are compacted and
the lag is counted in surviving-sample steps rather than in time. On a gappy series -- an
interrupted rain gauge, say -- the returned "lag" is therefore not a lag in hours. For
displaced-timing work on gappy observations use compute_xcorr_best_lag, which re-masks per lag
and so keeps the lag axis in real time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ndarray
|
1D model time series (n_times,). |
required |
obs
|
ndarray
|
1D observation time series (n_times,). |
required |
max_lag
|
int
|
Maximum lag (in timesteps) to evaluate in both directions. Default is n_times // 4. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
lags |
ndarray
|
Integer lag values from -max_lag to +max_lag. |
correlations |
ndarray
|
Pearson correlation at each lag. |
compute_mae(source_data, test_data)
¶
Compute Mean Absolute Error between source and test data. MAE = |test - source|
compute_mae_1d(model, obs)
¶
Mean absolute error averaged over time.
compute_mean_bias(model, obs)
¶
Mean error averaged over time. model and obs are 1D (n_times,).
compute_ne(source_data, test_data, epsilon=1e-10)
¶
Compute normalised error between source and test data, returning int16. NE = ((test - source) / source) * 100
compute_ne_1d(model, obs, epsilon=1e-10)
¶
Normalised error for 1D paired arrays (as percentage).
compute_ne_domain(source_data, test_data, mask=None, epsilon=1e-10)
¶
Compute domain-aggregated normalised error for each timestep.
compute_pearson_correlation(source_data, test_data)
¶
Compute Pearson correlation coefficient between two arrays.
compute_pearson_domain(source_data, test_data, mask=None)
¶
Pearson correlation over the spatial domain per timestep.
compute_residual_skill_score(resid, resid_base)
¶
Skill of one set of residuals against a baseline's, as a fraction of RMSE removed.
1 - rmse(resid) / rmse(resid_base). Positive means the model beat the baseline; zero means it
matched it; negative means the baseline was better, which is the outcome worth reporting rather
than hiding.
Two things to note before comparing this with a number from elsewhere:
- It takes residuals, not
(model, obs)pairs, unlike the rest of this module. The baseline is often something with no per-point prediction to subtract -- a leave-one-out mean, a climatology -- so residuals are the only common currency. - It is an RMSE ratio, not the more common MSE-ratio skill score (MSESS). The two are not interchangeable: an MSE ratio of 0.5 is an RMSE ratio of about 0.29.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resid
|
ndarray
|
Residuals of the method under test. |
required |
resid_base
|
ndarray
|
Residuals of the reference method. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Skill score, or NaN if the baseline has zero RMSE (nothing to improve on). |
compute_rmse_1d(model, obs)
¶
RMSE over a 1D time series.
compute_rmse_domain(source_data, test_data, mask=None)
¶
Compute domain-aggregated root mean square error for each timestep.
compute_rse(source_data, test_data)
¶
Compute root squared error between source and test data. RSE = sqrt((test - source)^2)
compute_vector_rmse(source_u, source_v, test_u, test_v)
¶
Vector RMSE: sqrt(mean((du)^2 + (dv)^2)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_u
|
ndarray
|
Reference U/V wind components (2D or 1D). |
required |
source_v
|
ndarray
|
Reference U/V wind components (2D or 1D). |
required |
test_u
|
ndarray
|
Test U/V wind components (2D or 1D). |
required |
test_v
|
ndarray
|
Test U/V wind components (2D or 1D). |
required |
Returns:
| Type | Description |
|---|---|
float
|
Scalar vector RMSE. |
compute_wind_direction_bias(source_u, source_v, test_u, test_v)
¶
Mean angular difference in wind direction (degrees). Uses circular statistics to handle wraparound.
Returns:
| Type | Description |
|---|---|
float
|
Mean directional bias in degrees [-180, 180]. |
compute_wind_speed_bias(source_u, source_v, test_u, test_v)
¶
Wind speed bias: mean(test_speed - source_speed).
Returns:
| Type | Description |
|---|---|
float
|
Mean wind speed bias. |
compute_xcorr_best_lag(model, obs, max_lag, min_pairs=48)
¶
Best-correlating lag between an observation series and a model series, masked per lag.
Sits beside compute_lagged_correlation deliberately, because the two compute the same physical
quantity and disagree about its sign. Keeping them apart would let a reader use either without
noticing.
WARNING: the sign convention here is the OPPOSITE of compute_lagged_correlation.
- Here: a positive lag means the model is later than the observations -- matching a
model_start - obs_starttiming difference, which is how a displacement is usually reported. - There: a positive lag means the model leads.
Neither is wrong; they were written for different questions. Do not "fix" one to match the other -- published results depend on both.
Because of that, swapping the two series is invisible: it returns the negated lag with a
bitwise-identical correlation, and no output reveals the mistake. The argument order here is
therefore (model, obs), matching every other function in this module, so habit gives the
right answer.
Differs from compute_lagged_correlation in one other way that matters more than it looks: the
finite mask is recomputed at each lag rather than once up front. Dropping non-finite pairs
before lagging compacts the series, so the lag axis stops being time. Re-masking keeps it honest
on gappy observations, at the cost of comparing slightly different samples across lags -- which
is why min_pairs exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obs
|
ndarray
|
Observation series, evenly spaced in time. Non-finite entries mark gaps. |
required |
model
|
ndarray
|
Model series on the same time axis. |
required |
max_lag
|
int
|
Largest lag to test, in timesteps, in both directions. |
required |
min_pairs
|
int
|
A lag is skipped unless this many finite pairs overlap. Guards against a lag winning on a handful of coincidentally-agreeing points at the edge of the overlap. |
48
|
Returns:
| Name | Type | Description |
|---|---|---|
best_lag |
float
|
Lag with the highest correlation -- integer-valued, but typed float because it is NaN when
no lag had enough pairs. Exact ties resolve to the
most negative lag, since the scan runs from |
r_best |
float
|
Correlation at that lag. |
r_zero |
float
|
Correlation at zero lag, for comparison -- how much the alignment actually bought. |
n_pairs |
int
|
Finite pairs contributing at the best lag. |