From 7e461a18d9f6928132afec6f48ce968b3e989ba6 Mon Sep 17 00:00:00 2001 From: Kaiqi Dong Date: Mon, 3 Dec 2018 17:43:52 +0100 Subject: [PATCH 01/10] remove \n from docstring --- pandas/core/arrays/datetimes.py | 26 +++++++++++++------------- pandas/core/arrays/timedeltas.py | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index cfe3afcf3730a..b3df505d56d78 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -82,7 +82,7 @@ def f(self): return result f.__name__ = name - f.__doc__ = docstring + f.__doc__ = "\n{}\n".format(docstring) return property(f) @@ -1072,19 +1072,19 @@ def date(self): return tslib.ints_to_pydatetime(timestamps, box="date") - year = _field_accessor('year', 'Y', "\n The year of the datetime\n") + year = _field_accessor('year', 'Y', "The year of the datetime") month = _field_accessor('month', 'M', - "\n The month as January=1, December=12 \n") - day = _field_accessor('day', 'D', "\nThe days of the datetime\n") - hour = _field_accessor('hour', 'h', "\nThe hours of the datetime\n") - minute = _field_accessor('minute', 'm', "\nThe minutes of the datetime\n") - second = _field_accessor('second', 's', "\nThe seconds of the datetime\n") + "The month as January=1, December=12") + day = _field_accessor('day', 'D', "The days of the datetime") + hour = _field_accessor('hour', 'h', "The hours of the datetime") + minute = _field_accessor('minute', 'm', "The minutes of the datetime") + second = _field_accessor('second', 's', "The seconds of the datetime") microsecond = _field_accessor('microsecond', 'us', - "\nThe microseconds of the datetime\n") + "The microseconds of the datetime") nanosecond = _field_accessor('nanosecond', 'ns', - "\nThe nanoseconds of the datetime\n") + "The nanoseconds of the datetime") weekofyear = _field_accessor('weekofyear', 'woy', - "\nThe week ordinal of the year\n") + "The week ordinal of the year") week = weekofyear _dayofweek_doc = """ The day of the week with Monday=0, Sunday=6. @@ -1129,12 +1129,12 @@ def date(self): "The name of day in a week (ex: Friday)\n\n.. deprecated:: 0.23.0") dayofyear = _field_accessor('dayofyear', 'doy', - "\nThe ordinal day of the year\n") - quarter = _field_accessor('quarter', 'q', "\nThe quarter of the date\n") + "The ordinal day of the year") + quarter = _field_accessor('quarter', 'q', "The quarter of the date") days_in_month = _field_accessor( 'days_in_month', 'dim', - "\nThe number of days in the month\n") + "The number of days in the month") daysinmonth = days_in_month _is_month_doc = """ Indicates whether the date is the {first_or_last} day of the month. diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 830283d31a929..4afc9f5483c2a 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -59,7 +59,7 @@ def f(self): return result f.__name__ = name - f.__doc__ = docstring + f.__doc__ = "\n{}\n".format(docstring) return property(f) @@ -684,16 +684,16 @@ def to_pytimedelta(self): return tslibs.ints_to_pytimedelta(self.asi8) days = _field_accessor("days", "days", - "\nNumber of days for each element.\n") + "Number of days for each element.") seconds = _field_accessor("seconds", "seconds", - "\nNumber of seconds (>= 0 and less than 1 day) " - "for each element.\n") + "Number of seconds (>= 0 and less than 1 day) " + "for each element.") microseconds = _field_accessor("microseconds", "microseconds", - "\nNumber of microseconds (>= 0 and less " - "than 1 second) for each element.\n") + "Number of microseconds (>= 0 and less " + "than 1 second) for each element.") nanoseconds = _field_accessor("nanoseconds", "nanoseconds", - "\nNumber of nanoseconds (>= 0 and less " - "than 1 microsecond) for each element.\n") + "Number of nanoseconds (>= 0 and less " + "than 1 microsecond) for each element.") @property def components(self): From dea38f24c0067ae3fe9484b837c9649714213bba Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Tue, 14 Jan 2020 21:26:31 +0100 Subject: [PATCH 02/10] fix issue 17038 --- pandas/core/reshape/pivot.py | 4 +++- pandas/tests/reshape/test_pivot.py | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index b443ba142369c..9743d90f4dd04 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -117,7 +117,9 @@ def pivot_table( agged[v] = maybe_downcast_to_dtype(agged[v], data[v].dtype) table = agged - if table.index.nlevels > 1: + + # GH 17038, this check should only happen if index is specified + if table.index.nlevels > 1 and index: # Related GH #17123 # If index_names are integers, determine whether the integers refer # to the level position or name. diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index 743fc50c87e96..46a05123c9fdd 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -896,12 +896,6 @@ def _check_output( totals = table.loc[("All", ""), value_col] assert totals == self.data[value_col].mean() - # no rows - rtable = self.data.pivot_table( - columns=["AA", "BB"], margins=True, aggfunc=np.mean - ) - assert isinstance(rtable, Series) - table = self.data.pivot_table(index=["AA", "BB"], margins=True, aggfunc="mean") for item in ["DD", "EE", "FF"]: totals = table.loc[("All", ""), item] @@ -972,6 +966,20 @@ def test_pivot_integer_columns(self): tm.assert_frame_equal(table, table2, check_names=False) + @pytest.mark.parametrize("cols", [(1, 2), ("a", "b"), (1, "b"), ("a", 1)]) + def test_pivot_table_multiindex_only(self, cols): + # GH 17038 + df2 = DataFrame({cols[0]: [1, 2, 3], cols[1]: [1, 2, 3], "v": [4, 5, 6]}) + + result = df2.pivot_table(values="v", columns=cols) + expected = DataFrame( + [[4, 5, 6]], + columns=MultiIndex.from_tuples([(1, 1), (2, 2), (3, 3)], names=cols), + index=Index(["v"]), + ) + + tm.assert_frame_equal(result, expected) + def test_pivot_no_level_overlap(self): # GH #1181 From cd9e7ac3f31ffaf95cd628863df911dea9fa1248 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Tue, 14 Jan 2020 21:29:43 +0100 Subject: [PATCH 03/10] revert change --- pandas/core/reshape/pivot.py | 3 +-- pandas/tests/reshape/test_pivot.py | 20 ++++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index 9743d90f4dd04..a7cdbb0da7a4e 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -118,8 +118,7 @@ def pivot_table( table = agged - # GH 17038, this check should only happen if index is specified - if table.index.nlevels > 1 and index: + if table.index.nlevels > 1: # Related GH #17123 # If index_names are integers, determine whether the integers refer # to the level position or name. diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index 46a05123c9fdd..743fc50c87e96 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -896,6 +896,12 @@ def _check_output( totals = table.loc[("All", ""), value_col] assert totals == self.data[value_col].mean() + # no rows + rtable = self.data.pivot_table( + columns=["AA", "BB"], margins=True, aggfunc=np.mean + ) + assert isinstance(rtable, Series) + table = self.data.pivot_table(index=["AA", "BB"], margins=True, aggfunc="mean") for item in ["DD", "EE", "FF"]: totals = table.loc[("All", ""), item] @@ -966,20 +972,6 @@ def test_pivot_integer_columns(self): tm.assert_frame_equal(table, table2, check_names=False) - @pytest.mark.parametrize("cols", [(1, 2), ("a", "b"), (1, "b"), ("a", 1)]) - def test_pivot_table_multiindex_only(self, cols): - # GH 17038 - df2 = DataFrame({cols[0]: [1, 2, 3], cols[1]: [1, 2, 3], "v": [4, 5, 6]}) - - result = df2.pivot_table(values="v", columns=cols) - expected = DataFrame( - [[4, 5, 6]], - columns=MultiIndex.from_tuples([(1, 1), (2, 2), (3, 3)], names=cols), - index=Index(["v"]), - ) - - tm.assert_frame_equal(result, expected) - def test_pivot_no_level_overlap(self): # GH #1181 From e5e912be0f596943067a7df812442764d311a086 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Tue, 14 Jan 2020 21:30:16 +0100 Subject: [PATCH 04/10] revert change --- pandas/core/reshape/pivot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index a7cdbb0da7a4e..b443ba142369c 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -117,7 +117,6 @@ def pivot_table( agged[v] = maybe_downcast_to_dtype(agged[v], data[v].dtype) table = agged - if table.index.nlevels > 1: # Related GH #17123 # If index_names are integers, determine whether the integers refer From 2d62d921a5145aff375bf75f5bf1562dba4eaa25 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Thu, 23 Jan 2020 22:28:12 +0100 Subject: [PATCH 05/10] fix issue 30346 --- doc/source/whatsnew/v1.1.0.rst | 2 ++ pandas/plotting/_matplotlib/boxplot.py | 32 ++++++++++++++------ pandas/tests/plotting/test_boxplot_method.py | 18 +++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index c8e811ce82b1f..a3a2c736144a2 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -164,6 +164,8 @@ Plotting - - +- Bug in :meth:`DataFrame.boxplot` and :meth:`DataFrame.plot.boxplot` lost color attributes of ``medianprops``, ``whiskerprops``, ``capprops`` and ``medianprops`` (:issue:`30346`) + Groupby/resample/rolling ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index deeeb0016142c..e36696dc23a87 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -107,10 +107,16 @@ def maybe_color_bp(self, bp): medians = self.color or self._medians_c caps = self.color or self._caps_c - setp(bp["boxes"], color=boxes, alpha=1) - setp(bp["whiskers"], color=whiskers, alpha=1) - setp(bp["medians"], color=medians, alpha=1) - setp(bp["caps"], color=caps, alpha=1) + # GH 30346, when users specifying those arguments explicitly, our defaults + # for these four kwargs should be overridden; if not, use Pandas settings + if not self.kwds.get("boxprops"): + setp(bp["boxes"], color=boxes, alpha=1) + if not self.kwds.get("whiskerprops"): + setp(bp["whiskers"], color=whiskers, alpha=1) + if not self.kwds.get("medianprops"): + setp(bp["medians"], color=medians, alpha=1) + if not self.kwds.get("capprops"): + setp(bp["caps"], color=caps, alpha=1) def _make_plot(self): if self.subplots: @@ -275,11 +281,17 @@ def _get_colors(): return result - def maybe_color_bp(bp): - 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 maybe_color_bp(bp, **kwds): + # GH 30346, when users specifying those arguments explicitly, our defaults + # for these four kwargs should be overridden; if not, use Pandas settings + if not kwds.get("boxprops"): + setp(bp["boxes"], color=colors[0], alpha=1) + if not kwds.get("whiskerprops"): + setp(bp["whiskers"], color=colors[1], alpha=1) + if not kwds.get("medianprops"): + setp(bp["medians"], color=colors[2], alpha=1) + if not kwds.get("capprops"): + setp(bp["caps"], color=colors[3], alpha=1) def plot_group(keys, values, ax): keys = [pprint_thing(x) for x in keys] @@ -291,7 +303,7 @@ def plot_group(keys, values, ax): ax.set_xticklabels(keys, rotation=rot) else: ax.set_yticklabels(keys, rotation=rot) - maybe_color_bp(bp) + maybe_color_bp(bp, **kwds) # Return axes in multiplot case, maybe revisit later # 985 if return_type == "dict": diff --git a/pandas/tests/plotting/test_boxplot_method.py b/pandas/tests/plotting/test_boxplot_method.py index 8ee279f0e1f38..5ff5abc60c1c6 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -203,6 +203,24 @@ def test_color_kwd_errors(self, dict_colors, msg): with pytest.raises(ValueError, match=msg): df.boxplot(color=dict_colors, return_type="dict") + @pytest.mark.parametrize( + "props, expected", + [ + ("boxprops", "boxes"), + ("whiskerprops", "whiskers"), + ("capprops", "caps"), + ("medianprops", "medians"), + ], + ) + def test_specified_props_kwd(self, props, expected): + # GH 30346 + df = DataFrame({k: np.random.random(100) for k in "ABC"}) + kwd = {props: dict(color="C1", lw=5)} + result = df.boxplot(return_type="dict", **kwd) + + assert result[expected][0].get_color() == "C1" + assert result[expected][0].get_lw() == 5 + @td.skip_if_no_mpl class TestDataFrameGroupByPlots(TestPlotBase): From 98693679d1147eb1883ae2812212914121dd2c41 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Thu, 23 Jan 2020 22:35:00 +0100 Subject: [PATCH 06/10] add test --- pandas/tests/plotting/test_frame.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index 1c429bafa9a19..a16914c4877cf 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -2352,6 +2352,24 @@ def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c="k", fliers_c=None): # Color contains invalid key results in ValueError df.plot.box(color=dict(boxes="red", xxxx="blue")) + @pytest.mark.parametrize( + "props, expected", + [ + ("boxprops", "boxes"), + ("whiskerprops", "whiskers"), + ("capprops", "caps"), + ("medianprops", "medians"), + ], + ) + def test_specified_props_kwd_plot_box(self, props, expected): + # GH 30346 + df = DataFrame({k: np.random.random(100) for k in "ABC"}) + kwd = {props: dict(color="C1", lw=5)} + result = df.plot.box(return_type="dict", **kwd) + + assert result[expected][0].get_color() == "C1" + assert result[expected][0].get_lw() == 5 + def test_default_color_cycle(self): import matplotlib.pyplot as plt import cycler From 0d33f57538045cb2cc615d7718a7bcc6e77778fe Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Fri, 24 Jan 2020 09:30:43 +0100 Subject: [PATCH 07/10] more robust --- pandas/plotting/_matplotlib/boxplot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index e36696dc23a87..494e19c03e4ef 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -111,12 +111,20 @@ def maybe_color_bp(self, bp): # for these four kwargs should be overridden; if not, use Pandas settings if not self.kwds.get("boxprops"): setp(bp["boxes"], color=boxes, alpha=1) + else: + setp(bp["boxes"], **self.kwds["boxprops"]) if not self.kwds.get("whiskerprops"): setp(bp["whiskers"], color=whiskers, alpha=1) + else: + setp(bp["whiskers"], **self.kwds["whiskerprops"]) if not self.kwds.get("medianprops"): setp(bp["medians"], color=medians, alpha=1) + else: + setp(bp["medians"], **self.kwds["medianprops"]) if not self.kwds.get("capprops"): setp(bp["caps"], color=caps, alpha=1) + else: + setp(bp["caps"], **self.kwds["capprops"]) def _make_plot(self): if self.subplots: From ff0081ccd33adb484e2fd53b5f1a2d8d5b025755 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Fri, 24 Jan 2020 09:33:43 +0100 Subject: [PATCH 08/10] robust --- pandas/plotting/_matplotlib/boxplot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 494e19c03e4ef..d3ebd5318ded3 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -294,12 +294,20 @@ def maybe_color_bp(bp, **kwds): # for these four kwargs should be overridden; if not, use Pandas settings if not kwds.get("boxprops"): setp(bp["boxes"], color=colors[0], alpha=1) + else: + setp(bp["boxes"], **kwds["boxprops"]) if not kwds.get("whiskerprops"): setp(bp["whiskers"], color=colors[1], alpha=1) + else: + setp(bp["whiskers"], **kwds["whiskerprops"]) if not kwds.get("medianprops"): setp(bp["medians"], color=colors[2], alpha=1) + else: + setp(bp["medians"], **kwds["medianprops"]) if not kwds.get("capprops"): setp(bp["caps"], color=colors[3], alpha=1) + else: + setp(bp["caps"], **kwds["capprops"]) def plot_group(keys, values, ax): keys = [pprint_thing(x) for x in keys] From b81ad5faf9692fb4aa45ad172f976ed2c62de987 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Fri, 24 Jan 2020 09:58:25 +0100 Subject: [PATCH 09/10] fix test --- pandas/plotting/_matplotlib/boxplot.py | 16 ---------------- pandas/tests/plotting/test_boxplot_method.py | 3 +-- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index d3ebd5318ded3..e36696dc23a87 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -111,20 +111,12 @@ def maybe_color_bp(self, bp): # for these four kwargs should be overridden; if not, use Pandas settings if not self.kwds.get("boxprops"): setp(bp["boxes"], color=boxes, alpha=1) - else: - setp(bp["boxes"], **self.kwds["boxprops"]) if not self.kwds.get("whiskerprops"): setp(bp["whiskers"], color=whiskers, alpha=1) - else: - setp(bp["whiskers"], **self.kwds["whiskerprops"]) if not self.kwds.get("medianprops"): setp(bp["medians"], color=medians, alpha=1) - else: - setp(bp["medians"], **self.kwds["medianprops"]) if not self.kwds.get("capprops"): setp(bp["caps"], color=caps, alpha=1) - else: - setp(bp["caps"], **self.kwds["capprops"]) def _make_plot(self): if self.subplots: @@ -294,20 +286,12 @@ def maybe_color_bp(bp, **kwds): # for these four kwargs should be overridden; if not, use Pandas settings if not kwds.get("boxprops"): setp(bp["boxes"], color=colors[0], alpha=1) - else: - setp(bp["boxes"], **kwds["boxprops"]) if not kwds.get("whiskerprops"): setp(bp["whiskers"], color=colors[1], alpha=1) - else: - setp(bp["whiskers"], **kwds["whiskerprops"]) if not kwds.get("medianprops"): setp(bp["medians"], color=colors[2], alpha=1) - else: - setp(bp["medians"], **kwds["medianprops"]) if not kwds.get("capprops"): setp(bp["caps"], color=colors[3], alpha=1) - else: - setp(bp["caps"], **kwds["capprops"]) 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 5ff5abc60c1c6..b84fcffe26991 100644 --- a/pandas/tests/plotting/test_boxplot_method.py +++ b/pandas/tests/plotting/test_boxplot_method.py @@ -215,11 +215,10 @@ def test_color_kwd_errors(self, dict_colors, msg): def test_specified_props_kwd(self, props, expected): # GH 30346 df = DataFrame({k: np.random.random(100) for k in "ABC"}) - kwd = {props: dict(color="C1", lw=5)} + kwd = {props: dict(color="C1")} result = df.boxplot(return_type="dict", **kwd) assert result[expected][0].get_color() == "C1" - assert result[expected][0].get_lw() == 5 @td.skip_if_no_mpl From d58884863a0c97dc4b199bf495021e0274244940 Mon Sep 17 00:00:00 2001 From: Kaiqi Date: Fri, 24 Jan 2020 09:59:54 +0100 Subject: [PATCH 10/10] fix test --- pandas/tests/plotting/test_frame.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py index a16914c4877cf..ffbd135466709 100644 --- a/pandas/tests/plotting/test_frame.py +++ b/pandas/tests/plotting/test_frame.py @@ -2364,11 +2364,10 @@ def _check_colors(bp, box_c, whiskers_c, medians_c, caps_c="k", fliers_c=None): def test_specified_props_kwd_plot_box(self, props, expected): # GH 30346 df = DataFrame({k: np.random.random(100) for k in "ABC"}) - kwd = {props: dict(color="C1", lw=5)} + kwd = {props: dict(color="C1")} result = df.plot.box(return_type="dict", **kwd) assert result[expected][0].get_color() == "C1" - assert result[expected][0].get_lw() == 5 def test_default_color_cycle(self): import matplotlib.pyplot as plt