diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 97bea5cb7f7ec..30505cfc4d48c 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -485,13 +485,16 @@ def plt(self): _need_to_set_index = False - def _get_xticks(self): + def _get_xticks(self, convert_period=False): index = self.data.index is_datetype = index.inferred_type in ('datetime', 'date', 'datetime64') if self.use_index: - if index.is_numeric() or is_datetype: + if convert_period and isinstance(index, PeriodIndex): + index = index.to_timestamp() + x = index._mpl_repr() + elif index.is_numeric() or is_datetype: """ Matplotlib supports numeric values or datetime objects as xaxis values. Taking LBYL approach here, by the time @@ -594,16 +597,14 @@ class LinePlot(MPLPlot): def __init__(self, data, **kwargs): MPLPlot.__init__(self, data, **kwargs) - @property - def has_ts_index(self): + def _index_freq(self): from pandas.core.frame import DataFrame if isinstance(self.data, (Series, DataFrame)): freq = (getattr(self.data.index, 'freq', None) or getattr(self.data.index, 'inferred_freq', None)) - return (freq is not None) and self._has_dynamic_index_freq(freq) - return False + return freq - def _has_dynamic_index_freq(self, freq): + def _is_dynamic_freq(self, freq): if isinstance(freq, DateOffset): freq = freq.rule_code else: @@ -611,13 +612,26 @@ def _has_dynamic_index_freq(self, freq): freq = get_period_alias(freq) return freq is not None + def _use_dynamic_x(self): + freq = self._index_freq() + + ax, _ = self._get_ax_and_style(0) + ax_freq = getattr(ax, 'freq', None) + if freq is None: # convert irregular if axes has freq info + freq = ax_freq + else: # do not use tsplot if irregular was plotted first + if (ax_freq is None) and (len(ax.get_lines()) > 0): + return False + + return (freq is not None) and self._is_dynamic_freq(freq) + def _make_plot(self): # this is slightly deceptive - if self.use_index and self.has_ts_index: + if self.use_index and self._use_dynamic_x(): data = self._maybe_convert_index(self.data) self._make_ts_plot(data) else: - x = self._get_xticks() + x = self._get_xticks(convert_period=True) plotf = self._get_plot_function() @@ -646,14 +660,17 @@ def _maybe_convert_index(self, data): if (isinstance(data.index, DatetimeIndex) and isinstance(data, DataFrame)): freq = getattr(data.index, 'freqstr', None) + if isinstance(freq, DateOffset): + freq = freq.rule_code freq = get_period_alias(freq) - if freq is None and hasattr(data.index, 'inferred_freq'): - freq = data.index.inferred_freq + if freq is None: + freq = getattr(data.index, 'inferred_freq', None) - if isinstance(freq, DateOffset): - freq = freq.rule_code + if freq is None: + ax, _ = self._get_ax_and_style(0) + freq = getattr(ax, 'freq', None) data = DataFrame(data.values, index=data.index.to_period(freq=freq), @@ -692,7 +709,7 @@ def _post_plot_logic(self): else: self.axes[0].legend(loc='best') - condition = (not self.has_ts_index + condition = (not self._use_dynamic_x and df.index.is_all_dates and not self.subplots or (self.subplots and self.sharex)) diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index 79823e8c12dca..3e3540271153a 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -43,31 +43,36 @@ def tsplot(series, plotf, **kwargs): """ # Used inferred freq is possible, need a test case for inferred - freq = getattr(series.index, 'freq', None) - if freq is None and hasattr(series.index, 'inferred_freq'): - freq = series.index.inferred_freq + if 'ax' in kwargs: + ax = kwargs.pop('ax') + else: + import matplotlib.pyplot as plt + ax = plt.gca() - if isinstance(freq, DateOffset): - freq = freq.rule_code + freq = _get_freq(ax, series) + # resample against axes freq if necessary + if freq is None: # pragma: no cover + raise ValueError('Cannot use dynamic axis without frequency info') else: - freq = frequencies.get_base_alias(freq) + ax_freq = getattr(ax, 'freq', None) + if (ax_freq is not None) and (freq != ax_freq): + if frequencies.is_subperiod(freq, ax_freq): # downsample + how = kwargs.pop('how', 'last') + series = series.resample(ax_freq, how=how) + elif frequencies.is_superperiod(freq, ax_freq): + series = series.resample(ax_freq) + else: # one freq is weekly + how = kwargs.pop('how', 'last') + series = series.resample('D', how=how, fill_method='pad') + series = series.resample(ax_freq, how=how, fill_method='pad') + freq = ax_freq - freq = frequencies.get_period_alias(freq) # Convert DatetimeIndex to PeriodIndex if isinstance(series.index, DatetimeIndex): series = series.to_period(freq=freq) - if freq != series.index.freq: - series = series.asfreq(freq) - style = kwargs.pop('style', None) - if 'ax' in kwargs: - ax = kwargs.pop('ax') - else: - import matplotlib.pyplot as plt - ax = plt.gca() - # Specialized ts plotting attributes for Axes ax.freq = freq xaxis = ax.get_xaxis() @@ -78,6 +83,21 @@ def tsplot(series, plotf, **kwargs): ax.date_axis_info = None # format args and lot + args = _maybe_mask(series) + + if style is not None: + args.append(style) + + plotf(ax, *args, **kwargs) + + format_dateaxis(ax, ax.freq) + + left, right = _get_xlim(ax.get_lines()) + ax.set_xlim(left, right) + + return ax + +def _maybe_mask(series): mask = isnull(series) if mask.any(): masked_array = np.ma.array(series.values) @@ -85,19 +105,37 @@ def tsplot(series, plotf, **kwargs): args = [series.index, masked_array] else: args = [series.index, series] + return args - if style is not None: - args.append(style) +def _get_freq(ax, series): + # get frequency from data + freq = getattr(series.index, 'freq', None) + if freq is None: + freq = getattr(series.index, 'inferred_freq', None) - plotf(ax, *args, **kwargs) + ax_freq = getattr(ax, 'freq', None) - format_dateaxis(ax, ax.freq) + # use axes freq if no data freq + if freq is None: + freq = ax_freq - left = series.index[0] #get_datevalue(series.index[0], freq) - right = series.index[-1] #get_datevalue(series.index[-1], freq) - ax.set_xlim(left, right) + # get the period frequency + if isinstance(freq, DateOffset): + freq = freq.rule_code + else: + freq = frequencies.get_base_alias(freq) - return ax + freq = frequencies.get_period_alias(freq) + + return freq + +def _get_xlim(lines): + left, right = np.inf, -np.inf + for l in lines: + x = l.get_xdata() + left = min(x[0].ordinal, left) + right = max(x[-1].ordinal, right) + return left, right def get_datevalue(date, freq): if isinstance(date, Period): diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 81f972debe6c2..718249330ec92 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -6,6 +6,7 @@ import numpy as np from numpy.testing.decorators import slow +from numpy.testing import assert_array_equal from pandas import Index, Series, DataFrame, isnull, notnull @@ -52,17 +53,22 @@ def setUp(self): def test_tsplot(self): from pandas.tseries.plotting import tsplot import matplotlib.pyplot as plt + plt.close('all') + ax = plt.gca() ts = tm.makeTimeSeries() plot_ax = tsplot(ts, plt.Axes.plot) self.assert_(plot_ax == ax) f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds) + plt.close('all') for s in self.period_ser: _check_plot_works(f, s.index.freq, ax=ax, series=s) + plt.close('all') for s in self.datetime_ser: _check_plot_works(f, s.index.freq.rule_code, ax=ax, series=s) + plt.close('all') plt.close('all') ax = ts.plot(style='k') @@ -234,8 +240,10 @@ def test_finder_daily(self): @slow def test_finder_quarterly(self): + import matplotlib.pyplot as plt xp = Period('1988Q1').ordinal yrs = [3.5, 11] + plt.close('all') for n in yrs: rng = period_range('1987Q2', periods=int(n * 4), freq='Q') ser = Series(np.random.randn(len(rng)), rng) @@ -250,8 +258,10 @@ def test_finder_quarterly(self): @slow def test_finder_monthly(self): + import matplotlib.pyplot as plt xp = Period('1988-1').ordinal yrs = [1.15, 2.5, 4, 11] + plt.close('all') for n in yrs: rng = period_range('1987Q2', periods=int(n * 12), freq='M') ser = Series(np.random.randn(len(rng)), rng) @@ -263,8 +273,12 @@ def test_finder_monthly(self): ax.set_xlim(vmin + 0.9, vmax) rs = xaxis.get_majorticklocs()[0] self.assertEqual(xp, rs) + plt.close('all') - + @slow + def test_finder_monthly_long(self): + import matplotlib.pyplot as plt + plt.close('all') rng = period_range('1988Q1', periods=24*12, freq='M') ser = Series(np.random.randn(len(rng)), rng) ax = ser.plot() @@ -433,6 +447,102 @@ def test_secondary_frame(self): self.assert_(axes[1].get_yaxis().get_ticks_position() == 'default') self.assert_(axes[2].get_yaxis().get_ticks_position() == 'right') + @slow + def test_mixed_freq_regular_first(self): + import matplotlib.pyplot as plt + plt.close('all') + s1 = tm.makeTimeSeries() + s2 = s1[[0, 5, 10, 11, 12, 13, 14, 15]] + s1.plot() + ax2 = s2.plot(style='g') + lines = ax2.get_lines() + idx1 = lines[0].get_xdata() + idx2 = lines[1].get_xdata() + self.assert_(idx1.equals(s1.index.to_period('B'))) + self.assert_(idx2.equals(s2.index.to_period('B'))) + left, right = ax2.get_xlim() + pidx = s1.index.to_period() + self.assert_(left == pidx[0].ordinal) + self.assert_(right == pidx[-1].ordinal) + plt.close('all') + + @slow + def test_mixed_freq_irregular_first(self): + import matplotlib.pyplot as plt + plt.close('all') + s1 = tm.makeTimeSeries() + s2 = s1[[0, 5, 10, 11, 12, 13, 14, 15]] + s2.plot(style='g') + ax = s1.plot() + self.assert_(not hasattr(ax, 'freq')) + lines = ax.get_lines() + x1 = lines[0].get_xdata() + assert_array_equal(x1, s2.index.asobject.values) + x2 = lines[1].get_xdata() + assert_array_equal(x2, s1.index.asobject.values) + plt.close('all') + + @slow + def test_mixed_freq_hf_first(self): + import matplotlib.pyplot as plt + plt.close('all') + idxh = date_range('1/1/1999', periods=365, freq='D') + idxl = date_range('1/1/1999', periods=12, freq='M') + high = Series(np.random.randn(len(idxh)), idxh) + low = Series(np.random.randn(len(idxl)), idxl) + high.plot() + ax = low.plot() + for l in ax.get_lines(): + self.assert_(l.get_xdata().freq == 'D') + + @slow + def test_mixed_freq_lf_first(self): + import matplotlib.pyplot as plt + plt.close('all') + idxh = date_range('1/1/1999', periods=365, freq='D') + idxl = date_range('1/1/1999', periods=12, freq='M') + high = Series(np.random.randn(len(idxh)), idxh) + low = Series(np.random.randn(len(idxl)), idxl) + low.plot() + ax = high.plot() + for l in ax.get_lines(): + self.assert_(l.get_xdata().freq == 'M') + + @slow + def test_mixed_freq_irreg_period(self): + ts = tm.makeTimeSeries() + irreg = ts[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 18, 29]] + rng = period_range('1/3/2000', periods=30, freq='B') + ps = Series(np.random.randn(len(rng)), rng) + irreg.plot() + ps.plot() + + @slow + def test_to_weekly_resampling(self): + import matplotlib.pyplot as plt + plt.close('all') + idxh = date_range('1/1/1999', periods=52, freq='W') + idxl = date_range('1/1/1999', periods=12, freq='M') + high = Series(np.random.randn(len(idxh)), idxh) + low = Series(np.random.randn(len(idxl)), idxl) + high.plot() + ax = low.plot() + for l in ax.get_lines(): + self.assert_(l.get_xdata().freq.startswith('W')) + + @slow + def test_from_weekly_resampling(self): + import matplotlib.pyplot as plt + plt.close('all') + idxh = date_range('1/1/1999', periods=52, freq='W') + idxl = date_range('1/1/1999', periods=12, freq='M') + high = Series(np.random.randn(len(idxh)), idxh) + low = Series(np.random.randn(len(idxl)), idxl) + low.plot() + ax = high.plot() + for l in ax.get_lines(): + self.assert_(l.get_xdata().freq == 'M') + PNG_PATH = 'tmp.png' def _check_plot_works(f, freq=None, series=None, *args, **kwargs): import matplotlib.pyplot as plt @@ -440,19 +550,22 @@ def _check_plot_works(f, freq=None, series=None, *args, **kwargs): fig = plt.gcf() plt.clf() ax = fig.add_subplot(211) + orig_ax = kwargs.pop('ax', plt.gca()) + orig_axfreq = getattr(orig_ax, 'freq', None) + ret = f(*args, **kwargs) assert(ret is not None) # do something more intelligent - orig_ax = kwargs.pop('ax', plt.gca()) - if series is not None: # non-business + ax = kwargs.pop('ax', plt.gca()) + if series is not None: dfreq = series.index.freq if isinstance(dfreq, DateOffset): dfreq = dfreq.rule_code - #dfreq = frequencies.offset_to_period_alias(dfreq) - assert(orig_ax.freq == dfreq) + if orig_axfreq is None: + assert(ax.freq == dfreq) - if freq is not None: - assert(orig_ax.freq == freq) + if freq is not None and orig_axfreq is None: + assert(ax.freq == freq) ax = fig.add_subplot(212) try: