Compare commits

...
Author SHA1 Message Date
Elizabeth ThompsonandClaude Opus 4.8 05bb34ae78 fix(pandas_postprocessing): map bare numpy callable aggregators to string names for GroupBy.agg
A bare numpy callable operator (e.g. np.median) passed through to
GroupBy.agg triggers a pandas FutureWarning:

  FutureWarning: The provided callable <function median ...> is currently
  using SeriesGroupBy.median. In a future version of pandas, the provided
  callable will be used directly. To keep current behavior pass the string
  "median" instead.

The existing _PANDAS_STRING_AGGREGATORS fix only covered string operators;
the callable-operator branch of _get_aggregate_funcs still forwarded the raw
numpy callable. Add a reverse lookup (reusing NUMPY_FUNCTIONS and
_PANDAS_STRING_AGGREGATORS) and swap such callables for their string name
before calling .agg(). Non-mapped callables, string operators, and
partial(func, **options) paths are unchanged, so results and dtypes are
identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 16:10:50 +00:00
2 changed files with 35 additions and 1 deletions
+14 -1
View File
@@ -61,6 +61,15 @@ _PANDAS_STRING_AGGREGATORS: frozenset[str] = frozenset(
{"max", "mean", "median", "min", "prod", "std", "sum", "var"}
)
# Reverse lookup from the numpy callables above to their pandas string name, so a
# bare callable operator (e.g. np.median) can be swapped for the string form before
# reaching GroupBy.agg and avoid the same FutureWarning.
_STRING_AGGREGATOR_BY_CALLABLE: dict[Callable[..., Any], str] = {
func: name
for name, func in NUMPY_FUNCTIONS.items()
if name in _PANDAS_STRING_AGGREGATORS
}
DENYLIST_ROLLING_FUNCTIONS = (
"count",
"corr",
@@ -173,7 +182,11 @@ def _get_aggregate_funcs(
)
operator = agg_obj["operator"]
if callable(operator):
aggfunc: str | Callable[..., Any] = operator
# A bare numpy callable (e.g. np.median) that pandas maps to its own
# GroupBy method triggers a FutureWarning; use the string name instead.
aggfunc: str | Callable[..., Any] = _STRING_AGGREGATOR_BY_CALLABLE.get(
operator, operator
)
else:
func = NUMPY_FUNCTIONS.get(operator)
if not func:
@@ -14,6 +14,10 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import warnings
import numpy as np
from superset.utils.pandas_postprocessing import aggregate
from tests.unit_tests.fixtures.dataframes import categories_df
from tests.unit_tests.pandas_postprocessing.utils import series_to_list
@@ -56,6 +60,23 @@ def test_aggregate_string_operators():
assert series_to_list(df["asc min"])[0] == 0
def test_aggregate_callable_operator_avoids_future_warning():
"""A bare numpy callable operator (e.g. np.median) that pandas maps to its own
GroupBy method must be swapped for the string form so GroupBy.agg does not raise
a FutureWarning, while producing the same result as the string operator."""
aggregates = {
"asc median": {"column": "asc_idx", "operator": np.median},
"asc mean": {"column": "asc_idx", "operator": np.mean},
"asc sum": {"column": "asc_idx", "operator": np.sum},
}
with warnings.catch_warnings():
warnings.simplefilter("error", FutureWarning)
df = aggregate(df=categories_df, groupby=["constant"], aggregates=aggregates)
assert series_to_list(df["asc median"])[0] == 50.0
assert series_to_list(df["asc mean"])[0] == 50.0
assert series_to_list(df["asc sum"])[0] == 5050
def test_aggregate_count_includes_nulls():
"""'count' operator uses np.ma.count, which counts all rows including NaN.
It is intentionally excluded from _PANDAS_STRING_AGGREGATORS to preserve this