diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index accfeee484430..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`) -- +- Bug in incorrect ticklabel positions when plotting an index that are non-numeric / non-datetime (:issue:`7612` :issue:`15912` :issue:`22334`) - - diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 7b1e8a8f0aaeb..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: @@ -410,8 +411,12 @@ 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) + 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,11 @@ 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) + 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) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index a0469d002f4cc..1556c4ff9720e 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -2995,6 +2995,40 @@ 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): + # 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']}) + 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 + + @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