Skip to content

(WIP) BUG/CLN: Better timeseries plotting / refactoring tsplot #7670

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions pandas/tools/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1665,21 +1665,35 @@ def __init__(self, data, **kwargs):

def _is_ts_plot(self):
# this is slightly deceptive
return not self.x_compat and self.use_index and self._use_dynamic_x()

def _use_dynamic_x(self):
from pandas.tseries.plotting import _use_dynamic_x
return _use_dynamic_x(self._get_ax(0), self.data)
from pandas.tseries.plotting import _use_dynamic_x, _get_freq
ax = self._get_ax(0)
freq, ax_freq = _get_freq(ax, self.data)
dynamic_x = _use_dynamic_x(ax, self.data)
return (not self.x_compat and self.use_index and
dynamic_x and freq is not None)

def _make_plot(self):
if self._is_ts_plot():
print('tsplot-path!!')
from pandas.tseries.plotting import _maybe_convert_index
data = _maybe_convert_index(self._get_ax(0), self.data)

from pandas.tseries.plotting import _maybe_resample
for ax in self.axes:
# resample data and replot if required
kwds = self.kwds.copy()
data = _maybe_resample(data, ax, kwds)

x = data.index # dummy, not used
plotf = self._ts_plot
it = self._iter_data(data=data, keep_index=True)
else:
print('xcompat-path!!')
from pandas.tseries.plotting import _replot_x_compat
for ax in self.axes:
# if ax holds _plot_data, replot them on the x_compat scale
_replot_x_compat(ax)

x = self._get_xticks(convert_period=True)
plotf = self._plot
it = self._iter_data()
Expand Down Expand Up @@ -1723,24 +1737,15 @@ def _plot(cls, ax, x, y, style=None, column_num=None,

@classmethod
def _ts_plot(cls, ax, x, data, style=None, **kwds):
from pandas.tseries.plotting import (_maybe_resample,
_decorate_axes,
format_dateaxis)
from pandas.tseries.plotting import _maybe_resample, format_dateaxis
# accept x to be consistent with normal plot func,
# x is not passed to tsplot as it uses data.index as x coordinate
# column_num must be in kwds for stacking purpose
freq, data = _maybe_resample(data, ax, kwds)

# Set ax with freq info
_decorate_axes(ax, freq, kwds)
# digging deeper
if hasattr(ax, 'left_ax'):
_decorate_axes(ax.left_ax, freq, kwds)
if hasattr(ax, 'right_ax'):
_decorate_axes(ax.right_ax, freq, kwds)
data = _maybe_resample(data, ax, kwds)
ax._plot_data.append((data, cls._kind, kwds))

lines = cls._plot(ax, data.index, data.values, style=style, **kwds)

# set date formatter, locators and rescale limits
format_dateaxis(ax, ax.freq)
return lines
Expand Down Expand Up @@ -1790,14 +1795,9 @@ def _update_stacker(cls, ax, stacking_id, values):
ax._stacker_neg_prior[stacking_id] += values

def _post_plot_logic(self, ax, data):
condition = (not self._use_dynamic_x() and
data.index.is_all_dates and
not self.subplots or
(self.subplots and self.sharex))

index_name = self._get_index_name()

if condition:
if not self._is_ts_plot():
# irregular TS rotated 30 deg. by default
# probably a better place to check / set this.
if not self._rot_set:
Expand Down
162 changes: 100 additions & 62 deletions pandas/tseries/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from matplotlib import pylab
from pandas.tseries.period import Period
import numpy as np

from pandas.tseries.offsets import DateOffset
import pandas.tseries.frequencies as frequencies
from pandas.tseries.index import DatetimeIndex
Expand Down Expand Up @@ -41,18 +43,14 @@ def tsplot(series, plotf, ax=None, **kwargs):
import matplotlib.pyplot as plt
ax = plt.gca()

freq, series = _maybe_resample(series, ax, kwargs)

# Set ax with freq info
_decorate_axes(ax, freq, kwargs)
series = _maybe_resample(series, ax, kwargs)
ax._plot_data.append((series, plotf, kwargs))
lines = plotf(ax, series.index._mpl_repr(), series.values, **kwargs)

# set date formatter, locators and rescale limits
format_dateaxis(ax, ax.freq)
return lines


def _maybe_resample(series, ax, kwargs):
# resample against axes freq if necessary
freq, ax_freq = _get_freq(ax, series)
Expand All @@ -75,11 +73,20 @@ def _maybe_resample(series, ax, kwargs):
series = getattr(series.resample(ax_freq), how)().dropna()
freq = ax_freq
elif frequencies.is_subperiod(freq, ax_freq) or _is_sub(freq, ax_freq):
_upsample_others(ax, freq, kwargs)
_upsample_others(ax, freq)
ax_freq = freq
else: # pragma: no cover
raise ValueError('Incompatible frequency conversion')
return freq, series

# Set ax with freq info
_decorate_axes(ax, freq)
# digging deeper
if hasattr(ax, 'left_ax'):
_decorate_axes(ax.left_ax, freq)
elif hasattr(ax, 'right_ax'):
_decorate_axes(ax.right_ax, freq)

return series


def _is_sub(f1, f2):
Expand All @@ -92,81 +99,112 @@ def _is_sup(f1, f2):
(f2.startswith('W') and frequencies.is_superperiod(f1, 'D')))


def _upsample_others(ax, freq, kwargs):
legend = ax.get_legend()
lines, labels = _replot_ax(ax, freq, kwargs)
_replot_ax(ax, freq, kwargs)
def _get_plot_func(plotf):
""" get actual function when plotf is specified with str """
# for tsplot
if isinstance(plotf, compat.string_types):
from pandas.tools.plotting import _plot_klass
plotf = _plot_klass[plotf]._plot
return plotf


def _upsample_others(ax, freq):

other_ax = None
def _replot(ax):
data = getattr(ax, '_plot_data', None)
if data is None:
return

# preserve legend
leg = ax.get_legend()
handles, labels = ax.get_legend_handles_labels()

ax._plot_data = []
ax.clear()
_decorate_axes(ax, freq)

for series, plotf, kwds in data:
series = series.copy()
idx = series.index.asfreq(freq, how='s')
series.index = idx
ax._plot_data.append((series, plotf, kwds))

plotf = _get_plot_func(plotf)
plotf(ax, series.index._mpl_repr(), series.values, **kwds)


if leg is not None:
ax.legend(handles, labels, title=leg.get_title().get_text())

_replot(ax)
if hasattr(ax, 'left_ax'):
other_ax = ax.left_ax
if hasattr(ax, 'right_ax'):
other_ax = ax.right_ax
_replot(ax.left_ax)
elif hasattr(ax, 'right_ax'):
_replot(ax.right_ax)

if other_ax is not None:
rlines, rlabels = _replot_ax(other_ax, freq, kwargs)
lines.extend(rlines)
labels.extend(rlabels)

if (legend is not None and kwargs.get('legend', True) and
len(lines) > 0):
title = legend.get_title().get_text()
if title == 'None':
title = None
ax.legend(lines, labels, loc='best', title=title)
def _replot_x_compat(ax):

def _replot(ax):
data = getattr(ax, '_plot_data', None)
if data is None:
return

def _replot_ax(ax, freq, kwargs):
data = getattr(ax, '_plot_data', None)
# preserve legend
leg = ax.get_legend()
handles, labels = ax.get_legend_handles_labels()

# clear current axes and data
ax._plot_data = []
ax.clear()
ax._plot_data = None
ax.clear()

_decorate_axes(ax, freq, kwargs)
_decorate_axes(ax, None)

lines = []
labels = []
if data is not None:
for series, plotf, kwds in data:
series = series.copy()
idx = series.index.asfreq(freq, how='S')
idx = series.index.to_timestamp(how='s')
series.index = idx
ax._plot_data.append((series, plotf, kwds))

# for tsplot
if isinstance(plotf, compat.string_types):
from pandas.tools.plotting import _plot_klass
plotf = _plot_klass[plotf]._plot
plotf = _get_plot_func(plotf)
plotf(ax, series.index._mpl_repr(), series, **kwds)

lines.append(plotf(ax, series.index._mpl_repr(),
series.values, **kwds)[0])
labels.append(pprint_thing(series.name))
if leg is not None:
ax.legend(handles, labels, title=leg.get_title().get_text())

return lines, labels
_replot(ax)
if hasattr(ax, 'left_ax'):
_replot(ax.left_ax)
elif hasattr(ax, 'right_ax'):
_replot(ax.right_ax)


def _decorate_axes(ax, freq, kwargs):
def _decorate_axes(ax, freq):
"""Initialize axes for time-series plotting"""
if not hasattr(ax, '_plot_data'):
ax._plot_data = []

ax.freq = freq
xaxis = ax.get_xaxis()
xaxis.freq = freq
if not hasattr(ax, 'legendlabels'):
ax.legendlabels = [kwargs.get('label', None)]
else:
ax.legendlabels.append(kwargs.get('label', None))
ax.view_interval = None
ax.date_axis_info = None


def _get_freq(ax, series):
def _get_index_freq(data):
freq = getattr(data.index, 'freq', None)
if freq is None:
freq = getattr(data.index, 'inferred_freq', None)
if freq == 'B':
weekdays = np.unique(data.index.dayofweek)
if (5 in weekdays) or (6 in weekdays):
freq = None
return freq


def _get_freq(ax, data):
# get frequency from data
freq = getattr(series.index, 'freq', None)
freq = getattr(data.index, 'freq', None)

if freq is None:
freq = getattr(series.index, 'inferred_freq', None)
freq = getattr(data.index, 'inferred_freq', None)

ax_freq = getattr(ax, 'freq', None)
if ax_freq is None:
Expand All @@ -175,17 +213,17 @@ def _get_freq(ax, series):
elif hasattr(ax, 'right_ax'):
ax_freq = getattr(ax.right_ax, 'freq', None)

# use axes freq if no data freq
if freq is None:
freq = ax_freq
if freq is not None:
# get the period frequency
if isinstance(freq, DateOffset):
freq = freq.rule_code
else:
freq = frequencies.get_base_alias(freq)

# get the period frequency
if isinstance(freq, DateOffset):
freq = freq.rule_code
else:
freq = frequencies.get_base_alias(freq)
if freq is None:
raise ValueError('Could not get frequency alias for plotting')
freq = frequencies.get_period_alias(freq)

freq = frequencies.get_period_alias(freq)
return freq, ax_freq


Expand Down
Loading