Source code for hydrobm.metrics

import numpy as np


[docs] def filter_nan(obs, sim): """Select only non-NaN values from both observed and simulated data. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. Returns ------- tuple Tuple of arrays with non-NaN values from both observed and simulated data. """ # Catch the case where we have all NaNs. # Rare, but might happen for short timeseries and the lagged benchmarks. if np.all(np.isnan(obs)) or np.all(np.isnan(sim)): return np.nan, np.nan mask = ~np.isnan(obs) & ~np.isnan(sim) return obs[mask], sim[mask]
[docs] def nse(obs, sim, ignore_nan=True): """Calculate Nash-Sutcliffe efficiency. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. ignore_nan : bool, optional Flag to consider only non-NaN values. Default is True. Returns ------- float Nash-Sutcliffe efficiency. """ if ignore_nan: obs, sim = filter_nan(obs, sim) return 1 - np.sum((obs - sim) ** 2) / np.sum((obs - np.mean(obs)) ** 2)
[docs] def mse(obs, sim, ignore_nan=True): """Calculate mean square error. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. ignore_nan : bool, optional Flag to consider only non-NaN values. Default is True. Returns ------- float Mean square error. """ if ignore_nan: obs, sim = filter_nan(obs, sim) return np.mean((obs - sim) ** 2)
[docs] def rmse(obs, sim, ignore_nan=True): """Calculate root mean square error. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. Returns ------- float Root mean square error. """ if ignore_nan: obs, sim = filter_nan(obs, sim) return np.sqrt(np.mean((obs - sim) ** 2))
[docs] def kge(obs, sim, ignore_nan=True): """Calculate Kling-Gupta efficiency. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. ignore_nan : bool, optional Flag to consider only non-NaN values. Default is True. Returns ------- float Kling-Gupta efficiency. """ if ignore_nan: obs, sim = filter_nan(obs, sim) # Account for the case where obs has zero variance if (np.std(obs) == 0) or (np.std(sim) == 0): r = 0 else: r = np.corrcoef(obs, sim)[0, 1] alpha = np.std(sim) / np.std(obs) beta = np.mean(sim) / np.mean(obs) return 1 - np.sqrt((r - 1) ** 2 + (alpha - 1) ** 2 + (beta - 1) ** 2)
[docs] def calculate_metric(obs, sim, metric, ignore_nan=True): """Helper function to check metric existence and simplify loops. Parameters ---------- obs : array-like Observed values. sim : array-like Simulated values. metric: str Name of the metric to calculate. ignore_nan : bool, optional Flag to consider only non-NaN values. Default is True. Returns ------- float Metric score. """ # List of currently implemented metrics metric_list = [ "nse", "mse", "rmse", "kge", ] assert metric in metric_list, f"Requested metric {metric} not found." if metric == "nse": val = nse(obs, sim, ignore_nan=ignore_nan) elif metric == "mse": val = mse(obs, sim, ignore_nan=ignore_nan) elif metric == "rmse": val = rmse(obs, sim, ignore_nan=ignore_nan) elif metric == "kge": val = kge(obs, sim, ignore_nan=ignore_nan) return val