diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index f46229feee250..c26a8a40d97b8 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -413,7 +413,7 @@ Other Enhancements - :func:`read_fwf` now accepts keyword ``infer_nrows`` (:issue:`15138`). - :func:`~DataFrame.to_parquet` now supports writing a ``DataFrame`` as a directory of parquet files partitioned by a subset of the columns when ``engine = 'pyarrow'`` (:issue:`23283`) - :meth:`Timestamp.tz_localize`, :meth:`DatetimeIndex.tz_localize`, and :meth:`Series.tz_localize` have gained the ``nonexistent`` argument for alternative handling of nonexistent times. See :ref:`timeseries.timezone_nonexistent` (:issue:`8917`, :issue:`24466`) -- :meth:`Index.difference` now has an optional ``sort`` parameter to specify whether the results should be sorted if possible (:issue:`17839`) +- :meth:`Index.difference`, :meth:`Index.intersection`, :meth:`Index.union`, and :meth:`Index.symmetric_difference` now have an optional ``sort`` parameter to control whether the results should be sorted if possible (:issue:`17839`, :issue:`24471`) - :meth:`read_excel()` now accepts ``usecols`` as a list of column names or callable (:issue:`18273`) - :meth:`MultiIndex.to_flat_index` has been added to flatten multiple levels into a single-level :class:`Index` object. - :meth:`DataFrame.to_stata` and :class:`pandas.io.stata.StataWriter117` can write mixed sting columns to Stata strl format (:issue:`23633`) diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index 85eb6c3421222..f845a5437ded4 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -200,7 +200,21 @@ def item_from_zerodim(val: object) -> object: @cython.wraparound(False) @cython.boundscheck(False) -def fast_unique_multiple(list arrays): +def fast_unique_multiple(list arrays, sort: bool=True): + """ + Generate a list of unique values from a list of arrays. + + Parameters + ---------- + list : array-like + A list of array-like objects + sort : boolean + Whether or not to sort the resulting unique list + + Returns + ------- + unique_list : list of unique values + """ cdef: ndarray[object] buf Py_ssize_t k = len(arrays) @@ -217,10 +231,11 @@ def fast_unique_multiple(list arrays): if val not in table: table[val] = stub uniques.append(val) - try: - uniques.sort() - except Exception: - pass + if sort: + try: + uniques.sort() + except Exception: + pass return uniques diff --git a/pandas/core/indexes/api.py b/pandas/core/indexes/api.py index 6299fc482d0df..684a19c56c92f 100644 --- a/pandas/core/indexes/api.py +++ b/pandas/core/indexes/api.py @@ -112,7 +112,7 @@ def _get_combined_index(indexes, intersect=False, sort=False): elif intersect: index = indexes[0] for other in indexes[1:]: - index = index.intersection(other) + index = index.intersection(other, sort=sort) else: index = _union_indexes(indexes, sort=sort) index = ensure_index(index) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 5a9bf6c2c6263..93091f5125b7c 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2241,13 +2241,17 @@ def _get_reconciled_name_object(self, other): return self._shallow_copy(name=name) return self - def union(self, other): + def union(self, other, sort=True): """ - Form the union of two Index objects and sorts if possible. + Form the union of two Index objects. Parameters ---------- other : Index or array-like + sort : bool, default True + Sort the resulting index if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -2277,7 +2281,7 @@ def union(self, other): if not is_dtype_union_equal(self.dtype, other.dtype): this = self.astype('O') other = other.astype('O') - return this.union(other) + return this.union(other, sort=sort) # TODO(EA): setops-refactor, clean all this up if is_period_dtype(self) or is_datetime64tz_dtype(self): @@ -2311,12 +2315,13 @@ def union(self, other): else: result = lvals - try: - result = sorting.safe_sort(result) - except TypeError as e: - warnings.warn("%s, sort order is undefined for " - "incomparable objects" % e, RuntimeWarning, - stacklevel=3) + if sort: + try: + result = sorting.safe_sort(result) + except TypeError as e: + warnings.warn("{}, sort order is undefined for " + "incomparable objects".format(e), + RuntimeWarning, stacklevel=3) # for subclasses return self._wrap_setop_result(other, result) @@ -2324,16 +2329,19 @@ def union(self, other): def _wrap_setop_result(self, other, result): return self._constructor(result, name=get_op_result_name(self, other)) - def intersection(self, other): + def intersection(self, other, sort=True): """ Form the intersection of two Index objects. - This returns a new Index with elements common to the index and `other`, - preserving the order of the calling index. + This returns a new Index with elements common to the index and `other`. Parameters ---------- other : Index or array-like + sort : bool, default True + Sort the resulting index if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -2356,7 +2364,7 @@ def intersection(self, other): if not is_dtype_equal(self.dtype, other.dtype): this = self.astype('O') other = other.astype('O') - return this.intersection(other) + return this.intersection(other, sort=sort) # TODO(EA): setops-refactor, clean all this up if is_period_dtype(self): @@ -2385,8 +2393,18 @@ def intersection(self, other): indexer = indexer[indexer != -1] taken = other.take(indexer) + + if sort: + taken = sorting.safe_sort(taken.values) + if self.name != other.name: + name = None + else: + name = self.name + return self._shallow_copy(taken, name=name) + if self.name != other.name: taken.name = None + return taken def difference(self, other, sort=True): @@ -2442,16 +2460,18 @@ def difference(self, other, sort=True): return this._shallow_copy(the_diff, name=result_name, freq=None) - def symmetric_difference(self, other, result_name=None): + def symmetric_difference(self, other, result_name=None, sort=True): """ Compute the symmetric difference of two Index objects. - It's sorted if sorting is possible. - Parameters ---------- other : Index or array-like result_name : str + sort : bool, default True + Sort the resulting index if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -2496,10 +2516,11 @@ def symmetric_difference(self, other, result_name=None): right_diff = other.values.take(right_indexer) the_diff = _concat._concat_compat([left_diff, right_diff]) - try: - the_diff = sorting.safe_sort(the_diff) - except TypeError: - pass + if sort: + try: + the_diff = sorting.safe_sort(the_diff) + except TypeError: + pass attribs = self._get_attributes_dict() attribs['name'] = result_name @@ -3226,8 +3247,12 @@ def join(self, other, how='left', level=None, return_indexers=False, elif how == 'right': join_index = other elif how == 'inner': - join_index = self.intersection(other) + # TODO: sort=False here for backwards compat. It may + # be better to use the sort parameter passed into join + join_index = self.intersection(other, sort=False) elif how == 'outer': + # TODO: sort=True here for backwards compat. It may + # be better to use the sort parameter passed into join join_index = self.union(other) if sort: diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index a4e058160e567..cc373c06efcc9 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -594,7 +594,7 @@ def _wrap_setop_result(self, other, result): name = get_op_result_name(self, other) return self._shallow_copy(result, name=name, freq=None, tz=self.tz) - def intersection(self, other): + def intersection(self, other, sort=True): """ Specialized intersection for DatetimeIndex objects. May be much faster than Index.intersection @@ -617,7 +617,7 @@ def intersection(self, other): other = DatetimeIndex(other) except (TypeError, ValueError): pass - result = Index.intersection(self, other) + result = Index.intersection(self, other, sort=sort) if isinstance(result, DatetimeIndex): if result.freq is None: result.freq = to_offset(result.inferred_freq) @@ -627,7 +627,7 @@ def intersection(self, other): other.freq != self.freq or not other.freq.isAnchored() or (not self.is_monotonic or not other.is_monotonic)): - result = Index.intersection(self, other) + result = Index.intersection(self, other, sort=sort) # Invalidate the freq of `result`, which may not be correct at # this point, depending on the values. result.freq = None diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index f4c37413260b5..2a6044fb0a08b 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -1104,11 +1104,8 @@ def func(self, other, sort=True): 'objects that have compatible dtypes') raise TypeError(msg.format(op=op_name)) - if op_name == 'difference': - result = getattr(self._multiindex, op_name)(other._multiindex, - sort) - else: - result = getattr(self._multiindex, op_name)(other._multiindex) + result = getattr(self._multiindex, op_name)(other._multiindex, + sort=sort) result_name = get_op_result_name(self, other) # GH 19101: ensure empty results have correct dtype diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 795bfe7a73541..e4d01a40bd181 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2879,13 +2879,17 @@ def equal_levels(self, other): return False return True - def union(self, other): + def union(self, other, sort=True): """ - Form the union of two MultiIndex objects, sorting if possible + Form the union of two MultiIndex objects Parameters ---------- other : MultiIndex or array / Index of tuples + sort : bool, default True + Sort the resulting MultiIndex if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -2900,17 +2904,23 @@ def union(self, other): return self uniq_tuples = lib.fast_unique_multiple([self._ndarray_values, - other._ndarray_values]) + other._ndarray_values], + sort=sort) + return MultiIndex.from_arrays(lzip(*uniq_tuples), sortorder=0, names=result_names) - def intersection(self, other): + def intersection(self, other, sort=True): """ - Form the intersection of two MultiIndex objects, sorting if possible + Form the intersection of two MultiIndex objects. Parameters ---------- other : MultiIndex or array / Index of tuples + sort : bool, default True + Sort the resulting MultiIndex if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -2924,7 +2934,11 @@ def intersection(self, other): self_tuples = self._ndarray_values other_tuples = other._ndarray_values - uniq_tuples = sorted(set(self_tuples) & set(other_tuples)) + uniq_tuples = set(self_tuples) & set(other_tuples) + + if sort: + uniq_tuples = sorted(uniq_tuples) + if len(uniq_tuples) == 0: return MultiIndex(levels=self.levels, codes=[[]] * self.nlevels, @@ -2935,7 +2949,7 @@ def intersection(self, other): def difference(self, other, sort=True): """ - Compute sorted set difference of two MultiIndex objects + Compute set difference of two MultiIndex objects Parameters ---------- diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 110c9f4025bd8..ebf5b279563cf 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -343,14 +343,17 @@ def equals(self, other): return super(RangeIndex, self).equals(other) - def intersection(self, other): + def intersection(self, other, sort=True): """ - Form the intersection of two Index objects. Sortedness of the result is - not guaranteed + Form the intersection of two Index objects. Parameters ---------- other : Index or array-like + sort : bool, default True + Sort the resulting index if possible + + .. versionadded:: 0.24.0 Returns ------- @@ -361,7 +364,7 @@ def intersection(self, other): return self._get_reconciled_name_object(other) if not isinstance(other, RangeIndex): - return super(RangeIndex, self).intersection(other) + return super(RangeIndex, self).intersection(other, sort=sort) if not len(self) or not len(other): return RangeIndex._simple_new(None) @@ -398,6 +401,8 @@ def intersection(self, other): if (self._step < 0 and other._step < 0) is not (new_index._step < 0): new_index = new_index[::-1] + if sort: + new_index = new_index.sort_values() return new_index def _min_fitting_element(self, lower_limit): diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 4f410a34f7fda..4e103482f48a2 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -4473,7 +4473,7 @@ def _reindex_axis(obj, axis, labels, other=None): labels = ensure_index(labels.unique()) if other is not None: - labels = ensure_index(other.unique()) & labels + labels = ensure_index(other.unique()).intersection(labels, sort=False) if not labels.equals(ax): slicer = [slice(None, None)] * obj.ndim slicer[axis] = labels diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py index 1f7a2eee75750..bd37cc815d0f7 100644 --- a/pandas/tests/indexes/datetimes/test_setops.py +++ b/pandas/tests/indexes/datetimes/test_setops.py @@ -138,7 +138,8 @@ def test_intersection2(self): @pytest.mark.parametrize("tz", [None, 'Asia/Tokyo', 'US/Eastern', 'dateutil/US/Pacific']) - def test_intersection(self, tz): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection(self, tz, sort): # GH 4690 (with tz) base = date_range('6/1/2000', '6/30/2000', freq='D', name='idx') @@ -185,7 +186,9 @@ def test_intersection(self, tz): for (rng, expected) in [(rng2, expected2), (rng3, expected3), (rng4, expected4)]: - result = base.intersection(rng) + result = base.intersection(rng, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result, expected) assert result.name == expected.name assert result.freq is None diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index fffd66e97c1b4..db69258c1d3d2 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -783,53 +783,63 @@ def test_non_contiguous(self, closed): assert 1.5 not in index - def test_union(self, closed): + @pytest.mark.parametrize("sort", [True, False]) + def test_union(self, closed, sort): index = self.create_index(closed=closed) other = IntervalIndex.from_breaks(range(5, 13), closed=closed) expected = IntervalIndex.from_breaks(range(13), closed=closed) - result = index.union(other) - tm.assert_index_equal(result, expected) + result = index[::-1].union(other, sort=sort) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) - result = other.union(index) - tm.assert_index_equal(result, expected) + result = other[::-1].union(index, sort=sort) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) - tm.assert_index_equal(index.union(index), index) - tm.assert_index_equal(index.union(index[:1]), index) + tm.assert_index_equal(index.union(index, sort=sort), index) + tm.assert_index_equal(index.union(index[:1], sort=sort), index) # GH 19101: empty result, same dtype index = IntervalIndex(np.array([], dtype='int64'), closed=closed) - result = index.union(index) + result = index.union(index, sort=sort) tm.assert_index_equal(result, index) # GH 19101: empty result, different dtypes other = IntervalIndex(np.array([], dtype='float64'), closed=closed) - result = index.union(other) + result = index.union(other, sort=sort) tm.assert_index_equal(result, index) - def test_intersection(self, closed): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection(self, closed, sort): index = self.create_index(closed=closed) other = IntervalIndex.from_breaks(range(5, 13), closed=closed) expected = IntervalIndex.from_breaks(range(5, 11), closed=closed) - result = index.intersection(other) - tm.assert_index_equal(result, expected) + result = index[::-1].intersection(other, sort=sort) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) - result = other.intersection(index) - tm.assert_index_equal(result, expected) + result = other[::-1].intersection(index, sort=sort) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) - tm.assert_index_equal(index.intersection(index), index) + tm.assert_index_equal(index.intersection(index, sort=sort), index) # GH 19101: empty result, same dtype other = IntervalIndex.from_breaks(range(300, 314), closed=closed) expected = IntervalIndex(np.array([], dtype='int64'), closed=closed) - result = index.intersection(other) + result = index.intersection(other, sort=sort) tm.assert_index_equal(result, expected) # GH 19101: empty result, different dtypes breaks = np.arange(300, 314, dtype='float64') other = IntervalIndex.from_breaks(breaks, closed=closed) - result = index.intersection(other) + result = index.intersection(other, sort=sort) tm.assert_index_equal(result, expected) @pytest.mark.parametrize("sort", [True, False]) @@ -837,43 +847,49 @@ def test_difference(self, closed, sort): index = IntervalIndex.from_arrays([1, 0, 3, 2], [1, 2, 3, 4], closed=closed) - result = index.difference(index[:1], sort) + result = index.difference(index[:1], sort=sort) expected = index[1:] if sort: expected = expected.sort_values() tm.assert_index_equal(result, expected) # GH 19101: empty result, same dtype - result = index.difference(index, sort) + result = index.difference(index, sort=sort) expected = IntervalIndex(np.array([], dtype='int64'), closed=closed) tm.assert_index_equal(result, expected) # GH 19101: empty result, different dtypes other = IntervalIndex.from_arrays(index.left.astype('float64'), index.right, closed=closed) - result = index.difference(other, sort) + result = index.difference(other, sort=sort) tm.assert_index_equal(result, expected) - def test_symmetric_difference(self, closed): + @pytest.mark.parametrize("sort", [True, False]) + def test_symmetric_difference(self, closed, sort): index = self.create_index(closed=closed) - result = index[1:].symmetric_difference(index[:-1]) + result = index[1:].symmetric_difference(index[:-1], sort=sort) expected = IntervalIndex([index[0], index[-1]]) - tm.assert_index_equal(result, expected) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) # GH 19101: empty result, same dtype - result = index.symmetric_difference(index) + result = index.symmetric_difference(index, sort=sort) expected = IntervalIndex(np.array([], dtype='int64'), closed=closed) - tm.assert_index_equal(result, expected) + if sort: + tm.assert_index_equal(result, expected) + assert tm.equalContents(result, expected) # GH 19101: empty result, different dtypes other = IntervalIndex.from_arrays(index.left.astype('float64'), index.right, closed=closed) - result = index.symmetric_difference(other) + result = index.symmetric_difference(other, sort=sort) tm.assert_index_equal(result, expected) @pytest.mark.parametrize('op_name', [ 'union', 'intersection', 'difference', 'symmetric_difference']) - def test_set_operation_errors(self, closed, op_name): + @pytest.mark.parametrize("sort", [True, False]) + def test_set_operation_errors(self, closed, op_name, sort): index = self.create_index(closed=closed) set_op = getattr(index, op_name) @@ -881,7 +897,7 @@ def test_set_operation_errors(self, closed, op_name): msg = ('the other index needs to be an IntervalIndex too, but ' 'was type Int64Index') with pytest.raises(TypeError, match=msg): - set_op(Index([1, 2, 3])) + set_op(Index([1, 2, 3]), sort=sort) # mixed closed msg = ('can only do set operations between two IntervalIndex objects ' @@ -889,14 +905,14 @@ def test_set_operation_errors(self, closed, op_name): for other_closed in {'right', 'left', 'both', 'neither'} - {closed}: other = self.create_index(closed=other_closed) with pytest.raises(ValueError, match=msg): - set_op(other) + set_op(other, sort=sort) # GH 19016: incompatible dtypes other = interval_range(Timestamp('20180101'), periods=9, closed=closed) msg = ('can only do {op} between two IntervalIndex objects that have ' 'compatible dtypes').format(op=op_name) with pytest.raises(TypeError, match=msg): - set_op(other) + set_op(other, sort=sort) def test_isin(self, closed): index = self.create_index(closed=closed) diff --git a/pandas/tests/indexes/multi/test_set_ops.py b/pandas/tests/indexes/multi/test_set_ops.py index d53d15844b3a5..208d6cf1c639f 100644 --- a/pandas/tests/indexes/multi/test_set_ops.py +++ b/pandas/tests/indexes/multi/test_set_ops.py @@ -9,91 +9,110 @@ @pytest.mark.parametrize("case", [0.5, "xxx"]) +@pytest.mark.parametrize("sort", [True, False]) @pytest.mark.parametrize("method", ["intersection", "union", "difference", "symmetric_difference"]) -def test_set_ops_error_cases(idx, case, method): +def test_set_ops_error_cases(idx, case, sort, method): # non-iterable input msg = "Input must be Index or array-like" with pytest.raises(TypeError, match=msg): - getattr(idx, method)(case) + getattr(idx, method)(case, sort=sort) -def test_intersection_base(idx): +@pytest.mark.parametrize("sort", [True, False]) +def test_intersection_base(idx, sort): first = idx[:5] second = idx[:3] - intersect = first.intersection(second) + intersect = first.intersection(second, sort=sort) + if sort: + tm.assert_index_equal(intersect, second.sort_values()) assert tm.equalContents(intersect, second) # GH 10149 cases = [klass(second.values) for klass in [np.array, Series, list]] for case in cases: - result = first.intersection(case) + result = first.intersection(case, sort=sort) + if sort: + tm.assert_index_equal(result, second.sort_values()) assert tm.equalContents(result, second) msg = "other must be a MultiIndex or a list of tuples" with pytest.raises(TypeError, match=msg): - first.intersection([1, 2, 3]) + first.intersection([1, 2, 3], sort=sort) -def test_union_base(idx): +@pytest.mark.parametrize("sort", [True, False]) +def test_union_base(idx, sort): first = idx[3:] second = idx[:5] everything = idx - union = first.union(second) + union = first.union(second, sort=sort) + if sort: + tm.assert_index_equal(union, everything.sort_values()) assert tm.equalContents(union, everything) # GH 10149 cases = [klass(second.values) for klass in [np.array, Series, list]] for case in cases: - result = first.union(case) + result = first.union(case, sort=sort) + if sort: + tm.assert_index_equal(result, everything.sort_values()) assert tm.equalContents(result, everything) msg = "other must be a MultiIndex or a list of tuples" with pytest.raises(TypeError, match=msg): - first.union([1, 2, 3]) + first.union([1, 2, 3], sort=sort) @pytest.mark.parametrize("sort", [True, False]) def test_difference_base(idx, sort): - first = idx[2:] - second = idx[:4] - answer = idx[4:] - result = first.difference(second, sort) + second = idx[4:] + answer = idx[:4] + result = idx.difference(second, sort=sort) + + if sort: + answer = answer.sort_values() - assert tm.equalContents(result, answer) + assert result.equals(answer) + tm.assert_index_equal(result, answer) # GH 10149 cases = [klass(second.values) for klass in [np.array, Series, list]] for case in cases: - result = first.difference(case, sort) - assert tm.equalContents(result, answer) + result = idx.difference(case, sort=sort) + tm.assert_index_equal(result, answer) msg = "other must be a MultiIndex or a list of tuples" with pytest.raises(TypeError, match=msg): - first.difference([1, 2, 3], sort) + idx.difference([1, 2, 3], sort=sort) -def test_symmetric_difference(idx): +@pytest.mark.parametrize("sort", [True, False]) +def test_symmetric_difference(idx, sort): first = idx[1:] second = idx[:-1] - answer = idx[[0, -1]] - result = first.symmetric_difference(second) - assert tm.equalContents(result, answer) + answer = idx[[-1, 0]] + result = first.symmetric_difference(second, sort=sort) + + if sort: + answer = answer.sort_values() + + tm.assert_index_equal(result, answer) # GH 10149 cases = [klass(second.values) for klass in [np.array, Series, list]] for case in cases: - result = first.symmetric_difference(case) - assert tm.equalContents(result, answer) + result = first.symmetric_difference(case, sort=sort) + tm.assert_index_equal(result, answer) msg = "other must be a MultiIndex or a list of tuples" with pytest.raises(TypeError, match=msg): - first.symmetric_difference([1, 2, 3]) + first.symmetric_difference([1, 2, 3], sort=sort) def test_empty(idx): @@ -106,7 +125,7 @@ def test_empty(idx): def test_difference(idx, sort): first = idx - result = first.difference(idx[-3:], sort) + result = first.difference(idx[-3:], sort=sort) vals = idx[:-3].values if sort: @@ -119,21 +138,22 @@ def test_difference(idx, sort): assert isinstance(result, MultiIndex) assert result.equals(expected) assert result.names == idx.names + tm.assert_index_equal(result, expected) # empty difference: reflexive - result = idx.difference(idx, sort) + result = idx.difference(idx, sort=sort) expected = idx[:0] assert result.equals(expected) assert result.names == idx.names # empty difference: superset - result = idx[-3:].difference(idx, sort) + result = idx[-3:].difference(idx, sort=sort) expected = idx[:0] assert result.equals(expected) assert result.names == idx.names # empty difference: degenerate - result = idx[:0].difference(idx, sort) + result = idx[:0].difference(idx, sort=sort) expected = idx[:0] assert result.equals(expected) assert result.names == idx.names @@ -141,24 +161,24 @@ def test_difference(idx, sort): # names not the same chunklet = idx[-3:] chunklet.names = ['foo', 'baz'] - result = first.difference(chunklet, sort) + result = first.difference(chunklet, sort=sort) assert result.names == (None, None) # empty, but non-equal - result = idx.difference(idx.sortlevel(1)[0], sort) + result = idx.difference(idx.sortlevel(1)[0], sort=sort) assert len(result) == 0 # raise Exception called with non-MultiIndex - result = first.difference(first.values, sort) + result = first.difference(first.values, sort=sort) assert result.equals(first[:0]) # name from empty array - result = first.difference([], sort) + result = first.difference([], sort=sort) assert first.equals(result) assert first.names == result.names # name from non-empty array - result = first.difference([('foo', 'one')], sort) + result = first.difference([('foo', 'one')], sort=sort) expected = pd.MultiIndex.from_tuples([('bar', 'one'), ('baz', 'two'), ( 'foo', 'two'), ('qux', 'one'), ('qux', 'two')]) expected.names = first.names @@ -166,25 +186,26 @@ def test_difference(idx, sort): msg = "other must be a MultiIndex or a list of tuples" with pytest.raises(TypeError, match=msg): - first.difference([1, 2, 3, 4, 5]) + first.difference([1, 2, 3, 4, 5], sort=sort) -def test_union(idx): +@pytest.mark.parametrize("sort", [True, False]) +def test_union(idx, sort): piece1 = idx[:5][::-1] piece2 = idx[3:] - the_union = piece1 | piece2 + the_union = piece1.union(piece2, sort=sort) - tups = sorted(idx.values) - expected = MultiIndex.from_tuples(tups) + if sort: + tm.assert_index_equal(the_union, idx.sort_values()) - assert the_union.equals(expected) + assert tm.equalContents(the_union, idx) # corner case, pass self or empty thing: - the_union = idx.union(idx) + the_union = idx.union(idx, sort=sort) assert the_union is idx - the_union = idx.union(idx[:0]) + the_union = idx.union(idx[:0], sort=sort) assert the_union is idx # won't work in python 3 @@ -204,21 +225,23 @@ def test_union(idx): # assert result.equals(result2) -def test_intersection(idx): +@pytest.mark.parametrize("sort", [True, False]) +def test_intersection(idx, sort): piece1 = idx[:5][::-1] piece2 = idx[3:] - the_int = piece1 & piece2 - tups = sorted(idx[3:5].values) - expected = MultiIndex.from_tuples(tups) - assert the_int.equals(expected) + the_int = piece1.intersection(piece2, sort=sort) + + if sort: + tm.assert_index_equal(the_int, idx[3:5]) + assert tm.equalContents(the_int, idx[3:5]) # corner case, pass self - the_int = idx.intersection(idx) + the_int = idx.intersection(idx, sort=sort) assert the_int is idx # empty intersection: disjoint - empty = idx[:2] & idx[2:] + empty = idx[:2].intersection(idx[2:], sort=sort) expected = idx[:0] assert empty.equals(expected) diff --git a/pandas/tests/indexes/period/test_setops.py b/pandas/tests/indexes/period/test_setops.py index 565e64607350f..a97ab47bcda16 100644 --- a/pandas/tests/indexes/period/test_setops.py +++ b/pandas/tests/indexes/period/test_setops.py @@ -38,10 +38,11 @@ def test_join_does_not_recur(self): df.columns[0], df.columns[1]], object) tm.assert_index_equal(res, expected) - def test_union(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_union(self, sort): # union - rng1 = pd.period_range('1/1/2000', freq='D', periods=5) - other1 = pd.period_range('1/6/2000', freq='D', periods=5) + other1 = pd.period_range('1/1/2000', freq='D', periods=5) + rng1 = pd.period_range('1/6/2000', freq='D', periods=5) expected1 = pd.period_range('1/1/2000', freq='D', periods=10) rng2 = pd.period_range('1/1/2000', freq='D', periods=5) @@ -78,32 +79,46 @@ def test_union(self): other7 = pd.period_range('1998-01-01', freq='A', periods=8) expected7 = pd.period_range('1998-01-01', freq='A', periods=10) + rng8 = pd.PeriodIndex(['1/3/2000', '1/2/2000', '1/1/2000', + '1/5/2000', '1/4/2000'], freq='D') + other8 = pd.period_range('1/6/2000', freq='D', periods=5) + expected8 = pd.PeriodIndex(['1/3/2000', '1/2/2000', '1/1/2000', + '1/5/2000', '1/4/2000', '1/6/2000', + '1/7/2000', '1/8/2000', '1/9/2000', + '1/10/2000'], freq='D') + for rng, other, expected in [(rng1, other1, expected1), (rng2, other2, expected2), - (rng3, other3, expected3), (rng4, other4, - expected4), - (rng5, other5, expected5), (rng6, other6, - expected6), - (rng7, other7, expected7)]: + (rng3, other3, expected3), + (rng4, other4, expected4), + (rng5, other5, expected5), + (rng6, other6, expected6), + (rng7, other7, expected7), + (rng8, other8, expected8)]: - result_union = rng.union(other) + result_union = rng.union(other, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result_union, expected) - def test_union_misc(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_union_misc(self, sort): index = period_range('1/1/2000', '1/20/2000', freq='D') - result = index[:-5].union(index[10:]) + result = index[:-5].union(index[10:], sort=sort) tm.assert_index_equal(result, index) # not in order - result = _permute(index[:-5]).union(_permute(index[10:])) - tm.assert_index_equal(result, index) + result = _permute(index[:-5]).union(_permute(index[10:]), sort=sort) + if sort: + tm.assert_index_equal(result, index) + assert tm.equalContents(result, index) # raise if different frequencies index = period_range('1/1/2000', '1/20/2000', freq='D') index2 = period_range('1/1/2000', '1/20/2000', freq='W-WED') with pytest.raises(period.IncompatibleFrequency): - index.union(index2) + index.union(index2, sort=sort) msg = 'can only call with other PeriodIndex-ed objects' with pytest.raises(ValueError, match=msg): @@ -124,29 +139,33 @@ def test_union_dataframe_index(self): exp = pd.period_range('1/1/1980', '1/1/2012', freq='M') tm.assert_index_equal(df.index, exp) - def test_intersection(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection(self, sort): index = period_range('1/1/2000', '1/20/2000', freq='D') - result = index[:-5].intersection(index[10:]) + result = index[:-5].intersection(index[10:], sort=sort) tm.assert_index_equal(result, index[10:-5]) # not in order left = _permute(index[:-5]) right = _permute(index[10:]) - result = left.intersection(right).sort_values() - tm.assert_index_equal(result, index[10:-5]) + result = left.intersection(right, sort=sort) + if sort: + tm.assert_index_equal(result, index[10:-5]) + assert tm.equalContents(result, index[10:-5]) # raise if different frequencies index = period_range('1/1/2000', '1/20/2000', freq='D') index2 = period_range('1/1/2000', '1/20/2000', freq='W-WED') with pytest.raises(period.IncompatibleFrequency): - index.intersection(index2) + index.intersection(index2, sort=sort) index3 = period_range('1/1/2000', '1/20/2000', freq='2D') with pytest.raises(period.IncompatibleFrequency): - index.intersection(index3) + index.intersection(index3, sort=sort) - def test_intersection_cases(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_cases(self, sort): base = period_range('6/1/2000', '6/30/2000', freq='D', name='idx') # if target has the same name, it is preserved @@ -164,7 +183,7 @@ def test_intersection_cases(self): for (rng, expected) in [(rng2, expected2), (rng3, expected3), (rng4, expected4)]: - result = base.intersection(rng) + result = base.intersection(rng, sort=sort) tm.assert_index_equal(result, expected) assert result.name == expected.name assert result.freq == expected.freq @@ -190,7 +209,9 @@ def test_intersection_cases(self): for (rng, expected) in [(rng2, expected2), (rng3, expected3), (rng4, expected4)]: - result = base.intersection(rng) + result = base.intersection(rng, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result, expected) assert result.name == expected.name assert result.freq == 'D' @@ -254,7 +275,7 @@ def test_difference(self, sort): (rng5, other5, expected5), (rng6, other6, expected6), (rng7, other7, expected7), ]: - result_union = rng.difference(other, sort) + result_difference = rng.difference(other, sort=sort) if sort: expected = expected.sort_values() - tm.assert_index_equal(result_union, expected) + tm.assert_index_equal(result_difference, expected) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 7f6b76f7442af..f3e9d835c7391 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -684,24 +684,28 @@ def test_empty_fancy_raises(self, attr): # np.ndarray only accepts ndarray of int & bool dtypes, so should Index pytest.raises(IndexError, index.__getitem__, empty_farr) - def test_intersection(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection(self, sort): first = self.strIndex[:20] second = self.strIndex[:10] - intersect = first.intersection(second) + intersect = first.intersection(second, sort=sort) + if sort: + tm.assert_index_equal(intersect, second.sort_values()) assert tm.equalContents(intersect, second) # Corner cases - inter = first.intersection(first) + inter = first.intersection(first, sort=sort) assert inter is first @pytest.mark.parametrize("index2,keeps_name", [ (Index([3, 4, 5, 6, 7], name="index"), True), # preserve same name (Index([3, 4, 5, 6, 7], name="other"), False), # drop diff names (Index([3, 4, 5, 6, 7]), False)]) - def test_intersection_name_preservation(self, index2, keeps_name): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_name_preservation(self, index2, keeps_name, sort): index1 = Index([1, 2, 3, 4, 5], name='index') expected = Index([3, 4, 5]) - result = index1.intersection(index2) + result = index1.intersection(index2, sort) if keeps_name: expected.name = 'index' @@ -711,75 +715,89 @@ def test_intersection_name_preservation(self, index2, keeps_name): @pytest.mark.parametrize("first_name,second_name,expected_name", [ ('A', 'A', 'A'), ('A', 'B', None), (None, 'B', None)]) + @pytest.mark.parametrize("sort", [True, False]) def test_intersection_name_preservation2(self, first_name, second_name, - expected_name): + expected_name, sort): first = self.strIndex[5:20] second = self.strIndex[:10] first.name = first_name second.name = second_name - intersect = first.intersection(second) + intersect = first.intersection(second, sort=sort) assert intersect.name == expected_name @pytest.mark.parametrize("index2,keeps_name", [ (Index([4, 7, 6, 5, 3], name='index'), True), (Index([4, 7, 6, 5, 3], name='other'), False)]) - def test_intersection_monotonic(self, index2, keeps_name): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_monotonic(self, index2, keeps_name, sort): index1 = Index([5, 3, 2, 4, 1], name='index') expected = Index([5, 3, 4]) if keeps_name: expected.name = "index" - result = index1.intersection(index2) + result = index1.intersection(index2, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result, expected) @pytest.mark.parametrize("index2,expected_arr", [ (Index(['B', 'D']), ['B']), (Index(['B', 'D', 'A']), ['A', 'B', 'A'])]) - def test_intersection_non_monotonic_non_unique(self, index2, expected_arr): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_non_monotonic_non_unique(self, index2, expected_arr, + sort): # non-monotonic non-unique index1 = Index(['A', 'B', 'A', 'C']) expected = Index(expected_arr, dtype='object') - result = index1.intersection(index2) + result = index1.intersection(index2, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result, expected) - def test_intersect_str_dates(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersect_str_dates(self, sort): dt_dates = [datetime(2012, 2, 9), datetime(2012, 2, 22)] i1 = Index(dt_dates, dtype=object) i2 = Index(['aa'], dtype=object) - result = i2.intersection(i1) + result = i2.intersection(i1, sort=sort) assert len(result) == 0 - def test_chained_union(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_chained_union(self, sort): # Chained unions handles names correctly i1 = Index([1, 2], name='i1') - i2 = Index([3, 4], name='i2') - i3 = Index([5, 6], name='i3') - union = i1.union(i2.union(i3)) - expected = i1.union(i2).union(i3) + i2 = Index([5, 6], name='i2') + i3 = Index([3, 4], name='i3') + union = i1.union(i2.union(i3, sort=sort), sort=sort) + expected = i1.union(i2, sort=sort).union(i3, sort=sort) tm.assert_index_equal(union, expected) j1 = Index([1, 2], name='j1') j2 = Index([], name='j2') j3 = Index([], name='j3') - union = j1.union(j2.union(j3)) - expected = j1.union(j2).union(j3) + union = j1.union(j2.union(j3, sort=sort), sort=sort) + expected = j1.union(j2, sort=sort).union(j3, sort=sort) tm.assert_index_equal(union, expected) - def test_union(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_union(self, sort): # TODO: Replace with fixturesult first = self.strIndex[5:20] second = self.strIndex[:10] everything = self.strIndex[:20] - union = first.union(second) + union = first.union(second, sort=sort) + if sort: + tm.assert_index_equal(union, everything.sort_values()) assert tm.equalContents(union, everything) @pytest.mark.parametrize("klass", [ np.array, Series, list]) - def test_union_from_iterables(self, klass): + @pytest.mark.parametrize("sort", [True, False]) + def test_union_from_iterables(self, klass, sort): # GH 10149 # TODO: Replace with fixturesult first = self.strIndex[5:20] @@ -787,37 +805,47 @@ def test_union_from_iterables(self, klass): everything = self.strIndex[:20] case = klass(second.values) - result = first.union(case) + result = first.union(case, sort=sort) + if sort: + tm.assert_index_equal(result, everything.sort_values()) assert tm.equalContents(result, everything) - def test_union_identity(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_union_identity(self, sort): # TODO: replace with fixturesult first = self.strIndex[5:20] - union = first.union(first) + union = first.union(first, sort=sort) assert union is first - union = first.union([]) + union = first.union([], sort=sort) assert union is first - union = Index([]).union(first) + union = Index([]).union(first, sort=sort) assert union is first - @pytest.mark.parametrize("first_list", [list('ab'), list()]) + @pytest.mark.parametrize("first_list", [list('ba'), list()]) @pytest.mark.parametrize("second_list", [list('ab'), list()]) @pytest.mark.parametrize("first_name, second_name, expected_name", [ ('A', 'B', None), (None, 'B', None), ('A', None, None)]) + @pytest.mark.parametrize("sort", [True, False]) def test_union_name_preservation(self, first_list, second_list, first_name, - second_name, expected_name): + second_name, expected_name, sort): first = Index(first_list, name=first_name) second = Index(second_list, name=second_name) - union = first.union(second) + union = first.union(second, sort=sort) - vals = sorted(set(first_list).union(second_list)) - expected = Index(vals, name=expected_name) - tm.assert_index_equal(union, expected) + vals = set(first_list).union(second_list) + + if sort and len(first_list) > 0 and len(second_list) > 0: + expected = Index(sorted(vals), name=expected_name) + tm.assert_index_equal(union, expected) + else: + expected = Index(vals, name=expected_name) + assert tm.equalContents(union, expected) - def test_union_dt_as_obj(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_union_dt_as_obj(self, sort): # TODO: Replace with fixturesult firstCat = self.strIndex.union(self.dateIndex) secondCat = self.strIndex.union(self.strIndex) @@ -963,7 +991,7 @@ def test_difference_name_preservation(self, second_name, expected, sort): first.name = 'name' second.name = second_name - result = first.difference(second, sort) + result = first.difference(second, sort=sort) assert tm.equalContents(result, answer) @@ -1003,47 +1031,60 @@ def test_difference_sort(self, sort): tm.assert_index_equal(result, expected) - def test_symmetric_difference(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_symmetric_difference(self, sort): # smoke - index1 = Index([1, 2, 3, 4], name='index1') - index2 = Index([2, 3, 4, 5]) - result = index1.symmetric_difference(index2) - expected = Index([1, 5]) + index1 = Index([5, 2, 3, 4], name='index1') + index2 = Index([2, 3, 4, 1]) + result = index1.symmetric_difference(index2, sort=sort) + expected = Index([5, 1]) assert tm.equalContents(result, expected) assert result.name is None + if sort: + expected = expected.sort_values() + tm.assert_index_equal(result, expected) # __xor__ syntax expected = index1 ^ index2 assert tm.equalContents(result, expected) assert result.name is None - def test_symmetric_difference_mi(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_symmetric_difference_mi(self, sort): index1 = MultiIndex.from_tuples(self.tuples) index2 = MultiIndex.from_tuples([('foo', 1), ('bar', 3)]) - result = index1.symmetric_difference(index2) + result = index1.symmetric_difference(index2, sort=sort) expected = MultiIndex.from_tuples([('bar', 2), ('baz', 3), ('bar', 3)]) + if sort: + expected = expected.sort_values() + tm.assert_index_equal(result, expected) assert tm.equalContents(result, expected) @pytest.mark.parametrize("index2,expected", [ - (Index([0, 1, np.nan]), Index([0.0, 2.0, 3.0])), - (Index([0, 1]), Index([0.0, 2.0, 3.0, np.nan]))]) - def test_symmetric_difference_missing(self, index2, expected): + (Index([0, 1, np.nan]), Index([2.0, 3.0, 0.0])), + (Index([0, 1]), Index([np.nan, 2.0, 3.0, 0.0]))]) + @pytest.mark.parametrize("sort", [True, False]) + def test_symmetric_difference_missing(self, index2, expected, sort): # GH 13514 change: {nan} - {nan} == {} # (GH 6444, sorting of nans, is no longer an issue) index1 = Index([1, np.nan, 2, 3]) - result = index1.symmetric_difference(index2) + result = index1.symmetric_difference(index2, sort=sort) + if sort: + expected = expected.sort_values() tm.assert_index_equal(result, expected) - def test_symmetric_difference_non_index(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_symmetric_difference_non_index(self, sort): index1 = Index([1, 2, 3, 4], name='index1') index2 = np.array([2, 3, 4, 5]) expected = Index([1, 5]) - result = index1.symmetric_difference(index2) + result = index1.symmetric_difference(index2, sort=sort) assert tm.equalContents(result, expected) assert result.name == 'index1' - result = index1.symmetric_difference(index2, result_name='new_name') + result = index1.symmetric_difference(index2, result_name='new_name', + sort=sort) assert tm.equalContents(result, expected) assert result.name == 'new_name' @@ -1054,7 +1095,7 @@ def test_difference_type(self, sort): # needs to preserve the type of the index skip_index_keys = ['repeats'] for key, index in self.generate_index_types(skip_index_keys): - result = index.difference(index, sort) + result = index.difference(index, sort=sort) expected = index.drop(index) tm.assert_index_equal(result, expected) @@ -1067,7 +1108,7 @@ def test_intersection_difference(self, sort): skip_index_keys = ['repeats'] for key, index in self.generate_index_types(skip_index_keys): inter = index.intersection(index.drop(index)) - diff = index.difference(index, sort) + diff = index.difference(index, sort=sort) tm.assert_index_equal(inter, diff) @pytest.mark.parametrize("attr,expected", [ @@ -1555,7 +1596,7 @@ def test_drop_tuple(self, values, to_drop): pytest.raises(KeyError, removed.drop, drop_me) @pytest.mark.parametrize("method,expected", [ - ('intersection', np.array([(1, 'A'), (2, 'A'), (1, 'B'), (2, 'B')], + ('intersection', np.array([(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')], dtype=[('num', int), ('let', 'a1')])), ('union', np.array([(1, 'A'), (1, 'B'), (1, 'C'), (2, 'A'), (2, 'B'), (2, 'C')], dtype=[('num', int), ('let', 'a1')])) @@ -2206,25 +2247,27 @@ def test_unique_na(self): result = idx.unique() tm.assert_index_equal(result, expected) - def test_intersection_base(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_base(self, sort): # (same results for py2 and py3 but sortedness not tested elsewhere) index = self.create_index() first = index[:5] second = index[:3] - result = first.intersection(second) - expected = Index([0, 'a', 1]) + expected = Index([0, 1, 'a']) if sort else Index([0, 'a', 1]) + result = first.intersection(second, sort=sort) tm.assert_index_equal(result, expected) @pytest.mark.parametrize("klass", [ np.array, Series, list]) - def test_intersection_different_type_base(self, klass): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection_different_type_base(self, klass, sort): # GH 10149 index = self.create_index() first = index[:5] second = index[:3] - result = first.intersection(klass(second.values)) + result = first.intersection(klass(second.values), sort=sort) assert tm.equalContents(result, second) @pytest.mark.parametrize("sort", [True, False]) diff --git a/pandas/tests/indexes/test_range.py b/pandas/tests/indexes/test_range.py index 30822975a3ea0..bbd1e0ccc19b1 100644 --- a/pandas/tests/indexes/test_range.py +++ b/pandas/tests/indexes/test_range.py @@ -503,74 +503,75 @@ def test_join_self(self): joined = self.index.join(self.index, how=kind) assert self.index is joined - def test_intersection(self): + @pytest.mark.parametrize("sort", [True, False]) + def test_intersection(self, sort): # intersect with Int64Index other = Index(np.arange(1, 6)) - result = self.index.intersection(other) + result = self.index.intersection(other, sort=sort) expected = Index(np.sort(np.intersect1d(self.index.values, other.values))) tm.assert_index_equal(result, expected) - result = other.intersection(self.index) + result = other.intersection(self.index, sort=sort) expected = Index(np.sort(np.asarray(np.intersect1d(self.index.values, other.values)))) tm.assert_index_equal(result, expected) # intersect with increasing RangeIndex other = RangeIndex(1, 6) - result = self.index.intersection(other) + result = self.index.intersection(other, sort=sort) expected = Index(np.sort(np.intersect1d(self.index.values, other.values))) tm.assert_index_equal(result, expected) # intersect with decreasing RangeIndex other = RangeIndex(5, 0, -1) - result = self.index.intersection(other) + result = self.index.intersection(other, sort=sort) expected = Index(np.sort(np.intersect1d(self.index.values, other.values))) tm.assert_index_equal(result, expected) # reversed (GH 17296) - result = other.intersection(self.index) + result = other.intersection(self.index, sort=sort) tm.assert_index_equal(result, expected) # GH 17296: intersect two decreasing RangeIndexes first = RangeIndex(10, -2, -2) other = RangeIndex(5, -4, -1) - expected = first.astype(int).intersection(other.astype(int)) - result = first.intersection(other).astype(int) + expected = first.astype(int).intersection(other.astype(int), sort=sort) + result = first.intersection(other, sort=sort).astype(int) tm.assert_index_equal(result, expected) # reversed - result = other.intersection(first).astype(int) + result = other.intersection(first, sort=sort).astype(int) tm.assert_index_equal(result, expected) index = RangeIndex(5) # intersect of non-overlapping indices other = RangeIndex(5, 10, 1) - result = index.intersection(other) + result = index.intersection(other, sort=sort) expected = RangeIndex(0, 0, 1) tm.assert_index_equal(result, expected) other = RangeIndex(-1, -5, -1) - result = index.intersection(other) + result = index.intersection(other, sort=sort) expected = RangeIndex(0, 0, 1) tm.assert_index_equal(result, expected) # intersection of empty indices other = RangeIndex(0, 0, 1) - result = index.intersection(other) + result = index.intersection(other, sort=sort) expected = RangeIndex(0, 0, 1) tm.assert_index_equal(result, expected) - result = other.intersection(index) + result = other.intersection(index, sort=sort) tm.assert_index_equal(result, expected) # intersection of non-overlapping values based on start value and gcd index = RangeIndex(1, 10, 2) other = RangeIndex(0, 10, 4) - result = index.intersection(other) + result = index.intersection(other, sort=sort) expected = RangeIndex(0, 0, 1) tm.assert_index_equal(result, expected)