Skip to content

Commit 0940b78

Browse files
Chang Shewesm
Chang She
authored andcommitted
BUG: plot mixed frequencies #1517
1 parent 66690f2 commit 0940b78

File tree

3 files changed

+140
-45
lines changed

3 files changed

+140
-45
lines changed

pandas/tools/plotting.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,16 @@ def plt(self):
485485

486486
_need_to_set_index = False
487487

488-
def _get_xticks(self):
488+
def _get_xticks(self, convert_period=False):
489489
index = self.data.index
490490
is_datetype = index.inferred_type in ('datetime', 'date',
491491
'datetime64')
492492

493493
if self.use_index:
494-
if index.is_numeric() or is_datetype:
494+
if convert_period and isinstance(index, PeriodIndex):
495+
index = index.to_timestamp()
496+
x = index._mpl_repr()
497+
elif index.is_numeric() or is_datetype:
495498
"""
496499
Matplotlib supports numeric values or datetime objects as
497500
xaxis values. Taking LBYL approach here, by the time
@@ -594,33 +597,41 @@ class LinePlot(MPLPlot):
594597
def __init__(self, data, **kwargs):
595598
MPLPlot.__init__(self, data, **kwargs)
596599

597-
@property
598-
def has_ts_index(self):
599-
# TODO refactor this whole regular/irregular kludge
600+
def _index_freq(self):
600601
from pandas.core.frame import DataFrame
601602
if isinstance(self.data, (Series, DataFrame)):
602-
ax, _ = self._get_ax_and_style(0)
603603
freq = (getattr(self.data.index, 'freq', None)
604-
or getattr(self.data.index, 'inferred_freq', None)
605-
or getattr(ax, 'freq', None))
606-
return (freq is not None) and self._has_dynamic_index_freq(freq)
607-
return False
604+
or getattr(self.data.index, 'inferred_freq', None))
605+
return freq
608606

609-
def _has_dynamic_index_freq(self, freq):
607+
def _is_dynamic_freq(self, freq):
610608
if isinstance(freq, DateOffset):
611609
freq = freq.rule_code
612610
else:
613611
freq = get_base_alias(freq)
614612
freq = get_period_alias(freq)
615613
return freq is not None
616614

615+
def _use_dynamic_x(self):
616+
freq = self._index_freq()
617+
618+
ax, _ = self._get_ax_and_style(0)
619+
ax_freq = getattr(ax, 'freq', None)
620+
if freq is None: # convert irregular if axes has freq info
621+
freq = ax_freq
622+
else: # do not use tsplot if irregular was plotted first
623+
if (ax_freq is None) and (len(ax.get_lines()) > 0):
624+
return False
625+
626+
return (freq is not None) and self._is_dynamic_freq(freq)
627+
617628
def _make_plot(self):
618629
# this is slightly deceptive
619-
if self.use_index and self.has_ts_index:
630+
if self.use_index and self._use_dynamic_x():
620631
data = self._maybe_convert_index(self.data)
621632
self._make_ts_plot(data)
622633
else:
623-
x = self._get_xticks()
634+
x = self._get_xticks(convert_period=True)
624635

625636
plotf = self._get_plot_function()
626637

@@ -649,6 +660,8 @@ def _maybe_convert_index(self, data):
649660
if (isinstance(data.index, DatetimeIndex) and
650661
isinstance(data, DataFrame)):
651662
freq = getattr(data.index, 'freqstr', None)
663+
if isinstance(freq, DateOffset):
664+
freq = freq.rule_code
652665

653666
freq = get_period_alias(freq)
654667

@@ -659,9 +672,6 @@ def _maybe_convert_index(self, data):
659672
ax, _ = self._get_ax_and_style(0)
660673
freq = getattr(ax, 'freq', None)
661674

662-
if isinstance(freq, DateOffset):
663-
freq = freq.rule_code
664-
665675
data = DataFrame(data.values,
666676
index=data.index.to_period(freq=freq),
667677
columns=data.columns)
@@ -699,7 +709,7 @@ def _post_plot_logic(self):
699709
else:
700710
self.axes[0].legend(loc='best')
701711

702-
condition = (not self.has_ts_index
712+
condition = (not self._use_dynamic_x
703713
and df.index.is_all_dates
704714
and not self.subplots
705715
or (self.subplots and self.sharex))

pandas/tseries/plotting.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,30 @@ def tsplot(series, plotf, **kwargs):
4343
4444
"""
4545
# Used inferred freq is possible, need a test case for inferred
46-
freq = getattr(series.index, 'freq', None)
47-
if freq is None:
48-
freq = getattr(series.index, 'inferred_freq', None)
49-
5046
if 'ax' in kwargs:
5147
ax = kwargs.pop('ax')
5248
else:
5349
import matplotlib.pyplot as plt
5450
ax = plt.gca()
5551

56-
if freq is None:
57-
freq = getattr(ax, 'freq', None)
58-
59-
if isinstance(freq, DateOffset):
60-
freq = freq.rule_code
52+
freq = _get_freq(ax, series)
53+
# resample against axes freq if necessary
54+
if freq is None: # pragma: no cover
55+
raise ValueError('Cannot use dynamic axis without frequency info')
6156
else:
62-
freq = frequencies.get_base_alias(freq)
57+
ax_freq = getattr(ax, 'freq', None)
58+
if (ax_freq is not None) and (freq != ax_freq):
59+
if frequencies.is_subperiod(freq, ax_freq): # downsample
60+
how = kwargs.pop('how', 'first')
61+
series = series.resample(ax_freq, how=how)
62+
elif frequencies.is_superperiod(freq, ax_freq):
63+
series = series.resample(ax_freq)
64+
freq = ax_freq
6365

64-
freq = frequencies.get_period_alias(freq)
6566
# Convert DatetimeIndex to PeriodIndex
6667
if isinstance(series.index, DatetimeIndex):
6768
series = series.to_period(freq=freq)
6869

69-
if freq != series.index.freq:
70-
series = series.asfreq(freq)
71-
7270
style = kwargs.pop('style', None)
7371

7472
# Specialized ts plotting attributes for Axes
@@ -81,13 +79,7 @@ def tsplot(series, plotf, **kwargs):
8179
ax.date_axis_info = None
8280

8381
# format args and lot
84-
mask = isnull(series)
85-
if mask.any():
86-
masked_array = np.ma.array(series.values)
87-
masked_array = np.ma.masked_where(mask, masked_array)
88-
args = [series.index, masked_array]
89-
else:
90-
args = [series.index, series]
82+
args = _maybe_mask(series)
9183

9284
if style is not None:
9385
args.append(style)
@@ -101,6 +93,38 @@ def tsplot(series, plotf, **kwargs):
10193

10294
return ax
10395

96+
def _maybe_mask(series):
97+
mask = isnull(series)
98+
if mask.any():
99+
masked_array = np.ma.array(series.values)
100+
masked_array = np.ma.masked_where(mask, masked_array)
101+
args = [series.index, masked_array]
102+
else:
103+
args = [series.index, series]
104+
return args
105+
106+
def _get_freq(ax, series):
107+
# get frequency from data
108+
freq = getattr(series.index, 'freq', None)
109+
if freq is None:
110+
freq = getattr(series.index, 'inferred_freq', None)
111+
112+
ax_freq = getattr(ax, 'freq', None)
113+
114+
# use axes freq if no data freq
115+
if freq is None:
116+
freq = ax_freq
117+
118+
# get the period frequency
119+
if isinstance(freq, DateOffset):
120+
freq = freq.rule_code
121+
else:
122+
freq = frequencies.get_base_alias(freq)
123+
124+
freq = frequencies.get_period_alias(freq)
125+
126+
return freq
127+
104128
def _get_xlim(lines):
105129
left, right = np.inf, -np.inf
106130
for l in lines:

pandas/tseries/tests/test_plotting.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import numpy as np
88
from numpy.testing.decorators import slow
9+
from numpy.testing import assert_array_equal
910

1011
from pandas import Index, Series, DataFrame, isnull, notnull
1112

@@ -52,17 +53,22 @@ def setUp(self):
5253
def test_tsplot(self):
5354
from pandas.tseries.plotting import tsplot
5455
import matplotlib.pyplot as plt
56+
plt.close('all')
57+
5558
ax = plt.gca()
5659
ts = tm.makeTimeSeries()
5760
plot_ax = tsplot(ts, plt.Axes.plot)
5861
self.assert_(plot_ax == ax)
5962

6063
f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds)
64+
plt.close('all')
6165

6266
for s in self.period_ser:
6367
_check_plot_works(f, s.index.freq, ax=ax, series=s)
68+
plt.close('all')
6469
for s in self.datetime_ser:
6570
_check_plot_works(f, s.index.freq.rule_code, ax=ax, series=s)
71+
plt.close('all')
6672

6773
plt.close('all')
6874
ax = ts.plot(style='k')
@@ -442,7 +448,7 @@ def test_secondary_frame(self):
442448
self.assert_(axes[2].get_yaxis().get_ticks_position() == 'right')
443449

444450
@slow
445-
def test_mixed_freq(self):
451+
def test_mixed_freq_regular_first(self):
446452
import matplotlib.pyplot as plt
447453
plt.close('all')
448454
s1 = tm.makeTimeSeries()
@@ -458,6 +464,58 @@ def test_mixed_freq(self):
458464
pidx = s1.index.to_period()
459465
self.assert_(left == pidx[0].ordinal)
460466
self.assert_(right == pidx[-1].ordinal)
467+
plt.close('all')
468+
469+
@slow
470+
def test_mixed_freq_irregular_first(self):
471+
import matplotlib.pyplot as plt
472+
plt.close('all')
473+
s1 = tm.makeTimeSeries()
474+
s2 = s1[[0, 5, 10, 11, 12, 13, 14, 15]]
475+
s2.plot(style='g')
476+
ax = s1.plot()
477+
self.assert_(not hasattr(ax, 'freq'))
478+
lines = ax.get_lines()
479+
x1 = lines[0].get_xdata()
480+
assert_array_equal(x1, s2.index.asobject.values)
481+
x2 = lines[1].get_xdata()
482+
assert_array_equal(x2, s1.index.asobject.values)
483+
plt.close('all')
484+
485+
@slow
486+
def test_mixed_freq_hf_first(self):
487+
import matplotlib.pyplot as plt
488+
plt.close('all')
489+
idxh = date_range('1/1/1999', periods=365, freq='D')
490+
idxl = date_range('1/1/1999', periods=12, freq='M')
491+
high = Series(np.random.randn(len(idxh)), idxh)
492+
low = Series(np.random.randn(len(idxl)), idxl)
493+
high.plot()
494+
ax = low.plot()
495+
for l in ax.get_lines():
496+
self.assert_(l.get_xdata().freq == 'D')
497+
498+
@slow
499+
def test_mixed_freq_lf_first(self):
500+
import matplotlib.pyplot as plt
501+
plt.close('all')
502+
idxh = date_range('1/1/1999', periods=365, freq='D')
503+
idxl = date_range('1/1/1999', periods=12, freq='M')
504+
high = Series(np.random.randn(len(idxh)), idxh)
505+
low = Series(np.random.randn(len(idxl)), idxl)
506+
low.plot()
507+
ax = high.plot()
508+
for l in ax.get_lines():
509+
self.assert_(l.get_xdata().freq == 'M')
510+
511+
@slow
512+
def test_mixed_freq_irreg_period(self):
513+
ts = tm.makeTimeSeries()
514+
irreg = ts[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 29]]
515+
rng = period_range('1/3/2000', periods=30, freq='B')
516+
ps = Series(np.random.randn(len(rng)), rng)
517+
irreg.plot()
518+
ps.plot()
461519

462520
PNG_PATH = 'tmp.png'
463521
def _check_plot_works(f, freq=None, series=None, *args, **kwargs):
@@ -466,19 +524,22 @@ def _check_plot_works(f, freq=None, series=None, *args, **kwargs):
466524
fig = plt.gcf()
467525
plt.clf()
468526
ax = fig.add_subplot(211)
527+
orig_ax = kwargs.pop('ax', plt.gca())
528+
orig_axfreq = getattr(orig_ax, 'freq', None)
529+
469530
ret = f(*args, **kwargs)
470531
assert(ret is not None) # do something more intelligent
471532

472-
orig_ax = kwargs.pop('ax', plt.gca())
473-
if series is not None: # non-business
533+
ax = kwargs.pop('ax', plt.gca())
534+
if series is not None:
474535
dfreq = series.index.freq
475536
if isinstance(dfreq, DateOffset):
476537
dfreq = dfreq.rule_code
477-
#dfreq = frequencies.offset_to_period_alias(dfreq)
478-
assert(orig_ax.freq == dfreq)
538+
if orig_axfreq is None:
539+
assert(ax.freq == dfreq)
479540

480-
if freq is not None:
481-
assert(orig_ax.freq == freq)
541+
if freq is not None and orig_axfreq is None:
542+
assert(ax.freq == freq)
482543

483544
ax = fig.add_subplot(212)
484545
try:

0 commit comments

Comments
 (0)