Skip to content

Commit 5748b64

Browse files
authored
REF: share fillna (#36488)
1 parent 6f2c60e commit 5748b64

File tree

4 files changed

+42
-78
lines changed

4 files changed

+42
-78
lines changed

pandas/core/arrays/_mixins.py

+33
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from pandas.compat.numpy import function as nv
77
from pandas.errors import AbstractMethodError
88
from pandas.util._decorators import cache_readonly, doc
9+
from pandas.util._validators import validate_fillna_kwargs
910

11+
from pandas.core.dtypes.inference import is_array_like
12+
13+
from pandas.core import missing
1014
from pandas.core.algorithms import take, unique
1115
from pandas.core.array_algos.transforms import shift
1216
from pandas.core.arrays.base import ExtensionArray
@@ -194,3 +198,32 @@ def __getitem__(self, key):
194198

195199
def _validate_getitem_key(self, key):
196200
return check_array_indexer(self, key)
201+
202+
@doc(ExtensionArray.fillna)
203+
def fillna(self: _T, value=None, method=None, limit=None) -> _T:
204+
value, method = validate_fillna_kwargs(value, method)
205+
206+
mask = self.isna()
207+
208+
# TODO: share this with EA base class implementation
209+
if is_array_like(value):
210+
if len(value) != len(self):
211+
raise ValueError(
212+
f"Length of 'value' does not match. Got ({len(value)}) "
213+
f" expected {len(self)}"
214+
)
215+
value = value[mask]
216+
217+
if mask.any():
218+
if method is not None:
219+
func = missing.get_fill_func(method)
220+
new_values = func(self._ndarray.copy(), limit=limit, mask=mask)
221+
# TODO: PandasArray didnt used to copy, need tests for this
222+
new_values = self._from_backing_data(new_values)
223+
else:
224+
# fill with value
225+
new_values = self.copy()
226+
new_values[mask] = value
227+
else:
228+
new_values = self.copy()
229+
return new_values

pandas/core/arrays/datetimelike.py

+1-41
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from pandas.compat.numpy import function as nv
2929
from pandas.errors import AbstractMethodError, NullFrequencyError, PerformanceWarning
3030
from pandas.util._decorators import Appender, Substitution, cache_readonly
31-
from pandas.util._validators import validate_fillna_kwargs
3231

3332
from pandas.core.dtypes.common import (
3433
is_categorical_dtype,
@@ -48,11 +47,9 @@
4847
is_unsigned_integer_dtype,
4948
pandas_dtype,
5049
)
51-
from pandas.core.dtypes.generic import ABCSeries
52-
from pandas.core.dtypes.inference import is_array_like
5350
from pandas.core.dtypes.missing import is_valid_nat_for_dtype, isna
5451

55-
from pandas.core import missing, nanops, ops
52+
from pandas.core import nanops, ops
5653
from pandas.core.algorithms import checked_add_with_arr, unique1d, value_counts
5754
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
5855
from pandas.core.arrays.base import ExtensionOpsMixin
@@ -979,43 +976,6 @@ def _maybe_mask_results(self, result, fill_value=iNaT, convert=None):
979976
result[self._isnan] = fill_value
980977
return result
981978

982-
def fillna(self, value=None, method=None, limit=None):
983-
# TODO(GH-20300): remove this
984-
# Just overriding to ensure that we avoid an astype(object).
985-
# Either 20300 or a `_values_for_fillna` would avoid this duplication.
986-
if isinstance(value, ABCSeries):
987-
value = value.array
988-
989-
value, method = validate_fillna_kwargs(value, method)
990-
991-
mask = self.isna()
992-
993-
if is_array_like(value):
994-
if len(value) != len(self):
995-
raise ValueError(
996-
f"Length of 'value' does not match. Got ({len(value)}) "
997-
f" expected {len(self)}"
998-
)
999-
value = value[mask]
1000-
1001-
if mask.any():
1002-
if method is not None:
1003-
if method == "pad":
1004-
func = missing.pad_1d
1005-
else:
1006-
func = missing.backfill_1d
1007-
1008-
values = self.copy()
1009-
new_values = func(values, limit=limit, mask=mask)
1010-
new_values = self._from_backing_data(new_values)
1011-
else:
1012-
# fill with value
1013-
new_values = self.copy()
1014-
new_values[mask] = value
1015-
else:
1016-
new_values = self.copy()
1017-
return new_values
1018-
1019979
# ------------------------------------------------------------------
1020980
# Frequency Properties/Methods
1021981

pandas/core/arrays/numpy_.py

+1-33
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import numbers
2-
from typing import Optional, Tuple, Type, Union
2+
from typing import Tuple, Type, Union
33

44
import numpy as np
55
from numpy.lib.mixins import NDArrayOperatorsMixin
66

77
from pandas._libs import lib
88
from pandas._typing import Scalar
99
from pandas.compat.numpy import function as nv
10-
from pandas.util._validators import validate_fillna_kwargs
1110

1211
from pandas.core.dtypes.dtypes import ExtensionDtype
13-
from pandas.core.dtypes.inference import is_array_like
1412
from pandas.core.dtypes.missing import isna
1513

1614
from pandas import compat
@@ -19,7 +17,6 @@
1917
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
2018
from pandas.core.arrays.base import ExtensionOpsMixin
2119
from pandas.core.construction import extract_array
22-
from pandas.core.missing import backfill_1d, pad_1d
2320

2421

2522
class PandasDtype(ExtensionDtype):
@@ -263,35 +260,6 @@ def _validate_setitem_value(self, value):
263260
def isna(self) -> np.ndarray:
264261
return isna(self._ndarray)
265262

266-
def fillna(
267-
self, value=None, method: Optional[str] = None, limit: Optional[int] = None
268-
) -> "PandasArray":
269-
# TODO(_values_for_fillna): remove this
270-
value, method = validate_fillna_kwargs(value, method)
271-
272-
mask = self.isna()
273-
274-
if is_array_like(value):
275-
if len(value) != len(self):
276-
raise ValueError(
277-
f"Length of 'value' does not match. Got ({len(value)}) "
278-
f" expected {len(self)}"
279-
)
280-
value = value[mask]
281-
282-
if mask.any():
283-
if method is not None:
284-
func = pad_1d if method == "pad" else backfill_1d
285-
new_values = func(self._ndarray, limit=limit, mask=mask)
286-
new_values = self._from_sequence(new_values, dtype=self.dtype)
287-
else:
288-
# fill with value
289-
new_values = self.copy()
290-
new_values[mask] = value
291-
else:
292-
new_values = self.copy()
293-
return new_values
294-
295263
def _validate_fill_value(self, fill_value):
296264
if fill_value is None:
297265
# Primarily for subclasses

pandas/core/missing.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ def interpolate_2d(
587587
return values
588588

589589

590-
def _cast_values_for_fillna(values, dtype: DtypeObj):
590+
def _cast_values_for_fillna(values, dtype: DtypeObj, has_mask: bool):
591591
"""
592592
Cast values to a dtype that algos.pad and algos.backfill can handle.
593593
"""
@@ -597,8 +597,10 @@ def _cast_values_for_fillna(values, dtype: DtypeObj):
597597
if needs_i8_conversion(dtype):
598598
values = values.view(np.int64)
599599

600-
elif is_integer_dtype(values):
600+
elif is_integer_dtype(values) and not has_mask:
601601
# NB: this check needs to come after the datetime64 check above
602+
# has_mask check to avoid casting i8 values that have already
603+
# been cast from PeriodDtype
602604
values = ensure_float64(values)
603605

604606
return values
@@ -609,11 +611,12 @@ def _fillna_prep(values, mask=None, dtype: Optional[DtypeObj] = None):
609611
if dtype is None:
610612
dtype = values.dtype
611613

612-
if mask is None:
614+
has_mask = mask is not None
615+
if not has_mask:
613616
# This needs to occur before datetime/timedeltas are cast to int64
614617
mask = isna(values)
615618

616-
values = _cast_values_for_fillna(values, dtype)
619+
values = _cast_values_for_fillna(values, dtype, has_mask)
617620

618621
mask = mask.view(np.uint8)
619622
return values, mask

0 commit comments

Comments
 (0)