1from __future__ import annotations
2
3from typing import (
4 TYPE_CHECKING,
5 Any,
6 ClassVar,
7)
8
9import numpy as np
10
11from pandas.util._decorators import set_module
12
13from pandas.core.dtypes.base import register_extension_dtype
14from pandas.core.dtypes.common import is_integer_dtype
15
16from pandas.core.arrays.numeric import (
17 NumericArray,
18 NumericDtype,
19)
20
21if TYPE_CHECKING:
22 from collections.abc import Callable
23
24
25class IntegerDtype(NumericDtype):
26 """
27 An ExtensionDtype to hold a single size & kind of integer dtype.
28
29 These specific implementations are subclasses of the non-public
30 IntegerDtype. For example, we have Int8Dtype to represent signed int 8s.
31
32 The attributes name & type are set when these subclasses are created.
33 """
34
35 # The value used to fill '_data' to avoid upcasting
36 _internal_fill_value = 1
37 _default_np_dtype = np.dtype(np.int64)
38 _checker: Callable[[Any], bool] = is_integer_dtype
39
40 def construct_array_type(self) -> type[IntegerArray]:
41 """
42 Return the array type associated with this dtype.
43
44 Returns
45 -------
46 type
47 """
48 return IntegerArray
49
50 @classmethod
51 def _get_dtype_mapping(cls) -> dict[np.dtype, IntegerDtype]:
52 return NUMPY_INT_TO_DTYPE
53
54 @classmethod
55 def _safe_cast(cls, values: np.ndarray, dtype: np.dtype, copy: bool) -> np.ndarray:
56 """
57 Safely cast the values to the given dtype.
58
59 "safe" in this context means the casting is lossless. e.g. if 'values'
60 has a floating dtype, each value must be an integer.
61 """
62 try:
63 return values.astype(dtype, casting="safe", copy=copy)
64 except TypeError as err:
65 casted = values.astype(dtype, copy=copy)
66 if (casted == values).all():
67 return casted
68
69 raise TypeError(
70 f"cannot safely cast non-equivalent {values.dtype} to {np.dtype(dtype)}"
71 ) from err
72
73
74@set_module("pandas.arrays")
75class IntegerArray(NumericArray):
76 """
77 Array of integer (optional missing) values.
78
79 Uses :attr:`pandas.NA` as the missing value.
80
81 .. warning::
82
83 IntegerArray is currently experimental, and its API or internal
84 implementation may change without warning.
85
86 We represent an IntegerArray with 2 numpy arrays:
87
88 - data: contains a numpy integer array of the appropriate dtype
89 - mask: a boolean array holding a mask on the data, True is missing
90
91 To construct an IntegerArray from generic array-like input, use
92 :func:`pandas.array` with one of the integer dtypes (see examples).
93
94 See :ref:`integer_na` for more.
95
96 Parameters
97 ----------
98 values : numpy.ndarray
99 A 1-d integer-dtype array.
100 mask : numpy.ndarray
101 A 1-d boolean-dtype array indicating missing values.
102 copy : bool, default False
103 Whether to copy the `values` and `mask`.
104
105 Attributes
106 ----------
107 None
108
109 Methods
110 -------
111 None
112
113 Returns
114 -------
115 IntegerArray
116
117 See Also
118 --------
119 array : Create an array using the appropriate dtype, including ``IntegerArray``.
120 Int32Dtype : An ExtensionDtype for int32 integer data.
121 UInt16Dtype : An ExtensionDtype for uint16 integer data.
122
123 Examples
124 --------
125 Create an IntegerArray with :func:`pandas.array`.
126
127 >>> int_array = pd.array([1, None, 3], dtype=pd.Int32Dtype())
128 >>> int_array
129 <IntegerArray>
130 [1, <NA>, 3]
131 Length: 3, dtype: Int32
132
133 String aliases for the dtypes are also available. They are capitalized.
134
135 >>> pd.array([1, None, 3], dtype="Int32")
136 <IntegerArray>
137 [1, <NA>, 3]
138 Length: 3, dtype: Int32
139
140 >>> pd.array([1, None, 3], dtype="UInt16")
141 <IntegerArray>
142 [1, <NA>, 3]
143 Length: 3, dtype: UInt16
144 """
145
146 _dtype_cls = IntegerDtype
147
148
149_dtype_docstring = """
150An ExtensionDtype for {dtype} integer data.
151
152Uses :attr:`pandas.NA` as its missing value, rather than :attr:`numpy.nan`.
153
154Attributes
155----------
156None
157
158Methods
159-------
160None
161
162See Also
163--------
164Int8Dtype : 8-bit nullable integer type.
165Int16Dtype : 16-bit nullable integer type.
166Int32Dtype : 32-bit nullable integer type.
167Int64Dtype : 64-bit nullable integer type.
168
169Examples
170--------
171For Int8Dtype:
172
173>>> ser = pd.Series([2, pd.NA], dtype=pd.Int8Dtype())
174>>> ser.dtype
175Int8Dtype()
176
177For Int16Dtype:
178
179>>> ser = pd.Series([2, pd.NA], dtype=pd.Int16Dtype())
180>>> ser.dtype
181Int16Dtype()
182
183For Int32Dtype:
184
185>>> ser = pd.Series([2, pd.NA], dtype=pd.Int32Dtype())
186>>> ser.dtype
187Int32Dtype()
188
189For Int64Dtype:
190
191>>> ser = pd.Series([2, pd.NA], dtype=pd.Int64Dtype())
192>>> ser.dtype
193Int64Dtype()
194
195For UInt8Dtype:
196
197>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt8Dtype())
198>>> ser.dtype
199UInt8Dtype()
200
201For UInt16Dtype:
202
203>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt16Dtype())
204>>> ser.dtype
205UInt16Dtype()
206
207For UInt32Dtype:
208
209>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt32Dtype())
210>>> ser.dtype
211UInt32Dtype()
212
213For UInt64Dtype:
214
215>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt64Dtype())
216>>> ser.dtype
217UInt64Dtype()
218"""
219
220# create the Dtype
221
222
223@register_extension_dtype
224@set_module("pandas")
225class Int8Dtype(IntegerDtype):
226 type = np.int8
227 name: ClassVar[str] = "Int8"
228 __doc__ = _dtype_docstring.format(dtype="int8")
229
230
231@register_extension_dtype
232@set_module("pandas")
233class Int16Dtype(IntegerDtype):
234 type = np.int16
235 name: ClassVar[str] = "Int16"
236 __doc__ = _dtype_docstring.format(dtype="int16")
237
238
239@register_extension_dtype
240@set_module("pandas")
241class Int32Dtype(IntegerDtype):
242 type = np.int32
243 name: ClassVar[str] = "Int32"
244 __doc__ = _dtype_docstring.format(dtype="int32")
245
246
247@register_extension_dtype
248@set_module("pandas")
249class Int64Dtype(IntegerDtype):
250 type = np.int64
251 name: ClassVar[str] = "Int64"
252 __doc__ = _dtype_docstring.format(dtype="int64")
253
254
255@register_extension_dtype
256@set_module("pandas")
257class UInt8Dtype(IntegerDtype):
258 type = np.uint8
259 name: ClassVar[str] = "UInt8"
260 __doc__ = _dtype_docstring.format(dtype="uint8")
261
262
263@register_extension_dtype
264@set_module("pandas")
265class UInt16Dtype(IntegerDtype):
266 type = np.uint16
267 name: ClassVar[str] = "UInt16"
268 __doc__ = _dtype_docstring.format(dtype="uint16")
269
270
271@register_extension_dtype
272@set_module("pandas")
273class UInt32Dtype(IntegerDtype):
274 type = np.uint32
275 name: ClassVar[str] = "UInt32"
276 __doc__ = _dtype_docstring.format(dtype="uint32")
277
278
279@register_extension_dtype
280@set_module("pandas")
281class UInt64Dtype(IntegerDtype):
282 type = np.uint64
283 name: ClassVar[str] = "UInt64"
284 __doc__ = _dtype_docstring.format(dtype="uint64")
285
286
287NUMPY_INT_TO_DTYPE: dict[np.dtype, IntegerDtype] = {
288 np.dtype(np.int8): Int8Dtype(),
289 np.dtype(np.int16): Int16Dtype(),
290 np.dtype(np.int32): Int32Dtype(),
291 np.dtype(np.int64): Int64Dtype(),
292 np.dtype(np.uint8): UInt8Dtype(),
293 np.dtype(np.uint16): UInt16Dtype(),
294 np.dtype(np.uint32): UInt32Dtype(),
295 np.dtype(np.uint64): UInt64Dtype(),
296}