diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index c13eb3f109354..4986822a3ed5a 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -13,6 +13,7 @@ Union, cast, ) +import warnings import numpy as np @@ -191,16 +192,6 @@ def __init__(self, values, placement: BlockPlacement, ndim: int): self._mgr_locs = placement self.values = values - @property - def _holder(self): - """ - The array-like that can hold the underlying values. - - None for 'Block', overridden by subclasses that don't - use an ndarray. - """ - return None - @final @property def _consolidate_key(self): @@ -227,7 +218,14 @@ def _can_hold_na(self) -> bool: @final @property def is_categorical(self) -> bool: - return self._holder is Categorical + warnings.warn( + "Block.is_categorical is deprecated and will be removed in a " + "future version. Use isinstance(block.values, Categorical) " + "instead. See https://github.com/pandas-dev/pandas/issues/40226", + DeprecationWarning, + stacklevel=2, + ) + return isinstance(self.values, Categorical) @final def external_values(self): @@ -797,8 +795,10 @@ def _replace_list( """ See BlockManager._replace_list docstring. """ + values = self.values + # TODO: dont special-case Categorical - if self.is_categorical and len(algos.unique(dest_list)) == 1: + if isinstance(values, Categorical) and len(algos.unique(dest_list)) == 1: # We likely got here by tiling value inside NDFrame.replace, # so un-tile here return self.replace(src_list, dest_list[0], inplace, regex) @@ -813,17 +813,17 @@ def _replace_list( src_len = len(pairs) - 1 - if self.is_object: + if values.dtype == _dtype_obj: # Calculate the mask once, prior to the call of comp # in order to avoid repeating the same computations - mask = ~isna(self.values) + mask = ~isna(values) masks = [ - compare_or_regex_search(self.values, s[0], regex=regex, mask=mask) + compare_or_regex_search(values, s[0], regex=regex, mask=mask) for s in pairs ] else: # GH#38086 faster if we know we dont need to check for regex - masks = [missing.mask_missing(self.values, s[0]) for s in pairs] + masks = [missing.mask_missing(values, s[0]) for s in pairs] # error: Argument 1 to "extract_bool_array" has incompatible type # "Union[ExtensionArray, ndarray, bool]"; expected "Union[ExtensionArray, @@ -1504,11 +1504,6 @@ def putmask(self, mask, new) -> List[Block]: new_values[mask] = new return [self.make_block(values=new_values)] - @property - def _holder(self): - # For extension blocks, the holder is values-dependent. - return type(self.values) - @property def is_view(self) -> bool: """Extension arrays are never treated as views.""" @@ -1714,7 +1709,7 @@ def where(self, other, cond, errors="raise") -> List[Block]: # NotImplementedError for class not implementing `__setitem__` # TypeError for SparseArray, which implements just to raise # a TypeError - result = self._holder._from_sequence( + result = type(self.values)._from_sequence( np.where(cond, self.values, other), dtype=dtype ) @@ -1904,10 +1899,6 @@ class DatetimeLikeBlockMixin(NDArrayBackedExtensionBlock): def array_values(self): return ensure_wrapped_if_datetimelike(self.values) - @property - def _holder(self): - return type(self.array_values()) - class DatetimeBlock(DatetimeLikeBlockMixin): __slots__ = () diff --git a/pandas/core/internals/concat.py b/pandas/core/internals/concat.py index b40e2d90869ec..d5e549ec874da 100644 --- a/pandas/core/internals/concat.py +++ b/pandas/core/internals/concat.py @@ -39,6 +39,7 @@ import pandas.core.algorithms as algos from pandas.core.arrays import ( + Categorical, DatetimeArray, ExtensionArray, ) @@ -367,7 +368,7 @@ def get_reindexed_values(self, empty_dtype: DtypeObj, upcasted_na) -> ArrayLike: # preserve these for validation in concat_compat return self.block.values - if self.block.is_bool and not self.block.is_categorical: + if self.block.is_bool and not isinstance(self.block.values, Categorical): # External code requested filling/upcasting, bool values must # be upcasted to object to avoid being upcasted to numeric. values = self.block.astype(np.object_).values diff --git a/pandas/tests/internals/test_internals.py b/pandas/tests/internals/test_internals.py index ef1c3ec0c2860..fc06b85b1f954 100644 --- a/pandas/tests/internals/test_internals.py +++ b/pandas/tests/internals/test_internals.py @@ -27,11 +27,7 @@ ) import pandas._testing as tm import pandas.core.algorithms as algos -from pandas.core.arrays import ( - DatetimeArray, - SparseArray, - TimedeltaArray, -) +from pandas.core.arrays import SparseArray from pandas.core.internals import ( BlockManager, SingleBlockManager, @@ -320,6 +316,12 @@ def test_split(self): for res, exp in zip(result, expected): assert_block_equal(res, exp) + def test_is_categorical_deprecated(self): + # GH#40571 + blk = self.fblock + with tm.assert_produces_warning(DeprecationWarning): + blk.is_categorical + class TestBlockManager: def test_attrs(self): @@ -1302,21 +1304,6 @@ def test_should_store_categorical(self): assert not blk.should_store(np.asarray(cat)) -@pytest.mark.parametrize( - "typestr, holder", - [ - ("category", Categorical), - ("M8[ns]", DatetimeArray), - ("M8[ns, US/Central]", DatetimeArray), - ("m8[ns]", TimedeltaArray), - ("sparse", SparseArray), - ], -) -def test_holder(typestr, holder, block_maker): - blk = create_block(typestr, [1], maker=block_maker) - assert blk._holder is holder - - def test_validate_ndim(block_maker): values = np.array([1.0, 2.0]) placement = slice(2)