Skip to content

Commit 87436a3

Browse files
committed
Merge pull request #7459 from sinhrks/secondary_y
BUG/CLN: LinePlot uses incorrect xlim when secondary_y=True
2 parents d17f1e9 + 18facdf commit 87436a3

File tree

4 files changed

+62
-84
lines changed

4 files changed

+62
-84
lines changed

doc/source/v0.14.1.txt

+5
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ Performance
146146

147147
.. _whatsnew_0141.experimental:
148148

149+
150+
- Bug in line plot doesn't set correct ``xlim`` if ``secondary_y=True`` (:issue:`7459`)
151+
152+
153+
149154
Experimental
150155
~~~~~~~~~~~~
151156

pandas/tests/test_graphics.py

+35
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,20 @@ def test_plot_figsize_and_title(self):
457457
self._check_text_labels(ax.title, 'Test')
458458
self._check_axes_shape(ax, axes_num=1, layout=(1, 1), figsize=(16, 8))
459459

460+
def test_ts_line_lim(self):
461+
ax = self.ts.plot()
462+
xmin, xmax = ax.get_xlim()
463+
lines = ax.get_lines()
464+
self.assertEqual(xmin, lines[0].get_data(orig=False)[0][0])
465+
self.assertEqual(xmax, lines[0].get_data(orig=False)[0][-1])
466+
tm.close()
467+
468+
ax = self.ts.plot(secondary_y=True)
469+
xmin, xmax = ax.get_xlim()
470+
lines = ax.get_lines()
471+
self.assertEqual(xmin, lines[0].get_data(orig=False)[0][0])
472+
self.assertEqual(xmax, lines[0].get_data(orig=False)[0][-1])
473+
460474
def test_ts_area_lim(self):
461475
ax = self.ts.plot(kind='area', stacked=False)
462476
xmin, xmax = ax.get_xlim()
@@ -1098,6 +1112,27 @@ def test_line_area_nan_df(self):
10981112
self.assert_numpy_array_equal(ax.lines[0].get_ydata(), expected1)
10991113
self.assert_numpy_array_equal(ax.lines[1].get_ydata(), expected2)
11001114

1115+
def test_line_lim(self):
1116+
df = DataFrame(rand(6, 3), columns=['x', 'y', 'z'])
1117+
ax = df.plot()
1118+
xmin, xmax = ax.get_xlim()
1119+
lines = ax.get_lines()
1120+
self.assertEqual(xmin, lines[0].get_data()[0][0])
1121+
self.assertEqual(xmax, lines[0].get_data()[0][-1])
1122+
1123+
ax = df.plot(secondary_y=True)
1124+
xmin, xmax = ax.get_xlim()
1125+
lines = ax.get_lines()
1126+
self.assertEqual(xmin, lines[0].get_data()[0][0])
1127+
self.assertEqual(xmax, lines[0].get_data()[0][-1])
1128+
1129+
axes = df.plot(secondary_y=True, subplots=True)
1130+
for ax in axes:
1131+
xmin, xmax = ax.get_xlim()
1132+
lines = ax.get_lines()
1133+
self.assertEqual(xmin, lines[0].get_data()[0][0])
1134+
self.assertEqual(xmax, lines[0].get_data()[0][-1])
1135+
11011136
def test_area_lim(self):
11021137
df = DataFrame(rand(6, 4),
11031138
columns=['x', 'y', 'z', 'four'])

pandas/tools/plotting.py

+21-71
Original file line numberDiff line numberDiff line change
@@ -909,13 +909,9 @@ def _args_adjust(self):
909909
pass
910910

911911
def _maybe_right_yaxis(self, ax):
912-
_types = (list, tuple, np.ndarray)
913-
sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
914-
list_sec = isinstance(self.secondary_y, _types)
915-
has_sec = list_sec and len(self.secondary_y) > 0
916-
all_sec = list_sec and len(self.secondary_y) == self.nseries
917-
918-
if (sec_true or has_sec) and not hasattr(ax, 'right_ax'):
912+
if hasattr(ax, 'right_ax'):
913+
return ax.right_ax
914+
else:
919915
orig_ax, new_ax = ax, ax.twinx()
920916
new_ax._get_lines.color_cycle = orig_ax._get_lines.color_cycle
921917

@@ -924,38 +920,25 @@ def _maybe_right_yaxis(self, ax):
924920

925921
if len(orig_ax.get_lines()) == 0: # no data on left y
926922
orig_ax.get_yaxis().set_visible(False)
927-
928-
if len(new_ax.get_lines()) == 0:
929-
new_ax.get_yaxis().set_visible(False)
930-
931-
if sec_true or all_sec:
932-
ax = new_ax
933-
else:
934-
ax.get_yaxis().set_visible(True)
935-
936-
return ax
923+
return new_ax
937924

938925
def _setup_subplots(self):
939926
if self.subplots:
940927
nrows, ncols = self._get_layout()
941928
fig, axes = _subplots(nrows=nrows, ncols=ncols,
942929
sharex=self.sharex, sharey=self.sharey,
943-
figsize=self.figsize, ax=self.ax,
944-
secondary_y=self.secondary_y,
945-
data=self.data)
930+
figsize=self.figsize, ax=self.ax)
946931
if not com.is_list_like(axes):
947932
axes = np.array([axes])
948933
else:
949934
if self.ax is None:
950935
fig = self.plt.figure(figsize=self.figsize)
951936
ax = fig.add_subplot(111)
952-
ax = self._maybe_right_yaxis(ax)
953937
else:
954938
fig = self.ax.get_figure()
955939
if self.figsize is not None:
956940
fig.set_size_inches(self.figsize)
957-
ax = self._maybe_right_yaxis(self.ax)
958-
941+
ax = self.ax
959942
axes = [ax]
960943

961944
if self.logx or self.loglog:
@@ -1182,14 +1165,21 @@ def _get_ax(self, i):
11821165
# get the twinx ax if appropriate
11831166
if self.subplots:
11841167
ax = self.axes[i]
1168+
1169+
if self.on_right(i):
1170+
ax = self._maybe_right_yaxis(ax)
1171+
self.axes[i] = ax
11851172
else:
11861173
ax = self.axes[0]
11871174

1188-
if self.on_right(i):
1189-
if hasattr(ax, 'right_ax'):
1190-
ax = ax.right_ax
1191-
elif hasattr(ax, 'left_ax'):
1192-
ax = ax.left_ax
1175+
if self.on_right(i):
1176+
ax = self._maybe_right_yaxis(ax)
1177+
1178+
sec_true = isinstance(self.secondary_y, bool) and self.secondary_y
1179+
all_sec = (com.is_list_like(self.secondary_y) and
1180+
len(self.secondary_y) == self.nseries)
1181+
if sec_true or all_sec:
1182+
self.axes[0] = ax
11931183

11941184
ax.get_yaxis().set_visible(True)
11951185
return ax
@@ -1550,8 +1540,6 @@ def _make_plot(self):
15501540
data = self._maybe_convert_index(self.data)
15511541
self._make_ts_plot(data)
15521542
else:
1553-
from pandas.core.frame import DataFrame
1554-
lines = []
15551543
x = self._get_xticks(convert_period=True)
15561544

15571545
plotf = self._get_plot_function()
@@ -1563,8 +1551,6 @@ def _make_plot(self):
15631551
kwds = self.kwds.copy()
15641552
self._maybe_add_color(colors, kwds, style, i)
15651553

1566-
lines += _get_all_lines(ax)
1567-
15681554
errors = self._get_errorbars(label=label, index=i)
15691555
kwds = dict(kwds, **errors)
15701556

@@ -1588,15 +1574,13 @@ def _make_plot(self):
15881574
newlines = plotf(*args, **kwds)
15891575
self._add_legend_handle(newlines[0], label, index=i)
15901576

1591-
lines.append(newlines[0])
1592-
15931577
if self.stacked and not self.subplots:
15941578
if (y >= 0).all():
15951579
self._pos_prior += y
15961580
elif (y <= 0).all():
15971581
self._neg_prior += y
15981582

1599-
if self._is_datetype():
1583+
lines = _get_all_lines(ax)
16001584
left, right = _get_xlim(lines)
16011585
ax.set_xlim(left, right)
16021586

@@ -2253,14 +2237,7 @@ def plot_series(series, label=None, kind='line', use_index=True, rot=None,
22532237
import matplotlib.pyplot as plt
22542238
if ax is None and len(plt.get_fignums()) > 0:
22552239
ax = _gca()
2256-
if ax.get_yaxis().get_ticks_position().strip().lower() == 'right':
2257-
fig = _gcf()
2258-
axes = fig.get_axes()
2259-
for i in reversed(range(len(axes))):
2260-
ax = axes[i]
2261-
ypos = ax.get_yaxis().get_ticks_position().strip().lower()
2262-
if ypos == 'left':
2263-
break
2240+
ax = getattr(ax, 'left_ax', ax)
22642241

22652242
# is there harm in this?
22662243
if label is None:
@@ -2884,8 +2861,7 @@ def _get_layout(nplots, layout=None):
28842861

28852862

28862863
def _subplots(nrows=1, ncols=1, naxes=None, sharex=False, sharey=False, squeeze=True,
2887-
subplot_kw=None, ax=None, secondary_y=False, data=None,
2888-
**fig_kw):
2864+
subplot_kw=None, ax=None, **fig_kw):
28892865
"""Create a figure with a set of subplots already made.
28902866
28912867
This utility wrapper makes it convenient to create common layouts of
@@ -2926,12 +2902,6 @@ def _subplots(nrows=1, ncols=1, naxes=None, sharex=False, sharey=False, squeeze=
29262902
29272903
ax : Matplotlib axis object, optional
29282904
2929-
secondary_y : boolean or sequence of ints, default False
2930-
If True then y-axis will be on the right
2931-
2932-
data : DataFrame, optional
2933-
If secondary_y is a sequence, data is used to select columns.
2934-
29352905
fig_kw : Other keyword arguments to be passed to the figure() call.
29362906
Note that all keywords not recognized above will be
29372907
automatically included here.
@@ -2996,22 +2966,8 @@ def _subplots(nrows=1, ncols=1, naxes=None, sharex=False, sharey=False, squeeze=
29962966

29972967
axarr = np.empty(nplots, dtype=object)
29982968

2999-
def on_right(i):
3000-
if isinstance(secondary_y, bool):
3001-
return secondary_y
3002-
if isinstance(data, DataFrame):
3003-
return data.columns[i] in secondary_y
3004-
30052969
# Create first subplot separately, so we can share it if requested
30062970
ax0 = fig.add_subplot(nrows, ncols, 1, **subplot_kw)
3007-
if on_right(0):
3008-
orig_ax = ax0
3009-
ax0 = ax0.twinx()
3010-
ax0._get_lines.color_cycle = orig_ax._get_lines.color_cycle
3011-
3012-
orig_ax.get_yaxis().set_visible(False)
3013-
orig_ax.right_ax = ax0
3014-
ax0.left_ax = orig_ax
30152971

30162972
if sharex:
30172973
subplot_kw['sharex'] = ax0
@@ -3023,12 +2979,6 @@ def on_right(i):
30232979
# convention.
30242980
for i in range(1, nplots):
30252981
ax = fig.add_subplot(nrows, ncols, i + 1, **subplot_kw)
3026-
if on_right(i):
3027-
orig_ax = ax
3028-
ax = ax.twinx()
3029-
ax._get_lines.color_cycle = orig_ax._get_lines.color_cycle
3030-
3031-
orig_ax.get_yaxis().set_visible(False)
30322982
axarr[i] = ax
30332983

30342984
if nplots > 1:

pandas/tseries/plotting.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
"""
55

66
#!!! TODO: Use the fact that axis can have units to simplify the process
7-
import datetime as pydt
8-
from datetime import datetime
9-
107
from matplotlib import pylab
11-
import matplotlib.units as units
128

139
import numpy as np
1410

@@ -22,7 +18,7 @@
2218
from pandas.tseries.converter import (PeriodConverter, TimeSeries_DateLocator,
2319
TimeSeries_DateFormatter)
2420

25-
from pandas.tools.plotting import _get_all_lines
21+
from pandas.tools.plotting import _get_all_lines, _get_xlim
2622

2723
#----------------------------------------------------------------------
2824
# Plotting functions and monkey patches
@@ -222,14 +218,6 @@ def _get_freq(ax, series):
222218
return freq
223219

224220

225-
def _get_xlim(lines):
226-
left, right = np.inf, -np.inf
227-
for l in lines:
228-
x = l.get_xdata()
229-
left = min(x[0].ordinal, left)
230-
right = max(x[-1].ordinal, right)
231-
return left, right
232-
233221
# Patch methods for subplot. Only format_dateaxis is currently used.
234222
# Do we need the rest for convenience?
235223

0 commit comments

Comments
 (0)