Skip to content

ENH/VIS: Dataframe bar plot can now accept width and pos keywords #6644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/source/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~

Expand Down
4 changes: 4 additions & 0 deletions doc/source/v0.14.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
295 changes: 214 additions & 81 deletions pandas/tests/test_graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.])
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
20 changes: 13 additions & 7 deletions pandas/tools/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down