Skip to content

Commit 8d87a29

Browse files
sinhrksTom Augspurger
authored and
Tom Augspurger
committed
dataframe bar plot can now accept width and pos keywords for more flexible alignment
1 parent 935da55 commit 8d87a29

File tree

4 files changed

+240
-88
lines changed

4 files changed

+240
-88
lines changed

doc/source/release.rst

+3
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ API Changes
140140
the index, rather than requiring a list of tuple (:issue:`4370`)
141141

142142
- Fix a bug where invalid eval/query operations would blow the stack (:issue:`5198`)
143+
- Following keywords are now acceptable for :meth:`DataFrame.plot(kind='bar')` and :meth:`DataFrame.plot(kind='barh')`.
144+
- `width`: Specify the bar width. In previous versions, static value 0.5 was passed to matplotlib and it cannot be overwritten.
145+
- `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`)
143146

144147
Deprecations
145148
~~~~~~~~~~~~

doc/source/v0.14.0.txt

+4
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ These are out-of-bounds selections
178178
``FutureWarning`` is raised to alert that the old ``rows`` and ``cols`` arguments
179179
will not be supported in a future release (:issue:`5505`)
180180

181+
- Following keywords are now acceptable for :meth:`DataFrame.plot(kind='bar')` and :meth:`DataFrame.plot(kind='barh')`.
182+
- `width`: Specify the bar width. In previous versions, static value 0.5 was passed to matplotlib and it cannot be overwritten.
183+
- `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`)
184+
181185

182186
MultiIndexing Using Slicers
183187
~~~~~~~~~~~~~~~~~~~~~~~~~~~

pandas/tests/test_graphics.py

+214-81
Original file line numberDiff line numberDiff line change
@@ -71,87 +71,6 @@ def test_plot_figsize_and_title(self):
7171
assert_array_equal(np.round(ax.figure.get_size_inches()),
7272
np.array((16., 8.)))
7373

74-
@slow
75-
def test_bar_colors(self):
76-
import matplotlib.pyplot as plt
77-
import matplotlib.colors as colors
78-
79-
default_colors = plt.rcParams.get('axes.color_cycle')
80-
custom_colors = 'rgcby'
81-
82-
df = DataFrame(randn(5, 5))
83-
ax = df.plot(kind='bar')
84-
85-
rects = ax.patches
86-
87-
conv = colors.colorConverter
88-
for i, rect in enumerate(rects[::5]):
89-
xp = conv.to_rgba(default_colors[i % len(default_colors)])
90-
rs = rect.get_facecolor()
91-
self.assertEqual(xp, rs)
92-
93-
tm.close()
94-
95-
ax = df.plot(kind='bar', color=custom_colors)
96-
97-
rects = ax.patches
98-
99-
conv = colors.colorConverter
100-
for i, rect in enumerate(rects[::5]):
101-
xp = conv.to_rgba(custom_colors[i])
102-
rs = rect.get_facecolor()
103-
self.assertEqual(xp, rs)
104-
105-
tm.close()
106-
from matplotlib import cm
107-
108-
# Test str -> colormap functionality
109-
ax = df.plot(kind='bar', colormap='jet')
110-
111-
rects = ax.patches
112-
113-
rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5))
114-
for i, rect in enumerate(rects[::5]):
115-
xp = rgba_colors[i]
116-
rs = rect.get_facecolor()
117-
self.assertEqual(xp, rs)
118-
119-
tm.close()
120-
121-
# Test colormap functionality
122-
ax = df.plot(kind='bar', colormap=cm.jet)
123-
124-
rects = ax.patches
125-
126-
rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5))
127-
for i, rect in enumerate(rects[::5]):
128-
xp = rgba_colors[i]
129-
rs = rect.get_facecolor()
130-
self.assertEqual(xp, rs)
131-
132-
tm.close()
133-
df.ix[:, [0]].plot(kind='bar', color='DodgerBlue')
134-
135-
@slow
136-
def test_bar_linewidth(self):
137-
df = DataFrame(randn(5, 5))
138-
139-
# regular
140-
ax = df.plot(kind='bar', linewidth=2)
141-
for r in ax.patches:
142-
self.assertEqual(r.get_linewidth(), 2)
143-
144-
# stacked
145-
ax = df.plot(kind='bar', stacked=True, linewidth=2)
146-
for r in ax.patches:
147-
self.assertEqual(r.get_linewidth(), 2)
148-
149-
# subplots
150-
axes = df.plot(kind='bar', linewidth=2, subplots=True)
151-
for ax in axes:
152-
for r in ax.patches:
153-
self.assertEqual(r.get_linewidth(), 2)
154-
15574
@slow
15675
def test_bar_log(self):
15776
expected = np.array([1., 10., 100., 1000.])
@@ -574,6 +493,170 @@ def test_subplots(self):
574493
[self.assert_(label.get_visible())
575494
for label in ax.get_yticklabels()]
576495

496+
@slow
497+
def test_bar_colors(self):
498+
import matplotlib.pyplot as plt
499+
import matplotlib.colors as colors
500+
501+
default_colors = plt.rcParams.get('axes.color_cycle')
502+
custom_colors = 'rgcby'
503+
504+
df = DataFrame(randn(5, 5))
505+
ax = df.plot(kind='bar')
506+
507+
rects = ax.patches
508+
509+
conv = colors.colorConverter
510+
for i, rect in enumerate(rects[::5]):
511+
xp = conv.to_rgba(default_colors[i % len(default_colors)])
512+
rs = rect.get_facecolor()
513+
self.assertEqual(xp, rs)
514+
515+
tm.close()
516+
517+
ax = df.plot(kind='bar', color=custom_colors)
518+
519+
rects = ax.patches
520+
521+
conv = colors.colorConverter
522+
for i, rect in enumerate(rects[::5]):
523+
xp = conv.to_rgba(custom_colors[i])
524+
rs = rect.get_facecolor()
525+
self.assertEqual(xp, rs)
526+
527+
tm.close()
528+
from matplotlib import cm
529+
530+
# Test str -> colormap functionality
531+
ax = df.plot(kind='bar', colormap='jet')
532+
533+
rects = ax.patches
534+
535+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5))
536+
for i, rect in enumerate(rects[::5]):
537+
xp = rgba_colors[i]
538+
rs = rect.get_facecolor()
539+
self.assertEqual(xp, rs)
540+
541+
tm.close()
542+
543+
# Test colormap functionality
544+
ax = df.plot(kind='bar', colormap=cm.jet)
545+
546+
rects = ax.patches
547+
548+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, 5))
549+
for i, rect in enumerate(rects[::5]):
550+
xp = rgba_colors[i]
551+
rs = rect.get_facecolor()
552+
self.assertEqual(xp, rs)
553+
554+
tm.close()
555+
df.ix[:, [0]].plot(kind='bar', color='DodgerBlue')
556+
557+
@slow
558+
def test_bar_linewidth(self):
559+
df = DataFrame(randn(5, 5))
560+
561+
# regular
562+
ax = df.plot(kind='bar', linewidth=2)
563+
for r in ax.patches:
564+
self.assertEqual(r.get_linewidth(), 2)
565+
566+
# stacked
567+
ax = df.plot(kind='bar', stacked=True, linewidth=2)
568+
for r in ax.patches:
569+
self.assertEqual(r.get_linewidth(), 2)
570+
571+
# subplots
572+
axes = df.plot(kind='bar', linewidth=2, subplots=True)
573+
for ax in axes:
574+
for r in ax.patches:
575+
self.assertEqual(r.get_linewidth(), 2)
576+
577+
@slow
578+
def test_bar_barwidth(self):
579+
df = DataFrame(randn(5, 5))
580+
581+
width = 0.9
582+
583+
# regular
584+
ax = df.plot(kind='bar', width=width)
585+
for r in ax.patches:
586+
self.assertEqual(r.get_width(), width / len(df.columns))
587+
588+
# stacked
589+
ax = df.plot(kind='bar', stacked=True, width=width)
590+
for r in ax.patches:
591+
self.assertEqual(r.get_width(), width)
592+
593+
# horizontal regular
594+
ax = df.plot(kind='barh', width=width)
595+
for r in ax.patches:
596+
self.assertEqual(r.get_height(), width / len(df.columns))
597+
598+
# horizontal stacked
599+
ax = df.plot(kind='barh', stacked=True, width=width)
600+
for r in ax.patches:
601+
self.assertEqual(r.get_height(), width)
602+
603+
# subplots
604+
axes = df.plot(kind='bar', width=width, subplots=True)
605+
for ax in axes:
606+
for r in ax.patches:
607+
self.assertEqual(r.get_width(), width)
608+
609+
# horizontal subplots
610+
axes = df.plot(kind='barh', width=width, subplots=True)
611+
for ax in axes:
612+
for r in ax.patches:
613+
self.assertEqual(r.get_height(), width)
614+
615+
@slow
616+
def test_bar_barwidth_position(self):
617+
df = DataFrame(randn(5, 5))
618+
619+
width = 0.9
620+
position = 0.2
621+
622+
# regular
623+
ax = df.plot(kind='bar', width=width, position=position)
624+
p = ax.patches[0]
625+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
626+
p.get_x() + p.get_width() * position * len(df.columns))
627+
628+
# stacked
629+
ax = df.plot(kind='bar', stacked=True, width=width, position=position)
630+
p = ax.patches[0]
631+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
632+
p.get_x() + p.get_width() * position)
633+
634+
# horizontal regular
635+
ax = df.plot(kind='barh', width=width, position=position)
636+
p = ax.patches[0]
637+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
638+
p.get_y() + p.get_height() * position * len(df.columns))
639+
640+
# horizontal stacked
641+
ax = df.plot(kind='barh', stacked=True, width=width, position=position)
642+
p = ax.patches[0]
643+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
644+
p.get_y() + p.get_height() * position)
645+
646+
# subplots
647+
axes = df.plot(kind='bar', width=width, position=position, subplots=True)
648+
for ax in axes:
649+
p = ax.patches[0]
650+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
651+
p.get_x() + p.get_width() * position)
652+
653+
# horizontal subplots
654+
axes = df.plot(kind='barh', width=width, position=position, subplots=True)
655+
for ax in axes:
656+
p = ax.patches[0]
657+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
658+
p.get_y() + p.get_height() * position)
659+
577660
@slow
578661
def test_plot_scatter(self):
579662
from matplotlib.pylab import close
@@ -616,11 +699,61 @@ def test_bar_stacked_center(self):
616699
self.assertEqual(ax.xaxis.get_ticklocs()[0],
617700
ax.patches[0].get_x() + ax.patches[0].get_width() / 2)
618701

702+
ax = df.plot(kind='bar', stacked='True', width=0.9, grid=True)
703+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
704+
ax.patches[0].get_x() + ax.patches[0].get_width() / 2)
705+
706+
ax = df.plot(kind='barh', stacked='True', grid=True)
707+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
708+
ax.patches[0].get_y() + ax.patches[0].get_height() / 2)
709+
710+
ax = df.plot(kind='barh', stacked='True', width=0.9, grid=True)
711+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
712+
ax.patches[0].get_y() + ax.patches[0].get_height() / 2)
713+
619714
def test_bar_center(self):
620715
df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5))
621716
ax = df.plot(kind='bar', grid=True)
622717
self.assertEqual(ax.xaxis.get_ticklocs()[0],
623718
ax.patches[0].get_x() + ax.patches[0].get_width())
719+
720+
ax = df.plot(kind='bar', width=0.9, grid=True)
721+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
722+
ax.patches[0].get_x() + ax.patches[0].get_width())
723+
724+
ax = df.plot(kind='barh', grid=True)
725+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
726+
ax.patches[0].get_y() + ax.patches[0].get_height())
727+
728+
ax = df.plot(kind='barh', width=0.9, grid=True)
729+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
730+
ax.patches[0].get_y() + ax.patches[0].get_height())
731+
732+
def test_bar_subplots_center(self):
733+
df = DataFrame({'A': [3] * 5, 'B': lrange(5)}, index=lrange(5))
734+
axes = df.plot(kind='bar', grid=True, subplots=True)
735+
for ax in axes:
736+
for r in ax.patches:
737+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
738+
ax.patches[0].get_x() + ax.patches[0].get_width() / 2)
739+
740+
axes = df.plot(kind='bar', width=0.9, grid=True, subplots=True)
741+
for ax in axes:
742+
for r in ax.patches:
743+
self.assertEqual(ax.xaxis.get_ticklocs()[0],
744+
ax.patches[0].get_x() + ax.patches[0].get_width() / 2)
745+
746+
axes = df.plot(kind='barh', grid=True, subplots=True)
747+
for ax in axes:
748+
for r in ax.patches:
749+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
750+
ax.patches[0].get_y() + ax.patches[0].get_height() / 2)
751+
752+
axes = df.plot(kind='barh', width=0.9, grid=True, subplots=True)
753+
for ax in axes:
754+
for r in ax.patches:
755+
self.assertEqual(ax.yaxis.get_ticklocs()[0],
756+
ax.patches[0].get_y() + ax.patches[0].get_height() / 2)
624757

625758
@slow
626759
def test_bar_log_no_subplots(self):

pandas/tools/plotting.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -1672,15 +1672,20 @@ class BarPlot(MPLPlot):
16721672
def __init__(self, data, **kwargs):
16731673
self.mark_right = kwargs.pop('mark_right', True)
16741674
self.stacked = kwargs.pop('stacked', False)
1675-
self.ax_pos = np.arange(len(data)) + 0.25
1676-
if self.stacked:
1677-
self.tickoffset = 0.25
1678-
else:
1679-
self.tickoffset = 0.375
1680-
self.bar_width = 0.5
1675+
1676+
self.bar_width = kwargs.pop('width', 0.5)
1677+
pos = kwargs.pop('position', 0.5)
1678+
self.ax_pos = np.arange(len(data)) + self.bar_width * pos
1679+
16811680
self.log = kwargs.pop('log',False)
16821681
MPLPlot.__init__(self, data, **kwargs)
16831682

1683+
if self.stacked or self.subplots:
1684+
self.tickoffset = self.bar_width * pos
1685+
else:
1686+
K = self.nseries
1687+
self.tickoffset = self.bar_width * pos + self.bar_width / K
1688+
16841689
def _args_adjust(self):
16851690
if self.rot is None:
16861691
self.rot = self._default_rot[self.kind]
@@ -1757,7 +1762,8 @@ def _make_plot(self):
17571762
pos_prior = pos_prior + np.where(mask, y, 0)
17581763
neg_prior = neg_prior + np.where(mask, 0, y)
17591764
else:
1760-
rect = bar_f(ax, self.ax_pos + i * 0.75 / K, y, 0.75 / K,
1765+
w = self.bar_width / K
1766+
rect = bar_f(ax, self.ax_pos + (i + 1) * w, y, w,
17611767
start=start, label=label, **kwds)
17621768
rects.append(rect)
17631769
if self.mark_right:
@@ -1881,6 +1887,9 @@ def plot_frame(frame=None, x=None, y=None, subplots=False, sharex=True,
18811887
colormap : str or matplotlib colormap object, default None
18821888
Colormap to select colors from. If string, load colormap with that name
18831889
from matplotlib.
1890+
position : float
1891+
Specify relative alignments for bar plot layout.
1892+
From 0 (left/bottom-end) to 1 (right/top-end). Default is 0.5 (center)
18841893
kwds : keywords
18851894
Options to pass to matplotlib plotting method
18861895
@@ -2016,6 +2025,9 @@ def plot_series(series, label=None, kind='line', use_index=True, rot=None,
20162025
secondary_y : boolean or sequence of ints, default False
20172026
If True then y-axis will be on the right
20182027
figsize : a tuple (width, height) in inches
2028+
position : float
2029+
Specify relative alignments for bar plot layout.
2030+
From 0 (left/bottom-end) to 1 (right/top-end). Default is 0.5 (center)
20192031
kwds : keywords
20202032
Options to pass to matplotlib plotting method
20212033

0 commit comments

Comments
 (0)