66from contextlib import contextmanager
77import copy
88from functools import partial
9+ import operator
910from typing import (
1011 Any ,
1112 Callable ,
2122 FrameOrSeries ,
2223 FrameOrSeriesUnion ,
2324 IndexLabel ,
25+ Scalar ,
2426)
2527from pandas .compat ._optional import import_optional_dependency
2628from pandas .util ._decorators import doc
@@ -1352,6 +1354,7 @@ def highlight_null(
13521354 --------
13531355 Styler.highlight_max: Highlight the maximum with a style.
13541356 Styler.highlight_min: Highlight the minimum with a style.
1357+ Styler.highlight_between: Highlight a defined range with a style.
13551358 """
13561359
13571360 def f (data : DataFrame , props : str ) -> np .ndarray :
@@ -1399,6 +1402,7 @@ def highlight_max(
13991402 --------
14001403 Styler.highlight_null: Highlight missing values with a style.
14011404 Styler.highlight_min: Highlight the minimum with a style.
1405+ Styler.highlight_between: Highlight a defined range with a style.
14021406 """
14031407
14041408 def f (data : FrameOrSeries , props : str ) -> np .ndarray :
@@ -1446,6 +1450,7 @@ def highlight_min(
14461450 --------
14471451 Styler.highlight_null: Highlight missing values with a style.
14481452 Styler.highlight_max: Highlight the maximum with a style.
1453+ Styler.highlight_between: Highlight a defined range with a style.
14491454 """
14501455
14511456 def f (data : FrameOrSeries , props : str ) -> np .ndarray :
@@ -1459,6 +1464,157 @@ def f(data: FrameOrSeries, props: str) -> np.ndarray:
14591464 f , axis = axis , subset = subset , props = props # type: ignore[arg-type]
14601465 )
14611466
1467+ def highlight_between (
1468+ self ,
1469+ subset : IndexLabel | None = None ,
1470+ color : str = "yellow" ,
1471+ axis : Axis | None = 0 ,
1472+ left : Scalar | Sequence | None = None ,
1473+ right : Scalar | Sequence | None = None ,
1474+ inclusive : str = "both" ,
1475+ props : str | None = None ,
1476+ ) -> Styler :
1477+ """
1478+ Highlight a defined range with a style.
1479+
1480+ .. versionadded:: 1.3.0
1481+
1482+ Parameters
1483+ ----------
1484+ subset : IndexSlice, default None
1485+ A valid slice for ``data`` to limit the style application to.
1486+ color : str, default 'yellow'
1487+ Background color to use for highlighting.
1488+ axis : {0 or 'index', 1 or 'columns', None}, default 0
1489+ If ``left`` or ``right`` given as sequence, axis along which to apply those
1490+ boundaries. See examples.
1491+ left : scalar or datetime-like, or sequence or array-like, default None
1492+ Left bound for defining the range.
1493+ right : scalar or datetime-like, or sequence or array-like, default None
1494+ Right bound for defining the range.
1495+ inclusive : {'both', 'neither', 'left', 'right'}
1496+ Identify whether bounds are closed or open.
1497+ props : str, default None
1498+ CSS properties to use for highlighting. If ``props`` is given, ``color``
1499+ is not used.
1500+
1501+ Returns
1502+ -------
1503+ self : Styler
1504+
1505+ See Also
1506+ --------
1507+ Styler.highlight_null: Highlight missing values with a style.
1508+ Styler.highlight_max: Highlight the maximum with a style.
1509+ Styler.highlight_min: Highlight the minimum with a style.
1510+
1511+ Notes
1512+ -----
1513+ If ``left`` is ``None`` only the right bound is applied.
1514+ If ``right`` is ``None`` only the left bound is applied. If both are ``None``
1515+ all values are highlighted.
1516+
1517+ ``axis`` is only needed if ``left`` or ``right`` are provided as a sequence or
1518+ an array-like object for aligning the shapes. If ``left`` and ``right`` are
1519+ both scalars then all ``axis`` inputs will give the same result.
1520+
1521+ This function only works with compatible ``dtypes``. For example a datetime-like
1522+ region can only use equivalent datetime-like ``left`` and ``right`` arguments.
1523+ Use ``subset`` to control regions which have multiple ``dtypes``.
1524+
1525+ Examples
1526+ --------
1527+ Basic usage
1528+
1529+ >>> df = pd.DataFrame({
1530+ ... 'One': [1.2, 1.6, 1.5],
1531+ ... 'Two': [2.9, 2.1, 2.5],
1532+ ... 'Three': [3.1, 3.2, 3.8],
1533+ ... })
1534+ >>> df.style.highlight_between(left=2.1, right=2.9)
1535+
1536+ .. figure:: ../../_static/style/hbetw_basic.png
1537+
1538+ Using a range input sequnce along an ``axis``, in this case setting a ``left``
1539+ and ``right`` for each column individually
1540+
1541+ >>> df.style.highlight_between(left=[1.4, 2.4, 3.4], right=[1.6, 2.6, 3.6],
1542+ ... axis=1, color="#fffd75")
1543+
1544+ .. figure:: ../../_static/style/hbetw_seq.png
1545+
1546+ Using ``axis=None`` and providing the ``left`` argument as an array that
1547+ matches the input DataFrame, with a constant ``right``
1548+
1549+ >>> df.style.highlight_between(left=[[2,2,3],[2,2,3],[3,3,3]], right=3.5,
1550+ ... axis=None, color="#fffd75")
1551+
1552+ .. figure:: ../../_static/style/hbetw_axNone.png
1553+
1554+ Using ``props`` instead of default background coloring
1555+
1556+ >>> df.style.highlight_between(left=1.5, right=3.5,
1557+ ... props='font-weight:bold;color:#e83e8c')
1558+
1559+ .. figure:: ../../_static/style/hbetw_props.png
1560+ """
1561+
1562+ def f (
1563+ data : FrameOrSeries ,
1564+ props : str ,
1565+ left : Scalar | Sequence | np .ndarray | FrameOrSeries | None = None ,
1566+ right : Scalar | Sequence | np .ndarray | FrameOrSeries | None = None ,
1567+ inclusive : bool | str = True ,
1568+ ) -> np .ndarray :
1569+ if np .iterable (left ) and not isinstance (left , str ):
1570+ left = _validate_apply_axis_arg (
1571+ left , "left" , None , data # type: ignore[arg-type]
1572+ )
1573+
1574+ if np .iterable (right ) and not isinstance (right , str ):
1575+ right = _validate_apply_axis_arg (
1576+ right , "right" , None , data # type: ignore[arg-type]
1577+ )
1578+
1579+ # get ops with correct boundary attribution
1580+ if inclusive == "both" :
1581+ ops = (operator .ge , operator .le )
1582+ elif inclusive == "neither" :
1583+ ops = (operator .gt , operator .lt )
1584+ elif inclusive == "left" :
1585+ ops = (operator .ge , operator .lt )
1586+ elif inclusive == "right" :
1587+ ops = (operator .gt , operator .le )
1588+ else :
1589+ raise ValueError (
1590+ f"'inclusive' values can be 'both', 'left', 'right', or 'neither' "
1591+ f"got { inclusive } "
1592+ )
1593+
1594+ g_left = (
1595+ ops [0 ](data , left )
1596+ if left is not None
1597+ else np .full (data .shape , True , dtype = bool )
1598+ )
1599+ l_right = (
1600+ ops [1 ](data , right )
1601+ if right is not None
1602+ else np .full (data .shape , True , dtype = bool )
1603+ )
1604+ return np .where (g_left & l_right , props , "" )
1605+
1606+ if props is None :
1607+ props = f"background-color: { color } ;"
1608+ return self .apply (
1609+ f , # type: ignore[arg-type]
1610+ axis = axis ,
1611+ subset = subset ,
1612+ props = props ,
1613+ left = left ,
1614+ right = right ,
1615+ inclusive = inclusive ,
1616+ )
1617+
14621618 @classmethod
14631619 def from_custom_template (cls , searchpath , name ):
14641620 """
0 commit comments