From 3f068f74f891ab7e73405ea122d56b5d6d38f0e9 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:15:13 -0500 Subject: [PATCH 01/11] BUG: Make it possible to add y-axis label to barh plot --- pandas/plotting/_matplotlib/core.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index ca3eb75ede3f6..33819eca91f24 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -767,8 +767,10 @@ def _get_index_name(self) -> str | None: name = pprint_thing(name) # GH 9093, override the default xlabel if xlabel is provided. - if self.xlabel is not None: + if self.xlabel is not None and self.orientation == "vertical": name = pprint_thing(self.xlabel) + if self.ylabel is not None and self.orientation == "horizontal": + name = pprint_thing(self.ylabel) return name @@ -1567,17 +1569,19 @@ def _make_plot(self): ) self._append_legend_handles_labels(rect, label) + def _get_bar_index_name(self): + return self.xlabel or self._get_index_name() + def _post_plot_logic(self, ax: Axes, data): if self.use_index: 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_bar_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_bar_index_name(self): + return self.ylabel or self._get_index_name() + 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): From 51b160e8154ba6cec5b35d89919b5d0d4d3b26aa Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:30:55 -0500 Subject: [PATCH 02/11] TST: Add test for barh labels --- pandas/tests/plotting/test_series.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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) From fed131a2019ef0a9e73cf87735c3d72570bc4c70 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Fri, 31 Dec 2021 17:51:11 -0500 Subject: [PATCH 03/11] DOC: Add whatsnew entry --- doc/source/whatsnew/v1.4.0.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 06b11827bca4a..5fb944dcce054 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -757,6 +757,8 @@ Timedelta ^^^^^^^^^ - Bug in division of all-``NaT`` :class:`TimeDeltaIndex`, :class:`Series` or :class:`DataFrame` column with object-dtype arraylike of numbers failing to infer the result as timedelta64-dtype (:issue:`39750`) - Bug in floor division of ``timedelta64[ns]`` data with a scalar returning garbage values (:issue:`44466`) +- Bug in :class:`Timedelta` now properly taking into account any nanoseconds contribution of any kwarg (:issue:`43764`) +- Timezones ^^^^^^^^^ @@ -917,7 +919,7 @@ Period Plotting ^^^^^^^^ - When given non-numeric data, :meth:`DataFrame.boxplot` now raises a ``ValueError`` rather than a cryptic ``KeyError`` or ``ZeroDivisionError``, in line with other plotting functions like :meth:`DataFrame.hist`. (:issue:`43480`) -- +- Bug in :meth:`DataFrame.plot.barh` that prevented labeling the x-axis and ``xlabel`` updating the y-axis label (:issue:`45144`) Groupby/resample/rolling ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -966,6 +968,7 @@ Reshaping - Bug in :meth:`Series.unstack` with object doing unwanted type inference on resulting columns (:issue:`44595`) - Bug in :class:`MultiIndex` failing join operations with overlapping ``IntervalIndex`` levels (:issue:`44096`) - Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` results is different ``dtype`` based on ``regex`` parameter (:issue:`44864`) +- Bug in :meth:`DataFrame.pivot` with ``index=None`` when the :class:`DataFrame` index was a :class:`MultiIndex` (:issue:`23955`) Sparse ^^^^^^ From 14d7cce4f0042618f7ddc294d18bcaf0f17eae9c Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:36:34 -0500 Subject: [PATCH 04/11] Address PR comments --- doc/source/whatsnew/v1.4.0.rst | 1 - doc/source/whatsnew/v1.5.0.rst | 2 +- pandas/plotting/_matplotlib/core.py | 18 ++++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index f0b7367cad5a0..5dd6fa0d4f72d 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -886,7 +886,6 @@ Period Plotting ^^^^^^^^ - When given non-numeric data, :meth:`DataFrame.boxplot` now raises a ``ValueError`` rather than a cryptic ``KeyError`` or ``ZeroDivisionError``, in line with other plotting functions like :meth:`DataFrame.hist`. (:issue:`43480`) -- 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/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 33819eca91f24..284fdfb7fa8d8 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -767,10 +767,20 @@ def _get_index_name(self) -> str | None: name = pprint_thing(name) # GH 9093, override the default xlabel if xlabel is provided. - if self.xlabel is not None and self.orientation == "vertical": - name = pprint_thing(self.xlabel) - if self.ylabel is not None and self.orientation == "horizontal": - name = pprint_thing(self.ylabel) + if self.xlabel is not None: + if self.orientation == "vertical": + name = pprint_thing(self.xlabel) + else: + warnings.warn( + "'xlabel' was provided, but 'orientation' is not vertical. Ignoring 'xlabel'" + ) + if self.ylabel is not None: + if self.orientation == "horizontal": + name = pprint_thing(self.ylabel) + else: + warnings.warn( + "'ylabel' was provided, but 'orientation' is not horizontal. Ignoring 'ylabel'" + ) return name From 2221787cf854323ea2d32a21b5a4fe5fb2fb90ea Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:39:44 -0500 Subject: [PATCH 05/11] Linting. --- pandas/plotting/_matplotlib/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 284fdfb7fa8d8..fd55c1a3521e1 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -772,14 +772,16 @@ def _get_index_name(self) -> str | None: name = pprint_thing(self.xlabel) else: warnings.warn( - "'xlabel' was provided, but 'orientation' is not vertical. Ignoring 'xlabel'" + "'xlabel' was provided, but 'orientation' is not vertical. " + "Ignoring 'xlabel'" ) if self.ylabel is not None: if self.orientation == "horizontal": name = pprint_thing(self.ylabel) else: warnings.warn( - "'ylabel' was provided, but 'orientation' is not horizontal. Ignoring 'ylabel'" + "'ylabel' was provided, but 'orientation' is not horizontal. " + "Ignoring 'ylabel'" ) return name From 14525b1d933b7d1afa219b036e395e9e4ea00095 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:47:35 -0500 Subject: [PATCH 06/11] Revert warnings change. --- pandas/plotting/_matplotlib/core.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index fd55c1a3521e1..33819eca91f24 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -767,22 +767,10 @@ def _get_index_name(self) -> str | None: name = pprint_thing(name) # GH 9093, override the default xlabel if xlabel is provided. - if self.xlabel is not None: - if self.orientation == "vertical": - name = pprint_thing(self.xlabel) - else: - warnings.warn( - "'xlabel' was provided, but 'orientation' is not vertical. " - "Ignoring 'xlabel'" - ) - if self.ylabel is not None: - if self.orientation == "horizontal": - name = pprint_thing(self.ylabel) - else: - warnings.warn( - "'ylabel' was provided, but 'orientation' is not horizontal. " - "Ignoring 'ylabel'" - ) + if self.xlabel is not None and self.orientation == "vertical": + name = pprint_thing(self.xlabel) + if self.ylabel is not None and self.orientation == "horizontal": + name = pprint_thing(self.ylabel) return name From f1b8af5c9926403cf818074d9ef07fdc01e36528 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:56:59 -0500 Subject: [PATCH 07/11] Allow plot classes to specify whether xlabel/ylabel overrides index name --- pandas/plotting/_matplotlib/core.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 33819eca91f24..d0816f955adb7 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) -> str | None: + """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,11 +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 and self.orientation == "vertical": - name = pprint_thing(self.xlabel) - if self.ylabel is not None and self.orientation == "horizontal": - name = pprint_thing(self.ylabel) + # GH 45145, override the default axis if one is provided. + index_name = self._get_custom_index_name() + if index_name: + name = pprint_thing(index_name) return name @@ -1612,6 +1615,9 @@ def _plot( # type: ignore[override] ): return ax.barh(x, y, w, left=start, log=log, **kwds) + def _get_custom_index_name(self) -> str | None: + return self.ylabel + def _get_bar_index_name(self): return self.ylabel or self._get_index_name() From 6c8731afce9c6711eab9fcc4dd533bc9b0c972c1 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:06:27 -0500 Subject: [PATCH 08/11] Fix typo in comment. --- pandas/plotting/_matplotlib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index d0816f955adb7..6639136331dc2 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -770,7 +770,7 @@ def _get_index_name(self) -> str | None: if name is not None: name = pprint_thing(name) - # GH 45145, override the default axis if one is provided. + # GH 45145, override the default axis label if one is provided. index_name = self._get_custom_index_name() if index_name: name = pprint_thing(index_name) From bb5e56ca587da71fec927f24d9f1512fd70e6b12 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:12:24 -0500 Subject: [PATCH 09/11] Removing now redundant method. --- pandas/plotting/_matplotlib/core.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 6639136331dc2..9451ee6117539 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1572,9 +1572,6 @@ def _make_plot(self): ) self._append_legend_handles_labels(rect, label) - def _get_bar_index_name(self): - return self.xlabel or self._get_index_name() - def _post_plot_logic(self, ax: Axes, data): if self.use_index: str_index = [pprint_thing(key) for key in data.index] @@ -1584,7 +1581,7 @@ def _post_plot_logic(self, ax: Axes, data): 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, self._get_bar_index_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)) @@ -1618,9 +1615,6 @@ def _plot( # type: ignore[override] def _get_custom_index_name(self) -> str | None: return self.ylabel - def _get_bar_index_name(self): - return self.ylabel or self._get_index_name() - def _decorate_ticks(self, ax: Axes, name, ticklabels, start_edge, end_edge): # horizontal bars ax.set_ylim((start_edge, end_edge)) From 217569272566d8b7b4c8eb25d78327e85d2eb4d7 Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:34:05 -0500 Subject: [PATCH 10/11] Fix code check error. --- pandas/plotting/_matplotlib/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 9451ee6117539..2a92de501de21 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -754,7 +754,7 @@ 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) -> str | None: + def _get_custom_index_name(self): """Specify whether xlabel/ylabel should be used to override index name""" return self.xlabel @@ -1612,7 +1612,7 @@ def _plot( # type: ignore[override] ): return ax.barh(x, y, w, left=start, log=log, **kwds) - def _get_custom_index_name(self) -> str | None: + def _get_custom_index_name(self): return self.ylabel def _decorate_ticks(self, ax: Axes, name, ticklabels, start_edge, end_edge): From f48e030b874a713bc58bd3ebfd3fbfff59f09b4a Mon Sep 17 00:00:00 2001 From: Stefanie Molin <24376333+stefmolin@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:36:17 -0500 Subject: [PATCH 11/11] Continue to allow empty string in label. --- pandas/plotting/_matplotlib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 2a92de501de21..e7c49d7bffb14 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -772,7 +772,7 @@ def _get_index_name(self) -> str | None: # GH 45145, override the default axis label if one is provided. index_name = self._get_custom_index_name() - if index_name: + if index_name is not None: name = pprint_thing(index_name) return name