Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/indexes/base.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

2385 statements  

1from __future__ import annotations 

2 

3from collections import abc 

4from datetime import datetime 

5import functools 

6from itertools import zip_longest 

7import operator 

8from typing import ( 

9 TYPE_CHECKING, 

10 Any, 

11 ClassVar, 

12 Literal, 

13 NoReturn, 

14 Self, 

15 cast, 

16 final, 

17 overload, 

18) 

19import warnings 

20 

21import numpy as np 

22 

23from pandas._config import ( 

24 get_option, 

25 is_nan_na, 

26 using_string_dtype, 

27) 

28 

29from pandas._libs import ( 

30 NaT, 

31 algos as libalgos, 

32 index as libindex, 

33 lib, 

34 writers, 

35) 

36from pandas._libs.internals import BlockValuesRefs 

37import pandas._libs.join as libjoin 

38from pandas._libs.lib import ( 

39 is_datetime_array, 

40 no_default, 

41) 

42from pandas._libs.tslibs import ( 

43 OutOfBoundsDatetime, 

44 Timestamp, 

45 tz_compare, 

46) 

47from pandas._typing import ( 

48 AnyAll, 

49 ArrayLike, 

50 Axes, 

51 Axis, 

52 AxisInt, 

53 DropKeep, 

54 Dtype, 

55 DtypeObj, 

56 F, 

57 IgnoreRaise, 

58 IndexLabel, 

59 IndexT, 

60 JoinHow, 

61 Level, 

62 NaPosition, 

63 ReindexMethod, 

64 Shape, 

65 SliceType, 

66 npt, 

67) 

68from pandas.compat.numpy import function as nv 

69from pandas.errors import ( 

70 DuplicateLabelError, 

71 InvalidIndexError, 

72 Pandas4Warning, 

73) 

74from pandas.util._decorators import ( 

75 cache_readonly, 

76 set_module, 

77) 

78from pandas.util._exceptions import ( 

79 find_stack_level, 

80 rewrite_exception, 

81) 

82 

83from pandas.core.dtypes.astype import ( 

84 astype_array, 

85 astype_is_view, 

86) 

87from pandas.core.dtypes.cast import ( 

88 LossySetitemError, 

89 can_hold_element, 

90 common_dtype_categorical_compat, 

91 find_result_type, 

92 infer_dtype_from, 

93 maybe_unbox_numpy_scalar, 

94 np_can_hold_element, 

95) 

96from pandas.core.dtypes.common import ( 

97 ensure_int64, 

98 ensure_object, 

99 ensure_platform_int, 

100 is_any_real_numeric_dtype, 

101 is_bool_dtype, 

102 is_ea_or_datetimelike_dtype, 

103 is_float, 

104 is_hashable, 

105 is_integer, 

106 is_iterator, 

107 is_list_like, 

108 is_numeric_dtype, 

109 is_object_dtype, 

110 is_scalar, 

111 is_signed_integer_dtype, 

112 is_string_dtype, 

113 needs_i8_conversion, 

114 pandas_dtype, 

115 validate_all_hashable, 

116) 

117from pandas.core.dtypes.concat import concat_compat 

118from pandas.core.dtypes.dtypes import ( 

119 ArrowDtype, 

120 CategoricalDtype, 

121 DatetimeTZDtype, 

122 ExtensionDtype, 

123 IntervalDtype, 

124 PeriodDtype, 

125 SparseDtype, 

126) 

127from pandas.core.dtypes.generic import ( 

128 ABCCategoricalIndex, 

129 ABCDataFrame, 

130 ABCDatetimeIndex, 

131 ABCIntervalIndex, 

132 ABCMultiIndex, 

133 ABCPeriodIndex, 

134 ABCRangeIndex, 

135 ABCSeries, 

136 ABCTimedeltaIndex, 

137) 

138from pandas.core.dtypes.inference import is_dict_like 

139from pandas.core.dtypes.missing import ( 

140 array_equivalent, 

141 is_valid_na_for_dtype, 

142 isna, 

143) 

144 

145from pandas.core import ( 

146 arraylike, 

147 nanops, 

148 ops, 

149) 

150from pandas.core.accessor import Accessor 

151import pandas.core.algorithms as algos 

152from pandas.core.array_algos.putmask import ( 

153 setitem_datetimelike_compat, 

154 validate_putmask, 

155) 

156from pandas.core.arrays import ( 

157 ArrowExtensionArray, 

158 BaseMaskedArray, 

159 Categorical, 

160 DatetimeArray, 

161 ExtensionArray, 

162 TimedeltaArray, 

163) 

164from pandas.core.arrays.floating import FloatingDtype 

165from pandas.core.arrays.string_ import ( 

166 StringArray, 

167 StringDtype, 

168) 

169from pandas.core.base import ( 

170 IndexOpsMixin, 

171 PandasObject, 

172) 

173import pandas.core.common as com 

174from pandas.core.construction import ( 

175 ensure_wrapped_if_datetimelike, 

176 extract_array, 

177 sanitize_array, 

178) 

179from pandas.core.indexers import ( 

180 disallow_ndim_indexing, 

181 is_valid_positional_slice, 

182) 

183from pandas.core.indexes.frozen import FrozenList 

184from pandas.core.missing import clean_reindex_fill_method 

185from pandas.core.ops import get_op_result_name 

186from pandas.core.sorting import ( 

187 ensure_key_mapped, 

188 get_group_index_sorter, 

189 nargsort, 

190) 

191from pandas.core.strings.accessor import StringMethods 

192 

193from pandas.io.formats.printing import ( 

194 PrettyDict, 

195 default_pprint, 

196 format_object_summary, 

197 pprint_thing, 

198) 

199 

200if TYPE_CHECKING: 

201 from collections.abc import ( 

202 Callable, 

203 Hashable, 

204 Iterable, 

205 Sequence, 

206 ) 

207 

208 from pandas import ( 

209 CategoricalIndex, 

210 DataFrame, 

211 MultiIndex, 

212 Series, 

213 ) 

214 from pandas.core.arrays import ( 

215 IntervalArray, 

216 PeriodArray, 

217 ) 

218 

219__all__ = ["Index"] 

220 

221_unsortable_types = frozenset(("mixed", "mixed-integer")) 

222 

223_index_doc_kwargs: dict[str, str] = { 

224 "klass": "Index", 

225 "inplace": "", 

226 "target_klass": "Index", 

227 "raises_section": "", 

228 "unique": "Index", 

229 "duplicated": "np.ndarray", 

230} 

231_index_shared_docs: dict[str, str] = {} 

232str_t = str 

233 

234_dtype_obj = np.dtype("object") 

235 

236_masked_engines = { 

237 "Complex128": libindex.MaskedComplex128Engine, 

238 "Complex64": libindex.MaskedComplex64Engine, 

239 "Float64": libindex.MaskedFloat64Engine, 

240 "Float32": libindex.MaskedFloat32Engine, 

241 "UInt64": libindex.MaskedUInt64Engine, 

242 "UInt32": libindex.MaskedUInt32Engine, 

243 "UInt16": libindex.MaskedUInt16Engine, 

244 "UInt8": libindex.MaskedUInt8Engine, 

245 "Int64": libindex.MaskedInt64Engine, 

246 "Int32": libindex.MaskedInt32Engine, 

247 "Int16": libindex.MaskedInt16Engine, 

248 "Int8": libindex.MaskedInt8Engine, 

249 "boolean": libindex.MaskedBoolEngine, 

250 "double[pyarrow]": libindex.MaskedFloat64Engine, 

251 "float64[pyarrow]": libindex.MaskedFloat64Engine, 

252 "float32[pyarrow]": libindex.MaskedFloat32Engine, 

253 "float[pyarrow]": libindex.MaskedFloat32Engine, 

254 "uint64[pyarrow]": libindex.MaskedUInt64Engine, 

255 "uint32[pyarrow]": libindex.MaskedUInt32Engine, 

256 "uint16[pyarrow]": libindex.MaskedUInt16Engine, 

257 "uint8[pyarrow]": libindex.MaskedUInt8Engine, 

258 "int64[pyarrow]": libindex.MaskedInt64Engine, 

259 "int32[pyarrow]": libindex.MaskedInt32Engine, 

260 "int16[pyarrow]": libindex.MaskedInt16Engine, 

261 "int8[pyarrow]": libindex.MaskedInt8Engine, 

262 "bool[pyarrow]": libindex.MaskedBoolEngine, 

263} 

264 

265 

266def _maybe_return_indexers(meth: F) -> F: 

267 """ 

268 Decorator to simplify 'return_indexers' checks in Index.join. 

269 """ 

270 

271 @functools.wraps(meth) 

272 def join( 

273 self, 

274 other: Index, 

275 *, 

276 how: JoinHow = "left", 

277 level=None, 

278 return_indexers: bool = False, 

279 sort: bool = False, 

280 ): 

281 join_index, lidx, ridx = meth(self, other, how=how, level=level, sort=sort) 

282 if not return_indexers: 

283 return join_index 

284 

285 if lidx is not None: 

286 lidx = ensure_platform_int(lidx) 

287 if ridx is not None: 

288 ridx = ensure_platform_int(ridx) 

289 return join_index, lidx, ridx 

290 

291 return cast(F, join) 

292 

293 

294def _new_Index(cls, d): 

295 """ 

296 This is called upon unpickling, rather than the default which doesn't 

297 have arguments and breaks __new__. 

298 """ 

299 # required for backward compat, because PI can't be instantiated with 

300 # ordinals through __new__ GH #13277 

301 d["copy"] = False 

302 if issubclass(cls, ABCPeriodIndex): 

303 from pandas.core.indexes.period import _new_PeriodIndex 

304 

305 return _new_PeriodIndex(cls, **d) 

306 

307 if issubclass(cls, ABCMultiIndex): 

308 if "labels" in d and "codes" not in d: 

309 # GH#23752 "labels" kwarg has been replaced with "codes" 

310 d["codes"] = d.pop("labels") 

311 

312 # Since this was a valid MultiIndex at pickle-time, we don't need to 

313 # check validty at un-pickle time. 

314 d["verify_integrity"] = False 

315 

316 elif "dtype" not in d and "data" in d: 

317 # Prevent Index.__new__ from conducting inference; 

318 # "data" key not in RangeIndex 

319 d["dtype"] = d["data"].dtype 

320 return cls.__new__(cls, **d) 

321 

322 

323@set_module("pandas") 

324class Index(IndexOpsMixin, PandasObject): 

325 """ 

326 Immutable sequence used for indexing and alignment. 

327 

328 The basic object storing axis labels for all pandas objects. 

329 

330 .. versionchanged:: 2.0.0 

331 

332 Index can hold all numpy numeric dtypes (except float16). Previously only 

333 int64/uint64/float64 dtypes were accepted. 

334 

335 Parameters 

336 ---------- 

337 data : array-like (1-dimensional) 

338 An array-like structure containing the data for the index. This could be a 

339 Python list, a NumPy array, or a pandas Series. 

340 dtype : str, numpy.dtype, or ExtensionDtype, optional 

341 Data type for the output Index. If not specified, this will be 

342 inferred from `data`. 

343 See the :ref:`user guide <basics.dtypes>` for more usages. 

344 copy : bool, default None 

345 Whether to copy input data, only relevant for array, Series, and Index 

346 inputs (for other input, e.g. a list, a new array is created anyway). 

347 Defaults to True for array input and False for Index/Series. 

348 Set to False to avoid copying array input at your own risk (if you 

349 know the input data won't be modified elsewhere). 

350 Set to True to force copying Series/Index input up front. 

351 name : object 

352 Name to be stored in the index. 

353 tupleize_cols : bool (default: True) 

354 When True, attempt to create a MultiIndex if possible. 

355 

356 See Also 

357 -------- 

358 RangeIndex : Index implementing a monotonic integer range. 

359 CategoricalIndex : Index of :class:`Categorical` s. 

360 MultiIndex : A multi-level, or hierarchical Index. 

361 IntervalIndex : An Index of :class:`Interval` s. 

362 DatetimeIndex : Index of datetime64 data. 

363 TimedeltaIndex : Index of timedelta64 data. 

364 PeriodIndex : Index of Period data. 

365 

366 Notes 

367 ----- 

368 An Index instance can **only** contain hashable objects. 

369 An Index instance *can not* hold numpy float16 dtype. 

370 

371 Examples 

372 -------- 

373 >>> pd.Index([1, 2, 3]) 

374 Index([1, 2, 3], dtype='int64') 

375 

376 >>> pd.Index(list("abc")) 

377 Index(['a', 'b', 'c'], dtype='str') 

378 

379 >>> pd.Index([1, 2, 3], dtype="uint8") 

380 Index([1, 2, 3], dtype='uint8') 

381 """ 

382 

383 # similar to __array_priority__, positions Index after Series and DataFrame 

384 # but before ExtensionArray. Should NOT be overridden by subclasses. 

385 __pandas_priority__ = 2000 

386 

387 # Cython methods; see github.com/cython/cython/issues/2647 

388 # for why we need to wrap these instead of making them class attributes 

389 # Moreover, cython will choose the appropriate-dtyped sub-function 

390 # given the dtypes of the passed arguments 

391 

392 @final 

393 def _left_indexer_unique(self, other: Self) -> npt.NDArray[np.intp]: 

394 # Caller is responsible for ensuring other.dtype == self.dtype 

395 sv = self._get_join_target() 

396 ov = other._get_join_target() 

397 # similar but not identical to ov.searchsorted(sv) 

398 return libjoin.left_join_indexer_unique(sv, ov) 

399 

400 @final 

401 def _left_indexer( 

402 self, other: Self 

403 ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

404 # Caller is responsible for ensuring other.dtype == self.dtype 

405 sv = self._get_join_target() 

406 ov = other._get_join_target() 

407 joined_ndarray, lidx, ridx = libjoin.left_join_indexer(sv, ov) 

408 joined = self._from_join_target(joined_ndarray) 

409 return joined, lidx, ridx 

410 

411 @final 

412 def _inner_indexer( 

413 self, other: Self 

414 ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

415 # Caller is responsible for ensuring other.dtype == self.dtype 

416 sv = self._get_join_target() 

417 ov = other._get_join_target() 

418 joined_ndarray, lidx, ridx = libjoin.inner_join_indexer(sv, ov) 

419 joined = self._from_join_target(joined_ndarray) 

420 return joined, lidx, ridx 

421 

422 @final 

423 def _outer_indexer( 

424 self, other: Self 

425 ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

426 # Caller is responsible for ensuring other.dtype == self.dtype 

427 sv = self._get_join_target() 

428 ov = other._get_join_target() 

429 joined_ndarray, lidx, ridx = libjoin.outer_join_indexer(sv, ov) 

430 joined = self._from_join_target(joined_ndarray) 

431 return joined, lidx, ridx 

432 

433 _typ: str = "index" 

434 _data: ExtensionArray | np.ndarray 

435 _data_cls: type[ExtensionArray] | tuple[type[np.ndarray], type[ExtensionArray]] = ( 

436 np.ndarray, 

437 ExtensionArray, 

438 ) 

439 _id: object | None = None 

440 _name: Hashable = None 

441 # MultiIndex.levels previously allowed setting the index name. We 

442 # don't allow this anymore, and raise if it happens rather than 

443 # failing silently. 

444 _no_setting_name: bool = False 

445 _comparables: list[str] = ["name"] 

446 _attributes: list[str] = ["name"] 

447 

448 @cache_readonly 

449 def _can_hold_strings(self) -> bool: 

450 return not is_numeric_dtype(self.dtype) 

451 

452 _engine_types: dict[np.dtype | ExtensionDtype, type[libindex.IndexEngine]] = { 

453 np.dtype(np.int8): libindex.Int8Engine, 

454 np.dtype(np.int16): libindex.Int16Engine, 

455 np.dtype(np.int32): libindex.Int32Engine, 

456 np.dtype(np.int64): libindex.Int64Engine, 

457 np.dtype(np.uint8): libindex.UInt8Engine, 

458 np.dtype(np.uint16): libindex.UInt16Engine, 

459 np.dtype(np.uint32): libindex.UInt32Engine, 

460 np.dtype(np.uint64): libindex.UInt64Engine, 

461 np.dtype(np.float32): libindex.Float32Engine, 

462 np.dtype(np.float64): libindex.Float64Engine, 

463 np.dtype(np.complex64): libindex.Complex64Engine, 

464 np.dtype(np.complex128): libindex.Complex128Engine, 

465 } 

466 

467 @property 

468 def _engine_type( 

469 self, 

470 ) -> type[libindex.IndexEngine | libindex.ExtensionEngine]: 

471 return self._engine_types.get(self.dtype, libindex.ObjectEngine) 

472 

473 # whether we support partial string indexing. Overridden 

474 # in DatetimeIndex and PeriodIndex 

475 _supports_partial_string_indexing = False 

476 

477 _accessors = {"str"} 

478 

479 str = Accessor("str", StringMethods) 

480 

481 _references: BlockValuesRefs | None = None 

482 

483 # -------------------------------------------------------------------- 

484 # Constructors 

485 

486 def __new__( 

487 cls, 

488 data=None, 

489 dtype=None, 

490 copy: bool | None = None, 

491 name=None, 

492 tupleize_cols: bool = True, 

493 ) -> Self: 

494 from pandas.core.indexes.range import RangeIndex 

495 

496 name = maybe_extract_name(name, data, cls) 

497 

498 if dtype is not None: 

499 dtype = pandas_dtype(dtype) 

500 

501 data_dtype = getattr(data, "dtype", None) 

502 

503 refs = None 

504 if not copy and isinstance(data, (ABCSeries, Index)): 

505 refs = data._references 

506 

507 # GH 63306, GH 63388 

508 data, copy = cls._maybe_copy_array_input(data, copy, dtype) 

509 

510 # range 

511 if isinstance(data, (range, RangeIndex)): 

512 result = RangeIndex(start=data, copy=bool(copy), name=name) 

513 if dtype is not None: 

514 return result.astype(dtype, copy=False) 

515 # error: Incompatible return value type (got "MultiIndex", 

516 # expected "Self") 

517 return result # type: ignore[return-value] 

518 

519 elif is_ea_or_datetimelike_dtype(dtype): 

520 # non-EA dtype indexes have special casting logic, so we punt here 

521 if isinstance(data, (set, frozenset)): 

522 data = list(data) 

523 

524 elif is_ea_or_datetimelike_dtype(data_dtype): 

525 pass 

526 

527 elif isinstance(data, (np.ndarray, ABCMultiIndex)): 

528 if isinstance(data, ABCMultiIndex): 

529 data = data._values 

530 

531 if data.dtype.kind not in "iufcbmM": 

532 # GH#11836 we need to avoid having numpy coerce 

533 # things that look like ints/floats to ints unless 

534 # they are actually ints, e.g. '0' and 0.0 

535 # should not be coerced 

536 data = com.asarray_tuplesafe(data, dtype=_dtype_obj) 

537 elif isinstance(data, (ABCSeries, Index)): 

538 # GH 56244: Avoid potential inference on object types 

539 pass 

540 elif is_scalar(data): 

541 raise cls._raise_scalar_data_error(data) 

542 elif hasattr(data, "__array__"): 

543 return cls(np.asarray(data), dtype=dtype, copy=copy, name=name) 

544 elif not is_list_like(data) and not isinstance(data, memoryview): 

545 # 2022-11-16 the memoryview check is only necessary on some CI 

546 # builds, not clear why 

547 raise cls._raise_scalar_data_error(data) 

548 

549 else: 

550 if tupleize_cols: 

551 # GH21470: convert iterable to list before determining if empty 

552 if is_iterator(data): 

553 data = list(data) 

554 

555 if data and all(isinstance(e, tuple) for e in data): 

556 # we must be all tuples, otherwise don't construct 

557 # 10697 

558 from pandas.core.indexes.multi import MultiIndex 

559 

560 # error: Incompatible return value type (got "MultiIndex", 

561 # expected "Self") 

562 return MultiIndex.from_tuples( # type: ignore[return-value] 

563 data, names=name 

564 ) 

565 # other iterable of some kind 

566 

567 if not isinstance(data, (list, tuple)): 

568 # we allow set/frozenset, which Series/sanitize_array does not, so 

569 # cast to list here 

570 data = list(data) 

571 if len(data) == 0: 

572 # unlike Series, we default to object dtype: 

573 data = np.array(data, dtype=object) 

574 

575 if len(data) and isinstance(data[0], tuple): 

576 # Ensure we get 1-D array of tuples instead of 2D array. 

577 data = com.asarray_tuplesafe(data, dtype=_dtype_obj) 

578 

579 try: 

580 arr = sanitize_array(data, None, dtype=dtype, copy=bool(copy)) 

581 except ValueError as err: 

582 if "index must be specified when data is not list-like" in str(err): 

583 raise cls._raise_scalar_data_error(data) from err 

584 if "Data must be 1-dimensional" in str(err): 

585 raise ValueError("Index data must be 1-dimensional") from err 

586 raise 

587 arr = ensure_wrapped_if_datetimelike(arr) 

588 

589 klass = cls._dtype_to_subclass(arr.dtype) 

590 

591 arr = klass._ensure_array(arr, arr.dtype, copy=False) 

592 return klass._simple_new(arr, name, refs=refs) 

593 

594 @classmethod 

595 def _ensure_array(cls, data, dtype, copy: bool): 

596 """ 

597 Ensure we have a valid array to pass to _simple_new. 

598 """ 

599 if data.ndim > 1: 

600 # GH#13601, GH#20285, GH#27125 

601 raise ValueError("Index data must be 1-dimensional") 

602 elif dtype == np.float16: 

603 # float16 not supported (no indexing engine) 

604 raise NotImplementedError("float16 indexes are not supported") 

605 

606 if copy: 

607 # asarray_tuplesafe does not always copy underlying data, 

608 # so need to make sure that this happens 

609 data = data.copy() 

610 return data 

611 

612 @final 

613 @classmethod 

614 def _dtype_to_subclass(cls, dtype: DtypeObj): 

615 # Delay import for perf. https://github.com/pandas-dev/pandas/pull/31423 

616 

617 if isinstance(dtype, ExtensionDtype): 

618 return dtype.index_class 

619 

620 if dtype.kind == "M": 

621 from pandas import DatetimeIndex 

622 

623 return DatetimeIndex 

624 

625 elif dtype.kind == "m": 

626 from pandas import TimedeltaIndex 

627 

628 return TimedeltaIndex 

629 

630 elif dtype.kind == "O": 

631 # NB: assuming away MultiIndex 

632 return Index 

633 

634 elif issubclass(dtype.type, str) or is_numeric_dtype(dtype): 

635 return Index 

636 

637 raise NotImplementedError(dtype) 

638 

639 # NOTE for new Index creation: 

640 

641 # - _simple_new: It returns new Index with the same type as the caller. 

642 # All metadata (such as name) must be provided by caller's responsibility. 

643 # Using _shallow_copy is recommended because it fills these metadata 

644 # otherwise specified. 

645 

646 # - _shallow_copy: It returns new Index with the same type (using 

647 # _simple_new), but fills caller's metadata otherwise specified. Passed 

648 # kwargs will overwrite corresponding metadata. 

649 

650 # See each method's docstring. 

651 

652 @classmethod 

653 def _simple_new( 

654 cls, 

655 values: ArrayLike, 

656 name: Hashable | None = None, 

657 refs: BlockValuesRefs | None = None, 

658 ) -> Self: 

659 """ 

660 We require that we have a dtype compat for the values. If we are passed 

661 a non-dtype compat, then coerce using the constructor. 

662 

663 Must be careful not to recurse. 

664 """ 

665 assert isinstance(values, cls._data_cls), type(values) 

666 

667 result = object.__new__(cls) 

668 result._data = values 

669 result._name = name 

670 result._cache = {} 

671 result._reset_identity() 

672 if refs is not None: 

673 result._references = refs 

674 else: 

675 result._references = BlockValuesRefs() 

676 result._references.add_index_reference(result) 

677 

678 return result 

679 

680 @classmethod 

681 def _with_infer(cls, *args, **kwargs): 

682 """ 

683 Constructor that uses the 1.0.x behavior inferring numeric dtypes 

684 for ndarray[object] inputs. 

685 """ 

686 result = cls(*args, **kwargs) 

687 

688 if result.dtype == _dtype_obj and not result._is_multi: 

689 # error: Argument 1 to "maybe_convert_objects" has incompatible type 

690 # "Union[ExtensionArray, ndarray[Any, Any]]"; expected 

691 # "ndarray[Any, Any]" 

692 values = lib.maybe_convert_objects(result._values) # type: ignore[arg-type] 

693 if values.dtype.kind in "iufb": 

694 return Index(values, name=result.name, copy=False) 

695 

696 return result 

697 

698 @cache_readonly 

699 def _constructor(self) -> type[Self]: 

700 return type(self) 

701 

702 @final 

703 def _maybe_check_unique(self) -> None: 

704 """ 

705 Check that an Index has no duplicates. 

706 

707 This is typically only called via 

708 `NDFrame.flags.allows_duplicate_labels.setter` when it's set to 

709 True (duplicates aren't allowed). 

710 

711 Raises 

712 ------ 

713 DuplicateLabelError 

714 When the index is not unique. 

715 """ 

716 if not self.is_unique: 

717 msg = """Index has duplicates.""" 

718 duplicates = self._format_duplicate_message() 

719 msg += f"\n{duplicates}" 

720 

721 raise DuplicateLabelError(msg) 

722 

723 @final 

724 def _format_duplicate_message(self) -> DataFrame: 

725 """ 

726 Construct the DataFrame for a DuplicateLabelError. 

727 

728 This returns a DataFrame indicating the labels and positions 

729 of duplicates in an index. This should only be called when it's 

730 already known that duplicates are present. 

731 

732 Examples 

733 -------- 

734 >>> idx = pd.Index(["a", "b", "a"]) 

735 >>> idx._format_duplicate_message() 

736 positions 

737 label 

738 a [0, 2] 

739 """ 

740 from pandas import Series 

741 

742 duplicates = self[self.duplicated(keep="first")].unique() 

743 assert len(duplicates) 

744 

745 out = ( 

746 Series(np.arange(len(self)), copy=False) 

747 .groupby(self, observed=False) 

748 .agg(list)[duplicates] 

749 ) 

750 if self._is_multi: 

751 # test_format_duplicate_labels_message_multi 

752 # error: "Type[Index]" has no attribute "from_tuples" [attr-defined] 

753 out.index = type(self).from_tuples(out.index) # type: ignore[attr-defined] 

754 

755 if self.nlevels == 1: 

756 out = out.rename_axis("label") 

757 return out.to_frame(name="positions") 

758 

759 # -------------------------------------------------------------------- 

760 # Index Internals Methods 

761 

762 def _shallow_copy(self, values, name: Hashable = no_default) -> Self: 

763 """ 

764 Create a new Index with the same class as the caller, don't copy the 

765 data, use the same object attributes with passed in attributes taking 

766 precedence. 

767 

768 *this is an internal non-public method* 

769 

770 Parameters 

771 ---------- 

772 values : the values to create the new Index, optional 

773 name : Label, defaults to self.name 

774 """ 

775 name = self._name if name is no_default else name 

776 

777 return self._simple_new(values, name=name, refs=self._references) 

778 

779 def _view(self) -> Self: 

780 """ 

781 fastpath to make a shallow copy, i.e. new object with same data. 

782 """ 

783 result = self._simple_new(self._values, name=self._name, refs=self._references) 

784 

785 result._cache = self._cache 

786 return result 

787 

788 @final 

789 def _rename(self, name: Hashable) -> Self: 

790 """ 

791 fastpath for rename if new name is already validated. 

792 """ 

793 result = self._view() 

794 result._name = name 

795 return result 

796 

797 @final 

798 def is_(self, other) -> bool: 

799 """ 

800 More flexible, faster check like ``is`` but that works through views. 

801 

802 Note: this is *not* the same as ``Index.identical()``, which checks 

803 that metadata is also the same. 

804 

805 Parameters 

806 ---------- 

807 other : object 

808 Other object to compare against. 

809 

810 Returns 

811 ------- 

812 bool 

813 True if both have same underlying data, False otherwise. 

814 

815 See Also 

816 -------- 

817 Index.identical : Works like ``Index.is_`` but also checks metadata. 

818 

819 Examples 

820 -------- 

821 >>> idx1 = pd.Index(["1", "2", "3"]) 

822 >>> idx1.is_(idx1.view()) 

823 True 

824 

825 >>> idx1.is_(idx1.copy()) 

826 False 

827 """ 

828 if self is other: 

829 return True 

830 elif not hasattr(other, "_id"): 

831 return False 

832 elif self._id is None or other._id is None: 

833 return False 

834 else: 

835 return self._id is other._id 

836 

837 @final 

838 def _reset_identity(self) -> None: 

839 """ 

840 Initializes or resets ``_id`` attribute with new object. 

841 """ 

842 self._id = object() 

843 

844 @final 

845 def _cleanup(self) -> None: 

846 if "_engine" in self._cache: 

847 self._engine.clear_mapping() 

848 

849 @cache_readonly 

850 def _engine( 

851 self, 

852 ) -> libindex.IndexEngine | libindex.ExtensionEngine | libindex.MaskedIndexEngine: 

853 # For base class (object dtype) we get ObjectEngine 

854 target_values = self._get_engine_target() 

855 

856 if isinstance(self._values, ArrowExtensionArray) and self.dtype.kind in "Mm": 

857 import pyarrow as pa 

858 

859 pa_type = self._values._pa_array.type 

860 if pa.types.is_timestamp(pa_type): 

861 target_values = self._values._to_datetimearray() 

862 return libindex.DatetimeEngine(target_values._ndarray) 

863 elif pa.types.is_duration(pa_type): 

864 target_values = self._values._to_timedeltaarray() 

865 return libindex.TimedeltaEngine(target_values._ndarray) 

866 

867 if isinstance(target_values, ExtensionArray): 

868 if isinstance(target_values, (BaseMaskedArray, ArrowExtensionArray)): 

869 try: 

870 return _masked_engines[target_values.dtype.name](target_values) 

871 except KeyError: 

872 # Not supported yet e.g. decimal 

873 pass 

874 elif self._engine_type is libindex.ObjectEngine: 

875 return libindex.ExtensionEngine(target_values) 

876 

877 target_values = cast(np.ndarray, target_values) 

878 # to avoid a reference cycle, bind `target_values` to a local variable, so 

879 # `self` is not passed into the lambda. 

880 if target_values.dtype == bool: 

881 return libindex.BoolEngine(target_values) 

882 elif target_values.dtype == np.complex64: 

883 return libindex.Complex64Engine(target_values) 

884 elif target_values.dtype == np.complex128: 

885 return libindex.Complex128Engine(target_values) 

886 elif needs_i8_conversion(self.dtype): 

887 # We need to keep M8/m8 dtype when initializing the Engine, 

888 # but don't want to change _get_engine_target bc it is used 

889 # elsewhere 

890 # error: Item "ExtensionArray" of "Union[ExtensionArray, 

891 # ndarray[Any, Any]]" has no attribute "_ndarray" [union-attr] 

892 target_values = self._data._ndarray # type: ignore[union-attr] 

893 elif is_string_dtype(self.dtype) and not is_object_dtype(self.dtype): 

894 return libindex.StringObjectEngine(target_values, self.dtype.na_value) # type: ignore[union-attr] 

895 

896 # error: Argument 1 to "ExtensionEngine" has incompatible type 

897 # "ndarray[Any, Any]"; expected "ExtensionArray" 

898 return self._engine_type(target_values) # type: ignore[arg-type] 

899 

900 @final 

901 @cache_readonly 

902 def _dir_additions_for_owner(self) -> set[str_t]: 

903 """ 

904 Add the string-like labels to the owner dataframe/series dir output. 

905 

906 If this is a MultiIndex, it's first level values are used. 

907 """ 

908 return { 

909 c 

910 for c in self.unique(level=0)[: get_option("display.max_dir_items")] 

911 if isinstance(c, str) and c.isidentifier() 

912 } 

913 

914 # -------------------------------------------------------------------- 

915 # Array-Like Methods 

916 

917 # ndarray compat 

918 def __len__(self) -> int: 

919 """ 

920 Return the length of the Index. 

921 """ 

922 return len(self._data) 

923 

924 def __array__(self, dtype=None, copy=None) -> np.ndarray: 

925 """ 

926 The array interface, return my values. 

927 """ 

928 if copy is None: 

929 # Note, that the if branch exists for NumPy 1.x support 

930 return np.asarray(self._data, dtype=dtype) 

931 

932 return np.array(self._data, dtype=dtype, copy=copy) 

933 

934 def __array_ufunc__(self, ufunc: np.ufunc, method: str_t, *inputs, **kwargs): 

935 if any(isinstance(other, (ABCSeries, ABCDataFrame)) for other in inputs): 

936 return NotImplemented 

937 

938 result = arraylike.maybe_dispatch_ufunc_to_dunder_op( 

939 self, ufunc, method, *inputs, **kwargs 

940 ) 

941 if result is not NotImplemented: 

942 return result 

943 

944 if "out" in kwargs: 

945 # e.g. test_dti_isub_tdi 

946 return arraylike.dispatch_ufunc_with_out( 

947 self, ufunc, method, *inputs, **kwargs 

948 ) 

949 

950 if method == "reduce": 

951 result = arraylike.dispatch_reduction_ufunc( 

952 self, ufunc, method, *inputs, **kwargs 

953 ) 

954 if result is not NotImplemented: 

955 return result 

956 

957 new_inputs = [x if x is not self else x._values for x in inputs] 

958 result = getattr(ufunc, method)(*new_inputs, **kwargs) 

959 if ufunc.nout == 2: 

960 # i.e. np.divmod, np.modf, np.frexp 

961 return tuple(self.__array_wrap__(x) for x in result) 

962 elif method == "reduce": 

963 result = lib.item_from_zerodim(result) 

964 return maybe_unbox_numpy_scalar(result) 

965 elif is_scalar(result): 

966 # e.g. matmul 

967 return maybe_unbox_numpy_scalar(result) 

968 

969 if result.dtype == np.float16: 

970 result = result.astype(np.float32) 

971 

972 return self.__array_wrap__(result) 

973 

974 @final 

975 def __array_wrap__(self, result, context=None, return_scalar=False): 

976 """ 

977 Gets called after a ufunc and other functions e.g. np.split. 

978 """ 

979 result = lib.item_from_zerodim(result) 

980 if np.ndim(result) > 1: 

981 # Reached in plotting tests with e.g. np.nonzero(index) 

982 return result 

983 

984 return Index(result, name=self.name) 

985 

986 @cache_readonly 

987 def dtype(self) -> DtypeObj: 

988 """ 

989 Return the dtype object of the underlying data. 

990 

991 See Also 

992 -------- 

993 Index.inferred_type: Return a string of the type inferred from the values. 

994 

995 Examples 

996 -------- 

997 >>> idx = pd.Index([1, 2, 3]) 

998 >>> idx 

999 Index([1, 2, 3], dtype='int64') 

1000 >>> idx.dtype 

1001 dtype('int64') 

1002 """ 

1003 return self._data.dtype 

1004 

1005 @final 

1006 def ravel(self, order: str_t = "C") -> Self: 

1007 """ 

1008 Return a view on self. 

1009 

1010 Parameters 

1011 ---------- 

1012 order : {'K', 'A', 'C', 'F'}, default 'C' 

1013 Specify the memory layout of the view. This parameter is not 

1014 implemented currently. 

1015 

1016 Returns 

1017 ------- 

1018 Index 

1019 A view on self. 

1020 

1021 See Also 

1022 -------- 

1023 numpy.ndarray.ravel : Return a flattened array. 

1024 

1025 Examples 

1026 -------- 

1027 >>> s = pd.Series([1, 2, 3], index=["a", "b", "c"]) 

1028 >>> s.index.ravel() 

1029 Index(['a', 'b', 'c'], dtype='str') 

1030 """ 

1031 return self[:] 

1032 

1033 def view(self, cls=None): 

1034 """ 

1035 Return a view of the Index with the specified dtype or a new Index instance. 

1036 

1037 This method returns a view of the calling Index object if no arguments are 

1038 provided. If a dtype is specified through the `cls` argument, it attempts 

1039 to return a view of the Index with the specified dtype. Note that viewing 

1040 the Index as a different dtype reinterprets the underlying data, which can 

1041 lead to unexpected results for non-numeric or incompatible dtype conversions. 

1042 

1043 Parameters 

1044 ---------- 

1045 cls : data-type or ndarray sub-class, optional 

1046 Data-type descriptor of the returned view, e.g., float32 or int16. 

1047 Omitting it results in the view having the same data-type as `self`. 

1048 This argument can also be specified as an ndarray sub-class, 

1049 e.g., np.int64 or np.float32 which then specifies the type of 

1050 the returned object. 

1051 

1052 Returns 

1053 ------- 

1054 Index or ndarray 

1055 A view of the Index. If `cls` is None, the returned object is an Index 

1056 view with the same dtype as the calling object. If a numeric `cls` is 

1057 specified an ndarray view with the new dtype is returned. 

1058 

1059 Raises 

1060 ------ 

1061 ValueError 

1062 If attempting to change to a dtype in a way that is not compatible with 

1063 the original dtype's memory layout, for example, viewing an 'int64' Index 

1064 as 'str'. 

1065 

1066 See Also 

1067 -------- 

1068 Index.copy : Returns a copy of the Index. 

1069 numpy.ndarray.view : Returns a new view of array with the same data. 

1070 

1071 Examples 

1072 -------- 

1073 >>> idx = pd.Index([-1, 0, 1]) 

1074 >>> idx.view() 

1075 Index([-1, 0, 1], dtype='int64') 

1076 

1077 >>> idx.view(np.uint64) 

1078 array([18446744073709551615, 0, 1], 

1079 dtype=uint64) 

1080 

1081 Viewing as 'int32' or 'float32' reinterprets the memory, which may lead to 

1082 unexpected behavior: 

1083 

1084 >>> idx.view("float32") 

1085 array([ nan, nan, 0.e+00, 0.e+00, 1.e-45, 0.e+00], dtype=float32) 

1086 """ 

1087 # we need to see if we are subclassing an 

1088 # index type here 

1089 if cls is not None: 

1090 dtype = cls 

1091 if isinstance(cls, str): 

1092 dtype = pandas_dtype(cls) 

1093 

1094 if needs_i8_conversion(dtype): 

1095 idx_cls = self._dtype_to_subclass(dtype) 

1096 arr = self.array.view(dtype) 

1097 if isinstance(arr, ExtensionArray): 

1098 # here we exclude non-supported dt64/td64 dtypes 

1099 return idx_cls._simple_new( 

1100 arr, name=self.name, refs=self._references 

1101 ) 

1102 return arr 

1103 

1104 result = self._data.view(cls) 

1105 else: 

1106 result = self._view() 

1107 if isinstance(result, Index): 

1108 result._id = self._id 

1109 return result 

1110 

1111 def astype(self, dtype: Dtype, copy: bool = True): 

1112 """ 

1113 Create an Index with values cast to dtypes. 

1114 

1115 The class of a new Index is determined by dtype. When conversion is 

1116 impossible, a TypeError exception is raised. 

1117 

1118 Parameters 

1119 ---------- 

1120 dtype : numpy dtype or pandas type 

1121 Note that any signed integer `dtype` is treated as ``'int64'``, 

1122 and any unsigned integer `dtype` is treated as ``'uint64'``, 

1123 regardless of the size. 

1124 copy : bool, default True 

1125 By default, astype always returns a newly allocated object. 

1126 If copy is set to False and internal requirements on dtype are 

1127 satisfied, the original data is used to create a new Index 

1128 or the original Index is returned. 

1129 

1130 Returns 

1131 ------- 

1132 Index 

1133 Index with values cast to specified dtype. 

1134 

1135 See Also 

1136 -------- 

1137 Index.dtype: Return the dtype object of the underlying data. 

1138 Index.dtypes: Return the dtype object of the underlying data. 

1139 Index.convert_dtypes: Convert columns to the best possible dtypes. 

1140 

1141 Examples 

1142 -------- 

1143 >>> idx = pd.Index([1, 2, 3]) 

1144 >>> idx 

1145 Index([1, 2, 3], dtype='int64') 

1146 >>> idx.astype("float") 

1147 Index([1.0, 2.0, 3.0], dtype='float64') 

1148 """ 

1149 if dtype is not None: 

1150 dtype = pandas_dtype(dtype) 

1151 

1152 if self.dtype == dtype: 

1153 # Ensure that self.astype(self.dtype) is self 

1154 return self.copy() if copy else self 

1155 

1156 values = self._data 

1157 if isinstance(values, ExtensionArray): 

1158 with rewrite_exception(type(values).__name__, type(self).__name__): 

1159 new_values = values.astype(dtype, copy=copy) 

1160 

1161 elif isinstance(dtype, ExtensionDtype): 

1162 cls = dtype.construct_array_type() 

1163 # Note: for RangeIndex and CategoricalDtype self vs self._values 

1164 # behaves differently here. 

1165 new_values = cls._from_sequence(self, dtype=dtype, copy=copy) 

1166 

1167 else: 

1168 # GH#13149 specifically use astype_array instead of astype 

1169 new_values = astype_array(values, dtype=dtype, copy=copy) 

1170 

1171 # pass copy=False because any copying will be done in the astype above 

1172 result = Index(new_values, name=self.name, dtype=new_values.dtype, copy=False) 

1173 if ( 

1174 not copy 

1175 and self._references is not None 

1176 and astype_is_view(self.dtype, dtype) 

1177 ): 

1178 result._references = self._references 

1179 result._references.add_index_reference(result) 

1180 return result 

1181 

1182 _index_shared_docs["take"] = """ 

1183 Return a new %(klass)s of the values selected by the indices. 

1184 

1185 For internal compatibility with numpy arrays. 

1186 

1187 Parameters 

1188 ---------- 

1189 indices : array-like 

1190 Indices to be taken. 

1191 axis : {0 or 'index'}, optional 

1192 The axis over which to select values, always 0 or 'index'. 

1193 allow_fill : bool, default True 

1194 How to handle negative values in `indices`. 

1195 

1196 * False: negative values in `indices` indicate positional indices 

1197 from the right (the default). This is similar to 

1198 :func:`numpy.take`. 

1199 

1200 * True: negative values in `indices` indicate 

1201 missing values. These values are set to `fill_value`. Any other 

1202 other negative values raise a ``ValueError``. 

1203 

1204 fill_value : scalar, default None 

1205 If allow_fill=True and fill_value is not None, indices specified by 

1206 -1 are regarded as NA. If Index doesn't hold NA, raise ValueError. 

1207 **kwargs 

1208 Required for compatibility with numpy. 

1209 

1210 Returns 

1211 ------- 

1212 Index 

1213 An index formed of elements at the given indices. Will be the same 

1214 type as self, except for RangeIndex. 

1215 

1216 See Also 

1217 -------- 

1218 numpy.ndarray.take: Return an array formed from the 

1219 elements of a at the given indices. 

1220 

1221 Examples 

1222 -------- 

1223 >>> idx = pd.Index(['a', 'b', 'c']) 

1224 >>> idx.take([2, 2, 1, 2]) 

1225 Index(['c', 'c', 'b', 'c'], dtype='str') 

1226 """ 

1227 

1228 def take( 

1229 self, 

1230 indices, 

1231 axis: Axis = 0, 

1232 allow_fill: bool = True, 

1233 fill_value=None, 

1234 **kwargs, 

1235 ) -> Self: 

1236 """ 

1237 Return a new Index of the values selected by the indices. 

1238 

1239 For internal compatibility with numpy arrays. 

1240 

1241 Parameters 

1242 ---------- 

1243 indices : array-like 

1244 Indices to be taken. 

1245 axis : {0 or 'index'}, optional 

1246 The axis over which to select values, always 0 or 'index'. 

1247 allow_fill : bool, default True 

1248 How to handle negative values in `indices`. 

1249 

1250 * False: negative values in `indices` indicate positional indices 

1251 from the right (the default). This is similar to 

1252 :func:`numpy.take`. 

1253 

1254 * True: negative values in `indices` indicate 

1255 missing values. These values are set to `fill_value`. Any 

1256 other negative values raise a ``ValueError``. 

1257 

1258 fill_value : scalar, default None 

1259 If allow_fill=True and fill_value is not None, indices specified by 

1260 -1 are regarded as NA. If Index doesn't hold NA, raise ValueError. 

1261 **kwargs 

1262 Required for compatibility with numpy. 

1263 

1264 Returns 

1265 ------- 

1266 Index 

1267 An index formed of elements at the given indices. Will be the same 

1268 type as self, except for RangeIndex. 

1269 

1270 See Also 

1271 -------- 

1272 numpy.ndarray.take: Return an array formed from the 

1273 elements of a at the given indices. 

1274 

1275 Examples 

1276 -------- 

1277 >>> idx = pd.Index(["a", "b", "c"]) 

1278 >>> idx.take([2, 2, 1, 2]) 

1279 Index(['c', 'c', 'b', 'c'], dtype='str') 

1280 """ 

1281 if kwargs: 

1282 nv.validate_take((), kwargs) 

1283 if is_scalar(indices): 

1284 raise TypeError("Expected indices to be array-like") 

1285 indices = ensure_platform_int(indices) 

1286 allow_fill = self._maybe_disallow_fill(allow_fill, fill_value, indices) 

1287 

1288 if indices.ndim == 1 and lib.is_range_indexer(indices, len(self)): 

1289 return self.copy() 

1290 

1291 # Note: we discard fill_value and use self._na_value, only relevant 

1292 # in the case where allow_fill is True and fill_value is not None 

1293 values = self._values 

1294 if isinstance(values, np.ndarray): 

1295 taken = algos.take( 

1296 values, indices, allow_fill=allow_fill, fill_value=self._na_value 

1297 ) 

1298 else: 

1299 # algos.take passes 'axis' keyword which not all EAs accept 

1300 taken = values.take( 

1301 indices, allow_fill=allow_fill, fill_value=self._na_value 

1302 ) 

1303 return self._constructor._simple_new(taken, name=self.name) 

1304 

1305 @final 

1306 def _maybe_disallow_fill(self, allow_fill: bool, fill_value, indices) -> bool: 

1307 """ 

1308 We only use pandas-style take when allow_fill is True _and_ 

1309 fill_value is not None. 

1310 """ 

1311 if allow_fill and fill_value is not None: 

1312 # only fill if we are passing a non-None fill_value 

1313 if self._can_hold_na: 

1314 if (indices < -1).any(): 

1315 raise ValueError( 

1316 "When allow_fill=True and fill_value is not None, " 

1317 "all indices must be >= -1" 

1318 ) 

1319 else: 

1320 cls_name = type(self).__name__ 

1321 raise ValueError( 

1322 f"Unable to fill values because {cls_name} cannot contain NA" 

1323 ) 

1324 else: 

1325 allow_fill = False 

1326 return allow_fill 

1327 

1328 def repeat(self, repeats, axis: None = None) -> Self: 

1329 """ 

1330 Repeat elements of an Index. 

1331 

1332 Returns a new Index where each element of the current Index 

1333 is repeated consecutively a given number of times. 

1334 

1335 Parameters 

1336 ---------- 

1337 repeats : int or array of ints 

1338 The number of repetitions for each element. This should be a 

1339 non-negative integer. Repeating 0 times will return an empty 

1340 Index. 

1341 axis : None 

1342 Must be ``None``. Has no effect but is accepted for compatibility 

1343 with numpy. 

1344 

1345 Returns 

1346 ------- 

1347 Index 

1348 Newly created Index with repeated elements. 

1349 

1350 See Also 

1351 -------- 

1352 Series.repeat : Equivalent function for Series. 

1353 numpy.repeat : Similar method for :class:`numpy.ndarray`. 

1354 

1355 Examples 

1356 -------- 

1357 >>> idx = pd.Index(["a", "b", "c"]) 

1358 >>> idx 

1359 Index(['a', 'b', 'c'], dtype='str') 

1360 >>> idx.repeat(2) 

1361 Index(['a', 'a', 'b', 'b', 'c', 'c'], dtype='str') 

1362 >>> idx.repeat([1, 2, 3]) 

1363 Index(['a', 'b', 'b', 'c', 'c', 'c'], dtype='str') 

1364 """ 

1365 repeats = ensure_platform_int(repeats) 

1366 nv.validate_repeat((), {"axis": axis}) 

1367 res_values = self._values.repeat(repeats) 

1368 

1369 # _constructor so RangeIndex-> Index with an int64 dtype 

1370 return self._constructor._simple_new(res_values, name=self.name) 

1371 

1372 # -------------------------------------------------------------------- 

1373 # Copying Methods 

1374 

1375 def copy( 

1376 self, 

1377 name: Hashable | None = None, 

1378 deep: bool = False, 

1379 ) -> Self: 

1380 """ 

1381 Make a copy of this object. 

1382 

1383 Name is set on the new object. 

1384 

1385 Parameters 

1386 ---------- 

1387 name : Label, optional 

1388 Set name for new object. 

1389 deep : bool, default False 

1390 If True attempts to make a deep copy of the Index. 

1391 Else makes a shallow copy. 

1392 

1393 Returns 

1394 ------- 

1395 Index 

1396 Index refer to new object which is a copy of this object. 

1397 

1398 See Also 

1399 -------- 

1400 Index.delete: Make new Index with passed location(-s) deleted. 

1401 Index.drop: Make new Index with passed list of labels deleted. 

1402 

1403 Notes 

1404 ----- 

1405 In most cases, there should be no functional difference from using 

1406 ``deep``, but if ``deep`` is passed it will attempt to deepcopy. 

1407 

1408 Examples 

1409 -------- 

1410 >>> idx = pd.Index(["a", "b", "c"]) 

1411 >>> new_idx = idx.copy() 

1412 >>> idx is new_idx 

1413 False 

1414 """ 

1415 

1416 name = self._validate_names(name=name, deep=deep)[0] 

1417 if deep: 

1418 new_data = self._data.copy() 

1419 new_index = type(self)._simple_new(new_data, name=name) 

1420 else: 

1421 new_index = self._rename(name=name) 

1422 return new_index 

1423 

1424 @final 

1425 def __copy__(self) -> Self: 

1426 return self.copy(deep=False) 

1427 

1428 @final 

1429 def __deepcopy__(self, memo=None) -> Self: 

1430 """ 

1431 Parameters 

1432 ---------- 

1433 memo, default None 

1434 Standard signature. Unused 

1435 """ 

1436 return self.copy(deep=True) 

1437 

1438 # -------------------------------------------------------------------- 

1439 # Rendering Methods 

1440 

1441 @final 

1442 def __repr__(self) -> str_t: 

1443 """ 

1444 Return a string representation for this object. 

1445 """ 

1446 klass_name = type(self).__name__ 

1447 data = self._format_data() 

1448 attrs = self._format_attrs() 

1449 attrs_str = [f"{k}={v}" for k, v in attrs] 

1450 prepr = ", ".join(attrs_str) 

1451 

1452 return f"{klass_name}({data}{prepr})" 

1453 

1454 @property 

1455 def _formatter_func(self): 

1456 """ 

1457 Return the formatter function. 

1458 """ 

1459 return default_pprint 

1460 

1461 @final 

1462 def _format_data(self, name=None) -> str_t: 

1463 """ 

1464 Return the formatted data as a unicode string. 

1465 """ 

1466 # do we want to justify (only do so for non-objects) 

1467 is_justify = True 

1468 

1469 if self.inferred_type == "string": 

1470 is_justify = False 

1471 elif isinstance(self.dtype, CategoricalDtype): 

1472 self = cast("CategoricalIndex", self) 

1473 if is_string_dtype(self.categories.dtype): 

1474 is_justify = False 

1475 elif isinstance(self, ABCRangeIndex): 

1476 # We will do the relevant formatting via attrs 

1477 return "" 

1478 

1479 return format_object_summary( 

1480 self, 

1481 self._formatter_func, 

1482 is_justify=is_justify, 

1483 name=name, 

1484 line_break_each_value=self._is_multi, 

1485 ) 

1486 

1487 def _format_attrs(self) -> list[tuple[str_t, str_t | int | bool | None]]: 

1488 """ 

1489 Return a list of tuples of the (attr,formatted_value). 

1490 """ 

1491 attrs: list[tuple[str_t, str_t | int | bool | None]] = [] 

1492 

1493 if not self._is_multi: 

1494 attrs.append(("dtype", f"'{self.dtype}'")) 

1495 

1496 if self.name is not None: 

1497 attrs.append(("name", default_pprint(self.name))) 

1498 elif self._is_multi and any(x is not None for x in self.names): 

1499 attrs.append(("names", default_pprint(self.names))) 

1500 

1501 max_seq_items = get_option("display.max_seq_items") or len(self) 

1502 if len(self) > max_seq_items: 

1503 attrs.append(("length", len(self))) 

1504 return attrs 

1505 

1506 @final 

1507 def _get_level_names(self) -> range | Sequence[Hashable]: 

1508 """ 

1509 Return a name or list of names with None replaced by the level number. 

1510 """ 

1511 if self._is_multi: 

1512 return maybe_sequence_to_range( 

1513 [ 

1514 level if name is None else name 

1515 for level, name in enumerate(self.names) 

1516 ] 

1517 ) 

1518 else: 

1519 return range(1) if self.name is None else [self.name] 

1520 

1521 @final 

1522 def _mpl_repr(self) -> np.ndarray: 

1523 # how to represent ourselves to matplotlib 

1524 if isinstance(self.dtype, np.dtype) and self.dtype.kind != "M": 

1525 return cast(np.ndarray, self.values) 

1526 return self.astype(object, copy=False)._values 

1527 

1528 _default_na_rep = "NaN" 

1529 

1530 @final 

1531 def _format_flat( 

1532 self, 

1533 *, 

1534 include_name: bool, 

1535 formatter: Callable | None = None, 

1536 ) -> list[str_t]: 

1537 """ 

1538 Render a string representation of the Index. 

1539 """ 

1540 header = [] 

1541 if include_name: 

1542 header.append( 

1543 pprint_thing(self.name, escape_chars=("\t", "\r", "\n")) 

1544 if self.name is not None 

1545 else "" 

1546 ) 

1547 

1548 if formatter is not None: 

1549 return header + list(self.map(formatter)) 

1550 

1551 return self._format_with_header(header=header, na_rep=self._default_na_rep) 

1552 

1553 def _format_with_header(self, *, header: list[str_t], na_rep: str_t) -> list[str_t]: 

1554 from pandas.io.formats.format import format_array 

1555 

1556 values = self._values 

1557 

1558 if ( 

1559 is_object_dtype(values.dtype) 

1560 or is_string_dtype(values.dtype) 

1561 or isinstance(self.dtype, (IntervalDtype, CategoricalDtype)) 

1562 ): 

1563 # TODO: why do we need different justify for these cases? 

1564 justify = "all" 

1565 else: 

1566 justify = "left" 

1567 # passing leading_space=False breaks test_format_missing, 

1568 # test_index_repr_in_frame_with_nan, but would otherwise make 

1569 # trim_front unnecessary 

1570 formatted = format_array(values, None, justify=justify) 

1571 result = trim_front(formatted) 

1572 return header + result 

1573 

1574 def _get_values_for_csv( 

1575 self, 

1576 *, 

1577 na_rep: str_t = "", 

1578 decimal: str_t = ".", 

1579 float_format=None, 

1580 date_format=None, 

1581 quoting=None, 

1582 ) -> npt.NDArray[np.object_]: 

1583 return get_values_for_csv( 

1584 self._values, 

1585 na_rep=na_rep, 

1586 decimal=decimal, 

1587 float_format=float_format, 

1588 date_format=date_format, 

1589 quoting=quoting, 

1590 ) 

1591 

1592 def _summary(self, name=None) -> str_t: 

1593 """ 

1594 Return a summarized representation. 

1595 

1596 Parameters 

1597 ---------- 

1598 name : str 

1599 name to use in the summary representation 

1600 

1601 Returns 

1602 ------- 

1603 String with a summarized representation of the index 

1604 """ 

1605 if len(self) > 0: 

1606 head = self[0] 

1607 if hasattr(head, "format") and not isinstance(head, str): 

1608 head = head.format() 

1609 elif needs_i8_conversion(self.dtype): 

1610 # e.g. Timedelta, display as values, not quoted 

1611 head = self._formatter_func(head).replace("'", "") 

1612 tail = self[-1] 

1613 if hasattr(tail, "format") and not isinstance(tail, str): 

1614 tail = tail.format() 

1615 elif needs_i8_conversion(self.dtype): 

1616 # e.g. Timedelta, display as values, not quoted 

1617 tail = self._formatter_func(tail).replace("'", "") 

1618 

1619 index_summary = f", {head} to {tail}" 

1620 else: 

1621 index_summary = "" 

1622 

1623 if name is None: 

1624 name = type(self).__name__ 

1625 return f"{name}: {len(self)} entries{index_summary}" 

1626 

1627 # -------------------------------------------------------------------- 

1628 # Conversion Methods 

1629 

1630 def to_flat_index(self) -> Self: 

1631 """ 

1632 Identity method. 

1633 

1634 This is implemented for compatibility with subclass implementations 

1635 when chaining. 

1636 

1637 Returns 

1638 ------- 

1639 pd.Index 

1640 Caller. 

1641 

1642 See Also 

1643 -------- 

1644 MultiIndex.to_flat_index : Subclass implementation. 

1645 """ 

1646 return self 

1647 

1648 @final 

1649 def to_series(self, index=None, name: Hashable | None = None) -> Series: 

1650 """ 

1651 Create a Series with both index and values equal to the index keys. 

1652 

1653 Useful with map for returning an indexer based on an index. 

1654 

1655 Parameters 

1656 ---------- 

1657 index : Index, optional 

1658 Index of resulting Series. If None, defaults to original index. 

1659 name : str, optional 

1660 Name of resulting Series. If None, defaults to name of original 

1661 index. 

1662 

1663 Returns 

1664 ------- 

1665 Series 

1666 The dtype will be based on the type of the Index values. 

1667 

1668 See Also 

1669 -------- 

1670 Index.to_frame : Convert an Index to a DataFrame. 

1671 Series.to_frame : Convert Series to DataFrame. 

1672 

1673 Examples 

1674 -------- 

1675 >>> idx = pd.Index(["Ant", "Bear", "Cow"], name="animal") 

1676 

1677 By default, the original index and original name is reused. 

1678 

1679 >>> idx.to_series() 

1680 animal 

1681 Ant Ant 

1682 Bear Bear 

1683 Cow Cow 

1684 Name: animal, dtype: str 

1685 

1686 To enforce a new index, specify new labels to ``index``: 

1687 

1688 >>> idx.to_series(index=[0, 1, 2]) 

1689 0 Ant 

1690 1 Bear 

1691 2 Cow 

1692 Name: animal, dtype: str 

1693 

1694 To override the name of the resulting column, specify ``name``: 

1695 

1696 >>> idx.to_series(name="zoo") 

1697 animal 

1698 Ant Ant 

1699 Bear Bear 

1700 Cow Cow 

1701 Name: zoo, dtype: str 

1702 """ 

1703 from pandas import Series 

1704 

1705 if index is None: 

1706 index = self._view() 

1707 if name is None: 

1708 name = self.name 

1709 

1710 return Series(self._values.copy(), index=index, name=name) 

1711 

1712 def to_frame( 

1713 self, index: bool = True, name: Hashable = lib.no_default 

1714 ) -> DataFrame: 

1715 """ 

1716 Create a DataFrame with a column containing the Index. 

1717 

1718 Parameters 

1719 ---------- 

1720 index : bool, default True 

1721 Set the index of the returned DataFrame as the original Index. 

1722 

1723 name : object, defaults to index.name 

1724 The passed name should substitute for the index name (if it has 

1725 one). 

1726 

1727 Returns 

1728 ------- 

1729 DataFrame 

1730 DataFrame containing the original Index data. 

1731 

1732 See Also 

1733 -------- 

1734 Index.to_series : Convert an Index to a Series. 

1735 Series.to_frame : Convert Series to DataFrame. 

1736 

1737 Examples 

1738 -------- 

1739 >>> idx = pd.Index(["Ant", "Bear", "Cow"], name="animal") 

1740 >>> idx.to_frame() 

1741 animal 

1742 animal 

1743 Ant Ant 

1744 Bear Bear 

1745 Cow Cow 

1746 

1747 By default, the original Index is reused. To enforce a new Index: 

1748 

1749 >>> idx.to_frame(index=False) 

1750 animal 

1751 0 Ant 

1752 1 Bear 

1753 2 Cow 

1754 

1755 To override the name of the resulting column, specify `name`: 

1756 

1757 >>> idx.to_frame(index=False, name="zoo") 

1758 zoo 

1759 0 Ant 

1760 1 Bear 

1761 2 Cow 

1762 """ 

1763 from pandas import DataFrame 

1764 

1765 if name is lib.no_default: 

1766 result_name = self._get_level_names() 

1767 else: 

1768 result_name = Index([name]) # type: ignore[assignment] 

1769 result = DataFrame(self, copy=False) 

1770 result.columns = result_name 

1771 

1772 if index: 

1773 result.index = self 

1774 return result 

1775 

1776 # -------------------------------------------------------------------- 

1777 # Name-Centric Methods 

1778 

1779 @property 

1780 def name(self) -> Hashable: 

1781 """ 

1782 Return Index or MultiIndex name. 

1783 

1784 Returns 

1785 ------- 

1786 label (hashable object) 

1787 The name of the Index. 

1788 

1789 See Also 

1790 -------- 

1791 Index.set_names: Able to set new names partially and by level. 

1792 Index.rename: Able to set new names partially and by level. 

1793 Series.name: Corresponding Series property. 

1794 

1795 Examples 

1796 -------- 

1797 >>> idx = pd.Index([1, 2, 3], name="x") 

1798 >>> idx 

1799 Index([1, 2, 3], dtype='int64', name='x') 

1800 >>> idx.name 

1801 'x' 

1802 """ 

1803 return self._name 

1804 

1805 @name.setter 

1806 def name(self, value: Hashable) -> None: 

1807 if self._no_setting_name: 

1808 # Used in MultiIndex.levels to avoid silently ignoring name updates. 

1809 raise RuntimeError( 

1810 "Cannot set name on a level of a MultiIndex. Use " 

1811 "'MultiIndex.set_names' instead." 

1812 ) 

1813 maybe_extract_name(value, None, type(self)) 

1814 self._name = value 

1815 

1816 @final 

1817 def _validate_names( 

1818 self, name=None, names=None, deep: bool = False 

1819 ) -> list[Hashable]: 

1820 """ 

1821 Handles the quirks of having a singular 'name' parameter for general 

1822 Index and plural 'names' parameter for MultiIndex. 

1823 """ 

1824 from copy import deepcopy 

1825 

1826 if names is not None and name is not None: 

1827 raise TypeError("Can only provide one of `names` and `name`") 

1828 if names is None and name is None: 

1829 new_names = deepcopy(self.names) if deep else self.names 

1830 elif names is not None: 

1831 if not is_list_like(names): 

1832 raise TypeError("Must pass list-like as `names`.") 

1833 new_names = names 

1834 elif not is_list_like(name): 

1835 new_names = [name] 

1836 else: 

1837 new_names = name 

1838 

1839 if len(new_names) != len(self.names): 

1840 raise ValueError( 

1841 f"Length of new names must be {len(self.names)}, got {len(new_names)}" 

1842 ) 

1843 

1844 # All items in 'new_names' need to be hashable 

1845 validate_all_hashable(*new_names, error_name=f"{type(self).__name__}.name") 

1846 

1847 return new_names 

1848 

1849 def _get_default_index_names( 

1850 self, names: Hashable | Sequence[Hashable] | None = None, default=None 

1851 ) -> list[Hashable]: 

1852 """ 

1853 Get names of index. 

1854 

1855 Parameters 

1856 ---------- 

1857 names : int, str or 1-dimensional list, default None 

1858 Index names to set. 

1859 default : str 

1860 Default name of index. 

1861 

1862 Raises 

1863 ------ 

1864 TypeError 

1865 if names not str or list-like 

1866 """ 

1867 from pandas.core.indexes.multi import MultiIndex 

1868 

1869 if names is not None: 

1870 if isinstance(names, (int, str)): 

1871 names = [names] 

1872 

1873 if not isinstance(names, list) and names is not None: 

1874 raise ValueError("Index names must be str or 1-dimensional list") 

1875 

1876 if not names: 

1877 if isinstance(self, MultiIndex): 

1878 names = com.fill_missing_names(self.names) 

1879 else: 

1880 names = [default] if self.name is None else [self.name] 

1881 

1882 return names 

1883 

1884 def _get_names(self) -> FrozenList: 

1885 """ 

1886 Get names on index. 

1887 

1888 This method returns a FrozenList containing the names of the object. 

1889 It's primarily intended for internal use. 

1890 

1891 Returns 

1892 ------- 

1893 FrozenList 

1894 A FrozenList containing the object's names, contains None if the object 

1895 does not have a name. 

1896 

1897 See Also 

1898 -------- 

1899 Index.name : Index name as a string, or None for MultiIndex. 

1900 

1901 Examples 

1902 -------- 

1903 >>> idx = pd.Index([1, 2, 3], name="x") 

1904 >>> idx.names 

1905 FrozenList(['x']) 

1906 

1907 >>> idx = pd.Index([1, 2, 3], name=("x", "y")) 

1908 >>> idx.names 

1909 FrozenList([('x', 'y')]) 

1910 

1911 If the index does not have a name set: 

1912 

1913 >>> idx = pd.Index([1, 2, 3]) 

1914 >>> idx.names 

1915 FrozenList([None]) 

1916 """ 

1917 return FrozenList((self.name,)) 

1918 

1919 def _set_names(self, values, *, level=None) -> None: 

1920 """ 

1921 Set new names on index. Each name has to be a hashable type. 

1922 

1923 Parameters 

1924 ---------- 

1925 values : str or sequence 

1926 name(s) to set 

1927 level : int, level name, or sequence of int/level names (default None) 

1928 If the index is a MultiIndex (hierarchical), level(s) to set (None 

1929 for all levels). Otherwise level must be None 

1930 

1931 Raises 

1932 ------ 

1933 TypeError if each name is not hashable. 

1934 """ 

1935 if not is_list_like(values): 

1936 raise ValueError("Names must be a list-like") 

1937 if len(values) != 1: 

1938 raise ValueError(f"Length of new names must be 1, got {len(values)}") 

1939 

1940 # GH 20527 

1941 # All items in 'name' need to be hashable: 

1942 validate_all_hashable(*values, error_name=f"{type(self).__name__}.name") 

1943 

1944 self._name = values[0] 

1945 

1946 names = property(fset=_set_names, fget=_get_names) 

1947 

1948 @overload 

1949 def set_names(self, names, *, level=..., inplace: Literal[False] = ...) -> Self: ... 

1950 

1951 @overload 

1952 def set_names(self, names, *, level=..., inplace: Literal[True]) -> None: ... 

1953 

1954 @overload 

1955 def set_names(self, names, *, level=..., inplace: bool = ...) -> Self | None: ... 

1956 

1957 def set_names(self, names, *, level=None, inplace: bool = False) -> Self | None: 

1958 """ 

1959 Set Index or MultiIndex name. 

1960 

1961 Able to set new names partially and by level. 

1962 

1963 Parameters 

1964 ---------- 

1965 names : Hashable or a sequence of the previous or dict-like for MultiIndex 

1966 Name(s) to set. 

1967 

1968 level : int, Hashable or a sequence of the previous, optional 

1969 If the index is a MultiIndex and names is not dict-like, level(s) to set 

1970 (None for all levels). Otherwise level must be None. 

1971 

1972 inplace : bool, default False 

1973 Modifies the object directly, instead of creating a new Index or 

1974 MultiIndex. 

1975 

1976 Returns 

1977 ------- 

1978 Index or None 

1979 The same type as the caller or None if ``inplace=True``. 

1980 

1981 See Also 

1982 -------- 

1983 Index.rename : Able to set new names without level. 

1984 

1985 Examples 

1986 -------- 

1987 >>> idx = pd.Index([1, 2, 3, 4]) 

1988 >>> idx 

1989 Index([1, 2, 3, 4], dtype='int64') 

1990 >>> idx.set_names("quarter") 

1991 Index([1, 2, 3, 4], dtype='int64', name='quarter') 

1992 

1993 >>> idx = pd.MultiIndex.from_product([["python", "cobra"], [2018, 2019]]) 

1994 >>> idx 

1995 MultiIndex([('python', 2018), 

1996 ('python', 2019), 

1997 ( 'cobra', 2018), 

1998 ( 'cobra', 2019)], 

1999 ) 

2000 >>> idx = idx.set_names(["kind", "year"]) 

2001 >>> idx.set_names("species", level=0) 

2002 MultiIndex([('python', 2018), 

2003 ('python', 2019), 

2004 ( 'cobra', 2018), 

2005 ( 'cobra', 2019)], 

2006 names=['species', 'year']) 

2007 

2008 When renaming levels with a dict, levels can not be passed. 

2009 

2010 >>> idx.set_names({"kind": "snake"}) 

2011 MultiIndex([('python', 2018), 

2012 ('python', 2019), 

2013 ( 'cobra', 2018), 

2014 ( 'cobra', 2019)], 

2015 names=['snake', 'year']) 

2016 """ 

2017 if level is not None and not isinstance(self, ABCMultiIndex): 

2018 raise ValueError("Level must be None for non-MultiIndex") 

2019 

2020 if level is not None and not is_list_like(level) and is_list_like(names): 

2021 raise TypeError("Names must be a string when a single level is provided.") 

2022 

2023 if not is_list_like(names) and level is None and self.nlevels > 1: 

2024 raise TypeError("Must pass list-like as `names`.") 

2025 

2026 if is_dict_like(names) and not isinstance(self, ABCMultiIndex): 

2027 raise TypeError("Can only pass dict-like as `names` for MultiIndex.") 

2028 

2029 if is_dict_like(names) and level is not None: 

2030 raise TypeError("Can not pass level for dictlike `names`.") 

2031 

2032 if isinstance(self, ABCMultiIndex) and is_dict_like(names) and level is None: 

2033 # Transform dict to list of new names and corresponding levels 

2034 level, names_adjusted = [], [] 

2035 for i, name in enumerate(self.names): 

2036 if name in names.keys(): 

2037 level.append(i) 

2038 names_adjusted.append(names[name]) 

2039 names = names_adjusted 

2040 

2041 if not is_list_like(names): 

2042 names = [names] 

2043 if level is not None and not is_list_like(level): 

2044 level = [level] 

2045 

2046 if inplace: 

2047 idx = self 

2048 else: 

2049 idx = self._view() 

2050 

2051 idx._set_names(names, level=level) 

2052 if not inplace: 

2053 return idx 

2054 return None 

2055 

2056 @overload 

2057 def rename(self, name, *, inplace: Literal[False] = ...) -> Self: ... 

2058 

2059 @overload 

2060 def rename(self, name, *, inplace: Literal[True]) -> None: ... 

2061 

2062 def rename(self, name, *, inplace: bool = False) -> Self | None: 

2063 """ 

2064 Alter Index or MultiIndex name. 

2065 

2066 Able to set new names without level. Defaults to returning new index. 

2067 Length of names must match number of levels in MultiIndex. 

2068 

2069 Parameters 

2070 ---------- 

2071 name : Hashable or a sequence of the previous 

2072 Name(s) to set. 

2073 inplace : bool, default False 

2074 Modifies the object directly, instead of creating a new Index or 

2075 MultiIndex. 

2076 

2077 Returns 

2078 ------- 

2079 Index or None 

2080 The same type as the caller or None if ``inplace=True``. 

2081 

2082 See Also 

2083 -------- 

2084 Index.set_names : Able to set new names partially and by level. 

2085 

2086 Examples 

2087 -------- 

2088 >>> idx = pd.Index(["A", "C", "A", "B"], name="score") 

2089 >>> idx.rename("grade") 

2090 Index(['A', 'C', 'A', 'B'], dtype='str', name='grade') 

2091 

2092 >>> idx = pd.MultiIndex.from_product( 

2093 ... [["python", "cobra"], [2018, 2019]], names=["kind", "year"] 

2094 ... ) 

2095 >>> idx 

2096 MultiIndex([('python', 2018), 

2097 ('python', 2019), 

2098 ( 'cobra', 2018), 

2099 ( 'cobra', 2019)], 

2100 names=['kind', 'year']) 

2101 >>> idx.rename(["species", "year"]) 

2102 MultiIndex([('python', 2018), 

2103 ('python', 2019), 

2104 ( 'cobra', 2018), 

2105 ( 'cobra', 2019)], 

2106 names=['species', 'year']) 

2107 >>> idx.rename("species") 

2108 Traceback (most recent call last): 

2109 TypeError: Must pass list-like as `names`. 

2110 """ 

2111 return self.set_names([name], inplace=inplace) 

2112 

2113 # -------------------------------------------------------------------- 

2114 # Level-Centric Methods 

2115 

2116 @property 

2117 def nlevels(self) -> int: 

2118 """ 

2119 Number of levels. 

2120 """ 

2121 return 1 

2122 

2123 def _sort_levels_monotonic(self) -> Self: 

2124 """ 

2125 Compat with MultiIndex. 

2126 """ 

2127 return self 

2128 

2129 @final 

2130 def _validate_index_level(self, level) -> None: 

2131 """ 

2132 Validate index level. 

2133 

2134 For single-level Index getting level number is a no-op, but some 

2135 verification must be done like in MultiIndex. 

2136 

2137 """ 

2138 if isinstance(level, int): 

2139 if level < 0 and level != -1: 

2140 raise IndexError( 

2141 "Too many levels: Index has only 1 level, " 

2142 f"{level} is not a valid level number" 

2143 ) 

2144 if level > 0: 

2145 raise IndexError( 

2146 f"Too many levels: Index has only 1 level, not {level + 1}" 

2147 ) 

2148 elif level != self.name: 

2149 raise KeyError( 

2150 f"Requested level ({level}) does not match index name ({self.name})" 

2151 ) 

2152 

2153 def _get_level_number(self, level) -> int: 

2154 self._validate_index_level(level) 

2155 return 0 

2156 

2157 def sortlevel( 

2158 self, 

2159 level=None, 

2160 ascending: bool | list[bool] = True, 

2161 sort_remaining=None, 

2162 na_position: NaPosition = "first", 

2163 ) -> tuple[Self, np.ndarray]: 

2164 """ 

2165 For internal compatibility with the Index API. 

2166 

2167 Sort the Index. This is for compat with MultiIndex 

2168 

2169 Parameters 

2170 ---------- 

2171 ascending : bool, default True 

2172 False to sort in descending order 

2173 na_position : {'first' or 'last'}, default 'first' 

2174 Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at 

2175 the end. 

2176 

2177 .. versionadded:: 2.1.0 

2178 

2179 level, sort_remaining are compat parameters 

2180 

2181 Returns 

2182 ------- 

2183 Index 

2184 """ 

2185 if not isinstance(ascending, (list, bool)): 

2186 raise TypeError( 

2187 "ascending must be a single bool value or" 

2188 "a list of bool values of length 1" 

2189 ) 

2190 

2191 if isinstance(ascending, list): 

2192 if len(ascending) != 1: 

2193 raise TypeError("ascending must be a list of bool values of length 1") 

2194 ascending = ascending[0] 

2195 

2196 if not isinstance(ascending, bool): 

2197 raise TypeError("ascending must be a bool value") 

2198 

2199 return self.sort_values( 

2200 return_indexer=True, ascending=ascending, na_position=na_position 

2201 ) 

2202 

2203 def _get_level_values(self, level) -> Index: 

2204 """ 

2205 Return an Index of values for requested level. 

2206 

2207 This is primarily useful to get an individual level of values from a 

2208 MultiIndex, but is provided on Index as well for compatibility. 

2209 

2210 Parameters 

2211 ---------- 

2212 level : int or str 

2213 It is either the integer position or the name of the level. 

2214 

2215 Returns 

2216 ------- 

2217 Index 

2218 Calling object, as there is only one level in the Index. 

2219 

2220 See Also 

2221 -------- 

2222 MultiIndex.get_level_values : Get values for a level of a MultiIndex. 

2223 

2224 Notes 

2225 ----- 

2226 For Index, level should be 0, since there are no multiple levels. 

2227 

2228 Examples 

2229 -------- 

2230 >>> idx = pd.Index(list("abc")) 

2231 >>> idx 

2232 Index(['a', 'b', 'c'], dtype='str') 

2233 

2234 Get level values by supplying `level` as integer: 

2235 

2236 >>> idx.get_level_values(0) 

2237 Index(['a', 'b', 'c'], dtype='str') 

2238 """ 

2239 self._validate_index_level(level) 

2240 return self 

2241 

2242 get_level_values = _get_level_values 

2243 

2244 @final 

2245 def droplevel(self, level: IndexLabel = 0): 

2246 """ 

2247 Return index with requested level(s) removed. 

2248 

2249 If resulting index has only 1 level left, the result will be 

2250 of Index type, not MultiIndex. The original index is not modified inplace. 

2251 

2252 Parameters 

2253 ---------- 

2254 level : int, str, or list-like, default 0 

2255 If a string is given, must be the name of a level 

2256 If list-like, elements must be names or indexes of levels. 

2257 

2258 Returns 

2259 ------- 

2260 Index or MultiIndex 

2261 Returns an Index or MultiIndex object, depending on the resulting index 

2262 after removing the requested level(s). 

2263 

2264 See Also 

2265 -------- 

2266 Index.dropna : Return Index without NA/NaN values. 

2267 

2268 Examples 

2269 -------- 

2270 >>> mi = pd.MultiIndex.from_arrays( 

2271 ... [[1, 2], [3, 4], [5, 6]], names=["x", "y", "z"] 

2272 ... ) 

2273 >>> mi 

2274 MultiIndex([(1, 3, 5), 

2275 (2, 4, 6)], 

2276 names=['x', 'y', 'z']) 

2277 

2278 >>> mi.droplevel() 

2279 MultiIndex([(3, 5), 

2280 (4, 6)], 

2281 names=['y', 'z']) 

2282 

2283 >>> mi.droplevel(2) 

2284 MultiIndex([(1, 3), 

2285 (2, 4)], 

2286 names=['x', 'y']) 

2287 

2288 >>> mi.droplevel("z") 

2289 MultiIndex([(1, 3), 

2290 (2, 4)], 

2291 names=['x', 'y']) 

2292 

2293 >>> mi.droplevel(["x", "y"]) 

2294 Index([5, 6], dtype='int64', name='z') 

2295 """ 

2296 if not isinstance(level, (tuple, list)): 

2297 level = [level] 

2298 

2299 levnums = sorted((self._get_level_number(lev) for lev in level), reverse=True) 

2300 

2301 return self._drop_level_numbers(levnums) 

2302 

2303 @final 

2304 def _drop_level_numbers(self, levnums: list[int]): 

2305 """ 

2306 Drop MultiIndex levels by level _number_, not name. 

2307 """ 

2308 

2309 if not levnums and not isinstance(self, ABCMultiIndex): 

2310 return self 

2311 if len(levnums) >= self.nlevels: 

2312 raise ValueError( 

2313 f"Cannot remove {len(levnums)} levels from an index with " 

2314 f"{self.nlevels} levels: at least one level must be left." 

2315 ) 

2316 # The two checks above guarantee that here self is a MultiIndex 

2317 self = cast("MultiIndex", self) 

2318 

2319 new_levels = list(self.levels) 

2320 new_codes = list(self.codes) 

2321 new_names = list(self.names) 

2322 

2323 for i in levnums: 

2324 new_levels.pop(i) 

2325 new_codes.pop(i) 

2326 new_names.pop(i) 

2327 

2328 if len(new_levels) == 1: 

2329 lev = new_levels[0] 

2330 

2331 if len(lev) == 0: 

2332 # If lev is empty, lev.take will fail GH#42055 

2333 if len(new_codes[0]) == 0: 

2334 # GH#45230 preserve RangeIndex here 

2335 # see test_reset_index_empty_rangeindex 

2336 result = lev[:0] 

2337 else: 

2338 res_values = algos.take(lev._values, new_codes[0], allow_fill=True) 

2339 # _constructor instead of type(lev) for RangeIndex compat GH#35230 

2340 result = lev._constructor._simple_new(res_values, name=new_names[0]) 

2341 else: 

2342 # set nan if needed 

2343 mask = new_codes[0] == -1 

2344 result = new_levels[0].take(new_codes[0]) 

2345 if mask.any(): 

2346 result = result.putmask(mask, np.nan) 

2347 

2348 result._name = new_names[0] 

2349 

2350 return result 

2351 else: 

2352 from pandas.core.indexes.multi import MultiIndex 

2353 

2354 return MultiIndex( 

2355 levels=new_levels, 

2356 codes=new_codes, 

2357 names=new_names, 

2358 verify_integrity=False, 

2359 ) 

2360 

2361 # -------------------------------------------------------------------- 

2362 # Introspection Methods 

2363 

2364 @cache_readonly 

2365 @final 

2366 def _can_hold_na(self) -> bool: 

2367 if isinstance(self.dtype, ExtensionDtype): 

2368 return self.dtype._can_hold_na 

2369 if self.dtype.kind in "iub": 

2370 return False 

2371 return True 

2372 

2373 @property 

2374 def is_monotonic_increasing(self) -> bool: 

2375 """ 

2376 Return a boolean if the values are equal or increasing. 

2377 

2378 Returns 

2379 ------- 

2380 bool 

2381 

2382 See Also 

2383 -------- 

2384 Index.is_monotonic_decreasing : Check if the values are equal or decreasing. 

2385 

2386 Examples 

2387 -------- 

2388 >>> pd.Index([1, 2, 3]).is_monotonic_increasing 

2389 True 

2390 >>> pd.Index([1, 2, 2]).is_monotonic_increasing 

2391 True 

2392 >>> pd.Index([1, 3, 2]).is_monotonic_increasing 

2393 False 

2394 """ 

2395 return self._engine.is_monotonic_increasing 

2396 

2397 @property 

2398 def is_monotonic_decreasing(self) -> bool: 

2399 """ 

2400 Return a boolean if the values are equal or decreasing. 

2401 

2402 Returns 

2403 ------- 

2404 bool 

2405 

2406 See Also 

2407 -------- 

2408 Index.is_monotonic_increasing : Check if the values are equal or increasing. 

2409 

2410 Examples 

2411 -------- 

2412 >>> pd.Index([3, 2, 1]).is_monotonic_decreasing 

2413 True 

2414 >>> pd.Index([3, 2, 2]).is_monotonic_decreasing 

2415 True 

2416 >>> pd.Index([3, 1, 2]).is_monotonic_decreasing 

2417 False 

2418 """ 

2419 return self._engine.is_monotonic_decreasing 

2420 

2421 @final 

2422 @property 

2423 def _is_strictly_monotonic_increasing(self) -> bool: 

2424 """ 

2425 Return if the index is strictly monotonic increasing 

2426 (only increasing) values. 

2427 

2428 Examples 

2429 -------- 

2430 >>> Index([1, 2, 3])._is_strictly_monotonic_increasing 

2431 True 

2432 >>> Index([1, 2, 2])._is_strictly_monotonic_increasing 

2433 False 

2434 >>> Index([1, 3, 2])._is_strictly_monotonic_increasing 

2435 False 

2436 """ 

2437 return self.is_unique and self.is_monotonic_increasing 

2438 

2439 @final 

2440 @property 

2441 def _is_strictly_monotonic_decreasing(self) -> bool: 

2442 """ 

2443 Return if the index is strictly monotonic decreasing 

2444 (only decreasing) values. 

2445 

2446 Examples 

2447 -------- 

2448 >>> Index([3, 2, 1])._is_strictly_monotonic_decreasing 

2449 True 

2450 >>> Index([3, 2, 2])._is_strictly_monotonic_decreasing 

2451 False 

2452 >>> Index([3, 1, 2])._is_strictly_monotonic_decreasing 

2453 False 

2454 """ 

2455 return self.is_unique and self.is_monotonic_decreasing 

2456 

2457 @cache_readonly 

2458 def is_unique(self) -> bool: 

2459 """ 

2460 Return if the index has unique values. 

2461 

2462 Returns 

2463 ------- 

2464 bool 

2465 

2466 See Also 

2467 -------- 

2468 Index.has_duplicates : Inverse method that checks if it has duplicate values. 

2469 

2470 Examples 

2471 -------- 

2472 >>> idx = pd.Index([1, 5, 7, 7]) 

2473 >>> idx.is_unique 

2474 False 

2475 

2476 >>> idx = pd.Index([1, 5, 7]) 

2477 >>> idx.is_unique 

2478 True 

2479 

2480 >>> idx = pd.Index(["Watermelon", "Orange", "Apple", "Watermelon"]).astype( 

2481 ... "category" 

2482 ... ) 

2483 >>> idx.is_unique 

2484 False 

2485 

2486 >>> idx = pd.Index(["Orange", "Apple", "Watermelon"]).astype("category") 

2487 >>> idx.is_unique 

2488 True 

2489 """ 

2490 return self._engine.is_unique 

2491 

2492 @final 

2493 @property 

2494 def has_duplicates(self) -> bool: 

2495 """ 

2496 Check if the Index has duplicate values. 

2497 

2498 Returns 

2499 ------- 

2500 bool 

2501 Whether or not the Index has duplicate values. 

2502 

2503 See Also 

2504 -------- 

2505 Index.is_unique : Inverse method that checks if it has unique values. 

2506 

2507 Examples 

2508 -------- 

2509 >>> idx = pd.Index([1, 5, 7, 7]) 

2510 >>> idx.has_duplicates 

2511 True 

2512 

2513 >>> idx = pd.Index([1, 5, 7]) 

2514 >>> idx.has_duplicates 

2515 False 

2516 

2517 >>> idx = pd.Index(["Watermelon", "Orange", "Apple", "Watermelon"]).astype( 

2518 ... "category" 

2519 ... ) 

2520 >>> idx.has_duplicates 

2521 True 

2522 

2523 >>> idx = pd.Index(["Orange", "Apple", "Watermelon"]).astype("category") 

2524 >>> idx.has_duplicates 

2525 False 

2526 """ 

2527 return not self.is_unique 

2528 

2529 @cache_readonly 

2530 def inferred_type(self) -> str_t: 

2531 """ 

2532 Return a string of the type inferred from the values. 

2533 

2534 See Also 

2535 -------- 

2536 Index.dtype : Return the dtype object of the underlying data. 

2537 

2538 Examples 

2539 -------- 

2540 >>> idx = pd.Index([1, 2, 3]) 

2541 >>> idx 

2542 Index([1, 2, 3], dtype='int64') 

2543 >>> idx.inferred_type 

2544 'integer' 

2545 """ 

2546 return lib.infer_dtype(self._values, skipna=False) 

2547 

2548 @cache_readonly 

2549 @final 

2550 def _is_all_dates(self) -> bool: 

2551 """ 

2552 Whether or not the index values only consist of dates. 

2553 """ 

2554 if needs_i8_conversion(self.dtype): 

2555 return True 

2556 elif self.dtype != _dtype_obj: 

2557 # TODO(ExtensionIndex): 3rd party EA might override? 

2558 # Note: this includes IntervalIndex, even when the left/right 

2559 # contain datetime-like objects. 

2560 return False 

2561 elif self._is_multi: 

2562 return False 

2563 return is_datetime_array(ensure_object(self._values)) 

2564 

2565 @final 

2566 @cache_readonly 

2567 def _is_multi(self) -> bool: 

2568 """ 

2569 Cached check equivalent to isinstance(self, MultiIndex) 

2570 """ 

2571 return isinstance(self, ABCMultiIndex) 

2572 

2573 # -------------------------------------------------------------------- 

2574 # Pickle Methods 

2575 

2576 def __reduce__(self): 

2577 d = {"data": self._data, "name": self.name} 

2578 return _new_Index, (type(self), d), None 

2579 

2580 # -------------------------------------------------------------------- 

2581 # Null Handling Methods 

2582 

2583 @cache_readonly 

2584 def _na_value(self): 

2585 """The expected NA value to use with this index.""" 

2586 dtype = self.dtype 

2587 if isinstance(dtype, np.dtype): 

2588 if dtype.kind in "mM": 

2589 return NaT 

2590 return np.nan 

2591 return dtype.na_value 

2592 

2593 @cache_readonly 

2594 def _isnan(self) -> npt.NDArray[np.bool_]: 

2595 """ 

2596 Return if each value is NaN. 

2597 """ 

2598 if self._can_hold_na: 

2599 return isna(self) 

2600 else: 

2601 # shouldn't reach to this condition by checking hasnans beforehand 

2602 values = np.empty(len(self), dtype=np.bool_) 

2603 values.fill(False) 

2604 return values 

2605 

2606 @cache_readonly 

2607 def hasnans(self) -> bool: 

2608 """ 

2609 Return True if there are any NaNs. 

2610 

2611 Enables various performance speedups. 

2612 

2613 Returns 

2614 ------- 

2615 bool 

2616 

2617 See Also 

2618 -------- 

2619 Index.isna : Detect missing values. 

2620 Index.dropna : Return Index without NA/NaN values. 

2621 Index.fillna : Fill NA/NaN values with the specified value. 

2622 

2623 Examples 

2624 -------- 

2625 >>> s = pd.Series([1, 2, 3], index=["a", "b", None]) 

2626 >>> s 

2627 a 1 

2628 b 2 

2629 None 3 

2630 dtype: int64 

2631 >>> s.index.hasnans 

2632 True 

2633 """ 

2634 if self._can_hold_na: 

2635 return bool(self._isnan.any()) 

2636 else: 

2637 return False 

2638 

2639 @final 

2640 def isna(self) -> npt.NDArray[np.bool_]: 

2641 """ 

2642 Detect missing values. 

2643 

2644 Return a boolean same-sized object indicating if the values are NA. 

2645 NA values, such as ``None``, :attr:`numpy.NaN` or :attr:`pd.NaT`, get 

2646 mapped to ``True`` values. 

2647 Everything else get mapped to ``False`` values. Characters such as 

2648 empty strings `''` or :attr:`numpy.inf` are not considered NA values. 

2649 

2650 Returns 

2651 ------- 

2652 numpy.ndarray[bool] 

2653 A boolean array of whether my values are NA. 

2654 

2655 See Also 

2656 -------- 

2657 Index.notna : Boolean inverse of isna. 

2658 Index.dropna : Omit entries with missing values. 

2659 isna : Top-level isna. 

2660 Series.isna : Detect missing values in Series object. 

2661 

2662 Examples 

2663 -------- 

2664 Show which entries in a pandas.Index are NA. The result is an 

2665 array. 

2666 

2667 >>> idx = pd.Index([5.2, 6.0, np.nan]) 

2668 >>> idx 

2669 Index([5.2, 6.0, nan], dtype='float64') 

2670 >>> idx.isna() 

2671 array([False, False, True]) 

2672 

2673 Empty strings are not considered NA values. None is considered an NA 

2674 value. 

2675 

2676 >>> idx = pd.Index(["black", "", "red", None]) 

2677 >>> idx 

2678 Index(['black', '', 'red', nan], dtype='str') 

2679 >>> idx.isna() 

2680 array([False, False, False, True]) 

2681 

2682 For datetimes, `NaT` (Not a Time) is considered as an NA value. 

2683 

2684 >>> idx = pd.DatetimeIndex( 

2685 ... [pd.Timestamp("1940-04-25"), pd.Timestamp(""), None, pd.NaT] 

2686 ... ) 

2687 >>> idx 

2688 DatetimeIndex(['1940-04-25', 'NaT', 'NaT', 'NaT'], 

2689 dtype='datetime64[us]', freq=None) 

2690 >>> idx.isna() 

2691 array([False, True, True, True]) 

2692 """ 

2693 return self._isnan 

2694 

2695 isnull = isna 

2696 

2697 @final 

2698 def notna(self) -> npt.NDArray[np.bool_]: 

2699 """ 

2700 Detect existing (non-missing) values. 

2701 

2702 Return a boolean same-sized object indicating if the values are not NA. 

2703 Non-missing values get mapped to ``True``. Characters such as empty 

2704 strings ``''`` or :attr:`numpy.inf` are not considered NA values. 

2705 NA values, such as None or :attr:`numpy.NaN`, get mapped to ``False`` 

2706 values. 

2707 

2708 Returns 

2709 ------- 

2710 numpy.ndarray[bool] 

2711 Boolean array to indicate which entries are not NA. 

2712 

2713 See Also 

2714 -------- 

2715 Index.notnull : Alias of notna. 

2716 Index.isna: Inverse of notna. 

2717 notna : Top-level notna. 

2718 

2719 Examples 

2720 -------- 

2721 Show which entries in an Index are not NA. The result is an 

2722 array. 

2723 

2724 >>> idx = pd.Index([5.2, 6.0, np.nan]) 

2725 >>> idx 

2726 Index([5.2, 6.0, nan], dtype='float64') 

2727 >>> idx.notna() 

2728 array([ True, True, False]) 

2729 

2730 Empty strings are not considered NA values. None is considered a NA 

2731 value. 

2732 

2733 >>> idx = pd.Index(["black", "", "red", None]) 

2734 >>> idx 

2735 Index(['black', '', 'red', nan], dtype='str') 

2736 >>> idx.notna() 

2737 array([ True, True, True, False]) 

2738 """ 

2739 return ~self.isna() 

2740 

2741 notnull = notna 

2742 

2743 def fillna(self, value): 

2744 """ 

2745 Fill NA/NaN values with the specified value. 

2746 

2747 Parameters 

2748 ---------- 

2749 value : scalar 

2750 Scalar value to use to fill holes (e.g. 0). 

2751 This value cannot be a list-likes. 

2752 

2753 Returns 

2754 ------- 

2755 Index 

2756 NA/NaN values replaced with `value`. 

2757 

2758 See Also 

2759 -------- 

2760 DataFrame.fillna : Fill NaN values of a DataFrame. 

2761 Series.fillna : Fill NaN Values of a Series. 

2762 

2763 Examples 

2764 -------- 

2765 >>> idx = pd.Index([np.nan, np.nan, 3]) 

2766 >>> idx.fillna(0) 

2767 Index([0.0, 0.0, 3.0], dtype='float64') 

2768 """ 

2769 if not is_scalar(value): 

2770 raise TypeError(f"'value' must be a scalar, passed: {type(value).__name__}") 

2771 

2772 if self.hasnans: 

2773 result = self.putmask(self._isnan, value) 

2774 # no need to care metadata other than name 

2775 # because it can't have freq if it has NaTs 

2776 # _with_infer needed for test_fillna_categorical 

2777 return Index._with_infer(result, name=self.name, copy=False) 

2778 return self._view() 

2779 

2780 def dropna(self, how: AnyAll = "any") -> Self: 

2781 """ 

2782 Return Index without NA/NaN values. 

2783 

2784 Parameters 

2785 ---------- 

2786 how : {'any', 'all'}, default 'any' 

2787 If the Index is a MultiIndex, drop the value when any or all levels 

2788 are NaN. 

2789 

2790 Returns 

2791 ------- 

2792 Index 

2793 Returns an Index object after removing NA/NaN values. 

2794 

2795 See Also 

2796 -------- 

2797 Index.fillna : Fill NA/NaN values with the specified value. 

2798 Index.isna : Detect missing values. 

2799 

2800 Examples 

2801 -------- 

2802 >>> idx = pd.Index([1, np.nan, 3]) 

2803 >>> idx.dropna() 

2804 Index([1.0, 3.0], dtype='float64') 

2805 """ 

2806 if how not in ("any", "all"): 

2807 raise ValueError(f"invalid how option: {how}") 

2808 

2809 if self.hasnans: 

2810 res_values = self._values[~self._isnan] 

2811 return type(self)._simple_new(res_values, name=self.name) 

2812 return self._view() 

2813 

2814 # -------------------------------------------------------------------- 

2815 # Uniqueness Methods 

2816 

2817 def unique(self, level: Hashable | None = None) -> Self: 

2818 """ 

2819 Return unique values in the index. 

2820 

2821 Unique values are returned in order of appearance, this does NOT sort. 

2822 

2823 Parameters 

2824 ---------- 

2825 level : int or hashable, optional 

2826 Only return values from specified level (for MultiIndex). 

2827 If int, gets the level by integer position, else by level name. 

2828 

2829 Returns 

2830 ------- 

2831 Index 

2832 Unique values in the index. 

2833 

2834 See Also 

2835 -------- 

2836 unique : Numpy array of unique values in that column. 

2837 Series.unique : Return unique values of Series object. 

2838 

2839 Examples 

2840 -------- 

2841 >>> idx = pd.Index([1, 1, 2, 3, 3]) 

2842 >>> idx.unique() 

2843 Index([1, 2, 3], dtype='int64') 

2844 """ 

2845 if level is not None: 

2846 self._validate_index_level(level) 

2847 

2848 if self.is_unique: 

2849 return self._view() 

2850 

2851 result = super().unique() 

2852 return self._shallow_copy(result) 

2853 

2854 def drop_duplicates(self, *, keep: DropKeep = "first") -> Self: 

2855 """ 

2856 Return Index with duplicate values removed. 

2857 

2858 Parameters 

2859 ---------- 

2860 keep : {'first', 'last', ``False``}, default 'first' 

2861 - 'first' : Drop duplicates except for the first occurrence. 

2862 - 'last' : Drop duplicates except for the last occurrence. 

2863 - ``False`` : Drop all duplicates. 

2864 

2865 Returns 

2866 ------- 

2867 Index 

2868 A new Index object with the duplicate values removed. 

2869 

2870 See Also 

2871 -------- 

2872 Series.drop_duplicates : Equivalent method on Series. 

2873 DataFrame.drop_duplicates : Equivalent method on DataFrame. 

2874 Index.duplicated : Related method on Index, indicating duplicate 

2875 Index values. 

2876 

2877 Examples 

2878 -------- 

2879 Generate a pandas.Index with duplicate values. 

2880 

2881 >>> idx = pd.Index(["llama", "cow", "llama", "beetle", "llama", "hippo"]) 

2882 

2883 The `keep` parameter controls which duplicate values are removed. 

2884 The value 'first' keeps the first occurrence for each 

2885 set of duplicated entries. The default value of keep is 'first'. 

2886 

2887 >>> idx.drop_duplicates(keep="first") 

2888 Index(['llama', 'cow', 'beetle', 'hippo'], dtype='str') 

2889 

2890 The value 'last' keeps the last occurrence for each set of duplicated 

2891 entries. 

2892 

2893 >>> idx.drop_duplicates(keep="last") 

2894 Index(['cow', 'beetle', 'llama', 'hippo'], dtype='str') 

2895 

2896 The value ``False`` discards all sets of duplicated entries. 

2897 

2898 >>> idx.drop_duplicates(keep=False) 

2899 Index(['cow', 'beetle', 'hippo'], dtype='str') 

2900 """ 

2901 if self.is_unique: 

2902 return self._view() 

2903 

2904 return super().drop_duplicates(keep=keep) 

2905 

2906 def duplicated(self, keep: DropKeep = "first") -> npt.NDArray[np.bool_]: 

2907 """ 

2908 Indicate duplicate index values. 

2909 

2910 Duplicated values are indicated as ``True`` values in the resulting 

2911 array. Either all duplicates, all except the first, or all except the 

2912 last occurrence of duplicates can be indicated. 

2913 

2914 Parameters 

2915 ---------- 

2916 keep : {'first', 'last', False}, default 'first' 

2917 The value or values in a set of duplicates to mark as missing. 

2918 

2919 - 'first' : Mark duplicates as ``True`` except for the first 

2920 occurrence. 

2921 - 'last' : Mark duplicates as ``True`` except for the last 

2922 occurrence. 

2923 - ``False`` : Mark all duplicates as ``True``. 

2924 

2925 Returns 

2926 ------- 

2927 np.ndarray[bool] 

2928 A numpy array of boolean values indicating duplicate index values. 

2929 

2930 See Also 

2931 -------- 

2932 Series.duplicated : Equivalent method on pandas.Series. 

2933 DataFrame.duplicated : Equivalent method on pandas.DataFrame. 

2934 Index.drop_duplicates : Remove duplicate values from Index. 

2935 

2936 Examples 

2937 -------- 

2938 By default, for each set of duplicated values, the first occurrence is 

2939 set to False and all others to True: 

2940 

2941 >>> idx = pd.Index(["llama", "cow", "llama", "beetle", "llama"]) 

2942 >>> idx.duplicated() 

2943 array([False, False, True, False, True]) 

2944 

2945 which is equivalent to 

2946 

2947 >>> idx.duplicated(keep="first") 

2948 array([False, False, True, False, True]) 

2949 

2950 By using 'last', the last occurrence of each set of duplicated values 

2951 is set on False and all others on True: 

2952 

2953 >>> idx.duplicated(keep="last") 

2954 array([ True, False, True, False, False]) 

2955 

2956 By setting keep on ``False``, all duplicates are True: 

2957 

2958 >>> idx.duplicated(keep=False) 

2959 array([ True, False, True, False, True]) 

2960 """ 

2961 if self.is_unique: 

2962 # fastpath available bc we are immutable 

2963 return np.zeros(len(self), dtype=bool) 

2964 return self._duplicated(keep=keep) 

2965 

2966 # -------------------------------------------------------------------- 

2967 # Arithmetic & Logical Methods 

2968 

2969 def __iadd__(self, other): 

2970 # alias for __add__ 

2971 return self + other 

2972 

2973 @final 

2974 def __bool__(self) -> NoReturn: 

2975 raise ValueError( 

2976 f"The truth value of a {type(self).__name__} is ambiguous. " 

2977 "Use a.empty, a.bool(), a.item(), a.any() or a.all()." 

2978 ) 

2979 

2980 # -------------------------------------------------------------------- 

2981 # Set Operation Methods 

2982 

2983 def _get_reconciled_name_object(self, other): 

2984 """ 

2985 If the result of a set operation will be self, 

2986 return a shallow copy of self. 

2987 """ 

2988 name = get_op_result_name(self, other) 

2989 if self.name is not name: 

2990 return self.rename(name) 

2991 return self.copy(deep=False) 

2992 

2993 @final 

2994 def _validate_sort_keyword(self, sort) -> None: 

2995 if sort not in [None, False, True]: 

2996 raise ValueError( 

2997 "The 'sort' keyword only takes the values of " 

2998 f"None, True, or False; {sort} was passed." 

2999 ) 

3000 

3001 @final 

3002 def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index]: 

3003 """ 

3004 With mismatched timezones, cast both to UTC. 

3005 """ 

3006 # Caller is responsible for checking 

3007 # `self.dtype != other.dtype` 

3008 if ( 

3009 isinstance(self, ABCDatetimeIndex) 

3010 and isinstance(other, ABCDatetimeIndex) 

3011 and self.tz is not None 

3012 and other.tz is not None 

3013 ): 

3014 # GH#39328, GH#45357, GH#60080 

3015 # If both timezones are the same, no need to convert to UTC 

3016 if self.tz == other.tz: 

3017 return self, other 

3018 else: 

3019 left = self.tz_convert("UTC") 

3020 right = other.tz_convert("UTC") 

3021 return left, right 

3022 return self, other 

3023 

3024 @final 

3025 def union(self, other, sort: bool | None = None): 

3026 """ 

3027 Form the union of two Index objects. 

3028 

3029 If the Index objects are incompatible, both Index objects will be 

3030 cast to dtype('object') first. 

3031 

3032 Parameters 

3033 ---------- 

3034 other : Index or array-like 

3035 Index or an array-like object containing elements to form the union 

3036 with the original Index. 

3037 sort : bool or None, default None 

3038 Whether to sort the resulting Index. 

3039 

3040 * None : Sort the result, except when 

3041 

3042 1. `self` and `other` are equal. 

3043 2. `self` or `other` has length 0. 

3044 3. Some values in `self` or `other` cannot be compared. 

3045 A RuntimeWarning is issued in this case. 

3046 

3047 * False : do not sort the result. 

3048 * True : Sort the result (which may raise TypeError). 

3049 

3050 Returns 

3051 ------- 

3052 Index 

3053 Returns a new Index object with all unique elements from both the original 

3054 Index and the `other` Index. 

3055 

3056 See Also 

3057 -------- 

3058 Index.unique : Return unique values in the index. 

3059 Index.intersection : Form the intersection of two Index objects. 

3060 Index.difference : Return a new Index with elements of index not in `other`. 

3061 

3062 Examples 

3063 -------- 

3064 Union matching dtypes 

3065 

3066 >>> idx1 = pd.Index([1, 2, 3, 4]) 

3067 >>> idx2 = pd.Index([3, 4, 5, 6]) 

3068 >>> idx1.union(idx2) 

3069 Index([1, 2, 3, 4, 5, 6], dtype='int64') 

3070 

3071 Union mismatched dtypes 

3072 

3073 >>> idx1 = pd.Index(["a", "b", "c", "d"]) 

3074 >>> idx2 = pd.Index([1, 2, 3, 4]) 

3075 >>> idx1.union(idx2) 

3076 Index(['a', 'b', 'c', 'd', 1, 2, 3, 4], dtype='object') 

3077 

3078 MultiIndex case 

3079 

3080 >>> idx1 = pd.MultiIndex.from_arrays( 

3081 ... [[1, 1, 2, 2], ["Red", "Blue", "Red", "Blue"]] 

3082 ... ) 

3083 >>> idx1 

3084 MultiIndex([(1, 'Red'), 

3085 (1, 'Blue'), 

3086 (2, 'Red'), 

3087 (2, 'Blue')], 

3088 ) 

3089 >>> idx2 = pd.MultiIndex.from_arrays( 

3090 ... [[3, 3, 2, 2], ["Red", "Green", "Red", "Green"]] 

3091 ... ) 

3092 >>> idx2 

3093 MultiIndex([(3, 'Red'), 

3094 (3, 'Green'), 

3095 (2, 'Red'), 

3096 (2, 'Green')], 

3097 ) 

3098 >>> idx1.union(idx2) 

3099 MultiIndex([(1, 'Blue'), 

3100 (1, 'Red'), 

3101 (2, 'Blue'), 

3102 (2, 'Green'), 

3103 (2, 'Red'), 

3104 (3, 'Green'), 

3105 (3, 'Red')], 

3106 ) 

3107 >>> idx1.union(idx2, sort=False) 

3108 MultiIndex([(1, 'Red'), 

3109 (1, 'Blue'), 

3110 (2, 'Red'), 

3111 (2, 'Blue'), 

3112 (3, 'Red'), 

3113 (3, 'Green'), 

3114 (2, 'Green')], 

3115 ) 

3116 """ 

3117 self._validate_sort_keyword(sort) 

3118 self._assert_can_do_setop(other) 

3119 other, result_name = self._convert_can_do_setop(other) 

3120 

3121 if self.dtype != other.dtype: 

3122 if ( 

3123 isinstance(self, ABCMultiIndex) 

3124 and not is_object_dtype(_unpack_nested_dtype(other)) 

3125 and len(other) > 0 

3126 ): 

3127 raise NotImplementedError( 

3128 "Can only union MultiIndex with MultiIndex or Index of tuples, " 

3129 "try mi.to_flat_index().union(other) instead." 

3130 ) 

3131 self, other = self._dti_setop_align_tzs(other, "union") 

3132 

3133 dtype = self._find_common_type_compat(other) 

3134 left = self.astype(dtype, copy=False) 

3135 right = other.astype(dtype, copy=False) 

3136 return left.union(right, sort=sort) 

3137 

3138 elif not len(other) or self.equals(other): 

3139 # NB: whether this (and the `if not len(self)` check below) come before 

3140 # or after the dtype equality check above affects the returned dtype 

3141 result = self._get_reconciled_name_object(other) 

3142 if sort is True: 

3143 return result.sort_values() 

3144 return result 

3145 

3146 elif not len(self): 

3147 result = other._get_reconciled_name_object(self) 

3148 if sort is True: 

3149 return result.sort_values() 

3150 return result 

3151 

3152 result = self._union(other, sort=sort) 

3153 

3154 return self._wrap_setop_result(other, result) 

3155 

3156 def _union(self, other: Index, sort: bool | None): 

3157 """ 

3158 Specific union logic should go here. In subclasses, union behavior 

3159 should be overwritten here rather than in `self.union`. 

3160 

3161 Parameters 

3162 ---------- 

3163 other : Index or array-like 

3164 sort : False or None, default False 

3165 Whether to sort the resulting index. 

3166 

3167 * True : sort the result 

3168 * False : do not sort the result. 

3169 * None : sort the result, except when `self` and `other` are equal 

3170 or when the values cannot be compared. 

3171 

3172 Returns 

3173 ------- 

3174 Index 

3175 """ 

3176 lvals = self._values 

3177 rvals = other._values 

3178 

3179 if ( 

3180 sort in (None, True) 

3181 and (self.is_unique or other.is_unique) 

3182 and self._can_use_libjoin 

3183 and other._can_use_libjoin 

3184 ): 

3185 # Both are monotonic and at least one is unique, so can use outer join 

3186 # (actually don't need either unique, but without this restriction 

3187 # test_union_same_value_duplicated_in_both fails) 

3188 try: 

3189 return self._outer_indexer(other)[0] 

3190 except TypeError: 

3191 # incomparable objects; should only be for object dtype 

3192 value_list = list(lvals) 

3193 

3194 # worth making this faster? a very unusual case 

3195 value_set = set(lvals) 

3196 value_list.extend(x for x in rvals if x not in value_set) 

3197 # If objects are unorderable, we must have object dtype. 

3198 return np.array(value_list, dtype=object) 

3199 

3200 elif not other.is_unique: 

3201 # other has duplicates 

3202 result_dups = algos.union_with_duplicates(self, other) 

3203 return _maybe_try_sort(result_dups, sort) 

3204 

3205 # The rest of this method is analogous to Index._intersection_via_get_indexer 

3206 

3207 # Self may have duplicates; other already checked as unique 

3208 # find indexes of things in "other" that are not in "self" 

3209 if self._index_as_unique: 

3210 indexer = self.get_indexer(other) 

3211 missing = (indexer == -1).nonzero()[0] 

3212 else: 

3213 missing = algos.unique1d(self.get_indexer_non_unique(other)[1]) 

3214 

3215 result: Index | MultiIndex | ArrayLike 

3216 if self._is_multi: 

3217 # Preserve MultiIndex to avoid losing dtypes 

3218 result = self.append(other.take(missing)) 

3219 

3220 elif len(missing) > 0: 

3221 other_diff = rvals.take(missing) 

3222 result = concat_compat((lvals, other_diff)) 

3223 else: 

3224 result = lvals 

3225 

3226 if not self.is_monotonic_increasing or not other.is_monotonic_increasing: 

3227 # if both are monotonic then result should already be sorted 

3228 result = _maybe_try_sort(result, sort) 

3229 

3230 return result 

3231 

3232 @final 

3233 def _wrap_setop_result(self, other: Index, result) -> Index: 

3234 name = get_op_result_name(self, other) 

3235 if isinstance(result, Index): 

3236 if result.name != name: 

3237 result = result.rename(name) 

3238 else: 

3239 result = self._shallow_copy(result, name=name) 

3240 return result 

3241 

3242 @final 

3243 def intersection(self, other, sort: bool = False): 

3244 # default sort keyword is different here from other setops intentionally 

3245 # done in GH#25063 

3246 """ 

3247 Form the intersection of two Index objects. 

3248 

3249 This returns a new Index with elements common to the index and `other`. 

3250 

3251 Parameters 

3252 ---------- 

3253 other : Index or array-like 

3254 An Index or an array-like object containing elements to form the 

3255 intersection with the original Index. 

3256 sort : True, False or None, default False 

3257 Whether to sort the resulting index. 

3258 

3259 * None : sort the result, except when `self` and `other` are equal 

3260 or when the values cannot be compared. 

3261 * False : do not sort the result. 

3262 * True : Sort the result (which may raise TypeError). 

3263 

3264 Returns 

3265 ------- 

3266 Index 

3267 Returns a new Index object with elements common to both the original Index 

3268 and the `other` Index. 

3269 

3270 See Also 

3271 -------- 

3272 Index.union : Form the union of two Index objects. 

3273 Index.difference : Return a new Index with elements of index not in other. 

3274 Index.isin : Return a boolean array where the index values are in values. 

3275 

3276 Examples 

3277 -------- 

3278 >>> idx1 = pd.Index([1, 2, 3, 4]) 

3279 >>> idx2 = pd.Index([3, 4, 5, 6]) 

3280 >>> idx1.intersection(idx2) 

3281 Index([3, 4], dtype='int64') 

3282 """ 

3283 self._validate_sort_keyword(sort) 

3284 self._assert_can_do_setop(other) 

3285 other, result_name = self._convert_can_do_setop(other) 

3286 

3287 if self.dtype != other.dtype: 

3288 self, other = self._dti_setop_align_tzs(other, "intersection") 

3289 

3290 if self.equals(other): 

3291 if not self.is_unique: 

3292 result = self.unique()._get_reconciled_name_object(other) 

3293 else: 

3294 result = self._get_reconciled_name_object(other) 

3295 if sort is True: 

3296 result = result.sort_values() 

3297 return result 

3298 

3299 if len(self) == 0 or len(other) == 0: 

3300 # fastpath; we need to be careful about having commutativity 

3301 

3302 if self._is_multi or other._is_multi: 

3303 # _convert_can_do_setop ensures that we have both or neither 

3304 # We retain self.levels 

3305 return self[:0].rename(result_name) 

3306 

3307 dtype = self._find_common_type_compat(other) 

3308 if self.dtype == dtype: 

3309 # Slicing allows us to retain DTI/TDI.freq, RangeIndex 

3310 

3311 # Note: self[:0] vs other[:0] affects 

3312 # 1) which index's `freq` we get in DTI/TDI cases 

3313 # This may be a historical artifact, i.e. no documented 

3314 # reason for this choice. 

3315 # 2) The `step` we get in RangeIndex cases 

3316 if len(self) == 0: 

3317 return self[:0].rename(result_name) 

3318 else: 

3319 return other[:0].rename(result_name) 

3320 

3321 return Index([], dtype=dtype, name=result_name) 

3322 

3323 elif not self._should_compare(other): 

3324 # We can infer that the intersection is empty. 

3325 if isinstance(self, ABCMultiIndex): 

3326 return self[:0].rename(result_name) 

3327 return Index([], name=result_name) 

3328 

3329 elif self.dtype != other.dtype: 

3330 dtype = self._find_common_type_compat(other) 

3331 this = self.astype(dtype, copy=False) 

3332 other = other.astype(dtype, copy=False) 

3333 return this.intersection(other, sort=sort) 

3334 

3335 result = self._intersection(other, sort=sort) 

3336 return self._wrap_intersection_result(other, result) 

3337 

3338 def _intersection(self, other: Index, sort: bool = False): 

3339 """ 

3340 intersection specialized to the case with matching dtypes. 

3341 """ 

3342 if self._can_use_libjoin and other._can_use_libjoin: 

3343 try: 

3344 res_indexer, indexer, _ = self._inner_indexer(other) 

3345 except TypeError: 

3346 # non-comparable; should only be for object dtype 

3347 pass 

3348 else: 

3349 # TODO: algos.unique1d should preserve DTA/TDA 

3350 if is_numeric_dtype(self.dtype): 

3351 # This is faster, because Index.unique() checks for uniqueness 

3352 # before calculating the unique values. 

3353 res = algos.unique1d(res_indexer) 

3354 else: 

3355 result = self.take(indexer) 

3356 res = result.drop_duplicates() # type: ignore[assignment] 

3357 return ensure_wrapped_if_datetimelike(res) 

3358 

3359 res_values = self._intersection_via_get_indexer(other, sort=sort) 

3360 res_values = _maybe_try_sort(res_values, sort) 

3361 return res_values 

3362 

3363 def _wrap_intersection_result(self, other, result): 

3364 # We will override for MultiIndex to handle empty results 

3365 return self._wrap_setop_result(other, result) 

3366 

3367 @final 

3368 def _intersection_via_get_indexer( 

3369 self, other: Index | MultiIndex, sort 

3370 ) -> ArrayLike | MultiIndex: 

3371 """ 

3372 Find the intersection of two Indexes using get_indexer. 

3373 

3374 Returns 

3375 ------- 

3376 np.ndarray or ExtensionArray or MultiIndex 

3377 The returned array will be unique. 

3378 """ 

3379 left_unique = self.unique() 

3380 right_unique = other.unique() 

3381 

3382 # even though we are unique, we need get_indexer_for for IntervalIndex 

3383 indexer = left_unique.get_indexer_for(right_unique) 

3384 

3385 mask = indexer != -1 

3386 

3387 taker = indexer.take(mask.nonzero()[0]) 

3388 if sort is False: 

3389 # sort bc we want the elements in the same order they are in self 

3390 # unnecessary in the case with sort=None bc we will sort later 

3391 taker = np.sort(taker) 

3392 

3393 result: MultiIndex | ExtensionArray | np.ndarray 

3394 if isinstance(left_unique, ABCMultiIndex): 

3395 result = left_unique.take(taker) 

3396 else: 

3397 result = left_unique.take(taker)._values 

3398 return result 

3399 

3400 @final 

3401 def difference(self, other, sort: bool | None = None): 

3402 """ 

3403 Return a new Index with elements of index not in `other`. 

3404 

3405 This is the set difference of two Index objects. 

3406 

3407 Parameters 

3408 ---------- 

3409 other : Index or array-like 

3410 Index object or an array-like object containing elements to be compared 

3411 with the elements of the original Index. 

3412 sort : bool or None, default None 

3413 Whether to sort the resulting index. By default, the 

3414 values are attempted to be sorted, but any TypeError from 

3415 incomparable elements is caught by pandas. 

3416 

3417 * None : Attempt to sort the result, but catch any TypeErrors 

3418 from comparing incomparable elements. 

3419 * False : Do not sort the result. 

3420 * True : Sort the result (which may raise TypeError). 

3421 

3422 Returns 

3423 ------- 

3424 Index 

3425 Returns a new Index object containing elements that are in the original 

3426 Index but not in the `other` Index. 

3427 

3428 See Also 

3429 -------- 

3430 Index.symmetric_difference : Compute the symmetric difference of two Index 

3431 objects. 

3432 Index.intersection : Form the intersection of two Index objects. 

3433 

3434 Examples 

3435 -------- 

3436 >>> idx1 = pd.Index([2, 1, 3, 4]) 

3437 >>> idx2 = pd.Index([3, 4, 5, 6]) 

3438 >>> idx1.difference(idx2) 

3439 Index([1, 2], dtype='int64') 

3440 >>> idx1.difference(idx2, sort=False) 

3441 Index([2, 1], dtype='int64') 

3442 """ 

3443 self._validate_sort_keyword(sort) 

3444 self._assert_can_do_setop(other) 

3445 other, result_name = self._convert_can_do_setop(other) 

3446 

3447 # Note: we do NOT call _dti_setop_align_tzs here, as there 

3448 # is no requirement that .difference be commutative, so it does 

3449 # not cast to object. 

3450 

3451 if self.equals(other): 

3452 # Note: we do not (yet) sort even if sort=None GH#24959 

3453 return self[:0].rename(result_name) 

3454 

3455 if len(other) == 0: 

3456 # Note: we do not (yet) sort even if sort=None GH#24959 

3457 result = self.unique().rename(result_name) 

3458 if sort is True: 

3459 return result.sort_values() 

3460 return result 

3461 

3462 if not self._should_compare(other): 

3463 # Nothing matches -> difference is everything 

3464 result = self.unique().rename(result_name) 

3465 if sort is True: 

3466 return result.sort_values() 

3467 return result 

3468 

3469 result = self._difference(other, sort=sort) 

3470 return self._wrap_difference_result(other, result) 

3471 

3472 def _difference(self, other, sort): 

3473 # overridden by RangeIndex 

3474 this = self 

3475 if isinstance(self, ABCCategoricalIndex) and self.hasnans and other.hasnans: 

3476 this = this.dropna() 

3477 other = other.unique() 

3478 the_diff = this[other.get_indexer_for(this) == -1] 

3479 the_diff = the_diff if this.is_unique else the_diff.unique() 

3480 the_diff = _maybe_try_sort(the_diff, sort) 

3481 return the_diff 

3482 

3483 def _wrap_difference_result(self, other, result): 

3484 # We will override for MultiIndex to handle empty results 

3485 return self._wrap_setop_result(other, result) 

3486 

3487 def symmetric_difference( 

3488 self, 

3489 other, 

3490 result_name: abc.Hashable | None = None, 

3491 sort: bool | None = None, 

3492 ): 

3493 """ 

3494 Compute the symmetric difference of two Index objects. 

3495 

3496 Parameters 

3497 ---------- 

3498 other : Index or array-like 

3499 Index or an array-like object with elements to compute the symmetric 

3500 difference with the original Index. 

3501 result_name : str 

3502 A string representing the name of the resulting Index, if desired. 

3503 sort : bool or None, default None 

3504 Whether to sort the resulting index. By default, the 

3505 values are attempted to be sorted, but any TypeError from 

3506 incomparable elements is caught by pandas. 

3507 

3508 * None : Attempt to sort the result, but catch any TypeErrors 

3509 from comparing incomparable elements. 

3510 * False : Do not sort the result. 

3511 * True : Sort the result (which may raise TypeError). 

3512 

3513 Returns 

3514 ------- 

3515 Index 

3516 Returns a new Index object containing elements that appear in either the 

3517 original Index or the `other` Index, but not both. 

3518 

3519 See Also 

3520 -------- 

3521 Index.difference : Return a new Index with elements of index not in other. 

3522 Index.union : Form the union of two Index objects. 

3523 Index.intersection : Form the intersection of two Index objects. 

3524 

3525 Notes 

3526 ----- 

3527 ``symmetric_difference`` contains elements that appear in either 

3528 ``idx1`` or ``idx2`` but not both. Equivalent to the Index created by 

3529 ``idx1.difference(idx2) | idx2.difference(idx1)`` with duplicates 

3530 dropped. 

3531 

3532 Examples 

3533 -------- 

3534 >>> idx1 = pd.Index([1, 2, 3, 4]) 

3535 >>> idx2 = pd.Index([2, 3, 4, 5]) 

3536 >>> idx1.symmetric_difference(idx2) 

3537 Index([1, 5], dtype='int64') 

3538 """ 

3539 self._validate_sort_keyword(sort) 

3540 self._assert_can_do_setop(other) 

3541 other, result_name_update = self._convert_can_do_setop(other) 

3542 if result_name is None: 

3543 result_name = result_name_update 

3544 

3545 if self.dtype != other.dtype: 

3546 self, other = self._dti_setop_align_tzs(other, "symmetric_difference") 

3547 

3548 if not self._should_compare(other): 

3549 return self.union(other, sort=sort).rename(result_name) 

3550 

3551 elif self.dtype != other.dtype: 

3552 dtype = self._find_common_type_compat(other) 

3553 this = self.astype(dtype, copy=False) 

3554 that = other.astype(dtype, copy=False) 

3555 return this.symmetric_difference(that, sort=sort).rename(result_name) 

3556 

3557 this = self.unique() 

3558 other = other.unique() 

3559 indexer = this.get_indexer_for(other) 

3560 

3561 # {this} minus {other} 

3562 common_indexer = indexer.take((indexer != -1).nonzero()[0]) 

3563 left_indexer = np.setdiff1d( 

3564 np.arange(this.size), common_indexer, assume_unique=True 

3565 ) 

3566 left_diff = this.take(left_indexer) 

3567 

3568 # {other} minus {this} 

3569 right_indexer = (indexer == -1).nonzero()[0] 

3570 right_diff = other.take(right_indexer) 

3571 

3572 res_values = left_diff.append(right_diff) 

3573 result = _maybe_try_sort(res_values, sort) 

3574 

3575 if not self._is_multi: 

3576 return Index(result, name=result_name, dtype=res_values.dtype) 

3577 else: 

3578 left_diff = cast("MultiIndex", left_diff) 

3579 if len(result) == 0: 

3580 # result might be an Index, if other was an Index 

3581 return left_diff.remove_unused_levels().set_names(result_name) 

3582 return result.set_names(result_name) 

3583 

3584 @final 

3585 def _assert_can_do_setop(self, other) -> bool: 

3586 if not is_list_like(other): 

3587 raise TypeError("Input must be Index or array-like") 

3588 return True 

3589 

3590 def _convert_can_do_setop(self, other) -> tuple[Index, Hashable]: 

3591 if not isinstance(other, Index): 

3592 other = Index(other, name=self.name) 

3593 result_name = self.name 

3594 else: 

3595 result_name = get_op_result_name(self, other) 

3596 return other, result_name 

3597 

3598 # -------------------------------------------------------------------- 

3599 # Indexing Methods 

3600 

3601 def get_loc(self, key): 

3602 """ 

3603 Get integer location, slice or boolean mask for requested label. 

3604 

3605 Parameters 

3606 ---------- 

3607 key : label 

3608 The key to check its location if it is present in the index. 

3609 

3610 Returns 

3611 ------- 

3612 int if unique index, slice if monotonic index, else mask 

3613 Integer location, slice or boolean mask. 

3614 

3615 See Also 

3616 -------- 

3617 Index.get_slice_bound : Calculate slice bound that corresponds to 

3618 given label. 

3619 Index.get_indexer : Computes indexer and mask for new index given 

3620 the current index. 

3621 Index.get_non_unique : Returns indexer and masks for new index given 

3622 the current index. 

3623 Index.get_indexer_for : Returns an indexer even when non-unique. 

3624 

3625 Examples 

3626 -------- 

3627 >>> unique_index = pd.Index(list("abc")) 

3628 >>> unique_index.get_loc("b") 

3629 1 

3630 

3631 >>> monotonic_index = pd.Index(list("abbc")) 

3632 >>> monotonic_index.get_loc("b") 

3633 slice(1, 3, None) 

3634 

3635 >>> non_monotonic_index = pd.Index(list("abcb")) 

3636 >>> non_monotonic_index.get_loc("b") 

3637 array([False, True, False, True]) 

3638 """ 

3639 casted_key = self._maybe_cast_indexer(key) 

3640 try: 

3641 return self._engine.get_loc(casted_key) 

3642 except KeyError as err: 

3643 if isinstance(casted_key, slice) or ( 

3644 isinstance(casted_key, abc.Iterable) 

3645 and any(isinstance(x, slice) for x in casted_key) 

3646 ): 

3647 raise InvalidIndexError(key) from err 

3648 raise KeyError(key) from err 

3649 except TypeError: 

3650 # If we have a listlike key, _check_indexing_error will raise 

3651 # InvalidIndexError. Otherwise we fall through and re-raise 

3652 # the TypeError. 

3653 self._check_indexing_error(key) 

3654 raise 

3655 

3656 @final 

3657 def get_indexer( 

3658 self, 

3659 target, 

3660 method: ReindexMethod | None = None, 

3661 limit: int | None = None, 

3662 tolerance=None, 

3663 ) -> npt.NDArray[np.intp]: 

3664 """ 

3665 Compute indexer and mask for new index given the current index. 

3666 

3667 The indexer should be then used as an input to ndarray.take to align the 

3668 current data to the new index. 

3669 

3670 Parameters 

3671 ---------- 

3672 target : Index 

3673 An iterable containing the values to be used for computing indexer. 

3674 method : {None, 'pad'/'ffill', 'backfill'/'bfill', 'nearest'}, optional 

3675 * default: exact matches only. 

3676 * pad / ffill: find the PREVIOUS index value if no exact match. 

3677 * backfill / bfill: use NEXT index value if no exact match 

3678 * nearest: use the NEAREST index value if no exact match. Tied 

3679 distances are broken by preferring the larger index value. 

3680 limit : int, optional 

3681 Maximum number of consecutive labels in ``target`` to match for 

3682 inexact matches. 

3683 tolerance : optional 

3684 Maximum distance between original and new labels for inexact 

3685 matches. The values of the index at the matching locations must 

3686 satisfy the equation ``abs(index[indexer] - target) <= tolerance``. 

3687 

3688 Tolerance may be a scalar value, which applies the same tolerance 

3689 to all values, or list-like, which applies variable tolerance per 

3690 element. List-like includes list, tuple, array, Series, and must be 

3691 the same size as the index and its dtype must exactly match the 

3692 index's type. 

3693 

3694 Returns 

3695 ------- 

3696 np.ndarray[np.intp] 

3697 Integers from 0 to n - 1 indicating that the index at these 

3698 positions matches the corresponding target values. Missing values 

3699 in the target are marked by -1. 

3700 

3701 See Also 

3702 -------- 

3703 Index.get_indexer_for : Returns an indexer even when non-unique. 

3704 Index.get_non_unique : Returns indexer and masks for new index given 

3705 the current index. 

3706 

3707 Notes 

3708 ----- 

3709 Returns -1 for unmatched values, for further explanation see the 

3710 example below. 

3711 

3712 Examples 

3713 -------- 

3714 >>> index = pd.Index(["c", "a", "b"]) 

3715 >>> index.get_indexer(["a", "b", "x"]) 

3716 array([ 1, 2, -1]) 

3717 

3718 Notice that the return value is an array of locations in ``index`` 

3719 and ``x`` is marked by -1, as it is not in ``index``. 

3720 """ 

3721 method = clean_reindex_fill_method(method) 

3722 orig_target = target 

3723 target = self._maybe_cast_listlike_indexer(target) 

3724 

3725 self._check_indexing_method(method, limit, tolerance) 

3726 

3727 if not self._index_as_unique: 

3728 raise InvalidIndexError(self._requires_unique_msg) 

3729 

3730 if len(target) == 0: 

3731 return np.array([], dtype=np.intp) 

3732 

3733 if not self._should_compare(target) and not self._should_partial_index(target): 

3734 # IntervalIndex get special treatment bc numeric scalars can be 

3735 # matched to Interval scalars 

3736 return self._get_indexer_non_comparable(target, method=method, unique=True) 

3737 

3738 if isinstance(self.dtype, CategoricalDtype): 

3739 # _maybe_cast_listlike_indexer ensures target has our dtype 

3740 # (could improve perf by doing _should_compare check earlier?) 

3741 assert self.dtype == target.dtype 

3742 

3743 indexer = self._engine.get_indexer(target.codes) 

3744 if self.hasnans and target.hasnans: 

3745 # After _maybe_cast_listlike_indexer, target elements which do not 

3746 # belong to some category are changed to NaNs 

3747 # Mask to track actual NaN values compared to inserted NaN values 

3748 # GH#45361 

3749 target_nans = isna(orig_target) 

3750 loc = self.get_loc(np.nan) 

3751 mask = target.isna() 

3752 indexer[target_nans] = loc 

3753 indexer[mask & ~target_nans] = -1 

3754 return indexer 

3755 

3756 if isinstance(target.dtype, CategoricalDtype): 

3757 # potential fastpath 

3758 # get an indexer for unique categories then propagate to codes via take_nd 

3759 # get_indexer instead of _get_indexer needed for MultiIndex cases 

3760 # e.g. test_append_different_columns_types 

3761 categories_indexer = self.get_indexer(target.categories) 

3762 

3763 indexer = algos.take_nd(categories_indexer, target.codes, fill_value=-1) 

3764 

3765 if (not self._is_multi and self.hasnans) and target.hasnans: 

3766 # Exclude MultiIndex because hasnans raises NotImplementedError 

3767 # we should only get here if we are unique, so loc is an integer 

3768 # GH#41934 

3769 loc = self.get_loc(np.nan) 

3770 mask = target.isna() 

3771 indexer[mask] = loc 

3772 

3773 return ensure_platform_int(indexer) 

3774 

3775 pself, ptarget = self._maybe_downcast_for_indexing(target) 

3776 if pself is not self or ptarget is not target: 

3777 return pself.get_indexer( 

3778 ptarget, method=method, limit=limit, tolerance=tolerance 

3779 ) 

3780 

3781 if self.dtype == target.dtype and self.equals(target): 

3782 # Only call equals if we have same dtype to avoid inference/casting 

3783 return np.arange(len(target), dtype=np.intp) 

3784 

3785 if self.dtype != target.dtype and not self._should_partial_index(target): 

3786 # _should_partial_index e.g. IntervalIndex with numeric scalars 

3787 # that can be matched to Interval scalars. 

3788 dtype = self._find_common_type_compat(target) 

3789 

3790 this = self.astype(dtype, copy=False) 

3791 target = target.astype(dtype, copy=False) 

3792 return this._get_indexer( 

3793 target, method=method, limit=limit, tolerance=tolerance 

3794 ) 

3795 

3796 return self._get_indexer(target, method, limit, tolerance) 

3797 

3798 def _get_indexer( 

3799 self, 

3800 target: Index, 

3801 method: str_t | None = None, 

3802 limit: int | None = None, 

3803 tolerance=None, 

3804 ) -> npt.NDArray[np.intp]: 

3805 if tolerance is not None: 

3806 tolerance = self._convert_tolerance(tolerance, target) 

3807 

3808 if method in ["pad", "backfill"]: 

3809 indexer = self._get_fill_indexer(target, method, limit, tolerance) 

3810 elif method == "nearest": 

3811 indexer = self._get_nearest_indexer(target, limit, tolerance) 

3812 else: 

3813 if target._is_multi and self._is_multi: 

3814 engine = self._engine 

3815 # error: Item "IndexEngine" of "Union[IndexEngine, ExtensionEngine]" 

3816 # has no attribute "_extract_level_codes" 

3817 tgt_values = engine._extract_level_codes( # type: ignore[union-attr] 

3818 target 

3819 ) 

3820 else: 

3821 tgt_values = target._get_engine_target() 

3822 

3823 indexer = self._engine.get_indexer(tgt_values) 

3824 

3825 return ensure_platform_int(indexer) 

3826 

3827 @final 

3828 def _should_partial_index(self, target: Index) -> bool: 

3829 """ 

3830 Should we attempt partial-matching indexing? 

3831 """ 

3832 if isinstance(self.dtype, IntervalDtype): 

3833 if isinstance(target.dtype, IntervalDtype): 

3834 return False 

3835 # "Index" has no attribute "left" 

3836 return self.left._should_compare(target) # type: ignore[attr-defined] 

3837 return False 

3838 

3839 @final 

3840 def _check_indexing_method( 

3841 self, 

3842 method: str_t | None, 

3843 limit: int | None = None, 

3844 tolerance=None, 

3845 ) -> None: 

3846 """ 

3847 Raise if we have a get_indexer `method` that is not supported or valid. 

3848 """ 

3849 if method not in [None, "bfill", "backfill", "pad", "ffill", "nearest"]: 

3850 # in practice the clean_reindex_fill_method call would raise 

3851 # before we get here 

3852 raise ValueError("Invalid fill method") # pragma: no cover 

3853 

3854 if self._is_multi: 

3855 if method == "nearest": 

3856 raise NotImplementedError( 

3857 "method='nearest' not implemented yet " 

3858 "for MultiIndex; see GitHub issue 9365" 

3859 ) 

3860 if method in ("pad", "backfill"): 

3861 if tolerance is not None: 

3862 raise NotImplementedError( 

3863 "tolerance not implemented yet for MultiIndex" 

3864 ) 

3865 

3866 if isinstance(self.dtype, (IntervalDtype, CategoricalDtype)): 

3867 # GH#37871 for now this is only for IntervalIndex and CategoricalIndex 

3868 if method is not None: 

3869 raise NotImplementedError( 

3870 f"method {method} not yet implemented for {type(self).__name__}" 

3871 ) 

3872 

3873 if method is None: 

3874 if tolerance is not None: 

3875 raise ValueError( 

3876 "tolerance argument only valid if doing pad, " 

3877 "backfill or nearest reindexing" 

3878 ) 

3879 if limit is not None: 

3880 raise ValueError( 

3881 "limit argument only valid if doing pad, " 

3882 "backfill or nearest reindexing" 

3883 ) 

3884 

3885 def _convert_tolerance(self, tolerance, target: np.ndarray | Index) -> np.ndarray: 

3886 # override this method on subclasses 

3887 tolerance = np.asarray(tolerance) 

3888 if target.size != tolerance.size and tolerance.size > 1: 

3889 raise ValueError("list-like tolerance size must match target index size") 

3890 elif is_numeric_dtype(self) and not np.issubdtype(tolerance.dtype, np.number): 

3891 if tolerance.ndim > 0: 

3892 raise ValueError( 

3893 f"tolerance argument for {type(self).__name__} with dtype " 

3894 f"{self.dtype} must contain numeric elements if it is list type" 

3895 ) 

3896 

3897 raise ValueError( 

3898 f"tolerance argument for {type(self).__name__} with dtype {self.dtype} " 

3899 f"must be numeric if it is a scalar: {tolerance!r}" 

3900 ) 

3901 return tolerance 

3902 

3903 @final 

3904 def _get_fill_indexer( 

3905 self, target: Index, method: str_t, limit: int | None = None, tolerance=None 

3906 ) -> npt.NDArray[np.intp]: 

3907 if self._is_multi: 

3908 if not (self.is_monotonic_increasing or self.is_monotonic_decreasing): 

3909 raise ValueError("index must be monotonic increasing or decreasing") 

3910 encoded = self.append(target)._engine.values # type: ignore[union-attr] 

3911 self_encoded = Index(encoded[: len(self)], copy=False) 

3912 target_encoded = Index(encoded[len(self) :], copy=False) 

3913 return self_encoded._get_fill_indexer( 

3914 target_encoded, method, limit, tolerance 

3915 ) 

3916 

3917 if self.is_monotonic_increasing and target.is_monotonic_increasing: 

3918 target_values = target._get_engine_target() 

3919 own_values = self._get_engine_target() 

3920 if not isinstance(target_values, np.ndarray) or not isinstance( 

3921 own_values, np.ndarray 

3922 ): 

3923 raise NotImplementedError 

3924 

3925 if method == "pad": 

3926 indexer = libalgos.pad(own_values, target_values, limit=limit) 

3927 else: 

3928 # i.e. "backfill" 

3929 indexer = libalgos.backfill(own_values, target_values, limit=limit) 

3930 else: 

3931 indexer = self._get_fill_indexer_searchsorted(target, method, limit) 

3932 if tolerance is not None and len(self): 

3933 indexer = self._filter_indexer_tolerance(target, indexer, tolerance) 

3934 return indexer 

3935 

3936 @final 

3937 def _get_fill_indexer_searchsorted( 

3938 self, target: Index, method: str_t, limit: int | None = None 

3939 ) -> npt.NDArray[np.intp]: 

3940 """ 

3941 Fallback pad/backfill get_indexer that works for monotonic decreasing 

3942 indexes and non-monotonic targets. 

3943 """ 

3944 if limit is not None: 

3945 raise ValueError( 

3946 f"limit argument for {method!r} method only well-defined " 

3947 "if index and target are monotonic" 

3948 ) 

3949 

3950 side: Literal["left", "right"] = "left" if method == "pad" else "right" 

3951 

3952 # find exact matches first (this simplifies the algorithm) 

3953 indexer = self.get_indexer(target) 

3954 nonexact = indexer == -1 

3955 indexer[nonexact] = self._searchsorted_monotonic(target[nonexact], side) 

3956 if side == "left": 

3957 # searchsorted returns "indices into a sorted array such that, 

3958 # if the corresponding elements in v were inserted before the 

3959 # indices, the order of a would be preserved". 

3960 # Thus, we need to subtract 1 to find values to the left. 

3961 indexer[nonexact] -= 1 

3962 # This also mapped not found values (values of 0 from 

3963 # np.searchsorted) to -1, which conveniently is also our 

3964 # sentinel for missing values 

3965 else: 

3966 # Mark indices to the right of the largest value as not found 

3967 indexer[indexer == len(self)] = -1 

3968 return indexer 

3969 

3970 @final 

3971 def _get_nearest_indexer( 

3972 self, target: Index, limit: int | None, tolerance 

3973 ) -> npt.NDArray[np.intp]: 

3974 """ 

3975 Get the indexer for the nearest index labels; requires an index with 

3976 values that can be subtracted from each other (e.g., not strings or 

3977 tuples). 

3978 """ 

3979 if not len(self): 

3980 return self._get_fill_indexer(target, "pad") 

3981 

3982 left_indexer = self.get_indexer(target, "pad", limit=limit) 

3983 right_indexer = self.get_indexer(target, "backfill", limit=limit) 

3984 

3985 left_distances = self._difference_compat(target, left_indexer) 

3986 right_distances = self._difference_compat(target, right_indexer) 

3987 

3988 op = operator.lt if self.is_monotonic_increasing else operator.le 

3989 indexer = np.where( 

3990 # error: Argument 1&2 has incompatible type "Union[ExtensionArray, 

3991 # ndarray[Any, Any]]"; expected "Union[SupportsDunderLE, 

3992 # SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]" 

3993 op(left_distances, right_distances) # type: ignore[arg-type] 

3994 | (right_indexer == -1), 

3995 left_indexer, 

3996 right_indexer, 

3997 ) 

3998 if tolerance is not None: 

3999 indexer = self._filter_indexer_tolerance(target, indexer, tolerance) 

4000 return indexer 

4001 

4002 @final 

4003 def _filter_indexer_tolerance( 

4004 self, 

4005 target: Index, 

4006 indexer: npt.NDArray[np.intp], 

4007 tolerance, 

4008 ) -> npt.NDArray[np.intp]: 

4009 distance = self._difference_compat(target, indexer) 

4010 

4011 return np.where(distance <= tolerance, indexer, -1) 

4012 

4013 @final 

4014 def _difference_compat( 

4015 self, target: Index, indexer: npt.NDArray[np.intp] 

4016 ) -> ArrayLike: 

4017 # Compatibility for PeriodArray, for which __sub__ returns an ndarray[object] 

4018 # of DateOffset objects, which do not support __abs__ (and would be slow 

4019 # if they did) 

4020 

4021 if isinstance(self.dtype, PeriodDtype): 

4022 # Note: we only get here with matching dtypes 

4023 own_values = cast("PeriodArray", self._data)._ndarray 

4024 target_values = cast("PeriodArray", target._data)._ndarray 

4025 diff = own_values[indexer] - target_values 

4026 else: 

4027 # error: Unsupported left operand type for - ("ExtensionArray") 

4028 diff = self._values[indexer] - target._values # type: ignore[operator] 

4029 return abs(diff) 

4030 

4031 # -------------------------------------------------------------------- 

4032 # Indexer Conversion Methods 

4033 

4034 @final 

4035 def _validate_positional_slice(self, key: slice) -> None: 

4036 """ 

4037 For positional indexing, a slice must have either int or None 

4038 for each of start, stop, and step. 

4039 """ 

4040 self._validate_indexer("positional", key.start, "iloc") 

4041 self._validate_indexer("positional", key.stop, "iloc") 

4042 self._validate_indexer("positional", key.step, "iloc") 

4043 

4044 def _convert_slice_indexer(self, key: slice, kind: Literal["loc", "getitem"]): 

4045 """ 

4046 Convert a slice indexer. 

4047 

4048 By definition, these are labels unless 'iloc' is passed in. 

4049 Floats are not allowed as the start, step, or stop of the slice. 

4050 

4051 Parameters 

4052 ---------- 

4053 key : label of the slice bound 

4054 kind : {'loc', 'getitem'} 

4055 """ 

4056 

4057 # potentially cast the bounds to integers 

4058 start, stop, step = key.start, key.stop, key.step 

4059 

4060 # figure out if this is a positional indexer 

4061 is_index_slice = is_valid_positional_slice(key) 

4062 

4063 # TODO(GH#50617): once Series.__[gs]etitem__ is removed we should be able 

4064 # to simplify this. 

4065 if kind == "getitem": 

4066 # called from the getitem slicers, validate that we are in fact integers 

4067 if is_index_slice: 

4068 # In this case the _validate_indexer checks below are redundant 

4069 return key 

4070 elif self.dtype.kind in "iu": 

4071 # Note: these checks are redundant if we know is_index_slice 

4072 self._validate_indexer("slice", key.start, "getitem") 

4073 self._validate_indexer("slice", key.stop, "getitem") 

4074 self._validate_indexer("slice", key.step, "getitem") 

4075 return key 

4076 

4077 # convert the slice to an indexer here; checking that the user didn't 

4078 # pass a positional slice to loc 

4079 is_positional = is_index_slice and self._should_fallback_to_positional 

4080 

4081 # if we are mixed and have integers 

4082 if is_positional: 

4083 try: 

4084 # Validate start & stop 

4085 if start is not None: 

4086 self.get_loc(start) 

4087 if stop is not None: 

4088 self.get_loc(stop) 

4089 is_positional = False 

4090 except KeyError: 

4091 pass 

4092 

4093 if com.is_null_slice(key): 

4094 # It doesn't matter if we are positional or label based 

4095 indexer = key 

4096 elif is_positional: 

4097 if kind == "loc": 

4098 # GH#16121, GH#24612, GH#31810 

4099 raise TypeError( 

4100 "Slicing a positional slice with .loc is not allowed, " 

4101 "Use .loc with labels or .iloc with positions instead.", 

4102 ) 

4103 indexer = key 

4104 else: 

4105 indexer = self.slice_indexer(start, stop, step) 

4106 

4107 return indexer 

4108 

4109 @final 

4110 def _raise_invalid_indexer( 

4111 self, 

4112 form: Literal["slice", "positional"], 

4113 key, 

4114 reraise: lib.NoDefault | None | Exception = lib.no_default, 

4115 ) -> None: 

4116 """ 

4117 Raise consistent invalid indexer message. 

4118 """ 

4119 msg = ( 

4120 f"cannot do {form} indexing on {type(self).__name__} with these " 

4121 f"indexers [{key}] of type {type(key).__name__}" 

4122 ) 

4123 if reraise is not lib.no_default: 

4124 raise TypeError(msg) from reraise 

4125 raise TypeError(msg) 

4126 

4127 # -------------------------------------------------------------------- 

4128 # Reindex Methods 

4129 

4130 @final 

4131 def _validate_can_reindex(self, indexer: np.ndarray) -> None: 

4132 """ 

4133 Check if we are allowing reindexing with this particular indexer. 

4134 

4135 Parameters 

4136 ---------- 

4137 indexer : an integer ndarray 

4138 

4139 Raises 

4140 ------ 

4141 ValueError if its a duplicate axis 

4142 """ 

4143 # trying to reindex on an axis with duplicates 

4144 if not self._index_as_unique and len(indexer): 

4145 raise ValueError("cannot reindex on an axis with duplicate labels") 

4146 

4147 def reindex( 

4148 self, 

4149 target, 

4150 method: ReindexMethod | None = None, 

4151 level=None, 

4152 limit: int | None = None, 

4153 tolerance: float | None = None, 

4154 ) -> tuple[Index, npt.NDArray[np.intp] | None]: 

4155 """ 

4156 Create index with target's values. 

4157 

4158 Parameters 

4159 ---------- 

4160 target : an iterable 

4161 An iterable containing the values to be used for creating the new index. 

4162 method : {None, 'pad'/'ffill', 'backfill'/'bfill', 'nearest'}, optional 

4163 * default: exact matches only. 

4164 * pad / ffill: find the PREVIOUS index value if no exact match. 

4165 * backfill / bfill: use NEXT index value if no exact match 

4166 * nearest: use the NEAREST index value if no exact match. Tied 

4167 distances are broken by preferring the larger index value. 

4168 level : int, optional 

4169 Level of multiindex. 

4170 limit : int, optional 

4171 Maximum number of consecutive labels in ``target`` to match for 

4172 inexact matches. 

4173 tolerance : int, float, or list-like, optional 

4174 Maximum distance between original and new labels for inexact 

4175 matches. The values of the index at the matching locations must 

4176 satisfy the equation ``abs(index[indexer] - target) <= tolerance``. 

4177 

4178 Tolerance may be a scalar value, which applies the same tolerance 

4179 to all values, or list-like, which applies variable tolerance per 

4180 element. List-like includes list, tuple, array, Series, and must be 

4181 the same size as the index and its dtype must exactly match the 

4182 index's type. 

4183 

4184 Returns 

4185 ------- 

4186 new_index : pd.Index 

4187 Resulting index. 

4188 indexer : np.ndarray[np.intp] or None 

4189 Indices of output values in original index. 

4190 

4191 Raises 

4192 ------ 

4193 TypeError 

4194 If ``method`` passed along with ``level``. 

4195 ValueError 

4196 If non-unique multi-index 

4197 ValueError 

4198 If non-unique index and ``method`` or ``limit`` passed. 

4199 

4200 See Also 

4201 -------- 

4202 Series.reindex : Conform Series to new index with optional filling logic. 

4203 DataFrame.reindex : Conform DataFrame to new index with optional filling logic. 

4204 

4205 Examples 

4206 -------- 

4207 >>> idx = pd.Index(["car", "bike", "train", "tractor"]) 

4208 >>> idx 

4209 Index(['car', 'bike', 'train', 'tractor'], dtype='str') 

4210 >>> idx.reindex(["car", "bike"]) 

4211 (Index(['car', 'bike'], dtype='str'), array([0, 1])) 

4212 """ 

4213 # GH6552: preserve names when reindexing to non-named target 

4214 # (i.e. neither Index nor Series). 

4215 preserve_names = not hasattr(target, "name") 

4216 

4217 # GH7774: preserve dtype/tz if target is empty and not an Index. 

4218 if is_iterator(target): 

4219 target = list(target) 

4220 

4221 if not isinstance(target, Index) and len(target) == 0: 

4222 if level is not None and self._is_multi: 

4223 # "Index" has no attribute "levels"; maybe "nlevels"? 

4224 idx = self.levels[level] # type: ignore[attr-defined] 

4225 else: 

4226 idx = self 

4227 target = idx[:0] 

4228 else: 

4229 target = ensure_index(target) 

4230 

4231 if level is not None and ( 

4232 isinstance(self, ABCMultiIndex) or isinstance(target, ABCMultiIndex) 

4233 ): 

4234 if method is not None: 

4235 raise TypeError("Fill method not supported if level passed") 

4236 

4237 # TODO: tests where passing `keep_order=not self._is_multi` 

4238 # makes a difference for non-MultiIndex case 

4239 target, indexer, _ = self._join_level( 

4240 target, level, how="right", keep_order=not self._is_multi 

4241 ) 

4242 

4243 elif self.equals(target): 

4244 indexer = None 

4245 elif self._index_as_unique: 

4246 indexer = self.get_indexer( 

4247 target, method=method, limit=limit, tolerance=tolerance 

4248 ) 

4249 elif self._is_multi: 

4250 raise ValueError("cannot handle a non-unique multi-index!") 

4251 elif not self.is_unique: 

4252 # GH#42568 

4253 raise ValueError("cannot reindex on an axis with duplicate labels") 

4254 else: 

4255 indexer, _ = self.get_indexer_non_unique(target) 

4256 

4257 target = self._wrap_reindex_result(target, indexer, preserve_names) 

4258 return target, indexer 

4259 

4260 def _wrap_reindex_result(self, target, indexer, preserve_names: bool): 

4261 target = self._maybe_preserve_names(target, preserve_names) 

4262 return target 

4263 

4264 def _maybe_preserve_names(self, target: IndexT, preserve_names: bool) -> IndexT: 

4265 if preserve_names and target.nlevels == 1 and target.name != self.name: 

4266 target = target.copy(deep=False) 

4267 target.name = self.name 

4268 return target 

4269 

4270 @final 

4271 def _reindex_non_unique( 

4272 self, target: Index 

4273 ) -> tuple[Index, npt.NDArray[np.intp], npt.NDArray[np.intp] | None]: 

4274 """ 

4275 Create a new index with target's values (move/add/delete values as 

4276 necessary) use with non-unique Index and a possibly non-unique target. 

4277 

4278 Parameters 

4279 ---------- 

4280 target : an iterable 

4281 

4282 Returns 

4283 ------- 

4284 new_index : pd.Index 

4285 Resulting index. 

4286 indexer : np.ndarray[np.intp] 

4287 Indices of output values in original index. 

4288 new_indexer : np.ndarray[np.intp] or None 

4289 

4290 """ 

4291 target = ensure_index(target) 

4292 if len(target) == 0: 

4293 # GH#13691 

4294 return self[:0], np.array([], dtype=np.intp), None 

4295 

4296 indexer, missing = self.get_indexer_non_unique(target) 

4297 check = indexer != -1 

4298 new_labels: Index | np.ndarray = self.take(indexer[check]) 

4299 new_indexer = None 

4300 

4301 if len(missing): 

4302 length = np.arange(len(indexer), dtype=np.intp) 

4303 

4304 missing = ensure_platform_int(missing) 

4305 missing_labels = target.take(missing) 

4306 missing_indexer = length[~check] 

4307 cur_labels = self.take(indexer[check]).values 

4308 cur_indexer = length[check] 

4309 

4310 # Index constructor below will do inference 

4311 new_labels = np.empty((len(indexer),), dtype=object) 

4312 new_labels[cur_indexer] = cur_labels 

4313 new_labels[missing_indexer] = missing_labels 

4314 

4315 # GH#38906 

4316 if not len(self): 

4317 new_indexer = np.arange(0, dtype=np.intp) 

4318 

4319 # a unique indexer 

4320 elif target.is_unique: 

4321 # see GH5553, make sure we use the right indexer 

4322 new_indexer = np.arange(len(indexer), dtype=np.intp) 

4323 new_indexer[cur_indexer] = np.arange(len(cur_labels)) 

4324 new_indexer[missing_indexer] = -1 

4325 

4326 # we have a non_unique selector, need to use the original 

4327 # indexer here 

4328 else: 

4329 # need to retake to have the same size as the indexer 

4330 indexer[~check] = -1 

4331 

4332 # reset the new indexer to account for the new size 

4333 new_indexer = np.arange(len(self.take(indexer)), dtype=np.intp) 

4334 new_indexer[~check] = -1 

4335 

4336 if not isinstance(self, ABCMultiIndex): 

4337 new_index = Index(new_labels, name=self.name, copy=False) 

4338 else: 

4339 new_index = type(self).from_tuples(new_labels, names=self.names) 

4340 return new_index, indexer, new_indexer 

4341 

4342 # -------------------------------------------------------------------- 

4343 # Join Methods 

4344 

4345 @overload 

4346 def join( 

4347 self, 

4348 other: Index, 

4349 *, 

4350 how: JoinHow = ..., 

4351 level: Level = ..., 

4352 return_indexers: Literal[True], 

4353 sort: bool = ..., 

4354 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: ... 

4355 

4356 @overload 

4357 def join( 

4358 self, 

4359 other: Index, 

4360 *, 

4361 how: JoinHow = ..., 

4362 level: Level = ..., 

4363 return_indexers: Literal[False] = ..., 

4364 sort: bool = ..., 

4365 ) -> Index: ... 

4366 

4367 @overload 

4368 def join( 

4369 self, 

4370 other: Index, 

4371 *, 

4372 how: JoinHow = ..., 

4373 level: Level = ..., 

4374 return_indexers: bool = ..., 

4375 sort: bool = ..., 

4376 ) -> ( 

4377 Index | tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None] 

4378 ): ... 

4379 

4380 @final 

4381 @_maybe_return_indexers 

4382 def join( 

4383 self, 

4384 other: Index, 

4385 *, 

4386 how: JoinHow = "left", 

4387 level: Level | None = None, 

4388 return_indexers: bool = False, 

4389 sort: bool = False, 

4390 ) -> Index | tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4391 """ 

4392 Compute join_index and indexers to conform data structures to the new index. 

4393 

4394 Parameters 

4395 ---------- 

4396 other : Index 

4397 The other index on which join is performed. 

4398 how : {'left', 'right', 'inner', 'outer'} 

4399 level : int or level name, default None 

4400 It is either the integer position or the name of the level. 

4401 return_indexers : bool, default False 

4402 Whether to return the indexers or not for both the index objects. 

4403 sort : bool, default False 

4404 Sort the join keys lexicographically in the result Index. If False, 

4405 the order of the join keys depends on the join type (how keyword). 

4406 

4407 Returns 

4408 ------- 

4409 join_index, (left_indexer, right_indexer) 

4410 The new index. 

4411 

4412 See Also 

4413 -------- 

4414 DataFrame.join : Join columns with `other` DataFrame either on index 

4415 or on a key. 

4416 DataFrame.merge : Merge DataFrame or named Series objects with a 

4417 database-style join. 

4418 

4419 Examples 

4420 -------- 

4421 >>> idx1 = pd.Index([1, 2, 3]) 

4422 >>> idx2 = pd.Index([4, 5, 6]) 

4423 >>> idx1.join(idx2, how="outer") 

4424 Index([1, 2, 3, 4, 5, 6], dtype='int64') 

4425 >>> idx1.join(other=idx2, how="outer", return_indexers=True) 

4426 (Index([1, 2, 3, 4, 5, 6], dtype='int64'), 

4427 array([ 0, 1, 2, -1, -1, -1]), array([-1, -1, -1, 0, 1, 2])) 

4428 """ 

4429 if not isinstance(other, Index): 

4430 warnings.warn( 

4431 f"Passing {type(other).__name__} to {type(self).__name__}.join " 

4432 "is deprecated and will raise in a future version. " 

4433 "Pass an Index instead.", 

4434 Pandas4Warning, 

4435 stacklevel=find_stack_level(), 

4436 ) 

4437 

4438 other = ensure_index(other) 

4439 sort = sort or how == "outer" 

4440 

4441 if isinstance(self, ABCDatetimeIndex) and isinstance(other, ABCDatetimeIndex): 

4442 if (self.tz is None) ^ (other.tz is None): 

4443 # Raise instead of casting to object below. 

4444 raise TypeError("Cannot join tz-naive with tz-aware DatetimeIndex") 

4445 

4446 if not self._is_multi and not other._is_multi: 

4447 # We have specific handling for MultiIndex below 

4448 pself, pother = self._maybe_downcast_for_indexing(other) 

4449 if pself is not self or pother is not other: 

4450 return pself.join( 

4451 pother, how=how, level=level, return_indexers=True, sort=sort 

4452 ) 

4453 

4454 # try to figure out the join level 

4455 # GH3662 

4456 if level is None and (self._is_multi or other._is_multi): 

4457 # have the same levels/names so a simple join 

4458 if self.names == other.names: 

4459 pass 

4460 else: 

4461 return self._join_multi(other, how=how) 

4462 

4463 # join on the level 

4464 if level is not None and (self._is_multi or other._is_multi): 

4465 return self._join_level(other, level, how=how) 

4466 

4467 if len(self) == 0 or len(other) == 0: 

4468 try: 

4469 return self._join_empty(other, how, sort) 

4470 except TypeError: 

4471 # object dtype; non-comparable objects 

4472 pass 

4473 

4474 if self.dtype != other.dtype: 

4475 dtype = self._find_common_type_compat(other) 

4476 this = self.astype(dtype, copy=False) 

4477 other = other.astype(dtype, copy=False) 

4478 return this.join(other, how=how, return_indexers=True) 

4479 elif ( 

4480 isinstance(self, ABCCategoricalIndex) 

4481 and isinstance(other, ABCCategoricalIndex) 

4482 and not self.ordered 

4483 and not self.categories.equals(other.categories) 

4484 ): 

4485 # dtypes are "equal" but categories are in different order 

4486 other = Index(other._values.reorder_categories(self.categories), copy=False) 

4487 

4488 _validate_join_method(how) 

4489 

4490 if ( 

4491 self.is_monotonic_increasing 

4492 and other.is_monotonic_increasing 

4493 and self._can_use_libjoin 

4494 and other._can_use_libjoin 

4495 and (self.is_unique or other.is_unique) 

4496 ): 

4497 try: 

4498 return self._join_monotonic(other, how=how) 

4499 except TypeError: 

4500 # object dtype; non-comparable objects 

4501 pass 

4502 elif not self.is_unique or not other.is_unique: 

4503 return self._join_non_unique(other, how=how, sort=sort) 

4504 

4505 return self._join_via_get_indexer(other, how, sort) 

4506 

4507 def _join_empty( 

4508 self, other: Index, how: JoinHow, sort: bool 

4509 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4510 assert len(self) == 0 or len(other) == 0 

4511 _validate_join_method(how) 

4512 

4513 lidx: np.ndarray | None 

4514 ridx: np.ndarray | None 

4515 

4516 if len(other): 

4517 how = cast(JoinHow, {"left": "right", "right": "left"}.get(how, how)) 

4518 join_index, ridx, lidx = other._join_empty(self, how, sort) 

4519 elif how in ["left", "outer"]: 

4520 if sort and not self.is_monotonic_increasing: 

4521 lidx = self.argsort() 

4522 join_index = self.take(lidx) 

4523 else: 

4524 lidx = None 

4525 join_index = self._view() 

4526 ridx = np.broadcast_to(np.intp(-1), len(join_index)) 

4527 else: 

4528 join_index = other._view() 

4529 lidx = np.array([], dtype=np.intp) 

4530 ridx = None 

4531 return join_index, lidx, ridx 

4532 

4533 @final 

4534 def _join_via_get_indexer( 

4535 self, other: Index, how: JoinHow, sort: bool 

4536 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4537 # Fallback if we do not have any fastpaths available based on 

4538 # uniqueness/monotonicity 

4539 

4540 # Note: at this point we have checked matching dtypes 

4541 lindexer: npt.NDArray[np.intp] | None 

4542 rindexer: npt.NDArray[np.intp] | None 

4543 

4544 if how == "left": 

4545 if sort: 

4546 join_index, lindexer = self.sort_values(return_indexer=True) 

4547 rindexer = other.get_indexer_for(join_index) 

4548 return join_index, lindexer, rindexer 

4549 else: 

4550 join_index = self 

4551 elif how == "right": 

4552 if sort: 

4553 join_index, rindexer = other.sort_values(return_indexer=True) 

4554 lindexer = self.get_indexer_for(join_index) 

4555 return join_index, lindexer, rindexer 

4556 else: 

4557 join_index = other 

4558 elif how == "inner": 

4559 join_index = self.intersection(other, sort=sort) 

4560 elif how == "outer": 

4561 try: 

4562 join_index = self.union(other, sort=sort) 

4563 except TypeError: 

4564 join_index = self.union(other) 

4565 try: 

4566 join_index = _maybe_try_sort(join_index, sort) 

4567 except TypeError: 

4568 pass 

4569 

4570 names = other.names if how == "right" else self.names 

4571 if join_index.names != names: 

4572 join_index = join_index.set_names(names) 

4573 

4574 if join_index is self: 

4575 lindexer = None 

4576 else: 

4577 lindexer = self.get_indexer_for(join_index) 

4578 if join_index is other: 

4579 rindexer = None 

4580 else: 

4581 rindexer = other.get_indexer_for(join_index) 

4582 return join_index, lindexer, rindexer 

4583 

4584 @final 

4585 def _join_multi(self, other: Index, how: JoinHow): 

4586 from pandas.core.indexes.multi import MultiIndex 

4587 from pandas.core.reshape.merge import restore_dropped_levels_multijoin 

4588 

4589 # figure out join names 

4590 self_names_list = list(self.names) 

4591 other_names_list = list(other.names) 

4592 self_names_order = self_names_list.index 

4593 other_names_order = other_names_list.index 

4594 self_names = set(self_names_list) 

4595 other_names = set(other_names_list) 

4596 overlap = self_names & other_names 

4597 

4598 # need at least 1 in common 

4599 if not overlap: 

4600 raise ValueError("cannot join with no overlapping index names") 

4601 

4602 if isinstance(self, MultiIndex) and isinstance(other, MultiIndex): 

4603 # Drop the non-matching levels from left and right respectively 

4604 ldrop_names = sorted(self_names - overlap, key=self_names_order) 

4605 rdrop_names = sorted(other_names - overlap, key=other_names_order) 

4606 

4607 # if only the order differs 

4608 if not len(ldrop_names + rdrop_names): 

4609 self_jnlevels = self 

4610 other_jnlevels = other.reorder_levels(self.names) 

4611 else: 

4612 self_jnlevels = self.droplevel(ldrop_names) 

4613 other_jnlevels = other.droplevel(rdrop_names) 

4614 

4615 # Join left and right 

4616 # Join on same leveled multi-index frames is supported 

4617 join_idx, lidx, ridx = self_jnlevels.join( 

4618 other_jnlevels, how=how, return_indexers=True 

4619 ) 

4620 

4621 # Restore the dropped levels 

4622 # Returned index level order is 

4623 # common levels, ldrop_names, rdrop_names 

4624 dropped_names = ldrop_names + rdrop_names 

4625 

4626 # error: Argument 5/6 to "restore_dropped_levels_multijoin" has 

4627 # incompatible type "Optional[ndarray[Any, dtype[signedinteger[Any 

4628 # ]]]]"; expected "ndarray[Any, dtype[signedinteger[Any]]]" 

4629 levels, codes, names = restore_dropped_levels_multijoin( 

4630 self, 

4631 other, 

4632 dropped_names, 

4633 join_idx, 

4634 lidx, # type: ignore[arg-type] 

4635 ridx, # type: ignore[arg-type] 

4636 ) 

4637 

4638 # Re-create the multi-index 

4639 multi_join_idx = MultiIndex( 

4640 levels=levels, codes=codes, names=names, verify_integrity=False 

4641 ) 

4642 

4643 multi_join_idx = multi_join_idx.remove_unused_levels() 

4644 

4645 # maintain the order of the index levels 

4646 if how == "right": 

4647 level_order = other_names_list + ldrop_names 

4648 else: 

4649 level_order = self_names_list + rdrop_names 

4650 multi_join_idx = multi_join_idx.reorder_levels(level_order) 

4651 

4652 return multi_join_idx, lidx, ridx 

4653 

4654 jl = next(iter(overlap)) 

4655 

4656 # Case where only one index is multi 

4657 # make the indices into mi's that match 

4658 flip_order = False 

4659 if isinstance(self, MultiIndex): 

4660 self, other = other, self 

4661 flip_order = True 

4662 # flip if join method is right or left 

4663 flip: dict[JoinHow, JoinHow] = {"right": "left", "left": "right"} 

4664 how = flip.get(how, how) 

4665 

4666 level = other.names.index(jl) 

4667 result = self._join_level(other, level, how=how) 

4668 

4669 if flip_order: 

4670 return result[0], result[2], result[1] 

4671 return result 

4672 

4673 @final 

4674 def _join_non_unique( 

4675 self, other: Index, how: JoinHow = "left", sort: bool = False 

4676 ) -> tuple[Index, npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

4677 from pandas.core.reshape.merge import get_join_indexers_non_unique 

4678 

4679 # We only get here if dtypes match 

4680 assert self.dtype == other.dtype 

4681 

4682 left_idx, right_idx = get_join_indexers_non_unique( 

4683 self._values, other._values, how=how, sort=sort 

4684 ) 

4685 

4686 if how == "right": 

4687 join_index = other.take(right_idx) 

4688 else: 

4689 join_index = self.take(left_idx) 

4690 

4691 if how == "outer": 

4692 mask = left_idx == -1 

4693 if mask.any(): 

4694 right = other.take(right_idx) 

4695 join_index = join_index.putmask(mask, right) 

4696 

4697 if isinstance(join_index, ABCMultiIndex) and how == "outer": 

4698 # test_join_index_levels 

4699 join_index = join_index._sort_levels_monotonic() 

4700 return join_index, left_idx, right_idx 

4701 

4702 @final 

4703 def _join_level( 

4704 self, other: Index, level, how: JoinHow = "left", keep_order: bool = True 

4705 ) -> tuple[MultiIndex, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4706 """ 

4707 The join method *only* affects the level of the resulting 

4708 MultiIndex. Otherwise it just exactly aligns the Index data to the 

4709 labels of the level in the MultiIndex. 

4710 

4711 If ```keep_order == True```, the order of the data indexed by the 

4712 MultiIndex will not be changed; otherwise, it will tie out 

4713 with `other`. 

4714 """ 

4715 from pandas.core.indexes.multi import MultiIndex 

4716 

4717 def _get_leaf_sorter(labels: list[np.ndarray]) -> npt.NDArray[np.intp]: 

4718 """ 

4719 Returns sorter for the inner most level while preserving the 

4720 order of higher levels. 

4721 

4722 Parameters 

4723 ---------- 

4724 labels : list[np.ndarray] 

4725 Each ndarray has signed integer dtype, not necessarily identical. 

4726 

4727 Returns 

4728 ------- 

4729 np.ndarray[np.intp] 

4730 """ 

4731 if labels[0].size == 0: 

4732 return np.empty(0, dtype=np.intp) 

4733 

4734 if len(labels) == 1: 

4735 return get_group_index_sorter(ensure_platform_int(labels[0])) 

4736 

4737 # find indexers of beginning of each set of 

4738 # same-key labels w.r.t all but last level 

4739 tic = labels[0][:-1] != labels[0][1:] 

4740 for lab in labels[1:-1]: 

4741 tic |= lab[:-1] != lab[1:] 

4742 

4743 starts = np.hstack(([True], tic, [True])).nonzero()[0] 

4744 lab = ensure_int64(labels[-1]) 

4745 return lib.get_level_sorter(lab, ensure_platform_int(starts)) 

4746 

4747 if isinstance(self, MultiIndex) and isinstance(other, MultiIndex): 

4748 raise TypeError("Join on level between two MultiIndex objects is ambiguous") 

4749 

4750 left, right = self, other 

4751 

4752 flip_order = not isinstance(self, MultiIndex) 

4753 if flip_order: 

4754 left, right = right, left 

4755 flip: dict[JoinHow, JoinHow] = {"right": "left", "left": "right"} 

4756 how = flip.get(how, how) 

4757 

4758 assert isinstance(left, MultiIndex) 

4759 

4760 level = left._get_level_number(level) 

4761 old_level = left.levels[level] 

4762 

4763 if not right.is_unique: 

4764 raise NotImplementedError( 

4765 "Index._join_level on non-unique index is not implemented" 

4766 ) 

4767 

4768 new_level, left_lev_indexer, right_lev_indexer = old_level.join( 

4769 right, how=how, return_indexers=True 

4770 ) 

4771 

4772 if left_lev_indexer is None: 

4773 if keep_order or len(left) == 0: 

4774 left_indexer = None 

4775 join_index = left 

4776 else: # sort the leaves 

4777 left_indexer = _get_leaf_sorter(left.codes[: level + 1]) 

4778 join_index = left[left_indexer] 

4779 

4780 else: 

4781 left_lev_indexer = ensure_platform_int(left_lev_indexer) 

4782 rev_indexer = lib.get_reverse_indexer(left_lev_indexer, len(old_level)) 

4783 old_codes = left.codes[level] 

4784 

4785 taker = old_codes[old_codes != -1] 

4786 new_lev_codes = rev_indexer.take(taker) 

4787 

4788 new_codes = list(left.codes) 

4789 new_codes[level] = new_lev_codes 

4790 

4791 new_levels = list(left.levels) 

4792 new_levels[level] = new_level 

4793 

4794 if keep_order: # just drop missing values. o.w. keep order 

4795 left_indexer = np.arange(len(left), dtype=np.intp) 

4796 left_indexer = cast(np.ndarray, left_indexer) 

4797 mask = new_lev_codes != -1 

4798 if not mask.all(): 

4799 new_codes = [lab[mask] for lab in new_codes] 

4800 left_indexer = left_indexer[mask] 

4801 

4802 elif level == 0: # outer most level, take the fast route 

4803 max_new_lev = 0 if len(new_lev_codes) == 0 else new_lev_codes.max() 

4804 ngroups = 1 + max_new_lev 

4805 left_indexer, counts = libalgos.groupsort_indexer( 

4806 new_lev_codes, ngroups 

4807 ) 

4808 

4809 # missing values are placed first; drop them! 

4810 left_indexer = left_indexer[counts[0] :] 

4811 new_codes = [lab[left_indexer] for lab in new_codes] 

4812 

4813 else: # sort the leaves 

4814 mask = new_lev_codes != -1 

4815 mask_all = mask.all() 

4816 if not mask_all: 

4817 new_codes = [lab[mask] for lab in new_codes] 

4818 

4819 left_indexer = _get_leaf_sorter(new_codes[: level + 1]) 

4820 new_codes = [lab[left_indexer] for lab in new_codes] 

4821 

4822 # left_indexers are w.r.t masked frame. 

4823 # reverse to original frame! 

4824 if not mask_all: 

4825 left_indexer = mask.nonzero()[0][left_indexer] 

4826 

4827 join_index = MultiIndex( 

4828 levels=new_levels, 

4829 codes=new_codes, 

4830 names=left.names, 

4831 verify_integrity=False, 

4832 ) 

4833 

4834 if right_lev_indexer is not None: 

4835 right_indexer = right_lev_indexer.take(join_index.codes[level]) 

4836 else: 

4837 right_indexer = join_index.codes[level] 

4838 

4839 if flip_order: 

4840 left_indexer, right_indexer = right_indexer, left_indexer 

4841 

4842 left_indexer = ( 

4843 None if left_indexer is None else ensure_platform_int(left_indexer) 

4844 ) 

4845 right_indexer = ( 

4846 None if right_indexer is None else ensure_platform_int(right_indexer) 

4847 ) 

4848 return join_index, left_indexer, right_indexer 

4849 

4850 def _join_monotonic( 

4851 self, other: Index, how: JoinHow = "left" 

4852 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4853 # We only get here with (caller is responsible for ensuring): 

4854 # 1) matching dtypes 

4855 # 2) both monotonic increasing 

4856 # 3) other.is_unique or self.is_unique 

4857 assert other.dtype == self.dtype 

4858 assert self._can_use_libjoin and other._can_use_libjoin 

4859 

4860 if self.equals(other): 

4861 # This is a convenient place for this check, but its correctness 

4862 # does not depend on monotonicity, so it could go earlier 

4863 # in the calling method. 

4864 ret_index = other if how == "right" else self 

4865 return ret_index, None, None 

4866 

4867 ridx: npt.NDArray[np.intp] | None 

4868 lidx: npt.NDArray[np.intp] | None 

4869 

4870 if how == "left": 

4871 if other.is_unique: 

4872 # We can perform much better than the general case 

4873 join_index = self 

4874 lidx = None 

4875 ridx = self._left_indexer_unique(other) 

4876 else: 

4877 join_array, lidx, ridx = self._left_indexer(other) 

4878 join_index, lidx, ridx = self._wrap_join_result( 

4879 join_array, other, lidx, ridx, how 

4880 ) 

4881 elif how == "right": 

4882 if self.is_unique: 

4883 # We can perform much better than the general case 

4884 join_index = other 

4885 lidx = other._left_indexer_unique(self) 

4886 ridx = None 

4887 else: 

4888 join_array, ridx, lidx = other._left_indexer(self) 

4889 join_index, lidx, ridx = self._wrap_join_result( 

4890 join_array, other, lidx, ridx, how 

4891 ) 

4892 elif how == "inner": 

4893 join_array, lidx, ridx = self._inner_indexer(other) 

4894 join_index, lidx, ridx = self._wrap_join_result( 

4895 join_array, other, lidx, ridx, how 

4896 ) 

4897 elif how == "outer": 

4898 join_array, lidx, ridx = self._outer_indexer(other) 

4899 join_index, lidx, ridx = self._wrap_join_result( 

4900 join_array, other, lidx, ridx, how 

4901 ) 

4902 

4903 lidx = None if lidx is None else ensure_platform_int(lidx) 

4904 ridx = None if ridx is None else ensure_platform_int(ridx) 

4905 return join_index, lidx, ridx 

4906 

4907 def _wrap_join_result( 

4908 self, 

4909 joined: ArrayLike, 

4910 other: Self, 

4911 lidx: npt.NDArray[np.intp] | None, 

4912 ridx: npt.NDArray[np.intp] | None, 

4913 how: JoinHow, 

4914 ) -> tuple[Self, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

4915 assert other.dtype == self.dtype 

4916 

4917 if lidx is not None and lib.is_range_indexer(lidx, len(self)): 

4918 lidx = None 

4919 if ridx is not None and lib.is_range_indexer(ridx, len(other)): 

4920 ridx = None 

4921 

4922 # return self or other if possible to maintain cached attributes 

4923 if lidx is None: 

4924 join_index = self 

4925 elif ridx is None: 

4926 join_index = other 

4927 else: 

4928 join_index = self._constructor._with_infer( 

4929 joined, dtype=self.dtype, copy=False 

4930 ) 

4931 

4932 names = other.names if how == "right" else self.names 

4933 if join_index.names != names: 

4934 join_index = join_index.set_names(names) 

4935 

4936 return join_index, lidx, ridx 

4937 

4938 @final 

4939 @cache_readonly 

4940 def _can_use_libjoin(self) -> bool: 

4941 """ 

4942 Whether we can use the fastpaths implemented in _libs.join. 

4943 

4944 This is driven by whether (in monotonic increasing cases that are 

4945 guaranteed not to have NAs) we can convert to an np.ndarray without 

4946 making a copy. If we cannot, this negates the performance benefit 

4947 of using libjoin. 

4948 """ 

4949 if not self.is_monotonic_increasing: 

4950 # The libjoin functions all assume monotonicity. 

4951 return False 

4952 

4953 if type(self) is Index: 

4954 # excludes EAs, but include masks, we get here with monotonic 

4955 # values only, meaning no NA 

4956 return ( 

4957 isinstance(self.dtype, np.dtype) 

4958 or isinstance(self._values, (ArrowExtensionArray, BaseMaskedArray)) 

4959 or ( 

4960 isinstance(self.dtype, StringDtype) 

4961 and self.dtype.storage == "python" 

4962 ) 

4963 ) 

4964 # Exclude index types where the conversion to numpy converts to object dtype, 

4965 # which negates the performance benefit of libjoin 

4966 # Subclasses should override to return False if _get_join_target is 

4967 # not zero-copy. 

4968 # TODO: exclude RangeIndex (which allocates memory)? 

4969 # Doing so seems to break test_concat_datetime_timezone 

4970 return not isinstance(self, (ABCIntervalIndex, ABCMultiIndex)) 

4971 

4972 # -------------------------------------------------------------------- 

4973 # Uncategorized Methods 

4974 

4975 @property 

4976 def values(self) -> ArrayLike: 

4977 """ 

4978 Return an array representing the data in the Index. 

4979 

4980 .. warning:: 

4981 

4982 We recommend using :attr:`Index.array` or 

4983 :meth:`Index.to_numpy`, depending on whether you need 

4984 a reference to the underlying data or a NumPy array. 

4985 

4986 .. versionchanged:: 3.0.0 

4987 

4988 The returned array is read-only. 

4989 

4990 Returns 

4991 ------- 

4992 array: numpy.ndarray or ExtensionArray 

4993 

4994 See Also 

4995 -------- 

4996 Index.array : Reference to the underlying data. 

4997 Index.to_numpy : A NumPy array representing the underlying data. 

4998 

4999 Examples 

5000 -------- 

5001 For :class:`pandas.Index`: 

5002 

5003 >>> idx = pd.Index([1, 2, 3]) 

5004 >>> idx 

5005 Index([1, 2, 3], dtype='int64') 

5006 >>> idx.values 

5007 array([1, 2, 3]) 

5008 

5009 For :class:`pandas.IntervalIndex`: 

5010 

5011 >>> idx = pd.interval_range(start=0, end=5) 

5012 >>> idx.values 

5013 <IntervalArray> 

5014 [(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]] 

5015 Length: 5, dtype: interval[int64, right] 

5016 """ 

5017 data = self._data 

5018 if isinstance(data, np.ndarray): 

5019 data = data.view() 

5020 data.flags.writeable = False 

5021 return data 

5022 

5023 @cache_readonly 

5024 def array(self) -> ExtensionArray: 

5025 """ 

5026 The ExtensionArray of the data backing this Index. 

5027 

5028 This property provides direct access to the underlying array data of 

5029 an Index without requiring conversion to a NumPy array. It 

5030 returns an ExtensionArray, which is the native storage format for 

5031 pandas extension dtypes. 

5032 

5033 Returns 

5034 ------- 

5035 ExtensionArray 

5036 An ExtensionArray of the values stored within. For extension 

5037 types, this is the actual array. For NumPy native types, this 

5038 is a thin (no copy) wrapper around :class:`numpy.ndarray`. 

5039 

5040 ``.array`` differs from ``.values``, which may require converting 

5041 the data to a different form. 

5042 

5043 See Also 

5044 -------- 

5045 Index.to_numpy : Similar method that always returns a NumPy array. 

5046 Series.to_numpy : Similar method that always returns a NumPy array. 

5047 

5048 Notes 

5049 ----- 

5050 This table lays out the different array types for each extension 

5051 dtype within pandas. 

5052 

5053 ================== ============================= 

5054 dtype array type 

5055 ================== ============================= 

5056 category Categorical 

5057 period PeriodArray 

5058 interval IntervalArray 

5059 IntegerNA IntegerArray 

5060 string StringArray 

5061 boolean BooleanArray 

5062 datetime64[ns, tz] DatetimeArray 

5063 ================== ============================= 

5064 

5065 For any 3rd-party extension types, the array type will be an 

5066 ExtensionArray. 

5067 

5068 For all remaining dtypes ``.array`` will be a 

5069 :class:`arrays.NumpyExtensionArray` wrapping the actual ndarray 

5070 stored within. If you absolutely need a NumPy array (possibly with 

5071 copying / coercing data), then use :meth:`Series.to_numpy` instead. 

5072 

5073 Examples 

5074 -------- 

5075 For regular NumPy types like int, and float, a NumpyExtensionArray 

5076 is returned. 

5077 

5078 >>> pd.Index([1, 2, 3]).array 

5079 <NumpyExtensionArray> 

5080 [1, 2, 3] 

5081 Length: 3, dtype: int64 

5082 

5083 For extension types, like Categorical, the actual ExtensionArray 

5084 is returned 

5085 

5086 >>> idx = pd.Index(pd.Categorical(["a", "b", "a"])) 

5087 >>> idx.array 

5088 ['a', 'b', 'a'] 

5089 Categories (2, str): ['a', 'b'] 

5090 """ 

5091 array = self._data 

5092 if isinstance(array, np.ndarray): 

5093 from pandas.core.arrays.numpy_ import NumpyExtensionArray 

5094 

5095 array = NumpyExtensionArray(array) 

5096 # TODO decide on read-only https://github.com/pandas-dev/pandas/issues/63099 

5097 # array = array.view() 

5098 # array._readonly = True 

5099 return array 

5100 

5101 @property 

5102 def _values(self) -> ExtensionArray | np.ndarray: 

5103 """ 

5104 The best array representation. 

5105 

5106 This is an ndarray or ExtensionArray. 

5107 

5108 ``_values`` are consistent between ``Series`` and ``Index``. 

5109 

5110 It may differ from the public '.values' method. 

5111 

5112 index | values | _values | 

5113 ----------------- | --------------- | ------------- | 

5114 Index | ndarray | ndarray | 

5115 CategoricalIndex | Categorical | Categorical | 

5116 DatetimeIndex | ndarray[M8ns] | DatetimeArray | 

5117 DatetimeIndex[tz] | ndarray[M8ns] | DatetimeArray | 

5118 PeriodIndex | ndarray[object] | PeriodArray | 

5119 IntervalIndex | IntervalArray | IntervalArray | 

5120 

5121 See Also 

5122 -------- 

5123 values : Values 

5124 """ 

5125 return self._data 

5126 

5127 def _get_engine_target(self) -> ArrayLike: 

5128 """ 

5129 Get the ndarray or ExtensionArray that we can pass to the IndexEngine 

5130 constructor. 

5131 """ 

5132 vals = self._values 

5133 if isinstance(vals, StringArray): 

5134 # GH#45652 much more performant than ExtensionEngine 

5135 return vals._ndarray 

5136 if isinstance(vals, ArrowExtensionArray) and self.dtype.kind in "Mm": 

5137 import pyarrow as pa 

5138 

5139 pa_type = vals._pa_array.type 

5140 if pa.types.is_timestamp(pa_type): 

5141 vals = vals._to_datetimearray() 

5142 return vals._ndarray.view("i8") 

5143 elif pa.types.is_duration(pa_type): 

5144 vals = vals._to_timedeltaarray() 

5145 return vals._ndarray.view("i8") 

5146 if ( 

5147 type(self) is Index 

5148 and isinstance(self._values, ExtensionArray) 

5149 and not isinstance(self._values, BaseMaskedArray) 

5150 and not ( 

5151 isinstance(self._values, ArrowExtensionArray) 

5152 and is_numeric_dtype(self.dtype) 

5153 # Exclude decimal 

5154 and self.dtype.kind != "O" 

5155 ) 

5156 ): 

5157 # TODO(ExtensionIndex): remove special-case, just use self._values 

5158 return self._values.astype(object) 

5159 return vals 

5160 

5161 @final 

5162 def _get_join_target(self) -> np.ndarray: 

5163 """ 

5164 Get the ndarray or ExtensionArray that we can pass to the join 

5165 functions. 

5166 """ 

5167 if isinstance(self._values, BaseMaskedArray): 

5168 # This is only used if our array is monotonic, so no NAs present 

5169 return self._values._data 

5170 elif ( 

5171 isinstance(self._values, ArrowExtensionArray) 

5172 and self.dtype.kind not in "mM" 

5173 ): 

5174 # This is only used if our array is monotonic, so no missing values 

5175 # present 

5176 # "mM" cases will go through _get_engine_target and cast to i8 

5177 return self._values.to_numpy() 

5178 

5179 # TODO: exclude ABCRangeIndex case here as it copies 

5180 target = self._get_engine_target() 

5181 if not isinstance(target, np.ndarray): 

5182 raise ValueError("_can_use_libjoin should return False.") 

5183 return target 

5184 

5185 def _from_join_target(self, result: np.ndarray) -> ArrayLike: 

5186 """ 

5187 Cast the ndarray returned from one of the libjoin.foo_indexer functions 

5188 back to type(self._data). 

5189 """ 

5190 if isinstance(self.values, BaseMaskedArray): 

5191 return type(self.values)(result, np.zeros(result.shape, dtype=np.bool_)) 

5192 elif isinstance(self.values, (ArrowExtensionArray, StringArray)): 

5193 return type(self.values)._from_sequence(result, dtype=self.dtype) 

5194 return result 

5195 

5196 def memory_usage(self, deep: bool = False) -> int: 

5197 """ 

5198 Memory usage of the values. 

5199 

5200 Parameters 

5201 ---------- 

5202 deep : bool, default False 

5203 Introspect the data deeply, interrogate 

5204 `object` dtypes for system-level memory consumption. 

5205 

5206 Returns 

5207 ------- 

5208 bytes used 

5209 Returns memory usage of the values in the Index in bytes. 

5210 

5211 See Also 

5212 -------- 

5213 numpy.ndarray.nbytes : Total bytes consumed by the elements of the 

5214 array. 

5215 

5216 Notes 

5217 ----- 

5218 Memory usage does not include memory consumed by elements that 

5219 are not components of the array if deep=False or if used on PyPy 

5220 

5221 Examples 

5222 -------- 

5223 >>> idx = pd.Index([1, 2, 3]) 

5224 >>> idx.memory_usage() 

5225 24 

5226 """ 

5227 result = self._memory_usage(deep=deep) 

5228 

5229 # include our engine hashtable, only if it's already cached 

5230 if "_engine" in self._cache: 

5231 result += self._engine.sizeof(deep=deep) 

5232 return result 

5233 

5234 @final 

5235 def where(self, cond, other=None) -> Index: 

5236 """ 

5237 Replace values where the condition is False. 

5238 

5239 The replacement is taken from other. 

5240 

5241 Parameters 

5242 ---------- 

5243 cond : bool array-like with the same length as self 

5244 Condition to select the values on. 

5245 other : scalar, or array-like, default None 

5246 Replacement if the condition is False. 

5247 

5248 Returns 

5249 ------- 

5250 pandas.Index 

5251 A copy of self with values replaced from other 

5252 where the condition is False. 

5253 

5254 See Also 

5255 -------- 

5256 Series.where : Same method for Series. 

5257 DataFrame.where : Same method for DataFrame. 

5258 

5259 Examples 

5260 -------- 

5261 >>> idx = pd.Index(["car", "bike", "train", "tractor"]) 

5262 >>> idx 

5263 Index(['car', 'bike', 'train', 'tractor'], dtype='str') 

5264 >>> idx.where(idx.isin(["car", "train"]), "other") 

5265 Index(['car', 'other', 'train', 'other'], dtype='str') 

5266 """ 

5267 if isinstance(self, ABCMultiIndex): 

5268 raise NotImplementedError( 

5269 ".where is not supported for MultiIndex operations" 

5270 ) 

5271 cond = np.asarray(cond, dtype=bool) 

5272 return self.putmask(~cond, other) 

5273 

5274 # construction helpers 

5275 @final 

5276 @classmethod 

5277 def _raise_scalar_data_error(cls, data): 

5278 # We return the TypeError so that we can raise it from the constructor 

5279 # in order to keep mypy happy 

5280 raise TypeError( 

5281 f"{cls.__name__}(...) must be called with a collection of some " 

5282 f"kind, {repr(data) if not isinstance(data, np.generic) else str(data)} " 

5283 "was passed" 

5284 ) 

5285 

5286 @classmethod 

5287 def _maybe_copy_array_input( 

5288 cls, data, copy: bool | None, dtype 

5289 ) -> tuple[Any, bool]: 

5290 """ 

5291 Ensure that the input data is copied if necessary. 

5292 GH#63388 

5293 """ 

5294 if isinstance(data, (ExtensionArray, np.ndarray)): 

5295 if copy is not False: 

5296 if dtype is None or astype_is_view(data.dtype, pandas_dtype(dtype)): 

5297 data = data.copy() 

5298 copy = False 

5299 return data, bool(copy) 

5300 

5301 def _validate_fill_value(self, value): 

5302 """ 

5303 Check if the value can be inserted into our array without casting, 

5304 and convert it to an appropriate native type if necessary. 

5305 

5306 Raises 

5307 ------ 

5308 TypeError 

5309 If the value cannot be inserted into an array of this dtype. 

5310 """ 

5311 dtype = self.dtype 

5312 if isinstance(dtype, np.dtype) and dtype.kind not in "mM": 

5313 if isinstance(value, tuple) and dtype != object: 

5314 # GH#54385 

5315 raise TypeError 

5316 try: 

5317 return np_can_hold_element(dtype, value) 

5318 except LossySetitemError as err: 

5319 # re-raise as TypeError for consistency 

5320 raise TypeError from err 

5321 elif not can_hold_element(self._values, value): 

5322 raise TypeError 

5323 return value 

5324 

5325 @cache_readonly 

5326 def _is_memory_usage_qualified(self) -> bool: 

5327 """ 

5328 Return a boolean if we need a qualified .info display. 

5329 """ 

5330 return is_object_dtype(self.dtype) or ( 

5331 is_string_dtype(self.dtype) and self.dtype.storage == "python" # type: ignore[union-attr] 

5332 ) 

5333 

5334 def __contains__(self, key: Any) -> bool: 

5335 """ 

5336 Return a boolean indicating whether the provided key is in the index. 

5337 

5338 Parameters 

5339 ---------- 

5340 key : label 

5341 The key to check if it is present in the index. 

5342 

5343 Returns 

5344 ------- 

5345 bool 

5346 Whether the key search is in the index. 

5347 

5348 Raises 

5349 ------ 

5350 TypeError 

5351 If the key is not hashable. 

5352 

5353 See Also 

5354 -------- 

5355 Index.isin : Returns an ndarray of boolean dtype indicating whether the 

5356 list-like key is in the index. 

5357 

5358 Examples 

5359 -------- 

5360 >>> idx = pd.Index([1, 2, 3, 4]) 

5361 >>> idx 

5362 Index([1, 2, 3, 4], dtype='int64') 

5363 

5364 >>> 2 in idx 

5365 True 

5366 >>> 6 in idx 

5367 False 

5368 """ 

5369 hash(key) 

5370 try: 

5371 return key in self._engine 

5372 except (OverflowError, TypeError, ValueError): 

5373 return False 

5374 

5375 # https://github.com/python/typeshed/issues/2148#issuecomment-520783318 

5376 # Incompatible types in assignment (expression has type "None", base class 

5377 # "object" defined the type as "Callable[[object], int]") 

5378 __hash__: ClassVar[None] # type: ignore[assignment] 

5379 

5380 @final 

5381 def __setitem__(self, key, value) -> None: 

5382 raise TypeError("Index does not support mutable operations") 

5383 

5384 def __getitem__(self, key): 

5385 """ 

5386 Override numpy.ndarray's __getitem__ method to work as desired. 

5387 

5388 This function adds lists and Series as valid boolean indexers 

5389 (ndarrays only supports ndarray with dtype=bool). 

5390 

5391 If resulting ndim != 1, plain ndarray is returned instead of 

5392 corresponding `Index` subclass. 

5393 

5394 """ 

5395 getitem = self._data.__getitem__ 

5396 

5397 key = lib.item_from_zerodim(key) 

5398 if is_integer(key) or is_float(key): 

5399 # GH#44051 exclude bool, which would return a 2d ndarray 

5400 key = com.cast_scalar_indexer(key) 

5401 return getitem(key) 

5402 

5403 if isinstance(key, slice): 

5404 # This case is separated from the conditional above to avoid 

5405 # pessimization com.is_bool_indexer and ndim checks. 

5406 return self._getitem_slice(key) 

5407 

5408 if com.is_bool_indexer(key): 

5409 # if we have list[bools, length=1e5] then doing this check+convert 

5410 # takes 166 µs + 2.1 ms and cuts the ndarray.__getitem__ 

5411 # time below from 3.8 ms to 496 µs 

5412 # if we already have ndarray[bool], the overhead is 1.4 µs or .25% 

5413 if isinstance(getattr(key, "dtype", None), ExtensionDtype): 

5414 key = key.to_numpy(dtype=bool, na_value=False) 

5415 else: 

5416 key = np.asarray(key, dtype=bool) 

5417 

5418 if not isinstance(self.dtype, ExtensionDtype): 

5419 if len(key) == 0 and len(key) != len(self): 

5420 raise ValueError( 

5421 "The length of the boolean indexer cannot be 0 " 

5422 "when the Index has length greater than 0." 

5423 ) 

5424 

5425 result = getitem(key) 

5426 # Because we ruled out integer above, we always get an arraylike here 

5427 if result.ndim > 1: 

5428 disallow_ndim_indexing(result) 

5429 

5430 # NB: Using _constructor._simple_new would break if MultiIndex 

5431 # didn't override __getitem__ 

5432 return self._constructor._simple_new(result, name=self._name) 

5433 

5434 def _getitem_slice(self, slobj: slice) -> Self: 

5435 """ 

5436 Fastpath for __getitem__ when we know we have a slice. 

5437 """ 

5438 res = self._data[slobj] 

5439 result = type(self)._simple_new(res, name=self._name, refs=self._references) 

5440 if "_engine" in self._cache: 

5441 reverse = slobj.step is not None and slobj.step < 0 

5442 result._engine._update_from_sliced(self._engine, reverse=reverse) # type: ignore[union-attr] 

5443 

5444 return result 

5445 

5446 @final 

5447 def _can_hold_identifiers_and_holds_name(self, name) -> bool: 

5448 """ 

5449 Faster check for ``name in self`` when we know `name` is a Python 

5450 identifier (e.g. in NDFrame.__getattr__, which hits this to support 

5451 . key lookup). For indexes that can't hold identifiers (everything 

5452 but object & categorical) we just return False. 

5453 

5454 https://github.com/pandas-dev/pandas/issues/19764 

5455 """ 

5456 if ( 

5457 is_object_dtype(self.dtype) 

5458 or is_string_dtype(self.dtype) 

5459 or isinstance(self.dtype, CategoricalDtype) 

5460 ): 

5461 return name in self 

5462 return False 

5463 

5464 def append(self, other: Index | Sequence[Index]) -> Index: 

5465 """ 

5466 Append a collection of Index options together. 

5467 

5468 Parameters 

5469 ---------- 

5470 other : Index or list/tuple of indices 

5471 Single Index or a collection of indices, which can be either a list or a 

5472 tuple. 

5473 

5474 Returns 

5475 ------- 

5476 Index 

5477 Returns a new Index object resulting from appending the provided other 

5478 indices to the original Index. 

5479 

5480 See Also 

5481 -------- 

5482 Index.insert : Make new Index inserting new item at location. 

5483 

5484 Examples 

5485 -------- 

5486 >>> idx = pd.Index([1, 2, 3]) 

5487 >>> idx.append(pd.Index([4])) 

5488 Index([1, 2, 3, 4], dtype='int64') 

5489 """ 

5490 to_concat = [self] 

5491 

5492 if isinstance(other, (list, tuple)): 

5493 to_concat += list(other) 

5494 else: 

5495 # error: Argument 1 to "append" of "list" has incompatible type 

5496 # "Union[Index, Sequence[Index]]"; expected "Index" 

5497 to_concat.append(other) # type: ignore[arg-type] 

5498 

5499 for obj in to_concat: 

5500 if not isinstance(obj, Index): 

5501 raise TypeError("all inputs must be Index") 

5502 

5503 names = {obj.name for obj in to_concat} 

5504 name = None if len(names) > 1 else self.name 

5505 

5506 return self._concat(to_concat, name) 

5507 

5508 def _concat(self, to_concat: list[Index], name: Hashable) -> Index: 

5509 """ 

5510 Concatenate multiple Index objects. 

5511 """ 

5512 to_concat_vals = [x._values for x in to_concat] 

5513 

5514 result = concat_compat(to_concat_vals) 

5515 

5516 return Index._with_infer(result, name=name, copy=False) 

5517 

5518 def putmask(self, mask, value) -> Index: 

5519 """ 

5520 Return a new Index of the values set with the mask. 

5521 

5522 Parameters 

5523 ---------- 

5524 mask : array-like of bool 

5525 Array of booleans denoting where values should be replaced. 

5526 value : scalar 

5527 Scalar value to use to fill holes (e.g. 0). 

5528 This value cannot be a list-likes. 

5529 

5530 Returns 

5531 ------- 

5532 Index 

5533 A new Index of the values set with the mask. 

5534 

5535 See Also 

5536 -------- 

5537 numpy.putmask : Changes elements of an array 

5538 based on conditional and input values. 

5539 

5540 Examples 

5541 -------- 

5542 >>> idx1 = pd.Index([1, 2, 3]) 

5543 >>> idx2 = pd.Index([5, 6, 7]) 

5544 >>> idx1.putmask([True, False, False], idx2) 

5545 Index([5, 2, 3], dtype='int64') 

5546 """ 

5547 mask, noop = validate_putmask(self._values, mask) 

5548 if noop: 

5549 return self.copy() 

5550 

5551 if self.dtype != object and is_valid_na_for_dtype(value, self.dtype): 

5552 # e.g. None -> np.nan, see also Block._standardize_fill_value 

5553 value = self._na_value 

5554 

5555 try: 

5556 converted = self._validate_fill_value(value) 

5557 except (LossySetitemError, ValueError, TypeError) as err: 

5558 if is_object_dtype(self.dtype): # pragma: no cover 

5559 raise err 

5560 

5561 # See also: Block.coerce_to_target_dtype 

5562 dtype = self._find_common_type_compat(value) 

5563 if dtype == self.dtype: 

5564 # GH#56376 avoid RecursionError 

5565 raise AssertionError( 

5566 "Something has gone wrong. Please report a bug at " 

5567 "github.com/pandas-dev/pandas" 

5568 ) from err 

5569 return self.astype(dtype).putmask(mask, value) 

5570 

5571 values = self._values.copy() 

5572 

5573 if isinstance(values, np.ndarray): 

5574 converted = setitem_datetimelike_compat(values, mask.sum(), converted) 

5575 np.putmask(values, mask, converted) 

5576 

5577 else: 

5578 # Note: we use the original value here, not converted, as 

5579 # _validate_fill_value is not idempotent 

5580 values._putmask(mask, value) 

5581 

5582 return self._shallow_copy(values) 

5583 

5584 def equals(self, other: Any) -> bool: 

5585 """ 

5586 Determine if two Index object are equal. 

5587 

5588 The things that are being compared are: 

5589 

5590 * The elements inside the Index object. 

5591 * The order of the elements inside the Index object. 

5592 

5593 Parameters 

5594 ---------- 

5595 other : Any 

5596 The other object to compare against. 

5597 

5598 Returns 

5599 ------- 

5600 bool 

5601 True if "other" is an Index and it has the same elements and order 

5602 as the calling index; False otherwise. 

5603 

5604 See Also 

5605 -------- 

5606 Index.identical: Checks that object attributes and types are also equal. 

5607 Index.has_duplicates: Check if the Index has duplicate values. 

5608 Index.is_unique: Return if the index has unique values. 

5609 

5610 Examples 

5611 -------- 

5612 >>> idx1 = pd.Index([1, 2, 3]) 

5613 >>> idx1 

5614 Index([1, 2, 3], dtype='int64') 

5615 >>> idx1.equals(pd.Index([1, 2, 3])) 

5616 True 

5617 

5618 The elements inside are compared 

5619 

5620 >>> idx2 = pd.Index(["1", "2", "3"]) 

5621 >>> idx2 

5622 Index(['1', '2', '3'], dtype='str') 

5623 

5624 >>> idx1.equals(idx2) 

5625 False 

5626 

5627 The order is compared 

5628 

5629 >>> ascending_idx = pd.Index([1, 2, 3]) 

5630 >>> ascending_idx 

5631 Index([1, 2, 3], dtype='int64') 

5632 >>> descending_idx = pd.Index([3, 2, 1]) 

5633 >>> descending_idx 

5634 Index([3, 2, 1], dtype='int64') 

5635 >>> ascending_idx.equals(descending_idx) 

5636 False 

5637 

5638 The dtype is *not* compared 

5639 

5640 >>> int64_idx = pd.Index([1, 2, 3], dtype="int64") 

5641 >>> int64_idx 

5642 Index([1, 2, 3], dtype='int64') 

5643 >>> uint64_idx = pd.Index([1, 2, 3], dtype="uint64") 

5644 >>> uint64_idx 

5645 Index([1, 2, 3], dtype='uint64') 

5646 >>> int64_idx.equals(uint64_idx) 

5647 True 

5648 """ 

5649 if self.is_(other): 

5650 return True 

5651 

5652 if not isinstance(other, Index): 

5653 return False 

5654 

5655 if len(self) != len(other): 

5656 # quickly return if the lengths are different 

5657 return False 

5658 

5659 if isinstance(self.dtype, StringDtype) and other.dtype != self.dtype: 

5660 # TODO(infer_string) can we avoid this special case? 

5661 # special case for object behavior 

5662 return other.equals(self.astype(object)) 

5663 

5664 if is_object_dtype(self.dtype) and not is_object_dtype(other.dtype): 

5665 # if other is not object, use other's logic for coercion 

5666 return other.equals(self) 

5667 

5668 if isinstance(other, ABCMultiIndex): 

5669 # d-level MultiIndex can equal d-tuple Index 

5670 return other.equals(self) 

5671 

5672 if isinstance(self._values, ExtensionArray): 

5673 # Dispatch to the ExtensionArray's .equals method. 

5674 if not isinstance(other, type(self)): 

5675 return False 

5676 

5677 earr = cast(ExtensionArray, self._data) 

5678 return earr.equals(other._data) 

5679 

5680 if isinstance(other.dtype, ExtensionDtype): 

5681 # All EA-backed Index subclasses override equals 

5682 return other.equals(self) 

5683 

5684 return array_equivalent(self._values, other._values) 

5685 

5686 @final 

5687 def identical(self, other) -> bool: 

5688 """ 

5689 Similar to equals, but checks that object attributes and types are also equal. 

5690 

5691 Parameters 

5692 ---------- 

5693 other : Index 

5694 The Index object you want to compare with the current Index object. 

5695 

5696 Returns 

5697 ------- 

5698 bool 

5699 If two Index objects have equal elements and same type True, 

5700 otherwise False. 

5701 

5702 See Also 

5703 -------- 

5704 Index.equals: Determine if two Index object are equal. 

5705 Index.has_duplicates: Check if the Index has duplicate values. 

5706 Index.is_unique: Return if the index has unique values. 

5707 

5708 Examples 

5709 -------- 

5710 >>> idx1 = pd.Index(["1", "2", "3"]) 

5711 >>> idx2 = pd.Index(["1", "2", "3"]) 

5712 >>> idx2.identical(idx1) 

5713 True 

5714 

5715 >>> idx1 = pd.Index(["1", "2", "3"], name="A") 

5716 >>> idx2 = pd.Index(["1", "2", "3"], name="B") 

5717 >>> idx2.identical(idx1) 

5718 False 

5719 """ 

5720 return ( 

5721 self.equals(other) 

5722 and all( 

5723 getattr(self, c, None) == getattr(other, c, None) 

5724 for c in self._comparables 

5725 ) 

5726 and type(self) == type(other) 

5727 and self.dtype == other.dtype 

5728 ) 

5729 

5730 @final 

5731 def asof(self, label): 

5732 """ 

5733 Return the label from the index, or, if not present, the previous one. 

5734 

5735 Assuming that the index is sorted, return the passed index label if it 

5736 is in the index, or return the previous index label if the passed one 

5737 is not in the index. 

5738 

5739 Parameters 

5740 ---------- 

5741 label : object 

5742 The label up to which the method returns the latest index label. 

5743 

5744 Returns 

5745 ------- 

5746 object 

5747 The passed label if it is in the index. The previous label if the 

5748 passed label is not in the sorted index or `NaN` if there is no 

5749 such label. 

5750 

5751 See Also 

5752 -------- 

5753 Series.asof : Return the latest value in a Series up to the 

5754 passed index. 

5755 merge_asof : Perform an asof merge (similar to left join but it 

5756 matches on nearest key rather than equal key). 

5757 Index.get_loc : An `asof` is a thin wrapper around `get_loc` 

5758 with method='pad'. 

5759 

5760 Examples 

5761 -------- 

5762 `Index.asof` returns the latest index label up to the passed label. 

5763 

5764 >>> idx = pd.Index(["2013-12-31", "2014-01-02", "2014-01-03"]) 

5765 >>> idx.asof("2014-01-01") 

5766 '2013-12-31' 

5767 

5768 If the label is in the index, the method returns the passed label. 

5769 

5770 >>> idx.asof("2014-01-02") 

5771 '2014-01-02' 

5772 

5773 If all of the labels in the index are later than the passed label, 

5774 NaN is returned. 

5775 

5776 >>> idx.asof("1999-01-02") 

5777 nan 

5778 

5779 If the index is not sorted, an error is raised. 

5780 

5781 >>> idx_not_sorted = pd.Index(["2013-12-31", "2015-01-02", "2014-01-03"]) 

5782 >>> idx_not_sorted.asof("2013-12-31") 

5783 Traceback (most recent call last): 

5784 ValueError: index must be monotonic increasing or decreasing 

5785 """ 

5786 self._searchsorted_monotonic(label) # validate sortedness 

5787 try: 

5788 loc = self.get_loc(label) 

5789 except (KeyError, TypeError) as err: 

5790 # KeyError -> No exact match, try for padded 

5791 # TypeError -> passed e.g. non-hashable, fall through to get 

5792 # the tested exception message 

5793 indexer = self.get_indexer([label], method="pad") 

5794 if indexer.ndim > 1 or indexer.size > 1: 

5795 raise TypeError("asof requires scalar valued input") from err 

5796 loc = indexer.item() 

5797 if loc == -1: 

5798 return self._na_value 

5799 else: 

5800 if isinstance(loc, slice): 

5801 return self[loc][-1] 

5802 

5803 return self[loc] 

5804 

5805 def asof_locs( 

5806 self, where: Index, mask: npt.NDArray[np.bool_] 

5807 ) -> npt.NDArray[np.intp]: 

5808 """ 

5809 Return the locations (indices) of labels in the index. 

5810 

5811 As in the :meth:`pandas.Index.asof`, if the label (a particular entry in 

5812 ``where``) is not in the index, the latest index label up to the 

5813 passed label is chosen and its index returned. 

5814 

5815 If all of the labels in the index are later than a label in ``where``, 

5816 -1 is returned. 

5817 

5818 ``mask`` is used to ignore ``NA`` values in the index during calculation. 

5819 

5820 Parameters 

5821 ---------- 

5822 where : Index 

5823 An Index consisting of an array of timestamps. 

5824 mask : np.ndarray[bool] 

5825 Array of booleans denoting where values in the original 

5826 data are not ``NA``. 

5827 

5828 Returns 

5829 ------- 

5830 np.ndarray[np.intp] 

5831 An array of locations (indices) of the labels from the index 

5832 which correspond to the return values of :meth:`pandas.Index.asof` 

5833 for every element in ``where``. 

5834 

5835 See Also 

5836 -------- 

5837 Index.asof : Return the label from the index, or, if not present, the 

5838 previous one. 

5839 

5840 Examples 

5841 -------- 

5842 >>> idx = pd.date_range("2023-06-01", periods=3, freq="D") 

5843 >>> where = pd.DatetimeIndex( 

5844 ... ["2023-05-30 00:12:00", "2023-06-01 00:00:00", "2023-06-02 23:59:59"] 

5845 ... ) 

5846 >>> mask = np.ones(3, dtype=bool) 

5847 >>> idx.asof_locs(where, mask) 

5848 array([-1, 0, 1]) 

5849 

5850 We can use ``mask`` to ignore certain values in the index during calculation. 

5851 

5852 >>> mask[1] = False 

5853 >>> idx.asof_locs(where, mask) 

5854 array([-1, 0, 0]) 

5855 """ 

5856 # error: No overload variant of "searchsorted" of "ndarray" matches argument 

5857 # types "Union[ExtensionArray, ndarray[Any, Any]]", "str" 

5858 # TODO: will be fixed when ExtensionArray.searchsorted() is fixed 

5859 locs = self._values[mask].searchsorted( 

5860 where._values, 

5861 side="right", # type: ignore[call-overload] 

5862 ) 

5863 locs = np.where(locs > 0, locs - 1, 0) 

5864 

5865 result = np.arange(len(self), dtype=np.intp)[mask].take(locs) 

5866 

5867 first_value = self._values[mask.argmax()] 

5868 result[(locs == 0) & (where._values < first_value)] = -1 

5869 

5870 return result 

5871 

5872 @overload 

5873 def sort_values( 

5874 self, 

5875 *, 

5876 return_indexer: Literal[False] = ..., 

5877 ascending: bool = ..., 

5878 na_position: NaPosition = ..., 

5879 key: Callable | None = ..., 

5880 ) -> Self: ... 

5881 

5882 @overload 

5883 def sort_values( 

5884 self, 

5885 *, 

5886 return_indexer: Literal[True], 

5887 ascending: bool = ..., 

5888 na_position: NaPosition = ..., 

5889 key: Callable | None = ..., 

5890 ) -> tuple[Self, np.ndarray]: ... 

5891 

5892 @overload 

5893 def sort_values( 

5894 self, 

5895 *, 

5896 return_indexer: bool = ..., 

5897 ascending: bool = ..., 

5898 na_position: NaPosition = ..., 

5899 key: Callable | None = ..., 

5900 ) -> Self | tuple[Self, np.ndarray]: ... 

5901 

5902 def sort_values( 

5903 self, 

5904 *, 

5905 return_indexer: bool = False, 

5906 ascending: bool = True, 

5907 na_position: NaPosition = "last", 

5908 key: Callable | None = None, 

5909 ) -> Self | tuple[Self, np.ndarray]: 

5910 """ 

5911 Return a sorted copy of the index. 

5912 

5913 Return a sorted copy of the index, and optionally return the indices 

5914 that sorted the index itself. 

5915 

5916 Parameters 

5917 ---------- 

5918 return_indexer : bool, default False 

5919 Should the indices that would sort the index be returned. 

5920 ascending : bool, default True 

5921 Should the index values be sorted in an ascending order. 

5922 na_position : {'first' or 'last'}, default 'last' 

5923 Argument 'first' puts NaNs at the beginning, 'last' puts NaNs at 

5924 the end. 

5925 key : callable, optional 

5926 If not None, apply the key function to the index values 

5927 before sorting. This is similar to the `key` argument in the 

5928 builtin :meth:`sorted` function, with the notable difference that 

5929 this `key` function should be *vectorized*. It should expect an 

5930 ``Index`` and return an ``Index`` of the same shape. 

5931 

5932 Returns 

5933 ------- 

5934 sorted_index : pandas.Index 

5935 Sorted copy of the index. 

5936 indexer : numpy.ndarray, optional 

5937 The indices that the index itself was sorted by. 

5938 

5939 See Also 

5940 -------- 

5941 Series.sort_values : Sort values of a Series. 

5942 DataFrame.sort_values : Sort values in a DataFrame. 

5943 

5944 Examples 

5945 -------- 

5946 >>> idx = pd.Index([10, 100, 1, 1000]) 

5947 >>> idx 

5948 Index([10, 100, 1, 1000], dtype='int64') 

5949 

5950 Sort values in ascending order (default behavior). 

5951 

5952 >>> idx.sort_values() 

5953 Index([1, 10, 100, 1000], dtype='int64') 

5954 

5955 Sort values in descending order, and also get the indices `idx` was 

5956 sorted by. 

5957 

5958 >>> idx.sort_values(ascending=False, return_indexer=True) 

5959 (Index([1000, 100, 10, 1], dtype='int64'), array([3, 1, 0, 2])) 

5960 """ 

5961 if key is None and ( 

5962 (ascending and self.is_monotonic_increasing) 

5963 or (not ascending and self.is_monotonic_decreasing) 

5964 ): 

5965 if return_indexer: 

5966 indexer = np.arange(len(self), dtype=np.intp) 

5967 return self.copy(), indexer 

5968 else: 

5969 return self.copy() 

5970 

5971 # GH 35584. Sort missing values according to na_position kwarg 

5972 # ignore na_position for MultiIndex 

5973 if not isinstance(self, ABCMultiIndex): 

5974 _as = nargsort( 

5975 items=self, ascending=ascending, na_position=na_position, key=key 

5976 ) 

5977 else: 

5978 idx = cast(Index, ensure_key_mapped(self, key)) 

5979 _as = idx.argsort(na_position=na_position) 

5980 if not ascending: 

5981 _as = _as[::-1] 

5982 

5983 sorted_index = self.take(_as) 

5984 

5985 if return_indexer: 

5986 return sorted_index, _as 

5987 else: 

5988 return sorted_index 

5989 

5990 def shift(self, periods: int = 1, freq=None) -> Self: 

5991 """ 

5992 Shift index by desired number of time frequency increments. 

5993 

5994 This method is for shifting the values of datetime-like indexes 

5995 by a specified time increment a given number of times. 

5996 

5997 Parameters 

5998 ---------- 

5999 periods : int, default 1 

6000 Number of periods (or increments) to shift by, 

6001 can be positive or negative. 

6002 freq : pandas.DateOffset, pandas.Timedelta or str, optional 

6003 Frequency increment to shift by. 

6004 If None, the index is shifted by its own `freq` attribute. 

6005 Offset aliases are valid strings, e.g., 'D', 'W', 'M' etc. 

6006 

6007 Returns 

6008 ------- 

6009 pandas.Index 

6010 Shifted index. 

6011 

6012 See Also 

6013 -------- 

6014 Series.shift : Shift values of Series. 

6015 

6016 Notes 

6017 ----- 

6018 This method is only implemented for datetime-like index classes, 

6019 i.e., DatetimeIndex, PeriodIndex and TimedeltaIndex. 

6020 

6021 Examples 

6022 -------- 

6023 Put the first 5 month starts of 2011 into an index. 

6024 

6025 >>> month_starts = pd.date_range("1/1/2011", periods=5, freq="MS") 

6026 >>> month_starts 

6027 DatetimeIndex(['2011-01-01', '2011-02-01', '2011-03-01', '2011-04-01', 

6028 '2011-05-01'], 

6029 dtype='datetime64[us]', freq='MS') 

6030 

6031 Shift the index by 10 days. 

6032 

6033 >>> month_starts.shift(10, freq="D") 

6034 DatetimeIndex(['2011-01-11', '2011-02-11', '2011-03-11', '2011-04-11', 

6035 '2011-05-11'], 

6036 dtype='datetime64[us]', freq=None) 

6037 

6038 The default value of `freq` is the `freq` attribute of the index, 

6039 which is 'MS' (month start) in this example. 

6040 

6041 >>> month_starts.shift(10) 

6042 DatetimeIndex(['2011-11-01', '2011-12-01', '2012-01-01', '2012-02-01', 

6043 '2012-03-01'], 

6044 dtype='datetime64[us]', freq='MS') 

6045 """ 

6046 raise NotImplementedError( 

6047 f"This method is only implemented for DatetimeIndex, PeriodIndex and " 

6048 f"TimedeltaIndex; Got type {type(self).__name__}" 

6049 ) 

6050 

6051 def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]: 

6052 """ 

6053 Return the integer indices that would sort the index. 

6054 

6055 Parameters 

6056 ---------- 

6057 *args 

6058 Passed to `numpy.ndarray.argsort`. 

6059 **kwargs 

6060 Passed to `numpy.ndarray.argsort`. 

6061 

6062 Returns 

6063 ------- 

6064 np.ndarray[np.intp] 

6065 Integer indices that would sort the index if used as 

6066 an indexer. 

6067 

6068 See Also 

6069 -------- 

6070 numpy.argsort : Similar method for NumPy arrays. 

6071 Index.sort_values : Return sorted copy of Index. 

6072 

6073 Examples 

6074 -------- 

6075 >>> idx = pd.Index(["b", "a", "d", "c"]) 

6076 >>> idx 

6077 Index(['b', 'a', 'd', 'c'], dtype='str') 

6078 

6079 >>> order = idx.argsort() 

6080 >>> order 

6081 array([1, 0, 3, 2]) 

6082 

6083 >>> idx[order] 

6084 Index(['a', 'b', 'c', 'd'], dtype='str') 

6085 """ 

6086 # This works for either ndarray or EA, is overridden 

6087 # by RangeIndex, MultIIndex 

6088 return self._data.argsort(*args, **kwargs) 

6089 

6090 def _check_indexing_error(self, key) -> None: 

6091 if not is_scalar(key): 

6092 # if key is not a scalar, directly raise an error (the code below 

6093 # would convert to numpy arrays and raise later any way) - GH29926 

6094 raise InvalidIndexError(key) 

6095 

6096 @cache_readonly 

6097 def _should_fallback_to_positional(self) -> bool: 

6098 """ 

6099 Should an integer key be treated as positional? 

6100 """ 

6101 return self.inferred_type not in { 

6102 "integer", 

6103 "mixed-integer", 

6104 "floating", 

6105 "complex", 

6106 } 

6107 

6108 _index_shared_docs["get_indexer_non_unique"] = """ 

6109 Compute indexer and mask for new index given the current index. 

6110 

6111 The indexer should be then used as an input to ndarray.take to align the 

6112 current data to the new index. 

6113 

6114 Parameters 

6115 ---------- 

6116 target : %(target_klass)s 

6117 An iterable containing the values to be used for computing indexer. 

6118 

6119 Returns 

6120 ------- 

6121 indexer : np.ndarray[np.intp] 

6122 Integers from 0 to n - 1 indicating that the index at these 

6123 positions matches the corresponding target values. Missing values 

6124 in the target are marked by -1. 

6125 missing : np.ndarray[np.intp] 

6126 An indexer into the target of the values not found. 

6127 These correspond to the -1 in the indexer array. 

6128 

6129 See Also 

6130 -------- 

6131 Index.get_indexer : Computes indexer and mask for new index given 

6132 the current index. 

6133 Index.get_indexer_for : Returns an indexer even when non-unique. 

6134 

6135 Examples 

6136 -------- 

6137 >>> index = pd.Index(['c', 'b', 'a', 'b', 'b']) 

6138 >>> index.get_indexer_non_unique(['b', 'b']) 

6139 (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64)) 

6140 

6141 In the example below there are no matched values. 

6142 

6143 >>> index = pd.Index(['c', 'b', 'a', 'b', 'b']) 

6144 >>> index.get_indexer_non_unique(['q', 'r', 't']) 

6145 (array([-1, -1, -1]), array([0, 1, 2])) 

6146 

6147 For this reason, the returned ``indexer`` contains only integers equal to -1. 

6148 It demonstrates that there's no match between the index and the ``target`` 

6149 values at these positions. The mask [0, 1, 2] in the return value shows that 

6150 the first, second, and third elements are missing. 

6151 

6152 Notice that the return value is a tuple contains two items. In the example 

6153 below the first item is an array of locations in ``index``. The second 

6154 item is a mask shows that the first and third elements are missing. 

6155 

6156 >>> index = pd.Index(['c', 'b', 'a', 'b', 'b']) 

6157 >>> index.get_indexer_non_unique(['f', 'b', 's']) 

6158 (array([-1, 1, 3, 4, -1]), array([0, 2])) 

6159 """ 

6160 

6161 def get_indexer_non_unique( 

6162 self, target 

6163 ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

6164 """ 

6165 Compute indexer and mask for new index given the current index. 

6166 

6167 The indexer should be then used as an input to ndarray.take to align the 

6168 current data to the new index. 

6169 

6170 Parameters 

6171 ---------- 

6172 target : Index 

6173 An iterable containing the values to be used for computing indexer. 

6174 

6175 Returns 

6176 ------- 

6177 indexer : np.ndarray[np.intp] 

6178 Integers from 0 to n - 1 indicating that the index at these 

6179 positions matches the corresponding target values. Missing values 

6180 in the target are marked by -1. 

6181 missing : np.ndarray[np.intp] 

6182 An indexer into the target of the values not found. 

6183 These correspond to the -1 in the indexer array. 

6184 

6185 See Also 

6186 -------- 

6187 Index.get_indexer : Computes indexer and mask for new index given 

6188 the current index. 

6189 Index.get_indexer_for : Returns an indexer even when non-unique. 

6190 

6191 Examples 

6192 -------- 

6193 >>> index = pd.Index(["c", "b", "a", "b", "b"]) 

6194 >>> index.get_indexer_non_unique(["b", "b"]) 

6195 (array([1, 3, 4, 1, 3, 4]), array([], dtype=int64)) 

6196 

6197 In the example below there are no matched values. 

6198 

6199 >>> index = pd.Index(["c", "b", "a", "b", "b"]) 

6200 >>> index.get_indexer_non_unique(["q", "r", "t"]) 

6201 (array([-1, -1, -1]), array([0, 1, 2])) 

6202 

6203 For this reason, the returned ``indexer`` contains only integers equal to -1. 

6204 It demonstrates that there's no match between the index and the ``target`` 

6205 values at these positions. The mask [0, 1, 2] in the return value shows that 

6206 the first, second, and third elements are missing. 

6207 

6208 Notice that the return value is a tuple contains two items. In the example 

6209 below the first item is an array of locations in ``index``. The second 

6210 item is a mask shows that the first and third elements are missing. 

6211 

6212 >>> index = pd.Index(["c", "b", "a", "b", "b"]) 

6213 >>> index.get_indexer_non_unique(["f", "b", "s"]) 

6214 (array([-1, 1, 3, 4, -1]), array([0, 2])) 

6215 """ 

6216 target = self._maybe_cast_listlike_indexer(target) 

6217 

6218 if not self._should_compare(target) and not self._should_partial_index(target): 

6219 # _should_partial_index e.g. IntervalIndex with numeric scalars 

6220 # that can be matched to Interval scalars. 

6221 return self._get_indexer_non_comparable(target, method=None, unique=False) 

6222 

6223 pself, ptarget = self._maybe_downcast_for_indexing(target) 

6224 if pself is not self or ptarget is not target: 

6225 return pself.get_indexer_non_unique(ptarget) 

6226 

6227 if self.dtype != target.dtype: 

6228 # TODO: if object, could use infer_dtype to preempt costly 

6229 # conversion if still non-comparable? 

6230 dtype = self._find_common_type_compat(target) 

6231 

6232 this = self.astype(dtype, copy=False) 

6233 that = target.astype(dtype, copy=False) 

6234 return this.get_indexer_non_unique(that) 

6235 

6236 # TODO: get_indexer has fastpaths for both Categorical-self and 

6237 # Categorical-target. Can we do something similar here? 

6238 

6239 # Note: _maybe_downcast_for_indexing ensures we never get here 

6240 # with MultiIndex self and non-Multi target 

6241 if self._is_multi and target._is_multi: 

6242 engine = self._engine 

6243 # Item "IndexEngine" of "Union[IndexEngine, ExtensionEngine]" has 

6244 # no attribute "_extract_level_codes" 

6245 tgt_values = engine._extract_level_codes(target) # type: ignore[union-attr] 

6246 else: 

6247 tgt_values = target._get_engine_target() 

6248 

6249 indexer, missing = self._engine.get_indexer_non_unique(tgt_values) 

6250 return ensure_platform_int(indexer), ensure_platform_int(missing) 

6251 

6252 @final 

6253 def get_indexer_for(self, target) -> npt.NDArray[np.intp]: 

6254 """ 

6255 Guaranteed return of an indexer even when non-unique. 

6256 

6257 This dispatches to get_indexer or get_indexer_non_unique 

6258 as appropriate. 

6259 

6260 Parameters 

6261 ---------- 

6262 target : Index 

6263 An iterable containing the values to be used for computing indexer. 

6264 

6265 Returns 

6266 ------- 

6267 np.ndarray[np.intp] 

6268 List of indices. 

6269 

6270 See Also 

6271 -------- 

6272 Index.get_indexer : Computes indexer and mask for new index given 

6273 the current index. 

6274 Index.get_non_unique : Returns indexer and masks for new index given 

6275 the current index. 

6276 

6277 Examples 

6278 -------- 

6279 >>> idx = pd.Index([np.nan, "var1", np.nan]) 

6280 >>> idx.get_indexer_for([np.nan]) 

6281 array([0, 2]) 

6282 """ 

6283 if self._index_as_unique: 

6284 return self.get_indexer(target) 

6285 indexer, _ = self.get_indexer_non_unique(target) 

6286 return indexer 

6287 

6288 def _get_indexer_strict(self, key, axis_name: str_t) -> tuple[Index, np.ndarray]: 

6289 """ 

6290 Analogue to get_indexer that raises if any elements are missing. 

6291 """ 

6292 keyarr = key 

6293 if not isinstance(keyarr, Index): 

6294 keyarr = com.asarray_tuplesafe(keyarr) 

6295 

6296 if self._index_as_unique: 

6297 indexer = self.get_indexer_for(keyarr) 

6298 keyarr = self.reindex(keyarr)[0] 

6299 else: 

6300 keyarr, indexer, new_indexer = self._reindex_non_unique(keyarr) 

6301 

6302 self._raise_if_missing(keyarr, indexer, axis_name) 

6303 

6304 keyarr = self.take(indexer) 

6305 if isinstance(key, Index): 

6306 # GH 42790 - Preserve name from an Index 

6307 keyarr.name = key.name 

6308 if lib.is_np_dtype(keyarr.dtype, "mM") or isinstance( 

6309 keyarr.dtype, DatetimeTZDtype 

6310 ): 

6311 # DTI/TDI.take can infer a freq in some cases when we dont want one 

6312 if isinstance(key, list) or ( 

6313 isinstance(key, type(self)) 

6314 # "Index" has no attribute "freq" 

6315 and key.freq is None # type: ignore[attr-defined] 

6316 ): 

6317 # error: "Index" has no attribute "_with_freq"; maybe "_with_infer"? 

6318 keyarr = keyarr._with_freq(None) # type: ignore[attr-defined] 

6319 

6320 return keyarr, indexer 

6321 

6322 def _raise_if_missing(self, key, indexer, axis_name: str_t) -> None: 

6323 """ 

6324 Check that indexer can be used to return a result. 

6325 

6326 e.g. at least one element was found, 

6327 unless the list of keys was actually empty. 

6328 

6329 Parameters 

6330 ---------- 

6331 key : list-like 

6332 Targeted labels (only used to show correct error message). 

6333 indexer: array-like of booleans 

6334 Indices corresponding to the key, 

6335 (with -1 indicating not found). 

6336 axis_name : str 

6337 

6338 Raises 

6339 ------ 

6340 KeyError 

6341 If at least one key was requested but none was found. 

6342 """ 

6343 if len(key) == 0: 

6344 return 

6345 

6346 # Count missing values 

6347 missing_mask = indexer < 0 

6348 nmissing = missing_mask.sum() 

6349 

6350 if nmissing: 

6351 if nmissing == len(indexer): 

6352 raise KeyError(f"None of [{key}] are in the [{axis_name}]") 

6353 

6354 not_found = list(ensure_index(key)[missing_mask.nonzero()[0]].unique()) 

6355 raise KeyError(f"{not_found} not in index") 

6356 

6357 @overload 

6358 def _get_indexer_non_comparable( 

6359 self, target: Index, method, unique: Literal[True] = ... 

6360 ) -> npt.NDArray[np.intp]: ... 

6361 

6362 @overload 

6363 def _get_indexer_non_comparable( 

6364 self, target: Index, method, unique: Literal[False] 

6365 ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ... 

6366 

6367 @overload 

6368 def _get_indexer_non_comparable( 

6369 self, target: Index, method, unique: bool = True 

6370 ) -> npt.NDArray[np.intp] | tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ... 

6371 

6372 @final 

6373 def _get_indexer_non_comparable( 

6374 self, target: Index, method, unique: bool = True 

6375 ) -> npt.NDArray[np.intp] | tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: 

6376 """ 

6377 Called from get_indexer or get_indexer_non_unique when the target 

6378 is of a non-comparable dtype. 

6379 

6380 For get_indexer lookups with method=None, get_indexer is an _equality_ 

6381 check, so non-comparable dtypes mean we will always have no matches. 

6382 

6383 For get_indexer lookups with a method, get_indexer is an _inequality_ 

6384 check, so non-comparable dtypes mean we will always raise TypeError. 

6385 

6386 Parameters 

6387 ---------- 

6388 target : Index 

6389 method : str or None 

6390 unique : bool, default True 

6391 * True if called from get_indexer. 

6392 * False if called from get_indexer_non_unique. 

6393 

6394 Raises 

6395 ------ 

6396 TypeError 

6397 If doing an inequality check, i.e. method is not None. 

6398 """ 

6399 if method is not None: 

6400 other_dtype = _unpack_nested_dtype(target) 

6401 raise TypeError(f"Cannot compare dtypes {self.dtype} and {other_dtype}") 

6402 

6403 no_matches = -1 * np.ones(target.shape, dtype=np.intp) 

6404 if unique: 

6405 # This is for get_indexer 

6406 return no_matches 

6407 else: 

6408 # This is for get_indexer_non_unique 

6409 missing = np.arange(len(target), dtype=np.intp) 

6410 return no_matches, missing 

6411 

6412 @property 

6413 def _index_as_unique(self) -> bool: 

6414 """ 

6415 Whether we should treat this as unique for the sake of 

6416 get_indexer vs get_indexer_non_unique. 

6417 

6418 For IntervalIndex compat. 

6419 """ 

6420 return self.is_unique 

6421 

6422 _requires_unique_msg = "Reindexing only valid with uniquely valued Index objects" 

6423 

6424 @final 

6425 def _maybe_downcast_for_indexing(self, other: Index) -> tuple[Index, Index]: 

6426 """ 

6427 When dealing with an object-dtype Index and a non-object Index, see 

6428 if we can upcast the object-dtype one to improve performance. 

6429 """ 

6430 

6431 if isinstance(self, ABCDatetimeIndex) and isinstance(other, ABCDatetimeIndex): 

6432 if ( 

6433 self.tz is not None 

6434 and other.tz is not None 

6435 and not tz_compare(self.tz, other.tz) 

6436 ): 

6437 # standardize on UTC 

6438 return self.tz_convert("UTC"), other.tz_convert("UTC") 

6439 

6440 elif self.inferred_type == "date" and isinstance(other, ABCDatetimeIndex): 

6441 try: 

6442 return type(other)(self), other 

6443 except OutOfBoundsDatetime: 

6444 return self, other 

6445 elif self.inferred_type == "timedelta" and isinstance(other, ABCTimedeltaIndex): 

6446 # TODO: we dont have tests that get here 

6447 return type(other)(self), other 

6448 

6449 elif self.dtype.kind == "u" and other.dtype.kind == "i": 

6450 # GH#41873 

6451 if other.min() >= 0: 

6452 # lookup min as it may be cached 

6453 # TODO: may need itemsize check if we have non-64-bit Indexes 

6454 return self, other.astype(self.dtype) 

6455 

6456 elif self._is_multi and not other._is_multi: 

6457 try: 

6458 # "Type[Index]" has no attribute "from_tuples" 

6459 other = type(self).from_tuples(other) # type: ignore[attr-defined] 

6460 except (TypeError, ValueError): 

6461 # let's instead try with a straight Index 

6462 self = Index(self._values, copy=False) 

6463 

6464 if not is_object_dtype(self.dtype) and is_object_dtype(other.dtype): 

6465 # Reverse op so we dont need to re-implement on the subclasses 

6466 other, self = other._maybe_downcast_for_indexing(self) 

6467 

6468 return self, other 

6469 

6470 @final 

6471 def _find_common_type_compat(self, target) -> DtypeObj: 

6472 """ 

6473 Implementation of find_common_type that adjusts for Index-specific 

6474 special cases. 

6475 """ 

6476 target_dtype, _ = infer_dtype_from(target) 

6477 

6478 if isinstance(target, tuple): 

6479 # GH#54385 

6480 return np.dtype(object) 

6481 

6482 if using_string_dtype(): 

6483 # special case: if left or right is a zero-length RangeIndex or 

6484 # Index[object], those can be created by the default empty constructors 

6485 # -> for that case ignore this dtype and always return the other 

6486 # (https://github.com/pandas-dev/pandas/pull/60797) 

6487 from pandas.core.indexes.range import RangeIndex 

6488 

6489 if len(self) == 0 and ( 

6490 isinstance(self, RangeIndex) or self.dtype == np.object_ 

6491 ): 

6492 return target_dtype 

6493 if ( 

6494 isinstance(target, Index) 

6495 and len(target) == 0 

6496 and (isinstance(target, RangeIndex) or target_dtype == np.object_) 

6497 ): 

6498 return self.dtype 

6499 

6500 # special case: if one dtype is uint64 and the other a signed int, return object 

6501 # See https://github.com/pandas-dev/pandas/issues/26778 for discussion 

6502 # Now it's: 

6503 # * float | [u]int -> float 

6504 # * uint64 | signed int -> object 

6505 # We may change union(float | [u]int) to go to object. 

6506 if self.dtype == "uint64" or target_dtype == "uint64": 

6507 if is_signed_integer_dtype(self.dtype) or is_signed_integer_dtype( 

6508 target_dtype 

6509 ): 

6510 return _dtype_obj 

6511 

6512 dtype = find_result_type(self.dtype, target) 

6513 dtype = common_dtype_categorical_compat([self, target], dtype) 

6514 return dtype 

6515 

6516 @final 

6517 def _should_compare(self, other: Index) -> bool: 

6518 """ 

6519 Check if `self == other` can ever have non-False entries. 

6520 """ 

6521 

6522 # NB: we use inferred_type rather than is_bool_dtype to catch 

6523 # object_dtype_of_bool and categorical[object_dtype_of_bool] cases 

6524 if ( 

6525 other.inferred_type == "boolean" and is_any_real_numeric_dtype(self.dtype) 

6526 ) or ( 

6527 self.inferred_type == "boolean" and is_any_real_numeric_dtype(other.dtype) 

6528 ): 

6529 # GH#16877 Treat boolean labels passed to a numeric index as not 

6530 # found. Without this fix False and True would be treated as 0 and 1 

6531 # respectively. 

6532 return False 

6533 

6534 dtype = _unpack_nested_dtype(other) 

6535 return ( 

6536 self._is_comparable_dtype(dtype) 

6537 or is_object_dtype(dtype) 

6538 or is_string_dtype(dtype) 

6539 ) 

6540 

6541 def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: 

6542 """ 

6543 Can we compare values of the given dtype to our own? 

6544 """ 

6545 if self.dtype.kind == "b": 

6546 return dtype.kind == "b" 

6547 elif is_numeric_dtype(self.dtype): 

6548 return is_numeric_dtype(dtype) 

6549 # TODO: this was written assuming we only get here with object-dtype, 

6550 # which is no longer correct. Can we specialize for EA? 

6551 return True 

6552 

6553 @final 

6554 def groupby(self, values) -> PrettyDict[Hashable, Index]: 

6555 """ 

6556 Group the index labels by a given array of values. 

6557 

6558 Parameters 

6559 ---------- 

6560 values : array 

6561 Values used to determine the groups. 

6562 

6563 Returns 

6564 ------- 

6565 dict 

6566 {group name -> group labels} 

6567 """ 

6568 # TODO: if we are a MultiIndex, we can do better 

6569 # that converting to tuples 

6570 if isinstance(values, ABCMultiIndex): 

6571 values = values._values 

6572 values = Categorical(values) 

6573 result = values._reverse_indexer() 

6574 

6575 # map to the label 

6576 result = {k: self.take(v) for k, v in result.items()} 

6577 

6578 return PrettyDict(result) 

6579 

6580 def map(self, mapper, na_action: Literal["ignore"] | None = None): 

6581 """ 

6582 Map values using an input mapping or function. 

6583 

6584 Parameters 

6585 ---------- 

6586 mapper : function, dict, or Series 

6587 Mapping correspondence. 

6588 na_action : {None, 'ignore'} 

6589 If 'ignore', propagate NA values, without passing them to the 

6590 mapping correspondence. 

6591 

6592 Returns 

6593 ------- 

6594 Union[Index, MultiIndex] 

6595 The output of the mapping function applied to the index. 

6596 If the function returns a tuple with more than one element 

6597 a MultiIndex will be returned. 

6598 

6599 See Also 

6600 -------- 

6601 Index.where : Replace values where the condition is False. 

6602 

6603 Examples 

6604 -------- 

6605 >>> idx = pd.Index([1, 2, 3]) 

6606 >>> idx.map({1: "a", 2: "b", 3: "c"}) 

6607 Index(['a', 'b', 'c'], dtype='str') 

6608 

6609 Using `map` with a function: 

6610 

6611 >>> idx = pd.Index([1, 2, 3]) 

6612 >>> idx.map("I am a {}".format) 

6613 Index(['I am a 1', 'I am a 2', 'I am a 3'], dtype='str') 

6614 

6615 >>> idx = pd.Index(["a", "b", "c"]) 

6616 >>> idx.map(lambda x: x.upper()) 

6617 Index(['A', 'B', 'C'], dtype='str') 

6618 """ 

6619 from pandas.core.indexes.multi import MultiIndex 

6620 

6621 new_values = self._map_values(mapper, na_action=na_action) 

6622 

6623 # we can return a MultiIndex 

6624 if new_values.size and isinstance(new_values[0], tuple): 

6625 if isinstance(self, MultiIndex): 

6626 names = self.names 

6627 elif self.name: 

6628 names = [self.name] * len(new_values[0]) 

6629 else: 

6630 names = None 

6631 return MultiIndex.from_tuples(new_values, names=names) 

6632 

6633 dtype = None 

6634 if not new_values.size: 

6635 # empty 

6636 dtype = self.dtype 

6637 elif isinstance(new_values, Categorical): 

6638 # cast_pointwise_result is unnecessary 

6639 dtype = new_values.dtype 

6640 else: 

6641 if isinstance(self, MultiIndex): 

6642 arr = self[:0].to_flat_index().array 

6643 else: 

6644 arr = self[:0].array 

6645 # e.g. if we are floating and new_values is all ints, then we 

6646 # don't want to cast back to floating. But if we are UInt64 

6647 # and new_values is all ints, we want to try. 

6648 new_values = arr._cast_pointwise_result(new_values) 

6649 dtype = new_values.dtype 

6650 return Index(new_values, dtype=dtype, copy=False, name=self.name) 

6651 

6652 # TODO: De-duplicate with map, xref GH#32349 

6653 @final 

6654 def _transform_index(self, func, *, level=None) -> Index: 

6655 """ 

6656 Apply function to all values found in index. 

6657 

6658 This includes transforming multiindex entries separately. 

6659 Only apply function to one level of the MultiIndex if level is specified. 

6660 """ 

6661 if isinstance(self, ABCMultiIndex): 

6662 values = [ 

6663 ( 

6664 self.get_level_values(i).map(func) 

6665 if i == level or level is None 

6666 else self.get_level_values(i) 

6667 ) 

6668 for i in range(self.nlevels) 

6669 ] 

6670 return type(self).from_arrays(values) 

6671 else: 

6672 items = [func(x) for x in self] 

6673 return Index(items, name=self.name, tupleize_cols=False) 

6674 

6675 def isin(self, values, level: str_t | int | None = None) -> npt.NDArray[np.bool_]: 

6676 """ 

6677 Return a boolean array where the index values are in `values`. 

6678 

6679 Compute boolean array of whether each index value is found in the 

6680 passed set of values. The length of the returned boolean array matches 

6681 the length of the index. 

6682 

6683 Parameters 

6684 ---------- 

6685 values : set or list-like 

6686 Sought values. 

6687 level : str or int, optional 

6688 Name or position of the index level to use (if the index is a 

6689 `MultiIndex`). 

6690 

6691 Returns 

6692 ------- 

6693 np.ndarray[bool] 

6694 NumPy array of boolean values. 

6695 

6696 See Also 

6697 -------- 

6698 Series.isin : Same for Series. 

6699 DataFrame.isin : Same method for DataFrames. 

6700 

6701 Notes 

6702 ----- 

6703 In the case of `MultiIndex` you must either specify `values` as a 

6704 list-like object containing tuples that are the same length as the 

6705 number of levels, or specify `level`. Otherwise it will raise a 

6706 ``ValueError``. 

6707 

6708 If `level` is specified: 

6709 

6710 - if it is the name of one *and only one* index level, use that level; 

6711 - otherwise it should be a number indicating level position. 

6712 

6713 Examples 

6714 -------- 

6715 >>> idx = pd.Index([1, 2, 3]) 

6716 >>> idx 

6717 Index([1, 2, 3], dtype='int64') 

6718 

6719 Check whether each index value in a list of values. 

6720 

6721 >>> idx.isin([1, 4]) 

6722 array([ True, False, False]) 

6723 

6724 >>> midx = pd.MultiIndex.from_arrays( 

6725 ... [[1, 2, 3], ["red", "blue", "green"]], names=["number", "color"] 

6726 ... ) 

6727 >>> midx 

6728 MultiIndex([(1, 'red'), 

6729 (2, 'blue'), 

6730 (3, 'green')], 

6731 names=['number', 'color']) 

6732 

6733 Check whether the strings in the 'color' level of the MultiIndex 

6734 are in a list of colors. 

6735 

6736 >>> midx.isin(["red", "orange", "yellow"], level="color") 

6737 array([ True, False, False]) 

6738 

6739 To check across the levels of a MultiIndex, pass a list of tuples: 

6740 

6741 >>> midx.isin([(1, "red"), (3, "red")]) 

6742 array([ True, False, False]) 

6743 """ 

6744 if level is not None: 

6745 self._validate_index_level(level) 

6746 return algos.isin(self._values, values) 

6747 

6748 def _get_string_slice(self, key: str_t): 

6749 # this is for partial string indexing, 

6750 # overridden in DatetimeIndex, TimedeltaIndex and PeriodIndex 

6751 raise NotImplementedError 

6752 

6753 def slice_indexer( 

6754 self, 

6755 start: Hashable | None = None, 

6756 end: Hashable | None = None, 

6757 step: int | None = None, 

6758 ) -> slice: 

6759 """ 

6760 Compute the slice indexer for input labels and step. 

6761 

6762 Index needs to be ordered and unique. 

6763 

6764 Parameters 

6765 ---------- 

6766 start : label, default None 

6767 If None, defaults to the beginning. 

6768 end : label, default None 

6769 If None, defaults to the end. 

6770 step : int, default None 

6771 If None, defaults to 1. 

6772 

6773 Returns 

6774 ------- 

6775 slice 

6776 A slice object. 

6777 

6778 Raises 

6779 ------ 

6780 KeyError : If key does not exist, or key is not unique and index is 

6781 not ordered. 

6782 

6783 See Also 

6784 -------- 

6785 Index.slice_locs : Computes slice locations for input labels. 

6786 Index.get_slice_bound : Retrieves slice bound that corresponds to given label. 

6787 

6788 Notes 

6789 ----- 

6790 This function assumes that the data is sorted, so use at your own peril. 

6791 

6792 Examples 

6793 -------- 

6794 This is a method on all index types. For example you can do: 

6795 

6796 >>> idx = pd.Index(list("abcd")) 

6797 >>> idx.slice_indexer(start="b", end="c") 

6798 slice(1, 3, None) 

6799 

6800 >>> idx = pd.MultiIndex.from_arrays([list("abcd"), list("efgh")]) 

6801 >>> idx.slice_indexer(start="b", end=("c", "g")) 

6802 slice(1, 3, None) 

6803 """ 

6804 start_slice, end_slice = self.slice_locs(start, end, step=step) 

6805 

6806 # return a slice 

6807 if not is_scalar(start_slice): 

6808 raise AssertionError("Start slice bound is non-scalar") 

6809 if not is_scalar(end_slice): 

6810 raise AssertionError("End slice bound is non-scalar") 

6811 

6812 return slice(start_slice, end_slice, step) 

6813 

6814 def _maybe_cast_indexer(self, key): 

6815 """ 

6816 If we have a float key and are not a floating index, then try to cast 

6817 to an int if equivalent. 

6818 """ 

6819 if ( 

6820 is_float(key) 

6821 and np.isnan(key) 

6822 and isinstance(self.dtype, FloatingDtype) 

6823 and is_nan_na() 

6824 ): 

6825 # TODO: better place to do this? 

6826 key = self.dtype.na_value 

6827 return key 

6828 

6829 def _maybe_cast_listlike_indexer(self, target) -> Index: 

6830 """ 

6831 Analogue to maybe_cast_indexer for get_indexer instead of get_loc. 

6832 """ 

6833 target_index = ensure_index(target) 

6834 if ( 

6835 not hasattr(target, "dtype") 

6836 and self.dtype == object 

6837 and target_index.dtype == "string" 

6838 ): 

6839 # If we started with a list-like, avoid inference to string dtype if self 

6840 # is object dtype (coercing to string dtype will alter the missing values) 

6841 target_index = Index(target, dtype=self.dtype) 

6842 elif ( 

6843 not hasattr(target, "dtype") 

6844 and isinstance(self.dtype, StringDtype) 

6845 and self.dtype.na_value is np.nan 

6846 and using_string_dtype() 

6847 ): 

6848 # Fill missing values to ensure consistent missing value representation 

6849 target_index = target_index.fillna(np.nan) 

6850 return target_index 

6851 

6852 @final 

6853 def _validate_indexer( 

6854 self, 

6855 form: Literal["positional", "slice"], 

6856 key, 

6857 kind: Literal["getitem", "iloc"], 

6858 ) -> None: 

6859 """ 

6860 If we are positional indexer, validate that we have appropriate 

6861 typed bounds must be an integer. 

6862 """ 

6863 if not lib.is_int_or_none(key): 

6864 self._raise_invalid_indexer(form, key) 

6865 

6866 def _maybe_cast_slice_bound(self, label, side: str_t): 

6867 """ 

6868 This function should be overloaded in subclasses that allow non-trivial 

6869 casting on label-slice bounds, e.g. datetime-like indices allowing 

6870 strings containing formatted datetimes. 

6871 

6872 Parameters 

6873 ---------- 

6874 label : object 

6875 side : {'left', 'right'} 

6876 

6877 Returns 

6878 ------- 

6879 label : object 

6880 

6881 Notes 

6882 ----- 

6883 Value of `side` parameter should be validated in caller. 

6884 """ 

6885 

6886 # We are a plain index here (sub-class override this method if they 

6887 # wish to have special treatment for floats/ints, e.g. datetimelike Indexes 

6888 

6889 if is_numeric_dtype(self.dtype): 

6890 return self._maybe_cast_indexer(label) 

6891 

6892 # reject them, if index does not contain label 

6893 if (is_float(label) or is_integer(label)) and label not in self: 

6894 self._raise_invalid_indexer("slice", label) 

6895 

6896 return label 

6897 

6898 def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"): 

6899 if self.is_monotonic_increasing: 

6900 return self.searchsorted(label, side=side) 

6901 elif self.is_monotonic_decreasing: 

6902 # np.searchsorted expects ascending sort order, have to reverse 

6903 # everything for it to work (element ordering, search side and 

6904 # resulting value). 

6905 pos = self[::-1].searchsorted( 

6906 label, side="right" if side == "left" else "left" 

6907 ) 

6908 return maybe_unbox_numpy_scalar(len(self) - pos) 

6909 

6910 raise ValueError("index must be monotonic increasing or decreasing") 

6911 

6912 def get_slice_bound(self, label, side: Literal["left", "right"]) -> int: 

6913 """ 

6914 Calculate slice bound that corresponds to given label. 

6915 

6916 Returns leftmost (one-past-the-rightmost if ``side=='right'``) position 

6917 of given label. 

6918 

6919 Parameters 

6920 ---------- 

6921 label : object 

6922 The label for which to calculate the slice bound. 

6923 side : {'left', 'right'} 

6924 if 'left' return leftmost position of given label. 

6925 if 'right' return one-past-the-rightmost position of given label. 

6926 

6927 Returns 

6928 ------- 

6929 int 

6930 Index of label. 

6931 

6932 See Also 

6933 -------- 

6934 Index.get_loc : Get integer location, slice or boolean mask for requested 

6935 label. 

6936 

6937 Examples 

6938 -------- 

6939 >>> idx = pd.RangeIndex(5) 

6940 >>> idx.get_slice_bound(3, "left") 

6941 3 

6942 

6943 >>> idx.get_slice_bound(3, "right") 

6944 4 

6945 

6946 If ``label`` is non-unique in the index, an error will be raised. 

6947 

6948 >>> idx_duplicate = pd.Index(["a", "b", "a", "c", "d"]) 

6949 >>> idx_duplicate.get_slice_bound("a", "left") 

6950 Traceback (most recent call last): 

6951 KeyError: Cannot get left slice bound for non-unique label: 'a' 

6952 """ 

6953 

6954 if side not in ("left", "right"): 

6955 raise ValueError( 

6956 "Invalid value for side kwarg, must be either " 

6957 f"'left' or 'right': {side}" 

6958 ) 

6959 

6960 original_label = label 

6961 

6962 # For datetime indices label may be a string that has to be converted 

6963 # to datetime boundary according to its resolution. 

6964 label = self._maybe_cast_slice_bound(label, side) 

6965 

6966 # we need to look up the label 

6967 try: 

6968 slc = self.get_loc(label) 

6969 except KeyError: 

6970 try: 

6971 return self._searchsorted_monotonic(label, side) 

6972 except ValueError: 

6973 raise KeyError( 

6974 f"Cannot get {side} slice bound for non-monotonic index " 

6975 f"with a missing label {original_label!r}. " 

6976 "Either sort the index or specify an existing label." 

6977 ) from None 

6978 

6979 if isinstance(slc, np.ndarray): 

6980 # get_loc may return a boolean array, which 

6981 # is OK as long as they are representable by a slice. 

6982 assert is_bool_dtype(slc.dtype) 

6983 slc = lib.maybe_booleans_to_slice(slc.view("u1")) 

6984 if isinstance(slc, np.ndarray): 

6985 raise KeyError( 

6986 f"Cannot get {side} slice bound for non-unique " 

6987 f"label: {original_label!r}" 

6988 ) 

6989 

6990 if isinstance(slc, slice): 

6991 if side == "left": 

6992 return slc.start 

6993 else: 

6994 return slc.stop 

6995 elif side == "right": 

6996 return slc + 1 

6997 else: 

6998 return slc 

6999 

7000 def slice_locs( 

7001 self, 

7002 start: SliceType = None, 

7003 end: SliceType = None, 

7004 step: int | None = None, 

7005 ) -> tuple[int, int]: 

7006 """ 

7007 Compute slice locations for input labels. 

7008 

7009 Parameters 

7010 ---------- 

7011 start : label, default None 

7012 If None, defaults to the beginning. 

7013 end : label, default None 

7014 If None, defaults to the end. 

7015 step : int, defaults None 

7016 If None, defaults to 1. 

7017 

7018 Returns 

7019 ------- 

7020 tuple[int, int] 

7021 Returns a tuple of two integers representing the slice locations for the 

7022 input labels within the index. 

7023 

7024 See Also 

7025 -------- 

7026 Index.get_loc : Get location for a single label. 

7027 

7028 Notes 

7029 ----- 

7030 This method only works if the index is monotonic or unique. 

7031 

7032 Examples 

7033 -------- 

7034 >>> idx = pd.Index(list("abcd")) 

7035 >>> idx.slice_locs(start="b", end="c") 

7036 (1, 3) 

7037 

7038 >>> idx = pd.Index(list("bcde")) 

7039 >>> idx.slice_locs(start="a", end="c") 

7040 (0, 2) 

7041 """ 

7042 inc = step is None or step >= 0 

7043 

7044 if not inc: 

7045 # If it's a reverse slice, temporarily swap bounds. 

7046 start, end = end, start 

7047 

7048 # GH 16785: If start and end happen to be date strings with UTC offsets 

7049 # attempt to parse and check that the offsets are the same 

7050 if isinstance(start, (str, datetime)) and isinstance(end, (str, datetime)): 

7051 try: 

7052 ts_start = Timestamp(start) 

7053 ts_end = Timestamp(end) 

7054 except (ValueError, TypeError): 

7055 pass 

7056 else: 

7057 if not tz_compare(ts_start.tzinfo, ts_end.tzinfo): 

7058 raise ValueError("Both dates must have the same UTC offset") 

7059 

7060 start_slice = None 

7061 if start is not None: 

7062 start_slice = self.get_slice_bound(start, "left") 

7063 if start_slice is None: 

7064 start_slice = 0 

7065 

7066 end_slice = None 

7067 if end is not None: 

7068 end_slice = self.get_slice_bound(end, "right") 

7069 if end_slice is None: 

7070 end_slice = len(self) 

7071 

7072 if not inc: 

7073 # Bounds at this moment are swapped, swap them back and shift by 1. 

7074 # 

7075 # slice_locs('B', 'A', step=-1): s='B', e='A' 

7076 # 

7077 # s='A' e='B' 

7078 # AFTER SWAP: | | 

7079 # v ------------------> V 

7080 # ----------------------------------- 

7081 # | | |A|A|A|A| | | | | |B|B| | | | | 

7082 # ----------------------------------- 

7083 # ^ <------------------ ^ 

7084 # SHOULD BE: | | 

7085 # end=s-1 start=e-1 

7086 # 

7087 end_slice, start_slice = start_slice - 1, end_slice - 1 

7088 

7089 # i == -1 triggers ``len(self) + i`` selection that points to the 

7090 # last element, not before-the-first one, subtracting len(self) 

7091 # compensates that. 

7092 if end_slice == -1: 

7093 end_slice -= len(self) 

7094 if start_slice == -1: 

7095 start_slice -= len(self) 

7096 

7097 start_slice = maybe_unbox_numpy_scalar(start_slice) 

7098 end_slice = maybe_unbox_numpy_scalar(end_slice) 

7099 return start_slice, end_slice 

7100 

7101 def delete( 

7102 self, loc: int | np.integer | list[int] | npt.NDArray[np.integer] 

7103 ) -> Self: 

7104 """ 

7105 Make new Index with passed location(-s) deleted. 

7106 

7107 Parameters 

7108 ---------- 

7109 loc : int or list of int 

7110 Location of item(-s) which will be deleted. 

7111 Use a list of locations to delete more than one value at the same time. 

7112 

7113 Returns 

7114 ------- 

7115 Index 

7116 Will be same type as self, except for RangeIndex. 

7117 

7118 See Also 

7119 -------- 

7120 numpy.delete : Delete any rows and column from NumPy array (ndarray). 

7121 

7122 Examples 

7123 -------- 

7124 >>> idx = pd.Index(["a", "b", "c"]) 

7125 >>> idx.delete(1) 

7126 Index(['a', 'c'], dtype='str') 

7127 

7128 >>> idx = pd.Index(["a", "b", "c"]) 

7129 >>> idx.delete([0, 2]) 

7130 Index(['b'], dtype='str') 

7131 """ 

7132 values = self._values 

7133 res_values: ArrayLike 

7134 if isinstance(values, np.ndarray): 

7135 # TODO(__array_function__): special casing will be unnecessary 

7136 res_values = np.delete(values, loc) 

7137 else: 

7138 res_values = values.delete(loc) 

7139 

7140 # _constructor so RangeIndex-> Index with an int64 dtype 

7141 return self._constructor._simple_new(res_values, name=self.name) 

7142 

7143 def insert(self, loc: int, item) -> Index: 

7144 """ 

7145 Make new Index inserting new item at location. 

7146 

7147 Follows Python numpy.insert semantics for negative values. 

7148 

7149 Parameters 

7150 ---------- 

7151 loc : int 

7152 The integer location where the new item will be inserted. 

7153 item : object 

7154 The new item to be inserted into the Index. 

7155 

7156 Returns 

7157 ------- 

7158 Index 

7159 Returns a new Index object resulting from inserting the specified item at 

7160 the specified location within the original Index. 

7161 

7162 See Also 

7163 -------- 

7164 Index.append : Append a collection of Indexes together. 

7165 

7166 Examples 

7167 -------- 

7168 >>> idx = pd.Index(["a", "b", "c"]) 

7169 >>> idx.insert(1, "x") 

7170 Index(['a', 'x', 'b', 'c'], dtype='str') 

7171 """ 

7172 item = lib.item_from_zerodim(item) 

7173 if is_valid_na_for_dtype(item, self.dtype) and self.dtype != object: 

7174 item = self._na_value 

7175 

7176 arr = self._values 

7177 

7178 if using_string_dtype() and len(self) == 0 and self.dtype == np.object_: 

7179 # special case: if we are an empty object-dtype Index, also 

7180 # take into account the inserted item for the resulting dtype 

7181 # (https://github.com/pandas-dev/pandas/pull/60797) 

7182 dtype = self._find_common_type_compat(item) 

7183 if dtype != self.dtype: 

7184 return self.astype(dtype).insert(loc, item) 

7185 

7186 try: 

7187 if isinstance(arr, ExtensionArray): 

7188 res_values = arr.insert(loc, item) 

7189 return type(self)._simple_new(res_values, name=self.name) 

7190 else: 

7191 item = self._validate_fill_value(item) 

7192 except (TypeError, ValueError, LossySetitemError): 

7193 # e.g. trying to insert an integer into a DatetimeIndex 

7194 # We cannot keep the same dtype, so cast to the (often object) 

7195 # minimal shared dtype before doing the insert. 

7196 dtype = self._find_common_type_compat(item) 

7197 if dtype == self.dtype: 

7198 # EA's might run into recursion errors if loc is invalid 

7199 raise 

7200 return self.astype(dtype).insert(loc, item) 

7201 

7202 if arr.dtype != object or not isinstance( 

7203 item, (tuple, np.datetime64, np.timedelta64) 

7204 ): 

7205 # with object-dtype we need to worry about numpy incorrectly casting 

7206 # dt64/td64 to integer, also about treating tuples as sequences 

7207 # special-casing dt64/td64 https://github.com/numpy/numpy/issues/12550 

7208 casted = arr.dtype.type(item) 

7209 new_values = np.insert(arr, loc, casted) 

7210 

7211 else: 

7212 # error: No overload variant of "insert" matches argument types 

7213 # "ndarray[Any, Any]", "int", "None" 

7214 new_values = np.insert(arr, loc, None) # type: ignore[call-overload] 

7215 loc = loc if loc >= 0 else loc - 1 

7216 new_values[loc] = item 

7217 

7218 # GH#51363 stopped doing dtype inference here 

7219 out = Index(new_values, dtype=new_values.dtype, name=self.name, copy=False) 

7220 return out 

7221 

7222 def drop( 

7223 self, 

7224 labels: Index | np.ndarray | Iterable[Hashable], 

7225 errors: IgnoreRaise = "raise", 

7226 ) -> Index: 

7227 """ 

7228 Make new Index with passed list of labels deleted. 

7229 

7230 Parameters 

7231 ---------- 

7232 labels : array-like or scalar 

7233 Array-like object or a scalar value, representing the labels to be removed 

7234 from the Index. 

7235 errors : {'ignore', 'raise'}, default 'raise' 

7236 If 'ignore', suppress error and existing labels are dropped. 

7237 

7238 Returns 

7239 ------- 

7240 Index 

7241 Will be same type as self, except for RangeIndex. 

7242 

7243 Raises 

7244 ------ 

7245 KeyError 

7246 If not all of the labels are found in the selected axis 

7247 

7248 See Also 

7249 -------- 

7250 Index.dropna : Return Index without NA/NaN values. 

7251 Index.drop_duplicates : Return Index with duplicate values removed. 

7252 

7253 Examples 

7254 -------- 

7255 >>> idx = pd.Index(["a", "b", "c"]) 

7256 >>> idx.drop(["a"]) 

7257 Index(['b', 'c'], dtype='str') 

7258 """ 

7259 if not isinstance(labels, Index): 

7260 # avoid materializing e.g. RangeIndex 

7261 arr_dtype = "object" if self.dtype == "object" else None 

7262 labels = com.index_labels_to_array(labels, dtype=arr_dtype) 

7263 

7264 indexer = self.get_indexer_for(labels) 

7265 mask = indexer == -1 

7266 if mask.any(): 

7267 if errors != "ignore": 

7268 raise KeyError(f"{labels[mask].tolist()} not found in axis") 

7269 indexer = indexer[~mask] 

7270 return self.delete(indexer) 

7271 

7272 @final 

7273 def infer_objects(self, copy: bool = True) -> Index: 

7274 """ 

7275 If we have an object dtype, try to infer a non-object dtype. 

7276 

7277 Parameters 

7278 ---------- 

7279 copy : bool, default True 

7280 Whether to make a copy in cases where no inference occurs. 

7281 

7282 Returns 

7283 ------- 

7284 Index 

7285 An Index with a new dtype if the dtype was inferred 

7286 or a shallow copy if the dtype could not be inferred. 

7287 

7288 See Also 

7289 -------- 

7290 Index.inferred_type: Return a string of the type inferred from the values. 

7291 

7292 Examples 

7293 -------- 

7294 >>> pd.Index(["a", 1]).infer_objects() 

7295 Index(['a', 1], dtype='object') 

7296 >>> pd.Index([1, 2], dtype="object").infer_objects() 

7297 Index([1, 2], dtype='int64') 

7298 """ 

7299 if self._is_multi: 

7300 raise NotImplementedError( 

7301 "infer_objects is not implemented for MultiIndex. " 

7302 "Use index.to_frame().infer_objects() instead." 

7303 ) 

7304 if self.dtype != object: 

7305 return self.copy() if copy else self 

7306 

7307 values = self._values 

7308 values = cast("npt.NDArray[np.object_]", values) 

7309 res_values = lib.maybe_convert_objects( 

7310 values, 

7311 convert_non_numeric=True, 

7312 ) 

7313 if copy and res_values is values: 

7314 return self.copy() 

7315 result = Index(res_values, name=self.name, copy=False) 

7316 if not copy and res_values is values and self._references is not None: 

7317 result._references = self._references 

7318 result._references.add_index_reference(result) 

7319 return result 

7320 

7321 @final 

7322 def diff(self, periods: int = 1) -> Index: 

7323 """ 

7324 Computes the difference between consecutive values in the Index object. 

7325 

7326 If periods is greater than 1, computes the difference between values that 

7327 are `periods` number of positions apart. 

7328 

7329 Parameters 

7330 ---------- 

7331 periods : int, optional 

7332 The number of positions between the current and previous 

7333 value to compute the difference with. Default is 1. 

7334 

7335 Returns 

7336 ------- 

7337 Index 

7338 A new Index object with the computed differences. 

7339 

7340 Examples 

7341 -------- 

7342 >>> import pandas as pd 

7343 >>> idx = pd.Index([10, 20, 30, 40, 50]) 

7344 >>> idx.diff() 

7345 Index([nan, 10.0, 10.0, 10.0, 10.0], dtype='float64') 

7346 

7347 """ 

7348 return Index(self.to_series().diff(periods)) 

7349 

7350 def round(self, decimals: int = 0) -> Self: 

7351 """ 

7352 Round each value in the Index to the given number of decimals. 

7353 

7354 Parameters 

7355 ---------- 

7356 decimals : int, optional 

7357 Number of decimal places to round to. If decimals is negative, 

7358 it specifies the number of positions to the left of the decimal point. 

7359 

7360 Returns 

7361 ------- 

7362 Index 

7363 A new Index with the rounded values. 

7364 

7365 Examples 

7366 -------- 

7367 >>> import pandas as pd 

7368 >>> idx = pd.Index([10.1234, 20.5678, 30.9123, 40.4567, 50.7890]) 

7369 >>> idx.round(decimals=2) 

7370 Index([10.12, 20.57, 30.91, 40.46, 50.79], dtype='float64') 

7371 

7372 """ 

7373 return self._constructor(self.to_series().round(decimals)) 

7374 

7375 # -------------------------------------------------------------------- 

7376 # Generated Arithmetic, Comparison, and Unary Methods 

7377 

7378 def _cmp_method(self, other, op): 

7379 """ 

7380 Wrapper used to dispatch comparison operations. 

7381 """ 

7382 if self.is_(other): 

7383 # fastpath 

7384 if op in {operator.eq, operator.le, operator.ge}: 

7385 arr = np.ones(len(self), dtype=bool) 

7386 if self._can_hold_na and not isinstance(self, ABCMultiIndex): 

7387 # TODO: should set MultiIndex._can_hold_na = False? 

7388 arr[self.isna()] = False 

7389 return arr 

7390 elif op is operator.ne: 

7391 arr = np.zeros(len(self), dtype=bool) 

7392 if self._can_hold_na and not isinstance(self, ABCMultiIndex): 

7393 arr[self.isna()] = True 

7394 return arr 

7395 

7396 if isinstance(other, (np.ndarray, Index, ABCSeries, ExtensionArray)) and len( 

7397 self 

7398 ) != len(other): 

7399 raise ValueError("Lengths must match to compare") 

7400 

7401 if not isinstance(other, ABCMultiIndex): 

7402 other = extract_array(other, extract_numpy=True) 

7403 else: 

7404 other = np.asarray(other) 

7405 

7406 result = ops.comparison_op(self._values, other, op) 

7407 

7408 return result 

7409 

7410 @final 

7411 def _logical_method(self, other, op): 

7412 res_name = ops.get_op_result_name(self, other) 

7413 

7414 lvalues = self._values 

7415 rvalues = extract_array(other, extract_numpy=True, extract_range=True) 

7416 

7417 res_values = ops.logical_op(lvalues, rvalues, op) 

7418 return self._construct_result(res_values, name=res_name, other=other) 

7419 

7420 @final 

7421 def _construct_result(self, result, name, other): 

7422 if isinstance(result, tuple): 

7423 return ( 

7424 Index(result[0], name=name, dtype=result[0].dtype, copy=False), 

7425 Index(result[1], name=name, dtype=result[1].dtype, copy=False), 

7426 ) 

7427 return Index(result, name=name, dtype=result.dtype, copy=False) 

7428 

7429 def _arith_method(self, other, op): 

7430 if ( 

7431 isinstance(other, Index) 

7432 and is_object_dtype(other.dtype) 

7433 and type(other) is not Index 

7434 ): 

7435 # We return NotImplemented for object-dtype index *subclasses* so they have 

7436 # a chance to implement ops before we unwrap them. 

7437 # See https://github.com/pandas-dev/pandas/issues/31109 

7438 return NotImplemented 

7439 

7440 return super()._arith_method(other, op) 

7441 

7442 @final 

7443 def _unary_method(self, op): 

7444 result = op(self._values) 

7445 return Index(result, name=self.name, copy=False) 

7446 

7447 def __abs__(self) -> Index: 

7448 return self._unary_method(operator.abs) 

7449 

7450 def __neg__(self) -> Index: 

7451 return self._unary_method(operator.neg) 

7452 

7453 def __pos__(self) -> Index: 

7454 return self._unary_method(operator.pos) 

7455 

7456 def __invert__(self) -> Index: 

7457 # GH#8875 

7458 return self._unary_method(operator.inv) 

7459 

7460 # -------------------------------------------------------------------- 

7461 # Reductions 

7462 

7463 def any(self, *args, **kwargs): 

7464 """ 

7465 Return whether any element is Truthy. 

7466 

7467 Parameters 

7468 ---------- 

7469 *args 

7470 Required for compatibility with numpy. 

7471 **kwargs 

7472 Required for compatibility with numpy. 

7473 

7474 Returns 

7475 ------- 

7476 bool or array-like (if axis is specified) 

7477 A single element array-like may be converted to bool. 

7478 

7479 See Also 

7480 -------- 

7481 Index.all : Return whether all elements are True. 

7482 Series.all : Return whether all elements are True. 

7483 

7484 Notes 

7485 ----- 

7486 Not a Number (NaN), positive infinity and negative infinity 

7487 evaluate to True because these are not equal to zero. 

7488 

7489 Examples 

7490 -------- 

7491 >>> index = pd.Index([0, 1, 2]) 

7492 >>> index.any() 

7493 True 

7494 

7495 >>> index = pd.Index([0, 0, 0]) 

7496 >>> index.any() 

7497 False 

7498 """ 

7499 nv.validate_any(args, kwargs) 

7500 self._maybe_disable_logical_methods("any") 

7501 vals = self._values 

7502 if not isinstance(vals, np.ndarray): 

7503 # i.e. EA, call _reduce instead of "any" to get TypeError instead 

7504 # of AttributeError 

7505 return vals._reduce("any") 

7506 return maybe_unbox_numpy_scalar(np.any(vals)) 

7507 

7508 def all(self, *args, **kwargs): 

7509 """ 

7510 Return whether all elements are Truthy. 

7511 

7512 Parameters 

7513 ---------- 

7514 *args 

7515 Required for compatibility with numpy. 

7516 **kwargs 

7517 Required for compatibility with numpy. 

7518 

7519 Returns 

7520 ------- 

7521 bool or array-like (if axis is specified) 

7522 A single element array-like may be converted to bool. 

7523 

7524 See Also 

7525 -------- 

7526 Index.any : Return whether any element in an Index is True. 

7527 Series.any : Return whether any element in a Series is True. 

7528 Series.all : Return whether all elements in a Series are True. 

7529 

7530 Notes 

7531 ----- 

7532 Not a Number (NaN), positive infinity and negative infinity 

7533 evaluate to True because these are not equal to zero. 

7534 

7535 Examples 

7536 -------- 

7537 True, because nonzero integers are considered True. 

7538 

7539 >>> pd.Index([1, 2, 3]).all() 

7540 True 

7541 

7542 False, because ``0`` is considered False. 

7543 

7544 >>> pd.Index([0, 1, 2]).all() 

7545 False 

7546 """ 

7547 nv.validate_all(args, kwargs) 

7548 self._maybe_disable_logical_methods("all") 

7549 vals = self._values 

7550 if not isinstance(vals, np.ndarray): 

7551 # i.e. EA, call _reduce instead of "all" to get TypeError instead 

7552 # of AttributeError 

7553 return vals._reduce("all") 

7554 return maybe_unbox_numpy_scalar(np.all(vals)) 

7555 

7556 @final 

7557 def _maybe_disable_logical_methods(self, opname: str_t) -> None: 

7558 """ 

7559 raise if this Index subclass does not support any or all. 

7560 """ 

7561 if isinstance(self, ABCMultiIndex): 

7562 raise TypeError(f"cannot perform {opname} with {type(self).__name__}") 

7563 

7564 def argmin( 

7565 self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs 

7566 ) -> int: 

7567 """ 

7568 Return int position of the smallest value in the Index. 

7569 

7570 If the minimum is achieved in multiple locations, 

7571 the first row position is returned. 

7572 

7573 Parameters 

7574 ---------- 

7575 axis : None 

7576 Unused. Parameter needed for compatibility with DataFrame. 

7577 skipna : bool, default True 

7578 Exclude NA/null values. If the entire Series is NA, or if ``skipna=False`` 

7579 and there is an NA value, this method will raise a ``ValueError``. 

7580 *args, **kwargs 

7581 Additional arguments and keywords for compatibility with NumPy. 

7582 

7583 Returns 

7584 ------- 

7585 int 

7586 Row position of the minimum value. 

7587 

7588 See Also 

7589 -------- 

7590 Series.argmin : Return position of the minimum value. 

7591 Series.argmax : Return position of the maximum value. 

7592 numpy.ndarray.argmin : Equivalent method for numpy arrays. 

7593 Series.idxmin : Return index label of the minimum values. 

7594 Series.idxmax : Return index label of the maximum values. 

7595 

7596 Examples 

7597 -------- 

7598 Consider dataset containing cereal calories 

7599 

7600 >>> idx = pd.Index([100.0, 110.0, 120.0, 110.0]) 

7601 >>> idx 

7602 Index([100.0, 110.0, 120.0, 110.0], dtype='float64') 

7603 

7604 >>> idx.argmax() 

7605 np.int64(2) 

7606 >>> idx.argmin() 

7607 np.int64(0) 

7608 

7609 The maximum cereal calories is the third element and 

7610 the minimum cereal calories is the first element, 

7611 since index is zero-indexed. 

7612 """ 

7613 nv.validate_argmin(args, kwargs) 

7614 nv.validate_minmax_axis(axis) 

7615 

7616 if not self._is_multi and self.hasnans: 

7617 if not skipna: 

7618 raise ValueError("Encountered an NA value with skipna=False") 

7619 elif self._isnan.all(): 

7620 raise ValueError("Encountered all NA values") 

7621 

7622 return super().argmin(skipna=skipna) 

7623 

7624 def argmax( 

7625 self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs 

7626 ) -> int: 

7627 """ 

7628 Return int position of the largest value in the Index. 

7629 

7630 If the maximum is achieved in multiple locations, 

7631 the first row position is returned. 

7632 

7633 Parameters 

7634 ---------- 

7635 axis : None 

7636 Unused. Parameter needed for compatibility with DataFrame. 

7637 skipna : bool, default True 

7638 Exclude NA/null values. If the entire Series is NA, or if ``skipna=False`` 

7639 and there is an NA value, this method will raise a ``ValueError``. 

7640 *args, **kwargs 

7641 Additional arguments and keywords for compatibility with NumPy. 

7642 

7643 Returns 

7644 ------- 

7645 int 

7646 Row position of the maximum value. 

7647 

7648 See Also 

7649 -------- 

7650 Series.argmax : Return position of the maximum value. 

7651 Series.argmin : Return position of the minimum value. 

7652 numpy.ndarray.argmax : Equivalent method for numpy arrays. 

7653 Series.idxmax : Return index label of the maximum values. 

7654 Series.idxmin : Return index label of the minimum values. 

7655 

7656 Examples 

7657 -------- 

7658 Consider dataset containing cereal calories 

7659 

7660 >>> idx = pd.Index([100.0, 110.0, 120.0, 110.0]) 

7661 >>> idx 

7662 Index([100.0, 110.0, 120.0, 110.0], dtype='float64') 

7663 

7664 >>> idx.argmax() 

7665 np.int64(2) 

7666 >>> idx.argmin() 

7667 np.int64(0) 

7668 

7669 The maximum cereal calories is the third element and 

7670 the minimum cereal calories is the first element, 

7671 since index is zero-indexed. 

7672 """ 

7673 nv.validate_argmax(args, kwargs) 

7674 nv.validate_minmax_axis(axis) 

7675 

7676 if not self._is_multi and self.hasnans: 

7677 if not skipna: 

7678 raise ValueError("Encountered an NA value with skipna=False") 

7679 elif self._isnan.all(): 

7680 raise ValueError("Encountered all NA values") 

7681 return super().argmax(skipna=skipna) 

7682 

7683 def min(self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs): 

7684 """ 

7685 Return the minimum value of the Index. 

7686 

7687 Parameters 

7688 ---------- 

7689 axis : {None} 

7690 Dummy argument for consistency with Series. 

7691 skipna : bool, default True 

7692 Exclude NA/null values when showing the result. 

7693 *args, **kwargs 

7694 Additional arguments and keywords for compatibility with NumPy. 

7695 

7696 Returns 

7697 ------- 

7698 scalar 

7699 Minimum value. 

7700 

7701 See Also 

7702 -------- 

7703 Index.max : Return the maximum value of the object. 

7704 Series.min : Return the minimum value in a Series. 

7705 DataFrame.min : Return the minimum values in a DataFrame. 

7706 

7707 Examples 

7708 -------- 

7709 >>> idx = pd.Index([3, 2, 1]) 

7710 >>> idx.min() 

7711 1 

7712 

7713 >>> idx = pd.Index(["c", "b", "a"]) 

7714 >>> idx.min() 

7715 'a' 

7716 

7717 For a MultiIndex, the minimum is determined lexicographically. 

7718 

7719 >>> idx = pd.MultiIndex.from_product([("a", "b"), (2, 1)]) 

7720 >>> idx.min() 

7721 ('a', 1) 

7722 """ 

7723 nv.validate_min(args, kwargs) 

7724 nv.validate_minmax_axis(axis) 

7725 

7726 if not len(self): 

7727 return self._na_value 

7728 

7729 if len(self) and self.is_monotonic_increasing: 

7730 # quick check 

7731 first = self[0] 

7732 if not isna(first): 

7733 return maybe_unbox_numpy_scalar(first) 

7734 

7735 if not self._is_multi and self.hasnans: 

7736 # Take advantage of cache 

7737 mask = self._isnan 

7738 if not skipna or mask.all(): 

7739 return self._na_value 

7740 

7741 if not self._is_multi and not isinstance(self._values, np.ndarray): 

7742 return self._values._reduce(name="min", skipna=skipna) 

7743 

7744 return maybe_unbox_numpy_scalar(nanops.nanmin(self._values, skipna=skipna)) 

7745 

7746 def max(self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs): 

7747 """ 

7748 Return the maximum value of the Index. 

7749 

7750 Parameters 

7751 ---------- 

7752 axis : int, optional 

7753 For compatibility with NumPy. Only 0 or None are allowed. 

7754 skipna : bool, default True 

7755 Exclude NA/null values when showing the result. 

7756 *args, **kwargs 

7757 Additional arguments and keywords for compatibility with NumPy. 

7758 

7759 Returns 

7760 ------- 

7761 scalar 

7762 Maximum value. 

7763 

7764 See Also 

7765 -------- 

7766 Index.min : Return the minimum value in an Index. 

7767 Series.max : Return the maximum value in a Series. 

7768 DataFrame.max : Return the maximum values in a DataFrame. 

7769 

7770 Examples 

7771 -------- 

7772 >>> idx = pd.Index([3, 2, 1]) 

7773 >>> idx.max() 

7774 3 

7775 

7776 >>> idx = pd.Index(["c", "b", "a"]) 

7777 >>> idx.max() 

7778 'c' 

7779 

7780 For a MultiIndex, the maximum is determined lexicographically. 

7781 

7782 >>> idx = pd.MultiIndex.from_product([("a", "b"), (2, 1)]) 

7783 >>> idx.max() 

7784 ('b', 2) 

7785 """ 

7786 

7787 nv.validate_max(args, kwargs) 

7788 nv.validate_minmax_axis(axis) 

7789 

7790 if not len(self): 

7791 return self._na_value 

7792 

7793 if len(self) and self.is_monotonic_increasing: 

7794 # quick check 

7795 last = self[-1] 

7796 if not isna(last): 

7797 return maybe_unbox_numpy_scalar(last) 

7798 

7799 if not self._is_multi and self.hasnans: 

7800 # Take advantage of cache 

7801 mask = self._isnan 

7802 if not skipna or mask.all(): 

7803 return maybe_unbox_numpy_scalar(self._na_value) 

7804 

7805 if not self._is_multi and not isinstance(self._values, np.ndarray): 

7806 return self._values._reduce(name="max", skipna=skipna) 

7807 

7808 return maybe_unbox_numpy_scalar(nanops.nanmax(self._values, skipna=skipna)) 

7809 

7810 # -------------------------------------------------------------------- 

7811 

7812 @final 

7813 @property 

7814 def shape(self) -> Shape: 

7815 """ 

7816 Return a tuple of the shape of the underlying data. 

7817 

7818 See Also 

7819 -------- 

7820 Index.size: Return the number of elements in the underlying data. 

7821 Index.ndim: Number of dimensions of the underlying data, by definition 1. 

7822 Index.dtype: Return the dtype object of the underlying data. 

7823 Index.values: Return an array representing the data in the Index. 

7824 

7825 Examples 

7826 -------- 

7827 >>> idx = pd.Index([1, 2, 3]) 

7828 >>> idx 

7829 Index([1, 2, 3], dtype='int64') 

7830 >>> idx.shape 

7831 (3,) 

7832 """ 

7833 # See GH#27775, GH#27384 for history/reasoning in how this is defined. 

7834 return (len(self),) 

7835 

7836 

7837def maybe_sequence_to_range(sequence) -> Any | range: 

7838 """ 

7839 Convert a 1D, non-pandas sequence to a range if possible. 

7840 

7841 Returns the input if not possible. 

7842 

7843 Parameters 

7844 ---------- 

7845 sequence : 1D sequence 

7846 names : sequence of str 

7847 

7848 Returns 

7849 ------- 

7850 Any : input or range 

7851 """ 

7852 if isinstance(sequence, (range, ExtensionArray)): 

7853 return sequence 

7854 elif len(sequence) == 1 or lib.infer_dtype(sequence, skipna=False) != "integer": 

7855 return sequence 

7856 elif isinstance(sequence, (ABCSeries, Index)) and not ( 

7857 isinstance(sequence.dtype, np.dtype) and sequence.dtype.kind == "i" 

7858 ): 

7859 return sequence 

7860 if len(sequence) == 0: 

7861 return range(0) 

7862 try: 

7863 np_sequence = np.asarray(sequence, dtype=np.int64) 

7864 except OverflowError: 

7865 return sequence 

7866 diff = np_sequence[1] - np_sequence[0] 

7867 if diff == 0: 

7868 return sequence 

7869 elif len(sequence) == 2 or lib.is_sequence_range(np_sequence, diff): 

7870 return range(np_sequence[0], np_sequence[-1] + diff, diff) 

7871 else: 

7872 return sequence 

7873 

7874 

7875def ensure_index_from_sequences(sequences, names=None) -> Index: 

7876 """ 

7877 Construct an index from sequences of data. 

7878 

7879 A single sequence returns an Index. Many sequences returns a 

7880 MultiIndex. 

7881 

7882 Parameters 

7883 ---------- 

7884 sequences : sequence of sequences 

7885 names : sequence of str 

7886 

7887 Returns 

7888 ------- 

7889 index : Index or MultiIndex 

7890 

7891 Examples 

7892 -------- 

7893 >>> ensure_index_from_sequences([[1, 2, 4]], names=["name"]) 

7894 Index([1, 2, 4], dtype='int64', name='name') 

7895 

7896 >>> ensure_index_from_sequences([["a", "a"], ["a", "b"]], names=["L1", "L2"]) 

7897 MultiIndex([('a', 'a'), 

7898 ('a', 'b')], 

7899 names=['L1', 'L2']) 

7900 

7901 See Also 

7902 -------- 

7903 ensure_index 

7904 """ 

7905 from pandas.core.indexes.api import default_index 

7906 from pandas.core.indexes.multi import MultiIndex 

7907 

7908 if len(sequences) == 0: 

7909 return default_index(0) 

7910 elif len(sequences) == 1: 

7911 if names is not None: 

7912 names = names[0] 

7913 return Index(maybe_sequence_to_range(sequences[0]), name=names) 

7914 else: 

7915 # TODO: Apply maybe_sequence_to_range to sequences? 

7916 return MultiIndex.from_arrays(sequences, names=names) 

7917 

7918 

7919def ensure_index(index_like: Axes, copy: bool = False) -> Index: 

7920 """ 

7921 Ensure that we have an index from some index-like object. 

7922 

7923 Parameters 

7924 ---------- 

7925 index_like : sequence 

7926 An Index or other sequence 

7927 copy : bool, default False 

7928 

7929 Returns 

7930 ------- 

7931 index : Index or MultiIndex 

7932 

7933 See Also 

7934 -------- 

7935 ensure_index_from_sequences 

7936 

7937 Examples 

7938 -------- 

7939 >>> ensure_index(["a", "b"]) 

7940 Index(['a', 'b'], dtype='str') 

7941 

7942 >>> ensure_index([("a", "a"), ("b", "c")]) 

7943 Index([('a', 'a'), ('b', 'c')], dtype='object') 

7944 

7945 >>> ensure_index([["a", "a"], ["b", "c"]]) 

7946 MultiIndex([('a', 'b'), 

7947 ('a', 'c')], 

7948 ) 

7949 """ 

7950 if isinstance(index_like, Index): 

7951 if copy: 

7952 index_like = index_like.copy() 

7953 return index_like 

7954 

7955 if isinstance(index_like, ABCSeries): 

7956 name = index_like.name 

7957 return Index(index_like, name=name, copy=copy) 

7958 

7959 if is_iterator(index_like): 

7960 index_like = list(index_like) 

7961 

7962 if isinstance(index_like, list): 

7963 if type(index_like) is not list: 

7964 # must check for exactly list here because of strict type 

7965 # check in clean_index_list 

7966 index_like = list(index_like) 

7967 

7968 if index_like and lib.is_all_arraylike(index_like): 

7969 from pandas.core.indexes.multi import MultiIndex 

7970 

7971 return MultiIndex.from_arrays(index_like) 

7972 else: 

7973 return Index(index_like, copy=copy, tupleize_cols=False) 

7974 else: 

7975 return Index(index_like, copy=copy) 

7976 

7977 

7978def trim_front(strings: list[str]) -> list[str]: 

7979 """ 

7980 Trims leading spaces evenly among all strings. 

7981 

7982 Examples 

7983 -------- 

7984 >>> trim_front([" a", " b"]) 

7985 ['a', 'b'] 

7986 

7987 >>> trim_front([" a", " "]) 

7988 ['a', ''] 

7989 """ 

7990 if not strings: 

7991 return strings 

7992 smallest_leading_space = min(len(x) - len(x.lstrip()) for x in strings) 

7993 if smallest_leading_space > 0: 

7994 strings = [x[smallest_leading_space:] for x in strings] 

7995 return strings 

7996 

7997 

7998def _validate_join_method(method: str) -> None: 

7999 if method not in ["left", "right", "inner", "outer"]: 

8000 raise ValueError(f"do not recognize join method {method}") 

8001 

8002 

8003def maybe_extract_name(name, obj, cls) -> Hashable: 

8004 """ 

8005 If no name is passed, then extract it from data, validating hashability. 

8006 """ 

8007 if name is None and isinstance(obj, (Index, ABCSeries)): 

8008 # Note we don't just check for "name" attribute since that would 

8009 # pick up e.g. dtype.name 

8010 name = obj.name 

8011 

8012 # GH#29069 

8013 if not is_hashable(name): 

8014 raise TypeError(f"{cls.__name__}.name must be a hashable type") 

8015 

8016 return name 

8017 

8018 

8019def get_unanimous_names(*indexes: Index) -> tuple[Hashable, ...]: 

8020 """ 

8021 Return common name if all indices agree, otherwise None (level-by-level). 

8022 

8023 Parameters 

8024 ---------- 

8025 indexes : list of Index objects 

8026 

8027 Returns 

8028 ------- 

8029 list 

8030 A list representing the unanimous 'names' found. 

8031 """ 

8032 name_tups = (tuple(i.names) for i in indexes) 

8033 name_sets = ({*ns} for ns in zip_longest(*name_tups)) 

8034 names = tuple(ns.pop() if len(ns) == 1 else None for ns in name_sets) 

8035 return names 

8036 

8037 

8038def _unpack_nested_dtype(other: Index) -> DtypeObj: 

8039 """ 

8040 When checking if our dtype is comparable with another, we need 

8041 to unpack CategoricalDtype to look at its categories.dtype. 

8042 

8043 Parameters 

8044 ---------- 

8045 other : Index 

8046 

8047 Returns 

8048 ------- 

8049 np.dtype or ExtensionDtype 

8050 """ 

8051 dtype = other.dtype 

8052 if isinstance(dtype, CategoricalDtype): 

8053 # If there is ever a SparseIndex, this could get dispatched 

8054 # here too. 

8055 return dtype.categories.dtype 

8056 elif isinstance(dtype, ArrowDtype): 

8057 # GH 53617 

8058 import pyarrow as pa 

8059 

8060 if pa.types.is_dictionary(dtype.pyarrow_dtype): 

8061 other = other[:0].astype(ArrowDtype(dtype.pyarrow_dtype.value_type)) 

8062 return other.dtype 

8063 

8064 

8065def _maybe_try_sort(result: Index | ArrayLike, sort: bool | None): 

8066 if sort is not False: 

8067 try: 

8068 # error: Incompatible types in assignment (expression has type 

8069 # "Union[ExtensionArray, ndarray[Any, Any], Index, Series, 

8070 # Tuple[Union[Union[ExtensionArray, ndarray[Any, Any]], Index, Series], 

8071 # ndarray[Any, Any]]]", variable has type "Union[Index, 

8072 # Union[ExtensionArray, ndarray[Any, Any]]]") 

8073 result = algos.safe_sort(result) # type: ignore[assignment] 

8074 except TypeError as err: 

8075 if sort is True: 

8076 raise 

8077 warnings.warn( 

8078 f"{err}, sort order is undefined for incomparable objects.", 

8079 RuntimeWarning, 

8080 stacklevel=find_stack_level(), 

8081 ) 

8082 return result 

8083 

8084 

8085def get_values_for_csv( 

8086 values: ArrayLike, 

8087 *, 

8088 date_format, 

8089 na_rep: str = "nan", 

8090 quoting=None, 

8091 float_format=None, 

8092 decimal: str = ".", 

8093) -> npt.NDArray[np.object_]: 

8094 """ 

8095 Convert to types which can be consumed by the standard library's 

8096 csv.writer.writerows. 

8097 """ 

8098 if isinstance(values, Categorical) and values.categories.dtype.kind in "Mm": 

8099 # GH#40754 Convert categorical datetimes to datetime array 

8100 values = algos.take_nd( 

8101 values.categories._values, 

8102 ensure_platform_int(values._codes), 

8103 fill_value=na_rep, 

8104 ) 

8105 

8106 values = ensure_wrapped_if_datetimelike(values) 

8107 

8108 if isinstance(values, (DatetimeArray, TimedeltaArray)): 

8109 if values.ndim == 1: 

8110 result = values._format_native_types(na_rep=na_rep, date_format=date_format) 

8111 result = result.astype(object, copy=False) 

8112 return result 

8113 

8114 # GH#21734 Process every column separately, they might have different formats 

8115 results_converted = [] 

8116 for i in range(len(values)): 

8117 result = values[i, :]._format_native_types( 

8118 na_rep=na_rep, date_format=date_format 

8119 ) 

8120 results_converted.append(result.astype(object, copy=False)) 

8121 return np.vstack(results_converted) 

8122 

8123 elif isinstance(values.dtype, PeriodDtype): 

8124 # TODO: tests that get here in column path 

8125 values = cast("PeriodArray", values) 

8126 res = values._format_native_types(na_rep=na_rep, date_format=date_format) 

8127 return res 

8128 

8129 elif isinstance(values.dtype, IntervalDtype): 

8130 # TODO: tests that get here in column path 

8131 values = cast("IntervalArray", values) 

8132 mask = values.isna() 

8133 if not quoting: 

8134 result = np.asarray(values).astype(str) 

8135 else: 

8136 result = np.array(values, dtype=object, copy=True) 

8137 

8138 result[mask] = na_rep 

8139 return result 

8140 

8141 elif values.dtype.kind == "f" and not isinstance(values.dtype, SparseDtype): 

8142 # see GH#13418: no special formatting is desired at the 

8143 # output (important for appropriate 'quoting' behaviour), 

8144 # so do not pass it through the FloatArrayFormatter 

8145 if float_format is None and decimal == ".": 

8146 mask = isna(values) 

8147 

8148 if not quoting: 

8149 values = values.astype(str) 

8150 else: 

8151 values = np.array(values, dtype="object") 

8152 

8153 values[mask] = na_rep 

8154 values = values.astype(object, copy=False) 

8155 return values 

8156 

8157 from pandas.io.formats.format import FloatArrayFormatter 

8158 

8159 formatter = FloatArrayFormatter( 

8160 values, 

8161 na_rep=na_rep, 

8162 float_format=float_format, 

8163 decimal=decimal, 

8164 quoting=quoting, 

8165 fixed_width=False, 

8166 ) 

8167 res = formatter.get_result_as_array() 

8168 res = res.astype(object, copy=False) 

8169 return res 

8170 

8171 elif isinstance(values, ExtensionArray): 

8172 mask = isna(values) 

8173 

8174 new_values = np.asarray(values.astype(object)) 

8175 new_values[mask] = na_rep 

8176 return new_values 

8177 

8178 else: 

8179 mask = isna(values) 

8180 itemsize = writers.word_len(na_rep) 

8181 

8182 if values.dtype != _dtype_obj and not quoting and itemsize: 

8183 values = values.astype(str) 

8184 if values.dtype.itemsize / np.dtype("U1").itemsize < itemsize: 

8185 # enlarge for the na_rep 

8186 values = values.astype(f"<U{itemsize}") 

8187 else: 

8188 values = np.array(values, dtype="object") 

8189 

8190 values[mask] = na_rep 

8191 values = values.astype(object, copy=False) 

8192 return values