Skip to content

Commit 14c8892

Browse files
authored
DEPR: Partial failure in Series.transform and DataFrame.transform. (#40238)
1 parent 0cae716 commit 14c8892

File tree

5 files changed

+64
-14
lines changed

5 files changed

+64
-14
lines changed

doc/source/whatsnew/v1.3.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ Deprecations
363363
- Deprecated :meth:`core.window.ewm.ExponentialMovingWindow.vol` (:issue:`39220`)
364364
- Using ``.astype`` to convert between ``datetime64[ns]`` dtype and :class:`DatetimeTZDtype` is deprecated and will raise in a future version, use ``obj.tz_localize`` or ``obj.dt.tz_localize`` instead (:issue:`38622`)
365365
- Deprecated casting ``datetime.date`` objects to ``datetime64`` when used as ``fill_value`` in :meth:`DataFrame.unstack`, :meth:`DataFrame.shift`, :meth:`Series.shift`, and :meth:`DataFrame.reindex`, pass ``pd.Timestamp(dateobj)`` instead (:issue:`39767`)
366+
- Deprecated allowing partial failure in :meth:`Series.transform` and :meth:`DataFrame.transform` when ``func`` is list-like or dict-like; will raise if any function fails on a column in a future version (:issue:`40211`)
366367

367368
.. ---------------------------------------------------------------------------
368369

pandas/core/apply.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Union,
1616
cast,
1717
)
18+
import warnings
1819

1920
import numpy as np
2021

@@ -267,6 +268,7 @@ def transform_dict_like(self, func):
267268
func = self.normalize_dictlike_arg("transform", obj, func)
268269

269270
results: Dict[Hashable, FrameOrSeriesUnion] = {}
271+
failed_names = []
270272
for name, how in func.items():
271273
colg = obj._gotitem(name, ndim=1)
272274
try:
@@ -277,10 +279,20 @@ def transform_dict_like(self, func):
277279
"No transform functions were provided",
278280
}:
279281
raise err
280-
282+
else:
283+
failed_names.append(name)
281284
# combine results
282285
if not results:
283286
raise ValueError("Transform function failed")
287+
if len(failed_names) > 0:
288+
warnings.warn(
289+
f"{failed_names} did not transform successfully. "
290+
f"Allowing for partial failure is deprecated, this will raise "
291+
f"a ValueError in a future version of pandas."
292+
f"Drop these columns/ops to avoid this warning.",
293+
FutureWarning,
294+
stacklevel=4,
295+
)
284296
return concat(results, axis=1)
285297

286298
def transform_str_or_callable(self, func) -> FrameOrSeriesUnion:

pandas/tests/apply/common.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pandas.core.groupby.base import transformation_kernels
2+
3+
# tshift only works on time index and is deprecated
4+
# There is no Series.cumcount or DataFrame.cumcount
5+
series_transform_kernels = [
6+
x for x in sorted(transformation_kernels) if x not in ["tshift", "cumcount"]
7+
]
8+
frame_transform_kernels = [
9+
x for x in sorted(transformation_kernels) if x not in ["tshift", "cumcount"]
10+
]

pandas/tests/apply/test_frame_transform.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,9 @@
99
Series,
1010
)
1111
import pandas._testing as tm
12-
from pandas.core.groupby.base import transformation_kernels
12+
from pandas.tests.apply.common import frame_transform_kernels
1313
from pandas.tests.frame.common import zip_frames
1414

15-
# tshift only works on time index and is deprecated
16-
# There is no DataFrame.cumcount
17-
frame_kernels = [
18-
x for x in sorted(transformation_kernels) if x not in ["tshift", "cumcount"]
19-
]
20-
2115

2216
def unpack_obj(obj, klass, axis):
2317
"""
@@ -44,7 +38,7 @@ def test_transform_ufunc(axis, float_frame, frame_or_series):
4438
tm.assert_equal(result, expected)
4539

4640

47-
@pytest.mark.parametrize("op", frame_kernels)
41+
@pytest.mark.parametrize("op", frame_transform_kernels)
4842
def test_transform_groupby_kernel(axis, float_frame, op):
4943
# GH 35964
5044

@@ -158,7 +152,7 @@ def test_transform_method_name(method):
158152

159153

160154
wont_fail = ["ffill", "bfill", "fillna", "pad", "backfill", "shift"]
161-
frame_kernels_raise = [x for x in frame_kernels if x not in wont_fail]
155+
frame_kernels_raise = [x for x in frame_transform_kernels if x not in wont_fail]
162156

163157

164158
# mypy doesn't allow adding lists of different types
@@ -187,21 +181,25 @@ def test_transform_bad_dtype(op, frame_or_series):
187181

188182
@pytest.mark.parametrize("op", frame_kernels_raise)
189183
def test_transform_partial_failure(op):
190-
# GH 35964
184+
# GH 35964 & GH 40211
185+
match = "Allowing for partial failure is deprecated"
191186

192187
# Using object makes most transform kernels fail
193188
df = DataFrame({"A": 3 * [object], "B": [1, 2, 3]})
194189

195190
expected = df[["B"]].transform([op])
196-
result = df.transform([op])
191+
with tm.assert_produces_warning(FutureWarning, match=match):
192+
result = df.transform([op])
197193
tm.assert_equal(result, expected)
198194

199195
expected = df[["B"]].transform({"B": op})
200-
result = df.transform({"B": op})
196+
with tm.assert_produces_warning(FutureWarning, match=match):
197+
result = df.transform({"A": op, "B": op})
201198
tm.assert_equal(result, expected)
202199

203200
expected = df[["B"]].transform({"B": [op]})
204-
result = df.transform({"B": [op]})
201+
with tm.assert_produces_warning(FutureWarning, match=match):
202+
result = df.transform({"A": [op], "B": [op]})
205203
tm.assert_equal(result, expected)
206204

207205

pandas/tests/apply/test_series_apply.py

+29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
timedelta_range,
2121
)
2222
import pandas._testing as tm
23+
from pandas.tests.apply.common import series_transform_kernels
2324

2425

2526
def test_series_map_box_timedelta():
@@ -256,6 +257,34 @@ def test_transform(string_series):
256257
tm.assert_series_equal(result.reindex_like(expected), expected)
257258

258259

260+
@pytest.mark.parametrize("op", series_transform_kernels)
261+
def test_transform_partial_failure(op, request):
262+
# GH 35964 & GH 40211
263+
if op in ("ffill", "bfill", "pad", "backfill", "shift"):
264+
request.node.add_marker(
265+
pytest.mark.xfail(reason=f"{op} is successful on any dtype")
266+
)
267+
match = "Allowing for partial failure is deprecated"
268+
269+
# Using object makes most transform kernels fail
270+
ser = Series(3 * [object])
271+
272+
expected = ser.transform(["shift"])
273+
with tm.assert_produces_warning(FutureWarning, match=match):
274+
result = ser.transform([op, "shift"])
275+
tm.assert_equal(result, expected)
276+
277+
expected = ser.transform({"B": "shift"})
278+
with tm.assert_produces_warning(FutureWarning, match=match):
279+
result = ser.transform({"A": op, "B": "shift"})
280+
tm.assert_equal(result, expected)
281+
282+
expected = ser.transform({"B": ["shift"]})
283+
with tm.assert_produces_warning(FutureWarning, match=match):
284+
result = ser.transform({"A": [op], "B": ["shift"]})
285+
tm.assert_equal(result, expected)
286+
287+
259288
def test_demo():
260289
# demonstration tests
261290
s = Series(range(6), dtype="int64", name="series")

0 commit comments

Comments
 (0)