From 6e506070a79dc3bf83045bf2cb234cb868663fef Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 26 Jan 2021 14:02:57 +0100 Subject: [PATCH 1/7] TST: edited and expanded for latest issue --- pandas/tests/io/formats/test_style.py | 33 ++++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/pandas/tests/io/formats/test_style.py b/pandas/tests/io/formats/test_style.py index 51810786395b5..80c06df18fb2a 100644 --- a/pandas/tests/io/formats/test_style.py +++ b/pandas/tests/io/formats/test_style.py @@ -355,27 +355,22 @@ def f(x): def test_applymap_subset_multiindex(self): # GH 19861 - # Smoke test for applymap - def color_negative_red(val): - """ - Takes a scalar and returns a string with - the css property `'color: red'` for negative - strings, black otherwise. - """ - color = "red" if val < 0 else "black" - return f"color: {color}" + # edited for GH 33562 + idx = pd.MultiIndex.from_product([["a", "b"], [1, 2]]) + col = pd.MultiIndex.from_product([["x", "y"], ["A", "B"]]) + df = DataFrame(np.random.rand(4, 4), columns=col, index=idx) - dic = { - ("a", "d"): [-1.12, 2.11], - ("a", "c"): [2.78, -2.88], - ("b", "c"): [-3.99, 3.77], - ("b", "d"): [4.21, -1.22], - } - - idx = pd.IndexSlice - df = DataFrame(dic, index=[0, 1]) + slices = [ + pd.IndexSlice[:, pd.IndexSlice["x", "A"]], + pd.IndexSlice[:, pd.IndexSlice[:, "A"]], + pd.IndexSlice[pd.IndexSlice["a", 1], :], + pd.IndexSlice[pd.IndexSlice[:, 1], :], + pd.IndexSlice[:, ("x", "A")], + pd.IndexSlice[("a", 1), :], + ] - (df.style.applymap(color_negative_red, subset=idx[:, idx["b", "d"]]).render()) + for slice in slices: + df.style.applymap(lambda x: "color: red;", subset=slice) def test_applymap_subset_multiindex_code(self): # https://github.com/pandas-dev/pandas/issues/25858 From cc5c719e6ddfd933a05e7770c54383c769275be7 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 26 Jan 2021 21:35:00 +0100 Subject: [PATCH 2/7] BUG: subset non-reducer --- pandas/core/indexing.py | 11 ++++-- .../tests/indexing/multiindex/test_slice.py | 35 +++++++++++++++++++ pandas/tests/io/formats/test_style.py | 4 ++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 998a34ce834ea..12f4407c67dac 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2387,9 +2387,14 @@ def pred(part) -> bool: """ # true when slice does *not* reduce, False when part is a tuple, # i.e. MultiIndex slice - return (isinstance(part, slice) or is_list_like(part)) and not isinstance( - part, tuple - ) + if isinstance(part, tuple): + # check for sub-slice: + for subpart in part: + if isinstance(subpart, slice) or is_list_like(subpart): + return True + return False + else: + return isinstance(part, slice) or is_list_like(part) if not is_list_like(slice_): if not isinstance(slice_, slice): diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py index b60135a802b8e..f34426dd97df1 100644 --- a/pandas/tests/indexing/multiindex/test_slice.py +++ b/pandas/tests/indexing/multiindex/test_slice.py @@ -780,6 +780,41 @@ def test_non_reducing_slice_on_multiindex(self): expected = DataFrame({("b", "d"): [4, 1]}) tm.assert_frame_equal(result, expected) + @pytest.mark.parametrize( + "slice_", + [ + pd.IndexSlice[:, :], + # check cols + pd.IndexSlice[:, pd.IndexSlice[["a"]]], # inferred deeper need list + pd.IndexSlice[:, pd.IndexSlice[["a"], ["c"]]], # inferred deeper need list + pd.IndexSlice[:, pd.IndexSlice["a", "c", :]], + pd.IndexSlice[:, pd.IndexSlice["a", :, "e"]], + pd.IndexSlice[:, pd.IndexSlice[:, "c", "e"]], + pd.IndexSlice[:, pd.IndexSlice["a", ["c", "d"], :]], # check list + pd.IndexSlice[:, pd.IndexSlice["a", ["c", "d", "-"], :]], # allow missing + pd.IndexSlice[:, pd.IndexSlice["a", ["c", "d", "-"], "e"]], # no slice + # check rows + pd.IndexSlice[pd.IndexSlice[["U"]], :], # inferred deeper need list + pd.IndexSlice[pd.IndexSlice[["U"], ["W"]], :], # inferred deeper need list + pd.IndexSlice[pd.IndexSlice["U", "W", :], :], + pd.IndexSlice[pd.IndexSlice["U", :, "Y"], :], + pd.IndexSlice[pd.IndexSlice[:, "W", "Y"], :], + pd.IndexSlice[pd.IndexSlice[:, "W", ["Y", "Z"]], :], # check list + pd.IndexSlice[pd.IndexSlice[:, "W", ["Y", "Z", "-"]], :], # allow missing + pd.IndexSlice[pd.IndexSlice["U", "W", ["Y", "Z", "-"]], :], # no slice + # check simultaneous + pd.IndexSlice[pd.IndexSlice[:, "W", "Y"], pd.IndexSlice["a", "c", :]], + ], + ) + def test_non_reducing_multi_slice_on_multiindex(self, slice_): + cols = pd.MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]) + idxs = pd.MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]]) + df = DataFrame(np.arange(64).reshape(8, 8), columns=cols, index=idxs) + + expected = df.loc[slice_] + result = df.loc[non_reducing_slice(slice_)] + tm.assert_frame_equal(result, expected) + def test_loc_slice_negative_stepsize(self): # GH#38071 mi = MultiIndex.from_product([["a", "b"], [0, 1]]) diff --git a/pandas/tests/io/formats/test_style.py b/pandas/tests/io/formats/test_style.py index defb00668eb2a..8b6a18c9e3a6a 100644 --- a/pandas/tests/io/formats/test_style.py +++ b/pandas/tests/io/formats/test_style.py @@ -387,14 +387,16 @@ def test_applymap_subset_multiindex(self): slices = [ pd.IndexSlice[:, pd.IndexSlice["x", "A"]], pd.IndexSlice[:, pd.IndexSlice[:, "A"]], + pd.IndexSlice[:, pd.IndexSlice[:, ["A", "C"]]], # missing col element pd.IndexSlice[pd.IndexSlice["a", 1], :], pd.IndexSlice[pd.IndexSlice[:, 1], :], + pd.IndexSlice[pd.IndexSlice[:, [1, 3]], :], # missing row element pd.IndexSlice[:, ("x", "A")], pd.IndexSlice[("a", 1), :], ] for slice in slices: - df.style.applymap(lambda x: "color: red;", subset=slice) + df.style.applymap(lambda x: "color: red;", subset=slice).render() def test_applymap_subset_multiindex_code(self): # https://github.com/pandas-dev/pandas/issues/25858 From 49ffa55ebf6dd73be66f38a05820f74d156df98e Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 26 Jan 2021 21:48:55 +0100 Subject: [PATCH 3/7] BUG: subset non-reducer --- pandas/tests/indexing/multiindex/test_slice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/indexing/multiindex/test_slice.py b/pandas/tests/indexing/multiindex/test_slice.py index f34426dd97df1..6c7d5f06ac355 100644 --- a/pandas/tests/indexing/multiindex/test_slice.py +++ b/pandas/tests/indexing/multiindex/test_slice.py @@ -807,6 +807,7 @@ def test_non_reducing_slice_on_multiindex(self): ], ) def test_non_reducing_multi_slice_on_multiindex(self, slice_): + # GH 33562 cols = pd.MultiIndex.from_product([["a", "b"], ["c", "d"], ["e", "f"]]) idxs = pd.MultiIndex.from_product([["U", "V"], ["W", "X"], ["Y", "Z"]]) df = DataFrame(np.arange(64).reshape(8, 8), columns=cols, index=idxs) From dc09ffbcd24e0eaf51f547dcbc02f2261d75fc4d Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 26 Jan 2021 22:01:29 +0100 Subject: [PATCH 4/7] BUG: subset non-reducer --- pandas/tests/io/formats/test_style.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pandas/tests/io/formats/test_style.py b/pandas/tests/io/formats/test_style.py index 8b6a18c9e3a6a..0e732ddc0a27b 100644 --- a/pandas/tests/io/formats/test_style.py +++ b/pandas/tests/io/formats/test_style.py @@ -377,14 +377,9 @@ def f(x): } assert result == expected - def test_applymap_subset_multiindex(self): - # GH 19861 - # edited for GH 33562 - idx = pd.MultiIndex.from_product([["a", "b"], [1, 2]]) - col = pd.MultiIndex.from_product([["x", "y"], ["A", "B"]]) - df = DataFrame(np.random.rand(4, 4), columns=col, index=idx) - - slices = [ + @pytest.mark.parametrize( + "slice_", + [ pd.IndexSlice[:, pd.IndexSlice["x", "A"]], pd.IndexSlice[:, pd.IndexSlice[:, "A"]], pd.IndexSlice[:, pd.IndexSlice[:, ["A", "C"]]], # missing col element @@ -393,10 +388,15 @@ def test_applymap_subset_multiindex(self): pd.IndexSlice[pd.IndexSlice[:, [1, 3]], :], # missing row element pd.IndexSlice[:, ("x", "A")], pd.IndexSlice[("a", 1), :], - ] - - for slice in slices: - df.style.applymap(lambda x: "color: red;", subset=slice).render() + ], + ) + def test_applymap_subset_multiindex(self, slice_): + # GH 19861 + # edited for GH 33562 + idx = pd.MultiIndex.from_product([["a", "b"], [1, 2]]) + col = pd.MultiIndex.from_product([["x", "y"], ["A", "B"]]) + df = DataFrame(np.random.rand(4, 4), columns=col, index=idx) + df.style.applymap(lambda x: "color: red;", subset=slice_).render() def test_applymap_subset_multiindex_code(self): # https://github.com/pandas-dev/pandas/issues/25858 From 634ecf91b318165bdcf3c60ef13aac12d63e6ff2 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 27 Jan 2021 20:33:32 +0100 Subject: [PATCH 5/7] whats new --- doc/source/whatsnew/v1.3.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 1dcde2000fc89..74c689db77fd4 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -386,6 +386,7 @@ Other - Bug in :class:`Index` constructor sometimes silently ignorning a specified ``dtype`` (:issue:`38879`) - Bug in constructing a :class:`Series` from a list and a :class:`PandasDtype` (:issue:`39357`) - Bug in :class:`Styler` which caused CSS to duplicate on multiple renders. (:issue:`39395`) +- Bug in :class:`Styler` where `subset` arg in methods raised an error for some valid multiindex slices (:issue:`33562`) - .. --------------------------------------------------------------------------- From 12ec537ccb5b34316f2b0012ed8d9f8dca29af67 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Fri, 29 Jan 2021 11:19:34 +0100 Subject: [PATCH 6/7] whats new --- doc/source/whatsnew/v1.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 4d11f32467cfb..6f1615539485e 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -432,7 +432,7 @@ Other - Bug in :class:`Index` constructor sometimes silently ignorning a specified ``dtype`` (:issue:`38879`) - Bug in constructing a :class:`Series` from a list and a :class:`PandasDtype` (:issue:`39357`) - Bug in :class:`Styler` which caused CSS to duplicate on multiple renders. (:issue:`39395`) -- Bug in :class:`Styler` where `subset` arg in methods raised an error for some valid multiindex slices (:issue:`33562`) +- Bug in :class:`Styler` where ``subset`` arg in methods raised an error for some valid multiindex slices (:issue:`33562`) - .. --------------------------------------------------------------------------- From 92742259d4447286a1f29ba4b380ab55865610bf Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Wed, 3 Feb 2021 19:08:11 +0100 Subject: [PATCH 7/7] use any() --- pandas/core/indexing.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index f365814ca65dd..cc7c5f666feda 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2408,11 +2408,8 @@ def pred(part) -> bool: # true when slice does *not* reduce, False when part is a tuple, # i.e. MultiIndex slice if isinstance(part, tuple): - # check for sub-slice: - for subpart in part: - if isinstance(subpart, slice) or is_list_like(subpart): - return True - return False + # GH#39421 check for sub-slice: + return any((isinstance(s, slice) or is_list_like(s)) for s in part) else: return isinstance(part, slice) or is_list_like(part)