From 64e3063ed797a24c177a267f79cac09abe838699 Mon Sep 17 00:00:00 2001 From: sinhrks Date: Sat, 15 Mar 2014 22:51:58 +0900 Subject: [PATCH] dataframe bar plot can now accept width and pos keywords for more flexible alignment --- doc/source/release.rst | 4 + doc/source/v0.14.0.txt | 4 + pandas/tests/test_graphics.py | 295 ++++++++++++++++++++++++---------- pandas/tools/plotting.py | 20 ++- 4 files changed, 235 insertions(+), 88 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index bc4807a293d12..db9161a05f29c 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -137,6 +137,10 @@ API Changes - A tuple passed to ``DataFame.sort_index`` will be interpreted as the levels of the index, rather than requiring a list of tuple (:issue:`4370`) +- Following keywords are now acceptable for :meth:`DataFrame.plot(kind='bar')` and :meth:`DataFrame.plot(kind='barh')`. + - `width`: Specify the bar width. In previous versions, static value 0.5 was passed to matplotlib and it cannot be overwritten. + - `position`: Specify relative alignments for bar plot layout. From 0 (left/bottom-end) to 1(right/top-end). Default is 0.5 (center). (:issue:`6604`) + Deprecations ~~~~~~~~~~~~ diff --git a/doc/source/v0.14.0.txt b/doc/source/v0.14.0.txt index ea321cbab545a..490924024829b 100644 --- a/doc/source/v0.14.0.txt +++ b/doc/source/v0.14.0.txt @@ -178,6 +178,10 @@ These are out-of-bounds selections ``FutureWarning`` is raised to alert that the old ``rows`` and ``cols`` arguments will not be supported in a future release (:issue:`5505`) +- Following keywords are now acceptable for :meth:`DataFrame.plot(kind='bar')` and :meth:`DataFrame.plot(kind='barh')`. + - `width`: Specify the bar width. In previous versions, static value 0.5 was passed to matplotlib and it cannot be overwritten. + - `position`: Specify relative alignments for bar plot layout. From 0 (left/bottom-end) to 1(right/top-end). Default is 0.5 (center). (:issue:`6604`) + MultiIndexing Using Slicers ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index 30ba5cd5a70fe..5aad5df72f530 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -71,87 +71,6 @@ def test_plot_figsize_and_title(self): assert_array_equal(np.round(ax.figure.get_size_inches()), np.array((16., 8.))) - @slow - def test_bar_colors(self): - import matplotlib.pyplot as plt - import matplotlib.colors as colors - - default_colors = plt.rcParams.get('axes.color_cycle') - custom_colors = 'rgcby' - - df = DataFrame(randn(5, 5)) - ax = df.plot(kind='bar') - - rects = ax.patches - - conv = colors.colorConverter - for i, rect in enumerate(rects[::5]): - xp = conv.to_rgba(default_colors[i % len(default_colors)]) - rs = rect.get_facecolor() - self.assertEqual(xp, rs) - - tm.close() - - ax = df.plot(kind='bar', color=custom_colors) - - rects = ax.patches - - conv = colors.colorConverter - for i, rect in enumerate(rects[::5]): - xp = conv.to_rgba(custom_colors[i]) - rs = rect.get_facecolor() - self.assertEqual(xp, rs) - - tm.close() - from matplotlib import cm - - # Test str -> colormap functionality - ax = df.plot(kind='bar', colormap='jet') - - rects = ax.patches - - rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5)) - for i, rect in enumerate(rects[::5]): - xp = rgba_colors[i] - rs = rect.get_facecolor() - self.assertEqual(xp, rs) - - tm.close() - - # Test colormap functionality - ax = df.plot(kind='bar', colormap=cm.jet) - - rects = ax.patches - - rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5)) - for i, rect in enumerate(rects[::5]): - xp = rgba_colors[i] - rs = rect.get_facecolor() - self.assertEqual(xp, rs) - - tm.close() - df.ix[:, [0]].plot(kind='bar', color='DodgerBlue') - - @slow - def test_bar_linewidth(self): - df = DataFrame(randn(5, 5)) - - # regular - ax = df.plot(kind='bar', linewidth=2) - for r in ax.patches: - self.assertEqual(r.get_linewidth(), 2) - - # stacked - ax = df.plot(kind='bar', stacked=True, linewidth=2) - for r in ax.patches: - self.assertEqual(r.get_linewidth(), 2) - - # subplots - axes = df.plot(kind='bar', linewidth=2, subplots=True) - for ax in axes: - for r in ax.patches: - self.assertEqual(r.get_linewidth(), 2) - @slow def test_bar_log(self): expected = np.array([1., 10., 100., 1000.]) @@ -545,6 +464,170 @@ def test_subplots(self): [self.assert_(label.get_visible()) for label in ax.get_yticklabels()] + @slow + def test_bar_colors(self): + import matplotlib.pyplot as plt + import matplotlib.colors as colors + + default_colors = plt.rcParams.get('axes.color_cycle') + custom_colors = 'rgcby' + + df = DataFrame(randn(5, 5)) + ax = df.plot(kind='bar') + + rects = ax.patches + + conv = colors.colorConverter + for i, rect in enumerate(rects[::5]): + xp = conv.to_rgba(default_colors[i % len(default_colors)]) + rs = rect.get_facecolor() + self.assertEqual(xp, rs) + + tm.close() + + ax = df.plot(kind='bar', color=custom_colors) + + rects = ax.patches + + conv = colors.colorConverter + for i, rect in enumerate(rects[::5]): + xp = conv.to_rgba(custom_colors[i]) + rs = rect.get_facecolor() + self.assertEqual(xp, rs) + + tm.close() + from matplotlib import cm + + # Test str -> colormap functionality + ax = df.plot(kind='bar', colormap='jet') + + rects = ax.patches + + rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5)) + for i, rect in enumerate(rects[::5]): + xp = rgba_colors[i] + rs = rect.get_facecolor() + self.assertEqual(xp, rs) + + tm.close() + + # Test colormap functionality + ax = df.plot(kind='bar', colormap=cm.jet) + + rects = ax.patches + + rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5)) + for i, rect in enumerate(rects[::5]): + xp = rgba_colors[i] + rs = rect.get_facecolor() + self.assertEqual(xp, rs) + + tm.close() + df.ix[:, [0]].plot(kind='bar', color='DodgerBlue') + + @slow + def test_bar_linewidth(self): + df = DataFrame(randn(5, 5)) + + # regular + ax = df.plot(kind='bar', linewidth=2) + for r in ax.patches: + self.assertEqual(r.get_linewidth(), 2) + + # stacked + ax = df.plot(kind='bar', stacked=True, linewidth=2) + for r in ax.patches: + self.assertEqual(r.get_linewidth(), 2) + + # subplots + axes = df.plot(kind='bar', linewidth=2, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(r.get_linewidth(), 2) + + @slow + def test_bar_barwidth(self): + df = DataFrame(randn(5, 5)) + + width = 0.9 + + # regular + ax = df.plot(kind='bar', width=width) + for r in ax.patches: + self.assertEqual(r.get_width(), width / len(df.columns)) + + # stacked + ax = df.plot(kind='bar', stacked=True, width=width) + for r in ax.patches: + self.assertEqual(r.get_width(), width) + + # horizontal regular + ax = df.plot(kind='barh', width=width) + for r in ax.patches: + self.assertEqual(r.get_height(), width / len(df.columns)) + + # horizontal stacked + ax = df.plot(kind='barh', stacked=True, width=width) + for r in ax.patches: + self.assertEqual(r.get_height(), width) + + # subplots + axes = df.plot(kind='bar', width=width, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(r.get_width(), width) + + # horizontal subplots + axes = df.plot(kind='barh', width=width, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(r.get_height(), width) + + @slow + def test_bar_barwidth_position(self): + df = DataFrame(randn(5, 5)) + + width = 0.9 + position = 0.2 + + # regular + ax = df.plot(kind='bar', width=width, position=position) + p = ax.patches[0] + self.assertEqual(ax.xaxis.get_ticklocs()[0], + p.get_x() + p.get_width() * position * len(df.columns)) + + # stacked + ax = df.plot(kind='bar', stacked=True, width=width, position=position) + p = ax.patches[0] + self.assertEqual(ax.xaxis.get_ticklocs()[0], + p.get_x() + p.get_width() * position) + + # horizontal regular + ax = df.plot(kind='barh', width=width, position=position) + p = ax.patches[0] + self.assertEqual(ax.yaxis.get_ticklocs()[0], + p.get_y() + p.get_height() * position * len(df.columns)) + + # horizontal stacked + ax = df.plot(kind='barh', stacked=True, width=width, position=position) + p = ax.patches[0] + self.assertEqual(ax.yaxis.get_ticklocs()[0], + p.get_y() + p.get_height() * position) + + # subplots + axes = df.plot(kind='bar', width=width, position=position, subplots=True) + for ax in axes: + p = ax.patches[0] + self.assertEqual(ax.xaxis.get_ticklocs()[0], + p.get_x() + p.get_width() * position) + + # horizontal subplots + axes = df.plot(kind='barh', width=width, position=position, subplots=True) + for ax in axes: + p = ax.patches[0] + self.assertEqual(ax.yaxis.get_ticklocs()[0], + p.get_y() + p.get_height() * position) + @slow def test_plot_scatter(self): from matplotlib.pylab import close @@ -587,11 +670,61 @@ def test_bar_stacked_center(self): self.assertEqual(ax.xaxis.get_ticklocs()[0], ax.patches[0].get_x() + ax.patches[0].get_width() / 2) + ax = df.plot(kind='bar', stacked='True', width=0.9, grid=True) + self.assertEqual(ax.xaxis.get_ticklocs()[0], + ax.patches[0].get_x() + ax.patches[0].get_width() / 2) + + ax = df.plot(kind='barh', stacked='True', grid=True) + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height() / 2) + + ax = df.plot(kind='barh', stacked='True', width=0.9, grid=True) + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height() / 2) + def test_bar_center(self): df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) ax = df.plot(kind='bar', grid=True) self.assertEqual(ax.xaxis.get_ticklocs()[0], ax.patches[0].get_x() + ax.patches[0].get_width()) + + ax = df.plot(kind='bar', width=0.9, grid=True) + self.assertEqual(ax.xaxis.get_ticklocs()[0], + ax.patches[0].get_x() + ax.patches[0].get_width()) + + ax = df.plot(kind='barh', grid=True) + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height()) + + ax = df.plot(kind='barh', width=0.9, grid=True) + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height()) + + def test_bar_subplots_center(self): + df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5)) + axes = df.plot(kind='bar', grid=True, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(ax.xaxis.get_ticklocs()[0], + ax.patches[0].get_x() + ax.patches[0].get_width() / 2) + + axes = df.plot(kind='bar', width=0.9, grid=True, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(ax.xaxis.get_ticklocs()[0], + ax.patches[0].get_x() + ax.patches[0].get_width() / 2) + + axes = df.plot(kind='barh', grid=True, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height() / 2) + + axes = df.plot(kind='barh', width=0.9, grid=True, subplots=True) + for ax in axes: + for r in ax.patches: + self.assertEqual(ax.yaxis.get_ticklocs()[0], + ax.patches[0].get_y() + ax.patches[0].get_height() / 2) @slow def test_bar_log_no_subplots(self): diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 7038284b6c2a0..00a3da0d0785f 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1553,15 +1553,20 @@ class BarPlot(MPLPlot): def __init__(self, data, **kwargs): self.mark_right = kwargs.pop('mark_right', True) self.stacked = kwargs.pop('stacked', False) - self.ax_pos = np.arange(len(data)) + 0.25 - if self.stacked: - self.tickoffset = 0.25 - else: - self.tickoffset = 0.375 - self.bar_width = 0.5 + + self.bar_width = kwargs.pop('width', 0.5) + pos = kwargs.pop('position', 0.5) + self.ax_pos = np.arange(len(data)) + self.bar_width * pos + self.log = kwargs.pop('log',False) MPLPlot.__init__(self, data, **kwargs) + if self.stacked or self.subplots: + self.tickoffset = self.bar_width * pos + else: + K = self.nseries + self.tickoffset = self.bar_width * pos + self.bar_width / K + def _args_adjust(self): if self.rot is None: self.rot = self._default_rot[self.kind] @@ -1622,7 +1627,8 @@ def _make_plot(self): pos_prior = pos_prior + np.where(mask, y, 0) neg_prior = neg_prior + np.where(mask, 0, y) else: - rect = bar_f(ax, self.ax_pos + i * 0.75 / K, y, 0.75 / K, + w = self.bar_width / K + rect = bar_f(ax, self.ax_pos + (i + 1) * w, y, w, start=start, label=label, **kwds) rects.append(rect) if self.mark_right: