From b2850b64524392e681381c2dcc0c57552a52a721 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Mon, 2 Aug 2021 19:28:50 +0200 Subject: [PATCH 1/3] amend min/max to handle pd.na --- pandas/io/formats/style.py | 12 ++++++++++-- pandas/tests/io/formats/style/test_highlight.py | 5 +---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 3d6705ed593d2..5621be2e14902 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -2348,7 +2348,11 @@ def highlight_max( """ def f(data: FrameOrSeries, props: str) -> np.ndarray: - return np.where(data == np.nanmax(data.to_numpy()), props, "") + arg = {"skipna": True} + if isinstance(data, DataFrame): + return np.where(data == data.max(**arg).max(**arg), props, "") + else: + return np.where(data == data.max(**arg), props, "") if props is None: props = f"background-color: {color};" @@ -2399,7 +2403,11 @@ def highlight_min( """ def f(data: FrameOrSeries, props: str) -> np.ndarray: - return np.where(data == np.nanmin(data.to_numpy()), props, "") + arg = {"skipna": True} + if isinstance(data, DataFrame): + return np.where(data == data.min(**arg).min(**arg), props, "") + else: + return np.where(data == data.min(**arg), props, "") if props is None: props = f"background-color: {color};" diff --git a/pandas/tests/io/formats/style/test_highlight.py b/pandas/tests/io/formats/style/test_highlight.py index a681d7c65a190..9e956e055d1aa 100644 --- a/pandas/tests/io/formats/style/test_highlight.py +++ b/pandas/tests/io/formats/style/test_highlight.py @@ -5,7 +5,6 @@ DataFrame, IndexSlice, ) -import pandas._testing as tm pytest.importorskip("jinja2") @@ -55,9 +54,7 @@ def test_highlight_minmax_basic(df, f): } if f == "highlight_min": df = -df - with tm.assert_produces_warning(RuntimeWarning): - # All-NaN slice encountered - result = getattr(df.style, f)(axis=1, color="red")._compute().ctx + result = getattr(df.style, f)(axis=1, color="red")._compute().ctx assert result == expected From cbab83fffdb2546a432e1ac0a37cd1c54e1ce93b Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (iMac)" Date: Tue, 3 Aug 2021 10:04:31 +0200 Subject: [PATCH 2/3] whats new 1.3.2 --- doc/source/whatsnew/v1.3.2.rst | 2 +- .../tests/io/formats/style/test_highlight.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.3.2.rst b/doc/source/whatsnew/v1.3.2.rst index 8723b1b766485..4b43fe691f8fe 100644 --- a/doc/source/whatsnew/v1.3.2.rst +++ b/doc/source/whatsnew/v1.3.2.rst @@ -22,7 +22,7 @@ Fixed regressions - Regression in :meth:`DataFrame.drop` does nothing if :class:`MultiIndex` has duplicates and indexer is a tuple or list of tuples (:issue:`42771`) - Fixed regression where :meth:`pandas.read_csv` raised a ``ValueError`` when parameters ``names`` and ``prefix`` were both set to None (:issue:`42387`) - Fixed regression in comparisons between :class:`Timestamp` object and ``datetime64`` objects outside the implementation bounds for nanosecond ``datetime64`` (:issue:`42794`) -- +- Fixed regression in :meth:`.Styler.highlight_min` and :meth:`.Styler.highlight_max` where ``pandas.NA`` was not successfully ignored (:issue:`42650`) .. --------------------------------------------------------------------------- diff --git a/pandas/tests/io/formats/style/test_highlight.py b/pandas/tests/io/formats/style/test_highlight.py index 9e956e055d1aa..1b579a43370a2 100644 --- a/pandas/tests/io/formats/style/test_highlight.py +++ b/pandas/tests/io/formats/style/test_highlight.py @@ -2,6 +2,7 @@ import pytest from pandas import ( + NA, DataFrame, IndexSlice, ) @@ -75,6 +76,26 @@ def test_highlight_minmax_ext(df, f, kwargs): assert result == expected +@pytest.mark.parametrize("f", ["highlight_min", "highlight_max"]) +@pytest.mark.parametrize("axis", [None, 0, 1]) +def test_highlight_minmax_nulls(f, axis): + # GH 42750 + expected = { + (1, 0): [("background-color", "yellow")], + (1, 1): [("background-color", "yellow")], + } + if axis == 1: + expected.update({(2, 1): [("background-color", "yellow")]}) + + if f == "highlight_max": + df = DataFrame({"a": [NA, 1, None], "b": [np.nan, 1, -1]}) + else: + df = DataFrame({"a": [NA, -1, None], "b": [np.nan, -1, 1]}) + + result = getattr(df.style, f)(axis=axis)._compute().ctx + assert result == expected + + @pytest.mark.parametrize( "kwargs", [ From 1344563fb1e6790e537aa7dd581da4edeb4b78f9 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (MBP)" Date: Tue, 3 Aug 2021 21:51:25 +0200 Subject: [PATCH 3/3] refactor --- pandas/io/formats/style.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 5621be2e14902..64b6de5722a56 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -2347,19 +2347,15 @@ def highlight_max( Styler.highlight_quantile: Highlight values defined by a quantile with a style. """ - def f(data: FrameOrSeries, props: str) -> np.ndarray: - arg = {"skipna": True} - if isinstance(data, DataFrame): - return np.where(data == data.max(**arg).max(**arg), props, "") - else: - return np.where(data == data.max(**arg), props, "") - if props is None: props = f"background-color: {color};" # error: Argument 1 to "apply" of "Styler" has incompatible type # "Callable[[FrameOrSeries, str], ndarray]"; expected "Callable[..., Styler]" return self.apply( - f, axis=axis, subset=subset, props=props # type: ignore[arg-type] + partial(_highlight_value, op="max"), # type: ignore[arg-type] + axis=axis, + subset=subset, + props=props, ) def highlight_min( @@ -2402,19 +2398,15 @@ def highlight_min( Styler.highlight_quantile: Highlight values defined by a quantile with a style. """ - def f(data: FrameOrSeries, props: str) -> np.ndarray: - arg = {"skipna": True} - if isinstance(data, DataFrame): - return np.where(data == data.min(**arg).min(**arg), props, "") - else: - return np.where(data == data.min(**arg), props, "") - if props is None: props = f"background-color: {color};" # error: Argument 1 to "apply" of "Styler" has incompatible type # "Callable[[FrameOrSeries, str], ndarray]"; expected "Callable[..., Styler]" return self.apply( - f, axis=axis, subset=subset, props=props # type: ignore[arg-type] + partial(_highlight_value, op="min"), # type: ignore[arg-type] + axis=axis, + subset=subset, + props=props, ) def highlight_between( @@ -2920,6 +2912,16 @@ def _highlight_between( return np.where(g_left & l_right, props, "") +def _highlight_value(data: FrameOrSeries, op: str, props: str) -> np.ndarray: + """ + Return an array of css strings based on the condition of values matching an op. + """ + value = getattr(data, op)(skipna=True) + if isinstance(data, DataFrame): # min/max must be done twice to return scalar + value = getattr(value, op)(skipna=True) + return np.where(data == value, props, "") + + def _bar( data: FrameOrSeries, align: str | float | int | Callable,