|
8 | 8 |
|
9 | 9 | from pandas._config import option_context
|
10 | 10 |
|
| 11 | +from pandas._libs import lib |
11 | 12 | from pandas._typing import (
|
12 | 13 | AggFuncType,
|
13 | 14 | AggFuncTypeBase,
|
|
26 | 27 | from pandas.core.dtypes.generic import ABCSeries
|
27 | 28 |
|
28 | 29 | from pandas.core.aggregation import agg_dict_like, agg_list_like
|
29 |
| -from pandas.core.construction import create_series_with_explicit_dtype |
| 30 | +from pandas.core.construction import ( |
| 31 | + array as pd_array, |
| 32 | + create_series_with_explicit_dtype, |
| 33 | +) |
30 | 34 |
|
31 | 35 | if TYPE_CHECKING:
|
32 | 36 | from pandas import DataFrame, Index, Series
|
@@ -63,12 +67,30 @@ def frame_apply(
|
63 | 67 | )
|
64 | 68 |
|
65 | 69 |
|
| 70 | +def series_apply( |
| 71 | + obj: Series, |
| 72 | + how: str, |
| 73 | + func: AggFuncType, |
| 74 | + convert_dtype: bool = True, |
| 75 | + args=None, |
| 76 | + kwds=None, |
| 77 | +) -> SeriesApply: |
| 78 | + return SeriesApply( |
| 79 | + obj, |
| 80 | + how, |
| 81 | + func, |
| 82 | + convert_dtype, |
| 83 | + args, |
| 84 | + kwds, |
| 85 | + ) |
| 86 | + |
| 87 | + |
66 | 88 | class Apply(metaclass=abc.ABCMeta):
|
67 | 89 | axis: int
|
68 | 90 |
|
69 | 91 | def __init__(
|
70 | 92 | self,
|
71 |
| - obj: DataFrame, |
| 93 | + obj: FrameOrSeriesUnion, |
72 | 94 | how: str,
|
73 | 95 | func,
|
74 | 96 | raw: bool,
|
@@ -110,12 +132,62 @@ def f(x):
|
110 | 132 | def index(self) -> Index:
|
111 | 133 | return self.obj.index
|
112 | 134 |
|
113 |
| - @abc.abstractmethod |
114 | 135 | def get_result(self):
|
| 136 | + if self.how == "apply": |
| 137 | + return self.apply() |
| 138 | + else: |
| 139 | + return self.agg() |
| 140 | + |
| 141 | + @abc.abstractmethod |
| 142 | + def apply(self) -> FrameOrSeriesUnion: |
115 | 143 | pass
|
116 | 144 |
|
| 145 | + def agg(self) -> Tuple[Optional[FrameOrSeriesUnion], Optional[bool]]: |
| 146 | + """ |
| 147 | + Provide an implementation for the aggregators. |
| 148 | +
|
| 149 | + Returns |
| 150 | + ------- |
| 151 | + tuple of result, how. |
| 152 | +
|
| 153 | + Notes |
| 154 | + ----- |
| 155 | + how can be a string describe the required post-processing, or |
| 156 | + None if not required. |
| 157 | + """ |
| 158 | + obj = self.obj |
| 159 | + arg = self.f |
| 160 | + args = self.args |
| 161 | + kwargs = self.kwds |
| 162 | + |
| 163 | + _axis = kwargs.pop("_axis", None) |
| 164 | + if _axis is None: |
| 165 | + _axis = getattr(obj, "axis", 0) |
| 166 | + |
| 167 | + if isinstance(arg, str): |
| 168 | + return obj._try_aggregate_string_function(arg, *args, **kwargs), None |
| 169 | + elif is_dict_like(arg): |
| 170 | + arg = cast(AggFuncTypeDict, arg) |
| 171 | + return agg_dict_like(obj, arg, _axis), True |
| 172 | + elif is_list_like(arg): |
| 173 | + # we require a list, but not a 'str' |
| 174 | + arg = cast(List[AggFuncTypeBase], arg) |
| 175 | + return agg_list_like(obj, arg, _axis=_axis), None |
| 176 | + else: |
| 177 | + result = None |
| 178 | + |
| 179 | + if callable(arg): |
| 180 | + f = obj._get_cython_func(arg) |
| 181 | + if f and not args and not kwargs: |
| 182 | + return getattr(obj, f)(), None |
| 183 | + |
| 184 | + # caller can react |
| 185 | + return result, True |
| 186 | + |
117 | 187 |
|
118 | 188 | class FrameApply(Apply):
|
| 189 | + obj: DataFrame |
| 190 | + |
119 | 191 | # ---------------------------------------------------------------
|
120 | 192 | # Abstract Methods
|
121 | 193 |
|
@@ -168,48 +240,6 @@ def get_result(self):
|
168 | 240 | else:
|
169 | 241 | return self.agg()
|
170 | 242 |
|
171 |
| - def agg(self) -> Tuple[Optional[FrameOrSeriesUnion], Optional[bool]]: |
172 |
| - """ |
173 |
| - Provide an implementation for the aggregators. |
174 |
| -
|
175 |
| - Returns |
176 |
| - ------- |
177 |
| - tuple of result, how. |
178 |
| -
|
179 |
| - Notes |
180 |
| - ----- |
181 |
| - how can be a string describe the required post-processing, or |
182 |
| - None if not required. |
183 |
| - """ |
184 |
| - obj = self.obj |
185 |
| - arg = self.f |
186 |
| - args = self.args |
187 |
| - kwargs = self.kwds |
188 |
| - |
189 |
| - _axis = kwargs.pop("_axis", None) |
190 |
| - if _axis is None: |
191 |
| - _axis = getattr(obj, "axis", 0) |
192 |
| - |
193 |
| - if isinstance(arg, str): |
194 |
| - return obj._try_aggregate_string_function(arg, *args, **kwargs), None |
195 |
| - elif is_dict_like(arg): |
196 |
| - arg = cast(AggFuncTypeDict, arg) |
197 |
| - return agg_dict_like(obj, arg, _axis), True |
198 |
| - elif is_list_like(arg): |
199 |
| - # we require a list, but not a 'str' |
200 |
| - arg = cast(List[AggFuncTypeBase], arg) |
201 |
| - return agg_list_like(obj, arg, _axis=_axis), None |
202 |
| - else: |
203 |
| - result = None |
204 |
| - |
205 |
| - if callable(arg): |
206 |
| - f = obj._get_cython_func(arg) |
207 |
| - if f and not args and not kwargs: |
208 |
| - return getattr(obj, f)(), None |
209 |
| - |
210 |
| - # caller can react |
211 |
| - return result, True |
212 |
| - |
213 | 243 | def apply(self) -> FrameOrSeriesUnion:
|
214 | 244 | """ compute the results """
|
215 | 245 | # dispatch to agg
|
@@ -531,3 +561,79 @@ def infer_to_same_shape(self, results: ResType, res_index: Index) -> DataFrame:
|
531 | 561 | result = result.infer_objects()
|
532 | 562 |
|
533 | 563 | return result
|
| 564 | + |
| 565 | + |
| 566 | +class SeriesApply(Apply): |
| 567 | + obj: Series |
| 568 | + axis = 0 |
| 569 | + |
| 570 | + def __init__( |
| 571 | + self, |
| 572 | + obj: Series, |
| 573 | + how: str, |
| 574 | + func: AggFuncType, |
| 575 | + convert_dtype: bool, |
| 576 | + args, |
| 577 | + kwds, |
| 578 | + ): |
| 579 | + self.convert_dtype = convert_dtype |
| 580 | + |
| 581 | + super().__init__( |
| 582 | + obj, |
| 583 | + how, |
| 584 | + func, |
| 585 | + raw=False, |
| 586 | + result_type=None, |
| 587 | + args=args, |
| 588 | + kwds=kwds, |
| 589 | + ) |
| 590 | + |
| 591 | + def apply(self) -> FrameOrSeriesUnion: |
| 592 | + obj = self.obj |
| 593 | + func = self.f |
| 594 | + args = self.args |
| 595 | + kwds = self.kwds |
| 596 | + |
| 597 | + if len(obj) == 0: |
| 598 | + return self.apply_empty_result() |
| 599 | + |
| 600 | + # dispatch to agg |
| 601 | + if isinstance(func, (list, dict)): |
| 602 | + return obj.aggregate(func, *args, **kwds) |
| 603 | + |
| 604 | + # if we are a string, try to dispatch |
| 605 | + if isinstance(func, str): |
| 606 | + return obj._try_aggregate_string_function(func, *args, **kwds) |
| 607 | + |
| 608 | + return self.apply_standard() |
| 609 | + |
| 610 | + def apply_empty_result(self) -> Series: |
| 611 | + obj = self.obj |
| 612 | + return obj._constructor(dtype=obj.dtype, index=obj.index).__finalize__( |
| 613 | + obj, method="apply" |
| 614 | + ) |
| 615 | + |
| 616 | + def apply_standard(self) -> FrameOrSeriesUnion: |
| 617 | + f = self.f |
| 618 | + obj = self.obj |
| 619 | + |
| 620 | + with np.errstate(all="ignore"): |
| 621 | + if isinstance(f, np.ufunc): |
| 622 | + return f(obj) |
| 623 | + |
| 624 | + # row-wise access |
| 625 | + if is_extension_array_dtype(obj.dtype) and hasattr(obj._values, "map"): |
| 626 | + # GH#23179 some EAs do not have `map` |
| 627 | + mapped = obj._values.map(f) |
| 628 | + else: |
| 629 | + values = obj.astype(object)._values |
| 630 | + mapped = lib.map_infer(values, f, convert=self.convert_dtype) |
| 631 | + |
| 632 | + if len(mapped) and isinstance(mapped[0], ABCSeries): |
| 633 | + # GH 25959 use pd.array instead of tolist |
| 634 | + # so extension arrays can be used |
| 635 | + return obj._constructor_expanddim(pd_array(mapped), index=obj.index) |
| 636 | + else: |
| 637 | + return obj._constructor(mapped, index=obj.index).__finalize__( |
| 638 | + obj, method="apply" |
| 639 | + ) |
0 commit comments