diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index 8c582f2618882..3e6e9355da5a2 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -87,6 +87,8 @@ Other enhancements Backwards incompatible API changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- Line and kde plot with ``subplots=True`` now uses default colors, not all black. Specify ``color='k'`` to draw all lines in black (:issue:`9894`) + .. _whatsnew_0170.api_breaking.convert_objects: Changes to convert_objects @@ -341,6 +343,7 @@ Bug Fixes - Bug in ``Index.drop_duplicates`` dropping name(s) (:issue:`10115`) - Bug in ``pd.Series`` when setting a value on an empty ``Series`` whose index has a frequency. (:issue:`10193`) - Bug in ``DataFrame.plot`` raises ``ValueError`` when color name is specified by multiple characters (:issue:`10387`) + - Bug in ``DataFrame.reset_index`` when index contains `NaT`. (:issue:`10388`) - Bug in ``ExcelReader`` when worksheet is empty (:issue:`6403`) - Bug in ``Table.select_column`` where name is not preserved (:issue:`10392`) @@ -381,7 +384,8 @@ Bug Fixes - +- Bug in line and kde plot cannot accept multiple colors when ``subplots=True`` (:issue:`9894`) +- Bug in ``DataFrame.plot`` raises ``ValueError`` when color name is specified by multiple characters (:issue:`10387`) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index dd526720d2605..800c6f83f4902 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -2592,6 +2592,67 @@ def test_line_colors(self): self._check_colors(ax.get_lines(), linecolors=['red'] * 5) tm.close() + @slow + def test_line_colors_and_styles_subplots(self): + # GH 9894 + from matplotlib import cm + default_colors = self.plt.rcParams.get('axes.color_cycle') + + df = DataFrame(randn(5, 5)) + + axes = df.plot(subplots=True) + for ax, c in zip(axes, list(default_colors)): + self._check_colors(ax.get_lines(), linecolors=c) + tm.close() + + # single color char + axes = df.plot(subplots=True, color='k') + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['k']) + tm.close() + + # single color str + axes = df.plot(subplots=True, color='green') + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['green']) + tm.close() + + custom_colors = 'rgcby' + axes = df.plot(color=custom_colors, subplots=True) + for ax, c in zip(axes, list(custom_colors)): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + axes = df.plot(color=list(custom_colors), subplots=True) + for ax, c in zip(axes, list(custom_colors)): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df))) + for cmap in ['jet', cm.jet]: + axes = df.plot(colormap=cmap, subplots=True) + for ax, c in zip(axes, rgba_colors): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + # make color a list if plotting one column frame + # handles cases like df.plot(color='DodgerBlue') + axes = df.ix[:, [0]].plot(color='DodgerBlue', subplots=True) + self._check_colors(axes[0].lines, linecolors=['DodgerBlue']) + + # single character style + axes = df.plot(style='r', subplots=True) + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['r']) + tm.close() + + # list of styles + styles = list('rgcby') + axes = df.plot(style=styles, subplots=True) + for ax, c in zip(axes, styles): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + @slow def test_area_colors(self): from matplotlib import cm @@ -2694,6 +2755,64 @@ def test_kde_colors(self): rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df))) self._check_colors(ax.get_lines(), linecolors=rgba_colors) + @slow + def test_kde_colors_and_styles_subplots(self): + tm._skip_if_no_scipy() + _skip_if_no_scipy_gaussian_kde() + + from matplotlib import cm + default_colors = self.plt.rcParams.get('axes.color_cycle') + + df = DataFrame(randn(5, 5)) + + axes = df.plot(kind='kde', subplots=True) + for ax, c in zip(axes, list(default_colors)): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + # single color char + axes = df.plot(kind='kde', color='k', subplots=True) + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['k']) + tm.close() + + # single color str + axes = df.plot(kind='kde', color='red', subplots=True) + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['red']) + tm.close() + + custom_colors = 'rgcby' + axes = df.plot(kind='kde', color=custom_colors, subplots=True) + for ax, c in zip(axes, list(custom_colors)): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df))) + for cmap in ['jet', cm.jet]: + axes = df.plot(kind='kde', colormap=cmap, subplots=True) + for ax, c in zip(axes, rgba_colors): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + + # make color a list if plotting one column frame + # handles cases like df.plot(color='DodgerBlue') + axes = df.ix[:, [0]].plot(kind='kde', color='DodgerBlue', subplots=True) + self._check_colors(axes[0].lines, linecolors=['DodgerBlue']) + + # single character style + axes = df.plot(kind='kde', style='r', subplots=True) + for ax in axes: + self._check_colors(ax.get_lines(), linecolors=['r']) + tm.close() + + # list of styles + styles = list('rgcby') + axes = df.plot(kind='kde', style=styles, subplots=True) + for ax, c in zip(axes, styles): + self._check_colors(ax.get_lines(), linecolors=[c]) + tm.close() + @slow def test_boxplot_colors(self): diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 54298e8434a1b..6a822a0231a2b 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -1288,23 +1288,28 @@ def on_right(self, i): if isinstance(self.secondary_y, (tuple, list, np.ndarray, Index)): return self.data.columns[i] in self.secondary_y - def _get_style(self, i, col_name): - style = '' - if self.subplots: - style = 'k' - + def _apply_style_colors(self, colors, kwds, col_num, label): + """ + Manage style and color based on column number and its label. + Returns tuple of appropriate style and kwds which "color" may be added. + """ + style = None if self.style is not None: if isinstance(self.style, list): try: - style = self.style[i] + style = self.style[col_num] except IndexError: pass elif isinstance(self.style, dict): - style = self.style.get(col_name, style) + style = self.style.get(label, style) else: style = self.style - return style or None + has_color = 'color' in kwds or self.colormap is not None + nocolor_style = style is None or re.match('[a-z]+', style) is None + if (has_color or self.subplots) and nocolor_style: + kwds['color'] = colors[col_num % len(colors)] + return style, kwds def _get_colors(self, num_colors=None, color_kwds='color'): if num_colors is None: @@ -1314,11 +1319,6 @@ def _get_colors(self, num_colors=None, color_kwds='color'): colormap=self.colormap, color=self.kwds.get(color_kwds)) - def _maybe_add_color(self, colors, kwds, style, i): - has_color = 'color' in kwds or self.colormap is not None - if has_color and (style is None or re.match('[a-z]+', style) is None): - kwds['color'] = colors[i % len(colors)] - def _parse_errorbars(self, label, err): ''' Look for error keyword arguments and return the actual errorbar data @@ -1638,9 +1638,8 @@ def _make_plot(self): colors = self._get_colors() for i, (label, y) in enumerate(it): ax = self._get_ax(i) - style = self._get_style(i, label) kwds = self.kwds.copy() - self._maybe_add_color(colors, kwds, style, i) + style, kwds = self._apply_style_colors(colors, kwds, i, label) errors = self._get_errorbars(label=label, index=i) kwds = dict(kwds, **errors) @@ -1990,13 +1989,13 @@ def _make_plot(self): colors = self._get_colors() for i, (label, y) in enumerate(self._iter_data()): ax = self._get_ax(i) - style = self._get_style(i, label) - label = com.pprint_thing(label) kwds = self.kwds.copy() + + label = com.pprint_thing(label) kwds['label'] = label - self._maybe_add_color(colors, kwds, style, i) + style, kwds = self._apply_style_colors(colors, kwds, i, label) if style is not None: kwds['style'] = style