Skip to content

Commit

Permalink
Make (Int|UInt|Float)64Index inherit from NumIndex
Browse files Browse the repository at this point in the history
  • Loading branch information
topper-123 committed Apr 27, 2021
1 parent 5bc2568 commit 81b014f
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 197 deletions.
6 changes: 3 additions & 3 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def __new__(
return Index._simple_new(data, name=name)

# index-like
elif isinstance(data, NumIndex) and dtype is None:
elif type(data) is NumIndex and dtype is None:
return NumIndex(data, name=name, copy=copy)
elif isinstance(data, (np.ndarray, Index, ABCSeries)):

Expand Down Expand Up @@ -5490,8 +5490,8 @@ def map(self, mapper, na_action=None):
# empty
attributes["dtype"] = self.dtype

if isinstance(self, NumIndex):
return NumIndex(new_values, **attributes)
if type(self) is NumIndex:
return type(self)(new_values, **attributes)

return Index(new_values, **attributes)

Expand Down
2 changes: 1 addition & 1 deletion pandas/core/indexes/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def astype(self, dtype, copy: bool = True) -> Index:
dtype = pandas_dtype(dtype)

cat = self.categories
if isinstance(cat, NumIndex):
if type(cat) is NumIndex:
try:
cat._validate_dtype(dtype)
except ValueError:
Expand Down
306 changes: 114 additions & 192 deletions pandas/core/indexes/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class NumericIndex(Index):
"""

_values: np.ndarray
_default_dtype: np.dtype
_default_dtype: np.dtype | None = None
_dtype_validation_metadata: tuple[Callable[..., bool], str]

_is_numeric_dtype = True
Expand Down Expand Up @@ -88,7 +88,10 @@ def _ensure_array(cls, data, dtype, copy: bool):
if issubclass(data.dtype.type, str):
cls._string_data_error(data)

if copy or not is_dtype_equal(data.dtype, cls._default_dtype):
if copy or (
cls._default_dtype is not None
and not is_dtype_equal(data.dtype, cls._default_dtype)
):
subarr = np.array(data, dtype=cls._default_dtype, copy=copy)
cls._assert_safe_casting(data, subarr)
else:
Expand Down Expand Up @@ -202,173 +205,6 @@ def _is_all_dates(self) -> bool:
An Index instance can **only** contain hashable objects.
"""

_int64_descr_args = {
"klass": "Int64Index",
"ltype": "integer",
"dtype": "int64",
"extra": "",
}


class IntegerIndex(NumericIndex):
"""
This is an abstract class for Int64Index, UInt64Index.
"""

_default_dtype: np.dtype
_can_hold_na = False

@classmethod
def _assert_safe_casting(cls, data, subarr):
"""
Ensure incoming data can be represented with matching signed-ness.
"""
if data.dtype.kind != cls._default_dtype.kind:
if not np.array_equal(data, subarr):
raise TypeError("Unsafe NumPy casting, you must explicitly cast")

def __contains__(self, key) -> bool:
"""
Check if key is a float and has a decimal. If it has, return False.
"""
hash(key)
try:
if is_float(key) and int(key) != key:
# otherwise the `key in self._engine` check casts e.g. 1.1 -> 1
return False
return key in self._engine
except (OverflowError, TypeError, ValueError):
return False

@property
def inferred_type(self) -> str:
"""
Always 'integer' for ``Int64Index`` and ``UInt64Index``
"""
return "integer"

@property
def asi8(self) -> np.ndarray:
# do not cache or you'll create a memory leak
warnings.warn(
"Index.asi8 is deprecated and will be removed in a future version",
FutureWarning,
stacklevel=2,
)
return self._values.view(self._default_dtype)


class Int64Index(IntegerIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _int64_descr_args

_typ = "int64index"
_engine_type = libindex.Int64Engine
_default_dtype = np.dtype(np.int64)
_dtype_validation_metadata = (is_signed_integer_dtype, "signed integer")


_uint64_descr_args = {
"klass": "UInt64Index",
"ltype": "unsigned integer",
"dtype": "uint64",
"extra": "",
}


class UInt64Index(IntegerIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _uint64_descr_args

_typ = "uint64index"
_engine_type = libindex.UInt64Engine
_default_dtype = np.dtype(np.uint64)
_dtype_validation_metadata = (is_unsigned_integer_dtype, "unsigned integer")

# ----------------------------------------------------------------
# Indexing Methods

@doc(Index._convert_arr_indexer)
def _convert_arr_indexer(self, keyarr):
# Cast the indexer to uint64 if possible so that the values returned
# from indexing are also uint64.
dtype = None
if is_integer_dtype(keyarr) or (
lib.infer_dtype(keyarr, skipna=False) == "integer"
):
dtype = np.dtype(np.uint64)

return com.asarray_tuplesafe(keyarr, dtype=dtype)


_float64_descr_args = {
"klass": "Float64Index",
"dtype": "float64",
"ltype": "float",
"extra": "",
}


class Float64Index(NumericIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _float64_descr_args

_typ = "float64index"
_engine_type = libindex.Float64Engine
_default_dtype = np.dtype(np.float64)
_dtype_validation_metadata = (is_float_dtype, "float")

@property
def inferred_type(self) -> str:
"""
Always 'floating' for ``Float64Index``
"""
return "floating"

@doc(Index.astype)
def astype(self, dtype, copy=True):
dtype = pandas_dtype(dtype)
if needs_i8_conversion(dtype):
raise TypeError(
f"Cannot convert Float64Index to dtype {dtype}; integer "
"values are required for conversion"
)
elif is_integer_dtype(dtype) and not is_extension_array_dtype(dtype):
# TODO(jreback); this can change once we have an EA Index type
# GH 13149
arr = astype_nansafe(self._values, dtype=dtype)
return Int64Index(arr, name=self.name)
return super().astype(dtype, copy=copy)

# ----------------------------------------------------------------
# Indexing Methods

@doc(Index._should_fallback_to_positional)
def _should_fallback_to_positional(self) -> bool:
return False

@doc(Index._convert_slice_indexer)
def _convert_slice_indexer(self, key: slice, kind: str):
assert kind in ["loc", "getitem"]

# We always treat __getitem__ slicing as label-based
# translate to locations
return self.slice_indexer(key.start, key.stop, key.step, kind=kind)

# ----------------------------------------------------------------

def _format_native_types(
self, na_rep="", float_format=None, decimal=".", quoting=None, **kwargs
):
from pandas.io.formats.format import FloatArrayFormatter

formatter = FloatArrayFormatter(
self._values,
na_rep=na_rep,
float_format=float_format,
decimal=decimal,
quoting=quoting,
fixed_width=False,
)
return formatter.get_result_as_array()


_numindex_descr_args = {
"klass": "NumIndex",
Expand All @@ -382,7 +218,6 @@ class NumIndex(NumericIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _numindex_descr_args

_typ = "numindex"
_default_dtype: np.dtype
_dtype_validation_metadata = (is_numeric_dtype, "numeric type")

@cache_readonly
Expand Down Expand Up @@ -416,21 +251,13 @@ def inferred_type(self) -> str:
}[self.dtype.kind]

@classmethod
def _ensure_array(cls, data, dtype, copy: bool) -> np.ndarray:
"""
Ensure we have a valid array to pass to _simple_new.
"""
arr = np.array(data, dtype=dtype, copy=copy)
cls._validate_dtype(arr.dtype)
return arr

@classmethod
def _assert_safe_casting(cls, data, subarr):
def _assert_safe_casting(cls, data, subarr) -> None:
"""
Ensure incoming data can be represented with matching signed-ness.
"""
if data.dtype.kind not in {"i", "u"} and not np.array_equal(data, subarr):
raise TypeError("Unsafe NumPy casting, you must explicitly cast")
if is_integer_dtype(subarr.dtype):
if not np.array_equal(data, subarr):
raise TypeError("Unsafe NumPy casting, you must explicitly cast")

def __contains__(self, key) -> bool:
"""
Expand All @@ -450,11 +277,7 @@ def __contains__(self, key) -> bool:

@doc(Index.astype)
def astype(self, dtype, copy=True):
if is_categorical_dtype(dtype):
from pandas import CategoricalIndex

return CategoricalIndex(self, name=self.name, dtype=dtype, copy=copy)
elif is_float_dtype(dtype):
if is_float_dtype(self.dtype):
dtype = pandas_dtype(dtype)
if needs_i8_conversion(dtype):
raise TypeError(
Expand All @@ -465,17 +288,52 @@ def astype(self, dtype, copy=True):
# TODO(jreback); this can change once we have an EA Index type
# GH 13149
arr = astype_nansafe(self._values, dtype=dtype)
return type(self)(arr, name=self.name, dtype=dtype)
if isinstance(self, Float64Index):
return Int64Index(arr, name=self.name)
else:
return NumIndex(arr, name=self.name, dtype=dtype)
elif is_categorical_dtype(dtype):
from pandas import CategoricalIndex

return CategoricalIndex(self, name=self.name, dtype=dtype, copy=copy)

return super().astype(dtype, copy=copy)

# ----------------------------------------------------------------
# Indexing Methods

@doc(Index._should_fallback_to_positional)
def _should_fallback_to_positional(self) -> bool:
if self.inferred_type == "floating":
return False

return super()._should_fallback_to_positional()

@doc(Index._convert_slice_indexer)
def _convert_slice_indexer(self, key: slice, kind: str):
assert kind in ["loc", "getitem"]
if is_float_dtype(self.dtype):
assert kind in ["loc", "getitem"]

# We always treat __getitem__ slicing as label-based
# translate to locations
return self.slice_indexer(key.start, key.stop, key.step, kind=kind)

return super()._convert_slice_indexer(key, kind=kind)

@doc(Index._convert_arr_indexer)
def _convert_arr_indexer(self, keyarr) -> np.ndarray:
if is_unsigned_integer_dtype(self.dtype):
# Cast the indexer to uint64 if possible so that the values returned
# from indexing are also uint64.
dtype = None
if is_integer_dtype(keyarr) or (
lib.infer_dtype(keyarr, skipna=False) == "integer"
):
dtype = np.dtype(np.uint64)

# We always treat __getitem__ slicing as label-based
# translate to locations
return self.slice_indexer(key.start, key.stop, key.step, kind=kind)
return com.asarray_tuplesafe(keyarr, dtype=dtype)

return super()._convert_arr_indexer(keyarr)

# ----------------------------------------------------------------

Expand All @@ -502,3 +360,67 @@ def _format_native_types(
fixed_width=False,
)
return formatter.get_result_as_array()


_int64_descr_args = {
"klass": "Int64Index",
"ltype": "integer",
"dtype": "int64",
"extra": "",
}


class Int64Index(NumIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _int64_descr_args

_typ = "int64index"
_engine_type = libindex.Int64Engine
_default_dtype = np.dtype(np.int64)
_dtype_validation_metadata = (is_signed_integer_dtype, "signed integer")

@property
def asi8(self) -> np.ndarray:
# do not cache or you'll create a memory leak
warnings.warn(
"Index.asi8 is deprecated and will be removed in a future version",
FutureWarning,
stacklevel=2,
)
return self._values.view(self._default_dtype)


_uint64_descr_args = {
"klass": "UInt64Index",
"ltype": "unsigned integer",
"dtype": "uint64",
"extra": "",
}


class UInt64Index(NumIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _uint64_descr_args

_typ = "uint64index"
_engine_type = libindex.UInt64Engine
_default_dtype = np.dtype(np.uint64)
_dtype_validation_metadata = (is_unsigned_integer_dtype, "unsigned integer")

# ----------------------------------------------------------------
# Indexing Methods


_float64_descr_args = {
"klass": "Float64Index",
"dtype": "float64",
"ltype": "float",
"extra": "",
}


class Float64Index(NumIndex):
__doc__ = _num_index_shared_docs["class_descr"] % _float64_descr_args

_typ = "float64index"
_engine_type = libindex.Float64Engine
_default_dtype = np.dtype(np.float64)
_dtype_validation_metadata = (is_float_dtype, "float")
Loading

0 comments on commit 81b014f

Please sign in to comment.