From b2701fda3f0c082662758366214cd8b1c99fab18 Mon Sep 17 00:00:00 2001 From: 72004 Date: Sat, 17 Oct 2026 20:31:41 +0500 Subject: [PATCH] Return NaN from nse and r2 for pandas input with NaN model values np.sum on a pandas Series skips NaN, so nse and r2 returned 1.0 when model values were missing, while NumPy input returned NaN. Convert the inputs with np.asarray first. Fixes #720 --- src/modelskill/metrics.py | 6 ++++++ tests/test_metrics.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/modelskill/metrics.py b/src/modelskill/metrics.py index 5d0652666..6ea0838d6 100644 --- a/src/modelskill/metrics.py +++ b/src/modelskill/metrics.py @@ -251,6 +251,9 @@ def nash_sutcliffe_efficiency(obs: ArrayLike, model: ArrayLike) -> Any: Nash, J. E.; Sutcliffe, J. V. (1970). "River flow forecasting through conceptual models part I — A discussion of principles". Journal of Hydrology. 10 (3): 282–290. """ + # np.sum on a pandas Series skips NaN, so convert to get NaN like the other metrics + obs = np.asarray(obs) + model = np.asarray(model) assert obs.size == model.size if len(obs) == 0: @@ -331,6 +334,9 @@ def r2(obs: ArrayLike, model: ArrayLike) -> Any: >>> r2(obs,model) np.float64(0.6379999999999998) """ + # np.sum on a pandas Series skips NaN, so convert to get NaN like the other metrics + obs = np.asarray(obs) + model = np.asarray(model) assert obs.size == model.size if len(obs) == 0: return np.nan diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 26bc58579..89a675042 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -74,6 +74,19 @@ def test_kge_climatology_model(): assert mtr.kling_gupta_efficiency(obs, mod) == pytest.approx(-0.41, abs=1e-2) +@pytest.mark.parametrize("metric", [mtr.nse, mtr.r2]) +@pytest.mark.parametrize( + "model", [[1.0, 2.0, np.nan, np.nan], [np.nan, np.nan, np.nan, np.nan]] +) +def test_nse_r2_nan_in_model_series(metric: Callable, model: list) -> None: + # pandas Series must give the same result as NumPy arrays (NaN), not 1.0 + obs = pd.Series([1.0, 2.0, 3.0, 4.0]) + mod = pd.Series(model) + + assert np.isnan(metric(obs.to_numpy(), mod.to_numpy())) + assert np.isnan(metric(obs, mod)) + + def test_nse_suboptimal(): obs = np.array([1.0, 0.5, 0]) mod = np.array([1.0, 0.0, 0.5])