From 0ac41d1f0bd86ebc415bb82b691e75d9f4cad948 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 20 Oct 2020 19:05:59 +0700 Subject: [PATCH 1/4] TST: fix warnings on multiple subplots --- pandas/tests/plotting/common.py | 36 +++++++++++++++++++++++ pandas/tests/plotting/test_hist_method.py | 29 ++++++++++++------ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 2a6bd97c93b8e..7c3f9ff894708 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -529,7 +529,43 @@ def _unpack_cycler(self, rcParams, field="color"): return [v[field] for v in rcParams["axes.prop_cycle"]] +def _check_single_plot_works(f, filterwarnings="always", **kwargs): + """ + Similar to _check_plot_works, but this one plots at the default axes, + without creating subplots. + """ + import matplotlib.pyplot as plt + + ret = None + with warnings.catch_warnings(): + warnings.simplefilter(filterwarnings) + try: + try: + fig = kwargs["figure"] + except KeyError: + fig = plt.gcf() + + plt.clf() + + ret = f(**kwargs) + + tm.assert_is_valid_plot_return_object(ret) + + with tm.ensure_clean(return_filelike=True) as path: + plt.savefig(path) + finally: + tm.close(fig) + + return ret + + def _check_plot_works(f, filterwarnings="always", **kwargs): + # Should bootstrap plot be removed from here to a separate function? + # Like _check_bootstrap_plot_works? + # And what if we do not create subplot(211)? + # This will save us a trouble with handling warnings, + # when figure internally creating subplots (like df.hist) + # is forced to be plotted in a canvas with subplots already created. import matplotlib.pyplot as plt ret = None diff --git a/pandas/tests/plotting/test_hist_method.py b/pandas/tests/plotting/test_hist_method.py index d9a58e808661b..293981a96af9a 100644 --- a/pandas/tests/plotting/test_hist_method.py +++ b/pandas/tests/plotting/test_hist_method.py @@ -8,7 +8,11 @@ from pandas import DataFrame, Index, Series, to_datetime import pandas._testing as tm -from pandas.tests.plotting.common import TestPlotBase, _check_plot_works +from pandas.tests.plotting.common import ( + TestPlotBase, + _check_plot_works, + _check_single_plot_works, +) @td.skip_if_no_mpl @@ -138,7 +142,7 @@ def test_hist_with_legend(self, by, expected_axes_num, expected_layout): s = Series(np.random.randn(30), index=index, name="a") s.index.name = "b" - axes = _check_plot_works(s.hist, legend=True, by=by) + axes = _check_single_plot_works(s.hist, legend=True, by=by) self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout) self._check_legend_labels(axes, "a") @@ -318,7 +322,7 @@ def test_tight_layout(self): dtype=np.int64, ) ) - _check_plot_works(df.hist) + _check_single_plot_works(df.hist) self.plt.tight_layout() tm.close() @@ -331,7 +335,7 @@ def test_hist_subplot_xrot(self): "animal": ["pig", "rabbit", "pig", "pig", "rabbit"], } ) - axes = _check_plot_works( + axes = _check_single_plot_works( df.hist, filterwarnings="always", column="length", @@ -360,10 +364,16 @@ def test_hist_column_order_unchanged(self, column, expected): index=["pig", "rabbit", "duck", "chicken", "horse"], ) - axes = _check_plot_works(df.hist, column=column, layout=(1, 3)) - result = [axes[0, i].get_title() for i in range(3)] - - assert result == expected + with tm.assert_produces_warning(UserWarning): + # _check_plot_works creates subplots inside, + # meanwhile df.hist here creates subplots itself for each column. + # It leads to warnings like this: + # UserWarning: To output multiple subplots, + # the figure containing the passed axes is being cleared + # Similar warnings were observed in GH #13188 + axes = _check_plot_works(df.hist, column=column, layout=(1, 3)) + result = [axes[0, i].get_title() for i in range(3)] + assert result == expected @pytest.mark.parametrize("by", [None, "c"]) @pytest.mark.parametrize("column", [None, "b"]) @@ -378,7 +388,8 @@ def test_hist_with_legend(self, by, column): index = Index(15 * ["1"] + 15 * ["2"], name="c") df = DataFrame(np.random.randn(30, 2), index=index, columns=["a", "b"]) - axes = _check_plot_works(df.hist, legend=True, by=by, column=column) + axes = _check_single_plot_works(df.hist, legend=True, by=by, column=column) + self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout) if by is None and column is None: axes = axes[0] From 66a59234ea409a04db3f3984a0bfc8ed8b49f1c0 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 29 Oct 2020 19:08:57 +0700 Subject: [PATCH 2/4] REF: rename to _check_plot_default_axes_works --- pandas/tests/plotting/common.py | 14 +++++------ pandas/tests/plotting/test_hist_method.py | 30 +++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 7c3f9ff894708..6b8d8e15300d8 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -529,10 +529,14 @@ def _unpack_cycler(self, rcParams, field="color"): return [v[field] for v in rcParams["axes.prop_cycle"]] -def _check_single_plot_works(f, filterwarnings="always", **kwargs): +def _check_plot_default_axes_works(f, filterwarnings="always", **kwargs): """ + Create plot and ensure that plot return object is valid. + Similar to _check_plot_works, but this one plots at the default axes, - without creating subplots. + without creating subplots in advance. + Use this function when plotting method generate subplots itself, + otherwise use _check_plot_works. """ import matplotlib.pyplot as plt @@ -560,12 +564,6 @@ def _check_single_plot_works(f, filterwarnings="always", **kwargs): def _check_plot_works(f, filterwarnings="always", **kwargs): - # Should bootstrap plot be removed from here to a separate function? - # Like _check_bootstrap_plot_works? - # And what if we do not create subplot(211)? - # This will save us a trouble with handling warnings, - # when figure internally creating subplots (like df.hist) - # is forced to be plotted in a canvas with subplots already created. import matplotlib.pyplot as plt ret = None diff --git a/pandas/tests/plotting/test_hist_method.py b/pandas/tests/plotting/test_hist_method.py index 293981a96af9a..e44cefc74bef7 100644 --- a/pandas/tests/plotting/test_hist_method.py +++ b/pandas/tests/plotting/test_hist_method.py @@ -10,8 +10,8 @@ import pandas._testing as tm from pandas.tests.plotting.common import ( TestPlotBase, + _check_plot_default_axes_works, _check_plot_works, - _check_single_plot_works, ) @@ -142,7 +142,7 @@ def test_hist_with_legend(self, by, expected_axes_num, expected_layout): s = Series(np.random.randn(30), index=index, name="a") s.index.name = "b" - axes = _check_single_plot_works(s.hist, legend=True, by=by) + axes = _check_plot_default_axes_works(s.hist, legend=True, by=by) self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout) self._check_legend_labels(axes, "a") @@ -322,7 +322,7 @@ def test_tight_layout(self): dtype=np.int64, ) ) - _check_single_plot_works(df.hist) + _check_plot_default_axes_works(df.hist) self.plt.tight_layout() tm.close() @@ -335,7 +335,7 @@ def test_hist_subplot_xrot(self): "animal": ["pig", "rabbit", "pig", "pig", "rabbit"], } ) - axes = _check_single_plot_works( + axes = _check_plot_default_axes_works( df.hist, filterwarnings="always", column="length", @@ -364,16 +364,11 @@ def test_hist_column_order_unchanged(self, column, expected): index=["pig", "rabbit", "duck", "chicken", "horse"], ) - with tm.assert_produces_warning(UserWarning): - # _check_plot_works creates subplots inside, - # meanwhile df.hist here creates subplots itself for each column. - # It leads to warnings like this: - # UserWarning: To output multiple subplots, - # the figure containing the passed axes is being cleared - # Similar warnings were observed in GH #13188 - axes = _check_plot_works(df.hist, column=column, layout=(1, 3)) - result = [axes[0, i].get_title() for i in range(3)] - assert result == expected + # Use _check_single_plot_works when plotting method generate subplots itself + # if not, use _check_plot_works + axes = _check_plot_default_axes_works(df.hist, column=column, layout=(1, 3)) + result = [axes[0, i].get_title() for i in range(3)] + assert result == expected @pytest.mark.parametrize("by", [None, "c"]) @pytest.mark.parametrize("column", [None, "b"]) @@ -388,7 +383,12 @@ def test_hist_with_legend(self, by, column): index = Index(15 * ["1"] + 15 * ["2"], name="c") df = DataFrame(np.random.randn(30, 2), index=index, columns=["a", "b"]) - axes = _check_single_plot_works(df.hist, legend=True, by=by, column=column) + axes = _check_plot_default_axes_works( + df.hist, + legend=True, + by=by, + column=column, + ) self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout) if by is None and column is None: From ee1d941350d69c90aa8ec22dc4603ae3bd0fda68 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Mon, 9 Nov 2020 13:59:57 +0700 Subject: [PATCH 3/4] REF: refactor using default_axes kwarg --- pandas/tests/plotting/common.py | 74 +++++++++-------------- pandas/tests/plotting/test_hist_method.py | 30 +++++---- 2 files changed, 47 insertions(+), 57 deletions(-) diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 803b764014910..80152e223d478 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -550,73 +550,57 @@ def _unpack_cycler(self, rcParams, field="color"): return [v[field] for v in rcParams["axes.prop_cycle"]] -def _check_plot_default_axes_works(f, filterwarnings="always", **kwargs): +def _check_plot_works(f, filterwarnings="always", default_axes=False, **kwargs): """ Create plot and ensure that plot return object is valid. - - Similar to _check_plot_works, but this one plots at the default axes, - without creating subplots in advance. - Use this function when plotting method generate subplots itself, - otherwise use _check_plot_works. """ import matplotlib.pyplot as plt + if default_axes: + gen_plots = _gen_default_plot + else: + gen_plots = _gen_two_subplots + ret = None with warnings.catch_warnings(): warnings.simplefilter(filterwarnings) try: - try: - fig = kwargs["figure"] - except KeyError: - fig = plt.gcf() - + fig = kwargs.get("figure", plt.gcf()) plt.clf() - ret = f(**kwargs) - - tm.assert_is_valid_plot_return_object(ret) + for ret in gen_plots(f, fig, **kwargs): + tm.assert_is_valid_plot_return_object(ret) with tm.ensure_clean(return_filelike=True) as path: plt.savefig(path) + + except Exception as err: + raise err finally: tm.close(fig) return ret -def _check_plot_works(f, filterwarnings="always", **kwargs): - import matplotlib.pyplot as plt - - ret = None - with warnings.catch_warnings(): - warnings.simplefilter(filterwarnings) - try: - try: - fig = kwargs["figure"] - except KeyError: - fig = plt.gcf() - - plt.clf() - - kwargs.get("ax", fig.add_subplot(211)) - ret = f(**kwargs) - - tm.assert_is_valid_plot_return_object(ret) - - if f is pd.plotting.bootstrap_plot: - assert "ax" not in kwargs - else: - kwargs["ax"] = fig.add_subplot(212) +def _gen_default_plot(f, fig, **kwargs): + """ + Create plot in a default way. + """ + yield f(**kwargs) - ret = f(**kwargs) - tm.assert_is_valid_plot_return_object(ret) - with tm.ensure_clean(return_filelike=True) as path: - plt.savefig(path) - finally: - tm.close(fig) - - return ret +def _gen_two_subplots(f, fig, **kwargs): + """ + Create plot on two subplots forcefully created. + """ + kwargs.get("ax", fig.add_subplot(211)) + yield f(**kwargs) + + if f is pd.plotting.bootstrap_plot: + assert "ax" not in kwargs + else: + kwargs["ax"] = fig.add_subplot(212) + yield f(**kwargs) def curpath(): diff --git a/pandas/tests/plotting/test_hist_method.py b/pandas/tests/plotting/test_hist_method.py index 72fb9f247abe0..ab0024559333e 100644 --- a/pandas/tests/plotting/test_hist_method.py +++ b/pandas/tests/plotting/test_hist_method.py @@ -7,11 +7,7 @@ from pandas import DataFrame, Index, Series, to_datetime import pandas._testing as tm -from pandas.tests.plotting.common import ( - TestPlotBase, - _check_plot_default_axes_works, - _check_plot_works, -) +from pandas.tests.plotting.common import TestPlotBase, _check_plot_works @td.skip_if_no_mpl @@ -156,7 +152,8 @@ def test_hist_with_legend(self, by, expected_axes_num, expected_layout): s = Series(np.random.randn(30), index=index, name="a") s.index.name = "b" - axes = _check_plot_default_axes_works(s.hist, legend=True, by=by) + # Use default_axes=True when plotting method generate subplots itself + axes = _check_plot_works(s.hist, default_axes=True, legend=True, by=by) self._check_axes_shape(axes, axes_num=expected_axes_num, layout=expected_layout) self._check_legend_labels(axes, "a") @@ -336,7 +333,8 @@ def test_tight_layout(self): dtype=np.int64, ) ) - _check_plot_default_axes_works(df.hist) + # Use default_axes=True when plotting method generate subplots itself + _check_plot_works(df.hist, default_axes=True) self.plt.tight_layout() tm.close() @@ -349,8 +347,10 @@ def test_hist_subplot_xrot(self): "animal": ["pig", "rabbit", "pig", "pig", "rabbit"], } ) - axes = _check_plot_default_axes_works( + # Use default_axes=True when plotting method generate subplots itself + axes = _check_plot_works( df.hist, + default_axes=True, filterwarnings="always", column="length", by="animal", @@ -378,9 +378,13 @@ def test_hist_column_order_unchanged(self, column, expected): index=["pig", "rabbit", "duck", "chicken", "horse"], ) - # Use _check_single_plot_works when plotting method generate subplots itself - # if not, use _check_plot_works - axes = _check_plot_default_axes_works(df.hist, column=column, layout=(1, 3)) + # Use default_axes=True when plotting method generate subplots itself + axes = _check_plot_works( + df.hist, + default_axes=True, + column=column, + layout=(1, 3), + ) result = [axes[0, i].get_title() for i in range(3)] assert result == expected @@ -412,8 +416,10 @@ def test_hist_with_legend(self, by, column): index = Index(15 * ["1"] + 15 * ["2"], name="c") df = DataFrame(np.random.randn(30, 2), index=index, columns=["a", "b"]) - axes = _check_plot_default_axes_works( + # Use default_axes=True when plotting method generate subplots itself + axes = _check_plot_works( df.hist, + default_axes=True, legend=True, by=by, column=column, From d33322e24b58ba23c167c29509d2647ec62dd701 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Tue, 10 Nov 2020 00:36:02 +0700 Subject: [PATCH 4/4] DOC: add docstring to _check_plot_works --- pandas/tests/plotting/common.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py index 80152e223d478..c868c8d4fba07 100644 --- a/pandas/tests/plotting/common.py +++ b/pandas/tests/plotting/common.py @@ -553,6 +553,32 @@ def _unpack_cycler(self, rcParams, field="color"): def _check_plot_works(f, filterwarnings="always", default_axes=False, **kwargs): """ Create plot and ensure that plot return object is valid. + + Parameters + ---------- + f : func + Plotting function. + filterwarnings : str + Warnings filter. + See https://docs.python.org/3/library/warnings.html#warning-filter + default_axes : bool, optional + If False (default): + - If `ax` not in `kwargs`, then create subplot(211) and plot there + - Create new subplot(212) and plot there as well + - Mind special corner case for bootstrap_plot (see `_gen_two_subplots`) + If True: + - Simply run plotting function with kwargs provided + - All required axes instances will be created automatically + - It is recommended to use it when the plotting function + creates multiple axes itself. It helps avoid warnings like + 'UserWarning: To output multiple subplots, + the figure containing the passed axes is being cleared' + **kwargs + Keyword arguments passed to the plotting function. + + Returns + ------- + Plot object returned by the last plotting. """ import matplotlib.pyplot as plt