Skip to content

Commit cab20d6

Browse files
committed
BUG/CLN: Refactoring tsplot
1 parent 4950474 commit cab20d6

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
@@ -1517,18 +1517,6 @@ def __init__(self, data, **kwargs):
15171517
if 'x_compat' in self.kwds:
15181518
self.x_compat = bool(self.kwds.pop('x_compat'))
15191519

1520-
def _index_freq(self):
1521-
from pandas.core.frame import DataFrame
1522-
if isinstance(self.data, (Series, DataFrame)):
1523-
freq = getattr(self.data.index, 'freq', None)
1524-
if freq is None:
1525-
freq = getattr(self.data.index, 'inferred_freq', None)
1526-
if freq == 'B':
1527-
weekdays = np.unique(self.data.index.dayofweek)
1528-
if (5 in weekdays) or (6 in weekdays):
1529-
freq = None
1530-
return freq
1531-
15321520
def _is_dynamic_freq(self, freq):
15331521
if isinstance(freq, DateOffset):
15341522
freq = freq.rule_code
@@ -1539,9 +1527,7 @@ def _is_dynamic_freq(self, freq):
15391527

15401528
def _no_base(self, freq):
15411529
# hack this for 0.10.1, creating more technical debt...sigh
1542-
from pandas.core.frame import DataFrame
1543-
if (isinstance(self.data, (Series, DataFrame))
1544-
and isinstance(self.data.index, DatetimeIndex)):
1530+
if isinstance(self.data.index, DatetimeIndex):
15451531
base = frequencies.get_freq(freq)
15461532
x = self.data.index
15471533
if (base <= frequencies.FreqGroup.FR_DAY):
@@ -1551,7 +1537,8 @@ def _no_base(self, freq):
15511537
return True
15521538

15531539
def _use_dynamic_x(self):
1554-
freq = self._index_freq()
1540+
from pandas.tseries.plotting import _get_index_freq
1541+
freq = _get_index_freq(self.data)
15551542

15561543
ax = self._get_ax(0)
15571544
ax_freq = getattr(ax, 'freq', None)
@@ -1572,10 +1559,22 @@ def _make_plot(self):
15721559

15731560
if self._is_ts_plot():
15741561
data = self._maybe_convert_index(self.data)
1562+
freq = data.index.freq
1563+
kwds = self.kwds.copy()
1564+
from pandas.tseries.plotting import _maybe_resample
1565+
for ax in self.axes:
1566+
# resample data and replot if required
1567+
data = _maybe_resample(data, ax, freq, kwds)
1568+
15751569
x = data.index # dummy, not used
15761570
plotf = self._get_ts_plot_function()
15771571
it = self._iter_data(data=data, keep_index=True)
15781572
else:
1573+
from pandas.tseries.plotting import _replot_x_compat
1574+
for ax in self.axes:
1575+
# if ax holds _plot_data, replot them on the x_compat scale
1576+
_replot_x_compat(ax)
1577+
15791578
x = self._get_xticks(convert_period=True)
15801579
plotf = self._get_plot_function()
15811580
it = self._iter_data()
@@ -1649,30 +1648,13 @@ def _update_prior(self, y):
16491648
self._neg_prior += y
16501649

16511650
def _maybe_convert_index(self, data):
1652-
# tsplot converts automatically, but don't want to convert index
1653-
# over and over for DataFrames
1654-
from pandas.core.frame import DataFrame
1655-
if (isinstance(data.index, DatetimeIndex) and
1656-
isinstance(data, DataFrame)):
1657-
freq = getattr(data.index, 'freq', None)
1658-
1659-
if freq is None:
1660-
freq = getattr(data.index, 'inferred_freq', None)
1661-
if isinstance(freq, DateOffset):
1662-
freq = freq.rule_code
1663-
freq = frequencies.get_base_alias(freq)
1664-
freq = frequencies.get_period_alias(freq)
1665-
1666-
if freq is None:
1667-
ax = self._get_ax(0)
1668-
freq = getattr(ax, 'freq', None)
1669-
1651+
# tsplot only accepts PeriodIndex
1652+
if isinstance(data.index, DatetimeIndex):
1653+
from pandas.tseries.plotting import _get_freq
1654+
freq = _get_freq(self._get_ax(0), self.data)
16701655
if freq is None:
16711656
raise ValueError('Could not get frequency alias for plotting')
1672-
1673-
data = DataFrame(data.values,
1674-
index=data.index.to_period(freq=freq),
1675-
columns=data.columns)
1657+
data = data.to_period(freq=freq)
16761658
return data
16771659

16781660
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
@@ -83,19 +83,16 @@ def test_nonnumeric_exclude(self):
8383

8484
@slow
8585
def test_tsplot(self):
86-
from pandas.tseries.plotting import tsplot
8786
import matplotlib.pyplot as plt
8887

8988
ax = plt.gca()
9089
ts = tm.makeTimeSeries()
9190

92-
f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds)
93-
9491
for s in self.period_ser:
95-
_check_plot_works(f, s.index.freq, ax=ax, series=s)
92+
_check_plot_works(s.plot, ax=ax)
9693

9794
for s in self.datetime_ser:
98-
_check_plot_works(f, s.index.freq.rule_code, ax=ax, series=s)
95+
_check_plot_works(s.plot, ax=ax)
9996

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

0 commit comments

Comments
 (0)