Skip to content

Commit 5f8df6b

Browse files
authored
BUG: Boxplots ignore xlabel and boxplot() doesn't support horizontal layout (#45465)
* BUG: Fix support for xlabel/ylabel in boxplots. * TST: Add tests for checking boxplot axis labels. * DOC: Update whatsnew 1.5.0 * CLN: Add comments pointing to PR for changes. * CLN: Default vert to True in get(). * TST: Split new test into 3 distinct tests. * CLN: Move pprint_thing() import near other pandas imports. * CLN: Linting. * CLN: Add trailing comma to tests.
1 parent 7451de8 commit 5f8df6b

File tree

3 files changed

+92
-12
lines changed

3 files changed

+92
-12
lines changed

doc/source/whatsnew/v1.5.0.rst

+3
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ Period
291291
Plotting
292292
^^^^^^^^
293293
- Bug in :meth:`DataFrame.plot.barh` that prevented labeling the x-axis and ``xlabel`` updating the y-axis label (:issue:`45144`)
294+
- Bug in :meth:`DataFrame.plot.box` that prevented labeling the x-axis (:issue:`45463`)
295+
- Bug in :meth:`DataFrame.boxplot` that prevented passing in ``xlabel`` and ``ylabel`` (:issue:`45463`)
296+
- Bug in :meth:`DataFrame.boxplot` that prevented specifying ``vert=False`` (:issue:`36918`)
294297
-
295298

296299
Groupby/resample/rolling

pandas/plotting/_matplotlib/boxplot.py

+38-12
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ def _make_legend(self):
200200
pass
201201

202202
def _post_plot_logic(self, ax, data):
203-
pass
203+
# GH 45465: make sure that the boxplot doesn't ignore xlabel/ylabel
204+
if self.xlabel:
205+
ax.set_xlabel(pprint_thing(self.xlabel))
206+
if self.ylabel:
207+
ax.set_ylabel(pprint_thing(self.ylabel))
204208

205209
@property
206210
def orientation(self):
@@ -237,20 +241,31 @@ def _grouped_plot_by_column(
237241
columns = data._get_numeric_data().columns.difference(by)
238242
naxes = len(columns)
239243
fig, axes = create_subplots(
240-
naxes=naxes, sharex=True, sharey=True, figsize=figsize, ax=ax, layout=layout
244+
naxes=naxes,
245+
sharex=kwargs.pop("sharex", True),
246+
sharey=kwargs.pop("sharey", True),
247+
figsize=figsize,
248+
ax=ax,
249+
layout=layout,
241250
)
242251

243252
_axes = flatten_axes(axes)
244253

254+
# GH 45465: move the "by" label based on "vert"
255+
xlabel, ylabel = kwargs.pop("xlabel", None), kwargs.pop("ylabel", None)
256+
if kwargs.get("vert", True):
257+
xlabel = xlabel or by
258+
else:
259+
ylabel = ylabel or by
260+
245261
ax_values = []
246262

247263
for i, col in enumerate(columns):
248264
ax = _axes[i]
249265
gp_col = grouped[col]
250266
keys, values = zip(*gp_col)
251-
re_plotf = plotf(keys, values, ax, **kwargs)
267+
re_plotf = plotf(keys, values, ax, xlabel=xlabel, ylabel=ylabel, **kwargs)
252268
ax.set_title(col)
253-
ax.set_xlabel(pprint_thing(by))
254269
ax_values.append(re_plotf)
255270
ax.grid(grid)
256271

@@ -332,18 +347,28 @@ def maybe_color_bp(bp, **kwds):
332347
if not kwds.get("capprops"):
333348
setp(bp["caps"], color=colors[3], alpha=1)
334349

335-
def plot_group(keys, values, ax: Axes):
350+
def plot_group(keys, values, ax: Axes, **kwds):
351+
# GH 45465: xlabel/ylabel need to be popped out before plotting happens
352+
xlabel, ylabel = kwds.pop("xlabel", None), kwds.pop("ylabel", None)
353+
if xlabel:
354+
ax.set_xlabel(pprint_thing(xlabel))
355+
if ylabel:
356+
ax.set_ylabel(pprint_thing(ylabel))
357+
336358
keys = [pprint_thing(x) for x in keys]
337359
values = [np.asarray(remove_na_arraylike(v), dtype=object) for v in values]
338360
bp = ax.boxplot(values, **kwds)
339361
if fontsize is not None:
340362
ax.tick_params(axis="both", labelsize=fontsize)
341-
if kwds.get("vert", 1):
342-
ticks = ax.get_xticks()
343-
if len(ticks) != len(keys):
344-
i, remainder = divmod(len(ticks), len(keys))
345-
assert remainder == 0, remainder
346-
keys *= i
363+
364+
# GH 45465: x/y are flipped when "vert" changes
365+
is_vertical = kwds.get("vert", True)
366+
ticks = ax.get_xticks() if is_vertical else ax.get_yticks()
367+
if len(ticks) != len(keys):
368+
i, remainder = divmod(len(ticks), len(keys))
369+
assert remainder == 0, remainder
370+
keys *= i
371+
if is_vertical:
347372
ax.set_xticklabels(keys, rotation=rot)
348373
else:
349374
ax.set_yticklabels(keys, rotation=rot)
@@ -379,6 +404,7 @@ def plot_group(keys, values, ax: Axes):
379404
ax=ax,
380405
layout=layout,
381406
return_type=return_type,
407+
**kwds,
382408
)
383409
else:
384410
if return_type is None:
@@ -401,7 +427,7 @@ def plot_group(keys, values, ax: Axes):
401427
else:
402428
data = data[columns]
403429

404-
result = plot_group(columns, data.values.T, ax)
430+
result = plot_group(columns, data.values.T, ax, **kwds)
405431
ax.grid(grid)
406432

407433
return result

pandas/tests/plotting/test_boxplot_method.py

+51
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
_check_plot_works,
2222
)
2323

24+
from pandas.io.formats.printing import pprint_thing
2425
import pandas.plotting as plotting
2526

2627
pytestmark = pytest.mark.slow
@@ -254,6 +255,56 @@ def test_specified_props_kwd(self, props, expected):
254255

255256
assert result[expected][0].get_color() == "C1"
256257

258+
@pytest.mark.parametrize("vert", [True, False])
259+
def test_plot_xlabel_ylabel(self, vert):
260+
df = DataFrame(
261+
{
262+
"a": np.random.randn(100),
263+
"b": np.random.randn(100),
264+
"group": np.random.choice(["group1", "group2"], 100),
265+
}
266+
)
267+
xlabel, ylabel = "x", "y"
268+
ax = df.plot(kind="box", vert=vert, xlabel=xlabel, ylabel=ylabel)
269+
assert ax.get_xlabel() == xlabel
270+
assert ax.get_ylabel() == ylabel
271+
272+
@pytest.mark.parametrize("vert", [True, False])
273+
def test_boxplot_xlabel_ylabel(self, vert):
274+
df = DataFrame(
275+
{
276+
"a": np.random.randn(100),
277+
"b": np.random.randn(100),
278+
"group": np.random.choice(["group1", "group2"], 100),
279+
}
280+
)
281+
xlabel, ylabel = "x", "y"
282+
ax = df.boxplot(vert=vert, xlabel=xlabel, ylabel=ylabel)
283+
assert ax.get_xlabel() == xlabel
284+
assert ax.get_ylabel() == ylabel
285+
286+
@pytest.mark.parametrize("vert", [True, False])
287+
def test_boxplot_group_xlabel_ylabel(self, vert):
288+
df = DataFrame(
289+
{
290+
"a": np.random.randn(100),
291+
"b": np.random.randn(100),
292+
"group": np.random.choice(["group1", "group2"], 100),
293+
}
294+
)
295+
xlabel, ylabel = "x", "y"
296+
ax = df.boxplot(by="group", vert=vert, xlabel=xlabel, ylabel=ylabel)
297+
for subplot in ax:
298+
assert subplot.get_xlabel() == xlabel
299+
assert subplot.get_ylabel() == ylabel
300+
self.plt.close()
301+
302+
ax = df.boxplot(by="group", vert=vert)
303+
for subplot in ax:
304+
target_label = subplot.get_xlabel() if vert else subplot.get_ylabel()
305+
assert target_label == pprint_thing(["group"])
306+
self.plt.close()
307+
257308

258309
@td.skip_if_no_mpl
259310
class TestDataFrameGroupByPlots(TestPlotBase):

0 commit comments

Comments
 (0)