From c59ad2cf96b3574203f1c3f8b4e4fcee7ca7d9c1 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 19:47:17 -0500 Subject: [PATCH 1/9] BUG: Fix support for xlabel/ylabel in boxplots. --- pandas/plotting/_matplotlib/boxplot.py | 42 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index a2089de294e22..04292884a2900 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -200,7 +200,10 @@ def _make_legend(self): pass def _post_plot_logic(self, ax, data): - pass + if self.xlabel: + ax.set_xlabel(pprint_thing(self.xlabel)) + if self.ylabel: + ax.set_ylabel(pprint_thing(self.ylabel)) @property def orientation(self): @@ -237,20 +240,26 @@ def _grouped_plot_by_column( columns = data._get_numeric_data().columns.difference(by) naxes = len(columns) fig, axes = create_subplots( - naxes=naxes, sharex=True, sharey=True, figsize=figsize, ax=ax, layout=layout + naxes=naxes, sharex=kwargs.pop("sharex", True), + sharey=kwargs.pop("sharey", True), figsize=figsize, ax=ax, layout=layout ) _axes = flatten_axes(axes) + xlabel, ylabel = kwargs.pop("xlabel", None), kwargs.pop("ylabel", None) + if kwargs.get("vert", 1): + xlabel = xlabel or by + else: + ylabel = ylabel or by + ax_values = [] for i, col in enumerate(columns): ax = _axes[i] gp_col = grouped[col] keys, values = zip(*gp_col) - re_plotf = plotf(keys, values, ax, **kwargs) + re_plotf = plotf(keys, values, ax, xlabel=xlabel, ylabel=ylabel, **kwargs) ax.set_title(col) - ax.set_xlabel(pprint_thing(by)) ax_values.append(re_plotf) ax.grid(grid) @@ -332,18 +341,26 @@ def maybe_color_bp(bp, **kwds): if not kwds.get("capprops"): setp(bp["caps"], color=colors[3], alpha=1) - def plot_group(keys, values, ax: Axes): + def plot_group(keys, values, ax: Axes, **kwds): + # xlabel/ylabel need to be popped out before plotting happens + xlabel, ylabel = kwds.pop('xlabel', None), kwds.pop('ylabel', None) + if xlabel: + ax.set_xlabel(pprint_thing(xlabel)) + if ylabel: + ax.set_ylabel(pprint_thing(ylabel)) + keys = [pprint_thing(x) for x in keys] values = [np.asarray(remove_na_arraylike(v), dtype=object) for v in values] bp = ax.boxplot(values, **kwds) if fontsize is not None: ax.tick_params(axis="both", labelsize=fontsize) - if kwds.get("vert", 1): - ticks = ax.get_xticks() - if len(ticks) != len(keys): - i, remainder = divmod(len(ticks), len(keys)) - assert remainder == 0, remainder - keys *= i + is_vertical = kwds.get("vert", 1) + 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) @@ -379,6 +396,7 @@ def plot_group(keys, values, ax: Axes): ax=ax, layout=layout, return_type=return_type, + **kwds ) else: if return_type is None: @@ -401,7 +419,7 @@ def plot_group(keys, values, ax: Axes): else: data = data[columns] - result = plot_group(columns, data.values.T, ax) + result = plot_group(columns, data.values.T, ax, **kwds) ax.grid(grid) return result From d1afff891ffe80016d2cb236fc926d1d7e69dcc2 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 19:47:50 -0500 Subject: [PATCH 2/9] TST: Add tests for checking boxplot axis labels. --- pandas/tests/plotting/test_boxplot_method.py | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 5c543f96cb55f..94ec4020ab102 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -3,6 +3,8 @@ import itertools import string +from pandas.io.formats.printing import pprint_thing + import numpy as np import pytest @@ -254,6 +256,53 @@ def test_specified_props_kwd(self, props, expected): assert result[expected][0].get_color() == "C1" + def test_xlabel_ylabel(self): + df = DataFrame({ + "a": np.random.randn(100), "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + }) + xlabel, ylabel = "x", "y" + ax = df.plot(kind="box", xlabel=xlabel, ylabel=ylabel) + assert ax.get_xlabel() == xlabel + assert ax.get_ylabel() == ylabel + self.plt.close() + + ax = df.plot(kind="box", vert=False, xlabel=xlabel, ylabel=ylabel) + assert ax.get_xlabel() == xlabel + assert ax.get_ylabel() == ylabel + self.plt.close() + + ax = df.boxplot(xlabel=xlabel, ylabel=ylabel) + assert ax.get_xlabel() == xlabel + assert ax.get_ylabel() == ylabel + self.plt.close() + + ax = df.boxplot(vert=False, xlabel=xlabel, ylabel=ylabel) + assert ax.get_xlabel() == xlabel + assert ax.get_ylabel() == ylabel + self.plt.close() + + ax = df.boxplot(by="group", xlabel=xlabel, ylabel=ylabel) + for subplot in ax: + assert subplot.get_xlabel() == xlabel + assert subplot.get_ylabel() == ylabel + + ax = df.boxplot(by="group", vert=False, xlabel=xlabel, ylabel=ylabel) + for subplot in ax: + assert subplot.get_xlabel() == xlabel + assert subplot.get_ylabel() == ylabel + self.plt.close() + + ax = df.boxplot(by="group") + for subplot in ax: + assert subplot.get_xlabel() == pprint_thing(["group"]) + self.plt.close() + + ax = df.boxplot(by="group", vert=False) + for subplot in ax: + assert subplot.get_ylabel() == pprint_thing(["group"]) + self.plt.close() + @td.skip_if_no_mpl class TestDataFrameGroupByPlots(TestPlotBase): From 80bafc7b1301fdd5ca35131f6931bba567c9150f Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 20:05:26 -0500 Subject: [PATCH 3/9] DOC: Update whatsnew 1.5.0 --- doc/source/whatsnew/v1.5.0.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index b259f182a1197..7f06fe39e2f6c 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -237,6 +237,9 @@ Period Plotting ^^^^^^^^ - Bug in :meth:`DataFrame.plot.barh` that prevented labeling the x-axis and ``xlabel`` updating the y-axis label (:issue:`45144`) +- Bug in :meth:`DataFrame.plot.box` that prevented labeling the x-axis (:issue:`45463`) +- Bug in :meth:`DataFrame.boxplot` that prevented passing in ``xlabel`` and ``ylabel`` (:issue:`45463`) +- Bug in :meth:`DataFrame.boxplot` that prevented specifying ``vert=False`` (:issue:`36918`) - Groupby/resample/rolling From 9c5489e3ad485020c9179334e973d2171a9cb008 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 20:13:24 -0500 Subject: [PATCH 4/9] CLN: Add comments pointing to PR for changes. --- pandas/plotting/_matplotlib/boxplot.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 04292884a2900..57c0dccdcaa26 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -200,6 +200,7 @@ def _make_legend(self): pass def _post_plot_logic(self, ax, data): + # GH 45465: make sure that the boxplot doesn't ignore xlabel/ylabel if self.xlabel: ax.set_xlabel(pprint_thing(self.xlabel)) if self.ylabel: @@ -246,6 +247,7 @@ def _grouped_plot_by_column( _axes = flatten_axes(axes) + # GH 45465: move the "by" label based on "vert" xlabel, ylabel = kwargs.pop("xlabel", None), kwargs.pop("ylabel", None) if kwargs.get("vert", 1): xlabel = xlabel or by @@ -342,7 +344,7 @@ def maybe_color_bp(bp, **kwds): setp(bp["caps"], color=colors[3], alpha=1) def plot_group(keys, values, ax: Axes, **kwds): - # xlabel/ylabel need to be popped out before plotting happens + # GH 45465: xlabel/ylabel need to be popped out before plotting happens xlabel, ylabel = kwds.pop('xlabel', None), kwds.pop('ylabel', None) if xlabel: ax.set_xlabel(pprint_thing(xlabel)) @@ -354,6 +356,8 @@ def plot_group(keys, values, ax: Axes, **kwds): bp = ax.boxplot(values, **kwds) if fontsize is not None: ax.tick_params(axis="both", labelsize=fontsize) + + # GH 45465: x/y are flipped when "vert" changes is_vertical = kwds.get("vert", 1) ticks = ax.get_xticks() if is_vertical else ax.get_yticks() if len(ticks) != len(keys): From 6a5b9f952f4e47bb23cf5f49a0fbe6de208b3ee8 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:36:13 -0500 Subject: [PATCH 5/9] CLN: Default vert to True in get(). --- pandas/plotting/_matplotlib/boxplot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 57c0dccdcaa26..90175257678e6 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -249,7 +249,7 @@ def _grouped_plot_by_column( # GH 45465: move the "by" label based on "vert" xlabel, ylabel = kwargs.pop("xlabel", None), kwargs.pop("ylabel", None) - if kwargs.get("vert", 1): + if kwargs.get("vert", True): xlabel = xlabel or by else: ylabel = ylabel or by @@ -358,7 +358,7 @@ 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", 1) + 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)) From 845bd8ec260afde2a50e8d7179a068433f9098e1 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:46:04 -0500 Subject: [PATCH 6/9] TST: Split new test into 3 distinct tests. --- pandas/tests/plotting/test_boxplot_method.py | 50 +++++++++----------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 94ec4020ab102..8964eea6600d4 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -256,51 +256,45 @@ def test_specified_props_kwd(self, props, expected): assert result[expected][0].get_color() == "C1" - def test_xlabel_ylabel(self): + @pytest.mark.parametrize("vert", [True, False]) + def test_plot_xlabel_ylabel(self, vert): df = DataFrame({ "a": np.random.randn(100), "b": np.random.randn(100), "group": np.random.choice(["group1", "group2"], 100) }) xlabel, ylabel = "x", "y" - ax = df.plot(kind="box", xlabel=xlabel, ylabel=ylabel) + ax = df.plot(kind="box", vert=vert, xlabel=xlabel, ylabel=ylabel) assert ax.get_xlabel() == xlabel assert ax.get_ylabel() == ylabel - self.plt.close() - ax = df.plot(kind="box", vert=False, xlabel=xlabel, ylabel=ylabel) - assert ax.get_xlabel() == xlabel - assert ax.get_ylabel() == ylabel - self.plt.close() - - ax = df.boxplot(xlabel=xlabel, ylabel=ylabel) + @pytest.mark.parametrize("vert", [True, False]) + def test_boxplot_xlabel_ylabel(self, vert): + df = DataFrame({ + "a": np.random.randn(100), "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + }) + xlabel, ylabel = "x", "y" + ax = df.boxplot(vert=vert, xlabel=xlabel, ylabel=ylabel) assert ax.get_xlabel() == xlabel assert ax.get_ylabel() == ylabel - self.plt.close() - ax = df.boxplot(vert=False, xlabel=xlabel, ylabel=ylabel) - assert ax.get_xlabel() == xlabel - assert ax.get_ylabel() == ylabel - self.plt.close() - - ax = df.boxplot(by="group", xlabel=xlabel, ylabel=ylabel) - for subplot in ax: - assert subplot.get_xlabel() == xlabel - assert subplot.get_ylabel() == ylabel - - ax = df.boxplot(by="group", vert=False, xlabel=xlabel, ylabel=ylabel) + @pytest.mark.parametrize("vert", [True, False]) + def test_boxplot_group_xlabel_ylabel(self, vert): + df = DataFrame({ + "a": np.random.randn(100), "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + }) + xlabel, ylabel = "x", "y" + ax = df.boxplot(by="group", vert=vert, xlabel=xlabel, ylabel=ylabel) for subplot in ax: assert subplot.get_xlabel() == xlabel assert subplot.get_ylabel() == ylabel self.plt.close() - ax = df.boxplot(by="group") - for subplot in ax: - assert subplot.get_xlabel() == pprint_thing(["group"]) - self.plt.close() - - ax = df.boxplot(by="group", vert=False) + ax = df.boxplot(by="group", vert=vert) for subplot in ax: - assert subplot.get_ylabel() == pprint_thing(["group"]) + target_label = subplot.get_xlabel() if vert else subplot.get_ylabel() + assert target_label == pprint_thing(["group"]) self.plt.close() From f9a4f39516aafa3ae1d21ccf902629a307104df0 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:53:27 -0500 Subject: [PATCH 7/9] CLN: Move pprint_thing() import near other pandas imports. --- pandas/tests/plotting/test_boxplot_method.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 8964eea6600d4..dc53c180a5e95 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -3,8 +3,6 @@ import itertools import string -from pandas.io.formats.printing import pprint_thing - import numpy as np import pytest @@ -17,6 +15,7 @@ date_range, timedelta_range, ) +from pandas.io.formats.printing import pprint_thing import pandas._testing as tm from pandas.tests.plotting.common import ( TestPlotBase, From d82a93757bbb334942676772a23406b5686780da Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Wed, 19 Jan 2022 08:45:37 -0500 Subject: [PATCH 8/9] CLN: Linting. --- pandas/plotting/_matplotlib/boxplot.py | 12 ++++--- pandas/tests/plotting/test_boxplot_method.py | 35 ++++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 90175257678e6..74605021385f5 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -241,8 +241,12 @@ def _grouped_plot_by_column( columns = data._get_numeric_data().columns.difference(by) naxes = len(columns) fig, axes = create_subplots( - naxes=naxes, sharex=kwargs.pop("sharex", True), - sharey=kwargs.pop("sharey", True), figsize=figsize, ax=ax, layout=layout + naxes=naxes, + sharex=kwargs.pop("sharex", True), + sharey=kwargs.pop("sharey", True), + figsize=figsize, + ax=ax, + layout=layout, ) _axes = flatten_axes(axes) @@ -345,7 +349,7 @@ def maybe_color_bp(bp, **kwds): def plot_group(keys, values, ax: Axes, **kwds): # GH 45465: xlabel/ylabel need to be popped out before plotting happens - xlabel, ylabel = kwds.pop('xlabel', None), kwds.pop('ylabel', None) + xlabel, ylabel = kwds.pop("xlabel", None), kwds.pop("ylabel", None) if xlabel: ax.set_xlabel(pprint_thing(xlabel)) if ylabel: @@ -400,7 +404,7 @@ def plot_group(keys, values, ax: Axes, **kwds): ax=ax, layout=layout, return_type=return_type, - **kwds + **kwds, ) else: if return_type is None: diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index dc53c180a5e95..68acd309aa75a 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -15,13 +15,13 @@ date_range, timedelta_range, ) -from pandas.io.formats.printing import pprint_thing import pandas._testing as tm from pandas.tests.plotting.common import ( TestPlotBase, _check_plot_works, ) +from pandas.io.formats.printing import pprint_thing import pandas.plotting as plotting pytestmark = pytest.mark.slow @@ -257,10 +257,13 @@ def test_specified_props_kwd(self, props, expected): @pytest.mark.parametrize("vert", [True, False]) def test_plot_xlabel_ylabel(self, vert): - df = DataFrame({ - "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) - }) + df = DataFrame( + { + "a": np.random.randn(100), + "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + } + ) xlabel, ylabel = "x", "y" ax = df.plot(kind="box", vert=vert, xlabel=xlabel, ylabel=ylabel) assert ax.get_xlabel() == xlabel @@ -268,10 +271,13 @@ def test_plot_xlabel_ylabel(self, vert): @pytest.mark.parametrize("vert", [True, False]) def test_boxplot_xlabel_ylabel(self, vert): - df = DataFrame({ - "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) - }) + df = DataFrame( + { + "a": np.random.randn(100), + "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + } + ) xlabel, ylabel = "x", "y" ax = df.boxplot(vert=vert, xlabel=xlabel, ylabel=ylabel) assert ax.get_xlabel() == xlabel @@ -279,10 +285,13 @@ def test_boxplot_xlabel_ylabel(self, vert): @pytest.mark.parametrize("vert", [True, False]) def test_boxplot_group_xlabel_ylabel(self, vert): - df = DataFrame({ - "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) - }) + df = DataFrame( + { + "a": np.random.randn(100), + "b": np.random.randn(100), + "group": np.random.choice(["group1", "group2"], 100) + } + ) xlabel, ylabel = "x", "y" ax = df.boxplot(by="group", vert=vert, xlabel=xlabel, ylabel=ylabel) for subplot in ax: From 4988cf2fc98922d9781456add4ad2568c23a3bb0 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Wed, 19 Jan 2022 08:52:35 -0500 Subject: [PATCH 9/9] CLN: Add trailing comma to tests. --- pandas/tests/plotting/test_boxplot_method.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 68acd309aa75a..d2a7d0fdb0cc2 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -261,7 +261,7 @@ def test_plot_xlabel_ylabel(self, vert): { "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) + "group": np.random.choice(["group1", "group2"], 100), } ) xlabel, ylabel = "x", "y" @@ -275,7 +275,7 @@ def test_boxplot_xlabel_ylabel(self, vert): { "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) + "group": np.random.choice(["group1", "group2"], 100), } ) xlabel, ylabel = "x", "y" @@ -289,7 +289,7 @@ def test_boxplot_group_xlabel_ylabel(self, vert): { "a": np.random.randn(100), "b": np.random.randn(100), - "group": np.random.choice(["group1", "group2"], 100) + "group": np.random.choice(["group1", "group2"], 100), } ) xlabel, ylabel = "x", "y"