Skip to content

Commit 590633b

Browse files
committed
BUG/CLN: Refactoring tsplot
1 parent 9b842a0 commit 590633b

File tree

3 files changed

+106
-121
lines changed

3 files changed

+106
-121
lines changed

pandas/tools/plotting.py

+20-38
Original file line numberDiff line numberDiff line change
@@ -1552,18 +1552,6 @@ def __init__(self, data, **kwargs):
15521552
if 'x_compat' in self.kwds:
15531553
self.x_compat = bool(self.kwds.pop('x_compat'))
15541554

1555-
def _index_freq(self):
1556-
from pandas.core.frame import DataFrame
1557-
if isinstance(self.data, (Series, DataFrame)):
1558-
freq = getattr(self.data.index, 'freq', None)
1559-
if freq is None:
1560-
freq = getattr(self.data.index, 'inferred_freq', None)
1561-
if freq == 'B':
1562-
weekdays = np.unique(self.data.index.dayofweek)
1563-
if (5 in weekdays) or (6 in weekdays):
1564-
freq = None
1565-
return freq
1566-
15671555
def _is_dynamic_freq(self, freq):
15681556
if isinstance(freq, DateOffset):
15691557
freq = freq.rule_code
@@ -1574,9 +1562,7 @@ def _is_dynamic_freq(self, freq):
15741562

15751563
def _no_base(self, freq):
15761564
# hack this for 0.10.1, creating more technical debt...sigh
1577-
from pandas.core.frame import DataFrame
1578-
if (isinstance(self.data, (Series, DataFrame))
1579-
and isinstance(self.data.index, DatetimeIndex)):
1565+
if isinstance(self.data.index, DatetimeIndex):
15801566
base = frequencies.get_freq(freq)
15811567
x = self.data.index
15821568
if (base <= frequencies.FreqGroup.FR_DAY):
@@ -1586,7 +1572,8 @@ def _no_base(self, freq):
15861572
return True
15871573

15881574
def _use_dynamic_x(self):
1589-
freq = self._index_freq()
1575+
from pandas.tseries.plotting import _get_index_freq
1576+
freq = _get_index_freq(self.data)
15901577

15911578
ax = self._get_ax(0)
15921579
ax_freq = getattr(ax, 'freq', None)
@@ -1607,10 +1594,22 @@ def _make_plot(self):
16071594

16081595
if self._is_ts_plot():
16091596
data = self._maybe_convert_index(self.data)
1597+
freq = data.index.freq
1598+
kwds = self.kwds.copy()
1599+
from pandas.tseries.plotting import _maybe_resample
1600+
for ax in self.axes:
1601+
# resample data and replot if required
1602+
data = _maybe_resample(data, ax, freq, kwds)
1603+
16101604
x = data.index # dummy, not used
16111605
plotf = self._get_ts_plot_function()
16121606
it = self._iter_data(data=data, keep_index=True)
16131607
else:
1608+
from pandas.tseries.plotting import _replot_x_compat
1609+
for ax in self.axes:
1610+
# if ax holds _plot_data, replot them on the x_compat scale
1611+
_replot_x_compat(ax)
1612+
16141613
x = self._get_xticks(convert_period=True)
16151614
plotf = self._get_plot_function()
16161615
it = self._iter_data()
@@ -1684,30 +1683,13 @@ def _update_prior(self, y):
16841683
self._neg_prior += y
16851684

16861685
def _maybe_convert_index(self, data):
1687-
# tsplot converts automatically, but don't want to convert index
1688-
# over and over for DataFrames
1689-
from pandas.core.frame import DataFrame
1690-
if (isinstance(data.index, DatetimeIndex) and
1691-
isinstance(data, DataFrame)):
1692-
freq = getattr(data.index, 'freq', None)
1693-
1694-
if freq is None:
1695-
freq = getattr(data.index, 'inferred_freq', None)
1696-
if isinstance(freq, DateOffset):
1697-
freq = freq.rule_code
1698-
freq = frequencies.get_base_alias(freq)
1699-
freq = frequencies.get_period_alias(freq)
1700-
1701-
if freq is None:
1702-
ax = self._get_ax(0)
1703-
freq = getattr(ax, 'freq', None)
1704-
1686+
# tsplot only accepts PeriodIndex
1687+
if isinstance(data.index, DatetimeIndex):
1688+
from pandas.tseries.plotting import _get_freq
1689+
freq = _get_freq(self._get_ax(0), self.data)
17051690
if freq is None:
17061691
raise ValueError('Could not get frequency alias for plotting')
1707-
1708-
data = DataFrame(data.values,
1709-
index=data.index.to_period(freq=freq),
1710-
columns=data.columns)
1692+
data = data.to_period(freq=freq)
17111693
return data
17121694

17131695
def _post_plot_logic(self):

pandas/tseries/plotting.py

+84-78
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,18 @@
88

99
import numpy as np
1010

11-
from pandas import isnull
12-
from pandas.tseries.period import Period
11+
from pandas.tseries.period import Period, PeriodIndex
1312
from pandas.tseries.offsets import DateOffset
1413
import pandas.tseries.frequencies as frequencies
15-
from pandas.tseries.index import DatetimeIndex
16-
import pandas.core.common as com
1714

18-
from pandas.tseries.converter import (PeriodConverter, TimeSeries_DateLocator,
15+
from pandas.tseries.converter import (TimeSeries_DateLocator,
1916
TimeSeries_DateFormatter)
2017

2118
#----------------------------------------------------------------------
2219
# Plotting functions and monkey patches
2320

2421

25-
def tsplot(series, plotf, **kwargs):
22+
def tsplot(series, plotf, ax=None, **kwargs):
2623
"""
2724
Plots a Series on the given Matplotlib axes or the current axes
2825
@@ -36,36 +33,21 @@ def tsplot(series, plotf, **kwargs):
3633
Supports same kwargs as Axes.plot
3734
3835
"""
39-
# Used inferred freq is possible, need a test case for inferred
40-
if 'ax' in kwargs:
41-
ax = kwargs.pop('ax')
42-
else:
43-
import matplotlib.pyplot as plt
44-
ax = plt.gca()
45-
46-
freq = _get_freq(ax, series)
47-
# resample against axes freq if necessary
48-
if freq is None: # pragma: no cover
49-
raise ValueError('Cannot use dynamic axis without frequency info')
50-
else:
51-
# Convert DatetimeIndex to PeriodIndex
52-
if isinstance(series.index, DatetimeIndex):
53-
series = series.to_period(freq=freq)
54-
freq, ax_freq, series = _maybe_resample(series, ax, freq, plotf,
55-
kwargs)
56-
36+
if ax is None:
37+
raise ValueError('ax keyword must be passed')
38+
if not isinstance(series.index, PeriodIndex):
39+
raise ValueError('Passed data must have PeriodIndex')
5740
# Set ax with freq info
58-
_decorate_axes(ax, freq, kwargs)
41+
_decorate_axes(ax, series.index.freq)
5942

6043
# how to make sure ax.clear() flows through?
61-
if not hasattr(ax, '_plot_data'):
44+
if getattr(ax, '_plot_data', None) is None:
6245
ax._plot_data = []
6346
ax._plot_data.append((series, plotf, kwargs))
6447
lines = plotf(ax, series.index._mpl_repr(), series.values, **kwargs)
6548

6649
# set date formatter, locators and rescale limits
6750
format_dateaxis(ax, ax.freq)
68-
6951
# x and y coord info
7052
ax.format_coord = lambda t, y: ("t = {0} "
7153
"y = {1:8f}".format(Period(ordinal=int(t),
@@ -75,24 +57,24 @@ def tsplot(series, plotf, **kwargs):
7557
return lines
7658

7759

78-
def _maybe_resample(series, ax, freq, plotf, kwargs):
60+
def _maybe_resample(data, ax, freq, kwargs):
7961
ax_freq = _get_ax_freq(ax)
8062
if ax_freq is not None and freq != ax_freq:
8163
if frequencies.is_superperiod(freq, ax_freq): # upsample input
82-
series = series.copy()
83-
series.index = series.index.asfreq(ax_freq, how='s')
64+
data = data.copy()
65+
data.index = data.index.asfreq(ax_freq, how='s')
8466
freq = ax_freq
8567
elif _is_sup(freq, ax_freq): # one is weekly
8668
how = kwargs.pop('how', 'last')
87-
series = series.resample('D', how=how).dropna()
88-
series = series.resample(ax_freq, how=how).dropna()
69+
data = data.resample('D', how=how).dropna()
70+
data = data.resample(ax_freq, how=how).dropna()
8971
freq = ax_freq
9072
elif frequencies.is_subperiod(freq, ax_freq) or _is_sub(freq, ax_freq):
91-
_upsample_others(ax, freq, plotf, kwargs)
73+
_upsample_others(ax, freq)
9274
ax_freq = freq
9375
else: # pragma: no cover
9476
raise ValueError('Incompatible frequency conversion')
95-
return freq, ax_freq, series
77+
return data
9678

9779

9880
def _get_ax_freq(ax):
@@ -115,67 +97,91 @@ def _is_sup(f1, f2):
11597
(f2.startswith('W') and frequencies.is_superperiod(f1, 'D')))
11698

11799

118-
def _upsample_others(ax, freq, plotf, kwargs):
119-
legend = ax.get_legend()
120-
lines, labels = _replot_ax(ax, freq, kwargs)
100+
def _upsample_others(ax, freq):
101+
102+
def _replot(ax):
103+
data = getattr(ax, '_plot_data', None)
104+
if data is None:
105+
return
106+
107+
# preserve legend
108+
leg = ax.get_legend()
109+
handles, labels = ax.get_legend_handles_labels()
110+
111+
ax._plot_data = []
112+
ax.clear()
113+
_decorate_axes(ax, freq)
121114

122-
other_ax = None
123-
if hasattr(ax, 'left_ax'):
124-
other_ax = ax.left_ax
125-
if hasattr(ax, 'right_ax'):
126-
other_ax = ax.right_ax
127-
128-
if other_ax is not None:
129-
rlines, rlabels = _replot_ax(other_ax, freq, kwargs)
130-
lines.extend(rlines)
131-
labels.extend(rlabels)
132-
133-
if (legend is not None and kwargs.get('legend', True) and
134-
len(lines) > 0):
135-
title = legend.get_title().get_text()
136-
if title == 'None':
137-
title = None
138-
ax.legend(lines, labels, loc='best', title=title)
139-
140-
141-
def _replot_ax(ax, freq, kwargs):
142-
data = getattr(ax, '_plot_data', None)
143-
ax._plot_data = []
144-
ax.clear()
145-
_decorate_axes(ax, freq, kwargs)
146-
147-
lines = []
148-
labels = []
149-
if data is not None:
150115
for series, plotf, kwds in data:
151116
series = series.copy()
152117
idx = series.index.asfreq(freq, how='S')
153118
series.index = idx
154-
ax._plot_data.append(series)
155-
lines.append(plotf(ax, series.index._mpl_repr(), series.values, **kwds)[0])
156-
labels.append(com.pprint_thing(series.name))
119+
ax._plot_data.append((series, plotf, kwds))
120+
plotf(ax, series.index._mpl_repr(), series, **kwds)
121+
122+
if leg is not None:
123+
ax.legend(handles, labels, title=leg.get_title().get_text())
124+
125+
_replot(ax)
126+
if hasattr(ax, 'left_ax'):
127+
_replot(ax.left_ax)
128+
elif hasattr(ax, 'right_ax'):
129+
_replot(ax.right_ax)
130+
131+
132+
def _replot_x_compat(ax):
133+
134+
def _replot(ax):
135+
data = getattr(ax, '_plot_data', None)
136+
if data is None:
137+
return
138+
139+
# preserve legend
140+
leg = ax.get_legend()
141+
handles, labels = ax.get_legend_handles_labels()
142+
143+
ax._plot_data = None
144+
ax.clear()
145+
146+
_decorate_axes(ax, None)
147+
148+
for series, plotf, kwds in data:
149+
idx = series.index.to_timestamp(how='e')
150+
series.index = idx
151+
plotf(ax, series.index_mpl_repr(), series, **kwds)
157152

158-
return lines, labels
153+
if leg is not None:
154+
ax.legend(handles, labels, title=leg.get_title().get_text())
155+
156+
_replot(ax)
157+
if hasattr(ax, 'left_ax'):
158+
_replot(ax.left_ax)
159+
elif hasattr(ax, 'right_ax'):
160+
_replot(ax.right_ax)
159161

160162

161-
def _decorate_axes(ax, freq, kwargs):
163+
def _decorate_axes(ax, freq):
162164
ax.freq = freq
163165
xaxis = ax.get_xaxis()
164166
xaxis.freq = freq
165-
if not hasattr(ax, 'legendlabels'):
166-
ax.legendlabels = [kwargs.get('label', None)]
167-
else:
168-
ax.legendlabels.append(kwargs.get('label', None))
169167
ax.view_interval = None
170168
ax.date_axis_info = None
171169

172170

173-
def _get_freq(ax, series):
174-
# get frequency from data
175-
freq = getattr(series.index, 'freq', None)
171+
def _get_index_freq(data):
172+
freq = getattr(data.index, 'freq', None)
176173
if freq is None:
177-
freq = getattr(series.index, 'inferred_freq', None)
174+
freq = getattr(data.index, 'inferred_freq', None)
175+
if freq == 'B':
176+
weekdays = np.unique(data.index.dayofweek)
177+
if (5 in weekdays) or (6 in weekdays):
178+
freq = None
179+
return freq
180+
178181

182+
def _get_freq(ax, data):
183+
# get frequency from data
184+
freq = _get_index_freq(data)
179185
ax_freq = getattr(ax, 'freq', None)
180186

181187
# use axes freq if no data freq

pandas/tseries/tests/test_plotting.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,16 @@ def test_nonnumeric_exclude(self):
9191

9292
@slow
9393
def test_tsplot(self):
94-
from pandas.tseries.plotting import tsplot
9594
import matplotlib.pyplot as plt
9695

9796
ax = plt.gca()
9897
ts = tm.makeTimeSeries()
9998

100-
f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds)
101-
10299
for s in self.period_ser:
103-
_check_plot_works(f, s.index.freq, ax=ax, series=s)
100+
_check_plot_works(s.plot, ax=ax)
104101

105102
for s in self.datetime_ser:
106-
_check_plot_works(f, s.index.freq.rule_code, ax=ax, series=s)
103+
_check_plot_works(s.plot, ax=ax)
107104

108105
ax = ts.plot(style='k')
109106
self.assertEqual((0., 0., 0.), ax.get_lines()[0].get_color())

0 commit comments

Comments
 (0)