diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 0173807cb9bd0..41bba4b58be2e 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -179,7 +179,7 @@ Period Plotting ^^^^^^^^ -- +- Bug in :meth:`DataFrame.plot.barh` that prevented labeling the x-axis and ``xlabel`` updating the y-axis label (:issue:`45144`) - Groupby/resample/rolling diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index ca3eb75ede3f6..e7c49d7bffb14 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -754,6 +754,10 @@ def _plot(cls, ax: Axes, x, y, style=None, is_errorbar: bool = False, **kwds): args = (x, y, style) if style is not None else (x, y) return ax.plot(*args, **kwds) + def _get_custom_index_name(self): + """Specify whether xlabel/ylabel should be used to override index name""" + return self.xlabel + def _get_index_name(self) -> str | None: if isinstance(self.data.index, ABCMultiIndex): name = self.data.index.names @@ -766,9 +770,10 @@ def _get_index_name(self) -> str | None: if name is not None: name = pprint_thing(name) - # GH 9093, override the default xlabel if xlabel is provided. - if self.xlabel is not None: - name = pprint_thing(self.xlabel) + # GH 45145, override the default axis label if one is provided. + index_name = self._get_custom_index_name() + if index_name is not None: + name = pprint_thing(index_name) return name @@ -1572,12 +1577,11 @@ def _post_plot_logic(self, ax: Axes, data): str_index = [pprint_thing(key) for key in data.index] else: str_index = [pprint_thing(key) for key in range(data.shape[0])] - name = self._get_index_name() s_edge = self.ax_pos[0] - 0.25 + self.lim_offset e_edge = self.ax_pos[-1] + 0.25 + self.bar_width + self.lim_offset - self._decorate_ticks(ax, name, str_index, s_edge, e_edge) + self._decorate_ticks(ax, self._get_index_name(), str_index, s_edge, e_edge) def _decorate_ticks(self, ax: Axes, name, ticklabels, start_edge, end_edge): ax.set_xlim((start_edge, end_edge)) @@ -1608,6 +1612,9 @@ def _plot( # type: ignore[override] ): return ax.barh(x, y, w, left=start, log=log, **kwds) + def _get_custom_index_name(self): + return self.ylabel + def _decorate_ticks(self, ax: Axes, name, ticklabels, start_edge, end_edge): # horizontal bars ax.set_ylim((start_edge, end_edge)) @@ -1615,6 +1622,7 @@ def _decorate_ticks(self, ax: Axes, name, ticklabels, start_edge, end_edge): ax.set_yticklabels(ticklabels) if name is not None and self.use_index: ax.set_ylabel(name) + ax.set_xlabel(self.xlabel) class PiePlot(MPLPlot): diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 44fc6042ebaab..8f8f59cf291d0 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -787,16 +787,20 @@ def test_style_single_ok(self): "index_name, old_label, new_label", [(None, "", "new"), ("old", "old", "new"), (None, "", "")], ) - @pytest.mark.parametrize("kind", ["line", "area", "bar"]) + @pytest.mark.parametrize("kind", ["line", "area", "bar", "barh"]) def test_xlabel_ylabel_series(self, kind, index_name, old_label, new_label): # GH 9093 ser = Series([1, 2, 3, 4]) ser.index.name = index_name - # default is the ylabel is not shown and xlabel is index name + # default is the ylabel is not shown and xlabel is index name (reverse for barh) ax = ser.plot(kind=kind) - assert ax.get_ylabel() == "" - assert ax.get_xlabel() == old_label + if kind == "barh": + assert ax.get_xlabel() == "" + assert ax.get_ylabel() == old_label + else: + assert ax.get_ylabel() == "" + assert ax.get_xlabel() == old_label # old xlabel will be overridden and assigned ylabel will be used as ylabel ax = ser.plot(kind=kind, ylabel=new_label, xlabel=new_label)