Skip to content

Commit e17c194

Browse files
author
Chang She
committed
BUG: tsplot calling Series constructor wrong when converting from DatetimeIndex to PeriodIndex.
1 parent b1c7b20 commit e17c194

File tree

1 file changed

+114
-96
lines changed

1 file changed

+114
-96
lines changed

pandas/tseries/plotting.py

Lines changed: 114 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,30 @@ def tsplot(axes, series, *args, **kwargs):
6666
Supports same args and kwargs as Axes.plot
6767
6868
"""
69+
# Used inferred freq is possible, need a test case for inferred
6970
freq = getattr(series.index, 'freq', None)
7071
if freq is None and hasattr(series.index, 'inferred_freq'):
7172
freq = series.index.inferred_freq
7273
if isinstance(freq, DateOffset):
7374
freq = freq.rule_code
7475

76+
# Convert DatetimeIndex to PeriodIndex
7577
if isinstance(series.index, DatetimeIndex):
7678
idx = series.index.to_period(freq=freq)
77-
series = Series(series.values, idx, series.name)
79+
series = Series(series.values, idx, name=series.name)
7880

7981
if not isinstance(series.index, PeriodIndex):
8082
raise TypeError('series argument to tsplot must have DatetimeIndex or '
8183
'PeriodIndex')
8284

83-
args = _check_plot_params(series, series.index, freq, *args)
84-
85+
# Specialized ts plotting attributes for Axes
8586
axes.freq = freq
8687
axes.legendlabels = [kwargs.get('label', None)]
8788
axes.view_interval = None
8889
axes.date_axis_info = None
8990

91+
# format args and lot
92+
args = _check_plot_params(series, series.index, freq, *args)
9093
plotted = axes.plot(*args, **kwargs)
9194

9295
format_dateaxis(axes, axes.freq)
@@ -118,124 +121,110 @@ def get_datevalue(date, freq):
118121
return None
119122
raise ValueError("Unrecognizable date '%s'" % date)
120123

121-
def format_dateaxis(subplot, freq):
122-
"""
123-
Pretty-formats the date axis (x-axis).
124124

125-
Major and minor ticks are automatically set for the frequency of the
126-
current underlying series. As the dynamic mode is activated by
127-
default, changing the limits of the x axis will intelligently change
128-
the positions of the ticks.
129-
"""
130-
majlocator = TimeSeries_DateLocator(freq, dynamic_mode=True,
131-
minor_locator=False,
132-
plot_obj=subplot)
133-
minlocator = TimeSeries_DateLocator(freq, dynamic_mode=True,
134-
minor_locator=True,
135-
plot_obj=subplot)
136-
subplot.xaxis.set_major_locator(majlocator)
137-
subplot.xaxis.set_minor_locator(minlocator)
138-
139-
majformatter = TimeSeries_DateFormatter(freq, dynamic_mode=True,
140-
minor_locator=False,
141-
plot_obj=subplot)
142-
minformatter = TimeSeries_DateFormatter(freq, dynamic_mode=True,
143-
minor_locator=True,
144-
plot_obj=subplot)
145-
subplot.xaxis.set_major_formatter(majformatter)
146-
subplot.xaxis.set_minor_formatter(minformatter)
147-
pylab.draw_if_interactive()
125+
# Check and format plotting parameters
148126

149127
def _check_plot_params(series, xdata, freq, *args):
150128
"""
151129
Defines the plot coordinates (and basic plotting arguments).
152130
"""
153-
# TODO clean up this massive method
154131
remaining = list(args)
155132
noinfo_msg = "No date information available!"
133+
156134
# No args ? Use defaults, if any
157135
if len(args) == 0:
158136
if xdata is None:
159137
raise ValueError(noinfo_msg)
160138
return (xdata, series)
139+
161140
output = []
162141
while len(remaining) > 0:
163142
a = remaining.pop(0)
143+
output.extend(_handle_param(a, remaining, series, xdata, freq))
164144

165-
# The argument is a format: use default dates/
166-
if isinstance(a, str):
167-
if xdata is None:
168-
raise ValueError(noinfo_msg)
169-
else:
170-
output.extend([xdata, series, a])
171-
172-
# The argument is a Series: use its dates for x
173-
elif isinstance(a, Series):
174-
(x, y) = (a.index, a.values)
175-
if len(remaining) > 0 and isinstance(remaining[0], str):
176-
b = remaining.pop(0)
177-
output.extend([x, y, b])
178-
else:
179-
output.extend([x, y])
180-
181-
# The argument is a PeriodIndex............
182-
elif isinstance(a, PeriodIndex):
183-
# Force to current freq
184-
if freq is not None:
185-
if a.freq != freq:
186-
a = a.asfreq(freq)
187-
188-
# There's an argument after
189-
if len(remaining) > 0:
190-
191-
#...and it's a format string
192-
if isinstance(remaining[0], str):
193-
b = remaining.pop(0)
194-
if series is None:
195-
raise ValueError(noinfo_msg)
196-
else:
197-
output.extend([a, series, b])
198-
199-
#... and it's another date: use the default
200-
elif isinstance(remaining[0], PeriodIndex):
201-
if series is None:
202-
raise ValueError(noinfo_msg)
203-
else:
204-
output.extend([a, series])
205-
206-
#... and it must be some data
207-
else:
208-
b = remaining.pop(0)
209-
if len(remaining) > 0:
210-
if isinstance(remaining[0], str):
211-
c = remaining.pop(0)
212-
output.extend([a, b, c])
213-
else:
214-
output.extend([a, b])
215-
else:
216-
if series is None:
217-
raise ValueError(noinfo_msg)
218-
219-
# Otherwise..............................
220-
elif len(remaining) > 0 and isinstance(remaining[0], str):
221-
b = remaining.pop(0)
222-
if xdata is None:
223-
raise ValueError(noinfo_msg)
224-
else:
225-
output.extend([xdata, a, b])
226-
elif xdata is None:
227-
raise ValueError(noinfo_msg)
228-
else:
229-
output.extend([xdata, a])
230145
# Reinitialize the plot if needed ...........
231146
if xdata is None:
232147
xdata = output[0]
148+
233149
# Force the xdata to the current frequency
234150
elif output[0].freq != freq:
235151
output = list(output)
236152
output[0] = output[0].asfreq(freq)
153+
237154
return output
238155

156+
def _handle_param(curr, remaining, series, xdata, freq):
157+
# The argument is a format: use default dates/
158+
noinfo_msg = "No date information available!"
159+
if isinstance(curr, str):
160+
if xdata is None:
161+
raise ValueError(noinfo_msg)
162+
else:
163+
return [xdata, series, curr]
164+
165+
# The argument is a Series: use its dates for x
166+
elif isinstance(curr, Series):
167+
(x, y) = (curr.index, curr.values)
168+
if len(remaining) > 0 and isinstance(remaining[0], str):
169+
b = remaining.pop(0)
170+
return [x, y, b]
171+
else:
172+
return [x, y]
173+
174+
# The argument is a PeriodIndex............
175+
elif isinstance(curr, PeriodIndex):
176+
return _handle_period_index(curr, remaining, series, xdata, freq)
177+
178+
# Otherwise..............................
179+
elif len(remaining) > 0 and isinstance(remaining[0], str):
180+
b = remaining.pop(0)
181+
if xdata is None:
182+
raise ValueError(noinfo_msg)
183+
else:
184+
return [xdata, curr, b]
185+
elif xdata is None:
186+
raise ValueError(noinfo_msg)
187+
else:
188+
return [xdata, curr]
189+
190+
def _handle_period_index(curr, remaining, series, xdata, freq):
191+
# Force to current freq
192+
noinfo_msg = "No date information available!"
193+
if freq is not None:
194+
if curr.freq != freq:
195+
curr = curr.asfreq(freq)
196+
197+
# There's an argument after
198+
if len(remaining) > 0:
199+
#...and it's a format string
200+
if isinstance(remaining[0], str):
201+
b = remaining.pop(0)
202+
if series is None:
203+
raise ValueError(noinfo_msg)
204+
else:
205+
return [curr, series, b]
206+
207+
#... and it's another date: use the default
208+
elif isinstance(remaining[0], PeriodIndex):
209+
if series is None:
210+
raise ValueError(noinfo_msg)
211+
else:
212+
return [curr, series]
213+
214+
#... and it must be some data
215+
else:
216+
b = remaining.pop(0)
217+
if len(remaining) > 0:
218+
if isinstance(remaining[0], str):
219+
c = remaining.pop(0)
220+
return [curr, b, c]
221+
else:
222+
return [curr, b]
223+
else:
224+
if series is None:
225+
raise ValueError(noinfo_msg)
226+
227+
239228
##### -------------------------------------------------------------------------
240229
#---- --- Locators ---
241230
##### -------------------------------------------------------------------------
@@ -816,7 +805,36 @@ def __call__(self, x, pos=0):
816805
fmt = self.formatdict.pop(x, '')
817806
return Period(int(x), self.freq).strftime(fmt)
818807

819-
# Do we need these monkey patch methods for convenience?
808+
# Patch methods for subplot. Only format_dateaxis is currently used.
809+
# Do we need the rest for convenience?
810+
811+
def format_dateaxis(subplot, freq):
812+
"""
813+
Pretty-formats the date axis (x-axis).
814+
815+
Major and minor ticks are automatically set for the frequency of the
816+
current underlying series. As the dynamic mode is activated by
817+
default, changing the limits of the x axis will intelligently change
818+
the positions of the ticks.
819+
"""
820+
majlocator = TimeSeries_DateLocator(freq, dynamic_mode=True,
821+
minor_locator=False,
822+
plot_obj=subplot)
823+
minlocator = TimeSeries_DateLocator(freq, dynamic_mode=True,
824+
minor_locator=True,
825+
plot_obj=subplot)
826+
subplot.xaxis.set_major_locator(majlocator)
827+
subplot.xaxis.set_minor_locator(minlocator)
828+
829+
majformatter = TimeSeries_DateFormatter(freq, dynamic_mode=True,
830+
minor_locator=False,
831+
plot_obj=subplot)
832+
minformatter = TimeSeries_DateFormatter(freq, dynamic_mode=True,
833+
minor_locator=True,
834+
plot_obj=subplot)
835+
subplot.xaxis.set_major_formatter(majformatter)
836+
subplot.xaxis.set_minor_formatter(minformatter)
837+
pylab.draw_if_interactive()
820838

821839
def add_yaxis(fsp=None, position='right', yscale=None, basey=10, subsy=None):
822840
"""

0 commit comments

Comments
 (0)