Skip to content

Commit c917bd4

Browse files
committed
rebase after pandas-dev#41472
1 parent 4c67159 commit c917bd4

File tree

8 files changed

+126
-37
lines changed

8 files changed

+126
-37
lines changed

pandas/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
RangeIndex,
8080
Float64Index,
8181
MultiIndex,
82+
NumericIndex,
8283
IntervalIndex,
8384
TimedeltaIndex,
8485
DatetimeIndex,

pandas/core/index.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import warnings
22

33
from pandas.core.indexes.api import ( # noqa:F401
4-
BaseNumericIndex,
54
CategoricalIndex,
65
DatetimeIndex,
76
Float64Index,
@@ -10,6 +9,7 @@
109
IntervalIndex,
1110
MultiIndex,
1211
NaT,
12+
NumericIndex,
1313
PeriodIndex,
1414
RangeIndex,
1515
TimedeltaIndex,

pandas/core/indexes/api.py

-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from pandas.core.indexes.interval import IntervalIndex
2323
from pandas.core.indexes.multi import MultiIndex
2424
from pandas.core.indexes.numeric import (
25-
BaseNumericIndex,
2625
Float64Index,
2726
Int64Index,
2827
NumericIndex,
@@ -48,7 +47,6 @@
4847
"Index",
4948
"MultiIndex",
5049
"NumericIndex",
51-
"BaseNumericIndex",
5250
"Float64Index",
5351
"Int64Index",
5452
"CategoricalIndex",

pandas/core/indexes/base.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2435,16 +2435,17 @@ def _is_numeric_index(cls) -> bool:
24352435
24362436
Used to check if an operation should return NumericIndex or plain Index.
24372437
"""
2438-
from pandas.core.indexes.numeric import (
2438+
from pandas import (
24392439
Float64Index,
24402440
Int64Index,
24412441
NumericIndex,
2442+
RangeIndex,
24422443
UInt64Index,
24432444
)
24442445

24452446
if not issubclass(cls, NumericIndex):
24462447
return False
2447-
elif issubclass(cls, (Int64Index, UInt64Index, Float64Index)):
2448+
elif issubclass(cls, (RangeIndex, Int64Index, UInt64Index, Float64Index)):
24482449
return False
24492450
else:
24502451
return True

pandas/core/indexes/numeric.py

+116-12
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,54 @@
8080

8181

8282
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
8890

91+
_typ = "numericindex"
8992
_values: np.ndarray
9093
_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+
)
9398
_is_numeric_dtype = True
9499
_can_hold_strings = False
95100

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+
96131
def __new__(cls, data=None, dtype: Dtype | None = None, copy=False, name=None):
97132
name = maybe_extract_name(name, data, cls)
98133

@@ -170,9 +205,63 @@ def _ensure_dtype(
170205
else:
171206
return dtype
172207

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+
173244
# ----------------------------------------------------------------
174245
# Indexing Methods
175246

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+
176265
@doc(Index._maybe_cast_slice_bound)
177266
def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default):
178267
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):
181270
# we will try to coerce to integers
182271
return self._maybe_cast_indexer(label)
183272

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+
184288
# ----------------------------------------------------------------
185289

186290
@doc(Index._shallow_copy)
@@ -212,13 +316,13 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
212316
return is_numeric_dtype(dtype)
213317

214318
@classmethod
215-
def _assert_safe_casting(cls, data, subarr):
319+
def _assert_safe_casting(cls, data, subarr) -> None:
216320
"""
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.
220322
"""
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")
222326

223327
@property
224328
def _is_all_dates(self) -> bool:

pandas/core/indexes/range.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@
4242
import pandas.core.indexes.base as ibase
4343
from pandas.core.indexes.base import maybe_extract_name
4444
from pandas.core.indexes.numeric import (
45-
BaseNumericIndex,
4645
Float64Index,
4746
Int64Index,
47+
NumericIndex,
4848
)
4949
from pandas.core.ops.common import unpack_zerodim_and_defer
5050

@@ -54,7 +54,7 @@
5454
_empty_range = range(0)
5555

5656

57-
class RangeIndex(BaseNumericIndex):
57+
class RangeIndex(NumericIndex):
5858
"""
5959
Immutable Index implementing a monotonic integer range.
6060

pandas/tests/indexes/test_numpy_compat.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
)
1313
import pandas._testing as tm
1414
from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin
15-
from pandas.core.indexes.numeric import BaseNumericIndex
15+
from pandas.core.indexes.numeric import NumericIndex
1616

1717

1818
@pytest.mark.parametrize(
@@ -51,7 +51,7 @@ def test_numpy_ufuncs_basic(index, func):
5151
with tm.external_error_raised((TypeError, AttributeError)):
5252
with np.errstate(all="ignore"):
5353
func(index)
54-
elif isinstance(index, BaseNumericIndex):
54+
elif isinstance(index, NumericIndex):
5555
# coerces to float (e.g. np.sin)
5656
with np.errstate(all="ignore"):
5757
result = func(index)
@@ -106,7 +106,7 @@ def test_numpy_ufuncs_other(index, func, request):
106106
with tm.external_error_raised(TypeError):
107107
func(index)
108108

109-
elif isinstance(index, BaseNumericIndex):
109+
elif isinstance(index, NumericIndex):
110110
# Results in bool array
111111
result = func(index)
112112
assert isinstance(result, np.ndarray)

pandas/tests/indexes/test_setops.py

-15
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,7 @@
3131

3232
COMPATIBLE_INCONSISTENT_PAIRS = [
3333
(np.float64, np.int64),
34-
(np.float64, np.int32),
35-
(np.float64, np.int16),
36-
(np.float64, np.int8),
3734
(np.float64, np.uint64),
38-
(np.float64, np.uint32),
39-
(np.float64, np.uint16),
40-
(np.float64, np.uint8),
41-
(np.float32, np.int64),
42-
(np.float32, np.int32),
43-
(np.float32, np.int16),
44-
(np.float32, np.int8),
45-
(np.float32, np.uint64),
46-
(np.float32, np.uint32),
47-
(np.float32, np.uint16),
48-
(np.float32, np.uint8),
49-
(np.float32, np.float64),
5035
]
5136

5237

0 commit comments

Comments
 (0)