From 5cc68126c05d99c3a1de1d3a066ba3f5507861b1 Mon Sep 17 00:00:00 2001 From: JustinZhengBC Date: Sat, 18 May 2019 21:19:52 -0700 Subject: [PATCH 1/4] fix colors parameter in DataFrame.boxplot --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/plotting/_core.py | 23 +++++++++++++++----- pandas/tests/plotting/test_boxplot_method.py | 18 +++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index b89646d465fff..8418f39e3fca4 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -424,7 +424,7 @@ Plotting - Fixed bug where :class:`api.extensions.ExtensionArray` could not be used in matplotlib plotting (:issue:`25587`) - Bug in an error message in :meth:`DataFrame.plot`. Improved the error message if non-numerics are passed to :meth:`DataFrame.plot` (:issue:`25481`) - Bug in incorrect ticklabel positions when plotting an index that are non-numeric / non-datetime (:issue:`7612` :issue:`15912` :issue:`22334`) -- +- Bug where :meth:`DataFrame.boxplot` would not accept a `color` parameter like `DataFrame.plot.box` (:issue:`26214`) - Groupby/Resample/Rolling diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py index 90297ecfa3415..caade7a1c7e06 100644 --- a/pandas/plotting/_core.py +++ b/pandas/plotting/_core.py @@ -2202,14 +2202,25 @@ def boxplot(data, column=None, by=None, ax=None, fontsize=None, 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 color 2 for medians and 0 for all else + result = _get_standard_colors(num_colors=3) + result = np.take(result, [0, 0, 2, 0]) + + colors = kwds.pop('color', None) + if colors and isinstance(colors, dict): + valid_keys = ['boxes', 'whiskers', 'medians', 'caps'] + for i in range(4): + if valid_keys[i] in colors: + result[i] = colors[valid_keys[i]] + + return result def maybe_color_bp(bp): - if 'color' not in kwds: - from matplotlib.artist import setp - setp(bp['boxes'], color=colors[0], alpha=1) - setp(bp['whiskers'], color=colors[0], alpha=1) - setp(bp['medians'], color=colors[2], alpha=1) + from matplotlib.artist import setp + 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] diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index de1ac0c293189..0cc01971dead8 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -160,6 +160,24 @@ def test_fontsize(self): self._check_ticks_props(df.boxplot("a", fontsize=16), xlabelsize=16, ylabelsize=16) + @pytest.mark.parametrize('dict_colors, expected', + [(dict(boxes='r', + whiskers='b', + medians='g', + caps='c'), + dict(boxes='r', + whiskers='b', + medians='g', + caps='c')), + (dict(boxes='r', invalid='b'), dict(boxes='r')), + (102, dict())]) + def test_color_kwd(self, dict_colors, expected): + # GH: 26214 + df = DataFrame(random.rand(10, 2)) + result = df.boxplot(color=dict_colors, return_type='dict') + for k, v in expected.items(): + assert result[k][0].get_color() == v + @td.skip_if_no_mpl class TestDataFrameGroupByPlots(TestPlotBase): From a886bd57a521f6e8d0655579fb97f1f29b15b7d8 Mon Sep 17 00:00:00 2001 From: JustinZhengBC Date: Thu, 13 Jun 2019 15:26:38 -0700 Subject: [PATCH 2/4] improve errors --- pandas/plotting/_matplotlib/boxplot.py | 19 ++++++++++++++----- pandas/tests/plotting/test_boxplot_method.py | 13 +++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index c64a30afbb0f3..778a3bba9438b 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -231,11 +231,20 @@ def _get_colors(): result = np.take(result, [0, 0, 2, 0]) colors = kwds.pop('color', None) - if colors and isinstance(colors, dict): - valid_keys = ['boxes', 'whiskers', 'medians', 'caps'] - for i in range(4): - if valid_keys[i] in colors: - result[i] = colors[valid_keys[i]] + if colors: + if isinstance(colors, dict): + 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: + raise ValueError("color should be a dict") return result diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 0cc01971dead8..48c7a86204d6d 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -169,8 +169,7 @@ def test_fontsize(self): whiskers='b', medians='g', caps='c')), - (dict(boxes='r', invalid='b'), dict(boxes='r')), - (102, dict())]) + (dict(boxes='r'), dict(boxes='r'))]) def test_color_kwd(self, dict_colors, expected): # GH: 26214 df = DataFrame(random.rand(10, 2)) @@ -178,6 +177,16 @@ def test_color_kwd(self, dict_colors, expected): 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'"), + (102, "color should be a dict")]) + 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): From 9433eb19b673b3a74727acf8eba5c093d8f1b016 Mon Sep 17 00:00:00 2001 From: JustinZhengBC Date: Mon, 26 Aug 2019 14:07:24 -0700 Subject: [PATCH 3/4] black formatting --- pandas/plotting/_matplotlib/boxplot.py | 9 ++--- pandas/tests/plotting/test_boxplot_method.py | 35 +++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 4aafd14b8b8c0..12a54a80d7f27 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -267,10 +267,11 @@ def _get_colors(): 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)) + raise ValueError( + "color dict contains invalid " + "key '{0}' " + "The key must be either {1}".format(key, valid_keys) + ) else: raise ValueError("color should be a dict") diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index d4c699481e4da..1f117065680cf 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -175,32 +175,35 @@ 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('dict_colors, 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'))]) + @pytest.mark.parametrize( + "dict_colors, 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")), + ], + ) def test_color_kwd(self, dict_colors, expected): # GH: 26214 df = DataFrame(random.rand(10, 2)) - result = df.boxplot(color=dict_colors, return_type='dict') + result = df.boxplot(color=dict_colors, 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'"), - (102, "color should be a dict")]) + @pytest.mark.parametrize( + "dict_colors, msg", + [ + (dict(boxes="r", invalid_key="r"), "invalid key 'invalid_key'"), + (102, "color should be a dict"), + ], + ) 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') + df.boxplot(color=dict_colors, return_type="dict") @td.skip_if_no_mpl From 041f325be006afbad5186b226acc6db89018defe Mon Sep 17 00:00:00 2001 From: JustinZhengBC Date: Mon, 26 Aug 2019 17:23:05 -0700 Subject: [PATCH 4/4] match current implementation of DataFrame.plot.box --- pandas/plotting/_matplotlib/boxplot.py | 7 ++++--- pandas/tests/plotting/test_boxplot_method.py | 12 +++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 12a54a80d7f27..99035013092cc 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -251,9 +251,10 @@ def boxplot( def _get_colors(): # num_colors=3 is required as method maybe_color_bp takes the colors # in positions 0 and 2. - # if colors not provided, use color 2 for medians and 0 for all else + # 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, 0]) + result = np.take(result, [0, 0, 2]) + result = np.append(result, "k") colors = kwds.pop("color", None) if colors: @@ -273,7 +274,7 @@ def _get_colors(): "The key must be either {1}".format(key, valid_keys) ) else: - raise ValueError("color should be a dict") + result.fill(colors) return result diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 1f117065680cf..116d924f5a596 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -176,28 +176,26 @@ def test_boxplot_numeric_data(self): assert [x.get_text() for x in ax.get_xticklabels()] == ["b", "c"] @pytest.mark.parametrize( - "dict_colors, expected", + "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, dict_colors, expected): + def test_color_kwd(self, colors_kwd, expected): # GH: 26214 df = DataFrame(random.rand(10, 2)) - result = df.boxplot(color=dict_colors, return_type="dict") + 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'"), - (102, "color should be a dict"), - ], + [(dict(boxes="r", invalid_key="r"), "invalid key 'invalid_key'")], ) def test_color_kwd_errors(self, dict_colors, msg): # GH: 26214