Skip to content

Commit ce139c0

Browse files
Chang Shewesm
Chang She
authored andcommitted
ENH: column-wise secondary y-axis with subplots on
1 parent 2ad68ef commit ce139c0

File tree

2 files changed

+117
-25
lines changed

2 files changed

+117
-25
lines changed

pandas/tools/plotting.py

+53-25
Original file line numberDiff line numberDiff line change
@@ -363,34 +363,43 @@ def generate(self):
363363
def _args_adjust(self):
364364
pass
365365

366+
def _maybe_right_yaxis(self, ax):
367+
ypos = ax.get_yaxis().get_ticks_position().strip().lower()
368+
369+
if self.secondary_y and ypos != 'right':
370+
orig_ax = ax
371+
ax = ax.twinx()
372+
if len(orig_ax.get_lines()) == 0: # no data on left y
373+
orig_ax.get_yaxis().set_visible(False)
374+
else:
375+
ax.get_yaxis().set_visible(True)
376+
377+
return ax
378+
366379
def _setup_subplots(self):
367380
if self.subplots:
368381
nrows, ncols = self._get_layout()
369382
if self.ax is None:
370383
fig, axes = _subplots(nrows=nrows, ncols=ncols,
371384
sharex=self.sharex, sharey=self.sharey,
372385
figsize=self.figsize,
373-
secondary_y=self.secondary_y)
386+
secondary_y=self.secondary_y,
387+
data=self.data)
374388
else:
375389
fig, axes = _subplots(nrows=nrows, ncols=ncols,
376390
sharex=self.sharex, sharey=self.sharey,
377391
figsize=self.figsize, ax=self.ax,
378-
secondary_y=self.secondary_y)
392+
secondary_y=self.secondary_y,
393+
data=self.data)
379394
else:
380395
if self.ax is None:
381396
fig = self.plt.figure(figsize=self.figsize)
382397
ax = fig.add_subplot(111)
383-
ypos = ax.get_yaxis().get_ticks_position().strip().lower()
384-
if self.secondary_y and ypos != 'right':
385-
ax = ax.twinx()
398+
ax = self._maybe_right_yaxis(ax)
386399
self.ax = ax
387400
else:
388-
ax = self.ax
389401
fig = self.ax.get_figure()
390-
ypos = ax.get_yaxis().get_ticks_position().strip().lower()
391-
if self.secondary_y and ypos != 'right':
392-
ax = ax.twinx()
393-
self.ax = ax
402+
self.ax = self._maybe_right_yaxis(self.ax)
394403

395404
axes = [self.ax]
396405

@@ -640,21 +649,15 @@ def _make_ts_plot(self, data, **kwargs):
640649
plotf = self._get_plot_function()
641650

642651
if isinstance(data, Series):
643-
if self.subplots: # shouldn't even allow users to specify
644-
ax = self.axes[0]
645-
else:
646-
ax = self.ax
652+
ax, _ = self._get_ax_and_style(0) #self.axes[0]
647653

648654
label = com._stringify(self.label)
649655
tsplot(data, plotf, ax=ax, label=label, style=self.style,
650656
**kwargs)
651657
ax.grid(self.grid)
652658
else:
653659
for i, col in enumerate(data.columns):
654-
if self.subplots:
655-
ax = self.axes[i]
656-
else:
657-
ax = self.ax
660+
ax, _ = self._get_ax_and_style(i)
658661
label = com._stringify(col)
659662
tsplot(data[col], plotf, ax=ax, label=label, **kwargs)
660663
ax.grid(self.grid)
@@ -722,7 +725,7 @@ def _make_plot(self):
722725
rects = []
723726
labels = []
724727

725-
ax = self.axes[0]
728+
ax, _ = self._get_ax_and_style(0) #self.axes[0]
726729

727730
bar_f = self.bar_f
728731

@@ -737,7 +740,7 @@ def _make_plot(self):
737740
kwds['color'] = colors[i % len(colors)]
738741

739742
if self.subplots:
740-
ax = self.axes[i]
743+
ax, _ = self._get_ax_and_style(i) #self.axes[i]
741744
rect = bar_f(ax, self.ax_pos, y, 0.5, start=pos_prior,
742745
linewidth=1, **kwds)
743746
ax.set_title(label)
@@ -846,6 +849,9 @@ def plot_frame(frame=None, subplots=False, sharex=True, sharey=False,
846849
ylim : 2-tuple/list
847850
rot : int, default None
848851
Rotation for ticks
852+
secondary_y : boolean or sequence, default False
853+
Whether to plot on the secondary y-axis
854+
If dict then can select which columns to plot on secondary y-axis
849855
kwds : keywords
850856
Options to pass to matplotlib plotting method
851857
@@ -868,7 +874,8 @@ def plot_frame(frame=None, subplots=False, sharex=True, sharey=False,
868874
use_index=use_index, sharex=sharex, sharey=sharey,
869875
xticks=xticks, yticks=yticks, xlim=xlim, ylim=ylim,
870876
title=title, grid=grid, figsize=figsize, logy=logy,
871-
sort_columns=sort_columns, **kwds)
877+
sort_columns=sort_columns, secondary_y=secondary_y,
878+
**kwds)
872879
plot_obj.generate()
873880
plot_obj.draw()
874881
if subplots:
@@ -929,6 +936,14 @@ def plot_series(series, label=None, kind='line', use_index=True, rot=None,
929936

930937
if ax is None:
931938
ax = _gca()
939+
if ax.get_yaxis().get_ticks_position().strip().lower() == 'right':
940+
fig = _gcf()
941+
axes = fig.get_axes()
942+
for i in range(len(axes))[::-1]:
943+
ax = axes[i]
944+
ypos = ax.get_yaxis().get_ticks_position().strip().lower()
945+
if ypos == 'left':
946+
break
932947

933948
# is there harm in this?
934949
if label is None:
@@ -1275,7 +1290,8 @@ def _get_layout(nplots):
12751290
# copied from matplotlib/pyplot.py for compatibility with matplotlib < 1.0
12761291

12771292
def _subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
1278-
subplot_kw=None, ax=None, secondary_y=False, **fig_kw):
1293+
subplot_kw=None, ax=None, secondary_y=False, data=None,
1294+
**fig_kw):
12791295
"""Create a figure with a set of subplots already made.
12801296
12811297
This utility wrapper makes it convenient to create common layouts of
@@ -1317,7 +1333,7 @@ def _subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
13171333
13181334
ax : Matplotlib axis object, default None
13191335
1320-
secondary_y : boolean, default False
1336+
secondary_y : boolean or sequence of ints, default False
13211337
If True then y-axis will be on the right
13221338
13231339
Returns:
@@ -1348,6 +1364,7 @@ def _subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
13481364
plt.subplots(2, 2, subplot_kw=dict(polar=True))
13491365
"""
13501366
import matplotlib.pyplot as plt
1367+
from pandas.core.frame import DataFrame
13511368

13521369
if subplot_kw is None:
13531370
subplot_kw = {}
@@ -1363,10 +1380,18 @@ def _subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
13631380
nplots = nrows*ncols
13641381
axarr = np.empty(nplots, dtype=object)
13651382

1383+
def on_right(i):
1384+
if isinstance(secondary_y, bool):
1385+
return secondary_y
1386+
if isinstance(data, DataFrame):
1387+
return data.columns[i] in secondary_y
1388+
13661389
# Create first subplot separately, so we can share it if requested
13671390
ax0 = fig.add_subplot(nrows, ncols, 1, **subplot_kw)
1368-
if secondary_y:
1391+
if on_right(0):
1392+
orig_ax = ax0
13691393
ax0 = ax0.twinx()
1394+
orig_ax.get_yaxis().set_visible(False)
13701395

13711396
if sharex:
13721397
subplot_kw['sharex'] = ax0
@@ -1378,8 +1403,11 @@ def _subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True,
13781403
# convention.
13791404
for i in range(1, nplots):
13801405
ax = fig.add_subplot(nrows, ncols, i+1, **subplot_kw)
1381-
if secondary_y:
1406+
if on_right(i):
1407+
print 'on right ', data.columns[i]
1408+
orig_ax = ax
13821409
ax = ax.twinx()
1410+
orig_ax.get_yaxis().set_visible(False)
13831411
axarr[i] = ax
13841412

13851413
if nplots > 1:

pandas/tseries/tests/test_plotting.py

+64
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,78 @@ def test_secondary_y(self):
197197
import matplotlib.pyplot as plt
198198
plt.close('all')
199199
ser = Series(np.random.randn(10))
200+
ser2 = Series(np.random.randn(10))
200201
ax = ser.plot(secondary_y=True)
201202
fig = ax.get_figure()
202203
axes = fig.get_axes()
203204
l = ax.get_lines()[0]
204205
xp = Series(l.get_ydata(), l.get_xdata())
205206
assert_series_equal(ser, xp)
207+
self.assert_(ax.get_yaxis().get_ticks_position() == 'right')
208+
self.assert_(not axes[0].get_yaxis().get_visible())
209+
210+
ax2 = ser2.plot()
211+
self.assert_(ax2.get_yaxis().get_ticks_position() == 'left')
212+
213+
plt.close('all')
214+
ax = ser2.plot()
215+
ax2 = ser.plot(secondary_y=True)
216+
self.assert_(ax.get_yaxis().get_visible())
217+
218+
@slow
219+
def test_secondary_y_ts(self):
220+
import matplotlib.pyplot as plt
221+
plt.close('all')
222+
idx = date_range('1/1/2000', periods=10)
223+
ser = Series(np.random.randn(10), idx)
224+
ser2 = Series(np.random.randn(10), idx)
225+
ax = ser.plot(secondary_y=True)
226+
fig = ax.get_figure()
227+
axes = fig.get_axes()
228+
l = ax.get_lines()[0]
229+
xp = Series(l.get_ydata(), l.get_xdata()).to_timestamp()
230+
assert_series_equal(ser, xp)
231+
self.assert_(ax.get_yaxis().get_ticks_position() == 'right')
232+
self.assert_(not axes[0].get_yaxis().get_visible())
233+
234+
ax2 = ser2.plot()
235+
self.assert_(ax2.get_yaxis().get_ticks_position() == 'left')
236+
237+
plt.close('all')
238+
ax = ser2.plot()
239+
ax2 = ser.plot(secondary_y=True)
240+
self.assert_(ax.get_yaxis().get_visible())
241+
242+
@slow
243+
def test_secondary_kde(self):
244+
import matplotlib.pyplot as plt
245+
plt.close('all')
246+
ser = Series(np.random.randn(10))
247+
ax = ser.plot(secondary_y=True, kind='density')
248+
fig = ax.get_figure()
249+
axes = fig.get_axes()
206250
self.assert_(axes[1].get_yaxis().get_ticks_position() == 'right')
207251

252+
@slow
253+
def test_secondary_bar(self):
254+
import matplotlib.pyplot as plt
255+
plt.close('all')
256+
ser = Series(np.random.randn(10))
257+
ax = ser.plot(secondary_y=True, kind='bar')
258+
fig = ax.get_figure()
259+
axes = fig.get_axes()
260+
self.assert_(axes[1].get_yaxis().get_ticks_position() == 'right')
261+
262+
@slow
263+
def test_secondary_frame(self):
264+
import matplotlib.pyplot as plt
265+
plt.close('all')
266+
df = DataFrame(np.random.randn(5, 3), columns=['a', 'b', 'c'])
267+
axes = df.plot(secondary_y=['a', 'c'], subplots=True)
268+
self.assert_(axes[0].get_yaxis().get_ticks_position() == 'right')
269+
self.assert_(axes[1].get_yaxis().get_ticks_position() == 'default')
270+
self.assert_(axes[2].get_yaxis().get_ticks_position() == 'right')
271+
208272
PNG_PATH = 'tmp.png'
209273
def _check_plot_works(f, freq=None, series=None, *args, **kwargs):
210274
import matplotlib.pyplot as plt

0 commit comments

Comments
 (0)