Skip to content

Commit

Permalink
fix: compatibility with matplotlib 3.6 (#366)
Browse files Browse the repository at this point in the history
* refactor deprecated use of set_tight_layout
* update reference figures to matplotlib 3.6
* handle API differences between matplotlib<3.6 (for Python 3.7) and matplotlib>=3.6
* temporarily pin pyhf==0.7.0rc4
* skip latest Sphinx version for which RTD theme is broken
* switch from Python 3.7 to 3.8 for coverage evaluation
  • Loading branch information
alexander-held committed Sep 25, 2022
1 parent f0f9593 commit 278d088
Show file tree
Hide file tree
Showing 15 changed files with 61 additions and 36 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ jobs:
run: |
python -m pip install .[pyhf_backends] # install pyhf backends
- name: Test with pytest and generate coverage report
if: matrix.python-version == '3.7'
if: matrix.python-version == '3.8'
run: |
pytest --runslow --cov-report=xml
- name: Test with pytest
if: matrix.python-version != '3.7'
if: matrix.python-version != '3.8'
run: |
pytest --runslow
- name: Upload coverage to codecov
if: matrix.python-version == '3.7'
if: matrix.python-version == '3.8'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,6 @@ module = [
"iminuit",
"jsonschema",
"scipy.*",
"packaging.*",
]
ignore_missing_imports = true
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ packages = find:
package_dir = =src
python_requires = >=3.7
install_requires =
pyhf[minuit]~=0.7.0rc4 # model.config.suggested_fixed API change, staterror fix, set_poi(None)
pyhf[minuit]==0.7.0rc4 # model.config.suggested_fixed API change, staterror fix, set_poi(None)
boost_histogram>=1.0.0 # subclassing with family, 1.02 for stdev scaling fix (currently not needed)
awkward>=1.8 # _v2 API in submodule
tabulate>=0.8.1 # multiline text
Expand All @@ -36,6 +36,7 @@ install_requires =
jsonschema
click
scipy
packaging # for version parsing

[options.packages.find]
where = src
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
extras_require["pyhf_backends"] = ["pyhf[backends]"]
extras_require["docs"] = sorted(
{
"sphinx",
"sphinx!=5.2.0.post0", # broken due to version parsing in RTD theme
"sphinx-click",
"sphinx-copybutton",
"sphinx-jsonschema",
Expand Down
21 changes: 12 additions & 9 deletions src/cabinetry/visualize/plot_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import packaging.version

from cabinetry.visualize import utils


log = logging.getLogger(__name__)

# handling of matplotlib<3.6 (for Python 3.7)
if packaging.version.parse(mpl.__version__) < packaging.version.parse("3.6"):
MPL_STYLE = "seaborn-colorblind" # pragma: no cover
else:
MPL_STYLE = "seaborn-v0_8-colorblind"


def data_mc(
histogram_dict_list: List[Dict[str, Any]],
Expand Down Expand Up @@ -59,9 +66,9 @@ def data_mc(
mc_histograms_yields.append(h["yields"])
mc_labels.append(h["label"])

mpl.style.use("seaborn-colorblind")
mpl.style.use(MPL_STYLE)

fig = plt.figure(figsize=(6, 6))
fig = plt.figure(figsize=(6, 6), layout="constrained")
gs = fig.add_gridspec(nrows=2, ncols=1, hspace=0, height_ratios=[3, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
Expand Down Expand Up @@ -223,8 +230,6 @@ def data_mc(
ax2.tick_params(axis="both", which="major", pad=8)
ax2.tick_params(direction="in", top=True, right=True, which="both")

fig.set_tight_layout(True)

utils._save_and_close(fig, figure_path, close_figure)
return fig

Expand Down Expand Up @@ -267,8 +272,8 @@ def templates(
bin_width = bin_edges[1:] - bin_edges[:-1]
bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])

mpl.style.use("seaborn-colorblind")
fig = plt.figure(figsize=(8, 6))
mpl.style.use(MPL_STYLE)
fig = plt.figure(figsize=(8, 6), layout="constrained")
gs = fig.add_gridspec(nrows=2, ncols=1, hspace=0, height_ratios=[3, 1])
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
Expand Down Expand Up @@ -396,8 +401,6 @@ def templates(
ax2.tick_params(axis="both", which="major", pad=8)
ax2.tick_params(direction="in", top=True, right=True, which="both")

fig.set_tight_layout(True)

utils._save_and_close(fig, figure_path, close_figure)
return fig

Expand Down Expand Up @@ -446,8 +449,8 @@ def modifier_grid(
fig, ax = plt.subplots(
len(axis_labels[0]),
sharex=True,
constrained_layout=True,
figsize=(fig_width, fig_height),
layout="constrained",
squeeze=False, # always return array of axes (even with a single subplot)
)
ax = ax.flatten() # turn into 1d array
Expand Down
47 changes: 32 additions & 15 deletions src/cabinetry/visualize/plot_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import packaging.version

from cabinetry.visualize import utils


log = logging.getLogger(__name__)

# handling of matplotlib<3.6 (for Python 3.7)
if packaging.version.parse(mpl.__version__) < packaging.version.parse("3.6"):
MPL_GEQ_36 = False # pragma: no cover
MPL_STYLE = "seaborn-colorblind" # pragma: no cover
else:
MPL_GEQ_36 = True
MPL_STYLE = "seaborn-v0_8-colorblind"


def correlation_matrix(
corr_mat: np.ndarray,
Expand Down Expand Up @@ -41,6 +50,7 @@ def correlation_matrix(
fig, ax = plt.subplots(
figsize=(round(5 + len(labels) / 1.6, 1), round(3 + len(labels) / 1.6, 1)),
dpi=100,
layout="constrained",
)
im = ax.imshow(corr_mat, vmin=-1, vmax=1, cmap="RdBu")

Expand All @@ -54,7 +64,6 @@ def correlation_matrix(

fig.colorbar(im, ax=ax)
ax.set_aspect("auto") # to get colorbar aligned with matrix
fig.set_tight_layout(True)

# add correlation as text
for (j, i), corr in np.ndenumerate(corr_mat):
Expand Down Expand Up @@ -91,7 +100,7 @@ def pulls(
"""
num_pars = len(bestfit)
y_positions = np.arange(num_pars)[::-1]
fig, ax = plt.subplots(figsize=(6, 1 + num_pars / 4), dpi=100)
fig, ax = plt.subplots(figsize=(6, 1 + num_pars / 4), dpi=100, layout="constrained")
ax.errorbar(bestfit, y_positions, xerr=uncertainty, fmt="o", color="black")

ax.fill_between([-2, 2], -0.5, len(bestfit) - 0.5, color="yellow")
Expand All @@ -106,7 +115,6 @@ def pulls(
ax.xaxis.set_minor_locator(mpl.ticker.AutoMinorLocator()) # minor ticks
ax.tick_params(axis="both", which="major", pad=8)
ax.tick_params(direction="in", top=True, right=True, which="both")
fig.set_tight_layout(True)

utils._save_and_close(fig, figure_path, close_figure)
return fig
Expand Down Expand Up @@ -147,8 +155,21 @@ def ranking(
"""
num_pars = len(bestfit)

mpl.style.use("seaborn-colorblind")
fig, ax_pulls = plt.subplots(figsize=(8, 2.5 + num_pars * 0.45), dpi=100)
# layout to make space for legend on top
leg_space = 1.0 / (num_pars + 3) + 0.03
if MPL_GEQ_36:
import matplotlib.layout_engine

layout = matplotlib.layout_engine.ConstrainedLayoutEngine(
rect=[0, 0, 1.0, 1 - leg_space]
)
else:
layout = None # pragma: no cover # layout set after figure creation instead

mpl.style.use(MPL_STYLE)
fig, ax_pulls = plt.subplots(
figsize=(8, 2.5 + num_pars * 0.45), dpi=100, layout=layout
)
ax_impact = ax_pulls.twiny() # second x-axis with shared y-axis, used for pulls

# since pull axis is below impact axis, flip them so pulls show up on top
Expand Down Expand Up @@ -228,9 +249,9 @@ def ranking(
ncol=3,
fontsize="large",
)
leg_space = 1.0 / (num_pars + 3) + 0.03
# there might be a way to use set_tight_layout here as well with a bounding box
fig.tight_layout(rect=[0, 0, 1.0, 1 - leg_space]) # make space for legend on top

if not MPL_GEQ_36:
fig.tight_layout(rect=[0, 0, 1.0, 1 - leg_space]) # pragma: no cover

utils._save_and_close(fig, figure_path, close_figure)
return fig
Expand Down Expand Up @@ -263,8 +284,8 @@ def scan(
Returns:
matplotlib.figure.Figure: the likelihood scan figure
"""
mpl.style.use("seaborn-colorblind")
fig, ax = plt.subplots()
mpl.style.use(MPL_STYLE)
fig, ax = plt.subplots(layout="constrained")

y_lim = max(par_nlls) * 1.2 # upper y-axis limit, 20% headroom

Expand Down Expand Up @@ -308,8 +329,6 @@ def scan(

ax.legend(frameon=False, fontsize="large")

fig.set_tight_layout(True)

utils._save_and_close(fig, figure_path, close_figure)
return fig

Expand Down Expand Up @@ -339,7 +358,7 @@ def limit(
Returns:
matplotlib.figure.Figure: the CLs figure
"""
fig, ax = plt.subplots()
fig, ax = plt.subplots(layout="constrained")

xmin = min(poi_values)
xmax = max(poi_values)
Expand Down Expand Up @@ -403,7 +422,5 @@ def limit(
ax.tick_params(axis="both", which="major", pad=8)
ax.tick_params(direction="in", top=True, right=True, which="both")

fig.set_tight_layout(True)

utils._save_and_close(fig, figure_path, close_figure)
return fig
Binary file modified tests/visualize/reference/correlation_matrix.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/data_mc.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/data_mc_log.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/limit.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/pulls.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/scan.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/visualize/reference/templates.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 3 additions & 6 deletions tests/visualize/test_visualize_plot_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ def test_data_mc(tmp_path, caplog):
caplog.clear()

# expect three RuntimeWarnings from numpy due to division by zero
assert len(warn_record) == 3
for i in range(3):
assert "divide by zero" in str(warn_record[i].message)
assert sum("divide by zero" in str(m.message) for m in warn_record) == 3

plt.close("all")

Expand Down Expand Up @@ -170,7 +168,6 @@ def test_templates(tmp_path):

# compare figure returned by function
fname = tmp_path / "fig_from_return.png"
fig.set_tight_layout(False) # https://github.com/matplotlib/matplotlib/issues/21742
fig.savefig(fname)
assert (
compare_images("tests/visualize/reference/templates.png", str(fname), 0) is None
Expand Down Expand Up @@ -223,7 +220,7 @@ def test_modifier_grid(tmp_path):
)
# non-zero tolerance as layout changes very slightly in CI
assert (
compare_images("tests/visualize/reference/modifier_grid.png", str(fname), 15)
compare_images("tests/visualize/reference/modifier_grid.png", str(fname), 16)
is None
)

Expand All @@ -241,7 +238,7 @@ def test_modifier_grid(tmp_path):
fname = tmp_path / "fig_from_return.png"
fig.savefig(fname)
assert (
compare_images("tests/visualize/reference/modifier_grid.png", str(fname), 15)
compare_images("tests/visualize/reference/modifier_grid.png", str(fname), 16)
is None
)

Expand Down
8 changes: 7 additions & 1 deletion tests/visualize/test_visualize_plot_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ def test_correlation_matrix(tmp_path):

# compare figure returned by function
fname = tmp_path / "fig_from_return.png"

# adjust layout behavior, see https://github.com/matplotlib/matplotlib/issues/21742
if plot_result.MPL_GEQ_36:
fig.set_layout_engine(None)
else:
fig.set_constrained_layout(False)

fig.savefig(fname)
assert (
compare_images(
Expand Down Expand Up @@ -125,7 +132,6 @@ def test_scan(tmp_path):

# compare figure returned by function
fname = tmp_path / "fig_from_return.png"
fig.set_tight_layout(False) # https://github.com/matplotlib/matplotlib/issues/21742
fig.savefig(fname)
assert compare_images("tests/visualize/reference/scan.png", str(fname), 0) is None

Expand Down

0 comments on commit 278d088

Please sign in to comment.