From 3d00b89bb74631536b3366dd09e729364dfb4b04 Mon Sep 17 00:00:00 2001 From: Chang She Date: Mon, 16 Jul 2012 16:11:23 -0400 Subject: [PATCH 1/9] ENH: upsample existing data on plotting --- pandas/tseries/plotting.py | 102 +++++++++++++++++++------- pandas/tseries/tests/test_plotting.py | 5 +- 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index 678c272b27b26..bb11774787525 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -51,48 +51,100 @@ def tsplot(series, plotf, **kwargs): if freq is None: # pragma: no cover raise ValueError('Cannot use dynamic axis without frequency info') else: - 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, ax_freq, series = _maybe_resample(series, ax, freq, plotf, + kwargs) # Convert DatetimeIndex to PeriodIndex if isinstance(series.index, DatetimeIndex): series = series.to_period(freq=freq) - style = kwargs.pop('style', None) - - # Specialized ts plotting attributes for Axes - ax.freq = freq - xaxis = ax.get_xaxis() - xaxis.freq = freq - ax.legendlabels = [kwargs.get('label', None)] - ax.view_interval = None - ax.date_axis_info = None + # Set ax with freq info + _decorate_axes(ax, freq, kwargs) - # format args and lot + # mask missing values args = _maybe_mask(series) + # how to make sure ax.clear() flows through? + if not hasattr(ax, '_plot_data'): + ax._plot_data = [] + ax._plot_data.append((series, kwargs)) + + # styles + style = kwargs.pop('style', None) if style is not None: args.append(style) plotf(ax, *args, **kwargs) - format_dateaxis(ax, ax.freq) + if (ax.get_legend() is None and kwargs.get('legend', True) + and len(ax._plot_data) > 1): + ax.legend(loc='best') + # set date formatter, locators and rescale limits + format_dateaxis(ax, ax.freq) left, right = _get_xlim(ax.get_lines()) ax.set_xlim(left, right) return ax +def _maybe_resample(series, ax, freq, plotf, kwargs): + ax_freq = getattr(ax, 'freq', None) + if (ax_freq is not None) and (freq != ax_freq): + if frequencies.is_subperiod(freq, ax_freq): # upsample existing + _upsample_others(series, ax, freq, ax_freq, plotf, kwargs) + ax_freq = freq + elif frequencies.is_superperiod(freq, ax_freq): # upsample input + series = series.asfreq(ax_freq).dropna() + freq = ax_freq + elif _is_sup(freq, ax_freq): # one is weekly + how = kwargs.pop('how', 'last') + series = series.resample('D', how=how).dropna() + series = series.resample(ax_freq, how=how).dropna() + freq = ax_freq + elif _is_sub(freq, ax_freq): + _upsample_others(series, ax, freq, ax_freq, plotf, kwargs, True) + ax_freq = freq + else: + raise ValueError('Incompatible frequency conversion') + return freq, ax_freq, series + +def _is_sub(f1, f2): + return ((f1.startswith('W') and frequencies.is_subperiod('D', f2)) or + (f2.startswith('W') and frequencies.is_subperiod(f1, 'D'))) + +def _is_sup(f1, f2): + return ((f1.startswith('W') and frequencies.is_superperiod('D', f2)) or + (f2.startswith('W') and frequencies.is_superperiod(f1, 'D'))) + +def _upsample_others(series, ax, freq, ax_freq, plotf, kwargs, + via_daily=False): + data = ax._plot_data + ax._plot_data = [] + ax.clear() + _decorate_axes(ax, freq, kwargs) + for series, kwds in data: + series = _upsample(series, freq, via_daily) + ax._plot_data.append(series) + args = _maybe_mask(series) + plotf(ax, *args, **kwds) + +def _upsample(series, freq, via_daily): + if not via_daily: + return series.resample(freq).dropna() + else: + return series.resample('D').resample(freq).dropna() + +def _decorate_axes(ax, freq, kwargs): + 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 _maybe_mask(series): mask = isnull(series) if mask.any(): @@ -175,7 +227,3 @@ def format_dateaxis(subplot, freq): subplot.xaxis.set_major_formatter(majformatter) subplot.xaxis.set_minor_formatter(minformatter) pylab.draw_if_interactive() - - - - diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 5e94ec97c04e7..06fa4067a0142 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -583,7 +583,7 @@ def test_mixed_freq_lf_first(self): low.plot() ax = high.plot() for l in ax.get_lines(): - self.assert_(l.get_xdata().freq == 'M') + self.assert_(l.get_xdata().freq == 'D') @slow def test_mixed_freq_irreg_period(self): @@ -618,7 +618,7 @@ def test_from_weekly_resampling(self): low.plot() ax = high.plot() for l in ax.get_lines(): - self.assert_(l.get_xdata().freq == 'M') + self.assert_(l.get_xdata().freq.startswith('W')) @slow def test_irreg_dtypes(self): @@ -735,4 +735,3 @@ def _check_plot_works(f, freq=None, series=None, *args, **kwargs): if __name__ == '__main__': nose.runmodule(argv=[__file__,'-vvs','-x','--pdb', '--pdb-failure'], exit=False) - From c9228faa493e7c55c77219d1cdf6b29469b7d548 Mon Sep 17 00:00:00 2001 From: Chang She Date: Tue, 17 Jul 2012 00:59:31 -0400 Subject: [PATCH 2/9] ENH: unified legend for secondary axis --- pandas/tools/plotting.py | 68 +++++++++++++++++++++++---- pandas/tseries/plotting.py | 42 +++++++++++++---- pandas/tseries/tests/test_plotting.py | 3 +- 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 8900a1eb35a28..0b5aba7ae227b 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -531,6 +531,8 @@ def __init__(self, data, kind=None, by=None, subplots=False, sharex=True, self.fig = fig self.axes = None + if not isinstance(secondary_y, (bool, tuple, list, np.ndarray)): + secondary_y = [secondary_y] self.secondary_y = secondary_y self.kwds = kwds @@ -577,13 +579,25 @@ def _args_adjust(self): pass def _maybe_right_yaxis(self, ax): - ypos = ax.get_yaxis().get_ticks_position().strip().lower() - - if self.secondary_y and ypos != 'right': - orig_ax = ax - ax = ax.twinx() + _types = (list, tuple, np.ndarray) + need_second = ((isinstance(self.secondary_y, bool) and self.secondary_y) + or (isinstance(self.secondary_y, _types) + and len(self.secondary_y) > 0)) + + if need_second and not hasattr(ax, 'right_ax'): + orig_ax, new_ax = ax, ax.twinx() + orig_ax.right_ax, new_ax.left_ax = new_ax, orig_ax if len(orig_ax.get_lines()) == 0: # no data on left y orig_ax.get_yaxis().set_visible(False) + if len(new_ax.get_lines()) == 0: + new_ax.get_yaxis().set_visible(False) + + if ((isinstance(self.secondary_y, bool) and self.secondary_y) or + (isinstance(self.secondary_y, _types) and + (len(self.secondary_y) == self.nseries))): + ax = new_ax + else: + ax = orig_ax else: ax.get_yaxis().set_visible(True) @@ -749,8 +763,23 @@ def _get_ax(self, i): ax = self.axes[i] else: ax = self.ax + if self.on_right(i): + if hasattr(ax, 'right_ax'): + ax = ax.right_ax + elif hasattr(ax, 'left_ax'): + ax = ax.left_ax + + ax.get_yaxis().set_visible(True) return ax + def on_right(self, i): + from pandas.core.frame import DataFrame + if isinstance(self.secondary_y, bool): + return self.secondary_y + if (isinstance(self.data, DataFrame) and + isinstance(self.secondary_y, (tuple, list, np.ndarray))): + return self.data.columns[i] in self.secondary_y + def _get_ax_and_style(self, i, col_name): ax = self._get_ax(i) @@ -834,6 +863,8 @@ def _make_plot(self): data = self._maybe_convert_index(self.data) self._make_ts_plot(data) else: + lines = [] + labels = [] x = self._get_xticks(convert_period=True) plotf = self._get_plot_function() @@ -848,9 +879,14 @@ def _make_plot(self): y = np.ma.array(y) y = np.ma.masked_where(mask, y) - plotf(ax, x, y, style, label=label, **self.kwds) + newline = plotf(ax, x, y, style, label=label, **self.kwds)[0] + lines.append(newline) + labels.append(label) ax.grid(self.grid) + if self.legend and not self.subplots: + ax.legend(lines, labels, loc='best', title=self.legend_title) + def _maybe_convert_index(self, data): # tsplot converts automatically, but don't want to convert index # over and over for DataFrames @@ -883,22 +919,31 @@ def _make_ts_plot(self, data, **kwargs): from pandas.tseries.plotting import tsplot plotf = self._get_plot_function() + lines = [] + labels = [] if isinstance(data, Series): ax = self._get_ax(0) #self.axes[0] style = self.style or '' label = com._stringify(self.label) - tsplot(data, plotf, ax=ax, label=label, style=self.style, - **kwargs) + newline = tsplot(data, plotf, ax=ax, label=label, style=self.style, + **kwargs)[0] ax.grid(self.grid) + lines.append(newline) + labels.append(label) else: for i, col in enumerate(data.columns): ax, style = self._get_ax_and_style(i, col) label = com._stringify(col) - tsplot(data[col], plotf, ax=ax, label=label, style=style, - **kwargs) + newline = tsplot(data[col], plotf, ax=ax, label=label, + style=style, **kwargs)[0] + lines.append(newline) + labels.append(label) ax.grid(self.grid) + if self.legend and not self.subplots: + ax.legend(lines, labels, loc='best', title=self.legend_title) + # self.fig.subplots_adjust(wspace=0, hspace=0) @@ -926,6 +971,7 @@ def _post_plot_logic(self): if index_name is not None: ax.set_xlabel(index_name) + class BarPlot(MPLPlot): _default_rot = {'bar' : 90, 'barh' : 0} @@ -1715,6 +1761,8 @@ def on_right(i): orig_ax = ax0 ax0 = ax0.twinx() orig_ax.get_yaxis().set_visible(False) + orig_ax.right_ax = ax0 + ax0.left_ax = orig_ax if sharex: subplot_kw['sharex'] = ax0 diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index bb11774787525..0541f07eb406b 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -1,5 +1,6 @@ """ -Adapted from scikits.timeseries by Pierre GF Gerard-Marchant & Matt Knox +Period formatters and locators adapted from scikits.timeseries by +Pierre GF Gerard-Marchant & Matt Knox """ #!!! TODO: Use the fact that axis can have units to simplify the process @@ -74,18 +75,14 @@ def tsplot(series, plotf, **kwargs): if style is not None: args.append(style) - plotf(ax, *args, **kwargs) - - if (ax.get_legend() is None and kwargs.get('legend', True) - and len(ax._plot_data) > 1): - ax.legend(loc='best') + line = plotf(ax, *args, **kwargs) # set date formatter, locators and rescale limits format_dateaxis(ax, ax.freq) left, right = _get_xlim(ax.get_lines()) ax.set_xlim(left, right) - return ax + return line def _maybe_resample(series, ax, freq, plotf, kwargs): ax_freq = getattr(ax, 'freq', None) @@ -102,7 +99,7 @@ def _maybe_resample(series, ax, freq, plotf, kwargs): series = series.resample(ax_freq, how=how).dropna() freq = ax_freq elif _is_sub(freq, ax_freq): - _upsample_others(series, ax, freq, ax_freq, plotf, kwargs, True) + _upsample_others(ax, freq, ax_freq, plotf, kwargs, True) ax_freq = freq else: raise ValueError('Incompatible frequency conversion') @@ -116,17 +113,42 @@ def _is_sup(f1, f2): return ((f1.startswith('W') and frequencies.is_superperiod('D', f2)) or (f2.startswith('W') and frequencies.is_superperiod(f1, 'D'))) -def _upsample_others(series, ax, freq, ax_freq, plotf, kwargs, +def _upsample_others(ax, freq, ax_freq, plotf, kwargs, via_daily=False): + legend = ax.get_legend() + lines, labels = _replot_ax(ax, freq, ax_freq, plotf, kwargs, via_daily) + + other_ax = None + if hasattr(ax, 'left_ax'): + other_ax = ax.left_ax + if hasattr(ax, 'right_ax'): + other_ax = ax.right_ax + + if other_ax is not None: + other_leg = other_ax.get_legend() + rlines, rlabels = _replot_ax(ax, freq, ax_freq, plotf, kwargs, + via_daily) + lines.extend(rlines) + labels.extend(rlabels) + + if legend is not None and kwargs.get('legend', True): + ax.legend(lines, labels, loc='best', ax.get_title().get_text()) + +def _replot_ax(ax, freq, ax_freq, plotf, kwargs, via_daily): data = ax._plot_data ax._plot_data = [] ax.clear() _decorate_axes(ax, freq, kwargs) + lines = [] + labels = [] for series, kwds in data: series = _upsample(series, freq, via_daily) ax._plot_data.append(series) args = _maybe_mask(series) - plotf(ax, *args, **kwds) + lines.append(plotf(ax, *args, **kwds)[0]) + labels.append(com._stringify(series.name)) + + return lines, labels def _upsample(series, freq, via_daily): if not via_daily: diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 06fa4067a0142..14ff96f490b5c 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -73,8 +73,7 @@ def test_tsplot(self): ax = plt.gca() ts = tm.makeTimeSeries() - plot_ax = tsplot(ts, plt.Axes.plot) - self.assert_(plot_ax == ax) + tsplot(ts, plt.Axes.plot) f = lambda *args, **kwds: tsplot(s, plt.Axes.plot, *args, **kwds) plt.close('all') From c4a9c81231673964183dca2aad1c77622ee347e2 Mon Sep 17 00:00:00 2001 From: Chang She Date: Tue, 17 Jul 2012 01:31:55 -0400 Subject: [PATCH 3/9] BUG: unify legends for when existing data is resampled --- pandas/tseries/plotting.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index 0541f07eb406b..cdb6afe508fb9 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -75,20 +75,34 @@ def tsplot(series, plotf, **kwargs): if style is not None: args.append(style) - line = plotf(ax, *args, **kwargs) + lines = plotf(ax, *args, **kwargs) + label = kwargs.get('label', None) + if (ax.get_legend() is not None) and (kwargs.get('legend', True)): + _reset_legend(ax, lines[0], label) # set date formatter, locators and rescale limits format_dateaxis(ax, ax.freq) left, right = _get_xlim(ax.get_lines()) ax.set_xlim(left, right) - return line + return lines + +def _reset_legend(ax, line, label): + leg = ax.get_legend() + ext_lines = leg.get_lines() + ext_labels = [x.get_text() for x in leg.get_texts()] + ext_lines.append(line) + ext_labels.append(label) + title = leg.get_title().get_text() + if title == 'None': + title = None + ax.legend(ext_lines, ext_labels, loc='best', title=title) def _maybe_resample(series, ax, freq, plotf, kwargs): ax_freq = getattr(ax, 'freq', None) if (ax_freq is not None) and (freq != ax_freq): if frequencies.is_subperiod(freq, ax_freq): # upsample existing - _upsample_others(series, ax, freq, ax_freq, plotf, kwargs) + _upsample_others(ax, freq, ax_freq, plotf, kwargs) ax_freq = freq elif frequencies.is_superperiod(freq, ax_freq): # upsample input series = series.asfreq(ax_freq).dropna() @@ -131,8 +145,12 @@ def _upsample_others(ax, freq, ax_freq, plotf, kwargs, lines.extend(rlines) labels.extend(rlabels) - if legend is not None and kwargs.get('legend', True): - ax.legend(lines, labels, loc='best', ax.get_title().get_text()) + 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_ax(ax, freq, ax_freq, plotf, kwargs, via_daily): data = ax._plot_data From a28ef9979356c6719a0d70500c9ee24dea18ac84 Mon Sep 17 00:00:00 2001 From: Chang She Date: Tue, 17 Jul 2012 02:36:09 -0400 Subject: [PATCH 4/9] ENH: unified color cycle for 2nd yaxis --- pandas/tools/plotting.py | 12 +++++++++++- pandas/tseries/plotting.py | 39 ++++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 0b5aba7ae227b..1ea907385bf1d 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -863,6 +863,8 @@ def _make_plot(self): data = self._maybe_convert_index(self.data) self._make_ts_plot(data) else: + import matplotlib.pyplot as plt + colors = kwargs.pop('colors', str(plt.rcParams['axes.color_cycle'])) lines = [] labels = [] x = self._get_xticks(convert_period=True) @@ -871,6 +873,8 @@ def _make_plot(self): for i, (label, y) in enumerate(self._iter_data()): ax, style = self._get_ax_and_style(i, label) + kwds = kwargs.copy() + kwds['color'] = colors[i % len(colors)] label = com._stringify(label) @@ -885,6 +889,7 @@ def _make_plot(self): ax.grid(self.grid) if self.legend and not self.subplots: + ax = self._get_ax(0) ax.legend(lines, labels, loc='best', title=self.legend_title) def _maybe_convert_index(self, data): @@ -917,6 +922,8 @@ def _maybe_convert_index(self, data): def _make_ts_plot(self, data, **kwargs): from pandas.tseries.plotting import tsplot + import matplotlib.pyplot as plt + colors = kwargs.pop('colors', ''.join(plt.rcParams['axes.color_cycle'])) plotf = self._get_plot_function() lines = [] @@ -934,9 +941,12 @@ def _make_ts_plot(self, data, **kwargs): else: for i, col in enumerate(data.columns): ax, style = self._get_ax_and_style(i, col) + kwds = kwargs.copy() + kwds['color'] = colors[i % len(colors)] + label = com._stringify(col) newline = tsplot(data[col], plotf, ax=ax, label=label, - style=style, **kwargs)[0] + style=style, **kwds)[0] lines.append(newline) labels.append(label) ax.grid(self.grid) diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index cdb6afe508fb9..86f116fd1e696 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -77,8 +77,7 @@ def tsplot(series, plotf, **kwargs): lines = plotf(ax, *args, **kwargs) label = kwargs.get('label', None) - if (ax.get_legend() is not None) and (kwargs.get('legend', True)): - _reset_legend(ax, lines[0], label) + _reset_legend(ax, lines[0], label, kwargs) # set date formatter, locators and rescale limits format_dateaxis(ax, ax.freq) @@ -87,16 +86,32 @@ def tsplot(series, plotf, **kwargs): return lines -def _reset_legend(ax, line, label): +def _reset_legend(ax, line, label, kwargs): + ax, leg = _get_ax_legend(ax) + if leg and (kwargs.get('legend', True)): + ext_lines = leg.get_lines() + ext_labels = [x.get_text() for x in leg.get_texts()] + title = leg.get_title().get_text() + if title == 'None': + title = None + + ext_lines.append(line) + ext_labels.append(label) + ax.legend(ext_lines, ext_labels, loc='best', title=title) + +def _get_ax_legend(ax): leg = ax.get_legend() - ext_lines = leg.get_lines() - ext_labels = [x.get_text() for x in leg.get_texts()] - ext_lines.append(line) - ext_labels.append(label) - title = leg.get_title().get_text() - if title == 'None': - title = None - ax.legend(ext_lines, ext_labels, loc='best', title=title) + + other_ax = getattr(ax, 'right_ax', None) or getattr(ax, 'left_ax', None) + other_leg = None + if other_ax is not None: + other_leg = other_ax.get_legend() + + if leg is None: + leg = other_leg + ax = other_ax + + return ax, leg def _maybe_resample(series, ax, freq, plotf, kwargs): ax_freq = getattr(ax, 'freq', None) @@ -140,7 +155,7 @@ def _upsample_others(ax, freq, ax_freq, plotf, kwargs, if other_ax is not None: other_leg = other_ax.get_legend() - rlines, rlabels = _replot_ax(ax, freq, ax_freq, plotf, kwargs, + rlines, rlabels = _replot_ax(other_ax, freq, ax_freq, plotf, kwargs, via_daily) lines.extend(rlines) labels.extend(rlabels) From 54c62c61b3a328ad6ce24080ed3d1403c3317707 Mon Sep 17 00:00:00 2001 From: Chang She Date: Tue, 17 Jul 2012 14:33:14 -0400 Subject: [PATCH 5/9] TST: legends and secondary resampling --- pandas/tools/plotting.py | 147 +++++++++++++------------- pandas/tseries/plotting.py | 70 +++++------- pandas/tseries/tests/test_plotting.py | 76 ++++++++++++- 3 files changed, 174 insertions(+), 119 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 1ea907385bf1d..935bd0f3f48f7 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -2,6 +2,7 @@ # pylint: disable=E1101 from itertools import izip import datetime +import re import numpy as np @@ -623,12 +624,11 @@ def _setup_subplots(self): fig = self.plt.figure(figsize=self.figsize) ax = fig.add_subplot(111) ax = self._maybe_right_yaxis(ax) - self.ax = ax else: fig = self.ax.get_figure() - self.ax = self._maybe_right_yaxis(self.ax) + ax = self._maybe_right_yaxis(self.ax) - axes = [self.ax] + axes = [ax] self.fig = fig self.axes = axes @@ -646,10 +646,7 @@ def _post_plot_logic(self): pass def _adorn_subplots(self): - if self.subplots: - to_adorn = self.axes - else: - to_adorn = [self.ax] + to_adorn = self.axes # todo: sharex, sharey handling? @@ -668,14 +665,11 @@ def _adorn_subplots(self): ax.grid(self.grid) - if self.legend and not self.subplots: - self.ax.legend(loc='best', title=self.legend_title) - if self.title: if self.subplots: self.fig.suptitle(self.title) else: - self.ax.set_title(self.title) + self.axes[0].set_title(self.title) if self._need_to_set_index: labels = [_stringify(key) for key in self.data.index] @@ -762,7 +756,7 @@ def _get_ax(self, i): if self.subplots: ax = self.axes[i] else: - ax = self.ax + ax = self.axes[0] if self.on_right(i): if hasattr(ax, 'right_ax'): ax = ax.right_ax @@ -861,10 +855,12 @@ def _make_plot(self): # this is slightly deceptive if self.use_index and self._use_dynamic_x(): data = self._maybe_convert_index(self.data) - self._make_ts_plot(data) + self._make_ts_plot(data, **self.kwds) else: import matplotlib.pyplot as plt - colors = kwargs.pop('colors', str(plt.rcParams['axes.color_cycle'])) + cycle = ''.join(plt.rcParams.get('axes.color_cycle', + list('bgrcmyk'))) + colors = self.kwds.pop('colors', cycle) lines = [] labels = [] x = self._get_xticks(convert_period=True) @@ -873,8 +869,9 @@ def _make_plot(self): for i, (label, y) in enumerate(self._iter_data()): ax, style = self._get_ax_and_style(i, label) - kwds = kwargs.copy() - kwds['color'] = colors[i % len(colors)] + kwds = self.kwds.copy() + if re.match('[a-z]+', style) is None: + kwds['color'] = colors[i % len(colors)] label = com._stringify(label) @@ -883,47 +880,19 @@ def _make_plot(self): y = np.ma.array(y) y = np.ma.masked_where(mask, y) - newline = plotf(ax, x, y, style, label=label, **self.kwds)[0] + newline = plotf(ax, x, y, style, label=label, **kwds)[0] lines.append(newline) labels.append(label) ax.grid(self.grid) - if self.legend and not self.subplots: - ax = self._get_ax(0) - ax.legend(lines, labels, loc='best', title=self.legend_title) - - def _maybe_convert_index(self, data): - # tsplot converts automatically, but don't want to convert index - # over and over for DataFrames - from pandas.core.frame import DataFrame - if (isinstance(data.index, DatetimeIndex) and - isinstance(data, DataFrame)): - freq = getattr(data.index, 'freq', None) - - if freq is None: - freq = getattr(data.index, 'inferred_freq', None) - - if isinstance(freq, DateOffset): - freq = freq.rule_code - - freq = get_period_alias(freq) - - if freq is None: - ax = self._get_ax(0) - freq = getattr(ax, 'freq', None) - - if freq is None: - raise ValueError('Could not get frequency alias for plotting') - - data = DataFrame(data.values, - index=data.index.to_period(freq=freq), - columns=data.columns) - return data + self._make_legend(lines, labels) def _make_ts_plot(self, data, **kwargs): from pandas.tseries.plotting import tsplot import matplotlib.pyplot as plt - colors = kwargs.pop('colors', ''.join(plt.rcParams['axes.color_cycle'])) + kwargs = kwargs.copy() + cycle = ''.join(plt.rcParams.get('axes.color_cycle', list('bgrcmyk'))) + colors = kwargs.pop('colors', ''.join(cycle)) plotf = self._get_plot_function() lines = [] @@ -933,6 +902,8 @@ def _make_ts_plot(self, data, **kwargs): ax = self._get_ax(0) #self.axes[0] style = self.style or '' label = com._stringify(self.label) + if re.match('[a-z]+', style) is None: + kwargs['color'] = colors[0] newline = tsplot(data, plotf, ax=ax, label=label, style=self.style, **kwargs)[0] ax.grid(self.grid) @@ -942,7 +913,8 @@ def _make_ts_plot(self, data, **kwargs): for i, col in enumerate(data.columns): ax, style = self._get_ax_and_style(i, col) kwds = kwargs.copy() - kwds['color'] = colors[i % len(colors)] + if re.match('[a-z]+', style) is None: + kwds['color'] = colors[i % len(colors)] label = com._stringify(col) newline = tsplot(data[col], plotf, ax=ax, label=label, @@ -951,22 +923,63 @@ def _make_ts_plot(self, data, **kwargs): labels.append(label) ax.grid(self.grid) - if self.legend and not self.subplots: - ax.legend(lines, labels, loc='best', title=self.legend_title) + self._make_legend(lines, labels) - # self.fig.subplots_adjust(wspace=0, hspace=0) + def _make_legend(self, lines, labels): + ax, leg = self._get_ax_legend(self.axes[0]) + if (self.legend or leg is not None) and not self.subplots: + if leg is not None: + ext_lines = leg.get_lines() + ext_labels = [x.get_text() for x in leg.get_texts()] + ext_lines.extend(lines) + ext_labels.extend(labels) + else: + ext_lines = lines + ext_labels = labels + ax.legend(ext_lines, ext_labels, loc='best', + title=self.legend_title) + + def _get_ax_legend(self, ax): + leg = ax.get_legend() + other_ax = (getattr(ax, 'right_ax', None) or + getattr(ax, 'left_ax', None)) + other_leg = None + if other_ax is not None: + other_leg = other_ax.get_legend() + if leg is None and other_leg is not None: + leg = other_leg + ax = other_ax + return ax, leg + + def _maybe_convert_index(self, data): + # tsplot converts automatically, but don't want to convert index + # over and over for DataFrames + from pandas.core.frame import DataFrame + if (isinstance(data.index, DatetimeIndex) and + isinstance(data, DataFrame)): + freq = getattr(data.index, 'freq', None) + if freq is None: + freq = getattr(data.index, 'inferred_freq', None) + if isinstance(freq, DateOffset): + freq = freq.rule_code + freq = get_period_alias(freq) + + if freq is None: + ax = self._get_ax(0) + freq = getattr(ax, 'freq', None) + + if freq is None: + raise ValueError('Could not get frequency alias for plotting') + + data = DataFrame(data.values, + index=data.index.to_period(freq=freq), + columns=data.columns) + return data def _post_plot_logic(self): df = self.data - if self.legend: - if self.subplots: - for ax in self.axes: - ax.legend(loc='best') - else: - self.axes[0].legend(loc='best') - condition = (not self._use_dynamic_x and df.index.is_all_dates and not self.subplots @@ -1051,17 +1064,9 @@ def _make_plot(self): labels.append(label) if self.legend and not self.subplots: - patches =[r[0] for r in rects] - - # Legend to the right of the plot - # ax.legend(patches, labels, bbox_to_anchor=(1.05, 1), - # loc=2, borderaxespad=0.) - # self.fig.subplots_adjust(right=0.80) - - ax.legend(patches, labels, loc='best', - title=self.legend_title) - - # self.fig.subplots_adjust(top=0.8, wspace=0, hspace=0) + patches =[r[0] for r in rects] + self.axes[0].legend(patches, labels, loc='best', + title=self.legend_title) def _post_plot_logic(self): for ax in self.axes: diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index 86f116fd1e696..187fe2b4d44d4 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -77,7 +77,6 @@ def tsplot(series, plotf, **kwargs): lines = plotf(ax, *args, **kwargs) label = kwargs.get('label', None) - _reset_legend(ax, lines[0], label, kwargs) # set date formatter, locators and rescale limits format_dateaxis(ax, ax.freq) @@ -86,38 +85,11 @@ def tsplot(series, plotf, **kwargs): return lines -def _reset_legend(ax, line, label, kwargs): - ax, leg = _get_ax_legend(ax) - if leg and (kwargs.get('legend', True)): - ext_lines = leg.get_lines() - ext_labels = [x.get_text() for x in leg.get_texts()] - title = leg.get_title().get_text() - if title == 'None': - title = None - - ext_lines.append(line) - ext_labels.append(label) - ax.legend(ext_lines, ext_labels, loc='best', title=title) - -def _get_ax_legend(ax): - leg = ax.get_legend() - - other_ax = getattr(ax, 'right_ax', None) or getattr(ax, 'left_ax', None) - other_leg = None - if other_ax is not None: - other_leg = other_ax.get_legend() - - if leg is None: - leg = other_leg - ax = other_ax - - return ax, leg - def _maybe_resample(series, ax, freq, plotf, kwargs): - ax_freq = getattr(ax, 'freq', None) - if (ax_freq is not None) and (freq != ax_freq): + ax_freq = _get_ax_freq(ax) + if ax_freq is not None and freq != ax_freq: if frequencies.is_subperiod(freq, ax_freq): # upsample existing - _upsample_others(ax, freq, ax_freq, plotf, kwargs) + _upsample_others(ax, freq, plotf, kwargs) ax_freq = freq elif frequencies.is_superperiod(freq, ax_freq): # upsample input series = series.asfreq(ax_freq).dropna() @@ -128,12 +100,21 @@ def _maybe_resample(series, ax, freq, plotf, kwargs): series = series.resample(ax_freq, how=how).dropna() freq = ax_freq elif _is_sub(freq, ax_freq): - _upsample_others(ax, freq, ax_freq, plotf, kwargs, True) + _upsample_others(ax, freq, plotf, kwargs, True) ax_freq = freq else: raise ValueError('Incompatible frequency conversion') return freq, ax_freq, series +def _get_ax_freq(ax): + ax_freq = getattr(ax, 'freq', None) + if ax_freq is None: + if hasattr(ax, 'left_ax'): + ax_freq = getattr(ax.left_ax, 'freq', None) + if hasattr(ax, 'right_ax'): + ax_freq = getattr(ax.right_ax, 'freq', None) + return ax_freq + def _is_sub(f1, f2): return ((f1.startswith('W') and frequencies.is_subperiod('D', f2)) or (f2.startswith('W') and frequencies.is_subperiod(f1, 'D'))) @@ -142,10 +123,10 @@ def _is_sup(f1, f2): return ((f1.startswith('W') and frequencies.is_superperiod('D', f2)) or (f2.startswith('W') and frequencies.is_superperiod(f1, 'D'))) -def _upsample_others(ax, freq, ax_freq, plotf, kwargs, +def _upsample_others(ax, freq, plotf, kwargs, via_daily=False): legend = ax.get_legend() - lines, labels = _replot_ax(ax, freq, ax_freq, plotf, kwargs, via_daily) + lines, labels = _replot_ax(ax, freq, plotf, kwargs, via_daily) other_ax = None if hasattr(ax, 'left_ax'): @@ -154,8 +135,7 @@ def _upsample_others(ax, freq, ax_freq, plotf, kwargs, other_ax = ax.right_ax if other_ax is not None: - other_leg = other_ax.get_legend() - rlines, rlabels = _replot_ax(other_ax, freq, ax_freq, plotf, kwargs, + rlines, rlabels = _replot_ax(other_ax, freq, plotf, kwargs, via_daily) lines.extend(rlines) labels.extend(rlabels) @@ -167,19 +147,21 @@ def _upsample_others(ax, freq, ax_freq, plotf, kwargs, title = None ax.legend(lines, labels, loc='best', title=title) -def _replot_ax(ax, freq, ax_freq, plotf, kwargs, via_daily): - data = ax._plot_data +def _replot_ax(ax, freq, plotf, kwargs, via_daily): ax._plot_data = [] ax.clear() _decorate_axes(ax, freq, kwargs) + + data = getattr(ax, '_plot_data', None) lines = [] labels = [] - for series, kwds in data: - series = _upsample(series, freq, via_daily) - ax._plot_data.append(series) - args = _maybe_mask(series) - lines.append(plotf(ax, *args, **kwds)[0]) - labels.append(com._stringify(series.name)) + if data is not None: + for series, kwds in data: + series = _upsample(series, freq, via_daily) + ax._plot_data.append(series) + args = _maybe_mask(series) + lines.append(plotf(ax, *args, **kwds)[0]) + labels.append(com._stringify(series.name)) return lines, labels diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 14ff96f490b5c..fdcee41860ff5 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -450,7 +450,7 @@ def test_secondary_y(self): plt.close('all') ser = Series(np.random.randn(10)) ser2 = Series(np.random.randn(10)) - ax = ser.plot(secondary_y=True) + ax = ser.plot(secondary_y=True).right_ax fig = ax.get_figure() axes = fig.get_axes() l = ax.get_lines()[0] @@ -464,7 +464,7 @@ def test_secondary_y(self): plt.close('all') ax = ser2.plot() - ax2 = ser.plot(secondary_y=True) + ax2 = ser.plot(secondary_y=True).right_ax self.assert_(ax.get_yaxis().get_visible()) plt.close('all') @@ -476,7 +476,7 @@ def test_secondary_y_ts(self): idx = date_range('1/1/2000', periods=10) ser = Series(np.random.randn(10), idx) ser2 = Series(np.random.randn(10), idx) - ax = ser.plot(secondary_y=True) + ax = ser.plot(secondary_y=True).right_ax fig = ax.get_figure() axes = fig.get_axes() l = ax.get_lines()[0] @@ -498,7 +498,7 @@ def test_secondary_kde(self): import matplotlib.pyplot as plt plt.close('all') ser = Series(np.random.randn(10)) - ax = ser.plot(secondary_y=True, kind='density') + ax = ser.plot(secondary_y=True, kind='density').right_ax fig = ax.get_figure() axes = fig.get_axes() self.assert_(axes[1].get_yaxis().get_ticks_position() == 'right') @@ -697,6 +697,74 @@ def test_time_musec(self): rs = time(h, m, s).strftime('%H:%M:%S.%f') self.assert_(xp, rs) + @slow + def test_secondary_upsample(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(secondary_y=True) + for l in ax.get_lines(): + self.assert_(l.get_xdata().freq == 'D') + for l in ax.right_ax.get_lines(): + self.assert_(l.get_xdata().freq == 'D') + + @slow + def test_legend(self): + # TS plots + # secondary + # plot series after left, right + + # Irreg TS plot + # secondary + # plot series after left, right + + # Non TS + # secondary + # plot series after left, right + pass + + @slow + def test_secondary_color(self): + import matplotlib.pyplot as plt + fig = plt.gcf() + plt.clf() + ax = fig.add_subplot(211) + + #ts + df = tm.makeTimeDataFrame() + ax = df.plot(secondary_y=['A', 'B']) + leg = ax.get_legend() + self.assert_(len(leg.get_lines()) == 4) + self.assert_(ax.right_ax.get_legend() is None) + + plt.clf() + ax = fig.add_subplot(211) + df = tm.makeTimeDataFrame() + ax = df.plot(secondary_y=['C', 'D']) + leg = ax.get_legend() + self.assert_(len(leg.get_lines()) == 4) + self.assert_(ax.right_ax.get_legend() is None) + + #non-ts + df = tm.makeDataFrame() + plt.clf() + ax = fig.add_subplot(211) + ax = df.plot(secondary_y=['A', 'B']) + leg = ax.get_legend() + self.assert_(len(leg.get_lines()) == 4) + self.assert_(ax.right_ax.get_legend() is None) + + plt.clf() + ax = fig.add_subplot(211) + ax = df.plot(secondary_y=['C', 'D']) + leg = ax.get_legend() + self.assert_(len(leg.get_lines()) == 4) + self.assert_(ax.right_ax.get_legend() is None) + PNG_PATH = 'tmp.png' def _check_plot_works(f, freq=None, series=None, *args, **kwargs): import matplotlib.pyplot as plt From 9d15685dbf2d1bdf4597971a709336a2f1e7c997 Mon Sep 17 00:00:00 2001 From: Chang She Date: Tue, 17 Jul 2012 22:52:31 -0400 Subject: [PATCH 6/9] ENH: use asfreq and reset index instead of resample for plotting --- pandas/tools/plotting.py | 16 ++++++-- pandas/tseries/plotting.py | 41 ++++++++------------- pandas/tseries/tests/test_plotting.py | 53 +++++++++++++++++++-------- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 935bd0f3f48f7..9cbae792d1ae1 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -816,7 +816,8 @@ def _post_plot_logic(self): df = self.data if self.subplots and self.legend: - self.axes[0].legend(loc='best') + for ax in self.axes: + ax.legend(loc='best') class LinePlot(MPLPlot): @@ -994,6 +995,10 @@ def _post_plot_logic(self): if index_name is not None: ax.set_xlabel(index_name) + if self.subplots and self.legend: + for ax in self.axes: + ax.legend(loc='best') + class BarPlot(MPLPlot): _default_rot = {'bar' : 90, 'barh' : 0} @@ -1064,9 +1069,9 @@ def _make_plot(self): labels.append(label) if self.legend and not self.subplots: - patches =[r[0] for r in rects] - self.axes[0].legend(patches, labels, loc='best', - title=self.legend_title) + patches =[r[0] for r in rects] + self.axes[0].legend(patches, labels, loc='best', + title=self.legend_title) def _post_plot_logic(self): for ax in self.axes: @@ -1091,6 +1096,9 @@ def _post_plot_logic(self): if name is not None: ax.set_ylabel(name) + #if self.subplots and self.legend: + # self.axes[0].legend(loc='best') + class BoxPlot(MPLPlot): pass diff --git a/pandas/tseries/plotting.py b/pandas/tseries/plotting.py index 187fe2b4d44d4..07905d25402cf 100644 --- a/pandas/tseries/plotting.py +++ b/pandas/tseries/plotting.py @@ -52,13 +52,12 @@ def tsplot(series, plotf, **kwargs): if freq is None: # pragma: no cover raise ValueError('Cannot use dynamic axis without frequency info') else: + # Convert DatetimeIndex to PeriodIndex + if isinstance(series.index, DatetimeIndex): + series = series.to_period(freq=freq) freq, ax_freq, series = _maybe_resample(series, ax, freq, plotf, kwargs) - # Convert DatetimeIndex to PeriodIndex - if isinstance(series.index, DatetimeIndex): - series = series.to_period(freq=freq) - # Set ax with freq info _decorate_axes(ax, freq, kwargs) @@ -88,19 +87,17 @@ def tsplot(series, plotf, **kwargs): def _maybe_resample(series, ax, freq, plotf, kwargs): ax_freq = _get_ax_freq(ax) if ax_freq is not None and freq != ax_freq: - if frequencies.is_subperiod(freq, ax_freq): # upsample existing - _upsample_others(ax, freq, plotf, kwargs) - ax_freq = freq - elif frequencies.is_superperiod(freq, ax_freq): # upsample input - series = series.asfreq(ax_freq).dropna() + if frequencies.is_superperiod(freq, ax_freq): # upsample input + series = series.copy() + series.index = series.index.asfreq(ax_freq) freq = ax_freq elif _is_sup(freq, ax_freq): # one is weekly how = kwargs.pop('how', 'last') series = series.resample('D', how=how).dropna() series = series.resample(ax_freq, how=how).dropna() freq = ax_freq - elif _is_sub(freq, ax_freq): - _upsample_others(ax, freq, plotf, kwargs, True) + elif frequencies.is_subperiod(freq, ax_freq) or _is_sub(freq, ax_freq): + _upsample_others(ax, freq, plotf, kwargs) ax_freq = freq else: raise ValueError('Incompatible frequency conversion') @@ -123,10 +120,9 @@ def _is_sup(f1, f2): return ((f1.startswith('W') and frequencies.is_superperiod('D', f2)) or (f2.startswith('W') and frequencies.is_superperiod(f1, 'D'))) -def _upsample_others(ax, freq, plotf, kwargs, - via_daily=False): +def _upsample_others(ax, freq, plotf, kwargs): legend = ax.get_legend() - lines, labels = _replot_ax(ax, freq, plotf, kwargs, via_daily) + lines, labels = _replot_ax(ax, freq, plotf, kwargs) other_ax = None if hasattr(ax, 'left_ax'): @@ -135,8 +131,7 @@ def _upsample_others(ax, freq, plotf, kwargs, other_ax = ax.right_ax if other_ax is not None: - rlines, rlabels = _replot_ax(other_ax, freq, plotf, kwargs, - via_daily) + rlines, rlabels = _replot_ax(other_ax, freq, plotf, kwargs) lines.extend(rlines) labels.extend(rlabels) @@ -147,17 +142,19 @@ def _upsample_others(ax, freq, plotf, kwargs, title = None ax.legend(lines, labels, loc='best', title=title) -def _replot_ax(ax, freq, plotf, kwargs, via_daily): +def _replot_ax(ax, freq, plotf, kwargs): + data = getattr(ax, '_plot_data', None) ax._plot_data = [] ax.clear() _decorate_axes(ax, freq, kwargs) - data = getattr(ax, '_plot_data', None) lines = [] labels = [] if data is not None: for series, kwds in data: - series = _upsample(series, freq, via_daily) + series = series.copy() + idx = series.index.asfreq(freq) + series.index = idx ax._plot_data.append(series) args = _maybe_mask(series) lines.append(plotf(ax, *args, **kwds)[0]) @@ -165,12 +162,6 @@ def _replot_ax(ax, freq, plotf, kwargs, via_daily): return lines, labels -def _upsample(series, freq, via_daily): - if not via_daily: - return series.resample(freq).dropna() - else: - return series.resample('D').resample(freq).dropna() - def _decorate_axes(ax, freq, kwargs): ax.freq = freq xaxis = ax.get_xaxis() diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index fdcee41860ff5..5ff295ba85ab6 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -444,6 +444,26 @@ def test_gaps(self): mask = data.mask self.assert_(mask[2:5, 1].all()) + @slow + def test_gap_upsample(self): + import matplotlib.pyplot as plt + plt.close('all') + low = tm.makeTimeSeries() + low[5:25] = np.nan + ax = low.plot() + + idxh = date_range(low.index[0], low.index[-1], freq='12h') + s = Series(np.random.randn(len(idxh)), idxh) + s.plot(secondary_y=True) + lines = ax.get_lines() + self.assert_(len(lines) == 1) + self.assert_(len(ax.right_ax.get_lines()) == 1) + l = lines[0] + data = l.get_xydata() + self.assert_(isinstance(data, np.ma.core.MaskedArray)) + mask = data.mask + self.assert_(mask[5:25, 1].all()) + @slow def test_secondary_y(self): import matplotlib.pyplot as plt @@ -713,22 +733,7 @@ def test_secondary_upsample(self): self.assert_(l.get_xdata().freq == 'D') @slow - def test_legend(self): - # TS plots - # secondary - # plot series after left, right - - # Irreg TS plot - # secondary - # plot series after left, right - - # Non TS - # secondary - # plot series after left, right - pass - - @slow - def test_secondary_color(self): + def test_secondary_legend(self): import matplotlib.pyplot as plt fig = plt.gcf() plt.clf() @@ -740,6 +745,10 @@ def test_secondary_color(self): leg = ax.get_legend() self.assert_(len(leg.get_lines()) == 4) self.assert_(ax.right_ax.get_legend() is None) + colors = set() + for line in leg.get_lines(): + colors.add(line.get_color()) + self.assert_(len(colors) == 4) plt.clf() ax = fig.add_subplot(211) @@ -748,6 +757,10 @@ def test_secondary_color(self): leg = ax.get_legend() self.assert_(len(leg.get_lines()) == 4) self.assert_(ax.right_ax.get_legend() is None) + colors = set() + for line in leg.get_lines(): + colors.add(line.get_color()) + self.assert_(len(colors) == 4) #non-ts df = tm.makeDataFrame() @@ -757,6 +770,10 @@ def test_secondary_color(self): leg = ax.get_legend() self.assert_(len(leg.get_lines()) == 4) self.assert_(ax.right_ax.get_legend() is None) + colors = set() + for line in leg.get_lines(): + colors.add(line.get_color()) + self.assert_(len(colors) == 4) plt.clf() ax = fig.add_subplot(211) @@ -764,6 +781,10 @@ def test_secondary_color(self): leg = ax.get_legend() self.assert_(len(leg.get_lines()) == 4) self.assert_(ax.right_ax.get_legend() is None) + colors = set() + for line in leg.get_lines(): + colors.add(line.get_color()) + self.assert_(len(colors) == 4) PNG_PATH = 'tmp.png' def _check_plot_works(f, freq=None, series=None, *args, **kwargs): From ca1c45284fe82c26f1715dad6370e86545ab2e0b Mon Sep 17 00:00:00 2001 From: Chang She Date: Wed, 18 Jul 2012 15:23:19 -0400 Subject: [PATCH 7/9] minor cleanup in plotting module --- pandas/tools/plotting.py | 76 +++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 9cbae792d1ae1..c14f1db939458 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -581,24 +581,23 @@ def _args_adjust(self): def _maybe_right_yaxis(self, ax): _types = (list, tuple, np.ndarray) - need_second = ((isinstance(self.secondary_y, bool) and self.secondary_y) - or (isinstance(self.secondary_y, _types) - and len(self.secondary_y) > 0)) + sec_true = isinstance(self.secondary_y, bool) and self.secondary_y + list_sec = isinstance(self.secondary_y, _types) + has_sec = list_sec and len(self.secondary_y) > 0 + all_sec = list_sec and len(self.secondary_y) == self.nseries - if need_second and not hasattr(ax, 'right_ax'): + if (sec_true or has_sec) and not hasattr(ax, 'right_ax'): orig_ax, new_ax = ax, ax.twinx() orig_ax.right_ax, new_ax.left_ax = new_ax, orig_ax + if len(orig_ax.get_lines()) == 0: # no data on left y orig_ax.get_yaxis().set_visible(False) + if len(new_ax.get_lines()) == 0: new_ax.get_yaxis().set_visible(False) - if ((isinstance(self.secondary_y, bool) and self.secondary_y) or - (isinstance(self.secondary_y, _types) and - (len(self.secondary_y) == self.nseries))): + if sec_true or all_sec: ax = new_ax - else: - ax = orig_ax else: ax.get_yaxis().set_visible(True) @@ -753,15 +752,17 @@ def _get_index_name(self): return name def _get_ax(self, i): + # get the twinx ax if appropriate if self.subplots: ax = self.axes[i] else: ax = self.axes[0] - if self.on_right(i): - if hasattr(ax, 'right_ax'): - ax = ax.right_ax - elif hasattr(ax, 'left_ax'): - ax = ax.left_ax + + if self.on_right(i): + if hasattr(ax, 'right_ax'): + ax = ax.right_ax + elif hasattr(ax, 'left_ax'): + ax = ax.left_ax ax.get_yaxis().set_visible(True) return ax @@ -770,17 +771,15 @@ def on_right(self, i): from pandas.core.frame import DataFrame if isinstance(self.secondary_y, bool): return self.secondary_y + if (isinstance(self.data, DataFrame) and isinstance(self.secondary_y, (tuple, list, np.ndarray))): return self.data.columns[i] in self.secondary_y - def _get_ax_and_style(self, i, col_name): - ax = self._get_ax(i) - + def _get_style(self, i, col_name): + style = '' if self.subplots: style = 'k' - else: - style = '' if self.style is not None: if isinstance(self.style, list): @@ -790,7 +789,7 @@ def _get_ax_and_style(self, i, col_name): else: style = self.style - return ax, style + return style class KdePlot(MPLPlot): def __init__(self, data, **kwargs): @@ -800,7 +799,8 @@ def _make_plot(self): from scipy.stats import gaussian_kde plotf = self._get_plot_function() for i, (label, y) in enumerate(self._iter_data()): - ax, style = self._get_ax_and_style(i, label) + ax = self._get_ax(i) + style = self._get_style(i, label) label = com._stringify(label) @@ -869,7 +869,8 @@ def _make_plot(self): plotf = self._get_plot_function() for i, (label, y) in enumerate(self._iter_data()): - ax, style = self._get_ax_and_style(i, label) + ax = self._get_ax(i) + style = self._get_style(i, label) kwds = self.kwds.copy() if re.match('[a-z]+', style) is None: kwds['color'] = colors[i % len(colors)] @@ -905,22 +906,25 @@ def _make_ts_plot(self, data, **kwargs): label = com._stringify(self.label) if re.match('[a-z]+', style) is None: kwargs['color'] = colors[0] - newline = tsplot(data, plotf, ax=ax, label=label, style=self.style, - **kwargs)[0] + + newlines = tsplot(data, plotf, ax=ax, label=label, style=self.style, + **kwargs) ax.grid(self.grid) - lines.append(newline) + lines.append(newlines[0]) labels.append(label) else: for i, col in enumerate(data.columns): - ax, style = self._get_ax_and_style(i, col) + label = com._stringify(col) + ax = self._get_ax(i) + style = self._get_style(i, col) kwds = kwargs.copy() if re.match('[a-z]+', style) is None: kwds['color'] = colors[i % len(colors)] - label = com._stringify(col) - newline = tsplot(data[col], plotf, ax=ax, label=label, - style=style, **kwds)[0] - lines.append(newline) + newlines = tsplot(data[col], plotf, ax=ax, label=label, + style=style, **kwds) + + lines.append(newlines[0]) labels.append(label) ax.grid(self.grid) @@ -928,17 +932,17 @@ def _make_ts_plot(self, data, **kwargs): def _make_legend(self, lines, labels): ax, leg = self._get_ax_legend(self.axes[0]) - if (self.legend or leg is not None) and not self.subplots: + + if not self.subplots: if leg is not None: ext_lines = leg.get_lines() ext_labels = [x.get_text() for x in leg.get_texts()] ext_lines.extend(lines) ext_labels.extend(labels) - else: - ext_lines = lines - ext_labels = labels - ax.legend(ext_lines, ext_labels, loc='best', - title=self.legend_title) + ax.legend(ext_lines, ext_labels, loc='best', + title=self.legend_title) + elif self.legend: + ax.legend(lines, labels, loc='best', title=self.legend_title) def _get_ax_legend(self, ax): leg = ax.get_legend() From 7b189c1376c44e4e031e9d1b7cadeab7c48e28df Mon Sep 17 00:00:00 2001 From: Chang She Date: Wed, 18 Jul 2012 17:44:10 -0400 Subject: [PATCH 8/9] DOC: getting started on v0.8.1 rls notes --- doc/source/v0.8.1.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 doc/source/v0.8.1.txt diff --git a/doc/source/v0.8.1.txt b/doc/source/v0.8.1.txt new file mode 100644 index 0000000000000..da05b74d07983 --- /dev/null +++ b/doc/source/v0.8.1.txt @@ -0,0 +1,27 @@ +.. _whatsnew_0801: + +v.0.8.1 (July 23, 2012) +--------------------------- + +This release includes a few new features and addresses over a dozen bugs in +0.8.0, most notably NA friendly string processing functionality and a series of +new plot types and options. + +New features +~~~~~~~~~~~~ + + - Add string processing methods accesible via Series.str (GH620_) + - Add option to disable adjustment in EWMA (GH1584_) + - Radviz plot (GH1566_) + - Per column styles and secondary y-axis plotting (GH1559_) + - New datetime converters millisecond plotting (GH1599_) + +Performance improvements +~~~~~~~~~~~~~~~~~~~~~~~~ + + - Improved implementation of rolling min and max + - Set logic performance for primitives + - Significant datetime parsing performance improvments + +.. _GH561: https://github.com/pydata/pandas/issues/561 +.. _GH50: https://github.com/pydata/pandas/issues/50 From ce8fd029dd31917d7800f316086c8d13faca9849 Mon Sep 17 00:00:00 2001 From: Chang She Date: Wed, 18 Jul 2012 17:44:35 -0400 Subject: [PATCH 9/9] ENH: marking right y-axis columns --- doc/source/visualization.rst | 25 +++++++++++++++++++++++++ pandas/tools/plotting.py | 17 ++++++++++++++--- pandas/tseries/tests/test_plotting.py | 14 ++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/doc/source/visualization.rst b/doc/source/visualization.rst index ca6120f38606f..99651d87fdafd 100644 --- a/doc/source/visualization.rst +++ b/doc/source/visualization.rst @@ -120,6 +120,31 @@ To plot data on a secondary y-axis, use the ``secondary_y`` keyword: df.B.plot(secondary_y=True, style='g') +Selective Plotting on Secondary Y-axis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To plot some columns in a DataFrame, give the column names to the `secondary_y` +keyword: + +.. ipython:: python + + plt.figure() + + @savefig frame_plot_secondary_y.png width=4.5in + df.plot(secondary_y=['A', 'B']) + +Note that the columns plotted on the secondary y-axis is automatically marked +with "(right)" in the legend. To turn off the automatic marking, use the +`mark_right=False` keyword: + +.. ipython:: python + + plt.figure() + + @savefig frame_plot_secondary_y.png width=4.5in + df.plot(secondary_y=['A', 'B'], mark_right=False) + + Targeting different subplots ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index c14f1db939458..2df415252de51 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -822,6 +822,7 @@ def _post_plot_logic(self): class LinePlot(MPLPlot): def __init__(self, data, **kwargs): + self.mark_right = kwargs.pop('mark_right', True) MPLPlot.__init__(self, data, **kwargs) def _index_freq(self): @@ -884,7 +885,10 @@ def _make_plot(self): newline = plotf(ax, x, y, style, label=label, **kwds)[0] lines.append(newline) - labels.append(label) + leg_label = label + if self.mark_right and self.on_right(i): + leg_label += ' (right)' + labels.append(leg_label) ax.grid(self.grid) self._make_legend(lines, labels) @@ -900,6 +904,11 @@ def _make_ts_plot(self, data, **kwargs): lines = [] labels = [] + def to_leg_label(label, i): + if self.mark_right and self.on_right(i): + return label + ' (right)' + return label + if isinstance(data, Series): ax = self._get_ax(0) #self.axes[0] style = self.style or '' @@ -911,7 +920,8 @@ def _make_ts_plot(self, data, **kwargs): **kwargs) ax.grid(self.grid) lines.append(newlines[0]) - labels.append(label) + leg_label = to_leg_label(label, 0) + labels.append(leg_label) else: for i, col in enumerate(data.columns): label = com._stringify(col) @@ -925,7 +935,8 @@ def _make_ts_plot(self, data, **kwargs): style=style, **kwds) lines.append(newlines[0]) - labels.append(label) + leg_label = to_leg_label(label, i) + labels.append(leg_label) ax.grid(self.grid) self._make_legend(lines, labels) diff --git a/pandas/tseries/tests/test_plotting.py b/pandas/tseries/tests/test_plotting.py index 5ff295ba85ab6..0c884760e94d6 100644 --- a/pandas/tseries/tests/test_plotting.py +++ b/pandas/tseries/tests/test_plotting.py @@ -744,12 +744,26 @@ def test_secondary_legend(self): ax = df.plot(secondary_y=['A', 'B']) leg = ax.get_legend() self.assert_(len(leg.get_lines()) == 4) + self.assert_(leg.get_texts()[0].get_text() == 'A (right)') + self.assert_(leg.get_texts()[1].get_text() == 'B (right)') + self.assert_(leg.get_texts()[2].get_text() == 'C') + self.assert_(leg.get_texts()[3].get_text() == 'D') self.assert_(ax.right_ax.get_legend() is None) colors = set() for line in leg.get_lines(): colors.add(line.get_color()) self.assert_(len(colors) == 4) + plt.clf() + ax = fig.add_subplot(211) + ax = df.plot(secondary_y=['A', 'C'], mark_right=False) + leg = ax.get_legend() + self.assert_(len(leg.get_lines()) == 4) + self.assert_(leg.get_texts()[0].get_text() == 'A') + self.assert_(leg.get_texts()[1].get_text() == 'B') + self.assert_(leg.get_texts()[2].get_text() == 'C') + self.assert_(leg.get_texts()[3].get_text() == 'D') + plt.clf() ax = fig.add_subplot(211) df = tm.makeTimeDataFrame()