Repository: pandas-dev/pandas

Issue:
X axis weirdness with DataFrame.plot(kind='bar')
I encountered this issue when plotting some vertical axes spans on the same axes as a bar plot. Some example code is below to reproduce the issue. When using either scatter or line plots, the spans show up where they are supposed to, but the bar plot is off.

Digging a little deeper, I found that the plot call is setting the xticks to a zero-indexed array with a step size of one while setting the tick labels to the correct values.
If you specify the x ticks in the call to the bar plot, the spans are plotted at the correct position, but the bars are plotted from the same  0-indexed, singly incrementing array.

``` python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

spans = pd.DataFrame(np.array([[10,15]]), columns=['left','right'])
datdf = pd.DataFrame(zip(np.random.rand(15),np.random.rand(15)/10), columns=['foo','bar'], index=np.arange(5,20))

fig, axes = plt.subplots(nrows=3, ncols=1)

for a, ax in enumerate(axes):
    for i, row in spans.iterrows():
        ax.axvspan(row['left'],row['right'],color=(0,0,0,.5),zorder=0)


datdf.plot(ax=axes[0], kind='line') # all is good
datdf.plot(ax=axes[1], kind='bar', stacked=True) # span is wrong
datdf.plot(ax=axes[2], kind='bar', stacked=True, xticks=datdf.index.values) #bars are wrong

ticks = []
ticklabs = []

for a, ax in enumerate(axes):
    print a, 
    print zip(ax.xaxis.get_ticklocs(), [lab.get_text() for lab in ax.xaxis.get_ticklabels()])
```

![image](https://cloud.githubusercontent.com/assets/5347796/10809560/0111e020-7db5-11e5-9ab6-cd0adbeb1559.png)

axes 0
[(5.0, u''), (10.0, u''), (15.0, u''), (20.0, u''), (25.0, u''), (30.0, u''), (35.0, u'')]
axes 1
[(0, u'5'), (1, u'7'), (2, u'9'), (3, u'11'), (4, u'13'), (5, u'15'), (6, u'17'), (7, u'19'), (8, u'21'), (9, u'23'), (10, u'25'), (11, u'27'), (12, u'29'), (13, u'31'), (14, u'33')]
axes 2
[(5, u'5'), (7, u'7'), (9, u'9'), (11, u'11'), (13, u'13'), (15, u'15'), (17, u'17'), (19, u'19'), (21, u'21'), (23, u'23'), (25, u'25'), (27, u'27'), (29, u'29'), (31, u'31'), (33, u'33')]



Discussion:
Thanks for the report, might be related to #2980? PR is appreciated! 

I think both of these issues are related to https://github.com/pydata/pandas/issues/7612

Closing as duplicate of  #7612
re-opening as not fixed by #26185 
I am also running into the same problem. Only when kind='bar'.
I can confirm, running into the same issue. Any ideas on how to fix this?

Resolving patch:
diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py
--- a/pandas/plotting/_matplotlib/core.py
+++ b/pandas/plotting/_matplotlib/core.py
@@ -1359,7 +1359,6 @@ def __init__(self, data, **kwargs):
         self.bar_width = kwargs.pop("width", 0.5)
         pos = kwargs.pop("position", 0.5)
         kwargs.setdefault("align", "center")
-        self.tick_pos = np.arange(len(data))
 
         self.bottom = kwargs.pop("bottom", 0)
         self.left = kwargs.pop("left", 0)
@@ -1382,7 +1381,16 @@ def __init__(self, data, **kwargs):
                 self.tickoffset = self.bar_width * pos
                 self.lim_offset = 0
 
-        self.ax_pos = self.tick_pos - self.tickoffset
+        if isinstance(self.data.index, ABCMultiIndex):
+            if kwargs["ax"] is not None and kwargs["ax"].has_data():
+                warnings.warn(
+                    "Redrawing a bar plot with a MultiIndex is not supported "
+                    + "and may lead to inconsistent label positions.",
+                    UserWarning,
+                )
+            self.ax_index = np.arange(len(data))
+        else:
+            self.ax_index = self.data.index
 
     def _args_adjust(self):
         if is_list_like(self.bottom):
@@ -1409,6 +1417,15 @@ def _make_plot(self):
 
         for i, (label, y) in enumerate(self._iter_data(fillna=0)):
             ax = self._get_ax(i)
+
+            if self.orientation == "vertical":
+                ax.xaxis.update_units(self.ax_index)
+                self.tick_pos = ax.convert_xunits(self.ax_index).astype(np.int)
+            elif self.orientation == "horizontal":
+                ax.yaxis.update_units(self.ax_index)
+                self.tick_pos = ax.convert_yunits(self.ax_index).astype(np.int)
+            self.ax_pos = self.tick_pos - self.tickoffset
+
             kwds = self.kwds.copy()
             if self._is_series:
                 kwds["color"] = colors
@@ -1480,8 +1497,8 @@ def _post_plot_logic(self, ax: "Axes", data):
             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
+        s_edge = self.ax_pos.min() - 0.25 + self.lim_offset
+        e_edge = self.ax_pos.max() + 0.25 + self.bar_width + self.lim_offset
 
         self._decorate_ticks(ax, name, str_index, s_edge, e_edge)
 


Test patch:
diff --git a/pandas/tests/plotting/frame/test_frame.py b/pandas/tests/plotting/frame/test_frame.py
--- a/pandas/tests/plotting/frame/test_frame.py
+++ b/pandas/tests/plotting/frame/test_frame.py
@@ -2190,6 +2190,81 @@ def test_xlabel_ylabel_dataframe_plane_plot(self, kind, xlabel, ylabel):
         assert ax.get_xlabel() == (xcol if xlabel is None else xlabel)
         assert ax.get_ylabel() == (ycol if ylabel is None else ylabel)
 
+    @pytest.mark.slow
+    @pytest.mark.parametrize("method", ["bar", "barh"])
+    def test_bar_ticklabel_consistence(self, method):
+        # Draw two consecutiv bar plot with consistent ticklabels
+        # The labels positions should not move between two drawing on the same axis
+        # GH: 26186
+        def get_main_axis(ax):
+            if method == "barh":
+                return ax.yaxis
+            elif method == "bar":
+                return ax.xaxis
+
+        # Plot the first bar plot
+        data = {"A": 0, "B": 3, "C": -4}
+        df = DataFrame.from_dict(data, orient="index", columns=["Value"])
+        ax = getattr(df.plot, method)()
+        ax.get_figure().canvas.draw()
+
+        # Retrieve the label positions for the first drawing
+        xticklabels = [t.get_text() for t in get_main_axis(ax).get_ticklabels()]
+        label_positions_1 = dict(zip(xticklabels, get_main_axis(ax).get_ticklocs()))
+
+        # Modify the dataframe order and values and plot on same axis
+        df = df.sort_values("Value") * -2
+        ax = getattr(df.plot, method)(ax=ax, color="red")
+        ax.get_figure().canvas.draw()
+
+        # Retrieve the label positions for the second drawing
+        xticklabels = [t.get_text() for t in get_main_axis(ax).get_ticklabels()]
+        label_positions_2 = dict(zip(xticklabels, get_main_axis(ax).get_ticklocs()))
+
+        # Assert that the label positions did not change between the plotting
+        assert label_positions_1 == label_positions_2
+
+    def test_bar_numeric(self):
+        # Bar plot with numeric index have tick location values equal to index
+        # values
+        # GH: 11465
+        df = DataFrame(np.random.rand(10), index=np.arange(10, 20))
+        ax = df.plot.bar()
+        ticklocs = ax.xaxis.get_ticklocs()
+        expected = np.arange(10, 20, dtype=np.int64)
+        tm.assert_numpy_array_equal(ticklocs, expected)
+
+    def test_bar_multiindex(self):
+        # Test from pandas/doc/source/user_guide/visualization.rst
+        # at section Plotting With Error Bars
+        # Related to issue GH: 26186
+
+        ix3 = pd.MultiIndex.from_arrays(
+            [
+                ["a", "a", "a", "a", "b", "b", "b", "b"],
+                ["foo", "foo", "bar", "bar", "foo", "foo", "bar", "bar"],
+            ],
+            names=["letter", "word"],
+        )
+
+        df3 = DataFrame(
+            {"data1": [3, 2, 4, 3, 2, 4, 3, 2], "data2": [6, 5, 7, 5, 4, 5, 6, 5]},
+            index=ix3,
+        )
+
+        # Group by index labels and take the means and standard deviations
+        # for each group
+        gp3 = df3.groupby(level=("letter", "word"))
+        means = gp3.mean()
+        errors = gp3.std()
+
+        # No assertion we just ensure that we can plot a MultiIndex bar plot
+        # and are getting a UserWarning if redrawing
+        with tm.assert_produces_warning(None):
+            ax = means.plot.bar(yerr=errors, capsize=4)
+        with tm.assert_produces_warning(UserWarning):
+            means.plot.bar(yerr=errors, capsize=4, ax=ax)
+
 
 def _generate_4_axes_via_gridspec():
     import matplotlib as mpl