From 70bd4d6faffd139c56f3f001ff10109f71cf4c06 Mon Sep 17 00:00:00 2001 From: jschendel Date: Wed, 13 Mar 2019 13:23:26 -0600 Subject: [PATCH] ENH: Add public start, stop, and step attributes to RangeIndex --- doc/source/whatsnew/v0.25.0.rst | 1 + pandas/core/indexes/range.py | 27 ++++++++++- pandas/tests/indexes/test_range.py | 77 ++++++++++++++---------------- 3 files changed, 63 insertions(+), 42 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index e045f0dc0ee39..72c40b04a1195 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -26,6 +26,7 @@ Other Enhancements - :meth:`DataFrame.set_index` now works for instances of ``abc.Iterator``, provided their output is of the same length as the calling frame (:issue:`22484`, :issue:`24984`) - :meth:`DatetimeIndex.union` now supports the ``sort`` argument. The behaviour of the sort parameter matches that of :meth:`Index.union` (:issue:`24994`) - :meth:`DataFrame.rename` now supports the ``errors`` argument to raise errors when attempting to rename nonexistent keys (:issue:`13473`) +- :class:`RangeIndex` has gained :attr:`~RangeIndex.start`, :attr:`~RangeIndex.stop`, and :attr:`~RangeIndex.step` attributes (:issue:`25710`) .. _whatsnew_0250.api_breaking: diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 5aafe9734b6a0..886a48e2acfa9 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -48,7 +48,9 @@ class RangeIndex(Int64Index): Attributes ---------- - None + start + stop + step Methods ------- @@ -209,6 +211,29 @@ def _format_data(self, name=None): return None # -------------------------------------------------------------------- + @property + def start(self): + """ + The value of the `start` parameter (or ``0`` if this was not supplied) + """ + # GH 25710 + return self._start + + @property + def stop(self): + """ + The value of the `stop` parameter + """ + # GH 25710 + return self._stop + + @property + def step(self): + """ + The value of the `step` parameter (or ``1`` if this was not supplied) + """ + # GH 25710 + return self._step @cache_readonly def nbytes(self): diff --git a/pandas/tests/indexes/test_range.py b/pandas/tests/indexes/test_range.py index 96cf83d477376..583e6bd81bb99 100644 --- a/pandas/tests/indexes/test_range.py +++ b/pandas/tests/indexes/test_range.py @@ -35,47 +35,25 @@ def test_too_many_names(self): with pytest.raises(ValueError, match="^Length"): self.index.names = ["roger", "harold"] - def test_constructor(self): - index = RangeIndex(5) - expected = np.arange(5, dtype=np.int64) - assert isinstance(index, RangeIndex) - assert index._start == 0 - assert index._stop == 5 - assert index._step == 1 - assert index.name is None - tm.assert_index_equal(Index(expected), index) - - index = RangeIndex(1, 5) - expected = np.arange(1, 5, dtype=np.int64) - assert isinstance(index, RangeIndex) - assert index._start == 1 - tm.assert_index_equal(Index(expected), index) - - index = RangeIndex(1, 5, 2) - expected = np.arange(1, 5, 2, dtype=np.int64) - assert isinstance(index, RangeIndex) - assert index._step == 2 - tm.assert_index_equal(Index(expected), index) - - for index in [RangeIndex(0), RangeIndex(start=0), RangeIndex(stop=0), - RangeIndex(0, 0)]: - expected = np.empty(0, dtype=np.int64) - assert isinstance(index, RangeIndex) - assert index._start == 0 - assert index._stop == 0 - assert index._step == 1 - tm.assert_index_equal(Index(expected), index) - - for index in [RangeIndex(0, name='Foo'), - RangeIndex(start=0, name='Foo'), - RangeIndex(stop=0, name='Foo'), - RangeIndex(0, 0, name='Foo')]: - assert isinstance(index, RangeIndex) - assert index.name == 'Foo' - - # we don't allow on a bare Index - with pytest.raises(TypeError): - Index(0, 1000) + @pytest.mark.parametrize('name', [None, 'foo']) + @pytest.mark.parametrize('args, kwargs, start, stop, step', [ + ((5,), dict(), 0, 5, 1), + ((1, 5), dict(), 1, 5, 1), + ((1, 5, 2), dict(), 1, 5, 2), + ((0,), dict(), 0, 0, 1), + ((0, 0), dict(), 0, 0, 1), + (tuple(), dict(start=0), 0, 0, 1), + (tuple(), dict(stop=0), 0, 0, 1)]) + def test_constructor(self, args, kwargs, start, stop, step, name): + result = RangeIndex(*args, name=name, **kwargs) + expected = Index(np.arange(start, stop, step, dtype=np.int64), + name=name) + assert isinstance(result, RangeIndex) + assert result._start == start + assert result._stop == stop + assert result._step == step + assert result.name is name + tm.assert_index_equal(result, expected) def test_constructor_invalid_args(self): msg = "RangeIndex\\(\\.\\.\\.\\) must be called with integers" @@ -92,6 +70,12 @@ def test_constructor_invalid_args(self): with pytest.raises(TypeError): RangeIndex(i) + # we don't allow on a bare Index + msg = (r'Index\(\.\.\.\) must be called with a collection of some ' + r'kind, 0 was passed') + with pytest.raises(TypeError, match=msg): + Index(0, 1000) + def test_constructor_same(self): # pass thru w and w/o copy @@ -172,6 +156,17 @@ def test_constructor_corner(self): with pytest.raises(TypeError): RangeIndex(1, 5, dtype='float64') + @pytest.mark.parametrize('index, start, stop, step', [ + (RangeIndex(5), 0, 5, 1), + (RangeIndex(0, 5), 0, 5, 1), + (RangeIndex(5, step=2), 0, 5, 2), + (RangeIndex(1, 5, 2), 1, 5, 2)]) + def test_start_stop_step_attrs(self, index, start, stop, step): + # GH 25710 + assert index.start == start + assert index.stop == stop + assert index.step == step + def test_copy(self): i = RangeIndex(5, name='Foo') i_copy = i.copy()