Skip to content

Commit 22f0fca

Browse files
authored
Merge pull request #458 from DHI/feature/multiple_model_support_for_residual_hist_on_comparer
Feature/multiple model support for residual hist on comparer
2 parents f752b8c + b03265f commit 22f0fca

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

modelskill/comparison/_comparer_plotter.py

+51-4
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ def taylor(
763763

764764
def residual_hist(
765765
self, bins=100, title=None, color=None, figsize=None, ax=None, **kwargs
766-
) -> matplotlib.axes.Axes:
766+
) -> matplotlib.axes.Axes | list[matplotlib.axes.Axes]:
767767
"""plot histogram of residual values
768768
769769
Parameters
@@ -776,20 +776,67 @@ def residual_hist(
776776
residual color, by default "#8B8D8E"
777777
figsize : tuple, optional
778778
figure size, by default None
779-
ax : matplotlib.axes.Axes, optional
779+
ax : matplotlib.axes.Axes | list[matplotlib.axes.Axes], optional
780780
axes to plot on, by default None
781781
**kwargs
782782
other keyword arguments to plt.hist()
783783
784784
Returns
785785
-------
786-
matplotlib.axes.Axes
786+
matplotlib.axes.Axes | list[matplotlib.axes.Axes]
787787
"""
788+
cmp = self.comparer
789+
790+
if cmp.n_models == 1:
791+
return self._residual_hist_one_model(
792+
bins=bins,
793+
title=title,
794+
color=color,
795+
figsize=figsize,
796+
ax=ax,
797+
mod_name=cmp.mod_names[0],
798+
**kwargs,
799+
)
800+
801+
if ax is not None and len(ax) != len(cmp.mod_names):
802+
raise ValueError("Number of axes must match number of models")
803+
804+
axs = ax if ax is not None else [None] * len(cmp.mod_names)
805+
806+
for i, mod_name in enumerate(cmp.mod_names):
807+
cmp_model = cmp.sel(model=mod_name)
808+
ax_mod = cmp_model.plot.residual_hist(
809+
bins=bins,
810+
title=title,
811+
color=color,
812+
figsize=figsize,
813+
ax=axs[i],
814+
**kwargs,
815+
)
816+
axs[i] = ax_mod
817+
818+
return axs
819+
820+
def _residual_hist_one_model(
821+
self,
822+
bins=100,
823+
title=None,
824+
color=None,
825+
figsize=None,
826+
ax=None,
827+
mod_name=None,
828+
**kwargs,
829+
) -> matplotlib.axes.Axes:
830+
"""Residual histogram for one model only"""
788831
_, ax = _get_fig_ax(ax, figsize)
789832

790833
default_color = "#8B8D8E"
791834
color = default_color if color is None else color
792-
title = f"Residuals, {self.comparer.name}" if title is None else title
835+
title = (
836+
f"Residuals, Observation: {self.comparer.name}, Model: {mod_name}"
837+
if title is None
838+
else title
839+
)
793840
ax.hist(self.comparer._residual, bins=bins, color=color, **kwargs)
794841
ax.set_title(title)
795842
ax.set_xlabel(f"Residuals of {self.comparer._unit_text}")

tests/test_comparer.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,8 @@ def test_to_dataframe_tc(tc):
711711

712712
# ======================== plotting ========================
713713

714+
PLOT_FUNCS_RETURNING_MANY_AX = ["scatter", "hist", "residual_hist"]
715+
714716

715717
@pytest.fixture(
716718
params=[
@@ -727,11 +729,20 @@ def test_to_dataframe_tc(tc):
727729
def pc_plot_function(pc, request):
728730
func = getattr(pc.plot, request.param)
729731
# special cases requiring a model to be selected
730-
if request.param in ["scatter", "hist", "residual_hist"]:
732+
if request.param in PLOT_FUNCS_RETURNING_MANY_AX:
731733
func = getattr(pc.sel(model=0).plot, request.param)
732734
return func
733735

734736

737+
@pytest.mark.parametrize("kind", PLOT_FUNCS_RETURNING_MANY_AX)
738+
def test_plots_returning_multiple_axes(pc, kind):
739+
n_models = 2
740+
func = getattr(pc.plot, kind)
741+
ax = func()
742+
assert len(ax) == n_models
743+
assert all(isinstance(a, plt.Axes) for a in ax)
744+
745+
735746
def test_plot_returns_an_object(pc_plot_function):
736747
obj = pc_plot_function()
737748
assert obj is not None
@@ -824,7 +835,6 @@ def test_plots_directional(pt_df):
824835

825836

826837
def test_from_matched_track_data():
827-
828838
df = pd.DataFrame(
829839
{
830840
"lat": [55.0, 55.1],

0 commit comments

Comments
 (0)