def _simple_new(cls, left, right, closed=None, copy=False, dtype=None, verify_integrity=True): result = IntervalMixin.__new__(cls) closed = closed or 'right' left = ensure_index(left, copy=copy) right = ensure_index(right, copy=copy) if dtype is not None: # GH 19262: dtype must be an IntervalDtype to override inferred dtype = pandas_dtype(dtype) if not is_interval_dtype(dtype): msg = 'dtype must be an IntervalDtype, got {dtype}' raise TypeError(msg.format(dtype=dtype)) elif dtype.subtype is not None: left = left.astype(dtype.subtype) right = right.astype(dtype.subtype) # coerce dtypes to match if needed if is_float_dtype(left) and is_integer_dtype(right): right = right.astype(left.dtype) elif is_float_dtype(right) and is_integer_dtype(left): left = left.astype(right.dtype) if type(left) != type(right): msg = ('must not have differing left [{ltype}] and right ' '[{rtype}] types') raise ValueError(msg.format(ltype=type(left).__name__, rtype=type(right).__name__)) elif is_categorical_dtype(left.dtype) or is_string_dtype(left.dtype): # GH 19016 msg = ('category, object, and string subtypes are not supported ' 'for IntervalArray') raise TypeError(msg) elif isinstance(left, ABCPeriodIndex): msg = 'Period dtypes are not supported, use a PeriodIndex instead' raise ValueError(msg) elif (isinstance(left, ABCDatetimeIndex) and str(left.tz) != str(right.tz)): msg = ("left and right must have the same time zone, got " "'{left_tz}' and '{right_tz}'") raise ValueError(msg.format(left_tz=left.tz, right_tz=right.tz)) result._left = left result._right = right result._closed = closed if verify_integrity: result._validate() return result
def get_indexer(self, target, method=None, limit=None, tolerance=None): from pandas.core.arrays.categorical import _recode_for_categories method = missing.clean_reindex_fill_method(method) target = ibase.ensure_index(target) if self.is_unique and self.equals(target): return np.arange(len(self), dtype='intp') if method == 'pad' or method == 'backfill': raise NotImplementedError("method='pad' and method='backfill' not " "implemented yet for CategoricalIndex") elif method == 'nearest': raise NotImplementedError("method='nearest' not implemented yet " 'for CategoricalIndex') if (isinstance(target, CategoricalIndex) and self.values.is_dtype_equal(target)): if self.values.equals(target.values): # we have the same codes codes = target.codes else: codes = _recode_for_categories(target.codes, target.categories, self.values.categories) else: if isinstance(target, CategoricalIndex): code_indexer = self.categories.get_indexer(target.categories) codes = take_1d(code_indexer, target.codes, fill_value=-1) else: codes = self.categories.get_indexer(target) indexer, _ = self._engine.get_indexer_non_unique(codes) return ensure_platform_int(indexer)
def get_indexer(self, target, method=None, limit=None, tolerance=None): self._check_method(method) target = ensure_index(target) target = self._maybe_cast_indexed(target) if self.equals(target): return np.arange(len(self), dtype='intp') if self.is_non_overlapping_monotonic: start, stop = self._find_non_overlapping_monotonic_bounds(target) start_plus_one = start + 1 if not ((start_plus_one < stop).any()): return np.where(start_plus_one == stop, start, -1) if not self.is_unique: raise ValueError("cannot handle non-unique indices") # IntervalIndex if isinstance(target, IntervalIndex): indexer = self._get_reindexer(target) # non IntervalIndex else: indexer = np.concatenate([self.get_loc(i) for i in target]) return ensure_platform_int(indexer)
def func(self, other, sort=sort): self._assert_can_do_setop(other) other = ensure_index(other) if not isinstance(other, IntervalIndex): result = getattr(self.astype(object), op_name)(other) if op_name in ('difference',): result = result.astype(self.dtype) return result elif self.closed != other.closed: msg = ('can only do set operations between two IntervalIndex ' 'objects that are closed on the same side') raise ValueError(msg) # GH 19016: ensure set op will not return a prohibited dtype subtypes = [self.dtype.subtype, other.dtype.subtype] common_subtype = find_common_type(subtypes) if is_object_dtype(common_subtype): msg = ('can only do {op} between two IntervalIndex ' 'objects that have compatible dtypes') raise TypeError(msg.format(op=op_name)) 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 if result.empty: result = result.values.astype(self.dtype.subtype) else: result = result.values return type(self).from_tuples(result, closed=self.closed, name=result_name)
def _as_like_interval_index(self, other): self._assert_can_do_setop(other) other = ensure_index(other) if not isinstance(other, IntervalIndex): msg = ('the other index needs to be an IntervalIndex too, but ' 'was type {}').format(other.__class__.__name__) raise TypeError(msg) elif self.closed != other.closed: msg = ('can only do set operations between two IntervalIndex ' 'objects that are closed on the same side') raise ValueError(msg) return other
def _maybe_convert_i8(self, key): """ Maybe convert a given key to it's equivalent i8 value(s). Used as a preprocessing step prior to IntervalTree queries (self._engine), which expects numeric data. Parameters ---------- key : scalar or list-like The key that should maybe be converted to i8. Returns ------- key: scalar or list-like The original key if no conversion occured, int if converted scalar, Int64Index if converted list-like. """ original = key if is_list_like(key): key = ensure_index(key) if not self._needs_i8_conversion(key): return original scalar = is_scalar(key) if is_interval_dtype(key) or isinstance(key, Interval): # convert left/right and reconstruct left = self._maybe_convert_i8(key.left) right = self._maybe_convert_i8(key.right) constructor = Interval if scalar else IntervalIndex.from_arrays return constructor(left, right, closed=self.closed) if scalar: # Timestamp/Timedelta key_dtype, key_i8 = infer_dtype_from_scalar(key, pandas_dtype=True) else: # DatetimeIndex/TimedeltaIndex key_dtype, key_i8 = key.dtype, Index(key.asi8) if key.hasnans: # convert NaT from it's i8 value to np.nan so it's not viewed # as a valid value, maybe causing errors (e.g. is_overlapping) key_i8 = key_i8.where(~key._isnan) # ensure consistency with IntervalIndex subtype subtype = self.dtype.subtype msg = ('Cannot index an IntervalIndex of subtype {subtype} with ' 'values of dtype {other}') if not is_dtype_equal(subtype, key_dtype): raise ValueError(msg.format(subtype=subtype, other=key_dtype)) return key_i8
def get_indexer_non_unique(self, target): target = ibase.ensure_index(target) if isinstance(target, CategoricalIndex): # Indexing on codes is more efficient if categories are the same: if target.categories is self.categories: target = target.codes indexer, missing = self._engine.get_indexer_non_unique(target) return ensure_platform_int(indexer), missing target = target.values codes = self.categories.get_indexer(target) indexer, missing = self._engine.get_indexer_non_unique(codes) return ensure_platform_int(indexer), missing
def get_indexer(self, target, method=None, limit=None, tolerance=None): target = ensure_index(target) if hasattr(target, 'freq') and target.freq != self.freq: msg = DIFFERENT_FREQ_INDEX.format(self.freqstr, target.freqstr) raise IncompatibleFrequency(msg) if isinstance(target, PeriodIndex): target = target.asi8 if tolerance is not None: tolerance = self._convert_tolerance(tolerance, target) return Index.get_indexer(self._int64index, target, method, limit, tolerance)
def _get_combined_index(indexes, intersect=False, sort=False): # TODO: handle index names! indexes = _get_distinct_objs(indexes) if len(indexes) == 0: index = Index([]) elif len(indexes) == 1: index = indexes[0] elif intersect: index = indexes[0] for other in indexes[1:]: index = index.intersection(other) else: index = _union_indexes(indexes, sort=sort) index = ensure_index(index) if sort: try: index = index.sort_values() except TypeError: pass return index
def get_indexer_non_unique( self, target: AnyArrayLike) -> Tuple[np.ndarray, np.ndarray]: target_as_index = ensure_index(target) # check that target_as_index IntervalIndex is compatible if isinstance(target_as_index, IntervalIndex): common_subtype = find_common_type( [self.dtype.subtype, target_as_index.dtype.subtype]) if self.closed != target_as_index.closed or is_object_dtype( common_subtype): # different closed or incompatible subtype -> no matches return ( np.repeat(-1, len(target_as_index)), np.arange(len(target_as_index)), ) if is_object_dtype(target_as_index) or isinstance( target_as_index, IntervalIndex): # target_as_index might contain intervals: defer elementwise to get_loc indexer, missing = [], [] for i, key in enumerate(target_as_index): try: locs = self.get_loc(key) if isinstance(locs, slice): locs = np.arange(locs.start, locs.stop, locs.step, dtype="intp") locs = np.array(locs, ndmin=1) except KeyError: missing.append(i) locs = np.array([-1]) indexer.append(locs) indexer = np.concatenate(indexer) else: target_as_index = self._maybe_convert_i8(target_as_index) indexer, missing = self._engine.get_indexer_non_unique( target_as_index.values) return ensure_platform_int(indexer), ensure_platform_int(missing)
def difference(self, other, sort=None): self._validate_sort_keyword(sort) self._assert_can_do_setop(other) res_name = get_op_result_name(self, other) other = ensure_index(other) if self.equals(other): # pass an empty PeriodArray with the appropriate dtype return self._shallow_copy(self._data[:0]) if is_object_dtype(other): return self.astype(object).difference(other).astype(self.dtype) elif not is_dtype_equal(self.dtype, other.dtype): return self i8self = Int64Index._simple_new(self.asi8) i8other = Int64Index._simple_new(other.asi8) i8result = i8self.difference(i8other, sort=sort) result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name) return result
def get_indexer_non_unique(self, target: Index) -> Tuple[np.ndarray, np.ndarray]: target = ensure_index(target) if isinstance(target, IntervalIndex) and not self._should_compare(target): # different closed or incompatible subtype -> no matches return self._get_indexer_non_comparable(target, None, unique=False) elif is_object_dtype(target.dtype) or isinstance( target, IntervalIndex): # target might contain intervals: defer elementwise to get_loc return self._get_indexer_pointwise(target) else: # Note: this case behaves differently from other Index subclasses # because IntervalIndex does partial-int indexing target = self._maybe_convert_i8(target) indexer, missing = self._engine.get_indexer_non_unique( target.values) return ensure_platform_int(indexer), ensure_platform_int(missing)
def wrapped(self, other, sort=False): self._assert_can_do_setop(other) other = ensure_index(other) if not isinstance(other, IntervalIndex): result = getattr(self.astype(object), op_name)(other) if op_name in ("difference", ): result = result.astype(self.dtype) return result elif self.closed != other.closed: raise ValueError( "can only do set operations between two IntervalIndex " "objects that are closed on the same side") # GH 19016: ensure set op will not return a prohibited dtype subtypes = [self.dtype.subtype, other.dtype.subtype] common_subtype = find_common_type(subtypes) if is_object_dtype(common_subtype): raise TypeError(f"can only do {op_name} between two IntervalIndex " "objects that have compatible dtypes") return method(self, other, sort)
def intersection(self, other, sort=False): self._validate_sort_keyword(sort) self._assert_can_do_setop(other) res_name = get_op_result_name(self, other) other = ensure_index(other) if self.equals(other): return self._get_reconciled_name_object(other) if not is_dtype_equal(self.dtype, other.dtype): # TODO: fastpath for if we have a different PeriodDtype this = self.astype("O") other = other.astype("O") return this.intersection(other, sort=sort) i8self = Int64Index._simple_new(self.asi8) i8other = Int64Index._simple_new(other.asi8) i8result = i8self.intersection(i8other, sort=sort) result = self._shallow_copy(np.asarray(i8result, dtype=np.int64), name=res_name) return result
def get_indexer(self, target, method=None, limit=None, tolerance=None): target = ensure_index(target) if isinstance(target, PeriodIndex): if not self._is_comparable_dtype(target.dtype): # i.e. target.freq != self.freq # No matches no_matches = -1 * np.ones(self.shape, dtype=np.intp) return no_matches target = target._get_engine_target() # i.e. target.asi8 self_index = self._int64index else: self_index = self if tolerance is not None: tolerance = self._convert_tolerance(tolerance, target) if self_index is not self: # convert tolerance to i8 tolerance = self._maybe_convert_timedelta(tolerance) return Index.get_indexer(self_index, target, method, limit, tolerance)
def _get_combined_index(indexes, intersect=False, sort=False): """ Return the union or intersection of indexes. Parameters ---------- indexes : list of Index or list objects When intersect=True, do not accept list of lists. intersect : bool, default False If True, calculate the intersection between indexes. Otherwise, calculate the union. sort : bool, default False Whether the result index should come out sorted or not. Returns ------- Index """ # TODO: handle index names! indexes = _get_distinct_objs(indexes) if len(indexes) == 0: index = Index([]) elif len(indexes) == 1: index = indexes[0] elif intersect: index = indexes[0] for other in indexes[1:]: index = index.intersection(other) else: index = _union_indexes(indexes, sort=sort) index = ensure_index(index) if sort: try: index = index.sort_values() except TypeError: pass return index
def _get_combined_index(indexes, intersect=False, sort=False): """ Return the union or intersection of indexes. Parameters ---------- indexes : list of Index or list objects When intersect=True, do not accept list of lists. intersect : bool, default False If True, calculate the intersection between indexes. Otherwise, calculate the union. sort : bool, default False Whether the result index should come out sorted or not. Returns ------- Index """ # TODO: handle index names! indexes = _get_distinct_objs(indexes) if len(indexes) == 0: index = Index([]) elif len(indexes) == 1: index = indexes[0] elif intersect: index = indexes[0] for other in indexes[1:]: index = index.intersection(other) else: index = union_indexes(indexes, sort=sort) index = ensure_index(index) if sort: try: index = index.sort_values() except TypeError: pass return index
def func(intvidx_self, other, sort=False): intvidx_self._assert_can_do_setop(other) other = ensure_index(other) if not isinstance(other, IntervalIndex): result = getattr(intvidx_self.astype(object), self.op_name)(other) if self.op_name in ('difference',): result = result.astype(intvidx_self.dtype) return result elif intvidx_self.closed != other.closed: msg = ('can only do set operations between two IntervalIndex ' 'objects that are closed on the same side') raise ValueError(msg) # GH 19016: ensure set op will not return a prohibited dtype subtypes = [intvidx_self.dtype.subtype, other.dtype.subtype] common_subtype = find_common_type(subtypes) if is_object_dtype(common_subtype): msg = ('can only do {op} between two IntervalIndex ' 'objects that have compatible dtypes') raise TypeError(msg.format(op=self.op_name)) return setop(intvidx_self, other, sort)
def func(intvidx_self, other, sort=False): intvidx_self._assert_can_do_setop(other) other = ensure_index(other) if not isinstance(other, IntervalIndex): result = getattr(intvidx_self.astype(object), self.op_name)(other) if self.op_name in ('difference', ): result = result.astype(intvidx_self.dtype) return result elif intvidx_self.closed != other.closed: msg = ('can only do set operations between two IntervalIndex ' 'objects that are closed on the same side') raise ValueError(msg) # GH 19016: ensure set op will not return a prohibited dtype subtypes = [intvidx_self.dtype.subtype, other.dtype.subtype] common_subtype = find_common_type(subtypes) if is_object_dtype(common_subtype): msg = ('can only do {op} between two IntervalIndex ' 'objects that have compatible dtypes') raise TypeError(msg.format(op=self.op_name)) return setop(intvidx_self, other, sort)
def _maybe_convert_i8(self, key): """ Maybe convert a given key to its equivalent i8 value(s). Used as a preprocessing step prior to IntervalTree queries (self._engine), which expects numeric data. Parameters ---------- key : scalar or list-like The key that should maybe be converted to i8. Returns ------- scalar or list-like The original key if no conversion occurred, int if converted scalar, Int64Index if converted list-like. """ original = key if is_list_like(key): key = ensure_index(key) if not self._needs_i8_conversion(key): return original scalar = is_scalar(key) if is_interval_dtype(key) or isinstance(key, Interval): # convert left/right and reconstruct left = self._maybe_convert_i8(key.left) right = self._maybe_convert_i8(key.right) constructor = Interval if scalar else IntervalIndex.from_arrays # error: "object" not callable return constructor(left, right, closed=self.closed) # type: ignore[operator] if scalar: # Timestamp/Timedelta key_dtype, key_i8 = infer_dtype_from_scalar(key, pandas_dtype=True) if lib.is_period(key): key_i8 = key.ordinal elif isinstance(key_i8, Timestamp): key_i8 = key_i8.value elif isinstance(key_i8, (np.datetime64, np.timedelta64)): key_i8 = key_i8.view("i8") else: # DatetimeIndex/TimedeltaIndex key_dtype, key_i8 = key.dtype, Index(key.asi8) if key.hasnans: # convert NaT from its i8 value to np.nan so it's not viewed # as a valid value, maybe causing errors (e.g. is_overlapping) key_i8 = key_i8.where(~key._isnan) # ensure consistency with IntervalIndex subtype # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any], # ExtensionDtype]" has no attribute "subtype" subtype = self.dtype.subtype # type: ignore[union-attr] if not is_dtype_equal(subtype, key_dtype): raise ValueError( f"Cannot index an IntervalIndex of subtype {subtype} with " f"values of dtype {key_dtype}") return key_i8
def get_indexer_non_unique(self, target): target = self._maybe_cast_indexed(ensure_index(target)) return super(IntervalIndex, self).get_indexer_non_unique(target)
def reindex( self, target, method=None, level=None, limit=None, tolerance=None ) -> tuple[Index, npt.NDArray[np.intp] | None]: """ Create index with target's values (move/add/delete values as necessary) Returns ------- new_index : pd.Index Resulting index indexer : np.ndarray[np.intp] or None Indices of output values in original index """ if method is not None: raise NotImplementedError( "argument method is not implemented for CategoricalIndex.reindex" ) if level is not None: raise NotImplementedError( "argument level is not implemented for CategoricalIndex.reindex" ) if limit is not None: raise NotImplementedError( "argument limit is not implemented for CategoricalIndex.reindex" ) target = ibase.ensure_index(target) if self.equals(target): indexer = None missing = np.array([], dtype=np.intp) else: indexer, missing = self.get_indexer_non_unique(target) if not self.is_unique: # GH#42568 warnings.warn( "reindexing with a non-unique Index is deprecated and will " "raise in a future version.", FutureWarning, stacklevel=find_stack_level(), ) if len(self) and indexer is not None: new_target = self.take(indexer) else: new_target = target # filling in missing if needed if len(missing): cats = self.categories.get_indexer(target) if not isinstance(target, CategoricalIndex) or (cats == -1).any(): new_target, indexer, _ = super()._reindex_non_unique(target) else: codes = new_target.codes.copy() codes[indexer == -1] = cats[missing] cat = self._data._from_backing_data(codes) new_target = type(self)._simple_new(cat, name=self.name) # we always want to return an Index type here # to be consistent with .reindex for other index types (e.g. they don't # coerce based on the actual values, only on the dtype) # unless we had an initial Categorical to begin with # in which case we are going to conform to the passed Categorical if is_categorical_dtype(target): cat = Categorical(new_target, dtype=target.dtype) new_target = type(self)._simple_new(cat, name=self.name) else: # e.g. test_reindex_with_categoricalindex, test_reindex_duplicate_target new_target = np.asarray(new_target) new_target = Index._with_infer(new_target, name=self.name) return new_target, indexer
def reindex(self, target, method=None, level=None, limit=None, tolerance=None): """ Create index with target's values (move/add/delete values as necessary) Returns ------- new_index : pd.Index Resulting index indexer : np.ndarray or None Indices of output values in original index """ if method is not None: raise NotImplementedError("argument method is not implemented for " "CategoricalIndex.reindex") if level is not None: raise NotImplementedError("argument level is not implemented for " "CategoricalIndex.reindex") if limit is not None: raise NotImplementedError("argument limit is not implemented for " "CategoricalIndex.reindex") target = ibase.ensure_index(target) if not is_categorical_dtype(target) and not target.is_unique: raise ValueError("cannot reindex with a non-unique indexer") indexer, missing = self.get_indexer_non_unique(np.array(target)) if len(self.codes): new_target = self.take(indexer) else: new_target = target # filling in missing if needed if len(missing): cats = self.categories.get_indexer(target) if (cats == -1).any(): # coerce to a regular index here! result = Index(np.array(self), name=self.name) new_target, indexer, _ = result._reindex_non_unique( np.array(target)) else: codes = new_target.codes.copy() codes[indexer == -1] = cats[missing] new_target = self._create_from_codes(codes) # we always want to return an Index type here # to be consistent with .reindex for other index types (e.g. they don't # coerce based on the actual values, only on the dtype) # unless we had an initial Categorical to begin with # in which case we are going to conform to the passed Categorical new_target = np.asarray(new_target) if is_categorical_dtype(target): new_target = target._shallow_copy(new_target, name=self.name) else: new_target = Index(new_target, name=self.name) return new_target, indexer
def get_indexer_non_unique(self, target): target = ibase.ensure_index(target) return self._get_indexer_non_unique(target._values)
def get_indexer_non_unique(self, target): target = self._maybe_cast_indexed(ensure_index(target)) return super().get_indexer_non_unique(target)
def _simple_new( cls, left, right, closed=None, copy=False, dtype=None, verify_integrity=True ): result = IntervalMixin.__new__(cls) closed = closed or "right" left = ensure_index(left, copy=copy) right = ensure_index(right, copy=copy) if dtype is not None: # GH 19262: dtype must be an IntervalDtype to override inferred dtype = pandas_dtype(dtype) if not is_interval_dtype(dtype): msg = f"dtype must be an IntervalDtype, got {dtype}" raise TypeError(msg) elif dtype.subtype is not None: left = left.astype(dtype.subtype) right = right.astype(dtype.subtype) # coerce dtypes to match if needed if is_float_dtype(left) and is_integer_dtype(right): right = right.astype(left.dtype) elif is_float_dtype(right) and is_integer_dtype(left): left = left.astype(right.dtype) if type(left) != type(right): msg = ( f"must not have differing left [{type(left).__name__}] and " f"right [{type(right).__name__}] types" ) raise ValueError(msg) elif is_categorical_dtype(left.dtype) or is_string_dtype(left.dtype): # GH 19016 msg = ( "category, object, and string subtypes are not supported " "for IntervalArray" ) raise TypeError(msg) elif isinstance(left, ABCPeriodIndex): msg = "Period dtypes are not supported, use a PeriodIndex instead" raise ValueError(msg) elif isinstance(left, ABCDatetimeIndex) and str(left.tz) != str(right.tz): msg = ( "left and right must have the same time zone, got " f"'{left.tz}' and '{right.tz}'" ) raise ValueError(msg) # For dt64/td64 we want DatetimeArray/TimedeltaArray instead of ndarray from pandas.core.ops.array_ops import maybe_upcast_datetimelike_array left = maybe_upcast_datetimelike_array(left) left = extract_array(left, extract_numpy=True) right = maybe_upcast_datetimelike_array(right) right = extract_array(right, extract_numpy=True) lbase = getattr(left, "_ndarray", left).base rbase = getattr(right, "_ndarray", right).base if lbase is not None and lbase is rbase: # If these share data, then setitem could corrupt our IA right = right.copy() result._left = left result._right = right result._closed = closed if verify_integrity: result._validate() return result
def get_indexer_non_unique(self, target): target = ibase.ensure_index(target) codes = self._values._validate_listlike(target._values) indexer, missing = self._engine.get_indexer_non_unique(codes) return ensure_platform_int(indexer), missing
def get_indexer_non_unique(self, target) -> tuple[np.ndarray, np.ndarray]: # both returned ndarrays are np.intp target = ibase.ensure_index(target) return self._get_indexer_non_unique(target._values)
def reindex(self, target, method=None, level=None, limit=None, tolerance=None): """ Create index with target's values (move/add/delete values as necessary) Returns ------- new_index : pd.Index Resulting index indexer : np.ndarray or None Indices of output values in original index """ if method is not None: raise NotImplementedError("argument method is not implemented for " "CategoricalIndex.reindex") if level is not None: raise NotImplementedError("argument level is not implemented for " "CategoricalIndex.reindex") if limit is not None: raise NotImplementedError("argument limit is not implemented for " "CategoricalIndex.reindex") target = ibase.ensure_index(target) if self.equals(target): indexer = None missing = [] else: if not target.is_unique: raise ValueError("cannot reindex with a non-unique indexer") indexer, missing = self.get_indexer_non_unique(np.array(target)) if len(self.codes) and indexer is not None: new_target = self.take(indexer) else: new_target = target # filling in missing if needed if len(missing): cats = self.categories.get_indexer(target) if (cats == -1).any(): # coerce to a regular index here! result = Index(np.array(self), name=self.name) new_target, indexer, _ = result._reindex_non_unique( np.array(target)) else: codes = new_target.codes.copy() codes[indexer == -1] = cats[missing] new_target = self._create_from_codes(codes) # we always want to return an Index type here # to be consistent with .reindex for other index types (e.g. they don't # coerce based on the actual values, only on the dtype) # unless we had an initial Categorical to begin with # in which case we are going to conform to the passed Categorical new_target = np.asarray(new_target) if is_categorical_dtype(target): new_target = target._shallow_copy(new_target, name=self.name) else: new_target = Index(new_target, name=self.name) return new_target, indexer