6
6
from contextlib import contextmanager
7
7
import copy
8
8
from functools import partial
9
+ import operator
9
10
from typing import (
10
11
Any ,
11
12
Callable ,
21
22
FrameOrSeries ,
22
23
FrameOrSeriesUnion ,
23
24
IndexLabel ,
25
+ Scalar ,
24
26
)
25
27
from pandas .compat ._optional import import_optional_dependency
26
28
from pandas .util ._decorators import doc
@@ -1352,6 +1354,7 @@ def highlight_null(
1352
1354
--------
1353
1355
Styler.highlight_max: Highlight the maximum with a style.
1354
1356
Styler.highlight_min: Highlight the minimum with a style.
1357
+ Styler.highlight_between: Highlight a defined range with a style.
1355
1358
"""
1356
1359
1357
1360
def f (data : DataFrame , props : str ) -> np .ndarray :
@@ -1399,6 +1402,7 @@ def highlight_max(
1399
1402
--------
1400
1403
Styler.highlight_null: Highlight missing values with a style.
1401
1404
Styler.highlight_min: Highlight the minimum with a style.
1405
+ Styler.highlight_between: Highlight a defined range with a style.
1402
1406
"""
1403
1407
1404
1408
def f (data : FrameOrSeries , props : str ) -> np .ndarray :
@@ -1446,6 +1450,7 @@ def highlight_min(
1446
1450
--------
1447
1451
Styler.highlight_null: Highlight missing values with a style.
1448
1452
Styler.highlight_max: Highlight the maximum with a style.
1453
+ Styler.highlight_between: Highlight a defined range with a style.
1449
1454
"""
1450
1455
1451
1456
def f (data : FrameOrSeries , props : str ) -> np .ndarray :
@@ -1459,6 +1464,157 @@ def f(data: FrameOrSeries, props: str) -> np.ndarray:
1459
1464
f , axis = axis , subset = subset , props = props # type: ignore[arg-type]
1460
1465
)
1461
1466
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
+
1462
1618
@classmethod
1463
1619
def from_custom_template (cls , searchpath , name ):
1464
1620
"""
0 commit comments