Repository: pandas-dev/pandas

Issue:
Include NumericIndex at top level and document it for version 2.0
With the removal of `IntegerIndex`, `FloatIndex`, etc., and now promoting its replacement `NumericIndex`, there are a couple of things to consider:

1. I think that `NumericIndex` should appear in `pandas.__init__.py` like the other Index types so that `pd.NumericIndex` works.
2. We should document it, since we refer to it in the documentation.  It appears as a result in examples, and we refer to it, e.g. here: 

https://pandas.pydata.org/docs/dev/reference/api/pandas.Index.html?highlight=numericindex

@topper-123 may be tracking this based on comments in #50908 


Discussion:
I might be mistaken here, but I think the goal is to remove NumericIndex as well and to include everything in Index
> I might be mistaken here, but I think the goal is to remove NumericIndex as well and to include everything in Index

OK.  I'm just basing this on what our tests in `pandas-stubs` picked up when we run our tests with `pytest` against the nightly builds.  Had to remove references that we had to `IntegerIndex` and changed them to `NumericIndex`.  If we are getting rid of that too, then I'll make the change in the tests there when the nightly test fails.

Hi, yes, the goal is to remove `NumericIndex` and have the numpy dtypes that were previously utilized in `NumericIndex`, included directly in `Index` instead. I'm hoping to archieve it over this weekend, so I don't know if it will be more time efficient for you to wait doing the corrections now or maybe wait until `NumericIndex`has been removed.

I have not given any attention so far to `pandas-stubs` in this process, sorry. I could check if my changes fit well into that package or will you take care of that?
> I have not given any attention so far to `pandas-stubs` in this process, sorry. I could check if my changes fit well into that package or will you take care of that?

We'll take care of that.  Once you close this issue, I'll know you've made the changes and can modify the stubs and associated tests.
👍 

Patch:
diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py
index 1a22c3fe327e9..0037cd20e8c1e 100644
--- a/pandas/core/algorithms.py
+++ b/pandas/core/algorithms.py
@@ -1313,8 +1313,7 @@ def __init__(self, obj: DataFrame, n: int, keep: str, columns: IndexLabel) -> No
         self.columns = columns
 
     def compute(self, method: str) -> DataFrame:
-
-        from pandas.core.api import NumericIndex
+        from pandas.core.api import Index
 
         n = self.n
         frame = self.obj
@@ -1342,7 +1341,7 @@ def get_indexer(current_indexer, other_indexer):
         original_index = frame.index
         cur_frame = frame = frame.reset_index(drop=True)
         cur_n = n
-        indexer = NumericIndex([], dtype=np.int64)
+        indexer = Index([], dtype=np.int64)
 
         for i, column in enumerate(columns):
             # For each column we apply method to cur_frame[column].
diff --git a/pandas/core/api.py b/pandas/core/api.py
index 0dffa681acfc1..c0b828d9330b4 100644
--- a/pandas/core/api.py
+++ b/pandas/core/api.py
@@ -54,7 +54,6 @@
     Index,
     IntervalIndex,
     MultiIndex,
-    NumericIndex,
     PeriodIndex,
     RangeIndex,
     TimedeltaIndex,
@@ -117,7 +116,6 @@
     "NaT",
     "notna",
     "notnull",
-    "NumericIndex",
     "Period",
     "PeriodDtype",
     "PeriodIndex",
diff --git a/pandas/core/base.py b/pandas/core/base.py
index ab7a79162a0f2..b708ff44b39d0 100644
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -1161,7 +1161,7 @@ def factorize(
             uniques = uniques.astype(np.float32)
 
         if isinstance(self, ABCIndex):
-            # preserve e.g. NumericIndex, preserve MultiIndex
+            # preserve e.g. MultiIndex
             uniques = self._constructor(uniques)
         else:
             from pandas import Index
diff --git a/pandas/core/dtypes/generic.py b/pandas/core/dtypes/generic.py
index 190bd9d940ca3..5904ba4895aef 100644
--- a/pandas/core/dtypes/generic.py
+++ b/pandas/core/dtypes/generic.py
@@ -93,7 +93,6 @@ def _subclasscheck(cls, inst) -> bool:
         {
             "index",
             "rangeindex",
-            "numericindex",
             "multiindex",
             "datetimeindex",
             "timedeltaindex",
diff --git a/pandas/core/indexes/api.py b/pandas/core/indexes/api.py
index 08191363bfc93..fcf529f5be9ac 100644
--- a/pandas/core/indexes/api.py
+++ b/pandas/core/indexes/api.py
@@ -26,7 +26,6 @@
 from pandas.core.indexes.datetimes import DatetimeIndex
 from pandas.core.indexes.interval import IntervalIndex
 from pandas.core.indexes.multi import MultiIndex
-from pandas.core.indexes.numeric import NumericIndex
 from pandas.core.indexes.period import PeriodIndex
 from pandas.core.indexes.range import RangeIndex
 from pandas.core.indexes.timedeltas import TimedeltaIndex
@@ -46,7 +45,6 @@
 __all__ = [
     "Index",
     "MultiIndex",
-    "NumericIndex",
     "CategoricalIndex",
     "IntervalIndex",
     "RangeIndex",
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
index e1092996abce5..b607d170d5385 100644
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -317,7 +317,6 @@ class Index(IndexOpsMixin, PandasObject):
     DatetimeIndex : Index of datetime64 data.
     TimedeltaIndex : Index of timedelta64 data.
     PeriodIndex : Index of Period data.
-    NumericIndex : Index of numpy int/uint/float data.
 
     Notes
     -----
@@ -539,7 +538,6 @@ def __new__(
 
         klass = cls._dtype_to_subclass(arr.dtype)
 
-        # _ensure_array _may_ be unnecessary once NumericIndex etc are gone
         arr = klass._ensure_array(arr, arr.dtype, copy=False)
         return klass._simple_new(arr, name)
 
@@ -596,18 +594,11 @@ def _dtype_to_subclass(cls, dtype: DtypeObj):
 
             return TimedeltaIndex
 
-        elif dtype.kind in ["i", "f", "u"]:
-            from pandas.core.api import NumericIndex
-
-            return NumericIndex
-
         elif dtype.kind == "O":
             # NB: assuming away MultiIndex
             return Index
 
-        elif issubclass(
-            dtype.type, (str, bool, np.bool_, complex, np.complex64, np.complex128)
-        ):
+        elif issubclass(dtype.type, str) or is_numeric_dtype(dtype):
             return Index
 
         raise NotImplementedError(dtype)
@@ -1207,10 +1198,6 @@ def __repr__(self) -> str_t:
         Return a string representation for this object.
         """
         klass_name = type(self).__name__
-        from pandas.core.indexes.numeric import NumericIndex
-
-        if type(self) is NumericIndex:
-            klass_name = "Index"
         data = self._format_data()
         attrs = self._format_attrs()
         space = self._format_space()
@@ -5375,6 +5362,7 @@ def identical(self, other) -> bool:
                 for c in self._comparables
             )
             and type(self) == type(other)
+            and self.dtype == other.dtype
         )
 
     @final
diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py
deleted file mode 100644
index 8113b5ea2bb2a..0000000000000
--- a/pandas/core/indexes/numeric.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from __future__ import annotations
-
-from pandas.core.indexes.base import Index
-
-
-class NumericIndex(Index):
-    pass
diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py
index 144665401a2a0..ca34fcfc7a625 100644
--- a/pandas/core/indexes/range.py
+++ b/pandas/core/indexes/range.py
@@ -49,13 +49,12 @@
     Index,
     maybe_extract_name,
 )
-from pandas.core.indexes.numeric import NumericIndex
 from pandas.core.ops.common import unpack_zerodim_and_defer
 
 _empty_range = range(0)
 
 
-class RangeIndex(NumericIndex):
+class RangeIndex(Index):
     """
     Immutable Index implementing a monotonic integer range.
 
@@ -196,7 +195,7 @@ def _validate_dtype(cls, dtype: Dtype | None) -> None:
     @cache_readonly
     def _constructor(self) -> type[Index]:  # type: ignore[override]
         """return the class to use for construction"""
-        return NumericIndex
+        return Index
 
     # error: Signature of "_data" incompatible with supertype "Index"
     @cache_readonly
@@ -408,7 +407,7 @@ def _shallow_copy(self, values, name: Hashable = no_default):
             new_range = range(values[0], values[-1] + diff, diff)
             return type(self)._simple_new(new_range, name=name)
         else:
-            return NumericIndex._simple_new(values, name=name)
+            return self._constructor._simple_new(values, name=name)
 
     def _view(self: RangeIndex) -> RangeIndex:
         result = type(self)._simple_new(self._range, name=self._name)
diff --git a/pandas/io/formats/info.py b/pandas/io/formats/info.py
index 9a93724ca8f37..d826c0a148ebe 100644
--- a/pandas/io/formats/info.py
+++ b/pandas/io/formats/info.py
@@ -165,7 +165,7 @@
     >>> s = pd.Series(text_values, index=int_values)
     >>> s.info()
     <class 'pandas.core.series.Series'>
-    NumericIndex: 5 entries, 1 to 5
+    Index: 5 entries, 1 to 5
     Series name: None
     Non-Null Count  Dtype
     --------------  -----
@@ -177,7 +177,7 @@
 
     >>> s.info(verbose=False)
     <class 'pandas.core.series.Series'>
-    NumericIndex: 5 entries, 1 to 5
+    Index: 5 entries, 1 to 5
     dtypes: object(1)
     memory usage: 80.0+ bytes
 


Test patch:
diff --git a/pandas/_testing/asserters.py b/pandas/_testing/asserters.py
index 3e171b57af3ba..818ea1e6ef9d0 100644
--- a/pandas/_testing/asserters.py
+++ b/pandas/_testing/asserters.py
@@ -363,14 +363,7 @@ def is_class_equiv(idx: Index) -> bool:
         This only checks class equivalence. There is a separate check that the
         dtype is int64.
         """
-        from pandas.core.indexes.numeric import NumericIndex
-
-        if isinstance(idx, RangeIndex):
-            return True
-        elif type(idx) is Index or type(idx) is NumericIndex:
-            return True
-        else:
-            return False
+        return type(idx) is Index or isinstance(idx, RangeIndex)
 
     if type(left) == type(right):
         return
diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py
index 8bb5948029ca1..4bb7f62fb13bd 100644
--- a/pandas/tests/frame/indexing/test_indexing.py
+++ b/pandas/tests/frame/indexing/test_indexing.py
@@ -737,7 +737,7 @@ def test_getitem_setitem_float_labels(self, using_array_manager):
 
         # positional slicing only via iloc!
         msg = (
-            "cannot do positional indexing on NumericIndex with "
+            "cannot do positional indexing on Index with "
             r"these indexers \[1.0\] of type float"
         )
         with pytest.raises(TypeError, match=msg):
diff --git a/pandas/tests/indexes/multi/test_names.py b/pandas/tests/indexes/multi/test_names.py
index 5e3a427bc75ba..e9c65d32cbcd7 100644
--- a/pandas/tests/indexes/multi/test_names.py
+++ b/pandas/tests/indexes/multi/test_names.py
@@ -146,7 +146,6 @@ def test_setting_names_from_levels_raises():
         new.index.name = "bar"
 
     assert pd.Index._no_setting_name is False
-    assert pd.core.api.NumericIndex._no_setting_name is False
     assert pd.RangeIndex._no_setting_name is False
 
 
diff --git a/pandas/tests/indexes/numeric/test_numeric.py b/pandas/tests/indexes/numeric/test_numeric.py
index 68da64805ad1e..ed8071afe2ee2 100644
--- a/pandas/tests/indexes/numeric/test_numeric.py
+++ b/pandas/tests/indexes/numeric/test_numeric.py
@@ -43,8 +43,6 @@ def float_index(self, dtype):
         return self._index_cls([0.0, 2.5, 5.0, 7.5, 10.0], dtype=dtype)
 
     def test_repr_roundtrip(self, index):
-        from pandas.core.api import NumericIndex  # noqa: F401
-
         tm.assert_index_equal(eval(repr(index)), index, exact=True)
 
     def check_coerce(self, a, b, is_float_index=True):
diff --git a/pandas/tests/indexes/test_any_index.py b/pandas/tests/indexes/test_any_index.py
index fdea2945a8c0b..3b3d6dbaf697f 100644
--- a/pandas/tests/indexes/test_any_index.py
+++ b/pandas/tests/indexes/test_any_index.py
@@ -154,8 +154,6 @@ def test_str(self, index):
         # test the string repr
         index.name = "foo"
         assert "'foo'" in str(index)
-        if type(index).__name__ == "NumericIndex":  # TODO: remove NumericIndex
-            return
         assert type(index).__name__ in str(index)
 
 
diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py
index 957e7031f9bf6..40440bd8e0ff8 100644
--- a/pandas/tests/indexes/test_common.py
+++ b/pandas/tests/indexes/test_common.py
@@ -31,7 +31,6 @@
     RangeIndex,
 )
 import pandas._testing as tm
-from pandas.core.api import NumericIndex
 
 
 class TestCommon:
@@ -431,7 +430,7 @@ def test_hasnans_isnans(self, index_flat):
 
         if len(index) == 0:
             return
-        elif isinstance(index, NumericIndex) and is_integer_dtype(index.dtype):
+        elif is_integer_dtype(index.dtype):
             return
         elif index.dtype == bool:
             # values[1] = np.nan below casts to True!
diff --git a/pandas/tests/resample/test_resample_api.py b/pandas/tests/resample/test_resample_api.py
index 51a65d88d7b32..77b068aba765c 100644
--- a/pandas/tests/resample/test_resample_api.py
+++ b/pandas/tests/resample/test_resample_api.py
@@ -646,7 +646,7 @@ def test_selection_api_validation():
     # non DatetimeIndex
     msg = (
         "Only valid with DatetimeIndex, TimedeltaIndex or PeriodIndex, "
-        "but got an instance of 'NumericIndex'"
+        "but got an instance of 'Index'"
     )
     with pytest.raises(TypeError, match=msg):
         df.resample("2D", level="v")