@@ -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