From dddd165eaa15bd4e43b74386255afca252247bee Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 30 Nov 2020 15:43:32 -0800 Subject: [PATCH 1/2] BUG: name attr in RangeIndex.intersection --- pandas/conftest.py | 14 ++++++++++++ pandas/core/indexes/range.py | 20 ++++++++++++++--- pandas/tests/arithmetic/conftest.py | 12 ----------- pandas/tests/indexes/ranges/test_setops.py | 25 ++++++++++++++++------ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/pandas/conftest.py b/pandas/conftest.py index cb5b4145855d1..2bac2ed198789 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -1432,3 +1432,17 @@ def __init__(self, **kwargs): registry.pop("testmem", None) TestMemoryFS.test[0] = None TestMemoryFS.store.clear() + + +@pytest.fixture( + params=[ + ("foo", None, None), + ("Egon", "Venkman", None), + ("NCC1701D", "NCC1701D", "NCC1701D"), + ] +) +def names(request): + """ + A 3-tuple of names, the first two for operands, the last for a result. + """ + return request.param diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 669bf115df104..77de6bdaeec5a 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -15,6 +15,7 @@ from pandas.core.dtypes.common import ( ensure_platform_int, ensure_python_int, + is_dtype_equal, is_float, is_integer, is_list_like, @@ -504,13 +505,26 @@ def intersection(self, other, sort=False): intersection : Index """ self._validate_sort_keyword(sort) + self._assert_can_do_setop(other) + other, _ = self._convert_can_do_setop(other) - if self.equals(other): + if self.equals(other) and not self.has_duplicates: + # has_duplicates check is unnecessary for RangeIndex, but + # used to match other subclasses. return self._get_reconciled_name_object(other) - if not isinstance(other, RangeIndex): + if not is_dtype_equal(self.dtype, other.dtype): return super().intersection(other, sort=sort) + result = self._intersection(other, sort=sort) + return self._wrap_setop_result(other, result) + + def _intersection(self, other, sort=False): + + if not isinstance(other, RangeIndex): + # Int64Index + return super()._intersection(other, sort=sort) + if not len(self) or not len(other): return self._simple_new(_empty_range) @@ -551,7 +565,7 @@ def intersection(self, other, sort=False): if sort is None: new_index = new_index.sort_values() - return self._wrap_setop_result(other, new_index) + return new_index def _min_fitting_element(self, lower_limit: int) -> int: """Returns the smallest element greater than or equal to the limit""" diff --git a/pandas/tests/arithmetic/conftest.py b/pandas/tests/arithmetic/conftest.py index 149389b936def..f507c6d4f45fb 100644 --- a/pandas/tests/arithmetic/conftest.py +++ b/pandas/tests/arithmetic/conftest.py @@ -18,18 +18,6 @@ def id_func(x): # ------------------------------------------------------------------ -@pytest.fixture( - params=[ - ("foo", None, None), - ("Egon", "Venkman", None), - ("NCC1701D", "NCC1701D", "NCC1701D"), - ] -) -def names(request): - """ - A 3-tuple of names, the first two for operands, the last for a result. - """ - return request.param @pytest.fixture(params=[1, np.array(1, dtype=np.int64)]) diff --git a/pandas/tests/indexes/ranges/test_setops.py b/pandas/tests/indexes/ranges/test_setops.py index 5623b0904c0d5..660269f2d02a4 100644 --- a/pandas/tests/indexes/ranges/test_setops.py +++ b/pandas/tests/indexes/ranges/test_setops.py @@ -37,6 +37,18 @@ def test_intersection_mismatched_dtype(self, klass): result = flt[:0].intersection(index) tm.assert_index_equal(result, flt[:0], exact=True) + def test_intersection_empty(self, sort, names): + # name retention on empty intersections + index = RangeIndex(start=0, stop=20, step=2, name=names[0]) + + # empty other + result = index.intersection(index[:0].rename(names[1]), sort=sort) + tm.assert_index_equal(result, index[:0].rename(names[2]), exact=True) + + # empty self + result = index[:0].intersection(index.rename(names[1]), sort=sort) + tm.assert_index_equal(result, index[:0].rename(names[2]), exact=True) + def test_intersection(self, sort): # intersect with Int64Index index = RangeIndex(start=0, stop=20, step=2) @@ -78,12 +90,12 @@ def test_intersection(self, sort): result = other.intersection(first, sort=sort).astype(int) tm.assert_index_equal(result, expected) - index = RangeIndex(5) + index = RangeIndex(5, name="foo") # intersect of non-overlapping indices - other = RangeIndex(5, 10, 1) + other = RangeIndex(5, 10, 1, name="foo") result = index.intersection(other, sort=sort) - expected = RangeIndex(0, 0, 1) + expected = RangeIndex(0, 0, 1, name="foo") tm.assert_index_equal(result, expected) other = RangeIndex(-1, -5, -1) @@ -100,11 +112,12 @@ def test_intersection(self, sort): result = other.intersection(index, sort=sort) tm.assert_index_equal(result, expected) + def test_intersection_non_overlapping_gcd(self, sort, names): # intersection of non-overlapping values based on start value and gcd - index = RangeIndex(1, 10, 2) - other = RangeIndex(0, 10, 4) + index = RangeIndex(1, 10, 2, name=names[0]) + other = RangeIndex(0, 10, 4, name=names[1]) result = index.intersection(other, sort=sort) - expected = RangeIndex(0, 0, 1) + expected = RangeIndex(0, 0, 1, name=names[2]) tm.assert_index_equal(result, expected) def test_union_noncomparable(self, sort): From 631a63d3f786e9bc2afbe8b8697f6371014a02ac Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 1 Dec 2020 17:53:59 -0800 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 24db70481c136..3ec4a82727e9e 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -815,6 +815,7 @@ Other - Fixed metadata propagation in the :class:`Series.dt`, :class:`Series.str` accessors, :class:`DataFrame.duplicated`, :class:`DataFrame.stack`, :class:`DataFrame.unstack`, :class:`DataFrame.pivot`, :class:`DataFrame.append`, :class:`DataFrame.diff`, :class:`DataFrame.applymap` and :class:`DataFrame.update` methods (:issue:`28283`, :issue:`37381`) - Fixed metadata propagation when selecting columns with ``DataFrame.__getitem__`` (:issue:`28283`) - Bug in :meth:`Index.intersection` with non-:class:`Index` failing to set the correct name on the returned :class:`Index` (:issue:`38111`) +- Bug in :meth:`RangeIndex.intersection` failing to set the correct name on the returned :class:`Index` in some corner cases (:issue:`38197`) - Bug in :meth:`Index.union` behaving differently depending on whether operand is an :class:`Index` or other list-like (:issue:`36384`) - Bug in :meth:`Index.intersection` with non-matching numeric dtypes casting to ``object`` dtype instead of minimal common dtype (:issue:`38122`) - Passing an array with 2 or more dimensions to the :class:`Series` constructor now raises the more specific ``ValueError`` rather than a bare ``Exception`` (:issue:`35744`)