From e953aae23f613bd0f29c845f61e1264bbb9b53ec Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:33:01 +0800 Subject: [PATCH 1/8] fix: base logic and version doc --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/plotting/_matplotlib/core.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index d9ab0452c8334..3d228cd2bc609 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -882,6 +882,7 @@ Plotting ^^^^^^^^ - Bug in :meth:`DataFrame.plot.box` with ``vert=False`` and a Matplotlib ``Axes`` created with ``sharey=True`` (:issue:`54941`) - Bug in :meth:`DataFrame.plot.scatter` discarding string columns (:issue:`56142`) +- Bug in :meth:`DataFrame.plot` where bar and line plots are not aligned on the x-axis (:issue:`56611`) - Bug in :meth:`Series.plot` when reusing an ``ax`` object failing to raise when a ``how`` keyword is passed (:issue:`55953`) Groupby/resample/rolling diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 6fa75ba5fb12d..3a0056ae8bfa1 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1804,6 +1804,19 @@ def _kind(self) -> Literal["bar", "barh"]: def orientation(self) -> PlottingOrientation: return "vertical" + # GH56611 checks similar to LinePlot's _is_ts_plot and _use_dynamic_x + @final + def _is_usable_series(self) -> bool: + return self._is_series and not ( + self.use_index and use_dynamic_x(self._get_ax(0), self.data) + ) + + def _set_tick_pos(self, data) -> np.ndarray: + if self._is_usable_series(): + return np.array(self._get_xticks(), dtype=int) + else: + return np.arange(len(data)) + def __init__( self, data, @@ -1822,7 +1835,6 @@ def __init__( self.bar_width = width self._align = align self._position = position - self.tick_pos = np.arange(len(data)) if is_list_like(bottom): bottom = np.array(bottom) @@ -1835,6 +1847,8 @@ def __init__( MPLPlot.__init__(self, data, **kwargs) + self.tick_pos = self._set_tick_pos(data) + @cache_readonly def ax_pos(self) -> np.ndarray: return self.tick_pos - self.tickoffset From b0ac7c771eeea3da2d82ed88f85d6f530baa8174 Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Fri, 29 Dec 2023 10:28:13 +0800 Subject: [PATCH 2/8] fix: test; should plot in order and not sorted --- pandas/plotting/_matplotlib/core.py | 9 ++++----- pandas/tests/plotting/test_series.py | 5 +---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 3a0056ae8bfa1..8f44062ea449d 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1806,13 +1806,12 @@ def orientation(self) -> PlottingOrientation: # GH56611 checks similar to LinePlot's _is_ts_plot and _use_dynamic_x @final - def _is_usable_series(self) -> bool: - return self._is_series and not ( - self.use_index and use_dynamic_x(self._get_ax(0), self.data) - ) + def _is_ts_plot(self, data) -> bool: + return self.use_index and use_dynamic_x(self._get_ax(0), data) + @final def _set_tick_pos(self, data) -> np.ndarray: - if self._is_usable_series(): + if self._is_series and not self._is_ts_plot(data): return np.array(self._get_xticks(), dtype=int) else: return np.arange(len(data)) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 2b2f2f3b84307..41320f2ce5c18 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -919,10 +919,7 @@ def test_plot_order(self, data, index): ax = ser.plot(kind="bar") expected = ser.tolist() - result = [ - patch.get_bbox().ymax - for patch in sorted(ax.patches, key=lambda patch: patch.get_bbox().xmax) - ] + result = [patch.get_bbox().ymax for patch in ax.patches] assert expected == result def test_style_single_ok(self): From 3a353da9700a2d7c57bdbdd2698d6ad88cc77fd1 Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Fri, 29 Dec 2023 11:26:49 +0800 Subject: [PATCH 3/8] feat: added test to check barplot axes --- pandas/plotting/_matplotlib/core.py | 2 +- pandas/tests/plotting/test_series.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 8f44062ea449d..952e438ec2751 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1804,7 +1804,7 @@ def _kind(self) -> Literal["bar", "barh"]: def orientation(self) -> PlottingOrientation: return "vertical" - # GH56611 checks similar to LinePlot's _is_ts_plot and _use_dynamic_x + # GH56460 checks similar to LinePlot's _is_ts_plot and _use_dynamic_x @final def _is_ts_plot(self, data) -> bool: return self.use_index and use_dynamic_x(self._get_ax(0), data) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 41320f2ce5c18..e169c64fe5409 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -974,6 +974,25 @@ def test_series_none_color(self): expected = _unpack_cycler(mpl.pyplot.rcParams)[:1] _check_colors(ax.get_lines(), linecolors=expected) + @pytest.mark.parametrize( + "plot_data", + [ + ( + { + "bars": {-1: 0.5, 0: 1.0, 1: 3.0, 2: 3.5, 3: 1.5}, + "pct": {-1: 1.0, 0: 2.0, 1: 3.0, 2: 4.0, 3: 8.0}, + } + ) + ], + ) + def test_bar_plot_x_axis(self, plot_data): + df = DataFrame(plot_data) + ax_bar = df["bars"].plot(kind="bar") + df["pct"].plot(kind="line") + actual_bar_x = [bar.get_x() + bar.get_width() / 2.0 for bar in ax_bar.patches] + expected_x = [-1, 0, 1, 2, 3] + assert actual_bar_x == expected_x + @pytest.mark.slow def test_plot_no_warning(self, ts): # GH 55138 From 701571d2e24da1fec814da097860cbfbc6de491b Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:06:40 +0800 Subject: [PATCH 4/8] fix: pylint --- .tool-versions | 1 + pandas/tests/plotting/test_series.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000000000..47cd22e3c05e2 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.10.13 diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index e169c64fe5409..3ddedbebac90e 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -989,7 +989,7 @@ def test_bar_plot_x_axis(self, plot_data): df = DataFrame(plot_data) ax_bar = df["bars"].plot(kind="bar") df["pct"].plot(kind="line") - actual_bar_x = [bar.get_x() + bar.get_width() / 2.0 for bar in ax_bar.patches] + actual_bar_x = [ax.get_x() + ax.get_width() / 2.0 for ax in ax_bar.patches] expected_x = [-1, 0, 1, 2, 3] assert actual_bar_x == expected_x From 94eeefafb57a02663bfc0298ed556ddaf952ebfc Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:21:58 +0800 Subject: [PATCH 5/8] chore: remove file from branch --- .tool-versions | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 47cd22e3c05e2..0000000000000 --- a/.tool-versions +++ /dev/null @@ -1 +0,0 @@ -python 3.10.13 From 43a64f6002f2c1749b6bfca35cd95e54b9256014 Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:50:37 +0800 Subject: [PATCH 6/8] fix: check if int series, return current otherwise --- pandas/plotting/_matplotlib/core.py | 7 +----- pandas/tests/plotting/test_series.py | 32 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 952e438ec2751..4bef3d1d4242c 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1804,14 +1804,9 @@ def _kind(self) -> Literal["bar", "barh"]: def orientation(self) -> PlottingOrientation: return "vertical" - # GH56460 checks similar to LinePlot's _is_ts_plot and _use_dynamic_x - @final - def _is_ts_plot(self, data) -> bool: - return self.use_index and use_dynamic_x(self._get_ax(0), data) - @final def _set_tick_pos(self, data) -> np.ndarray: - if self._is_series and not self._is_ts_plot(data): + if self._is_series and is_integer_dtype(data.index): return np.array(self._get_xticks(), dtype=int) else: return np.arange(len(data)) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 3ddedbebac90e..e8af79287d436 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -974,25 +974,31 @@ def test_series_none_color(self): expected = _unpack_cycler(mpl.pyplot.rcParams)[:1] _check_colors(ax.get_lines(), linecolors=expected) - @pytest.mark.parametrize( - "plot_data", - [ - ( - { - "bars": {-1: 0.5, 0: 1.0, 1: 3.0, 2: 3.5, 3: 1.5}, - "pct": {-1: 1.0, 0: 2.0, 1: 3.0, 2: 4.0, 3: 8.0}, - } - ) - ], - ) - def test_bar_plot_x_axis(self, plot_data): - df = DataFrame(plot_data) + def test_bar_plot_x_axis(self): + df = DataFrame( + { + "bars": {-1: 0.5, 0: 1.0, 1: 3.0, 2: 3.5, 3: 1.5}, + "pct": {-1: 1.0, 0: 2.0, 1: 3.0, 2: 4.0, 3: 8.0}, + } + ) ax_bar = df["bars"].plot(kind="bar") df["pct"].plot(kind="line") actual_bar_x = [ax.get_x() + ax.get_width() / 2.0 for ax in ax_bar.patches] expected_x = [-1, 0, 1, 2, 3] assert actual_bar_x == expected_x + def test_non_numeric_bar_plt(self): + df = DataFrame( + { + "bars": {"a": 0.5, "b": 1.0}, + "pct": {"a": 4.0, "b": 2.0}, + } + ) + ax_bar = df["bars"].plot(kind="bar") + actual_bar_x = [ax.get_x() + ax.get_width() / 2.0 for ax in ax_bar.patches] + expected_x = [0, 1] + assert actual_bar_x == expected_x + @pytest.mark.slow def test_plot_no_warning(self, ts): # GH 55138 From aee71bfe4b2b297785e16244504e24f401f9a54a Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:55:32 +0800 Subject: [PATCH 7/8] chore: fix typo in test name --- pandas/tests/plotting/test_series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index e8af79287d436..2f63ec137f2b2 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -987,7 +987,7 @@ def test_bar_plot_x_axis(self): expected_x = [-1, 0, 1, 2, 3] assert actual_bar_x == expected_x - def test_non_numeric_bar_plt(self): + def test_non_numeric_bar_plot_x_axis(self): df = DataFrame( { "bars": {"a": 0.5, "b": 1.0}, From 152374ad5b2b295850115bf2f49d15f88e89a5e5 Mon Sep 17 00:00:00 2001 From: sharonwoo <29156885+sharonwoo@users.noreply.github.com> Date: Thu, 4 Jan 2024 18:00:44 +0800 Subject: [PATCH 8/8] refactor: simplify logic --- 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 4bef3d1d4242c..56f01d1b1ee85 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -1807,7 +1807,7 @@ def orientation(self) -> PlottingOrientation: @final def _set_tick_pos(self, data) -> np.ndarray: if self._is_series and is_integer_dtype(data.index): - return np.array(self._get_xticks(), dtype=int) + return np.array(data.index) else: return np.arange(len(data))