@@ -220,14 +220,12 @@ def apply_empty_result(self):
220220
221221 def apply_raw (self ):
222222 """ apply to the values as a numpy array """
223- try :
224- result = libreduction .compute_reduction (self .values , self .f , axis = self .axis )
225- except ValueError as err :
226- if "Function does not reduce" not in str (err ):
227- # catch only ValueError raised intentionally in libreduction
228- raise
229- # We expect np.apply_along_axis to give a two-dimensional result, or
230- # also raise.
223+ result , reduction_success = libreduction .compute_reduction (
224+ self .values , self .f , axis = self .axis
225+ )
226+
227+ # We expect np.apply_along_axis to give a two-dimensional result, or raise.
228+ if not reduction_success :
231229 result = np .apply_along_axis (self .f , self .axis , self .values )
232230
233231 # TODO: mixed type case
@@ -265,6 +263,9 @@ def apply_broadcast(self, target: "DataFrame") -> "DataFrame":
265263
266264 def apply_standard (self ):
267265
266+ # partial result that may be returned from reduction
267+ partial_result = None
268+
268269 # try to reduce first (by default)
269270 # this only matters if the reduction in values is of different dtype
270271 # e.g. if we want to apply to a SparseFrame, then can't directly reduce
@@ -292,13 +293,9 @@ def apply_standard(self):
292293 )
293294
294295 try :
295- result = libreduction .compute_reduction (
296+ result , reduction_success = libreduction .compute_reduction (
296297 values , self .f , axis = self .axis , dummy = dummy , labels = labels
297298 )
298- except ValueError as err :
299- if "Function does not reduce" not in str (err ):
300- # catch only ValueError raised intentionally in libreduction
301- raise
302299 except TypeError :
303300 # e.g. test_apply_ignore_failures we just ignore
304301 if not self .ignore_failures :
@@ -307,39 +304,53 @@ def apply_standard(self):
307304 # reached via numexpr; fall back to python implementation
308305 pass
309306 else :
310- return self .obj ._constructor_sliced (result , index = labels )
307+ if reduction_success :
308+ return self .obj ._constructor_sliced (result , index = labels )
311309
312- # compute the result using the series generator
313- results , res_index = self .apply_series_generator ()
310+ # no exceptions - however reduction was unsuccessful,
311+ # use the computed function result for first element
312+ partial_result = result [0 ]
313+ if isinstance (partial_result , ABCSeries ):
314+ partial_result = partial_result .infer_objects ()
315+
316+ # compute the result using the series generator,
317+ # use the result computed while trying to reduce if available.
318+ results , res_index = self .apply_series_generator (partial_result )
314319
315320 # wrap results
316321 return self .wrap_results (results , res_index )
317322
318- def apply_series_generator (self ) -> Tuple [ResType , "Index" ]:
323+ def apply_series_generator (self , partial_result = None ) -> Tuple [ResType , "Index" ]:
319324 series_gen = self .series_generator
320325 res_index = self .result_index
321326
322- keys = []
323327 results = {}
328+
329+ # If a partial result was already computed,
330+ # use it instead of running on the first element again
331+ series_gen_enumeration = enumerate (series_gen )
332+ if partial_result is not None :
333+ i , v = next (series_gen_enumeration )
334+ results [i ] = partial_result
335+
324336 if self .ignore_failures :
325337 successes = []
326- for i , v in enumerate ( series_gen ) :
338+ for i , v in series_gen_enumeration :
327339 try :
328340 results [i ] = self .f (v )
329341 except Exception :
330342 pass
331343 else :
332- keys .append (v .name )
333344 successes .append (i )
334345
335346 # so will work with MultiIndex
336347 if len (successes ) < len (res_index ):
337348 res_index = res_index .take (successes )
338349
339350 else :
340- for i , v in enumerate (series_gen ):
351+ for i , v in series_gen_enumeration :
352+
341353 results [i ] = self .f (v )
342- keys .append (v .name )
343354
344355 return results , res_index
345356
0 commit comments