diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index 968b39088e684..ae7e8191fc482 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -47,6 +47,7 @@ needs_i8_conversion, pandas_dtype, ) +from pandas.core.dtypes.dtypes import PandasDtype from pandas.core.dtypes.generic import ( ABCDatetimeArray, ABCExtensionArray, @@ -1929,7 +1930,6 @@ def diff(arr, n: int, axis: int = 0, stacklevel=3): ------- shifted """ - from pandas.core.arrays import PandasDtype n = int(n) na = np.nan @@ -1942,7 +1942,7 @@ def diff(arr, n: int, axis: int = 0, stacklevel=3): if isinstance(dtype, PandasDtype): # PandasArray cannot necessarily hold shifted versions of itself. - arr = np.asarray(arr) + arr = arr.to_numpy() dtype = arr.dtype if is_extension_array_dtype(dtype): diff --git a/pandas/core/arrays/__init__.py b/pandas/core/arrays/__init__.py index 93f679d13b33c..b6d98eb17eb6c 100644 --- a/pandas/core/arrays/__init__.py +++ b/pandas/core/arrays/__init__.py @@ -10,7 +10,7 @@ from pandas.core.arrays.integer import IntegerArray from pandas.core.arrays.interval import IntervalArray from pandas.core.arrays.masked import BaseMaskedArray -from pandas.core.arrays.numpy_ import PandasArray, PandasDtype +from pandas.core.arrays.numpy_ import PandasArray from pandas.core.arrays.period import PeriodArray, period_array from pandas.core.arrays.sparse import SparseArray from pandas.core.arrays.string_ import StringArray @@ -28,7 +28,6 @@ "IntegerArray", "IntervalArray", "PandasArray", - "PandasDtype", "PeriodArray", "period_array", "SparseArray", diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py index 787f89ab15679..9999a9ed411d8 100644 --- a/pandas/core/arrays/numpy_.py +++ b/pandas/core/arrays/numpy_.py @@ -1,7 +1,7 @@ from __future__ import annotations import numbers -from typing import Optional, Tuple, Type, Union +from typing import Optional, Tuple, Union import numpy as np from numpy.lib.mixins import NDArrayOperatorsMixin @@ -10,7 +10,7 @@ from pandas._typing import Dtype, NpDtype, Scalar from pandas.compat.numpy import function as nv -from pandas.core.dtypes.dtypes import ExtensionDtype +from pandas.core.dtypes.dtypes import PandasDtype from pandas.core.dtypes.missing import isna from pandas.core import nanops, ops @@ -19,101 +19,6 @@ from pandas.core.strings.object_array import ObjectStringArrayMixin -class PandasDtype(ExtensionDtype): - """ - A Pandas ExtensionDtype for NumPy dtypes. - - .. versionadded:: 0.24.0 - - This is mostly for internal compatibility, and is not especially - useful on its own. - - Parameters - ---------- - dtype : object - Object to be converted to a NumPy data type object. - - See Also - -------- - numpy.dtype - """ - - _metadata = ("_dtype",) - - def __init__(self, dtype: Optional[NpDtype]): - self._dtype = np.dtype(dtype) - - def __repr__(self) -> str: - return f"PandasDtype({repr(self.name)})" - - @property - def numpy_dtype(self) -> np.dtype: - """ - The NumPy dtype this PandasDtype wraps. - """ - return self._dtype - - @property - def name(self) -> str: - """ - A bit-width name for this data-type. - """ - return self._dtype.name - - @property - def type(self) -> Type[np.generic]: - """ - The type object used to instantiate a scalar of this NumPy data-type. - """ - return self._dtype.type - - @property - def _is_numeric(self) -> bool: - # exclude object, str, unicode, void. - return self.kind in set("biufc") - - @property - def _is_boolean(self) -> bool: - return self.kind == "b" - - @classmethod - def construct_from_string(cls, string: str) -> PandasDtype: - try: - dtype = np.dtype(string) - except TypeError as err: - if not isinstance(string, str): - msg = f"'construct_from_string' expects a string, got {type(string)}" - else: - msg = f"Cannot construct a 'PandasDtype' from '{string}'" - raise TypeError(msg) from err - return cls(dtype) - - @classmethod - def construct_array_type(cls) -> Type[PandasArray]: - """ - Return the array type associated with this dtype. - - Returns - ------- - type - """ - return PandasArray - - @property - def kind(self) -> str: - """ - A character code (one of 'biufcmMOSUV') identifying the general kind of data. - """ - return self._dtype.kind - - @property - def itemsize(self) -> int: - """ - The element size of this data-type object. - """ - return self._dtype.itemsize - - class PandasArray( OpsMixin, NDArrayBackedExtensionArray, diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 3cf6010a4f2c3..deafc17f76e10 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -31,7 +31,7 @@ to_offset, tz_compare, ) -from pandas._typing import Dtype, DtypeObj, Ordered +from pandas._typing import Dtype, DtypeObj, NpDtype, Ordered from pandas.core.dtypes.base import ExtensionDtype, register_extension_dtype from pandas.core.dtypes.generic import ABCCategoricalIndex, ABCIndex @@ -41,7 +41,12 @@ import pyarrow from pandas import Categorical - from pandas.core.arrays import DatetimeArray, IntervalArray, PeriodArray + from pandas.core.arrays import ( + DatetimeArray, + IntervalArray, + PandasArray, + PeriodArray, + ) str_type = str @@ -1230,3 +1235,103 @@ def _get_common_dtype(self, dtypes: List[DtypeObj]) -> Optional[DtypeObj]: if common == object: return np.dtype(object) return IntervalDtype(common, closed=closed) + + +class PandasDtype(ExtensionDtype): + """ + A Pandas ExtensionDtype for NumPy dtypes. + + .. versionadded:: 0.24.0 + + This is mostly for internal compatibility, and is not especially + useful on its own. + + Parameters + ---------- + dtype : object + Object to be converted to a NumPy data type object. + + See Also + -------- + numpy.dtype + """ + + _metadata = ("_dtype",) + + def __init__(self, dtype: Optional[Union[NpDtype, PandasDtype]]): + if isinstance(dtype, PandasDtype): + # make constructor univalent + dtype = dtype.numpy_dtype + self._dtype = np.dtype(dtype) + + def __repr__(self) -> str: + return f"PandasDtype({repr(self.name)})" + + @property + def numpy_dtype(self) -> np.dtype: + """ + The NumPy dtype this PandasDtype wraps. + """ + return self._dtype + + @property + def name(self) -> str: + """ + A bit-width name for this data-type. + """ + return self._dtype.name + + @property + def type(self) -> Type[np.generic]: + """ + The type object used to instantiate a scalar of this NumPy data-type. + """ + return self._dtype.type + + @property + def _is_numeric(self) -> bool: + # exclude object, str, unicode, void. + return self.kind in set("biufc") + + @property + def _is_boolean(self) -> bool: + return self.kind == "b" + + @classmethod + def construct_from_string(cls, string: str) -> PandasDtype: + try: + dtype = np.dtype(string) + except TypeError as err: + if not isinstance(string, str): + msg = f"'construct_from_string' expects a string, got {type(string)}" + else: + msg = f"Cannot construct a 'PandasDtype' from '{string}'" + raise TypeError(msg) from err + return cls(dtype) + + @classmethod + def construct_array_type(cls) -> Type[PandasArray]: + """ + Return the array type associated with this dtype. + + Returns + ------- + type + """ + from pandas.core.arrays import PandasArray + + return PandasArray + + @property + def kind(self) -> str: + """ + A character code (one of 'biufcmMOSUV') identifying the general kind of data. + """ + return self._dtype.kind + + @property + def itemsize(self) -> int: + """ + The element size of this data-type object. + """ + return self._dtype.itemsize diff --git a/pandas/core/internals/array_manager.py b/pandas/core/internals/array_manager.py index 6d6f9e8f4d3e8..0f677ff3180be 100644 --- a/pandas/core/internals/array_manager.py +++ b/pandas/core/internals/array_manager.py @@ -18,12 +18,12 @@ is_extension_array_dtype, is_numeric_dtype, ) -from pandas.core.dtypes.dtypes import ExtensionDtype +from pandas.core.dtypes.dtypes import ExtensionDtype, PandasDtype from pandas.core.dtypes.generic import ABCDataFrame, ABCSeries from pandas.core.dtypes.missing import isna import pandas.core.algorithms as algos -from pandas.core.arrays import ExtensionArray, PandasDtype +from pandas.core.arrays import ExtensionArray from pandas.core.arrays.sparse import SparseDtype from pandas.core.construction import extract_array from pandas.core.indexers import maybe_convert_indices diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 1a44968d5bf0e..0ff3637669388 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -50,7 +50,7 @@ is_sparse, pandas_dtype, ) -from pandas.core.dtypes.dtypes import CategoricalDtype, ExtensionDtype +from pandas.core.dtypes.dtypes import CategoricalDtype, ExtensionDtype, PandasDtype from pandas.core.dtypes.generic import ABCDataFrame, ABCIndex, ABCPandasArray, ABCSeries from pandas.core.dtypes.missing import isna @@ -67,7 +67,6 @@ DatetimeArray, ExtensionArray, PandasArray, - PandasDtype, TimedeltaArray, ) from pandas.core.base import PandasObject diff --git a/pandas/tests/arrays/test_numpy.py b/pandas/tests/arrays/test_numpy.py index 86793c4ec50dd..753ec99e683e6 100644 --- a/pandas/tests/arrays/test_numpy.py +++ b/pandas/tests/arrays/test_numpy.py @@ -5,10 +5,11 @@ import numpy as np import pytest +from pandas.core.dtypes.dtypes import PandasDtype + import pandas as pd import pandas._testing as tm from pandas.arrays import PandasArray -from pandas.core.arrays.numpy_ import PandasDtype @pytest.fixture( @@ -86,6 +87,13 @@ def test_constructor_from_string(): assert result == expected +def test_dtype_univalent(any_numpy_dtype): + dtype = PandasDtype(any_numpy_dtype) + + result = PandasDtype(dtype) + assert result == dtype + + # ---------------------------------------------------------------------------- # Construction diff --git a/pandas/tests/extension/test_numpy.py b/pandas/tests/extension/test_numpy.py index 4e054a07d8ef1..8b0dee832863b 100644 --- a/pandas/tests/extension/test_numpy.py +++ b/pandas/tests/extension/test_numpy.py @@ -16,11 +16,11 @@ import numpy as np import pytest -from pandas.core.dtypes.dtypes import ExtensionDtype +from pandas.core.dtypes.dtypes import ExtensionDtype, PandasDtype import pandas as pd import pandas._testing as tm -from pandas.core.arrays.numpy_ import PandasArray, PandasDtype +from pandas.core.arrays.numpy_ import PandasArray from . import base @@ -313,6 +313,10 @@ def test_arith_series_with_scalar(self, data, all_arithmetic_operators): def test_arith_series_with_array(self, data, all_arithmetic_operators): super().test_arith_series_with_array(data, all_arithmetic_operators) + @skip_nested + def test_arith_frame_with_scalar(self, data, all_arithmetic_operators): + super().test_arith_frame_with_scalar(data, all_arithmetic_operators) + class TestPrinting(BaseNumPyTests, base.BasePrintingTests): pass