Skip to content

ENH: RangeIndex._shallow_copy can return RangeIndex #47557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 8, 2022
4 changes: 2 additions & 2 deletions doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Other enhancements
- Allow reading compressed SAS files with :func:`read_sas` (e.g., ``.sas7bdat.gz`` files)
- :meth:`DatetimeIndex.astype` now supports casting timezone-naive indexes to ``datetime64[s]``, ``datetime64[ms]``, and ``datetime64[us]``, and timezone-aware indexes to the corresponding ``datetime64[unit, tzname]`` dtypes (:issue:`47579`)
- :class:`Series` reducers (e.g. ``min``, ``max``, ``sum``, ``mean``) will now successfully operate when the dtype is numeric and ``numeric_only=True`` is provided; previously this would raise a ``NotImplementedError`` (:issue:`47500`)
-
- :meth:`RangeIndex.union` now can return a :class:`RangeIndex` instead of a :class:`Int64Index` if the resulting values are equally spaced (:issue:`47557`, :issue:`43885`)

.. ---------------------------------------------------------------------------
.. _whatsnew_150.notable_bug_fixes:
Expand Down Expand Up @@ -1008,7 +1008,7 @@ Reshaping
- Bug in :func:`concat` with identical key leads to error when indexing :class:`MultiIndex` (:issue:`46519`)
- Bug in :meth:`DataFrame.join` with a list when using suffixes to join DataFrames with duplicate column names (:issue:`46396`)
- Bug in :meth:`DataFrame.pivot_table` with ``sort=False`` results in sorted index (:issue:`17041`)
-
- Bug in :meth:`concat` when ``axis=1`` and ``sort=False`` where the resulting Index was a :class:`Int64Index` instead of a :class:`RangeIndex` (:issue:`46675`)

Sparse
^^^^^^
Expand Down
9 changes: 8 additions & 1 deletion pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
index as libindex,
lib,
)
from pandas._libs.algos import unique_deltas
from pandas._libs.lib import no_default
from pandas._typing import (
Dtype,
Expand Down Expand Up @@ -435,7 +436,13 @@ def _shallow_copy(self, values, name: Hashable = no_default):

if values.dtype.kind == "f":
return Float64Index(values, name=name)
return Int64Index._simple_new(values, name=name)
unique_diffs = unique_deltas(values)
if len(unique_diffs) == 1 and unique_diffs[0] != 0:
diff = unique_diffs[0]
new_range = range(values[0], values[-1] + diff, diff)
return type(self)._simple_new(new_range, name=name)
else:
return Int64Index._simple_new(values, name=name)

def _view(self: RangeIndex) -> RangeIndex:
result = type(self)._simple_new(self._range, name=self._name)
Expand Down
37 changes: 19 additions & 18 deletions pandas/tests/indexes/ranges/test_setops.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ def test_union_noncomparable(self, sort):
expected = Index(np.concatenate((other, index)))
tm.assert_index_equal(result, expected)

@pytest.fixture(
params=[
@pytest.mark.parametrize(
"idx1, idx2, expected_sorted, expected_notsorted",
[
(
RangeIndex(0, 10, 1),
RangeIndex(0, 10, 1),
Expand All @@ -157,13 +158,13 @@ def test_union_noncomparable(self, sort):
RangeIndex(0, 10, 1),
RangeIndex(5, 20, 1),
RangeIndex(0, 20, 1),
Int64Index(range(20)),
RangeIndex(0, 20, 1),
),
(
RangeIndex(0, 10, 1),
RangeIndex(10, 20, 1),
RangeIndex(0, 20, 1),
Int64Index(range(20)),
RangeIndex(0, 20, 1),
),
(
RangeIndex(0, -10, -1),
Expand All @@ -175,7 +176,7 @@ def test_union_noncomparable(self, sort):
RangeIndex(0, -10, -1),
RangeIndex(-10, -20, -1),
RangeIndex(-19, 1, 1),
Int64Index(range(0, -20, -1)),
RangeIndex(0, -20, -1),
),
(
RangeIndex(0, 10, 2),
Expand Down Expand Up @@ -205,7 +206,7 @@ def test_union_noncomparable(self, sort):
RangeIndex(0, 100, 5),
RangeIndex(0, 100, 20),
RangeIndex(0, 100, 5),
Int64Index(range(0, 100, 5)),
RangeIndex(0, 100, 5),
),
(
RangeIndex(0, -100, -5),
Expand All @@ -230,7 +231,7 @@ def test_union_noncomparable(self, sort):
RangeIndex(0, 100, 2),
RangeIndex(100, 150, 200),
RangeIndex(0, 102, 2),
Int64Index(range(0, 102, 2)),
RangeIndex(0, 102, 2),
),
(
RangeIndex(0, -100, -2),
Expand All @@ -242,13 +243,13 @@ def test_union_noncomparable(self, sort):
RangeIndex(0, -100, -1),
RangeIndex(0, -50, -3),
RangeIndex(-99, 1, 1),
Int64Index(list(range(0, -100, -1))),
RangeIndex(0, -100, -1),
),
(
RangeIndex(0, 1, 1),
RangeIndex(5, 6, 10),
RangeIndex(0, 6, 5),
Int64Index([0, 5]),
RangeIndex(0, 10, 5),
),
(
RangeIndex(0, 10, 5),
Expand All @@ -274,16 +275,16 @@ def test_union_noncomparable(self, sort):
Int64Index([1, 5, 6]),
Int64Index([1, 5, 6]),
),
]
# GH 43885
(
RangeIndex(0, 10),
RangeIndex(0, 5),
RangeIndex(0, 10),
RangeIndex(0, 10),
),
],
)
def unions(self, request):
"""Inputs and expected outputs for RangeIndex.union tests"""
return request.param

def test_union_sorted(self, unions):

idx1, idx2, expected_sorted, expected_notsorted = unions

def test_union_sorted(self, idx1, idx2, expected_sorted, expected_notsorted):
res1 = idx1.union(idx2, sort=None)
tm.assert_index_equal(res1, expected_sorted, exact=True)

Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/reshape/concat/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,25 @@ def test_concat_index_keep_dtype(self, dtype):
[[0, 1, 1.0], [0, 1, np.nan]], columns=Index([1, 2, 3], dtype=dtype)
)
tm.assert_frame_equal(result, expected)

def test_concat_axis_1_sort_false_rangeindex(self):
# GH 46675
s1 = Series(["a", "b", "c"])
s2 = Series(["a", "b"])
s3 = Series(["a", "b", "c", "d"])
s4 = Series([], dtype=object)
result = concat(
[s1, s2, s3, s4], sort=False, join="outer", ignore_index=False, axis=1
)
expected = DataFrame(
[
["a"] * 3 + [np.nan],
["b"] * 3 + [np.nan],
["c", np.nan] * 2,
[np.nan] * 2 + ["d"] + [np.nan],
],
dtype=object,
)
tm.assert_frame_equal(
result, expected, check_index_type=True, check_column_type=True
)