diff --git a/doc/source/v0.14.1.txt b/doc/source/v0.14.1.txt index c41bc13b18606..6e02e771fecc7 100644 --- a/doc/source/v0.14.1.txt +++ b/doc/source/v0.14.1.txt @@ -256,5 +256,9 @@ Bug Fixes - Bug in ``DataFrame.reset_index`` loses ``tz`` (:issue:`3950`) +- Bug in single column bar plot is misaligned (:issue:`7498`). + + + - Bug in non-monotonic ``Index.union`` may preserve ``name`` incorrectly (:issue:`7458`) - Bug in ``DatetimeIndex.intersection`` doesn't preserve timezone (:issue:`4690`) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index c96fd08233238..a09f6bc4aa9b0 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -1255,43 +1255,40 @@ def _check_bar_alignment(self, df, kind='bar', stacked=False, align=align, width=width, position=position, grid=True) - tick_pos = np.arange(len(df)) - axes = self._flatten_visible(axes) for ax in axes: if kind == 'bar': axis = ax.xaxis ax_min, ax_max = ax.get_xlim() + min_edge = min([p.get_x() for p in ax.patches]) + max_edge = max([p.get_x() + p.get_width() for p in ax.patches]) elif kind == 'barh': axis = ax.yaxis ax_min, ax_max = ax.get_ylim() + min_edge = min([p.get_y() for p in ax.patches]) + max_edge = max([p.get_y() + p.get_height() for p in ax.patches]) else: raise ValueError + # GH 7498 + # compare margins between lim and bar edges + self.assertAlmostEqual(ax_min, min_edge - 0.25) + self.assertAlmostEqual(ax_max, max_edge + 0.25) + p = ax.patches[0] if kind == 'bar' and (stacked is True or subplots is True): edge = p.get_x() center = edge + p.get_width() * position - tickoffset = width * position elif kind == 'bar' and stacked is False: center = p.get_x() + p.get_width() * len(df.columns) * position edge = p.get_x() - if align == 'edge': - tickoffset = width * (position - 0.5) + p.get_width() * 1.5 - else: - tickoffset = width * position + p.get_width() elif kind == 'barh' and (stacked is True or subplots is True): center = p.get_y() + p.get_height() * position edge = p.get_y() - tickoffset = width * position elif kind == 'barh' and stacked is False: center = p.get_y() + p.get_height() * len(df.columns) * position edge = p.get_y() - if align == 'edge': - tickoffset = width * (position - 0.5) + p.get_height() * 1.5 - else: - tickoffset = width * position + p.get_height() else: raise ValueError @@ -1307,59 +1304,43 @@ def _check_bar_alignment(self, df, kind='bar', stacked=False, else: raise ValueError - # Check starting point and axes limit margin - self.assertEqual(ax_min, tick_pos[0] - tickoffset - 0.25) - self.assertEqual(ax_max, tick_pos[-1] - tickoffset + 1) - # Check tick locations and axes limit margin - t_min = axis.get_ticklocs()[0] - tickoffset - t_max = axis.get_ticklocs()[-1] - tickoffset - self.assertAlmostEqual(ax_min, t_min - 0.25) - self.assertAlmostEqual(ax_max, t_max + 1.0) return axes @slow def test_bar_stacked_center(self): # GH2157 df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) - axes = self._check_bar_alignment(df, kind='bar', stacked=True) - # Check the axes has the same drawing range before fixing # GH4525 - self.assertEqual(axes[0].get_xlim(), (-0.5, 4.75)) - + self._check_bar_alignment(df, kind='bar', stacked=True) self._check_bar_alignment(df, kind='bar', stacked=True, width=0.9) - - axes = self._check_bar_alignment(df, kind='barh', stacked=True) - self.assertEqual(axes[0].get_ylim(), (-0.5, 4.75)) - + self._check_bar_alignment(df, kind='barh', stacked=True) self._check_bar_alignment(df, kind='barh', stacked=True, width=0.9) @slow def test_bar_center(self): df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) - axes = self._check_bar_alignment(df, kind='bar', stacked=False) - self.assertEqual(axes[0].get_xlim(), (-0.75, 4.5)) - + self._check_bar_alignment(df, kind='bar', stacked=False) self._check_bar_alignment(df, kind='bar', stacked=False, width=0.9) - - axes = self._check_bar_alignment(df, kind='barh', stacked=False) - self.assertEqual(axes[0].get_ylim(), (-0.75, 4.5)) - + self._check_bar_alignment(df, kind='barh', stacked=False) self._check_bar_alignment(df, kind='barh', stacked=False, width=0.9) @slow def test_bar_subplots_center(self): df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) - axes = self._check_bar_alignment(df, kind='bar', subplots=True) - for ax in axes: - self.assertEqual(ax.get_xlim(), (-0.5, 4.75)) - + self._check_bar_alignment(df, kind='bar', subplots=True) self._check_bar_alignment(df, kind='bar', subplots=True, width=0.9) - - axes = self._check_bar_alignment(df, kind='barh', subplots=True) - for ax in axes: - self.assertEqual(ax.get_ylim(), (-0.5, 4.75)) - + self._check_bar_alignment(df, kind='barh', subplots=True) self._check_bar_alignment(df, kind='barh', subplots=True, width=0.9) + @slow + def test_bar_align_single_column(self): + df = DataFrame(randn(5)) + self._check_bar_alignment(df, kind='bar', stacked=False) + self._check_bar_alignment(df, kind='bar', stacked=True) + self._check_bar_alignment(df, kind='barh', stacked=False) + self._check_bar_alignment(df, kind='barh', stacked=True) + self._check_bar_alignment(df, kind='bar', subplots=True) + self._check_bar_alignment(df, kind='barh', subplots=True) + @slow def test_bar_edge(self): df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 37a982acc0bbd..03cfaa358c864 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1784,9 +1784,10 @@ def __init__(self, data, **kwargs): self.stacked = kwargs.pop('stacked', False) self.bar_width = kwargs.pop('width', 0.5) + pos = kwargs.pop('position', 0.5) - kwargs['align'] = kwargs.pop('align', 'center') + kwargs.setdefault('align', 'center') self.tick_pos = np.arange(len(data)) self.bottom = kwargs.pop('bottom', None) @@ -1797,14 +1798,19 @@ def __init__(self, data, **kwargs): if self.stacked or self.subplots: self.tickoffset = self.bar_width * pos - elif kwargs['align'] == 'edge': - K = self.nseries - w = self.bar_width / K - self.tickoffset = self.bar_width * (pos - 0.5) + w * 1.5 + if kwargs['align'] == 'edge': + self.lim_offset = self.bar_width / 2 + else: + self.lim_offset = 0 else: - K = self.nseries - w = self.bar_width / K - self.tickoffset = self.bar_width * pos + w + if kwargs['align'] == 'edge': + w = self.bar_width / self.nseries + self.tickoffset = self.bar_width * (pos - 0.5) + w * 0.5 + self.lim_offset = w * 0.5 + else: + self.tickoffset = self.bar_width * pos + self.lim_offset = 0 + self.ax_pos = self.tick_pos - self.tickoffset def _args_adjust(self): @@ -1881,9 +1887,8 @@ def _make_plot(self): neg_prior = neg_prior + np.where(mask, 0, y) else: w = self.bar_width / K - rect = bar_f(ax, self.ax_pos + (i + 1.5) * w, y, w, + rect = bar_f(ax, self.ax_pos + (i + 0.5) * w, y, w, start=start, label=label, **kwds) - self._add_legend_handle(rect, label, index=i) def _post_plot_logic(self): @@ -1894,8 +1899,12 @@ def _post_plot_logic(self): str_index = [com.pprint_thing(key) for key in range(self.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 + if self.kind == 'bar': - ax.set_xlim([self.ax_pos[0] - 0.25, self.ax_pos[-1] + 1]) + ax.set_xlim((s_edge, e_edge)) ax.set_xticks(self.tick_pos) ax.set_xticklabels(str_index, rotation=self.rot, fontsize=self.fontsize) @@ -1905,7 +1914,7 @@ def _post_plot_logic(self): ax.set_xlabel(name) elif self.kind == 'barh': # horizontal bars - ax.set_ylim([self.ax_pos[0] - 0.25, self.ax_pos[-1] + 1]) + ax.set_ylim((s_edge, e_edge)) ax.set_yticks(self.tick_pos) ax.set_yticklabels(str_index, rotation=self.rot, fontsize=self.fontsize) @@ -1915,9 +1924,6 @@ def _post_plot_logic(self): else: raise NotImplementedError(self.kind) - # if self.subplots and self.legend: - # self.axes[0].legend(loc='best') - class PiePlot(MPLPlot):