80
80
81
81
82
82
class NumericIndex (Index ):
83
- """
84
- Provide numeric type operations.
85
-
86
- This is an abstract class.
87
- """
83
+ _numeric_index_descr_args = {
84
+ "klass" : "NumericIndex" ,
85
+ "ltype" : "integer or float" ,
86
+ "dtype" : "inferred" ,
87
+ "extra" : "" ,
88
+ }
89
+ __doc__ = _num_index_shared_docs ["class_descr" ] % _numeric_index_descr_args
88
90
91
+ _typ = "numericindex"
89
92
_values : np .ndarray
90
93
_default_dtype : np .dtype | None = None
91
- _dtype_validation_metadata : tuple [Callable [..., bool ], str ]
92
-
94
+ _dtype_validation_metadata : tuple [Callable [..., bool ], str ] = (
95
+ is_numeric_dtype ,
96
+ "numeric type" ,
97
+ )
93
98
_is_numeric_dtype = True
94
99
_can_hold_strings = False
95
100
101
+ @cache_readonly
102
+ def _can_hold_na (self ) -> bool :
103
+ if is_float_dtype (self .dtype ):
104
+ return True
105
+ else :
106
+ return False
107
+
108
+ @property
109
+ def _engine_type (self ):
110
+ return {
111
+ np .int8 : libindex .Int8Engine ,
112
+ np .int16 : libindex .Int16Engine ,
113
+ np .int32 : libindex .Int32Engine ,
114
+ np .int64 : libindex .Int64Engine ,
115
+ np .uint8 : libindex .UInt8Engine ,
116
+ np .uint16 : libindex .UInt16Engine ,
117
+ np .uint32 : libindex .UInt32Engine ,
118
+ np .uint64 : libindex .UInt64Engine ,
119
+ np .float32 : libindex .Float32Engine ,
120
+ np .float64 : libindex .Float64Engine ,
121
+ }[self .dtype .type ]
122
+
123
+ @cache_readonly
124
+ def inferred_type (self ) -> str :
125
+ return {
126
+ "i" : "integer" ,
127
+ "u" : "integer" ,
128
+ "f" : "floating" ,
129
+ }[self .dtype .kind ]
130
+
96
131
def __new__ (cls , data = None , dtype : Dtype | None = None , copy = False , name = None ):
97
132
name = maybe_extract_name (name , data , cls )
98
133
@@ -170,9 +205,63 @@ def _ensure_dtype(
170
205
else :
171
206
return dtype
172
207
208
+ def __contains__ (self , key ) -> bool :
209
+ """
210
+ Check if key is a float and has a decimal. If it has, return False.
211
+ """
212
+ if not is_integer_dtype (self .dtype ):
213
+ return super ().__contains__ (key )
214
+
215
+ hash (key )
216
+ try :
217
+ if is_float (key ) and int (key ) != key :
218
+ # otherwise the `key in self._engine` check casts e.g. 1.1 -> 1
219
+ return False
220
+ return key in self ._engine
221
+ except (OverflowError , TypeError , ValueError ):
222
+ return False
223
+
224
+ @doc (Index .astype )
225
+ def astype (self , dtype , copy = True ):
226
+ if is_float_dtype (self .dtype ):
227
+ dtype = pandas_dtype (dtype )
228
+ if needs_i8_conversion (dtype ):
229
+ raise TypeError (
230
+ f"Cannot convert Float64Index to dtype { dtype } ; integer "
231
+ "values are required for conversion"
232
+ )
233
+ elif is_integer_dtype (dtype ) and not is_extension_array_dtype (dtype ):
234
+ # TODO(jreback); this can change once we have an EA Index type
235
+ # GH 13149
236
+ arr = astype_nansafe (self ._values , dtype = dtype )
237
+ if isinstance (self , Float64Index ):
238
+ return Int64Index (arr , name = self .name )
239
+ else :
240
+ return NumericIndex (arr , name = self .name , dtype = dtype )
241
+
242
+ return super ().astype (dtype , copy = copy )
243
+
173
244
# ----------------------------------------------------------------
174
245
# Indexing Methods
175
246
247
+ @doc (Index ._should_fallback_to_positional )
248
+ def _should_fallback_to_positional (self ) -> bool :
249
+ if self .inferred_type == "floating" :
250
+ return False
251
+
252
+ return super ()._should_fallback_to_positional ()
253
+
254
+ @doc (Index ._convert_slice_indexer )
255
+ def _convert_slice_indexer (self , key : slice , kind : str ):
256
+ if is_float_dtype (self .dtype ):
257
+ assert kind in ["loc" , "getitem" ]
258
+
259
+ # We always treat __getitem__ slicing as label-based
260
+ # translate to locations
261
+ return self .slice_indexer (key .start , key .stop , key .step , kind = kind )
262
+
263
+ return super ()._convert_slice_indexer (key , kind = kind )
264
+
176
265
@doc (Index ._maybe_cast_slice_bound )
177
266
def _maybe_cast_slice_bound (self , label , side : str , kind = lib .no_default ):
178
267
assert kind in ["loc" , "getitem" , None , lib .no_default ]
@@ -181,6 +270,21 @@ def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default):
181
270
# we will try to coerce to integers
182
271
return self ._maybe_cast_indexer (label )
183
272
273
+ @doc (Index ._convert_arr_indexer )
274
+ def _convert_arr_indexer (self , keyarr ) -> np .ndarray :
275
+ if is_unsigned_integer_dtype (self .dtype ):
276
+ # Cast the indexer to uint64 if possible so that the values returned
277
+ # from indexing are also uint64.
278
+ dtype = None
279
+ if is_integer_dtype (keyarr ) or (
280
+ lib .infer_dtype (keyarr , skipna = False ) == "integer"
281
+ ):
282
+ dtype = np .dtype (np .uint64 )
283
+
284
+ return com .asarray_tuplesafe (keyarr , dtype = dtype )
285
+
286
+ return super ()._convert_arr_indexer (keyarr )
287
+
184
288
# ----------------------------------------------------------------
185
289
186
290
@doc (Index ._shallow_copy )
@@ -212,13 +316,13 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
212
316
return is_numeric_dtype (dtype )
213
317
214
318
@classmethod
215
- def _assert_safe_casting (cls , data , subarr ):
319
+ def _assert_safe_casting (cls , data , subarr ) -> None :
216
320
"""
217
- Subclasses need to override this only if the process of casting data
218
- from some accepted dtype to the internal dtype(s) bears the risk of
219
- truncation (e.g. float to int).
321
+ Ensure incoming data can be represented with matching signed-ness.
220
322
"""
221
- pass
323
+ if is_integer_dtype (subarr .dtype ):
324
+ if not np .array_equal (data , subarr ):
325
+ raise TypeError ("Unsafe NumPy casting, you must explicitly cast" )
222
326
223
327
@property
224
328
def _is_all_dates (self ) -> bool :
0 commit comments