Skip to content

Commit 71efe61

Browse files
fleimgruberjreback
authored andcommitted
Include MultiIndex slice in non-reducing slices (#19881)
1 parent cf92230 commit 71efe61

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

doc/source/whatsnew/v0.24.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,7 @@ Missing
15381538
MultiIndex
15391539
^^^^^^^^^^
15401540

1541+
- Bug in :func:`io.formats.style.Styler.applymap` where ``subset=`` with :class:`MultiIndex` slice would reduce to :class:`Series` (:issue:`19861`)
15411542
- Removed compatibility for :class:`MultiIndex` pickles prior to version 0.8.0; compatibility with :class:`MultiIndex` pickles from version 0.13 forward is maintained (:issue:`21654`)
15421543
- :meth:`MultiIndex.get_loc_level` (and as a consequence, ``.loc`` on a ``Series`` or ``DataFrame`` with a :class:`MultiIndex` index) will now raise a ``KeyError``, rather than returning an empty ``slice``, if asked a label which is present in the ``levels`` but is unused (:issue:`22221`)
15431544
- :class:`MultiIndex` has gained the :meth:`MultiIndex.from_frame`, it allows constructing a :class:`MultiIndex` object from a :class:`DataFrame` (:issue:`22420`)

pandas/core/indexing.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -2732,8 +2732,10 @@ def _non_reducing_slice(slice_):
27322732
slice_ = IndexSlice[:, slice_]
27332733

27342734
def pred(part):
2735-
# true when slice does *not* reduce
2736-
return isinstance(part, slice) or is_list_like(part)
2735+
# true when slice does *not* reduce, False when part is a tuple,
2736+
# i.e. MultiIndex slice
2737+
return ((isinstance(part, slice) or is_list_like(part))
2738+
and not isinstance(part, tuple))
27372739

27382740
if not is_list_like(slice_):
27392741
if not isinstance(slice_, slice):

pandas/tests/indexing/test_indexing.py

+17
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,23 @@ def test_non_reducing_slice(self):
812812
tslice_ = _non_reducing_slice(slice_)
813813
assert isinstance(df.loc[tslice_], DataFrame)
814814

815+
def test_non_reducing_slice_on_multiindex(self):
816+
# GH 19861
817+
dic = {
818+
('a', 'd'): [1, 4],
819+
('a', 'c'): [2, 3],
820+
('b', 'c'): [3, 2],
821+
('b', 'd'): [4, 1]
822+
}
823+
df = pd.DataFrame(dic, index=[0, 1])
824+
idx = pd.IndexSlice
825+
slice_ = idx[:, idx['b', 'd']]
826+
tslice_ = _non_reducing_slice(slice_)
827+
828+
result = df.loc[tslice_]
829+
expected = pd.DataFrame({('b', 'd'): [4, 1]})
830+
tm.assert_frame_equal(result, expected)
831+
815832
def test_list_slice(self):
816833
# like dataframe getitem
817834
slices = [['A'], Series(['A']), np.array(['A'])]

pandas/tests/io/formats/test_style.py

+26
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,32 @@ def f(x):
274274
col in self.df.loc[slice_].columns}
275275
assert result == expected
276276

277+
def test_applymap_subset_multiindex(self):
278+
# GH 19861
279+
# Smoke test for applymap
280+
def color_negative_red(val):
281+
"""
282+
Takes a scalar and returns a string with
283+
the css property `'color: red'` for negative
284+
strings, black otherwise.
285+
"""
286+
color = 'red' if val < 0 else 'black'
287+
return 'color: %s' % color
288+
289+
dic = {
290+
('a', 'd'): [-1.12, 2.11],
291+
('a', 'c'): [2.78, -2.88],
292+
('b', 'c'): [-3.99, 3.77],
293+
('b', 'd'): [4.21, -1.22],
294+
}
295+
296+
idx = pd.IndexSlice
297+
df = pd.DataFrame(dic, index=[0, 1])
298+
299+
(df.style
300+
.applymap(color_negative_red, subset=idx[:, idx['b', 'd']])
301+
.render())
302+
277303
def test_where_with_one_style(self):
278304
# GH 17474
279305
def f(x):

0 commit comments

Comments
 (0)