From 729cb92e8d0386642a07a550ce98e02e1ccf940f Mon Sep 17 00:00:00 2001 From: Elizabeth Thompson Date: Tue, 21 Jul 2026 16:13:30 +0000 Subject: [PATCH] fix(pandas_postprocessing): avoid FutureWarning for max/min in boxplot MINMAX PR #42004 fixed the FutureWarning triggered by passing np.mean/np.median directly to GroupBy.agg, but missed that the MINMAX whisker type sets whisker_high/whisker_low to plain np.max/np.min, which trip the same warning. Pass the "max"/"min" string forms instead, which pandas resolves to the identical aggregation today (per the warning's own guidance). whisker_high/whisker_low remain callables for the outliers() calculation and for the TUKEY/PERCENTILE branches, where they're closures rather than bare numpy functions and aren't affected by this change. No behavior change. Added test_boxplot_minmax_no_future_warning, mirroring the existing mean/median test, asserting both the absence of the warning and value-equality against a plain pandas groupby. --- .../utils/pandas_postprocessing/boxplot.py | 10 ++++++--- .../pandas_postprocessing/test_boxplot.py | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/superset/utils/pandas_postprocessing/boxplot.py b/superset/utils/pandas_postprocessing/boxplot.py index 8dbe1faba670..17ded276ae64 100644 --- a/superset/utils/pandas_postprocessing/boxplot.py +++ b/superset/utils/pandas_postprocessing/boxplot.py @@ -111,12 +111,16 @@ def outliers(series: Series) -> set[float]: # "mean"/"median" are passed as strings rather than np.mean/np.median: the # callables trigger a pandas FutureWarning in GroupBy.agg (see # _PANDAS_STRING_AGGREGATORS in pandas_postprocessing/utils.py) and the - # string form resolves to the exact same aggregation. + # string form resolves to the exact same aggregation. Under MINMAX, + # whisker_high/whisker_low are themselves plain np.max/np.min, so the same + # substitution applies there; under TUKEY/PERCENTILE they're closures and + # aren't eligible for the string shortcut. + is_minmax = whisker_type == PostProcessingBoxplotWhiskerType.MINMAX operators: dict[str, Union[str, Callable[[Any], Any]]] = { "mean": "mean", "median": "median", - "max": whisker_high, - "min": whisker_low, + "max": "max" if is_minmax else whisker_high, + "min": "min" if is_minmax else whisker_low, "q1": quartile1, "q3": quartile3, "count": np.ma.count, diff --git a/tests/unit_tests/pandas_postprocessing/test_boxplot.py b/tests/unit_tests/pandas_postprocessing/test_boxplot.py index 29ca2a30c7e5..7bde4a2b0c36 100644 --- a/tests/unit_tests/pandas_postprocessing/test_boxplot.py +++ b/tests/unit_tests/pandas_postprocessing/test_boxplot.py @@ -68,6 +68,28 @@ def test_boxplot_mean_median_no_future_warning(): assert series_to_list(df["cars__median"]) == series_to_list(expected["median"]) +def test_boxplot_minmax_no_future_warning(): + """Under MINMAX, whisker_high/whisker_low are plain np.max/np.min, which + must be passed as strings (not the callables) to GroupBy.agg, else pandas + raises a FutureWarning. Also verify the values match a plain pandas + groupby, since the string and callable forms could silently diverge on a + future pandas version.""" + expected = names_df.groupby("region")["cars"].agg(["max", "min"]) + + with warnings.catch_warnings(): + warnings.simplefilter("error", FutureWarning) + df = boxplot( + df=names_df, + groupby=["region"], + whisker_type=PostProcessingBoxplotWhiskerType.MINMAX, + metrics=["cars"], + ) + + df = df.set_index("region") + assert series_to_list(df["cars__max"]) == series_to_list(expected["max"]) + assert series_to_list(df["cars__min"]) == series_to_list(expected["min"]) + + def test_boxplot_min_max(): df = boxplot( df=names_df,