Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from typing import Any, List 

2import warnings 

3 

4import numpy as np 

5 

6from pandas._config import get_option 

7 

8from pandas._libs import index as libindex 

9from pandas._libs.hashtable import duplicated_int64 

10from pandas._typing import AnyArrayLike 

11from pandas.util._decorators import Appender, cache_readonly 

12 

13from pandas.core.dtypes.common import ( 

14 ensure_platform_int, 

15 is_categorical_dtype, 

16 is_interval_dtype, 

17 is_list_like, 

18 is_scalar, 

19) 

20from pandas.core.dtypes.dtypes import CategoricalDtype 

21from pandas.core.dtypes.generic import ABCCategorical, ABCSeries 

22from pandas.core.dtypes.missing import isna 

23 

24from pandas.core import accessor 

25from pandas.core.algorithms import take_1d 

26from pandas.core.arrays.categorical import Categorical, _recode_for_categories, contains 

27import pandas.core.common as com 

28import pandas.core.indexes.base as ibase 

29from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name 

30from pandas.core.indexes.extension import ExtensionIndex, inherit_names 

31import pandas.core.missing as missing 

32from pandas.core.ops import get_op_result_name 

33 

34_index_doc_kwargs = dict(ibase._index_doc_kwargs) 

35_index_doc_kwargs.update(dict(target_klass="CategoricalIndex")) 

36 

37 

38@inherit_names( 

39 [ 

40 "argsort", 

41 "_internal_get_values", 

42 "tolist", 

43 "codes", 

44 "categories", 

45 "ordered", 

46 "_reverse_indexer", 

47 "searchsorted", 

48 "is_dtype_equal", 

49 "min", 

50 "max", 

51 ], 

52 Categorical, 

53) 

54@accessor.delegate_names( 

55 delegate=Categorical, 

56 accessors=[ 

57 "rename_categories", 

58 "reorder_categories", 

59 "add_categories", 

60 "remove_categories", 

61 "remove_unused_categories", 

62 "set_categories", 

63 "as_ordered", 

64 "as_unordered", 

65 ], 

66 typ="method", 

67 overwrite=True, 

68) 

69class CategoricalIndex(ExtensionIndex, accessor.PandasDelegate): 

70 """ 

71 Index based on an underlying :class:`Categorical`. 

72 

73 CategoricalIndex, like Categorical, can only take on a limited, 

74 and usually fixed, number of possible values (`categories`). Also, 

75 like Categorical, it might have an order, but numerical operations 

76 (additions, divisions, ...) are not possible. 

77 

78 Parameters 

79 ---------- 

80 data : array-like (1-dimensional) 

81 The values of the categorical. If `categories` are given, values not in 

82 `categories` will be replaced with NaN. 

83 categories : index-like, optional 

84 The categories for the categorical. Items need to be unique. 

85 If the categories are not given here (and also not in `dtype`), they 

86 will be inferred from the `data`. 

87 ordered : bool, optional 

88 Whether or not this categorical is treated as an ordered 

89 categorical. If not given here or in `dtype`, the resulting 

90 categorical will be unordered. 

91 dtype : CategoricalDtype or "category", optional 

92 If :class:`CategoricalDtype`, cannot be used together with 

93 `categories` or `ordered`. 

94 

95 .. versionadded:: 0.21.0 

96 copy : bool, default False 

97 Make a copy of input ndarray. 

98 name : object, optional 

99 Name to be stored in the index. 

100 

101 Attributes 

102 ---------- 

103 codes 

104 categories 

105 ordered 

106 

107 Methods 

108 ------- 

109 rename_categories 

110 reorder_categories 

111 add_categories 

112 remove_categories 

113 remove_unused_categories 

114 set_categories 

115 as_ordered 

116 as_unordered 

117 map 

118 

119 Raises 

120 ------ 

121 ValueError 

122 If the categories do not validate. 

123 TypeError 

124 If an explicit ``ordered=True`` is given but no `categories` and the 

125 `values` are not sortable. 

126 

127 See Also 

128 -------- 

129 Index : The base pandas Index type. 

130 Categorical : A categorical array. 

131 CategoricalDtype : Type for categorical data. 

132 

133 Notes 

134 ----- 

135 See the `user guide 

136 <https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#categoricalindex>`_ 

137 for more. 

138 

139 Examples 

140 -------- 

141 >>> pd.CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c']) 

142 CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'], categories=['a', 'b', 'c'], ordered=False, dtype='category') # noqa 

143 

144 ``CategoricalIndex`` can also be instantiated from a ``Categorical``: 

145 

146 >>> c = pd.Categorical(['a', 'b', 'c', 'a', 'b', 'c']) 

147 >>> pd.CategoricalIndex(c) 

148 CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'], categories=['a', 'b', 'c'], ordered=False, dtype='category') # noqa 

149 

150 Ordered ``CategoricalIndex`` can have a min and max value. 

151 

152 >>> ci = pd.CategoricalIndex(['a','b','c','a','b','c'], ordered=True, 

153 ... categories=['c', 'b', 'a']) 

154 >>> ci 

155 CategoricalIndex(['a', 'b', 'c', 'a', 'b', 'c'], categories=['c', 'b', 'a'], ordered=True, dtype='category') # noqa 

156 >>> ci.min() 

157 'c' 

158 """ 

159 

160 _typ = "categoricalindex" 

161 

162 _raw_inherit = { 

163 "argsort", 

164 "_internal_get_values", 

165 "tolist", 

166 "codes", 

167 "categories", 

168 "ordered", 

169 "_reverse_indexer", 

170 "searchsorted", 

171 } 

172 

173 codes: np.ndarray 

174 categories: Index 

175 

176 @property 

177 def _engine_type(self): 

178 # self.codes can have dtype int8, int16, int32 or int64, so we need 

179 # to return the corresponding engine type (libindex.Int8Engine, etc.). 

180 return { 

181 np.int8: libindex.Int8Engine, 

182 np.int16: libindex.Int16Engine, 

183 np.int32: libindex.Int32Engine, 

184 np.int64: libindex.Int64Engine, 

185 }[self.codes.dtype.type] 

186 

187 _attributes = ["name"] 

188 

189 # -------------------------------------------------------------------- 

190 # Constructors 

191 

192 def __new__( 

193 cls, data=None, categories=None, ordered=None, dtype=None, copy=False, name=None 

194 ): 

195 

196 dtype = CategoricalDtype._from_values_or_dtype(data, categories, ordered, dtype) 

197 

198 name = maybe_extract_name(name, data, cls) 

199 

200 if not is_categorical_dtype(data): 

201 # don't allow scalars 

202 # if data is None, then categories must be provided 

203 if is_scalar(data): 

204 if data is not None or categories is None: 

205 raise cls._scalar_data_error(data) 

206 data = [] 

207 

208 data = cls._create_categorical(data, dtype=dtype) 

209 

210 data = data.copy() if copy else data 

211 

212 return cls._simple_new(data, name=name) 

213 

214 def _create_from_codes(self, codes, dtype=None, name=None): 

215 """ 

216 *this is an internal non-public method* 

217 

218 create the correct categorical from codes 

219 

220 Parameters 

221 ---------- 

222 codes : new codes 

223 dtype: CategoricalDtype, defaults to existing 

224 name : optional name attribute, defaults to existing 

225 

226 Returns 

227 ------- 

228 CategoricalIndex 

229 """ 

230 

231 if dtype is None: 

232 dtype = self.dtype 

233 if name is None: 

234 name = self.name 

235 cat = Categorical.from_codes(codes, dtype=dtype) 

236 return CategoricalIndex(cat, name=name) 

237 

238 @classmethod 

239 def _create_categorical(cls, data, dtype=None): 

240 """ 

241 *this is an internal non-public method* 

242 

243 create the correct categorical from data and the properties 

244 

245 Parameters 

246 ---------- 

247 data : data for new Categorical 

248 dtype : CategoricalDtype, defaults to existing 

249 

250 Returns 

251 ------- 

252 Categorical 

253 """ 

254 if isinstance(data, (cls, ABCSeries)) and is_categorical_dtype(data): 

255 data = data.values 

256 

257 if not isinstance(data, ABCCategorical): 

258 return Categorical(data, dtype=dtype) 

259 

260 if isinstance(dtype, CategoricalDtype) and dtype != data.dtype: 

261 # we want to silently ignore dtype='category' 

262 data = data._set_dtype(dtype) 

263 return data 

264 

265 @classmethod 

266 def _simple_new(cls, values, name=None, dtype=None): 

267 result = object.__new__(cls) 

268 

269 values = cls._create_categorical(values, dtype=dtype) 

270 result._data = values 

271 result.name = name 

272 

273 result._reset_identity() 

274 result._no_setting_name = False 

275 return result 

276 

277 # -------------------------------------------------------------------- 

278 

279 @Appender(_index_shared_docs["_shallow_copy"]) 

280 def _shallow_copy(self, values=None, dtype=None, **kwargs): 

281 if dtype is None: 

282 dtype = self.dtype 

283 return super()._shallow_copy(values=values, dtype=dtype, **kwargs) 

284 

285 def _is_dtype_compat(self, other) -> bool: 

286 """ 

287 *this is an internal non-public method* 

288 

289 provide a comparison between the dtype of self and other (coercing if 

290 needed) 

291 

292 Raises 

293 ------ 

294 TypeError if the dtypes are not compatible 

295 """ 

296 if is_categorical_dtype(other): 

297 if isinstance(other, CategoricalIndex): 

298 other = other._values 

299 if not other.is_dtype_equal(self): 

300 raise TypeError( 

301 "categories must match existing categories when appending" 

302 ) 

303 else: 

304 values = other 

305 if not is_list_like(values): 

306 values = [values] 

307 other = CategoricalIndex(self._create_categorical(other, dtype=self.dtype)) 

308 if not other.isin(values).all(): 

309 raise TypeError( 

310 "cannot append a non-category item to a CategoricalIndex" 

311 ) 

312 

313 return other 

314 

315 def equals(self, other): 

316 """ 

317 Determine if two CategoricalIndex objects contain the same elements. 

318 

319 Returns 

320 ------- 

321 bool 

322 If two CategoricalIndex objects have equal elements True, 

323 otherwise False. 

324 """ 

325 if self.is_(other): 

326 return True 

327 

328 if not isinstance(other, Index): 

329 return False 

330 

331 try: 

332 other = self._is_dtype_compat(other) 

333 if isinstance(other, type(self)): 

334 other = other._data 

335 return self._data.equals(other) 

336 except (TypeError, ValueError): 

337 pass 

338 

339 return False 

340 

341 # -------------------------------------------------------------------- 

342 # Rendering Methods 

343 

344 @property 

345 def _formatter_func(self): 

346 return self.categories._formatter_func 

347 

348 def _format_attrs(self): 

349 """ 

350 Return a list of tuples of the (attr,formatted_value) 

351 """ 

352 max_categories = ( 

353 10 

354 if get_option("display.max_categories") == 0 

355 else get_option("display.max_categories") 

356 ) 

357 attrs = [ 

358 ( 

359 "categories", 

360 ibase.default_pprint(self.categories, max_seq_items=max_categories), 

361 ), 

362 ("ordered", self.ordered), 

363 ] 

364 if self.name is not None: 

365 attrs.append(("name", ibase.default_pprint(self.name))) 

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

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

368 if len(self) > max_seq_items: 

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

370 return attrs 

371 

372 # -------------------------------------------------------------------- 

373 

374 @property 

375 def inferred_type(self) -> str: 

376 return "categorical" 

377 

378 @property 

379 def values(self): 

380 """ return the underlying data, which is a Categorical """ 

381 return self._data 

382 

383 @property 

384 def _has_complex_internals(self): 

385 # used to avoid libreduction code paths, which raise or require conversion 

386 return True 

387 

388 def _wrap_setop_result(self, other, result): 

389 name = get_op_result_name(self, other) 

390 # We use _shallow_copy rather than the Index implementation 

391 # (which uses _constructor) in order to preserve dtype. 

392 return self._shallow_copy(result, name=name) 

393 

394 @Appender(_index_shared_docs["contains"] % _index_doc_kwargs) 

395 def __contains__(self, key) -> bool: 

396 # if key is a NaN, check if any NaN is in self. 

397 if is_scalar(key) and isna(key): 

398 return self.hasnans 

399 

400 return contains(self, key, container=self._engine) 

401 

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

403 """ the array interface, return my values """ 

404 return np.array(self._data, dtype=dtype) 

405 

406 @Appender(_index_shared_docs["astype"]) 

407 def astype(self, dtype, copy=True): 

408 if is_interval_dtype(dtype): 

409 from pandas import IntervalIndex 

410 

411 return IntervalIndex(np.array(self)) 

412 elif is_categorical_dtype(dtype): 

413 # GH 18630 

414 dtype = self.dtype.update_dtype(dtype) 

415 if dtype == self.dtype: 

416 return self.copy() if copy else self 

417 

418 return Index.astype(self, dtype=dtype, copy=copy) 

419 

420 @cache_readonly 

421 def _isnan(self): 

422 """ return if each value is nan""" 

423 return self._data.codes == -1 

424 

425 @Appender(ibase._index_shared_docs["fillna"]) 

426 def fillna(self, value, downcast=None): 

427 self._assert_can_do_op(value) 

428 return CategoricalIndex(self._data.fillna(value), name=self.name) 

429 

430 @cache_readonly 

431 def _engine(self): 

432 # we are going to look things up with the codes themselves. 

433 # To avoid a reference cycle, bind `codes` to a local variable, so 

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

435 codes = self.codes 

436 return self._engine_type(lambda: codes, len(self)) 

437 

438 # introspection 

439 @cache_readonly 

440 def is_unique(self) -> bool: 

441 return self._engine.is_unique 

442 

443 @property 

444 def is_monotonic_increasing(self): 

445 return self._engine.is_monotonic_increasing 

446 

447 @property 

448 def is_monotonic_decreasing(self) -> bool: 

449 return self._engine.is_monotonic_decreasing 

450 

451 @Appender(_index_shared_docs["index_unique"] % _index_doc_kwargs) 

452 def unique(self, level=None): 

453 if level is not None: 

454 self._validate_index_level(level) 

455 result = self.values.unique() 

456 # CategoricalIndex._shallow_copy keeps original dtype 

457 # if not otherwise specified 

458 return self._shallow_copy(result, dtype=result.dtype) 

459 

460 @Appender(Index.duplicated.__doc__) 

461 def duplicated(self, keep="first"): 

462 codes = self.codes.astype("i8") 

463 return duplicated_int64(codes, keep) 

464 

465 def _to_safe_for_reshape(self): 

466 """ convert to object if we are a categorical """ 

467 return self.astype("object") 

468 

469 def get_loc(self, key, method=None): 

470 """ 

471 Get integer location, slice or boolean mask for requested label. 

472 

473 Parameters 

474 ---------- 

475 key : label 

476 method : {None} 

477 * default: exact matches only. 

478 

479 Returns 

480 ------- 

481 loc : int if unique index, slice if monotonic index, else mask 

482 

483 Raises 

484 ------ 

485 KeyError : if the key is not in the index 

486 

487 Examples 

488 -------- 

489 >>> unique_index = pd.CategoricalIndex(list('abc')) 

490 >>> unique_index.get_loc('b') 

491 1 

492 

493 >>> monotonic_index = pd.CategoricalIndex(list('abbc')) 

494 >>> monotonic_index.get_loc('b') 

495 slice(1, 3, None) 

496 

497 >>> non_monotonic_index = pd.CategoricalIndex(list('abcb')) 

498 >>> non_monotonic_index.get_loc('b') 

499 array([False, True, False, True], dtype=bool) 

500 """ 

501 code = self.categories.get_loc(key) 

502 code = self.codes.dtype.type(code) 

503 try: 

504 return self._engine.get_loc(code) 

505 except KeyError: 

506 raise KeyError(key) 

507 

508 def get_value(self, series: AnyArrayLike, key: Any): 

509 """ 

510 Fast lookup of value from 1-dimensional ndarray. Only use this if you 

511 know what you're doing 

512 

513 Parameters 

514 ---------- 

515 series : Series, ExtensionArray, Index, or ndarray 

516 1-dimensional array to take values from 

517 key: : scalar 

518 The value of this index at the position of the desired value, 

519 otherwise the positional index of the desired value 

520 

521 Returns 

522 ------- 

523 Any 

524 The element of the series at the position indicated by the key 

525 """ 

526 try: 

527 k = com.values_from_object(key) 

528 k = self._convert_scalar_indexer(k, kind="getitem") 

529 indexer = self.get_loc(k) 

530 return series.take([indexer])[0] 

531 except (KeyError, TypeError): 

532 pass 

533 

534 # we might be a positional inexer 

535 return super().get_value(series, key) 

536 

537 @Appender(_index_shared_docs["where"]) 

538 def where(self, cond, other=None): 

539 # TODO: Investigate an alternative implementation with 

540 # 1. copy the underlying Categorical 

541 # 2. setitem with `cond` and `other` 

542 # 3. Rebuild CategoricalIndex. 

543 if other is None: 

544 other = self._na_value 

545 values = np.where(cond, self.values, other) 

546 cat = Categorical(values, dtype=self.dtype) 

547 return self._shallow_copy(cat, **self._get_attributes_dict()) 

548 

549 def reindex(self, target, method=None, level=None, limit=None, tolerance=None): 

550 """ 

551 Create index with target's values (move/add/delete values as necessary) 

552 

553 Returns 

554 ------- 

555 new_index : pd.Index 

556 Resulting index 

557 indexer : np.ndarray or None 

558 Indices of output values in original index 

559 

560 """ 

561 if method is not None: 

562 raise NotImplementedError( 

563 "argument method is not implemented for CategoricalIndex.reindex" 

564 ) 

565 if level is not None: 

566 raise NotImplementedError( 

567 "argument level is not implemented for CategoricalIndex.reindex" 

568 ) 

569 if limit is not None: 

570 raise NotImplementedError( 

571 "argument limit is not implemented for CategoricalIndex.reindex" 

572 ) 

573 

574 target = ibase.ensure_index(target) 

575 

576 missing: List[int] 

577 if self.equals(target): 

578 indexer = None 

579 missing = [] 

580 else: 

581 indexer, missing = self.get_indexer_non_unique(np.array(target)) 

582 

583 if len(self.codes) and indexer is not None: 

584 new_target = self.take(indexer) 

585 else: 

586 new_target = target 

587 

588 # filling in missing if needed 

589 if len(missing): 

590 cats = self.categories.get_indexer(target) 

591 

592 if (cats == -1).any(): 

593 # coerce to a regular index here! 

594 result = Index(np.array(self), name=self.name) 

595 new_target, indexer, _ = result._reindex_non_unique(np.array(target)) 

596 else: 

597 

598 codes = new_target.codes.copy() 

599 codes[indexer == -1] = cats[missing] 

600 new_target = self._create_from_codes(codes) 

601 

602 # we always want to return an Index type here 

603 # to be consistent with .reindex for other index types (e.g. they don't 

604 # coerce based on the actual values, only on the dtype) 

605 # unless we had an initial Categorical to begin with 

606 # in which case we are going to conform to the passed Categorical 

607 new_target = np.asarray(new_target) 

608 if is_categorical_dtype(target): 

609 new_target = target._shallow_copy(new_target, name=self.name) 

610 else: 

611 new_target = Index(new_target, name=self.name) 

612 

613 return new_target, indexer 

614 

615 def _reindex_non_unique(self, target): 

616 """ reindex from a non-unique; which CategoricalIndex's are almost 

617 always 

618 """ 

619 new_target, indexer = self.reindex(target) 

620 new_indexer = None 

621 

622 check = indexer == -1 

623 if check.any(): 

624 new_indexer = np.arange(len(self.take(indexer))) 

625 new_indexer[check] = -1 

626 

627 cats = self.categories.get_indexer(target) 

628 if not (cats == -1).any(): 

629 # .reindex returns normal Index. Revert to CategoricalIndex if 

630 # all targets are included in my categories 

631 new_target = self._shallow_copy(new_target) 

632 

633 return new_target, indexer, new_indexer 

634 

635 @Appender(_index_shared_docs["get_indexer"] % _index_doc_kwargs) 

636 def get_indexer(self, target, method=None, limit=None, tolerance=None): 

637 method = missing.clean_reindex_fill_method(method) 

638 target = ibase.ensure_index(target) 

639 

640 if self.is_unique and self.equals(target): 

641 return np.arange(len(self), dtype="intp") 

642 

643 if method == "pad" or method == "backfill": 

644 raise NotImplementedError( 

645 "method='pad' and method='backfill' not " 

646 "implemented yet for CategoricalIndex" 

647 ) 

648 elif method == "nearest": 

649 raise NotImplementedError( 

650 "method='nearest' not implemented yet for CategoricalIndex" 

651 ) 

652 

653 if isinstance(target, CategoricalIndex) and self.values.is_dtype_equal(target): 

654 if self.values.equals(target.values): 

655 # we have the same codes 

656 codes = target.codes 

657 else: 

658 codes = _recode_for_categories( 

659 target.codes, target.categories, self.values.categories 

660 ) 

661 else: 

662 if isinstance(target, CategoricalIndex): 

663 code_indexer = self.categories.get_indexer(target.categories) 

664 codes = take_1d(code_indexer, target.codes, fill_value=-1) 

665 else: 

666 codes = self.categories.get_indexer(target) 

667 

668 indexer, _ = self._engine.get_indexer_non_unique(codes) 

669 return ensure_platform_int(indexer) 

670 

671 @Appender(_index_shared_docs["get_indexer_non_unique"] % _index_doc_kwargs) 

672 def get_indexer_non_unique(self, target): 

673 target = ibase.ensure_index(target) 

674 

675 if isinstance(target, CategoricalIndex): 

676 # Indexing on codes is more efficient if categories are the same: 

677 if target.categories is self.categories: 

678 target = target.codes 

679 indexer, missing = self._engine.get_indexer_non_unique(target) 

680 return ensure_platform_int(indexer), missing 

681 target = target.values 

682 

683 codes = self.categories.get_indexer(target) 

684 indexer, missing = self._engine.get_indexer_non_unique(codes) 

685 return ensure_platform_int(indexer), missing 

686 

687 @Appender(_index_shared_docs["_convert_scalar_indexer"]) 

688 def _convert_scalar_indexer(self, key, kind=None): 

689 if kind == "loc": 

690 try: 

691 return self.categories._convert_scalar_indexer(key, kind=kind) 

692 except TypeError: 

693 self._invalid_indexer("label", key) 

694 return super()._convert_scalar_indexer(key, kind=kind) 

695 

696 @Appender(_index_shared_docs["_convert_list_indexer"]) 

697 def _convert_list_indexer(self, keyarr, kind=None): 

698 # Return our indexer or raise if all of the values are not included in 

699 # the categories 

700 

701 if self.categories._defer_to_indexing: 

702 indexer = self.categories._convert_list_indexer(keyarr, kind=kind) 

703 return Index(self.codes).get_indexer_for(indexer) 

704 

705 indexer = self.categories.get_indexer(np.asarray(keyarr)) 

706 if (indexer == -1).any(): 

707 raise KeyError( 

708 "a list-indexer must only include values that are in the categories" 

709 ) 

710 

711 return self.get_indexer(keyarr) 

712 

713 @Appender(_index_shared_docs["_convert_arr_indexer"]) 

714 def _convert_arr_indexer(self, keyarr): 

715 keyarr = com.asarray_tuplesafe(keyarr) 

716 

717 if self.categories._defer_to_indexing: 

718 return keyarr 

719 

720 return self._shallow_copy(keyarr) 

721 

722 @Appender(_index_shared_docs["_convert_index_indexer"]) 

723 def _convert_index_indexer(self, keyarr): 

724 return self._shallow_copy(keyarr) 

725 

726 def take_nd(self, *args, **kwargs): 

727 """Alias for `take`""" 

728 warnings.warn( 

729 "CategoricalIndex.take_nd is deprecated, use CategoricalIndex.take instead", 

730 FutureWarning, 

731 stacklevel=2, 

732 ) 

733 return self.take(*args, **kwargs) 

734 

735 @Appender(_index_shared_docs["_maybe_cast_slice_bound"]) 

736 def _maybe_cast_slice_bound(self, label, side, kind): 

737 if kind == "loc": 

738 return label 

739 

740 return super()._maybe_cast_slice_bound(label, side, kind) 

741 

742 def map(self, mapper): 

743 """ 

744 Map values using input correspondence (a dict, Series, or function). 

745 

746 Maps the values (their categories, not the codes) of the index to new 

747 categories. If the mapping correspondence is one-to-one the result is a 

748 :class:`~pandas.CategoricalIndex` which has the same order property as 

749 the original, otherwise an :class:`~pandas.Index` is returned. 

750 

751 If a `dict` or :class:`~pandas.Series` is used any unmapped category is 

752 mapped to `NaN`. Note that if this happens an :class:`~pandas.Index` 

753 will be returned. 

754 

755 Parameters 

756 ---------- 

757 mapper : function, dict, or Series 

758 Mapping correspondence. 

759 

760 Returns 

761 ------- 

762 pandas.CategoricalIndex or pandas.Index 

763 Mapped index. 

764 

765 See Also 

766 -------- 

767 Index.map : Apply a mapping correspondence on an 

768 :class:`~pandas.Index`. 

769 Series.map : Apply a mapping correspondence on a 

770 :class:`~pandas.Series`. 

771 Series.apply : Apply more complex functions on a 

772 :class:`~pandas.Series`. 

773 

774 Examples 

775 -------- 

776 >>> idx = pd.CategoricalIndex(['a', 'b', 'c']) 

777 >>> idx 

778 CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'], 

779 ordered=False, dtype='category') 

780 >>> idx.map(lambda x: x.upper()) 

781 CategoricalIndex(['A', 'B', 'C'], categories=['A', 'B', 'C'], 

782 ordered=False, dtype='category') 

783 >>> idx.map({'a': 'first', 'b': 'second', 'c': 'third'}) 

784 CategoricalIndex(['first', 'second', 'third'], categories=['first', 

785 'second', 'third'], ordered=False, dtype='category') 

786 

787 If the mapping is one-to-one the ordering of the categories is 

788 preserved: 

789 

790 >>> idx = pd.CategoricalIndex(['a', 'b', 'c'], ordered=True) 

791 >>> idx 

792 CategoricalIndex(['a', 'b', 'c'], categories=['a', 'b', 'c'], 

793 ordered=True, dtype='category') 

794 >>> idx.map({'a': 3, 'b': 2, 'c': 1}) 

795 CategoricalIndex([3, 2, 1], categories=[3, 2, 1], ordered=True, 

796 dtype='category') 

797 

798 If the mapping is not one-to-one an :class:`~pandas.Index` is returned: 

799 

800 >>> idx.map({'a': 'first', 'b': 'second', 'c': 'first'}) 

801 Index(['first', 'second', 'first'], dtype='object') 

802 

803 If a `dict` is used, all unmapped categories are mapped to `NaN` and 

804 the result is an :class:`~pandas.Index`: 

805 

806 >>> idx.map({'a': 'first', 'b': 'second'}) 

807 Index(['first', 'second', nan], dtype='object') 

808 """ 

809 return self._shallow_copy_with_infer(self.values.map(mapper)) 

810 

811 def delete(self, loc): 

812 """ 

813 Make new Index with passed location(-s) deleted 

814 

815 Returns 

816 ------- 

817 new_index : Index 

818 """ 

819 return self._create_from_codes(np.delete(self.codes, loc)) 

820 

821 def insert(self, loc, item): 

822 """ 

823 Make new Index inserting new item at location. Follows 

824 Python list.append semantics for negative values 

825 

826 Parameters 

827 ---------- 

828 loc : int 

829 item : object 

830 

831 Returns 

832 ------- 

833 new_index : Index 

834 

835 Raises 

836 ------ 

837 ValueError if the item is not in the categories 

838 

839 """ 

840 code = self.categories.get_indexer([item]) 

841 if (code == -1) and not (is_scalar(item) and isna(item)): 

842 raise TypeError( 

843 "cannot insert an item into a CategoricalIndex " 

844 "that is not already an existing category" 

845 ) 

846 

847 codes = self.codes 

848 codes = np.concatenate((codes[:loc], code, codes[loc:])) 

849 return self._create_from_codes(codes) 

850 

851 def _concat(self, to_concat, name): 

852 # if calling index is category, don't check dtype of others 

853 return CategoricalIndex._concat_same_dtype(self, to_concat, name) 

854 

855 def _concat_same_dtype(self, to_concat, name): 

856 """ 

857 Concatenate to_concat which has the same class 

858 ValueError if other is not in the categories 

859 """ 

860 codes = np.concatenate([self._is_dtype_compat(c).codes for c in to_concat]) 

861 result = self._create_from_codes(codes, name=name) 

862 # if name is None, _create_from_codes sets self.name 

863 result.name = name 

864 return result 

865 

866 def _delegate_property_get(self, name, *args, **kwargs): 

867 """ method delegation to the ._values """ 

868 prop = getattr(self._values, name) 

869 return prop # no wrapping for now 

870 

871 def _delegate_method(self, name, *args, **kwargs): 

872 """ method delegation to the ._values """ 

873 method = getattr(self._values, name) 

874 if "inplace" in kwargs: 

875 raise ValueError("cannot use inplace with CategoricalIndex") 

876 res = method(*args, **kwargs) 

877 if is_scalar(res) or name in self._raw_inherit: 

878 return res 

879 return CategoricalIndex(res, name=self.name) 

880 

881 

882CategoricalIndex._add_numeric_methods_add_sub_disabled() 

883CategoricalIndex._add_numeric_methods_disabled() 

884CategoricalIndex._add_logical_methods_disabled()