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 if np.can_cast(values.dtype, dtype, casting="safe"):
63 # asked and answered in ~0.2us; raising to find out costs ~5us per
64 # call, which read_csv pays per column per chunk (GH#55232)
65 return values.astype(dtype, copy=copy)
66
67 casted = values.astype(dtype, copy=copy)
68 if (casted == values).all():
69 return casted
70
71 raise TypeError(
72 f"cannot safely cast non-equivalent {values.dtype} to {np.dtype(dtype)}"
73 )
74
75
76@set_module("pandas.arrays")
77class IntegerArray(NumericArray):
78 """
79 Array of integer (optional missing) values.
80
81 Uses :attr:`pandas.NA` as the missing value.
82
83 .. warning::
84
85 IntegerArray is currently experimental, and its API or internal
86 implementation may change without warning.
87
88 We represent an IntegerArray with 2 numpy arrays:
89
90 - data: contains a numpy integer array of the appropriate dtype
91 - mask: a boolean array holding a mask on the data, True is missing
92
93 To construct an IntegerArray from generic array-like input, use
94 :func:`pandas.array` with one of the integer dtypes (see examples).
95
96 See :ref:`integer_na` for more.
97
98 Parameters
99 ----------
100 values : numpy.ndarray
101 A 1-d integer-dtype array.
102 mask : numpy.ndarray
103 A 1-d boolean-dtype array indicating missing values.
104 copy : bool, default False
105 Whether to copy the `values` and `mask`.
106
107 Attributes
108 ----------
109 None
110
111 Methods
112 -------
113 None
114
115 Returns
116 -------
117 IntegerArray
118
119 See Also
120 --------
121 array : Create an array using the appropriate dtype, including ``IntegerArray``.
122 Int32Dtype : An ExtensionDtype for int32 integer data.
123 UInt16Dtype : An ExtensionDtype for uint16 integer data.
124
125 Examples
126 --------
127 Create an IntegerArray with :func:`pandas.array`.
128
129 >>> int_array = pd.array([1, None, 3], dtype=pd.Int32Dtype())
130 >>> int_array
131 <IntegerArray>
132 [1, <NA>, 3]
133 Length: 3, dtype: Int32
134
135 String aliases for the dtypes are also available. They are capitalized.
136
137 >>> pd.array([1, None, 3], dtype="Int32")
138 <IntegerArray>
139 [1, <NA>, 3]
140 Length: 3, dtype: Int32
141
142 >>> pd.array([1, None, 3], dtype="UInt16")
143 <IntegerArray>
144 [1, <NA>, 3]
145 Length: 3, dtype: UInt16
146 """
147
148 _dtype_cls = IntegerDtype
149
150
151_dtype_docstring = """
152An ExtensionDtype for {dtype} integer data.
153
154Uses :attr:`pandas.NA` as its missing value, rather than :attr:`numpy.nan`.
155
156Attributes
157----------
158None
159
160Methods
161-------
162None
163
164See Also
165--------
166Int8Dtype : 8-bit nullable integer type.
167Int16Dtype : 16-bit nullable integer type.
168Int32Dtype : 32-bit nullable integer type.
169Int64Dtype : 64-bit nullable integer type.
170
171Examples
172--------
173For Int8Dtype:
174
175>>> ser = pd.Series([2, pd.NA], dtype=pd.Int8Dtype())
176>>> ser.dtype
177Int8Dtype()
178
179For Int16Dtype:
180
181>>> ser = pd.Series([2, pd.NA], dtype=pd.Int16Dtype())
182>>> ser.dtype
183Int16Dtype()
184
185For Int32Dtype:
186
187>>> ser = pd.Series([2, pd.NA], dtype=pd.Int32Dtype())
188>>> ser.dtype
189Int32Dtype()
190
191For Int64Dtype:
192
193>>> ser = pd.Series([2, pd.NA], dtype=pd.Int64Dtype())
194>>> ser.dtype
195Int64Dtype()
196
197For UInt8Dtype:
198
199>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt8Dtype())
200>>> ser.dtype
201UInt8Dtype()
202
203For UInt16Dtype:
204
205>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt16Dtype())
206>>> ser.dtype
207UInt16Dtype()
208
209For UInt32Dtype:
210
211>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt32Dtype())
212>>> ser.dtype
213UInt32Dtype()
214
215For UInt64Dtype:
216
217>>> ser = pd.Series([2, pd.NA], dtype=pd.UInt64Dtype())
218>>> ser.dtype
219UInt64Dtype()
220"""
221
222# create the Dtype
223
224
225@register_extension_dtype
226@set_module("pandas")
227class Int8Dtype(IntegerDtype):
228 type = np.int8
229 name: ClassVar[str] = "Int8"
230 __doc__ = _dtype_docstring.format(dtype="int8")
231
232
233@register_extension_dtype
234@set_module("pandas")
235class Int16Dtype(IntegerDtype):
236 type = np.int16
237 name: ClassVar[str] = "Int16"
238 __doc__ = _dtype_docstring.format(dtype="int16")
239
240
241@register_extension_dtype
242@set_module("pandas")
243class Int32Dtype(IntegerDtype):
244 type = np.int32
245 name: ClassVar[str] = "Int32"
246 __doc__ = _dtype_docstring.format(dtype="int32")
247
248
249@register_extension_dtype
250@set_module("pandas")
251class Int64Dtype(IntegerDtype):
252 type = np.int64
253 name: ClassVar[str] = "Int64"
254 __doc__ = _dtype_docstring.format(dtype="int64")
255
256
257@register_extension_dtype
258@set_module("pandas")
259class UInt8Dtype(IntegerDtype):
260 type = np.uint8
261 name: ClassVar[str] = "UInt8"
262 __doc__ = _dtype_docstring.format(dtype="uint8")
263
264
265@register_extension_dtype
266@set_module("pandas")
267class UInt16Dtype(IntegerDtype):
268 type = np.uint16
269 name: ClassVar[str] = "UInt16"
270 __doc__ = _dtype_docstring.format(dtype="uint16")
271
272
273@register_extension_dtype
274@set_module("pandas")
275class UInt32Dtype(IntegerDtype):
276 type = np.uint32
277 name: ClassVar[str] = "UInt32"
278 __doc__ = _dtype_docstring.format(dtype="uint32")
279
280
281@register_extension_dtype
282@set_module("pandas")
283class UInt64Dtype(IntegerDtype):
284 type = np.uint64
285 name: ClassVar[str] = "UInt64"
286 __doc__ = _dtype_docstring.format(dtype="uint64")
287
288
289NUMPY_INT_TO_DTYPE: dict[np.dtype, IntegerDtype] = {
290 np.dtype(np.int8): Int8Dtype(),
291 np.dtype(np.int16): Int16Dtype(),
292 np.dtype(np.int32): Int32Dtype(),
293 np.dtype(np.int64): Int64Dtype(),
294 np.dtype(np.uint8): UInt8Dtype(),
295 np.dtype(np.uint16): UInt16Dtype(),
296 np.dtype(np.uint32): UInt32Dtype(),
297 np.dtype(np.uint64): UInt64Dtype(),
298}