Skip to content

Commit 769fc54

Browse files
authored
REF: dispatch Block.fillna to putmask/where (#45911)
1 parent 6f8d279 commit 769fc54

File tree

1 file changed

+41
-50
lines changed

1 file changed

+41
-50
lines changed

pandas/core/internals/blocks.py

+41-50
Original file line numberDiff line numberDiff line change
@@ -1019,14 +1019,16 @@ def putmask(self, mask, new) -> list[Block]:
10191019
res_blocks.extend(rbs)
10201020
return res_blocks
10211021

1022-
def where(self, other, cond) -> list[Block]:
1022+
def where(self, other, cond, _downcast="infer") -> list[Block]:
10231023
"""
10241024
evaluate the block; return result block(s) from the result
10251025
10261026
Parameters
10271027
----------
10281028
other : a ndarray/object
10291029
cond : np.ndarray[bool], SparseArray[bool], or BooleanArray
1030+
_downcast : str or None, default "infer"
1031+
Private because we only specify it when calling from fillna.
10301032
10311033
Returns
10321034
-------
@@ -1066,7 +1068,7 @@ def where(self, other, cond) -> list[Block]:
10661068

10671069
block = self.coerce_to_target_dtype(other)
10681070
blocks = block.where(orig_other, cond)
1069-
return self._maybe_downcast(blocks, "infer")
1071+
return self._maybe_downcast(blocks, downcast=_downcast)
10701072

10711073
else:
10721074
# since _maybe_downcast would split blocks anyway, we
@@ -1083,7 +1085,7 @@ def where(self, other, cond) -> list[Block]:
10831085
oth = other[:, i : i + 1]
10841086

10851087
submask = cond[:, i : i + 1]
1086-
rbs = nb.where(oth, submask)
1088+
rbs = nb.where(oth, submask, _downcast=_downcast)
10871089
res_blocks.extend(rbs)
10881090
return res_blocks
10891091

@@ -1158,21 +1160,19 @@ def fillna(
11581160
if limit is not None:
11591161
mask[mask.cumsum(self.ndim - 1) > limit] = False
11601162

1161-
if self._can_hold_element(value):
1162-
nb = self if inplace else self.copy()
1163-
putmask_inplace(nb.values, mask, value)
1164-
return nb._maybe_downcast([nb], downcast)
1165-
1166-
elif self.ndim == 1 or self.shape[0] == 1:
1167-
blk = self.coerce_to_target_dtype(value)
1168-
# bc we have already cast, inplace=True may avoid an extra copy
1169-
return blk.fillna(value, limit=limit, inplace=True, downcast=None)
1170-
1163+
if inplace:
1164+
nbs = self.putmask(mask.T, value)
11711165
else:
1172-
# operate column-by-column
1173-
return self.split_and_operate(
1174-
type(self).fillna, value, limit=limit, inplace=inplace, downcast=None
1175-
)
1166+
# without _downcast, we would break
1167+
# test_fillna_dtype_conversion_equiv_replace
1168+
nbs = self.where(value, ~mask.T, _downcast=False)
1169+
1170+
# Note: blk._maybe_downcast vs self._maybe_downcast(nbs)
1171+
# makes a difference bc blk may have object dtype, which has
1172+
# different behavior in _maybe_downcast.
1173+
return extend_blocks(
1174+
[blk._maybe_downcast([blk], downcast=downcast) for blk in nbs]
1175+
)
11761176

11771177
def interpolate(
11781178
self,
@@ -1401,7 +1401,8 @@ def setitem(self, indexer, value):
14011401
else:
14021402
return self
14031403

1404-
def where(self, other, cond) -> list[Block]:
1404+
def where(self, other, cond, _downcast="infer") -> list[Block]:
1405+
# _downcast private bc we only specify it when calling from fillna
14051406
arr = self.values.T
14061407

14071408
cond = extract_bool_array(cond)
@@ -1429,7 +1430,7 @@ def where(self, other, cond) -> list[Block]:
14291430
# TestSetitemFloatIntervalWithIntIntervalValues
14301431
blk = self.coerce_to_target_dtype(orig_other)
14311432
nbs = blk.where(orig_other, orig_cond)
1432-
return self._maybe_downcast(nbs, "infer")
1433+
return self._maybe_downcast(nbs, downcast=_downcast)
14331434

14341435
elif isinstance(self, NDArrayBackedExtensionBlock):
14351436
# NB: not (yet) the same as
@@ -1485,39 +1486,29 @@ def fillna(
14851486
) -> list[Block]:
14861487
# Caller is responsible for validating limit; if int it is strictly positive
14871488

1488-
try:
1489-
new_values = self.values.fillna(value=value, limit=limit)
1490-
except (TypeError, ValueError) as err:
1491-
_catch_deprecated_value_error(err)
1492-
1493-
if is_interval_dtype(self.dtype):
1494-
# Discussion about what we want to support in the general
1495-
# case GH#39584
1496-
blk = self.coerce_to_target_dtype(value)
1497-
return blk.fillna(value, limit, inplace, downcast)
1498-
1499-
elif isinstance(self, NDArrayBackedExtensionBlock):
1500-
# We support filling a DatetimeTZ with a `value` whose timezone
1501-
# is different by coercing to object.
1502-
if self.dtype.kind == "m":
1503-
# GH#45746
1504-
warnings.warn(
1505-
"The behavior of fillna with timedelta64[ns] dtype and "
1506-
f"an incompatible value ({type(value)}) is deprecated. "
1507-
"In a future version, this will cast to a common dtype "
1508-
"(usually object) instead of raising, matching the "
1509-
"behavior of other dtypes.",
1510-
FutureWarning,
1511-
stacklevel=find_stack_level(),
1512-
)
1513-
raise
1514-
blk = self.coerce_to_target_dtype(value)
1515-
return blk.fillna(value, limit, inplace, downcast)
1516-
1517-
else:
1489+
if self.dtype.kind == "m":
1490+
try:
1491+
res_values = self.values.fillna(value, limit=limit)
1492+
except (ValueError, TypeError):
1493+
# GH#45746
1494+
warnings.warn(
1495+
"The behavior of fillna with timedelta64[ns] dtype and "
1496+
f"an incompatible value ({type(value)}) is deprecated. "
1497+
"In a future version, this will cast to a common dtype "
1498+
"(usually object) instead of raising, matching the "
1499+
"behavior of other dtypes.",
1500+
FutureWarning,
1501+
stacklevel=find_stack_level(),
1502+
)
15181503
raise
1504+
else:
1505+
res_blk = self.make_block(res_values)
1506+
return [res_blk]
15191507

1520-
return [self.make_block_same_class(values=new_values)]
1508+
# TODO: since this now dispatches to super, which in turn dispatches
1509+
# to putmask, it may *actually* respect 'inplace=True'. If so, add
1510+
# tests for this.
1511+
return super().fillna(value, limit=limit, inplace=inplace, downcast=downcast)
15211512

15221513
def delete(self, loc) -> Block:
15231514
# This will be unnecessary if/when __array_function__ is implemented

0 commit comments

Comments
 (0)