Repository: pandas-dev/pandas
Pull request: VIS: Allow 'C0'-like plotting for plotting colors #15516
 - [x] closes #15516 
 - [x] tests added / passed
 - [x] passes ``git diff upstream/master --name-only -- '*.py' | flake8 --diff``
 - [x] whatsnew entry

Defined special case for 'CN' cases where N is 0 to 9. There may be custom color cycles that can also be parsed as a single color so the ValueError message is left intact (unless this is false).

```
running: pytest --skip-slow --skip-network pandas
============================= test session starts ==============================
platform linux2 -- Python 2.7.13, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /home/sfoo/pandas, inifile: setup.cfg
collected 11894 items / 3 skipped 

10234 passed, 1586 skipped, 77 xfailed, 2084 pytest-warnings in 486.80 seconds 
```

Problem statement:
VIS: plot method cannot handle new 'C0'-like colors (matplotlib 2.0)
matplotlib 2.0 introduced a few 2-character color abbreviations: C0, C1, C2, ... to refer to the colors of the current color cycle 
(very handy! this way you can refer to a color of the color cycle without hardcoding it, and use it as a shortcut for `color='tab:blue'` if you want the new blue instead of the ugly `color='b'`)

But, it seems pandas cannot handle this at the moment, as it tries to interpret it as multiple colors:

```
df = pd.DataFrame(np.random.randn(10,3), columns=['a', 'b', 'c'])
df.plot(color='C0')
```

gives `ValueError: 'C0' can be parsed as both single color and color cycle. Specify each color using a list like ['C0'] or ['C', '0']`

I know the error message already gives the solution (using `color=['C0']`), but I think it would be nice to special case those to have to working by default.

Hints from issue discussion:
This one is pretty old, but I'd love to revive it. I'm not sure how tests will cover this change. Is it alright if I give it a try anyway?
sure!

try adding. test with the code as above (u will check this plot works)

Commit message: Added case color='' and support for mpl < 2.0

Patch to review:
diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt
index fa24c973a7549..f390e7eeb98bc 100644
--- a/doc/source/whatsnew/v0.20.0.txt
+++ b/doc/source/whatsnew/v0.20.0.txt
@@ -177,6 +177,7 @@ Other enhancements
 - The ``usecols`` argument in ``pd.read_csv`` now accepts a callable function as a value  (:issue:`14154`)
 - The ``skiprows`` argument in ``pd.read_csv`` now accepts a callable function as a value  (:issue:`10882`)
 - ``pd.DataFrame.plot`` now prints a title above each subplot if ``suplots=True`` and ``title`` is a list of strings (:issue:`14753`)
+- ``pd.DataFrame.plot`` can pass `matplotlib 2.0 default color cycle as a single string as color parameter <http://matplotlib.org/2.0.0/users/colors.html#cn-color-selection>`__. (:issue:`15516`)
 - ``pd.Series.interpolate`` now supports timedelta as an index type with ``method='time'`` (:issue:`6424`)
 - ``Timedelta.isoformat`` method added for formatting Timedeltas as an `ISO 8601 duration`_. See the :ref:`Timedelta docs <timedeltas.isoformat>` (:issue:`15136`)
 - ``pandas.io.json.json_normalize()`` gained the option ``errors='ignore'|'raise'``; the default is ``errors='raise'`` which is backward compatible. (:issue:`14583`)
diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py
index 48af366f24ea4..5659532e2811f 100644
--- a/pandas/tests/plotting/test_frame.py
+++ b/pandas/tests/plotting/test_frame.py
@@ -141,6 +141,16 @@ def test_plot(self):
             result = ax.get_axes()  # deprecated
         self.assertIs(result, axes[0])
 
+    # GH 15516
+    def test_mpl2_color_cycle_str(self):
+        # test C[0-9] as string
+        colors = ['C' + str(x) for x in range(10)]
+        df = DataFrame(randn(10, 3), columns=['a', 'b', 'c'])
+        for c in colors:
+            _check_plot_works(df.plot, color=c)
+        with tm.assertRaises(ValueError):
+            df.plot(color='')
+
     def test_color_and_style_arguments(self):
         df = DataFrame({'x': [1, 2], 'y': [3, 4]})
         # passing both 'color' and 'style' arguments should be allowed
@@ -1600,6 +1610,10 @@ def test_line_colors(self):
         self._check_colors(ax.get_lines(), linecolors=['red'] * 5)
         tm.close()
 
+        ax = df.plot(color='C0')
+        self._check_colors(ax.get_lines(), linecolors=['C0'] * 5)
+        tm.close()
+
         # GH 10299
         custom_colors = ['#FF0000', '#0000FF', '#FFFF00', '#000000', '#FFFFFF']
         ax = df.plot(color=custom_colors)
diff --git a/pandas/tools/plotting.py b/pandas/tools/plotting.py
index d46c38c117445..e696a2d8fbd43 100644
--- a/pandas/tools/plotting.py
+++ b/pandas/tools/plotting.py
@@ -217,10 +217,18 @@ def _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)))
+            # Special case for single str 'CN' match and convert to hex
+            # for supporting matplotlib < 2.0.0
+            if re.match(r'C[0-9]', colors) and len(colors) == 2:
+                prop_cycler = plt.rcParams['axes.prop_cycle']
+                hex_color = prop_cycler.by_key().get('color', ['k'])
+                colors = [hex_color[int(colors[1])]]
+            else:
+                # non-default cycle may be parsed as single or many colors
+                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:
@@ -229,7 +237,10 @@ def _maybe_valid_colors(colors):
             pass
 
     if len(colors) != num_colors:
-        multiple = num_colors // len(colors) - 1
+        try:
+            multiple = num_colors // len(colors) - 1
+        except ZeroDivisionError:
+            raise ValueError("Invalid color argument: ''")
         mod = num_colors % len(colors)
 
         colors += multiple * colors


Code review comments:
diff_hunk: @@ -217,10 +217,18 @@ def _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)))
+            # Special case for single str 'CN' match and convert to hex
+            # for supporting matplotlib < 2.0.0
+            if re.match(r'C[0-9]', colors) and len(colors) == 2:
+                prop_cycler = plt.rcParams['axes.prop_cycle']
+                hex_color = prop_cycler.by_key().get('color', ['k'])
path: pandas/tools/plotting.py
text: @user1:
I think this prop_cycler is also only supported for matplotlib versions >= 1.5?

See here how it is handled at another location: https://github.com/GuessWhoSamFoo/pandas/blob/e2497222ecf64e07f4bd7873a1b860021b0b605c/pandas/tools/plotting.py#L185-L190