diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 53b5bf8f182bd..89b3fa1c6a6ce 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -3070,6 +3070,30 @@ def intersection(self, other, sort=False): return self.unique()._get_reconciled_name_object(other) return self._get_reconciled_name_object(other) + if len(self) == 0 or len(other) == 0: + # fastpath; we need to be careful about having commutativity + + if self._is_multi or other._is_multi: + # _convert_can_do_setop ensures that we have both or neither + # We retain self.levels + return self[:0].rename(result_name) + + dtype = self._find_common_type_compat(other) + if is_dtype_equal(self.dtype, dtype): + # Slicing allows us to retain DTI/TDI.freq, RangeIndex + + # Note: self[:0] vs other[:0] affects + # 1) which index's `freq` we get in DTI/TDI cases + # This may be a historical artifact, i.e. no documented + # reason for this choice. + # 2) The `step` we get in RangeIndex cases + if len(self) == 0: + return self[:0].rename(result_name) + else: + return other[:0].rename(result_name) + + return Index([], dtype=dtype, name=result_name) + elif not self._should_compare(other): # We can infer that the intersection is empty. if isinstance(self, ABCMultiIndex): diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 8da99961a0a24..0ab41465c0636 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -532,15 +532,11 @@ def is_type_compatible(self, kind: str) -> bool: def _intersection(self, other: Index, sort=False) -> Index: """ - intersection specialized to the case with matching dtypes. + intersection specialized to the case with matching dtypes and both non-empty. """ other = cast("DatetimeTimedeltaMixin", other) - if len(self) == 0: - return self.copy()._get_reconciled_name_object(other) - if len(other) == 0: - return other.copy()._get_reconciled_name_object(self) - elif not self._can_fast_intersect(other): + if not self._can_fast_intersect(other): result = Index._intersection(self, other, sort=sort) # We need to invalidate the freq because Index._intersection # uses _shallow_copy on a view of self._data, which will preserve @@ -550,6 +546,11 @@ def _intersection(self, other: Index, sort=False) -> Index: result = self._wrap_setop_result(other, result) return result._with_freq(None)._with_freq("infer") + else: + return self._fast_intersect(other, sort) + + def _fast_intersect(self, other, sort): + # to make our life easier, "sort" the two ranges if self[0] <= other[0]: left, right = self, other @@ -627,11 +628,7 @@ def _can_fast_union(self: _T, other: _T) -> bool: return (right_start == left_end + freq) or right_start in left def _fast_union(self: _T, other: _T, sort=None) -> _T: - if len(other) == 0: - return self.view(type(self)) - - if len(self) == 0: - return other.view(type(self)) + # Caller is responsible for ensuring self and other are non-empty # to make our life easier, "sort" the two ranges if self[0] <= other[0]: diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 2a2993dedb25d..3971039a09b71 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -405,7 +405,8 @@ def union_many(self, others): this, other = this._maybe_utc_convert(other) - if this._can_fast_union(other): + if len(self) and len(other) and this._can_fast_union(other): + # union already has fastpath handling for empty cases this = this._fast_union(other) else: this = Index.union(this, other) diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 41997ad7ca46e..1c6ad0b3ddd30 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -553,14 +553,12 @@ def equals(self, other: object) -> bool: # Set Operations def _intersection(self, other: Index, sort=False): + # caller is responsible for checking self and other are both non-empty 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) - first = self._range[::-1] if self.step < 0 else self._range second = other._range[::-1] if other.step < 0 else other._range