Skip to content

Commit 3370c81

Browse files
authored
DEPR: Enforce deprecation of partial failure in transform for lists/dicts (#49375)
* DEPR: Enforce deprecation of partial failure in transform for lists and dicts * Warning message fix * Cleaner msgs
1 parent 4b98e0b commit 3370c81

File tree

5 files changed

+85
-127
lines changed

5 files changed

+85
-127
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ Removal of prior version deprecations/changes
276276
- Changed behavior of :class:`Index` constructor when passed a ``SparseArray`` or ``SparseDtype`` to retain that dtype instead of casting to ``numpy.ndarray`` (:issue:`43930`)
277277
- Removed the deprecated ``base`` and ``loffset`` arguments from :meth:`pandas.DataFrame.resample`, :meth:`pandas.Series.resample` and :class:`pandas.Grouper`. Use ``offset`` or ``origin`` instead (:issue:`31809`)
278278
- Changed behavior of :meth:`DataFrame.any` and :meth:`DataFrame.all` with ``bool_only=True``; object-dtype columns with all-bool values will no longer be included, manually cast to ``bool`` dtype first (:issue:`46188`)
279+
- Enforced deprecation of silently dropping columns that raised a ``TypeError`` in :class:`Series.transform` and :class:`DataFrame.transform` when used with a list or dictionary (:issue:`43740`)
279280
-
280281

281282
.. ---------------------------------------------------------------------------

pandas/core/apply.py

+1-26
Original file line numberDiff line numberDiff line change
@@ -266,34 +266,9 @@ def transform_dict_like(self, func):
266266
func = self.normalize_dictlike_arg("transform", obj, func)
267267

268268
results: dict[Hashable, DataFrame | Series] = {}
269-
failed_names = []
270-
all_type_errors = True
271269
for name, how in func.items():
272270
colg = obj._gotitem(name, ndim=1)
273-
try:
274-
results[name] = colg.transform(how, 0, *args, **kwargs)
275-
except Exception as err:
276-
if str(err) in {
277-
"Function did not transform",
278-
"No transform functions were provided",
279-
}:
280-
raise err
281-
else:
282-
if not isinstance(err, TypeError):
283-
all_type_errors = False
284-
failed_names.append(name)
285-
# combine results
286-
if not results:
287-
klass = TypeError if all_type_errors else ValueError
288-
raise klass("Transform function failed")
289-
if len(failed_names) > 0:
290-
warnings.warn(
291-
f"{failed_names} did not transform successfully. If any error is "
292-
f"raised, this will raise in a future version of pandas. "
293-
f"Drop these columns/ops to avoid this warning.",
294-
FutureWarning,
295-
stacklevel=find_stack_level(),
296-
)
271+
results[name] = colg.transform(how, 0, *args, **kwargs)
297272
return concat(results, axis=1)
298273

299274
def transform_str_or_callable(self, func) -> DataFrame | Series:

pandas/tests/apply/test_frame_transform.py

+46-51
Original file line numberDiff line numberDiff line change
@@ -133,32 +133,37 @@ def func(x):
133133
@pytest.mark.parametrize("op", [*frame_kernels_raise, lambda x: x + 1])
134134
def test_transform_bad_dtype(op, frame_or_series, request):
135135
# GH 35964
136-
if op == "rank":
137-
request.node.add_marker(
138-
pytest.mark.xfail(
139-
raises=ValueError, reason="GH 40418: rank does not raise a TypeError"
140-
)
141-
)
142-
elif op == "ngroup":
136+
if op == "ngroup":
143137
request.node.add_marker(
144138
pytest.mark.xfail(raises=ValueError, reason="ngroup not valid for NDFrame")
145139
)
146140

147141
obj = DataFrame({"A": 3 * [object]}) # DataFrame that will fail on most transforms
148142
obj = tm.get_obj(obj, frame_or_series)
143+
if op == "rank":
144+
error = ValueError
145+
msg = "Transform function failed"
146+
else:
147+
error = TypeError
148+
msg = "|".join(
149+
[
150+
"not supported between instances of 'type' and 'type'",
151+
"unsupported operand type",
152+
]
153+
)
149154

150-
with pytest.raises(TypeError, match="unsupported operand|not supported"):
155+
with pytest.raises(error, match=msg):
151156
obj.transform(op)
152-
with pytest.raises(TypeError, match="Transform function failed"):
157+
with pytest.raises(error, match=msg):
153158
obj.transform([op])
154-
with pytest.raises(TypeError, match="Transform function failed"):
159+
with pytest.raises(error, match=msg):
155160
obj.transform({"A": op})
156-
with pytest.raises(TypeError, match="Transform function failed"):
161+
with pytest.raises(error, match=msg):
157162
obj.transform({"A": [op]})
158163

159164

160165
@pytest.mark.parametrize("op", frame_kernels_raise)
161-
def test_transform_partial_failure_typeerror(request, op):
166+
def test_transform_failure_typeerror(request, op):
162167
# GH 35964
163168

164169
if op == "ngroup":
@@ -168,62 +173,52 @@ def test_transform_partial_failure_typeerror(request, op):
168173

169174
# Using object makes most transform kernels fail
170175
df = DataFrame({"A": 3 * [object], "B": [1, 2, 3]})
176+
if op == "rank":
177+
error = ValueError
178+
msg = "Transform function failed"
179+
else:
180+
error = TypeError
181+
msg = "|".join(
182+
[
183+
"not supported between instances of 'type' and 'type'",
184+
"unsupported operand type",
185+
]
186+
)
171187

172-
expected = df[["B"]].transform([op])
173-
match = r"\['A'\] did not transform successfully"
174-
with tm.assert_produces_warning(FutureWarning, match=match):
175-
result = df.transform([op])
176-
tm.assert_equal(result, expected)
188+
with pytest.raises(error, match=msg):
189+
df.transform([op])
177190

178-
expected = df[["B"]].transform({"B": op})
179-
match = r"\['A'\] did not transform successfully"
180-
with tm.assert_produces_warning(FutureWarning, match=match):
181-
result = df.transform({"A": op, "B": op})
182-
tm.assert_equal(result, expected)
191+
with pytest.raises(error, match=msg):
192+
df.transform({"A": op, "B": op})
183193

184-
expected = df[["B"]].transform({"B": [op]})
185-
match = r"\['A'\] did not transform successfully"
186-
with tm.assert_produces_warning(FutureWarning, match=match):
187-
result = df.transform({"A": [op], "B": [op]})
188-
tm.assert_equal(result, expected)
194+
with pytest.raises(error, match=msg):
195+
df.transform({"A": [op], "B": [op]})
189196

190-
expected = df.transform({"A": ["shift"], "B": [op]})
191-
match = rf"\['{op}'\] did not transform successfully"
192-
with tm.assert_produces_warning(FutureWarning, match=match):
193-
result = df.transform({"A": [op, "shift"], "B": [op]})
194-
tm.assert_equal(result, expected)
197+
with pytest.raises(error, match=msg):
198+
df.transform({"A": [op, "shift"], "B": [op]})
195199

196200

197-
def test_transform_partial_failure_valueerror():
201+
def test_transform_failure_valueerror():
198202
# GH 40211
199-
match = ".*did not transform successfully"
200-
201203
def op(x):
202204
if np.sum(np.sum(x)) < 10:
203205
raise ValueError
204206
return x
205207

206208
df = DataFrame({"A": [1, 2, 3], "B": [400, 500, 600]})
209+
msg = "Transform function failed"
207210

208-
expected = df[["B"]].transform([op])
209-
with tm.assert_produces_warning(FutureWarning, match=match):
210-
result = df.transform([op])
211-
tm.assert_equal(result, expected)
211+
with pytest.raises(ValueError, match=msg):
212+
df.transform([op])
212213

213-
expected = df[["B"]].transform({"B": op})
214-
with tm.assert_produces_warning(FutureWarning, match=match):
215-
result = df.transform({"A": op, "B": op})
216-
tm.assert_equal(result, expected)
214+
with pytest.raises(ValueError, match=msg):
215+
df.transform({"A": op, "B": op})
217216

218-
expected = df[["B"]].transform({"B": [op]})
219-
with tm.assert_produces_warning(FutureWarning, match=match):
220-
result = df.transform({"A": [op], "B": [op]})
221-
tm.assert_equal(result, expected)
217+
with pytest.raises(ValueError, match=msg):
218+
df.transform({"A": [op], "B": [op]})
222219

223-
expected = df.transform({"A": ["shift"], "B": [op]})
224-
with tm.assert_produces_warning(FutureWarning, match=match):
225-
result = df.transform({"A": [op, "shift"], "B": [op]})
226-
tm.assert_equal(result, expected)
220+
with pytest.raises(ValueError, match=msg):
221+
df.transform({"A": [op, "shift"], "B": [op]})
227222

228223

229224
@pytest.mark.parametrize("use_apply", [True, False])

pandas/tests/apply/test_invalid_arg.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def test_agg_none_to_type():
283283
def test_transform_none_to_type():
284284
# GH#34377
285285
df = DataFrame({"a": [None]})
286-
msg = "Transform function failed"
286+
msg = "argument must be a"
287287
with pytest.raises(TypeError, match=msg):
288288
df.transform({"a": int})
289289

pandas/tests/apply/test_series_apply.py

+36-49
Original file line numberDiff line numberDiff line change
@@ -280,72 +280,59 @@ def test_transform_partial_failure(op, request):
280280
# GH 35964
281281
if op in ("ffill", "bfill", "pad", "backfill", "shift"):
282282
request.node.add_marker(
283-
pytest.mark.xfail(
284-
raises=AssertionError, reason=f"{op} is successful on any dtype"
285-
)
283+
pytest.mark.xfail(reason=f"{op} is successful on any dtype")
286284
)
287285

288286
# Using object makes most transform kernels fail
289287
ser = Series(3 * [object])
290288

291-
expected = ser.transform(["shift"])
292-
match = rf"\['{op}'\] did not transform successfully"
293-
with tm.assert_produces_warning(FutureWarning, match=match):
294-
result = ser.transform([op, "shift"])
295-
tm.assert_equal(result, expected)
296-
297-
expected = ser.transform({"B": "shift"})
298-
match = r"\['A'\] did not transform successfully"
299-
with tm.assert_produces_warning(FutureWarning, match=match):
300-
result = ser.transform({"A": op, "B": "shift"})
301-
tm.assert_equal(result, expected)
302-
303-
expected = ser.transform({"B": ["shift"]})
304-
match = r"\['A'\] did not transform successfully"
305-
with tm.assert_produces_warning(FutureWarning, match=match):
306-
result = ser.transform({"A": [op], "B": ["shift"]})
307-
tm.assert_equal(result, expected)
308-
309-
match = r"\['B'\] did not transform successfully"
310-
with tm.assert_produces_warning(FutureWarning, match=match):
311-
expected = ser.transform({"A": ["shift"], "B": [op]})
312-
match = rf"\['{op}'\] did not transform successfully"
313-
with tm.assert_produces_warning(FutureWarning, match=match):
314-
result = ser.transform({"A": [op, "shift"], "B": [op]})
315-
tm.assert_equal(result, expected)
289+
if op in ("fillna", "ngroup", "rank"):
290+
error = ValueError
291+
msg = "Transform function failed"
292+
else:
293+
error = TypeError
294+
msg = "|".join(
295+
[
296+
"not supported between instances of 'type' and 'type'",
297+
"unsupported operand type",
298+
]
299+
)
300+
301+
with pytest.raises(error, match=msg):
302+
ser.transform([op, "shift"])
303+
304+
with pytest.raises(error, match=msg):
305+
ser.transform({"A": op, "B": "shift"})
306+
307+
with pytest.raises(error, match=msg):
308+
ser.transform({"A": [op], "B": ["shift"]})
309+
310+
with pytest.raises(error, match=msg):
311+
ser.transform({"A": [op, "shift"], "B": [op]})
316312

317313

318314
def test_transform_partial_failure_valueerror():
319315
# GH 40211
320-
match = ".*did not transform successfully"
321-
322316
def noop(x):
323317
return x
324318

325319
def raising_op(_):
326320
raise ValueError
327321

328322
ser = Series(3 * [object])
323+
msg = "Transform function failed"
324+
325+
with pytest.raises(ValueError, match=msg):
326+
ser.transform([noop, raising_op])
327+
328+
with pytest.raises(ValueError, match=msg):
329+
ser.transform({"A": raising_op, "B": noop})
330+
331+
with pytest.raises(ValueError, match=msg):
332+
ser.transform({"A": [raising_op], "B": [noop]})
329333

330-
expected = ser.transform([noop])
331-
with tm.assert_produces_warning(FutureWarning, match=match):
332-
result = ser.transform([noop, raising_op])
333-
tm.assert_equal(result, expected)
334-
335-
expected = ser.transform({"B": noop})
336-
with tm.assert_produces_warning(FutureWarning, match=match):
337-
result = ser.transform({"A": raising_op, "B": noop})
338-
tm.assert_equal(result, expected)
339-
340-
expected = ser.transform({"B": [noop]})
341-
with tm.assert_produces_warning(FutureWarning, match=match):
342-
result = ser.transform({"A": [raising_op], "B": [noop]})
343-
tm.assert_equal(result, expected)
344-
345-
expected = ser.transform({"A": [noop], "B": [noop]})
346-
with tm.assert_produces_warning(FutureWarning, match=match):
347-
result = ser.transform({"A": [noop, raising_op], "B": [noop]})
348-
tm.assert_equal(result, expected)
334+
with pytest.raises(ValueError, match=msg):
335+
ser.transform({"A": [noop, raising_op], "B": [noop]})
349336

350337

351338
def test_demo():

0 commit comments

Comments
 (0)