Skip to content

Commit fe415f5

Browse files
authored
DEPR: Deprecate returning a DataFrame in SeriesApply.apply_standard (pandas-dev#52123)
1 parent 3879640 commit fe415f5

File tree

7 files changed

+59
-31
lines changed

7 files changed

+59
-31
lines changed

doc/source/user_guide/cookbook.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -794,12 +794,12 @@ Apply
794794
index=["I", "II", "III"],
795795
)
796796
797-
def SeriesFromSubList(aList):
798-
return pd.Series(aList)
797+
def make_df(ser):
798+
new_vals = [pd.Series(value, name=name) for name, value in ser.items()]
799+
return pd.DataFrame(new_vals)
800+
801+
df_orgz = pd.concat({ind: row.pipe(make_df) for ind, row in df.iterrows()})
799802
800-
df_orgz = pd.concat(
801-
{ind: row.apply(SeriesFromSubList) for ind, row in df.iterrows()}
802-
)
803803
df_orgz
804804
805805
`Rolling apply with a DataFrame returning a Series

doc/source/user_guide/groupby.rst

-13
Original file line numberDiff line numberDiff line change
@@ -1221,19 +1221,6 @@ The dimension of the returned result can also change:
12211221
12221222
grouped.apply(f)
12231223
1224-
``apply`` on a Series can operate on a returned value from the applied function
1225-
that is itself a series, and possibly upcast the result to a DataFrame:
1226-
1227-
.. ipython:: python
1228-
1229-
def f(x):
1230-
return pd.Series([x, x ** 2], index=["x", "x^2"])
1231-
1232-
1233-
s = pd.Series(np.random.rand(5))
1234-
s
1235-
s.apply(f)
1236-
12371224
Similar to :ref:`groupby.aggregate.agg`, the resulting dtype will reflect that of the
12381225
apply function. If the results from different groups have different dtypes, then
12391226
a common dtype will be determined in the same way as ``DataFrame`` construction.

doc/source/whatsnew/v0.10.0.rst

+20-9
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,26 @@ Convenience methods ``ffill`` and ``bfill`` have been added:
243243
function, that is itself a series, and possibly upcast the result to a
244244
DataFrame
245245

246-
.. ipython:: python
247-
248-
def f(x):
249-
return pd.Series([x, x ** 2], index=["x", "x^2"])
250-
251-
252-
s = pd.Series(np.random.rand(5))
253-
s
254-
s.apply(f)
246+
.. code-block:: python
247+
248+
>>> def f(x):
249+
... return pd.Series([x, x ** 2], index=["x", "x^2"])
250+
>>>
251+
>>> s = pd.Series(np.random.rand(5))
252+
>>> s
253+
0 0.340445
254+
1 0.984729
255+
2 0.919540
256+
3 0.037772
257+
4 0.861549
258+
dtype: float64
259+
>>> s.apply(f)
260+
x x^2
261+
0 0.340445 0.115903
262+
1 0.984729 0.969691
263+
2 0.919540 0.845555
264+
3 0.037772 0.001427
265+
4 0.861549 0.742267
255266
256267
- New API functions for working with pandas options (:issue:`2097`):
257268

doc/source/whatsnew/v2.1.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Deprecations
172172
- Deprecated logical operations (``|``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``), wrap a sequence in a :class:`Series` or numpy array before operating instead (:issue:`51521`)
173173
- Deprecated the methods :meth:`Series.bool` and :meth:`DataFrame.bool` (:issue:`51749`)
174174
- Deprecated :meth:`DataFrame.swapaxes` and :meth:`Series.swapaxes`, use :meth:`DataFrame.transpose` or :meth:`Series.transpose` instead (:issue:`51946`)
175+
- Deprecated making :meth:`Series.apply` return a :class:`DataFrame` when the passed-in callable returns a :class:`Series` object. In the future this will return a :class:`Series` whose values are themselves :class:`Series`. This pattern was very slow and it's recommended to use alternative methods to archive the same goal (:issue:`52116`)
175176
- Deprecated parameter ``convert_type`` in :meth:`Series.apply` (:issue:`52140`)
176177
- Deprecated ``freq`` parameter in :class:`PeriodArray` constructor, pass ``dtype`` instead (:issue:`52462`)
177178
-

pandas/core/apply.py

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Sequence,
2020
cast,
2121
)
22+
import warnings
2223

2324
import numpy as np
2425

@@ -36,6 +37,7 @@
3637
)
3738
from pandas.errors import SpecificationError
3839
from pandas.util._decorators import cache_readonly
40+
from pandas.util._exceptions import find_stack_level
3941

4042
from pandas.core.dtypes.cast import is_nested_object
4143
from pandas.core.dtypes.common import (
@@ -1091,6 +1093,14 @@ def apply_standard(self) -> DataFrame | Series:
10911093
mapped = obj._map_values(mapper=f, na_action=action, convert=self.convert_dtype)
10921094

10931095
if len(mapped) and isinstance(mapped[0], ABCSeries):
1096+
warnings.warn(
1097+
"Returning a DataFrame from Series.apply when the supplied function"
1098+
"returns a Series is deprecated and will be removed in a future "
1099+
"version.",
1100+
FutureWarning,
1101+
stacklevel=find_stack_level(),
1102+
) # GH52116
1103+
10941104
# GH#43986 Need to do list(mapped) in order to get treated as nested
10951105
# See also GH#25959 regarding EA support
10961106
return obj._constructor_expanddim(list(mapped), index=obj.index)

pandas/core/series.py

+5
Original file line numberDiff line numberDiff line change
@@ -4407,6 +4407,11 @@ def apply(
44074407
"""
44084408
Invoke function on values of Series.
44094409
4410+
.. deprecated:: 2.1.0
4411+
4412+
If the result from ``func`` is a ``Series``, wrapping the output in a
4413+
``DataFrame`` instead of a ``Series`` has been deprecated.
4414+
44104415
Can be ufunc (a NumPy function that applies to the entire Series)
44114416
or a Python function that only works on single values.
44124417

pandas/tests/apply/test_series_apply.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,18 @@ def test_agg_apply_evaluate_lambdas_the_same(string_series):
361361
def test_with_nested_series(datetime_series):
362362
# GH 2316
363363
# .agg with a reducer and a transform, what to do
364-
result = datetime_series.apply(lambda x: Series([x, x**2], index=["x", "x^2"]))
364+
msg = "Returning a DataFrame from Series.apply when the supplied function"
365+
with tm.assert_produces_warning(FutureWarning, match=msg):
366+
# GH52123
367+
result = datetime_series.apply(
368+
lambda x: Series([x, x**2], index=["x", "x^2"])
369+
)
365370
expected = DataFrame({"x": datetime_series, "x^2": datetime_series**2})
366371
tm.assert_frame_equal(result, expected)
367372

368-
result = datetime_series.agg(lambda x: Series([x, x**2], index=["x", "x^2"]))
373+
with tm.assert_produces_warning(FutureWarning, match=msg):
374+
# GH52123
375+
result = datetime_series.agg(lambda x: Series([x, x**2], index=["x", "x^2"]))
369376
tm.assert_frame_equal(result, expected)
370377

371378

@@ -445,7 +452,10 @@ def test_apply_series_on_date_time_index_aware_series(dti, exp, aware):
445452
index = dti.tz_localize("UTC").index
446453
else:
447454
index = dti.index
448-
result = Series(index).apply(lambda x: Series([1, 2]))
455+
msg = "Returning a DataFrame from Series.apply when the supplied function"
456+
with tm.assert_produces_warning(FutureWarning, match=msg):
457+
# GH52123
458+
result = Series(index).apply(lambda x: Series([1, 2]))
449459
tm.assert_frame_equal(result, exp)
450460

451461

@@ -546,7 +556,11 @@ def test_apply_dictlike_transformer(string_series, ops):
546556
def test_apply_retains_column_name():
547557
# GH 16380
548558
df = DataFrame({"x": range(3)}, Index(range(3), name="x"))
549-
result = df.x.apply(lambda x: Series(range(x + 1), Index(range(x + 1), name="y")))
559+
func = lambda x: Series(range(x + 1), Index(range(x + 1), name="y"))
560+
msg = "Returning a DataFrame from Series.apply when the supplied function"
561+
with tm.assert_produces_warning(FutureWarning, match=msg):
562+
# GH52123
563+
result = df.x.apply(func)
550564
expected = DataFrame(
551565
[[0.0, np.nan, np.nan], [0.0, 1.0, np.nan], [0.0, 1.0, 2.0]],
552566
columns=Index(range(3), name="y"),

0 commit comments

Comments
 (0)