From 0451bc35472b6a3c9cdc5fd401e731e4ab91a45e Mon Sep 17 00:00:00 2001 From: sinhrks Date: Wed, 17 Jun 2015 22:45:33 +0900 Subject: [PATCH] BUG: DataFrame.plot raises ValueError when color name is specified by multiple characters --- doc/source/whatsnew/v0.17.0.txt | 2 +- pandas/tests/test_graphics.py | 61 ++++++++++++++++++++++++++++++++- pandas/tools/plotting.py | 26 ++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.17.0.txt b/doc/source/whatsnew/v0.17.0.txt index 742077d39fb18..9e8ecb6c57de5 100644 --- a/doc/source/whatsnew/v0.17.0.txt +++ b/doc/source/whatsnew/v0.17.0.txt @@ -81,7 +81,7 @@ Bug Fixes - 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`) diff --git a/pandas/tests/test_graphics.py b/pandas/tests/test_graphics.py index 2c8123244c53c..2fbb4dfc6fd91 100644 --- a/pandas/tests/test_graphics.py +++ b/pandas/tests/test_graphics.py @@ -1146,6 +1146,53 @@ def test_series_grid_settings(self): self._check_grid_settings(Series([1,2,3]), plotting._series_kinds + plotting._common_kinds) + @slow + def test_standard_colors(self): + for c in ['r', 'red', 'green', '#FF0000']: + result = plotting._get_standard_colors(1, color=c) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(1, color=[c]) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(3, color=c) + self.assertEqual(result, [c] * 3) + + result = plotting._get_standard_colors(3, color=[c]) + self.assertEqual(result, [c] * 3) + + @slow + def test_standard_colors_all(self): + import matplotlib.colors as colors + + # multiple colors like mediumaquamarine + for c in colors.cnames: + result = plotting._get_standard_colors(num_colors=1, color=c) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(num_colors=1, color=[c]) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(num_colors=3, color=c) + self.assertEqual(result, [c] * 3) + + result = plotting._get_standard_colors(num_colors=3, color=[c]) + self.assertEqual(result, [c] * 3) + + # single letter colors like k + for c in colors.ColorConverter.colors: + result = plotting._get_standard_colors(num_colors=1, color=c) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(num_colors=1, color=[c]) + self.assertEqual(result, [c]) + + result = plotting._get_standard_colors(num_colors=3, color=c) + self.assertEqual(result, [c] * 3) + + result = plotting._get_standard_colors(num_colors=3, color=[c]) + self.assertEqual(result, [c] * 3) + @tm.mplskip class TestDataFramePlots(TestPlotBase): @@ -1736,7 +1783,6 @@ def test_bar_colors(self): default_colors = plt.rcParams.get('axes.color_cycle') - df = DataFrame(randn(5, 5)) ax = df.plot(kind='bar') self._check_colors(ax.patches[::5], facecolors=default_colors[:5]) @@ -1762,6 +1808,11 @@ def test_bar_colors(self): ax = df.ix[:, [0]].plot(kind='bar', color='DodgerBlue') self._check_colors([ax.patches[0]], facecolors=['DodgerBlue']) + tm.close() + + ax = df.plot(kind='bar', color='green') + self._check_colors(ax.patches[::5], facecolors=['green'] * 5) + tm.close() @slow def test_bar_linewidth(self): @@ -2897,6 +2948,10 @@ def test_line_colors(self): ax = df.ix[:, [0]].plot(color='DodgerBlue') self._check_colors(ax.lines, linecolors=['DodgerBlue']) + ax = df.plot(color='red') + self._check_colors(ax.get_lines(), linecolors=['red'] * 5) + tm.close() + @slow def test_area_colors(self): from matplotlib import cm @@ -2972,6 +3027,10 @@ def test_hist_colors(self): ax = df.ix[:, [0]].plot(kind='hist', color='DodgerBlue') self._check_colors([ax.patches[0]], facecolors=['DodgerBlue']) + ax = df.plot(kind='hist', color='green') + self._check_colors(ax.patches[::10], facecolors=['green'] * 5) + tm.close() + @slow def test_kde_colors(self): tm._skip_if_no_scipy() diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py index 35893b9de8e75..3265889e4b268 100644 --- a/pandas/tools/plotting.py +++ b/pandas/tools/plotting.py @@ -134,6 +134,32 @@ def random_color(column): else: raise ValueError("color_type must be either 'default' or 'random'") + if isinstance(colors, compat.string_types): + import matplotlib.colors + conv = matplotlib.colors.ColorConverter() + def _maybe_valid_colors(colors): + try: + [conv.to_rgba(c) for c in colors] + return True + except ValueError: + return False + + # check whether the string can be convertable to single color + maybe_single_color = _maybe_valid_colors([colors]) + # check whether each character can be convertable to colors + maybe_color_cycle = _maybe_valid_colors(list(colors)) + if maybe_single_color and maybe_color_cycle and len(colors) > 1: + msg = ("'{0}' can be parsed as both single color and " + "color cycle. Specify each color using a list " + "like ['{0}'] or {1}") + raise ValueError(msg.format(colors, list(colors))) + elif maybe_single_color: + colors = [colors] + else: + # ``colors`` is regarded as color cycle. + # mpl will raise error any of them is invalid + pass + if len(colors) != num_colors: multiple = num_colors//len(colors) - 1 mod = num_colors % len(colors)