Skip to content

Commit 2a6fff3

Browse files
authored
ENH: consistent Styler.highlight_X arg signature with props (#40242)
1 parent 85dd837 commit 2a6fff3

File tree

4 files changed

+88
-56
lines changed

4 files changed

+88
-56
lines changed

doc/source/reference/style.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ Builtin styles
5353
.. autosummary::
5454
:toctree: api/
5555

56+
Styler.highlight_null
5657
Styler.highlight_max
5758
Styler.highlight_min
58-
Styler.highlight_null
5959
Styler.background_gradient
6060
Styler.bar
6161

doc/source/whatsnew/v1.3.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Other enhancements
135135
- :meth:`.Styler.set_tooltips_class` and :meth:`.Styler.set_table_styles` amended to optionally allow certain css-string input arguments (:issue:`39564`)
136136
- :meth:`.Styler.apply` now more consistently accepts ndarray function returns, i.e. in all cases for ``axis`` is ``0, 1 or None`` (:issue:`39359`)
137137
- :meth:`.Styler.apply` and :meth:`.Styler.applymap` now raise errors if wrong format CSS is passed on render (:issue:`39660`)
138+
- Builtin highlighting methods in :class:`Styler` have a more consistent signature and css customisability (:issue:`40242`)
138139
- :meth:`Series.loc.__getitem__` and :meth:`Series.loc.__setitem__` with :class:`MultiIndex` now raising helpful error message when indexer has too many dimensions (:issue:`35349`)
139140
- :meth:`pandas.read_stata` and :class:`StataReader` support reading data from compressed files.
140141
- Add support for parsing ``ISO 8601``-like timestamps with negative signs to :meth:`pandas.Timedelta` (:issue:`37172`)

pandas/io/formats/style.py

+52-16
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,7 @@ def apply(
941941
Examples
942942
--------
943943
>>> def highlight_max(x, color):
944-
... return np.where(x == np.nanmax(x.values), f"color: {color};", None)
944+
... return np.where(x == np.nanmax(x.to_numpy()), f"color: {color};", None)
945945
>>> df = pd.DataFrame(np.random.randn(5, 2))
946946
>>> df.style.apply(highlight_max, color='red')
947947
>>> df.style.apply(highlight_max, color='blue', axis=1)
@@ -1636,9 +1636,10 @@ def highlight_null(
16361636
self,
16371637
null_color: str = "red",
16381638
subset: Optional[IndexLabel] = None,
1639+
props: Optional[str] = None,
16391640
) -> Styler:
16401641
"""
1641-
Shade the background ``null_color`` for missing values.
1642+
Highlight missing values with a style.
16421643
16431644
Parameters
16441645
----------
@@ -1648,79 +1649,114 @@ def highlight_null(
16481649
16491650
.. versionadded:: 1.1.0
16501651
1652+
props : str, default None
1653+
CSS properties to use for highlighting. If ``props`` is given, ``color``
1654+
is not used.
1655+
1656+
.. versionadded:: 1.3.0
1657+
16511658
Returns
16521659
-------
16531660
self : Styler
1661+
1662+
See Also
1663+
--------
1664+
Styler.highlight_max: Highlight the maximum with a style.
1665+
Styler.highlight_min: Highlight the minimum with a style.
16541666
"""
16551667

16561668
def f(data: DataFrame, props: str) -> np.ndarray:
1657-
return np.where(pd.isna(data).values, props, "")
1669+
return np.where(pd.isna(data).to_numpy(), props, "")
16581670

1659-
return self.apply(
1660-
f, axis=None, subset=subset, props=f"background-color: {null_color};"
1661-
)
1671+
if props is None:
1672+
props = f"background-color: {null_color};"
1673+
return self.apply(f, axis=None, subset=subset, props=props)
16621674

16631675
def highlight_max(
16641676
self,
16651677
subset: Optional[IndexLabel] = None,
16661678
color: str = "yellow",
16671679
axis: Optional[Axis] = 0,
1680+
props: Optional[str] = None,
16681681
) -> Styler:
16691682
"""
1670-
Highlight the maximum by shading the background.
1683+
Highlight the maximum with a style.
16711684
16721685
Parameters
16731686
----------
16741687
subset : IndexSlice, default None
16751688
A valid slice for ``data`` to limit the style application to.
16761689
color : str, default 'yellow'
1690+
Background color to use for highlighting.
16771691
axis : {0 or 'index', 1 or 'columns', None}, default 0
16781692
Apply to each column (``axis=0`` or ``'index'``), to each row
16791693
(``axis=1`` or ``'columns'``), or to the entire DataFrame at once
16801694
with ``axis=None``.
1695+
props : str, default None
1696+
CSS properties to use for highlighting. If ``props`` is given, ``color``
1697+
is not used.
1698+
1699+
.. versionadded:: 1.3.0
16811700
16821701
Returns
16831702
-------
16841703
self : Styler
1704+
1705+
See Also
1706+
--------
1707+
Styler.highlight_null: Highlight missing values with a style.
1708+
Styler.highlight_min: Highlight the minimum with a style.
16851709
"""
16861710

16871711
def f(data: FrameOrSeries, props: str) -> np.ndarray:
1688-
return np.where(data == np.nanmax(data.values), props, "")
1712+
return np.where(data == np.nanmax(data.to_numpy()), props, "")
16891713

1690-
return self.apply(
1691-
f, axis=axis, subset=subset, props=f"background-color: {color};"
1692-
)
1714+
if props is None:
1715+
props = f"background-color: {color};"
1716+
return self.apply(f, axis=axis, subset=subset, props=props)
16931717

16941718
def highlight_min(
16951719
self,
16961720
subset: Optional[IndexLabel] = None,
16971721
color: str = "yellow",
16981722
axis: Optional[Axis] = 0,
1723+
props: Optional[str] = None,
16991724
) -> Styler:
17001725
"""
1701-
Highlight the minimum by shading the background.
1726+
Highlight the minimum with a style.
17021727
17031728
Parameters
17041729
----------
17051730
subset : IndexSlice, default None
17061731
A valid slice for ``data`` to limit the style application to.
17071732
color : str, default 'yellow'
1733+
Background color to use for highlighting.
17081734
axis : {0 or 'index', 1 or 'columns', None}, default 0
17091735
Apply to each column (``axis=0`` or ``'index'``), to each row
17101736
(``axis=1`` or ``'columns'``), or to the entire DataFrame at once
17111737
with ``axis=None``.
1738+
props : str, default None
1739+
CSS properties to use for highlighting. If ``props`` is given, ``color``
1740+
is not used.
1741+
1742+
.. versionadded:: 1.3.0
17121743
17131744
Returns
17141745
-------
17151746
self : Styler
1747+
1748+
See Also
1749+
--------
1750+
Styler.highlight_null: Highlight missing values with a style.
1751+
Styler.highlight_max: Highlight the maximum with a style.
17161752
"""
17171753

17181754
def f(data: FrameOrSeries, props: str) -> np.ndarray:
1719-
return np.where(data == np.nanmin(data.values), props, "")
1755+
return np.where(data == np.nanmin(data.to_numpy()), props, "")
17201756

1721-
return self.apply(
1722-
f, axis=axis, subset=subset, props=f"background-color: {color};"
1723-
)
1757+
if props is None:
1758+
props = f"background-color: {color};"
1759+
return self.apply(f, axis=axis, subset=subset, props=props)
17241760

17251761
@classmethod
17261762
def from_custom_template(cls, searchpath, name):

pandas/tests/io/formats/style/test_highlight.py

+34-39
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88

99
class TestStylerHighlight:
10+
def setup_method(self, method):
11+
np.random.seed(24)
12+
self.s = DataFrame({"A": np.random.permutation(range(6))})
13+
self.df = DataFrame({"A": [0, 1], "B": np.random.randn(2)})
14+
1015
def test_highlight_null(self):
1116
df = DataFrame({"A": [0, np.nan]})
1217
result = df.style.highlight_null()._compute().ctx
@@ -28,43 +33,33 @@ def test_highlight_null_subset(self):
2833
}
2934
assert result == expected
3035

31-
def test_highlight_max(self):
32-
df = DataFrame([[1, 2], [3, 4]], columns=["A", "B"])
33-
css_seq = [("background-color", "yellow")]
34-
# max(df) = min(-df)
35-
for max_ in [True, False]:
36-
if max_:
37-
attr = "highlight_max"
38-
else:
39-
df = -df
40-
attr = "highlight_min"
41-
result = getattr(df.style, attr)()._compute().ctx
42-
assert result[(1, 1)] == css_seq
43-
44-
result = getattr(df.style, attr)(color="green")._compute().ctx
45-
assert result[(1, 1)] == [("background-color", "green")]
46-
47-
result = getattr(df.style, attr)(subset="A")._compute().ctx
48-
assert result[(1, 0)] == css_seq
49-
50-
result = getattr(df.style, attr)(axis=0)._compute().ctx
51-
expected = {
52-
(1, 0): css_seq,
53-
(1, 1): css_seq,
54-
}
55-
assert result == expected
56-
57-
result = getattr(df.style, attr)(axis=1)._compute().ctx
58-
expected = {
59-
(0, 1): css_seq,
60-
(1, 1): css_seq,
61-
}
62-
assert result == expected
63-
64-
# separate since we can't negate the strs
65-
df["C"] = ["a", "b"]
66-
result = df.style.highlight_max()._compute().ctx
67-
expected = {(1, 1): css_seq}
36+
@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"])
37+
def test_highlight_minmax_basic(self, f):
38+
expected = {
39+
(0, 0): [("background-color", "red")],
40+
(1, 0): [("background-color", "red")],
41+
}
42+
if f == "highlight_min":
43+
df = -self.df
44+
else:
45+
df = self.df
46+
result = getattr(df.style, f)(axis=1, color="red")._compute().ctx
47+
assert result == expected
6848

69-
result = df.style.highlight_min()._compute().ctx
70-
expected = {(0, 0): css_seq}
49+
@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"])
50+
@pytest.mark.parametrize(
51+
"kwargs",
52+
[
53+
{"axis": None, "color": "red"}, # test axis
54+
{"axis": 0, "subset": ["A"], "color": "red"}, # test subset
55+
{"axis": None, "props": "background-color: red"}, # test props
56+
],
57+
)
58+
def test_highlight_minmax_ext(self, f, kwargs):
59+
expected = {(1, 0): [("background-color", "red")]}
60+
if f == "highlight_min":
61+
df = -self.df
62+
else:
63+
df = self.df
64+
result = getattr(df.style, f)(**kwargs)._compute().ctx
65+
assert result == expected

0 commit comments

Comments
 (0)