Repository: pandas-dev/pandas

Issue:
_dataframe.boxplot_ with _where_ and _by_ does not respect color keyword
### Bug report

**Bug summary**

The boxplot method on a dataframe which is using the "column, by" keywords does
not respect the _color_ keyword, and in fact crashes if it is present. This is not consistent with the documentation [here](http://pandas.pydata.org/pandas-docs/stable/user_guide/visualization.html#box-plots).

**Code for reproduction**

```python
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

def make_dummy_data():
    """ Return """
    df1 = pd.DataFrame(np.random.rand(10, 3), columns = ['x', 'y', 'z'])
    df2 = pd.DataFrame(2*np.random.rand(10, 3), columns = ['x', 'y', 'z'])
    return df1, df2

def comparative_results():
    """ stuff """

    df1, df2 = make_dummy_data()

    def draw_plot(ax, data, edge_color, fill_color=None):
        """ Controls details of color"""
        colors = dict(boxes=edge_color, whiskers=edge_color, medians=edge_color, caps=edge_color)
        ax = data.boxplot(column=['x'], by=['z'], showfliers=False, ax=ax, color=colors)
        return ax

    ax = None
    ax = draw_plot(ax, df1, 'k')
    ax = draw_plot(ax, df2, 'r')
    ax.set_title('dummy to expose bug')
    plt.show()


if __name__ == "__main__":
    comparative_results() 
```

**Actual outcome**

<!--The output produced by the above code, which may be a screenshot, console output, etc.-->

```
Traceback (most recent call last):
  File "/Users/BNL28/Code/DataPerformance/bug_report.py", line 33, in <module>
    comparative_results()
  File "/Users/BNL28/Code/DataPerformance/bug_report.py", line 26, in comparative_results
    ax = draw_plot(ax, df1, 'k')
  File "/Users/BNL28/Code/DataPerformance/bug_report.py", line 22, in draw_plot
    ax = data.boxplot(column=['x'], by=['z'], showfliers=False, ax=ax, color=colors)
  File "/Users/BNL28/anaconda3/lib/python3.6/site-packages/pandas/plotting/_core.py", line 2254, in boxplot_frame
    return_type=return_type, **kwds)
  File "/Users/BNL28/anaconda3/lib/python3.6/site-packages/pandas/plotting/_core.py", line 2223, in boxplot
    return_type=return_type)
  File "/Users/BNL28/anaconda3/lib/python3.6/site-packages/pandas/plotting/_core.py", line 2683, in _grouped_plot_by_column
    re_plotf = plotf(keys, values, ax, **kwargs)
  File "/Users/BNL28/anaconda3/lib/python3.6/site-packages/pandas/plotting/_core.py", line 2191, in plot_group
    bp = ax.boxplot(values, **kwds)
  File "/Users/BNL28/anaconda3/lib/python3.6/site-packages/matplotlib/__init__.py", line 1810, in inner
    return func(ax, *args, **kwargs)
TypeError: boxplot() got an unexpected keyword argument 'color'

Process finished with exit code 1 
```

**Expected outcome**

Expect two sets of box plots, one coloured black, and one coloured red. Code runs ok with no color keyword, but the boxes are indistinguishable without colour control.

**Environment**

  * Operating system: OSX
  * Matplotlib version: 3.0.2
  * Matplotlib backend (`print(matplotlib.get_backend())`):
  * Python version: Python 3.6.8 |Anaconda, Inc.| (default, Dec 29 2018, 19:04:46) 
  * Pandas version 0.24.2




Discussion:
Sorry about the title, and not noticing the mistake in the matplotlib backend version: 3.0.2

Resolving patch:
diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py
--- a/pandas/plotting/_matplotlib/boxplot.py
+++ b/pandas/plotting/_matplotlib/boxplot.py
@@ -4,6 +4,7 @@
 from matplotlib.artist import setp
 import numpy as np
 
+from pandas.core.dtypes.common import is_dict_like
 from pandas.core.dtypes.generic import ABCSeries
 from pandas.core.dtypes.missing import remove_na_arraylike
 
@@ -250,13 +251,38 @@ def boxplot(
     def _get_colors():
         #  num_colors=3 is required as method maybe_color_bp takes the colors
         #  in positions 0 and 2.
-        return _get_standard_colors(color=kwds.get("color"), num_colors=3)
+        #  if colors not provided, use same defaults as DataFrame.plot.box
+        result = _get_standard_colors(num_colors=3)
+        result = np.take(result, [0, 0, 2])
+        result = np.append(result, "k")
+
+        colors = kwds.pop("color", None)
+        if colors:
+            if is_dict_like(colors):
+                # replace colors in result array with user-specified colors
+                # taken from the colors dict parameter
+                # "boxes" value placed in position 0, "whiskers" in 1, etc.
+                valid_keys = ["boxes", "whiskers", "medians", "caps"]
+                key_to_index = dict(zip(valid_keys, range(4)))
+                for key, value in colors.items():
+                    if key in valid_keys:
+                        result[key_to_index[key]] = value
+                    else:
+                        raise ValueError(
+                            "color dict contains invalid "
+                            "key '{0}' "
+                            "The key must be either {1}".format(key, valid_keys)
+                        )
+            else:
+                result.fill(colors)
+
+        return result
 
     def maybe_color_bp(bp):
-        if "color" not in kwds:
-            setp(bp["boxes"], color=colors[0], alpha=1)
-            setp(bp["whiskers"], color=colors[0], alpha=1)
-            setp(bp["medians"], color=colors[2], alpha=1)
+        setp(bp["boxes"], color=colors[0], alpha=1)
+        setp(bp["whiskers"], color=colors[1], alpha=1)
+        setp(bp["medians"], color=colors[2], alpha=1)
+        setp(bp["caps"], color=colors[3], alpha=1)
 
     def plot_group(keys, values, ax):
         keys = [pprint_thing(x) for x in keys]


Test patch:
diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py
--- a/pandas/tests/plotting/test_boxplot_method.py
+++ b/pandas/tests/plotting/test_boxplot_method.py
@@ -175,6 +175,34 @@ def test_boxplot_numeric_data(self):
         ax = df.plot(kind="box")
         assert [x.get_text() for x in ax.get_xticklabels()] == ["b", "c"]
 
+    @pytest.mark.parametrize(
+        "colors_kwd, expected",
+        [
+            (
+                dict(boxes="r", whiskers="b", medians="g", caps="c"),
+                dict(boxes="r", whiskers="b", medians="g", caps="c"),
+            ),
+            (dict(boxes="r"), dict(boxes="r")),
+            ("r", dict(boxes="r", whiskers="r", medians="r", caps="r")),
+        ],
+    )
+    def test_color_kwd(self, colors_kwd, expected):
+        # GH: 26214
+        df = DataFrame(random.rand(10, 2))
+        result = df.boxplot(color=colors_kwd, return_type="dict")
+        for k, v in expected.items():
+            assert result[k][0].get_color() == v
+
+    @pytest.mark.parametrize(
+        "dict_colors, msg",
+        [(dict(boxes="r", invalid_key="r"), "invalid key 'invalid_key'")],
+    )
+    def test_color_kwd_errors(self, dict_colors, msg):
+        # GH: 26214
+        df = DataFrame(random.rand(10, 2))
+        with pytest.raises(ValueError, match=msg):
+            df.boxplot(color=dict_colors, return_type="dict")
+
 
 @td.skip_if_no_mpl
 class TestDataFrameGroupByPlots(TestPlotBase):