Skip to content

Commit 818f0a7

Browse files
committed
Merge pull request #9894 from sinhrks/subplots_style
ENH/BUG: color cannot be applied to line subplots
2 parents 6c48d12 + dd538a3 commit 818f0a7

File tree

3 files changed

+141
-19
lines changed

3 files changed

+141
-19
lines changed

doc/source/whatsnew/v0.17.0.txt

+5-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Other enhancements
8787
Backwards incompatible API changes
8888
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8989

90+
- 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`)
91+
9092
.. _whatsnew_0170.api_breaking.convert_objects:
9193

9294
Changes to convert_objects
@@ -341,6 +343,7 @@ Bug Fixes
341343
- Bug in ``Index.drop_duplicates`` dropping name(s) (:issue:`10115`)
342344
- Bug in ``pd.Series`` when setting a value on an empty ``Series`` whose index has a frequency. (:issue:`10193`)
343345
- Bug in ``DataFrame.plot`` raises ``ValueError`` when color name is specified by multiple characters (:issue:`10387`)
346+
344347
- Bug in ``DataFrame.reset_index`` when index contains `NaT`. (:issue:`10388`)
345348
- Bug in ``ExcelReader`` when worksheet is empty (:issue:`6403`)
346349
- Bug in ``Table.select_column`` where name is not preserved (:issue:`10392`)
@@ -381,7 +384,8 @@ Bug Fixes
381384

382385

383386

384-
387+
- Bug in line and kde plot cannot accept multiple colors when ``subplots=True`` (:issue:`9894`)
388+
- Bug in ``DataFrame.plot`` raises ``ValueError`` when color name is specified by multiple characters (:issue:`10387`)
385389

386390

387391

pandas/tests/test_graphics.py

+119
Original file line numberDiff line numberDiff line change
@@ -2592,6 +2592,67 @@ def test_line_colors(self):
25922592
self._check_colors(ax.get_lines(), linecolors=['red'] * 5)
25932593
tm.close()
25942594

2595+
@slow
2596+
def test_line_colors_and_styles_subplots(self):
2597+
# GH 9894
2598+
from matplotlib import cm
2599+
default_colors = self.plt.rcParams.get('axes.color_cycle')
2600+
2601+
df = DataFrame(randn(5, 5))
2602+
2603+
axes = df.plot(subplots=True)
2604+
for ax, c in zip(axes, list(default_colors)):
2605+
self._check_colors(ax.get_lines(), linecolors=c)
2606+
tm.close()
2607+
2608+
# single color char
2609+
axes = df.plot(subplots=True, color='k')
2610+
for ax in axes:
2611+
self._check_colors(ax.get_lines(), linecolors=['k'])
2612+
tm.close()
2613+
2614+
# single color str
2615+
axes = df.plot(subplots=True, color='green')
2616+
for ax in axes:
2617+
self._check_colors(ax.get_lines(), linecolors=['green'])
2618+
tm.close()
2619+
2620+
custom_colors = 'rgcby'
2621+
axes = df.plot(color=custom_colors, subplots=True)
2622+
for ax, c in zip(axes, list(custom_colors)):
2623+
self._check_colors(ax.get_lines(), linecolors=[c])
2624+
tm.close()
2625+
2626+
axes = df.plot(color=list(custom_colors), subplots=True)
2627+
for ax, c in zip(axes, list(custom_colors)):
2628+
self._check_colors(ax.get_lines(), linecolors=[c])
2629+
tm.close()
2630+
2631+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
2632+
for cmap in ['jet', cm.jet]:
2633+
axes = df.plot(colormap=cmap, subplots=True)
2634+
for ax, c in zip(axes, rgba_colors):
2635+
self._check_colors(ax.get_lines(), linecolors=[c])
2636+
tm.close()
2637+
2638+
# make color a list if plotting one column frame
2639+
# handles cases like df.plot(color='DodgerBlue')
2640+
axes = df.ix[:, [0]].plot(color='DodgerBlue', subplots=True)
2641+
self._check_colors(axes[0].lines, linecolors=['DodgerBlue'])
2642+
2643+
# single character style
2644+
axes = df.plot(style='r', subplots=True)
2645+
for ax in axes:
2646+
self._check_colors(ax.get_lines(), linecolors=['r'])
2647+
tm.close()
2648+
2649+
# list of styles
2650+
styles = list('rgcby')
2651+
axes = df.plot(style=styles, subplots=True)
2652+
for ax, c in zip(axes, styles):
2653+
self._check_colors(ax.get_lines(), linecolors=[c])
2654+
tm.close()
2655+
25952656
@slow
25962657
def test_area_colors(self):
25972658
from matplotlib import cm
@@ -2694,6 +2755,64 @@ def test_kde_colors(self):
26942755
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
26952756
self._check_colors(ax.get_lines(), linecolors=rgba_colors)
26962757

2758+
@slow
2759+
def test_kde_colors_and_styles_subplots(self):
2760+
tm._skip_if_no_scipy()
2761+
_skip_if_no_scipy_gaussian_kde()
2762+
2763+
from matplotlib import cm
2764+
default_colors = self.plt.rcParams.get('axes.color_cycle')
2765+
2766+
df = DataFrame(randn(5, 5))
2767+
2768+
axes = df.plot(kind='kde', subplots=True)
2769+
for ax, c in zip(axes, list(default_colors)):
2770+
self._check_colors(ax.get_lines(), linecolors=[c])
2771+
tm.close()
2772+
2773+
# single color char
2774+
axes = df.plot(kind='kde', color='k', subplots=True)
2775+
for ax in axes:
2776+
self._check_colors(ax.get_lines(), linecolors=['k'])
2777+
tm.close()
2778+
2779+
# single color str
2780+
axes = df.plot(kind='kde', color='red', subplots=True)
2781+
for ax in axes:
2782+
self._check_colors(ax.get_lines(), linecolors=['red'])
2783+
tm.close()
2784+
2785+
custom_colors = 'rgcby'
2786+
axes = df.plot(kind='kde', color=custom_colors, subplots=True)
2787+
for ax, c in zip(axes, list(custom_colors)):
2788+
self._check_colors(ax.get_lines(), linecolors=[c])
2789+
tm.close()
2790+
2791+
rgba_colors = lmap(cm.jet, np.linspace(0, 1, len(df)))
2792+
for cmap in ['jet', cm.jet]:
2793+
axes = df.plot(kind='kde', colormap=cmap, subplots=True)
2794+
for ax, c in zip(axes, rgba_colors):
2795+
self._check_colors(ax.get_lines(), linecolors=[c])
2796+
tm.close()
2797+
2798+
# make color a list if plotting one column frame
2799+
# handles cases like df.plot(color='DodgerBlue')
2800+
axes = df.ix[:, [0]].plot(kind='kde', color='DodgerBlue', subplots=True)
2801+
self._check_colors(axes[0].lines, linecolors=['DodgerBlue'])
2802+
2803+
# single character style
2804+
axes = df.plot(kind='kde', style='r', subplots=True)
2805+
for ax in axes:
2806+
self._check_colors(ax.get_lines(), linecolors=['r'])
2807+
tm.close()
2808+
2809+
# list of styles
2810+
styles = list('rgcby')
2811+
axes = df.plot(kind='kde', style=styles, subplots=True)
2812+
for ax, c in zip(axes, styles):
2813+
self._check_colors(ax.get_lines(), linecolors=[c])
2814+
tm.close()
2815+
26972816
@slow
26982817
def test_boxplot_colors(self):
26992818

pandas/tools/plotting.py

+17-18
Original file line numberDiff line numberDiff line change
@@ -1288,23 +1288,28 @@ def on_right(self, i):
12881288
if isinstance(self.secondary_y, (tuple, list, np.ndarray, Index)):
12891289
return self.data.columns[i] in self.secondary_y
12901290

1291-
def _get_style(self, i, col_name):
1292-
style = ''
1293-
if self.subplots:
1294-
style = 'k'
1295-
1291+
def _apply_style_colors(self, colors, kwds, col_num, label):
1292+
"""
1293+
Manage style and color based on column number and its label.
1294+
Returns tuple of appropriate style and kwds which "color" may be added.
1295+
"""
1296+
style = None
12961297
if self.style is not None:
12971298
if isinstance(self.style, list):
12981299
try:
1299-
style = self.style[i]
1300+
style = self.style[col_num]
13001301
except IndexError:
13011302
pass
13021303
elif isinstance(self.style, dict):
1303-
style = self.style.get(col_name, style)
1304+
style = self.style.get(label, style)
13041305
else:
13051306
style = self.style
13061307

1307-
return style or None
1308+
has_color = 'color' in kwds or self.colormap is not None
1309+
nocolor_style = style is None or re.match('[a-z]+', style) is None
1310+
if (has_color or self.subplots) and nocolor_style:
1311+
kwds['color'] = colors[col_num % len(colors)]
1312+
return style, kwds
13081313

13091314
def _get_colors(self, num_colors=None, color_kwds='color'):
13101315
if num_colors is None:
@@ -1314,11 +1319,6 @@ def _get_colors(self, num_colors=None, color_kwds='color'):
13141319
colormap=self.colormap,
13151320
color=self.kwds.get(color_kwds))
13161321

1317-
def _maybe_add_color(self, colors, kwds, style, i):
1318-
has_color = 'color' in kwds or self.colormap is not None
1319-
if has_color and (style is None or re.match('[a-z]+', style) is None):
1320-
kwds['color'] = colors[i % len(colors)]
1321-
13221322
def _parse_errorbars(self, label, err):
13231323
'''
13241324
Look for error keyword arguments and return the actual errorbar data
@@ -1638,9 +1638,8 @@ def _make_plot(self):
16381638
colors = self._get_colors()
16391639
for i, (label, y) in enumerate(it):
16401640
ax = self._get_ax(i)
1641-
style = self._get_style(i, label)
16421641
kwds = self.kwds.copy()
1643-
self._maybe_add_color(colors, kwds, style, i)
1642+
style, kwds = self._apply_style_colors(colors, kwds, i, label)
16441643

16451644
errors = self._get_errorbars(label=label, index=i)
16461645
kwds = dict(kwds, **errors)
@@ -1990,13 +1989,13 @@ def _make_plot(self):
19901989
colors = self._get_colors()
19911990
for i, (label, y) in enumerate(self._iter_data()):
19921991
ax = self._get_ax(i)
1993-
style = self._get_style(i, label)
1994-
label = com.pprint_thing(label)
19951992

19961993
kwds = self.kwds.copy()
1994+
1995+
label = com.pprint_thing(label)
19971996
kwds['label'] = label
1998-
self._maybe_add_color(colors, kwds, style, i)
19991997

1998+
style, kwds = self._apply_style_colors(colors, kwds, i, label)
20001999
if style is not None:
20012000
kwds['style'] = style
20022001

0 commit comments

Comments
 (0)