Skip to content

Commit a31e345

Browse files
committed
Backport PR pandas-dev#40090: REG: DataFrame/Series.transform with list and non-list dict values
1 parent fe82c37 commit a31e345

File tree

4 files changed

+41
-1
lines changed

4 files changed

+41
-1
lines changed

doc/source/whatsnew/v1.2.3.rst

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Fixed regressions
2424
Passing ``ascending=None`` is still considered invalid,
2525
and the new error message suggests a proper usage
2626
(``ascending`` must be a boolean or a list-like boolean).
27+
- Fixed regression in :meth:`DataFrame.transform` and :meth:`Series.transform` giving incorrect column labels when passed a dictionary with a mix of list and non-list values (:issue:`40018`)
28+
-
2729

2830
.. ---------------------------------------------------------------------------
2931

pandas/core/aggregation.py

+16
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,22 @@ def transform_dict_like(
491491
# GH 15931 - deprecation of renaming keys
492492
raise SpecificationError("nested renamer is not supported")
493493

494+
is_aggregator = lambda x: isinstance(x, (list, tuple, dict))
495+
496+
# if we have a dict of any non-scalars
497+
# eg. {'A' : ['mean']}, normalize all to
498+
# be list-likes
499+
# Cannot use func.values() because arg may be a Series
500+
if any(is_aggregator(x) for _, x in func.items()):
501+
new_func: AggFuncTypeDict = {}
502+
for k, v in func.items():
503+
if not is_aggregator(v):
504+
# mypy can't realize v is not a list here
505+
new_func[k] = [v] # type:ignore[list-item]
506+
else:
507+
new_func[k] = v
508+
func = new_func
509+
494510
results: Dict[Label, FrameOrSeriesUnion] = {}
495511
for name, how in func.items():
496512
colg = obj._gotitem(name, ndim=1)

pandas/tests/frame/apply/test_frame_transform.py

+11
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ def test_transform_dictlike(axis, float_frame, box):
9999
tm.assert_frame_equal(result, expected)
100100

101101

102+
def test_transform_dictlike_mixed():
103+
# GH 40018 - mix of lists and non-lists in values of a dictionary
104+
df = DataFrame({"a": [1, 2], "b": [1, 4], "c": [1, 4]})
105+
result = df.transform({"b": ["sqrt", "abs"], "c": "sqrt"})
106+
expected = DataFrame(
107+
[[1.0, 1, 1.0], [2.0, 4, 2.0]],
108+
columns=MultiIndex([("b", "c"), ("sqrt", "abs")], [(0, 0, 1), (0, 1, 0)]),
109+
)
110+
tm.assert_frame_equal(result, expected)
111+
112+
102113
@pytest.mark.parametrize(
103114
"ops",
104115
[

pandas/tests/series/apply/test_series_transform.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22
import pytest
33

4-
from pandas import DataFrame, Series, concat
4+
from pandas import DataFrame, MultiIndex, Series, concat
55
import pandas._testing as tm
66
from pandas.core.base import SpecificationError
77
from pandas.core.groupby.base import transformation_kernels
@@ -52,6 +52,17 @@ def test_transform_dictlike(string_series, box):
5252
tm.assert_frame_equal(result, expected)
5353

5454

55+
def test_transform_dictlike_mixed():
56+
# GH 40018 - mix of lists and non-lists in values of a dictionary
57+
df = Series([1, 4])
58+
result = df.transform({"b": ["sqrt", "abs"], "c": "sqrt"})
59+
expected = DataFrame(
60+
[[1.0, 1, 1.0], [2.0, 4, 2.0]],
61+
columns=MultiIndex([("b", "c"), ("sqrt", "abs")], [(0, 0, 1), (0, 1, 0)]),
62+
)
63+
tm.assert_frame_equal(result, expected)
64+
65+
5566
def test_transform_wont_agg(string_series):
5667
# GH 35964
5768
# we are trying to transform with an aggregator

0 commit comments

Comments
 (0)