Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/modelskill/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,16 @@ def _match_single_obs(
return None
matched_data.attrs["weight"] = obs.weight

# The dummy is extracted from the whole observation, but its mean must be the
# mean of what is compared, which is known only after matching.
obs_mean = float(matched_data["Observation"].mean())
for m in models:
if isinstance(m, DummyModelResult) and m.strategy == "mean":
matched_data[m.name] = matched_data[m.name].copy(
data=np.full(matched_data[m.name].shape, obs_mean)
)
raw_mod_data[m.name].data[m.name][:] = obs_mean

return Comparer(matched_data=matched_data, raw_mod_data=raw_mod_data)


Expand Down
7 changes: 6 additions & 1 deletion src/modelskill/model/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ class DummyModelResult:
name : str, optional
The name of the model result, by default 'dummy'
strategy : str, optional
The strategy to use, 'mean' uses the mean of the observation, 'constant' uses the value given in data, by default 'constant'
The strategy to use, by default 'constant'

- 'mean': the mean of the observation values that are compared. In `match()`
this is the mean over the time steps shared with the other models, so the
dummy has r2 = 0. For a long-term mean, pass it as `data`.
- 'constant': the value given in `data`

Examples
--------
Expand Down
23 changes: 21 additions & 2 deletions tests/test_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,13 +918,32 @@ def test_compare_model_vs_dummy_for_track(mr1, o3):

cmp2 = ms.match(obs=o3, mod=[mr1, mr])

# not identical to above since it is evaluated on a subset of the data
assert cmp2.score()["dummy"] == pytest.approx(1.225945)
# the mean is taken over the subset compared with SW_1, so r2 is still 0
assert cmp2.score(metric="r2")["dummy"] == pytest.approx(0.0)

# better than dummy 🙂
assert cmp2.score()["SW_1"] == pytest.approx(0.3524703)


def test_dummy_mean_is_mean_of_compared_observations():
time = pd.date_range("2020-01-01", periods=10, freq="h")
obs = ms.PointObservation(
pd.Series(np.arange(10.0), index=time), name="obs", x=0.0, y=0.0
)
# model covers only the last 4 time steps
mod = ms.PointModelResult(
pd.Series(np.arange(6.0, 10.0) + 0.5, index=time[6:]), name="mod"
)
dummy = ms.DummyModelResult(name="dummy", strategy="mean")

cmp = ms.match(obs=obs, mod=[mod, dummy])

assert cmp.n_points == 4
assert cmp.data["dummy"].values == pytest.approx([7.5] * 4)
assert cmp.score(metric="r2")["dummy"] == pytest.approx(0.0)
assert cmp.raw_mod_data["dummy"].values == pytest.approx([7.5] * 10)


def test_match_obs_model_pos_args_wrong_order_helpful_error_message():
# match is pretty helpful in converting strings or dataset
# so we need to use a ModelResult to trigger the error
Expand Down
Loading