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