@@ -575,7 +575,14 @@ def _is_builtin_func(self, arg):
575
575
576
576
@final
577
577
def _ea_wrap_cython_operation(
578
- self, kind: str, values, how: str, axis: int, min_count: int = -1, **kwargs
578
+ self,
579
+ kind: str,
580
+ values,
581
+ how: str,
582
+ axis: int,
583
+ min_count: int = -1,
584
+ mask: np.ndarray | None = None,
585
+ **kwargs,
579
586
) -> ArrayLike:
580
587
"""
581
588
If we have an ExtensionArray, unwrap, call _cython_operation, and
@@ -589,7 +596,7 @@ def _ea_wrap_cython_operation(
589
596
# operate on the tz-naive equivalents
590
597
values = values.view("M8[ns]")
591
598
res_values = self._cython_operation(
592
- kind, values, how, axis, min_count, **kwargs
599
+ kind, values, how, axis, min_count, mask=mask, **kwargs
593
600
)
594
601
if how in ["rank"]:
595
602
# preserve float64 dtype
@@ -603,7 +610,7 @@ def _ea_wrap_cython_operation(
603
610
# IntegerArray or BooleanArray
604
611
values = ensure_int_or_float(values)
605
612
res_values = self._cython_operation(
606
- kind, values, how, axis, min_count, **kwargs
613
+ kind, values, how, axis, min_count, mask=mask, **kwargs
607
614
)
608
615
dtype = maybe_cast_result_dtype(orig_values.dtype, how)
609
616
if isinstance(dtype, ExtensionDtype):
@@ -616,7 +623,7 @@ def _ea_wrap_cython_operation(
616
623
# FloatingArray
617
624
values = values.to_numpy(values.dtype.numpy_dtype, na_value=np.nan)
618
625
res_values = self._cython_operation(
619
- kind, values, how, axis, min_count, **kwargs
626
+ kind, values, how, axis, min_count, mask=mask, **kwargs
620
627
)
621
628
result = type(orig_values)._from_sequence(res_values)
622
629
return result
@@ -632,7 +639,8 @@ def _masked_ea_wrap_cython_operation(
632
639
values: BaseMaskedArray,
633
640
how: str,
634
641
axis: int,
635
- min_count: int = -1,
642
+ min_count: int,
643
+ mask: np.ndarray,
636
644
**kwargs,
637
645
) -> BaseMaskedArray:
638
646
"""
@@ -641,9 +649,6 @@ def _masked_ea_wrap_cython_operation(
641
649
"""
642
650
orig_values = values
643
651
644
- # isna just directly returns self._mask, so copy here to prevent
645
- # modifying the original
646
- mask = isna(values).copy()
647
652
arr = values._data
648
653
649
654
if is_integer_dtype(values.dtype) or is_bool_dtype(values.dtype):
@@ -658,7 +663,7 @@ def _masked_ea_wrap_cython_operation(
658
663
cls = dtype.construct_array_type()
659
664
660
665
return cls(
661
- res_values.astype(dtype.type, copy=False), mask.astype(bool, copy=False )
666
+ res_values.astype(dtype.type, copy=False), mask.astype(bool, copy=True )
662
667
)
663
668
664
669
@final
@@ -695,14 +700,20 @@ def _cython_operation(
695
700
cy_op.disallow_invalid_ops(dtype, is_numeric)
696
701
697
702
func_uses_mask = cy_op.uses_mask()
703
+
704
+ # Only compute the mask if we haven't yet
705
+ if func_uses_mask and mask is None:
706
+ mask = isna(values)
707
+
698
708
if is_extension_array_dtype(dtype):
699
709
if isinstance(values, BaseMaskedArray) and func_uses_mask:
710
+ assert mask is not None
700
711
return self._masked_ea_wrap_cython_operation(
701
- kind, values, how, axis, min_count, **kwargs
712
+ kind, values, how, axis, min_count, mask=mask, **kwargs
702
713
)
703
714
else:
704
715
return self._ea_wrap_cython_operation(
705
- kind, values, how, axis, min_count, **kwargs
716
+ kind, values, how, axis, min_count, mask=mask, **kwargs
706
717
)
707
718
708
719
elif values.ndim == 1:
0 commit comments