Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: compatibility with matplotlib 3.6 #366

Merged
merged 10 commits into from
Sep 25, 2022
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