From 76164baf4467f6b7ae17c1f9ff6581df9a8d9156 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 1 Sep 2023 22:53:08 +0200 Subject: [PATCH 1/4] BUG manage sharey in plot.box with vert=False --- pandas/plotting/_matplotlib/boxplot.py | 36 ++++++++++---------- pandas/plotting/_matplotlib/tools.py | 3 +- pandas/tests/plotting/test_boxplot_method.py | 16 +++++++++ 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 83cb8a6ab67dd..707f1a5cc65f7 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -40,6 +40,18 @@ from pandas._typing import MatplotlibColor +def _set_ticklabels(ax: Axes, labels: list[str], is_vertical: bool, **kwargs) -> None: + ticks = ax.get_xticks() if is_vertical else ax.get_yticks() + if len(ticks) != len(labels): + i, remainder = divmod(len(ticks), len(labels)) + assert remainder == 0, remainder + labels *= i + if is_vertical: + ax.set_xticklabels(labels, **kwargs) + else: + ax.set_yticklabels(labels, **kwargs) + + class BoxPlot(LinePlot): @property def _kind(self) -> Literal["box"]: @@ -209,13 +221,9 @@ def _make_plot(self) -> None: labels = [pprint_thing(left) for left in labels] if not self.use_index: labels = [pprint_thing(key) for key in range(len(labels))] - self._set_ticklabels(ax, labels) - - def _set_ticklabels(self, ax: Axes, labels: list[str]) -> None: - if self.orientation == "vertical": - ax.set_xticklabels(labels) - else: - ax.set_yticklabels(labels) + _set_ticklabels( + ax=ax, labels=labels, is_vertical=self.orientation == "vertical" + ) def _make_legend(self) -> None: pass @@ -382,17 +390,9 @@ def plot_group(keys, values, ax: Axes, **kwds): ax.tick_params(axis="both", labelsize=fontsize) # GH 45465: x/y are flipped when "vert" changes - is_vertical = kwds.get("vert", True) - ticks = ax.get_xticks() if is_vertical else ax.get_yticks() - if len(ticks) != len(keys): - i, remainder = divmod(len(ticks), len(keys)) - assert remainder == 0, remainder - keys *= i - if is_vertical: - ax.set_xticklabels(keys, rotation=rot) - else: - ax.set_yticklabels(keys, rotation=rot) - maybe_color_bp(bp, **kwds) + _set_ticklabels( + ax=ax, labels=keys, is_vertical=kwds.get("vert", True), rotation=rot + ) # Return axes in multiplot case, maybe revisit later # 985 if return_type == "dict": diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 8c0e401f991a6..77e76f91f3098 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -431,7 +431,8 @@ def handle_shared_axes( if is_first_col(ax): continue if sharey or _has_externally_shared_axis(ax, "y"): - _remove_labels_from_axis(ax.yaxis) + # _remove_labels_from_axis(ax.yaxis) + pass def flatten_axes(axes: Axes | Sequence[Axes]) -> np.ndarray: diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 555b9fd0c82c2..fc2ef7c2777a2 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -329,6 +329,22 @@ def test_plot_xlabel_ylabel(self, vert): assert ax.get_xlabel() == xlabel assert ax.get_ylabel() == ylabel + @pytest.mark.parametrize("vert", [True, False]) + def test_xxx(self, vert): + df1 = DataFrame( + np.random.default_rng(2).integers(0, 100, size=(100, 4)), + columns=list("ABCD"), + ) + df2 = DataFrame( + np.random.default_rng(2).integers(0, 100, size=(100, 4)), + columns=list("ABCD"), + ) + + f, axs = plt.subplots(1, 2, figsize=(10, 7), sharey=True) + df1.plot.box(ax=axs[0], showfliers=False, vert=vert) + df2.plot.box(ax=axs[1], showfliers=False, vert=vert) + mpl.pyplot.close() + @pytest.mark.parametrize("vert", [True, False]) def test_boxplot_xlabel_ylabel(self, vert): df = DataFrame( From 9994adcfa12064ec0eb535786c43a93ec6aff15f Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 1 Sep 2023 22:57:32 +0200 Subject: [PATCH 2/4] fix --- pandas/plotting/_matplotlib/boxplot.py | 5 ++++- pandas/plotting/_matplotlib/tools.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 707f1a5cc65f7..6cbf626e8c797 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -205,7 +205,9 @@ def _make_plot(self) -> None: ) self.maybe_color_bp(bp) self._return_obj[label] = ret - self._set_ticklabels(ax, ticklabels) + _set_ticklabels( + ax=ax, labels=ticklabels, is_vertical=self.orientation == "vertical" + ) else: y = self.data.values.T ax = self._get_ax(0) @@ -393,6 +395,7 @@ def plot_group(keys, values, ax: Axes, **kwds): _set_ticklabels( ax=ax, labels=keys, is_vertical=kwds.get("vert", True), rotation=rot ) + maybe_color_bp(bp, **kwds) # Return axes in multiplot case, maybe revisit later # 985 if return_type == "dict": diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 77e76f91f3098..8c0e401f991a6 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -431,8 +431,7 @@ def handle_shared_axes( if is_first_col(ax): continue if sharey or _has_externally_shared_axis(ax, "y"): - # _remove_labels_from_axis(ax.yaxis) - pass + _remove_labels_from_axis(ax.yaxis) def flatten_axes(axes: Axes | Sequence[Axes]) -> np.ndarray: From bcabafabe3fb1eb8eaa929a0f2e4df1b210a4b08 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 1 Sep 2023 23:13:22 +0200 Subject: [PATCH 3/4] add entry in whats new --- doc/source/whatsnew/v2.2.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 17abb3debe3e7..f05f1fc4cf5ad 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -235,7 +235,7 @@ Period Plotting ^^^^^^^^ -- +- Bug in :meth:`DataFrame.plot.box` with ``vert=False`` and a matplotlib ``Axes`` created with ``sharey=True`` (:issue:`54941`) - Groupby/resample/rolling From 0813a1949365d51c3df6c3d53f6f9f07a5755150 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Fri, 1 Sep 2023 23:44:45 +0200 Subject: [PATCH 4/4] iter --- pandas/plotting/_matplotlib/boxplot.py | 6 +++++ pandas/tests/plotting/test_boxplot_method.py | 24 ++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 6cbf626e8c797..5fcea796a9c6e 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -41,6 +41,12 @@ def _set_ticklabels(ax: Axes, labels: list[str], is_vertical: bool, **kwargs) -> None: + """Set the tick labels of a given axis. + + Due to https://github.com/matplotlib/matplotlib/pull/17266, we need to handle the + case of repeated ticks (due to `FixedLocator`) and thus we duplicate the number of + labels. + """ ticks = ax.get_xticks() if is_vertical else ax.get_yticks() if len(ticks) != len(labels): i, remainder = divmod(len(ticks), len(labels)) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index fc2ef7c2777a2..76f7fa1f22eec 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -330,19 +330,19 @@ def test_plot_xlabel_ylabel(self, vert): assert ax.get_ylabel() == ylabel @pytest.mark.parametrize("vert", [True, False]) - def test_xxx(self, vert): - df1 = DataFrame( - np.random.default_rng(2).integers(0, 100, size=(100, 4)), - columns=list("ABCD"), - ) - df2 = DataFrame( - np.random.default_rng(2).integers(0, 100, size=(100, 4)), - columns=list("ABCD"), - ) + def test_plot_box(self, vert): + # GH 54941 + rng = np.random.default_rng(2) + df1 = DataFrame(rng.integers(0, 100, size=(100, 4)), columns=list("ABCD")) + df2 = DataFrame(rng.integers(0, 100, size=(100, 4)), columns=list("ABCD")) - f, axs = plt.subplots(1, 2, figsize=(10, 7), sharey=True) - df1.plot.box(ax=axs[0], showfliers=False, vert=vert) - df2.plot.box(ax=axs[1], showfliers=False, vert=vert) + xlabel, ylabel = "x", "y" + _, axs = plt.subplots(ncols=2, figsize=(10, 7), sharey=True) + df1.plot.box(ax=axs[0], vert=vert, xlabel=xlabel, ylabel=ylabel) + df2.plot.box(ax=axs[1], vert=vert, xlabel=xlabel, ylabel=ylabel) + for ax in axs: + assert ax.get_xlabel() == xlabel + assert ax.get_ylabel() == ylabel mpl.pyplot.close() @pytest.mark.parametrize("vert", [True, False])