From 2e732b1dc3582f99bb2aa309cf584ec14d9c5a4c Mon Sep 17 00:00:00 2001 From: nicolas Date: Mon, 22 Apr 2019 18:44:53 +0200 Subject: [PATCH 1/7] BUG: Plotting use FixedLocator For string x values or multiindex, the axis locator is now set to a FixedLocator for Line and Area plot. The ticklabel stay consistent when scaling and zooming. This impact the following issues: GH7612, GH15912, GH22334 --- pandas/plotting/_core.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 7b1e8a8f0aaeb..4947ab6684683 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -410,8 +410,13 @@ def get_label(i): if self.orientation == 'vertical' or self.orientation is None: if self._need_to_set_index: - xticklabels = [get_label(x) for x in ax.get_xticks()] + xticks = ax.get_xticks() + xticklabels = [get_label(x) for x in xticks] ax.set_xticklabels(xticklabels) + from matplotlib.ticker import FixedLocator, FixedFormatter + ax.xaxis.set_major_locator(FixedLocator(xticks)) + ax.xaxis.set_major_formatter(FixedFormatter(xticklabels)) + self._apply_axis_properties(ax.xaxis, rot=self.rot, fontsize=self.fontsize) self._apply_axis_properties(ax.yaxis, fontsize=self.fontsize) @@ -422,8 +427,12 @@ def get_label(i): elif self.orientation == 'horizontal': if self._need_to_set_index: - yticklabels = [get_label(y) for y in ax.get_yticks()] + yticks = ax.get_yticks() + yticklabels = [get_label(y) for y in yticks] ax.set_yticklabels(yticklabels) + from matplotlib.ticker import FixedLocator, FixedFormatter + ax.xaxis.set_major_locator(FixedLocator(yticks)) + ax.xaxis.set_major_formatter(FixedFormatter(yticklabels)) self._apply_axis_properties(ax.yaxis, rot=self.rot, fontsize=self.fontsize) self._apply_axis_properties(ax.xaxis, fontsize=self.fontsize) From 7cea50ae6de4b359d9df57c8819296b977ba7d40 Mon Sep 17 00:00:00 2001 From: "nicolas.rebena" Date: Tue, 23 Apr 2019 09:42:14 +0200 Subject: [PATCH 2/7] TST: Add test for #7612 #22334 #15912 --- pandas/tests/plotting/test_frame.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index a0469d002f4cc..b9c793f2a7e61 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -2995,6 +2995,23 @@ def test_secondary_axis_font_size(self, method): self._check_ticks_props(axes=ax.right_ax, ylabelsize=fontsize) + @pytest.mark.slow + def test_x_string_values_ticks(self): + # GH: 7612 + # GH: 22334 + # GH: 15912 + df = pd.DataFrame({'sales': [3, 2, 3], + 'visits': [20, 42, 28], + 'day': ['Monday', 'Tuesday', 'Wednesday']}) + ax = df.plot.area(x='day') + ax.set_xlim(-1, 3) + xticklabels = [t.get_text() for t in ax.get_xticklabels()] + labels_position = dict(zip(xticklabels, ax.get_xticks())) + # Testing if the label stayed at the right position + assert labels_position['Monday'] == 0.0 + assert labels_position['Tuesday'] == 1.0 + assert labels_position['Wednesday'] == 2.0 + def _generate_4_axes_via_gridspec(): import matplotlib.pyplot as plt From 7e67f99bb64f064093f4e6a61ac5fa0c5e0a4565 Mon Sep 17 00:00:00 2001 From: "nicolas.rebena" Date: Tue, 23 Apr 2019 14:40:40 +0200 Subject: [PATCH 3/7] CLN: Fiex visual indent for PEP8 --- pandas/tests/plotting/test_frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index b9c793f2a7e61..0c4fd8f5262ff 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -3001,8 +3001,8 @@ def test_x_string_values_ticks(self): # GH: 22334 # GH: 15912 df = pd.DataFrame({'sales': [3, 2, 3], - 'visits': [20, 42, 28], - 'day': ['Monday', 'Tuesday', 'Wednesday']}) + 'visits': [20, 42, 28], + 'day': ['Monday', 'Tuesday', 'Wednesday']}) ax = df.plot.area(x='day') ax.set_xlim(-1, 3) xticklabels = [t.get_text() for t in ax.get_xticklabels()] From 362008075cbe38c89d9cf1cc269f7f58a4526e73 Mon Sep 17 00:00:00 2001 From: "nicolas.rebena" Date: Wed, 24 Apr 2019 15:37:34 +0200 Subject: [PATCH 4/7] TST: Split test for #15912 Separate the test for #7612 and #22334, that are about string index, and test for #15912, about multiindex. --- pandas/tests/plotting/test_frame.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 0c4fd8f5262ff..1556c4ff9720e 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -2997,9 +2997,8 @@ def test_secondary_axis_font_size(self, method): @pytest.mark.slow def test_x_string_values_ticks(self): - # GH: 7612 - # GH: 22334 - # GH: 15912 + # Test if string plot index have a fixed xtick position + # GH: 7612, GH: 22334 df = pd.DataFrame({'sales': [3, 2, 3], 'visits': [20, 42, 28], 'day': ['Monday', 'Tuesday', 'Wednesday']}) @@ -3012,6 +3011,24 @@ def test_x_string_values_ticks(self): assert labels_position['Tuesday'] == 1.0 assert labels_position['Wednesday'] == 2.0 + @pytest.mark.slow + def test_x_multiindex_values_ticks(self): + # Test if multiindex plot index have a fixed xtick position + # GH: 15912 + index = pd.MultiIndex.from_product([[2012, 2013], [1, 2]]) + df = pd.DataFrame(np.random.randn(4, 2), + columns=['A', 'B'], + index=index) + ax = df.plot() + ax.set_xlim(-1, 4) + xticklabels = [t.get_text() for t in ax.get_xticklabels()] + labels_position = dict(zip(xticklabels, ax.get_xticks())) + # Testing if the label stayed at the right position + assert labels_position['(2012, 1)'] == 0.0 + assert labels_position['(2012, 2)'] == 1.0 + assert labels_position['(2013, 1)'] == 2.0 + assert labels_position['(2013, 2)'] == 3.0 + def _generate_4_axes_via_gridspec(): import matplotlib.pyplot as plt From f343631db3b28b3feddfddc7d7cf73c74f02751f Mon Sep 17 00:00:00 2001 From: "nicolas.rebena" Date: Wed, 24 Apr 2019 15:56:09 +0200 Subject: [PATCH 5/7] CLN: Fix import position in _post_plot_logic_common --- pandas/plotting/_core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 4947ab6684683..5091aa4c8c443 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -401,6 +401,7 @@ def _add_table(self): def _post_plot_logic_common(self, ax, data): """Common post process for each axes""" + from matplotlib.ticker import FixedLocator, FixedFormatter def get_label(i): try: @@ -413,7 +414,6 @@ def get_label(i): xticks = ax.get_xticks() xticklabels = [get_label(x) for x in xticks] ax.set_xticklabels(xticklabels) - from matplotlib.ticker import FixedLocator, FixedFormatter ax.xaxis.set_major_locator(FixedLocator(xticks)) ax.xaxis.set_major_formatter(FixedFormatter(xticklabels)) @@ -430,7 +430,6 @@ def get_label(i): yticks = ax.get_yticks() yticklabels = [get_label(y) for y in yticks] ax.set_yticklabels(yticklabels) - from matplotlib.ticker import FixedLocator, FixedFormatter ax.xaxis.set_major_locator(FixedLocator(yticks)) ax.xaxis.set_major_formatter(FixedFormatter(yticklabels)) self._apply_axis_properties(ax.yaxis, rot=self.rot, From 62874fb2bf9e2c6a55a22ec739f6941b7ad27ffb Mon Sep 17 00:00:00 2001 From: "nicolas.rebena" Date: Wed, 24 Apr 2019 16:11:44 +0200 Subject: [PATCH 6/7] DOC: Add whatsnew entry in Plotting --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index accfeee484430..73389d5a0712c 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -371,7 +371,7 @@ Plotting - Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) - Bug in an error message in :meth:`DataFrame.plot`. Improved the error message if non-numerics are passed to :meth:`DataFrame.plot` (:issue:`25481`) -- +- If the index of a data frame is objects (i.e. non-numerical/non-datetime), the ticklabel position are now fixed using matplotlib.ticker.FixedLocator (:issue:`7612` :issue:`15912` :issue:`22334`) - - From 863ba3d2eac506e652219ea04cb2c0463484ae5b Mon Sep 17 00:00:00 2001 From: nicolas Date: Tue, 30 Apr 2019 10:36:29 +0200 Subject: [PATCH 7/7] DOC: Minor doc correction in whatsnew entry --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 73389d5a0712c..383ec7cda82cd 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -371,7 +371,7 @@ Plotting - Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) - Bug in an error message in :meth:`DataFrame.plot`. Improved the error message if non-numerics are passed to :meth:`DataFrame.plot` (:issue:`25481`) -- If the index of a data frame is objects (i.e. non-numerical/non-datetime), the ticklabel position are now fixed using matplotlib.ticker.FixedLocator (:issue:`7612` :issue:`15912` :issue:`22334`) +- Bug in incorrect ticklabel positions when plotting an index that are non-numeric / non-datetime (:issue:`7612` :issue:`15912` :issue:`22334`) - -