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

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

289 statements  

1""" 

2Base and utility classes for pandas objects. 

3""" 

4 

5from __future__ import annotations 

6 

7from typing import ( 

8 TYPE_CHECKING, 

9 Any, 

10 Generic, 

11 Literal, 

12 Self, 

13 cast, 

14 final, 

15 overload, 

16) 

17import warnings 

18 

19import numpy as np 

20 

21from pandas._libs import lib 

22from pandas._typing import ( 

23 AxisInt, 

24 DtypeObj, 

25 IndexLabel, 

26 NDFrameT, 

27 Shape, 

28 npt, 

29) 

30from pandas.compat import PYPY 

31from pandas.compat.numpy import function as nv 

32from pandas.errors import ( 

33 AbstractMethodError, 

34 Pandas4Warning, 

35) 

36from pandas.util._decorators import cache_readonly 

37from pandas.util._exceptions import find_stack_level 

38 

39from pandas.core.dtypes.cast import ( 

40 can_hold_element, 

41 maybe_unbox_numpy_scalar, 

42) 

43from pandas.core.dtypes.common import ( 

44 is_object_dtype, 

45 is_scalar, 

46) 

47from pandas.core.dtypes.dtypes import ExtensionDtype 

48from pandas.core.dtypes.generic import ( 

49 ABCDataFrame, 

50 ABCIndex, 

51 ABCMultiIndex, 

52 ABCSeries, 

53) 

54from pandas.core.dtypes.missing import ( 

55 isna, 

56 remove_na_arraylike, 

57) 

58 

59from pandas.core import ( 

60 algorithms, 

61 nanops, 

62 ops, 

63) 

64from pandas.core.accessor import DirNamesMixin 

65from pandas.core.arraylike import OpsMixin 

66from pandas.core.arrays import ExtensionArray 

67from pandas.core.construction import ( 

68 ensure_wrapped_if_datetimelike, 

69 extract_array, 

70) 

71 

72if TYPE_CHECKING: 

73 from collections.abc import ( 

74 Hashable, 

75 Iterator, 

76 ) 

77 

78 from pandas._typing import ( 

79 DropKeep, 

80 NumpySorter, 

81 NumpyValueArrayLike, 

82 ScalarLike_co, 

83 ) 

84 

85 from pandas import ( 

86 DataFrame, 

87 Index, 

88 Series, 

89 ) 

90 

91 

92class PandasObject(DirNamesMixin): 

93 """ 

94 Base class for various pandas objects. 

95 """ 

96 

97 # results from calls to methods decorated with cache_readonly get added to _cache 

98 _cache: dict[str, Any] 

99 

100 @property 

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

102 """ 

103 Class constructor (for this class it's just `__class__`). 

104 """ 

105 return type(self) 

106 

107 def __repr__(self) -> str: 

108 """ 

109 Return a string representation for a particular object. 

110 """ 

111 # Should be overwritten by base classes 

112 return object.__repr__(self) 

113 

114 def _reset_cache(self, key: str | None = None) -> None: 

115 """ 

116 Reset cached properties. If ``key`` is passed, only clears that key. 

117 """ 

118 if not hasattr(self, "_cache"): 

119 return 

120 if key is None: 

121 self._cache.clear() 

122 else: 

123 self._cache.pop(key, None) 

124 

125 def __sizeof__(self) -> int: 

126 """ 

127 Generates the total memory usage for an object that returns 

128 either a value or Series of values 

129 """ 

130 memory_usage = getattr(self, "memory_usage", None) 

131 if memory_usage: 

132 mem = memory_usage(deep=True) 

133 return int(mem if is_scalar(mem) else mem.sum()) 

134 

135 # no memory_usage attribute, so fall back to object's 'sizeof' 

136 return super().__sizeof__() 

137 

138 

139class NoNewAttributesMixin: 

140 """ 

141 Mixin which prevents adding new attributes. 

142 

143 Prevents additional attributes via xxx.attribute = "something" after a 

144 call to `self.__freeze()`. Mainly used to prevent the user from using 

145 wrong attributes on an accessor (`Series.cat/.str/.dt`). 

146 

147 If you really want to add a new attribute at a later time, you need to use 

148 `object.__setattr__(self, key, value)`. 

149 """ 

150 

151 def _freeze(self) -> None: 

152 """ 

153 Prevents setting additional attributes. 

154 """ 

155 object.__setattr__(self, "__frozen", True) 

156 

157 # prevent adding any attribute via s.xxx.new_attribute = ... 

158 def __setattr__(self, key: str, value) -> None: 

159 # _cache is used by a decorator 

160 # We need to check both 1.) cls.__dict__ and 2.) getattr(self, key) 

161 # because 

162 # 1.) getattr is false for attributes that raise errors 

163 # 2.) cls.__dict__ doesn't traverse into base classes 

164 if getattr(self, "__frozen", False) and not ( 

165 key == "_cache" 

166 or key in type(self).__dict__ 

167 or getattr(self, key, None) is not None 

168 ): 

169 raise AttributeError(f"You cannot add any new attribute '{key}'") 

170 object.__setattr__(self, key, value) 

171 

172 

173class SelectionMixin(Generic[NDFrameT]): 

174 """ 

175 mixin implementing the selection & aggregation interface on a group-like 

176 object sub-classes need to define: obj, exclusions 

177 """ 

178 

179 obj: NDFrameT 

180 _selection: IndexLabel | None = None 

181 exclusions: frozenset[Hashable] 

182 _internal_names = ["_cache", "__setstate__"] 

183 _internal_names_set = set(_internal_names) 

184 

185 @final 

186 @property 

187 def _selection_list(self): 

188 if not isinstance( 

189 self._selection, (list, tuple, ABCSeries, ABCIndex, np.ndarray) 

190 ): 

191 return [self._selection] 

192 return self._selection 

193 

194 @cache_readonly 

195 def _selected_obj(self): 

196 if self._selection is None or isinstance(self.obj, ABCSeries): 

197 return self.obj 

198 else: 

199 return self.obj[self._selection] 

200 

201 @final 

202 @cache_readonly 

203 def ndim(self) -> int: 

204 return self._selected_obj.ndim 

205 

206 @final 

207 @cache_readonly 

208 def _obj_with_exclusions(self): 

209 if isinstance(self.obj, ABCSeries): 

210 return self.obj 

211 

212 if self._selection is not None: 

213 return self.obj[self._selection_list] 

214 

215 if len(self.exclusions) > 0: 

216 return self.obj._drop_axis(self.exclusions, axis=1) 

217 else: 

218 return self.obj 

219 

220 def __getitem__(self, key): 

221 if self._selection is not None: 

222 raise IndexError(f"Column(s) {self._selection} already selected") 

223 

224 if isinstance(key, tuple): 

225 warnings.warn( 

226 "Passing a tuple to __getitem__ is deprecated and " 

227 "will raise a KeyError in a future version. Use a list instead.", 

228 Pandas4Warning, 

229 stacklevel=find_stack_level(), 

230 ) 

231 key = list(key) 

232 

233 if isinstance(key, (list, ABCSeries, ABCIndex, np.ndarray)): 

234 if len(self.obj.columns.intersection(key)) != len(set(key)): 

235 bad_keys = list(set(key).difference(self.obj.columns)) 

236 raise KeyError(f"Columns not found: {str(bad_keys)[1:-1]}") 

237 return self._gotitem(list(key), ndim=2) 

238 

239 else: 

240 if key not in self.obj: 

241 raise KeyError(f"Column not found: {key}") 

242 ndim = self.obj[key].ndim 

243 return self._gotitem(key, ndim=ndim) 

244 

245 def _gotitem(self, key, ndim: int, subset=None): 

246 """ 

247 sub-classes to define 

248 return a sliced object 

249 

250 Parameters 

251 ---------- 

252 key : str / list of selections 

253 ndim : {1, 2} 

254 requested ndim of result 

255 subset : object, default None 

256 subset to act on 

257 """ 

258 raise AbstractMethodError(self) 

259 

260 @final 

261 def _infer_selection(self, key, subset: Series | DataFrame): 

262 """ 

263 Infer the `selection` to pass to our constructor in _gotitem. 

264 """ 

265 # Shared by Rolling and Resample 

266 selection = None 

267 if subset.ndim == 2 and ( 

268 (lib.is_scalar(key) and key in subset) or lib.is_list_like(key) 

269 ): 

270 selection = key 

271 elif subset.ndim == 1 and lib.is_scalar(key) and key == subset.name: 

272 selection = key 

273 return selection 

274 

275 def aggregate(self, func, *args, **kwargs): 

276 raise AbstractMethodError(self) 

277 

278 agg = aggregate 

279 

280 

281class IndexOpsMixin(OpsMixin): 

282 """ 

283 Common ops mixin to support a unified interface / docs for Series / Index 

284 """ 

285 

286 # ndarray compatibility 

287 __array_priority__ = 1000 

288 _hidden_attrs: frozenset[str] = frozenset( 

289 ["tolist"] # tolist is not deprecated, just suppressed in the __dir__ 

290 ) 

291 

292 @property 

293 def dtype(self) -> DtypeObj: 

294 # must be defined here as a property for mypy 

295 raise AbstractMethodError(self) 

296 

297 @property 

298 def _values(self) -> ExtensionArray | np.ndarray: 

299 # must be defined here as a property for mypy 

300 raise AbstractMethodError(self) 

301 

302 @final 

303 def transpose(self, *args, **kwargs) -> Self: 

304 """ 

305 Return the transpose, which is by definition self. 

306 

307 Returns 

308 ------- 

309 %(klass)s 

310 """ 

311 nv.validate_transpose(args, kwargs) 

312 return self 

313 

314 T = property( 

315 transpose, 

316 doc=""" 

317 Return the transpose, which is by definition self. 

318 

319 For 1D objects (Series, Index) transposition has no effect; the 

320 object is returned unchanged. Provided for API compatibility with 

321 DataFrame and NumPy. 

322 

323 See Also 

324 -------- 

325 Index : Immutable sequence used for indexing and alignment. 

326 

327 Examples 

328 -------- 

329 For Series: 

330 

331 >>> s = pd.Series(['Ant', 'Bear', 'Cow']) 

332 >>> s 

333 0 Ant 

334 1 Bear 

335 2 Cow 

336 dtype: str 

337 >>> s.T 

338 0 Ant 

339 1 Bear 

340 2 Cow 

341 dtype: str 

342 

343 For Index: 

344 

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

346 >>> idx.T 

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

348 """, 

349 ) 

350 

351 @property 

352 def shape(self) -> Shape: 

353 """ 

354 Return a tuple of the shape of the underlying data. 

355 

356 For a Series this is ``(length,)``; for a 1D Index, likewise a 

357 single-element tuple. Useful for compatibility with NumPy and 

358 other array-like APIs. 

359 

360 See Also 

361 -------- 

362 Series.ndim : Number of dimensions of the underlying data. 

363 Series.size : Return the number of elements in the underlying data. 

364 Series.nbytes : Return the number of bytes in the underlying data. 

365 

366 Examples 

367 -------- 

368 >>> s = pd.Series([1, 2, 3]) 

369 >>> s.shape 

370 (3,) 

371 """ 

372 return self._values.shape 

373 

374 def __len__(self) -> int: 

375 # We need this defined here for mypy 

376 raise AbstractMethodError(self) 

377 

378 # Temporarily avoid using `-> Literal[1]:` because of an IPython (jedi) bug 

379 # https://github.com/ipython/ipython/issues/14412 

380 # https://github.com/davidhalter/jedi/issues/1990 

381 @property 

382 def ndim(self) -> int: 

383 """ 

384 Number of dimensions of the underlying data, by definition 1. 

385 

386 Series and Index are one-dimensional; this property always returns 1 

387 for compatibility with array-like interfaces. 

388 

389 See Also 

390 -------- 

391 Series.size: Return the number of elements in the underlying data. 

392 Series.shape: Return a tuple of the shape of the underlying data. 

393 Series.dtype: Return the dtype object of the underlying data. 

394 Series.values: Return Series as ndarray or ndarray-like depending on the dtype. 

395 

396 Examples 

397 -------- 

398 >>> s = pd.Series(["Ant", "Bear", "Cow"]) 

399 >>> s 

400 0 Ant 

401 1 Bear 

402 2 Cow 

403 dtype: str 

404 >>> s.ndim 

405 1 

406 

407 For Index: 

408 

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

410 >>> idx 

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

412 >>> idx.ndim 

413 1 

414 """ 

415 return 1 

416 

417 @final 

418 def item(self): 

419 """ 

420 Return the first element of the underlying data as a Python scalar. 

421 

422 This method extracts the single element from a length-1 Series or 

423 Index and returns it as a native Python scalar type (e.g., ``int``, 

424 ``float``, ``str``). 

425 

426 Returns 

427 ------- 

428 scalar 

429 The first element of Series or Index. 

430 

431 Raises 

432 ------ 

433 ValueError 

434 If the data is not length = 1. 

435 

436 See Also 

437 -------- 

438 Index.values : Returns an array representing the data in the Index. 

439 Series.head : Returns the first `n` rows. 

440 

441 Examples 

442 -------- 

443 >>> s = pd.Series([1]) 

444 >>> s.item() 

445 1 

446 

447 For an index: 

448 

449 >>> s = pd.Series([1], index=["a"]) 

450 >>> s.index.item() 

451 'a' 

452 """ 

453 if len(self) == 1: 

454 return next(iter(self)) 

455 raise ValueError("can only convert an array of size 1 to a Python scalar") 

456 

457 @property 

458 def nbytes(self) -> int: 

459 """ 

460 Return the number of bytes in the underlying data. 

461 

462 Includes only the memory used by the array values; overhead such as 

463 the index is not included. Useful for estimating memory usage. 

464 

465 See Also 

466 -------- 

467 Series.ndim : Number of dimensions of the underlying data. 

468 Series.size : Return the number of elements in the underlying data. 

469 

470 Examples 

471 -------- 

472 For Series: 

473 

474 >>> s = pd.Series(["Ant", "Bear", "Cow"]) 

475 >>> s 

476 0 Ant 

477 1 Bear 

478 2 Cow 

479 dtype: str 

480 >>> s.nbytes 

481 34 

482 

483 For Index: 

484 

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

486 >>> idx 

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

488 >>> idx.nbytes 

489 24 

490 """ 

491 return self._values.nbytes 

492 

493 @property 

494 def size(self) -> int: 

495 """ 

496 Return the number of elements in the underlying data. 

497 

498 For a Series or Index this is the length (number of rows). Equivalent 

499 to ``len(obj)`` and ``np.prod(obj.shape)``. 

500 

501 See Also 

502 -------- 

503 Series.ndim: Number of dimensions of the underlying data, by definition 1. 

504 Series.shape: Return a tuple of the shape of the underlying data. 

505 Series.dtype: Return the dtype object of the underlying data. 

506 Series.values: Return Series as ndarray or ndarray-like depending on the dtype. 

507 

508 Examples 

509 -------- 

510 For Series: 

511 

512 >>> s = pd.Series(["Ant", "Bear", "Cow"]) 

513 >>> s 

514 0 Ant 

515 1 Bear 

516 2 Cow 

517 dtype: str 

518 >>> s.size 

519 3 

520 

521 For Index: 

522 

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

524 >>> idx 

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

526 >>> idx.size 

527 3 

528 """ 

529 return len(self._values) 

530 

531 @property 

532 def array(self) -> ExtensionArray: 

533 """ 

534 The ExtensionArray of the data backing this Series or Index. 

535 

536 This property provides direct access to the underlying array data of a 

537 Series or Index without requiring conversion to a NumPy array. It 

538 returns an ExtensionArray, which is the native storage format for 

539 pandas extension dtypes. 

540 

541 Returns 

542 ------- 

543 ExtensionArray 

544 An ExtensionArray of the values stored within. For extension 

545 types, this is the actual array. For NumPy native types, this 

546 is a thin (no copy) wrapper around :class:`numpy.ndarray`. 

547 

548 ``.array`` differs from ``.values``, which may require converting 

549 the data to a different form. 

550 

551 See Also 

552 -------- 

553 Index.to_numpy : Similar method that always returns a NumPy array. 

554 Series.to_numpy : Similar method that always returns a NumPy array. 

555 

556 Notes 

557 ----- 

558 This table lays out the different array types for each extension 

559 dtype within pandas. 

560 

561 ================== ============================= 

562 dtype array type 

563 ================== ============================= 

564 category Categorical 

565 period PeriodArray 

566 interval IntervalArray 

567 IntegerNA IntegerArray 

568 string StringArray 

569 boolean BooleanArray 

570 datetime64[ns, tz] DatetimeArray 

571 ================== ============================= 

572 

573 For any 3rd-party extension types, the array type will be an 

574 ExtensionArray. 

575 

576 For all remaining dtypes ``.array`` will be a 

577 :class:`arrays.NumpyExtensionArray` wrapping the actual ndarray 

578 stored within. If you absolutely need a NumPy array (possibly with 

579 copying / coercing data), then use :meth:`Series.to_numpy` instead. 

580 

581 Examples 

582 -------- 

583 For regular NumPy types like int, and float, a NumpyExtensionArray 

584 is returned. 

585 

586 >>> pd.Series([1, 2, 3]).array 

587 <NumpyExtensionArray> 

588 [1, 2, 3] 

589 Length: 3, dtype: int64 

590 

591 For extension types, like Categorical, the actual ExtensionArray 

592 is returned 

593 

594 >>> ser = pd.Series(pd.Categorical(["a", "b", "a"])) 

595 >>> ser.array 

596 ['a', 'b', 'a'] 

597 Categories (2, str): ['a', 'b'] 

598 """ 

599 raise AbstractMethodError(self) 

600 

601 def to_numpy( 

602 self, 

603 dtype: npt.DTypeLike | None = None, 

604 copy: bool = False, 

605 na_value: object = lib.no_default, 

606 **kwargs, 

607 ) -> np.ndarray: 

608 """ 

609 A NumPy ndarray representing the values in this Series or Index. 

610 

611 This method converts the underlying data to a NumPy array, optionally 

612 specifying the desired dtype and handling of missing values. For 

613 extension types, this may require copying data and coercing to a NumPy 

614 type. 

615 

616 Parameters 

617 ---------- 

618 dtype : str or numpy.dtype, optional 

619 The dtype to pass to :meth:`numpy.asarray`. 

620 copy : bool, default False 

621 Whether to ensure that the returned value is not a view on 

622 another array. Note that ``copy=False`` does not *ensure* that 

623 ``to_numpy()`` is no-copy. Rather, ``copy=True`` ensure that 

624 a copy is made, even if not strictly necessary. 

625 na_value : Any, optional 

626 The value to use for missing values. The default value depends 

627 on `dtype` and the type of the array. 

628 **kwargs 

629 Additional keywords passed through to the ``to_numpy`` method 

630 of the underlying array (for extension arrays). 

631 

632 Returns 

633 ------- 

634 numpy.ndarray 

635 The NumPy ndarray holding the values from this Series or Index. 

636 The dtype of the array may differ. See Notes. 

637 

638 See Also 

639 -------- 

640 Series.array : Get the actual data stored within. 

641 Index.array : Get the actual data stored within. 

642 DataFrame.to_numpy : Similar method for DataFrame. 

643 

644 Notes 

645 ----- 

646 The returned array will be the same up to equality (values equal 

647 in `self` will be equal in the returned array; likewise for values 

648 that are not equal). When `self` contains an ExtensionArray, the 

649 dtype may be different. For example, for a category-dtype Series, 

650 ``to_numpy()`` will return a NumPy array and the categorical dtype 

651 will be lost. 

652 

653 For NumPy dtypes, this will be a reference to the actual data stored 

654 in this Series or Index (assuming ``copy=False``). Modifying the result 

655 in place will modify the data stored in the Series or Index (not that 

656 we recommend doing that). 

657 

658 For extension types, ``to_numpy()`` *may* require copying data and 

659 coercing the result to a NumPy type (possibly object), which may be 

660 expensive. When you need a no-copy reference to the underlying data, 

661 :attr:`Series.array` should be used instead. 

662 

663 This table lays out the different dtypes and default return types of 

664 ``to_numpy()`` for various dtypes within pandas. 

665 

666 ================== ================================ 

667 dtype array type 

668 ================== ================================ 

669 category[T] ndarray[T] (same dtype as input) 

670 period ndarray[object] (Periods) 

671 interval ndarray[object] (Intervals) 

672 IntegerNA ndarray[object] 

673 datetime64[ns] datetime64[ns] 

674 datetime64[ns, tz] ndarray[object] (Timestamps) 

675 ================== ================================ 

676 

677 For timezone-aware datetime data, calling ``to_numpy()`` without 

678 specifying ``dtype`` produces an object-dtype array of 

679 :class:`Timestamp` objects. This is significantly slower and uses 

680 more memory than a native ``datetime64`` array. To avoid this, 

681 pass an explicit ``dtype``: 

682 

683 - ``dtype="datetime64[ns]"`` converts to UTC and drops the 

684 timezone, returning a native ``datetime64[ns]`` array. 

685 - ``dtype=object`` explicitly requests Timestamp objects when 

686 you need to preserve per-element timezone information. 

687 

688 Examples 

689 -------- 

690 >>> ser = pd.Series(pd.Categorical(["a", "b", "a"])) 

691 >>> ser.to_numpy() 

692 array(['a', 'b', 'a'], dtype=object) 

693 

694 Specify the `dtype` to control how datetime-aware data is represented. 

695 Use ``dtype=object`` to return an ndarray of pandas :class:`Timestamp` 

696 objects, each with the correct ``tz``. 

697 

698 >>> ser = pd.Series(pd.date_range("2000", periods=2, tz="CET")) 

699 >>> ser.to_numpy(dtype=object) 

700 array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'), 

701 Timestamp('2000-01-02 00:00:00+0100', tz='CET')], 

702 dtype=object) 

703 

704 Or ``dtype='datetime64[ns]'`` to return an ndarray of native 

705 datetime64 values. The values are converted to UTC and the timezone 

706 info is dropped. 

707 

708 >>> ser.to_numpy(dtype="datetime64[ns]") 

709 ... # doctest: +ELLIPSIS 

710 array(['1999-12-31T23:00:00.000000000', '2000-01-01T23:00:00...'], 

711 dtype='datetime64[ns]') 

712 """ 

713 if isinstance(self.dtype, ExtensionDtype): 

714 return self.array.to_numpy(dtype, copy=copy, na_value=na_value, **kwargs) 

715 elif kwargs: 

716 bad_keys = next(iter(kwargs.keys())) 

717 raise TypeError( 

718 f"to_numpy() got an unexpected keyword argument '{bad_keys}'" 

719 ) 

720 

721 fillna = ( 

722 na_value is not lib.no_default 

723 # no need to fillna with np.nan if we already have a float dtype 

724 and not (na_value is np.nan and np.issubdtype(self.dtype, np.floating)) 

725 ) 

726 

727 values = self._values 

728 if fillna and self.hasnans: 

729 if not can_hold_element(values, na_value): 

730 # if we can't hold the na_value asarray either makes a copy or we 

731 # error before modifying values. The asarray later on thus won't make 

732 # another copy 

733 values = np.asarray(values, dtype=dtype) 

734 else: 

735 values = values.copy() 

736 

737 values[np.asanyarray(isna(self))] = na_value 

738 

739 result = np.asarray(values, dtype=dtype) 

740 

741 if (copy and not fillna) or not copy: 

742 # Check identity first (O(1)) before the more expensive 

743 # shares_memory check. Use the already-fetched `values` to 

744 # avoid a second _values property access. 

745 if result is values or np.shares_memory(values[:2], result[:2]): 

746 if not copy: 

747 result = result.view() 

748 result.flags.writeable = False 

749 else: 

750 result = result.copy() 

751 

752 return result 

753 

754 @final 

755 @property 

756 def empty(self) -> bool: 

757 """ 

758 Indicator whether Index is empty. 

759 

760 An Index is considered empty if it has no elements. This property can be 

761 useful for quickly checking the state of an Index, especially in data 

762 processing and analysis workflows where handling of empty datasets might 

763 be required. 

764 

765 Returns 

766 ------- 

767 bool 

768 If Index is empty, return True, if not return False. 

769 

770 See Also 

771 -------- 

772 Index.size : Return the number of elements in the underlying data. 

773 

774 Examples 

775 -------- 

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

777 >>> idx 

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

779 >>> idx.empty 

780 False 

781 

782 >>> idx_empty = pd.Index([]) 

783 >>> idx_empty 

784 Index([], dtype='object') 

785 >>> idx_empty.empty 

786 True 

787 

788 If we only have NaNs in our DataFrame, it is not considered empty! 

789 

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

791 >>> idx 

792 Index([nan, nan], dtype='float64') 

793 >>> idx.empty 

794 False 

795 """ 

796 return not self.size 

797 

798 def argmax( 

799 self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs 

800 ) -> int: 

801 """ 

802 Return int position of the largest value in the Series. 

803 

804 If the maximum is achieved in multiple locations, 

805 the first row position is returned. 

806 

807 Parameters 

808 ---------- 

809 axis : None 

810 Unused. Parameter needed for compatibility with DataFrame. 

811 skipna : bool, default True 

812 Exclude NA/null values. If the entire Series is NA, or if ``skipna=False`` 

813 and there is an NA value, this method will raise a ``ValueError``. 

814 *args, **kwargs 

815 Additional arguments and keywords for compatibility with NumPy. 

816 

817 Returns 

818 ------- 

819 int 

820 Row position of the maximum value. 

821 

822 See Also 

823 -------- 

824 Series.argmax : Return position of the maximum value. 

825 Series.argmin : Return position of the minimum value. 

826 numpy.ndarray.argmax : Equivalent method for numpy arrays. 

827 Series.idxmax : Return index label of the maximum values. 

828 Series.idxmin : Return index label of the minimum values. 

829 

830 Examples 

831 -------- 

832 Consider dataset containing cereal calories 

833 

834 >>> s = pd.Series( 

835 ... [100.0, 110.0, 120.0, 110.0], 

836 ... index=[ 

837 ... "Corn Flakes", 

838 ... "Almond Delight", 

839 ... "Cinnamon Toast Crunch", 

840 ... "Cocoa Puff", 

841 ... ], 

842 ... ) 

843 >>> s 

844 Corn Flakes 100.0 

845 Almond Delight 110.0 

846 Cinnamon Toast Crunch 120.0 

847 Cocoa Puff 110.0 

848 dtype: float64 

849 

850 >>> s.argmax() 

851 2 

852 >>> s.argmin() 

853 0 

854 

855 The maximum cereal calories is the third element and 

856 the minimum cereal calories is the first element, 

857 since series is zero-indexed. 

858 """ 

859 delegate = self._values 

860 nv.validate_minmax_axis(axis) 

861 skipna = nv.validate_argmax_with_skipna(skipna, args, kwargs) 

862 

863 if isinstance(delegate, ExtensionArray): 

864 result = delegate.argmax(skipna=skipna) 

865 else: 

866 # error: Incompatible types in assignment (expression has type 

867 # "int | ndarray", variable has type "int") 

868 result = nanops.nanargmax( # type: ignore[assignment] 

869 delegate, skipna=skipna 

870 ) 

871 return maybe_unbox_numpy_scalar(result) 

872 

873 def argmin( 

874 self, axis: AxisInt | None = None, skipna: bool = True, *args, **kwargs 

875 ) -> int: 

876 """ 

877 Return int position of the smallest value in the Series. 

878 

879 If the minimum is achieved in multiple locations, 

880 the first row position is returned. 

881 

882 Parameters 

883 ---------- 

884 axis : None 

885 Unused. Parameter needed for compatibility with DataFrame. 

886 skipna : bool, default True 

887 Exclude NA/null values. If the entire Series is NA, or if ``skipna=False`` 

888 and there is an NA value, this method will raise a ``ValueError``. 

889 *args, **kwargs 

890 Additional arguments and keywords for compatibility with NumPy. 

891 

892 Returns 

893 ------- 

894 int 

895 Row position of the minimum value. 

896 

897 See Also 

898 -------- 

899 Series.argmin : Return position of the minimum value. 

900 Series.argmax : Return position of the maximum value. 

901 numpy.ndarray.argmin : Equivalent method for numpy arrays. 

902 Series.idxmin : Return index label of the minimum values. 

903 Series.idxmax : Return index label of the maximum values. 

904 

905 Examples 

906 -------- 

907 Consider dataset containing cereal calories 

908 

909 >>> s = pd.Series( 

910 ... [100.0, 110.0, 120.0, 110.0], 

911 ... index=[ 

912 ... "Corn Flakes", 

913 ... "Almond Delight", 

914 ... "Cinnamon Toast Crunch", 

915 ... "Cocoa Puff", 

916 ... ], 

917 ... ) 

918 >>> s 

919 Corn Flakes 100.0 

920 Almond Delight 110.0 

921 Cinnamon Toast Crunch 120.0 

922 Cocoa Puff 110.0 

923 dtype: float64 

924 

925 >>> s.argmax() 

926 2 

927 >>> s.argmin() 

928 0 

929 

930 The maximum cereal calories is the third element and 

931 the minimum cereal calories is the first element, 

932 since series is zero-indexed. 

933 """ 

934 delegate = self._values 

935 nv.validate_minmax_axis(axis) 

936 skipna = nv.validate_argmax_with_skipna(skipna, args, kwargs) 

937 

938 if isinstance(delegate, ExtensionArray): 

939 result = delegate.argmin(skipna=skipna) 

940 else: 

941 # error: Incompatible types in assignment (expression has type 

942 # "int | ndarray", variable has type "int") 

943 result = nanops.nanargmin( # type: ignore[assignment] 

944 delegate, skipna=skipna 

945 ) 

946 return maybe_unbox_numpy_scalar(result) 

947 

948 def tolist(self) -> list[Any]: 

949 """ 

950 Return a list of the values. 

951 

952 These are each a scalar type, which is a Python scalar 

953 (for str, int, float) or a pandas scalar 

954 (for Timestamp/Timedelta/Interval/Period) 

955 

956 Returns 

957 ------- 

958 list 

959 List containing the values as Python or pandas scalers. 

960 

961 See Also 

962 -------- 

963 numpy.ndarray.tolist : Return the array as an a.ndim-levels deep 

964 nested list of Python scalars. 

965 

966 Examples 

967 -------- 

968 For Series 

969 

970 >>> s = pd.Series([1, 2, 3]) 

971 >>> s.to_list() 

972 [1, 2, 3] 

973 

974 For Index: 

975 

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

977 >>> idx 

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

979 

980 >>> idx.to_list() 

981 [1, 2, 3] 

982 """ 

983 return self._values.tolist() 

984 

985 to_list = tolist 

986 

987 def __iter__(self) -> Iterator[Any]: 

988 """ 

989 Return an iterator of the values. 

990 

991 These are each a scalar type, which is a Python scalar 

992 (for str, int, float) or a pandas scalar 

993 (for Timestamp/Timedelta/Interval/Period) 

994 

995 Returns 

996 ------- 

997 iterator 

998 An iterator yielding scalar values from the Series. 

999 

1000 See Also 

1001 -------- 

1002 Series.items : Lazily iterate over (index, value) tuples. 

1003 

1004 Examples 

1005 -------- 

1006 >>> s = pd.Series([1, 2, 3]) 

1007 >>> for x in s: 

1008 ... print(x) 

1009 1 

1010 2 

1011 3 

1012 """ 

1013 # We are explicitly making element iterators. 

1014 if not isinstance(self._values, np.ndarray): 

1015 # Check type instead of dtype to catch DTA/TDA 

1016 return iter(self._values) 

1017 else: 

1018 return map(self._values.item, range(self._values.size)) 

1019 

1020 @cache_readonly 

1021 def hasnans(self) -> bool: 

1022 """ 

1023 Return True if there are any NaNs. 

1024 

1025 Enables various performance speedups. 

1026 

1027 Returns 

1028 ------- 

1029 bool 

1030 

1031 See Also 

1032 -------- 

1033 Series.isna : Detect missing values. 

1034 Series.notna : Detect existing (non-missing) values. 

1035 

1036 Examples 

1037 -------- 

1038 >>> s = pd.Series([1, 2, 3, None]) 

1039 >>> s 

1040 0 1.0 

1041 1 2.0 

1042 2 3.0 

1043 3 NaN 

1044 dtype: float64 

1045 >>> s.hasnans 

1046 True 

1047 """ 

1048 # error: Item "bool" of "Union[bool, ndarray[Any, dtype[bool_]], NDFrame]" 

1049 # has no attribute "any" 

1050 return bool(isna(self).any()) # type: ignore[union-attr] 

1051 

1052 @final 

1053 def _map_values(self, mapper, na_action=None): 

1054 """ 

1055 An internal function that maps values using the input 

1056 correspondence (which can be a dict, Series, or function). 

1057 

1058 Parameters 

1059 ---------- 

1060 mapper : function, dict, or Series 

1061 The input correspondence object 

1062 na_action : {None, 'ignore'} 

1063 If 'ignore', propagate NA values, without passing them to the 

1064 mapping function 

1065 

1066 Returns 

1067 ------- 

1068 Union[Index, MultiIndex], inferred 

1069 The output of the mapping function applied to the index. 

1070 If the function returns a tuple with more than one element 

1071 a MultiIndex will be returned. 

1072 """ 

1073 arr = self._values 

1074 

1075 if isinstance(arr, ExtensionArray): 

1076 return arr.map(mapper, na_action=na_action) 

1077 

1078 return algorithms.map_array(arr, mapper, na_action=na_action) 

1079 

1080 def value_counts( 

1081 self, 

1082 normalize: bool = False, 

1083 sort: bool = True, 

1084 ascending: bool = False, 

1085 bins=None, 

1086 dropna: bool = True, 

1087 ) -> Series: 

1088 """ 

1089 Return a Series containing counts of unique values. 

1090 

1091 The resulting object will be in descending order so that the 

1092 first element is the most frequently-occurring element. 

1093 Excludes NA values by default. 

1094 

1095 Parameters 

1096 ---------- 

1097 normalize : bool, default False 

1098 If True then the object returned will contain the relative 

1099 frequencies of the unique values. 

1100 sort : bool, default True 

1101 Stable sort by frequencies when True. Preserve the order of the data 

1102 when False. 

1103 

1104 .. versionchanged:: 3.0.0 

1105 

1106 Prior to 3.0.0, the sort was unstable. 

1107 ascending : bool, default False 

1108 Sort in ascending order. 

1109 bins : int, optional 

1110 Rather than count values, group them into half-open bins, 

1111 a convenience for ``pd.cut``, only works with numeric data. 

1112 dropna : bool, default True 

1113 Don't include counts of NaN. 

1114 

1115 Returns 

1116 ------- 

1117 Series 

1118 Series containing counts of unique values. 

1119 

1120 See Also 

1121 -------- 

1122 Series.count: Number of non-NA elements in a Series. 

1123 DataFrame.count: Number of non-NA elements in a DataFrame. 

1124 DataFrame.value_counts: Equivalent method on DataFrames. 

1125 

1126 Examples 

1127 -------- 

1128 >>> index = pd.Index([3, 1, 2, 3, 4, np.nan]) 

1129 >>> index.value_counts() 

1130 3.0 2 

1131 1.0 1 

1132 2.0 1 

1133 4.0 1 

1134 Name: count, dtype: int64 

1135 

1136 With `normalize` set to `True`, returns the relative frequency by 

1137 dividing all values by the sum of values. 

1138 

1139 >>> s = pd.Series([3, 1, 2, 3, 4, np.nan]) 

1140 >>> s.value_counts(normalize=True) 

1141 3.0 0.4 

1142 1.0 0.2 

1143 2.0 0.2 

1144 4.0 0.2 

1145 Name: proportion, dtype: float64 

1146 

1147 **bins** 

1148 

1149 Bins can be useful for going from a continuous variable to a 

1150 categorical variable; instead of counting unique 

1151 apparitions of values, divide the index in the specified 

1152 number of half-open bins. 

1153 

1154 >>> s.value_counts(bins=3) 

1155 (0.996, 2.0] 2 

1156 (2.0, 3.0] 2 

1157 (3.0, 4.0] 1 

1158 Name: count, dtype: int64 

1159 

1160 **dropna** 

1161 

1162 With `dropna` set to `False` we can also see NaN index values. 

1163 

1164 >>> s.value_counts(dropna=False) 

1165 3.0 2 

1166 1.0 1 

1167 2.0 1 

1168 4.0 1 

1169 NaN 1 

1170 Name: count, dtype: int64 

1171 

1172 **Categorical Dtypes** 

1173 

1174 Rows with categorical type will be counted as one group 

1175 if they have same categories and order. 

1176 In the example below, even though ``a``, ``c``, and ``d`` 

1177 all have the same data types of ``category``, 

1178 only ``c`` and ``d`` will be counted as one group 

1179 since ``a`` doesn't have the same categories. 

1180 

1181 >>> df = pd.DataFrame({"a": [1], "b": ["2"], "c": [3], "d": [3]}) 

1182 >>> df = df.astype({"a": "category", "c": "category", "d": "category"}) 

1183 >>> df 

1184 a b c d 

1185 0 1 2 3 3 

1186 

1187 >>> df.dtypes 

1188 a category 

1189 b str 

1190 c category 

1191 d category 

1192 dtype: object 

1193 

1194 >>> df.dtypes.value_counts() 

1195 category 2 

1196 category 1 

1197 str 1 

1198 Name: count, dtype: int64 

1199 """ 

1200 return algorithms.value_counts_internal( 

1201 self, 

1202 sort=sort, 

1203 ascending=ascending, 

1204 normalize=normalize, 

1205 bins=bins, 

1206 dropna=dropna, 

1207 ) 

1208 

1209 def unique(self): 

1210 values = self._values 

1211 if not isinstance(values, np.ndarray): 

1212 # i.e. ExtensionArray 

1213 result = values.unique() 

1214 else: 

1215 result = algorithms.unique1d(values) # type: ignore[assignment] 

1216 return result 

1217 

1218 @final 

1219 def nunique(self, dropna: bool = True) -> int: 

1220 """ 

1221 Return number of unique elements in the object. 

1222 

1223 Excludes NA values by default. 

1224 

1225 Parameters 

1226 ---------- 

1227 dropna : bool, default True 

1228 Don't include NaN in the count. 

1229 

1230 Returns 

1231 ------- 

1232 int 

1233 An integer indicating the number of unique elements in the object. 

1234 

1235 See Also 

1236 -------- 

1237 DataFrame.nunique: Method nunique for DataFrame. 

1238 Series.count: Count non-NA/null observations in the Series. 

1239 

1240 Examples 

1241 -------- 

1242 >>> s = pd.Series([1, 3, 5, 7, 7]) 

1243 >>> s 

1244 0 1 

1245 1 3 

1246 2 5 

1247 3 7 

1248 4 7 

1249 dtype: int64 

1250 

1251 >>> s.nunique() 

1252 4 

1253 """ 

1254 uniqs = self.unique() 

1255 if dropna: 

1256 uniqs = remove_na_arraylike(uniqs) 

1257 return len(uniqs) 

1258 

1259 @property 

1260 def is_unique(self) -> bool: 

1261 """ 

1262 Return True if values in the object are unique. 

1263 

1264 This property checks whether all values in the Series or Index are 

1265 distinct, including missing values. 

1266 

1267 Returns 

1268 ------- 

1269 bool 

1270 

1271 See Also 

1272 -------- 

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

1274 Series.drop_duplicates : Return Series with duplicate values removed. 

1275 Series.duplicated : Indicate duplicate Series values. 

1276 

1277 Examples 

1278 -------- 

1279 >>> s = pd.Series([1, 2, 3]) 

1280 >>> s.is_unique 

1281 True 

1282 

1283 >>> s = pd.Series([1, 2, 3, 1]) 

1284 >>> s.is_unique 

1285 False 

1286 """ 

1287 return self.nunique(dropna=False) == len(self) 

1288 

1289 @property 

1290 def is_monotonic_increasing(self) -> bool: 

1291 """ 

1292 Return True if values in the object are monotonically increasing. 

1293 

1294 This property checks whether each element is greater than or equal to 

1295 the previous element. Equal consecutive values are considered 

1296 monotonically increasing. 

1297 

1298 Returns 

1299 ------- 

1300 bool 

1301 

1302 See Also 

1303 -------- 

1304 Series.is_monotonic_decreasing : Return boolean if values in the object are 

1305 monotonically decreasing. 

1306 

1307 Examples 

1308 -------- 

1309 >>> s = pd.Series([1, 2, 2]) 

1310 >>> s.is_monotonic_increasing 

1311 True 

1312 

1313 >>> s = pd.Series([3, 2, 1]) 

1314 >>> s.is_monotonic_increasing 

1315 False 

1316 """ 

1317 from pandas import Index 

1318 

1319 return Index(self).is_monotonic_increasing 

1320 

1321 @property 

1322 def is_monotonic_decreasing(self) -> bool: 

1323 """ 

1324 Return True if values in the object are monotonically decreasing. 

1325 

1326 This property checks whether each element is less than or equal to 

1327 the previous element. Equal consecutive values are considered 

1328 monotonically decreasing. 

1329 

1330 Returns 

1331 ------- 

1332 bool 

1333 

1334 See Also 

1335 -------- 

1336 Series.is_monotonic_increasing : Return boolean if values in the object are 

1337 monotonically increasing. 

1338 

1339 Examples 

1340 -------- 

1341 >>> s = pd.Series([3, 2, 2, 1]) 

1342 >>> s.is_monotonic_decreasing 

1343 True 

1344 

1345 >>> s = pd.Series([1, 2, 3]) 

1346 >>> s.is_monotonic_decreasing 

1347 False 

1348 """ 

1349 from pandas import Index 

1350 

1351 return Index(self).is_monotonic_decreasing 

1352 

1353 @final 

1354 def _memory_usage(self, deep: bool = False) -> int: 

1355 """ 

1356 Memory usage of the values. 

1357 

1358 Parameters 

1359 ---------- 

1360 deep : bool, default False 

1361 Introspect the data deeply, interrogate 

1362 `object` dtypes for system-level memory consumption. 

1363 

1364 Returns 

1365 ------- 

1366 bytes used 

1367 Returns memory usage of the values in the Index in bytes. 

1368 

1369 See Also 

1370 -------- 

1371 numpy.ndarray.nbytes : Total bytes consumed by the elements of the 

1372 array. 

1373 

1374 Notes 

1375 ----- 

1376 Memory usage does not include memory consumed by elements that 

1377 are not components of the array if deep=False or if used on PyPy 

1378 

1379 Examples 

1380 -------- 

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

1382 >>> idx.memory_usage() 

1383 24 

1384 """ 

1385 if hasattr(self.array, "memory_usage"): 

1386 return self.array.memory_usage( # pyright: ignore[reportAttributeAccessIssue] 

1387 deep=deep, 

1388 ) 

1389 

1390 v = self.array.nbytes 

1391 if deep and is_object_dtype(self.dtype) and not PYPY: 

1392 values = cast("np.ndarray", self._values) 

1393 v += lib.memory_usage_of_objects(values) 

1394 return v 

1395 

1396 def factorize( 

1397 self, 

1398 sort: bool = False, 

1399 use_na_sentinel: bool = True, 

1400 ) -> tuple[npt.NDArray[np.intp], Index]: 

1401 """ 

1402 Encode the object as an enumerated type or categorical variable. 

1403 

1404 This method is useful for obtaining a numeric representation of an 

1405 array when all that matters is identifying distinct values. `factorize` 

1406 is available as both a top-level function :func:`pandas.factorize`, 

1407 and as a method :meth:`Series.factorize` and :meth:`Index.factorize`. 

1408 

1409 Parameters 

1410 ---------- 

1411 sort : bool, default False 

1412 Sort `uniques` and shuffle `codes` to maintain the 

1413 relationship. 

1414 use_na_sentinel : bool, default True 

1415 If True, the sentinel -1 will be used for NaN values. If False, 

1416 NaN values will be encoded as non-negative integers and will not drop the 

1417 NaN from the uniques of the values. 

1418 

1419 Returns 

1420 ------- 

1421 codes : ndarray 

1422 An integer ndarray that's an indexer into `uniques`. 

1423 ``uniques.take(codes)`` will have the same values as `values`. 

1424 uniques : ndarray, Index, or Categorical 

1425 The unique valid values. When `values` is Categorical, `uniques` 

1426 is a Categorical. When `values` is some other pandas object, an 

1427 `Index` is returned. Otherwise, a 1-D ndarray is returned. 

1428 

1429 .. note:: 

1430 

1431 Even if there's a missing value in `values`, `uniques` will 

1432 *not* contain an entry for it. 

1433 

1434 See Also 

1435 -------- 

1436 cut : Discretize continuous-valued array. 

1437 unique : Find the unique value in an array. 

1438 

1439 Notes 

1440 ----- 

1441 Reference :ref:`the user guide <reshaping.factorize>` for more examples. 

1442 

1443 Examples 

1444 -------- 

1445 These examples all show factorize as a top-level method like 

1446 ``pd.factorize(values)``. The results are identical for methods like 

1447 :meth:`Series.factorize`. 

1448 

1449 >>> codes, uniques = pd.factorize( 

1450 ... np.array(["b", "b", "a", "c", "b"], dtype="O") 

1451 ... ) 

1452 >>> codes 

1453 array([0, 0, 1, 2, 0]) 

1454 >>> uniques 

1455 array(['b', 'a', 'c'], dtype=object) 

1456 

1457 With ``sort=True``, the `uniques` will be sorted, and `codes` will be 

1458 shuffled so that the relationship is the maintained. 

1459 

1460 >>> codes, uniques = pd.factorize( 

1461 ... np.array(["b", "b", "a", "c", "b"], dtype="O"), sort=True 

1462 ... ) 

1463 >>> codes 

1464 array([1, 1, 0, 2, 1]) 

1465 >>> uniques 

1466 array(['a', 'b', 'c'], dtype=object) 

1467 

1468 When ``use_na_sentinel=True`` (the default), missing values are indicated in 

1469 the `codes` with the sentinel value ``-1`` and missing values are not 

1470 included in `uniques`. 

1471 

1472 >>> codes, uniques = pd.factorize( 

1473 ... np.array(["b", None, "a", "c", "b"], dtype="O") 

1474 ... ) 

1475 >>> codes 

1476 array([ 0, -1, 1, 2, 0]) 

1477 >>> uniques 

1478 array(['b', 'a', 'c'], dtype=object) 

1479 

1480 Thus far, we've only factorized lists (which are internally coerced to 

1481 NumPy arrays). When factorizing pandas objects, the type of `uniques` 

1482 will differ. For Categoricals, a `Categorical` is returned. 

1483 

1484 >>> cat = pd.Categorical(["a", "a", "c"], categories=["a", "b", "c"]) 

1485 >>> codes, uniques = pd.factorize(cat) 

1486 >>> codes 

1487 array([0, 0, 1]) 

1488 >>> uniques 

1489 ['a', 'c'] 

1490 Categories (3, str): ['a', 'b', 'c'] 

1491 

1492 Notice that ``'b'`` is in ``uniques.categories``, despite not being 

1493 present in ``cat.values``. 

1494 

1495 For all other pandas objects, an Index of the appropriate type is 

1496 returned. 

1497 

1498 >>> cat = pd.Series(["a", "a", "c"]) 

1499 >>> codes, uniques = pd.factorize(cat) 

1500 >>> codes 

1501 array([0, 0, 1]) 

1502 >>> uniques 

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

1504 

1505 If NaN is in the values, and we want to include NaN in the uniques of the 

1506 values, it can be achieved by setting ``use_na_sentinel=False``. 

1507 

1508 >>> values = np.array([1, 2, 1, np.nan]) 

1509 >>> codes, uniques = pd.factorize(values) # default: use_na_sentinel=True 

1510 >>> codes 

1511 array([ 0, 1, 0, -1]) 

1512 >>> uniques 

1513 array([1., 2.]) 

1514 

1515 >>> codes, uniques = pd.factorize(values, use_na_sentinel=False) 

1516 >>> codes 

1517 array([0, 1, 0, 2]) 

1518 >>> uniques 

1519 array([ 1., 2., nan]) 

1520 """ 

1521 codes, uniques = algorithms.factorize( 

1522 self._values, sort=sort, use_na_sentinel=use_na_sentinel 

1523 ) 

1524 if uniques.dtype == np.float16: 

1525 uniques = uniques.astype(np.float32) 

1526 

1527 if isinstance(self, ABCMultiIndex): 

1528 # preserve MultiIndex 

1529 if len(self) == 0: 

1530 # GH#57517 

1531 uniques = self[:0] 

1532 else: 

1533 uniques = self._constructor(uniques) 

1534 else: 

1535 from pandas import Index 

1536 

1537 try: 

1538 uniques = Index(uniques, dtype=self.dtype, copy=False) 

1539 except NotImplementedError: 

1540 # not all dtypes are supported in Index that are allowed for Series 

1541 # e.g. float16 or bytes 

1542 uniques = Index(uniques, copy=False) 

1543 return codes, uniques 

1544 

1545 # This overload is needed so that the call to searchsorted in 

1546 # pandas.core.resample.TimeGrouper._get_period_bins picks the correct result 

1547 

1548 # error: Overloaded function signatures 1 and 2 overlap with incompatible 

1549 # return types 

1550 @overload 

1551 def searchsorted( # type: ignore[overload-overlap] 

1552 self, 

1553 value: ScalarLike_co, 

1554 side: Literal["left", "right"] = ..., 

1555 sorter: NumpySorter = ..., 

1556 ) -> np.intp: ... 

1557 

1558 @overload 

1559 def searchsorted( 

1560 self, 

1561 value: npt.ArrayLike | ExtensionArray, 

1562 side: Literal["left", "right"] = ..., 

1563 sorter: NumpySorter = ..., 

1564 ) -> npt.NDArray[np.intp]: ... 

1565 

1566 def searchsorted( 

1567 self, 

1568 value: NumpyValueArrayLike | ExtensionArray, 

1569 side: Literal["left", "right"] = "left", 

1570 sorter: NumpySorter | None = None, 

1571 ) -> npt.NDArray[np.intp] | np.intp: 

1572 """ 

1573 Find indices where elements should be inserted to maintain order. 

1574 

1575 Find the indices into a sorted Index `self` such that, if the 

1576 corresponding elements in `value` were inserted before the indices, 

1577 the order of `self` would be preserved. 

1578 

1579 .. note:: 

1580 

1581 The Index *must* be monotonically sorted, otherwise 

1582 wrong locations will likely be returned. Pandas does *not* 

1583 check this for you. 

1584 

1585 Parameters 

1586 ---------- 

1587 value : array-like or scalar 

1588 Values to insert into `self`. 

1589 side : {'left', 'right'}, optional 

1590 If 'left', the index of the first suitable location found is given. 

1591 If 'right', return the last such index. If there is no suitable 

1592 index, return either 0 or N (where N is the length of `self`). 

1593 sorter : 1-D array-like, optional 

1594 Optional array of integer indices that sort `self` into ascending 

1595 order. They are typically the result of ``np.argsort``. 

1596 

1597 Returns 

1598 ------- 

1599 int or array of int 

1600 A scalar or array of insertion points with the 

1601 same shape as `value`. 

1602 

1603 See Also 

1604 -------- 

1605 sort_values : Sort by the values along either axis. 

1606 numpy.searchsorted : Similar method from NumPy. 

1607 

1608 Notes 

1609 ----- 

1610 Binary search is used to find the required insertion points. 

1611 

1612 Examples 

1613 -------- 

1614 >>> ser = pd.Series([1, 2, 3]) 

1615 >>> ser 

1616 0 1 

1617 1 2 

1618 2 3 

1619 dtype: int64 

1620 

1621 >>> ser.searchsorted(4) 

1622 3 

1623 

1624 >>> ser.searchsorted([0, 4]) 

1625 array([0, 3]) 

1626 

1627 >>> ser.searchsorted([1, 3], side="left") 

1628 array([0, 2]) 

1629 

1630 >>> ser.searchsorted([1, 3], side="right") 

1631 array([1, 3]) 

1632 

1633 >>> ser = pd.Series(pd.to_datetime(["3/11/2000", "3/12/2000", "3/13/2000"])) 

1634 >>> ser 

1635 0 2000-03-11 

1636 1 2000-03-12 

1637 2 2000-03-13 

1638 dtype: datetime64[us] 

1639 

1640 >>> ser.searchsorted("3/14/2000") 

1641 3 

1642 

1643 >>> ser = pd.Categorical( 

1644 ... ["apple", "bread", "bread", "cheese", "milk"], ordered=True 

1645 ... ) 

1646 >>> ser 

1647 ['apple', 'bread', 'bread', 'cheese', 'milk'] 

1648 Categories (4, str): ['apple' < 'bread' < 'cheese' < 'milk'] 

1649 

1650 >>> ser.searchsorted("bread") 

1651 np.int64(1) 

1652 

1653 >>> ser.searchsorted(["bread"], side="right") 

1654 array([3]) 

1655 

1656 If the values are not monotonically sorted, wrong locations 

1657 may be returned: 

1658 

1659 >>> ser = pd.Series([2, 1, 3]) 

1660 >>> ser 

1661 0 2 

1662 1 1 

1663 2 3 

1664 dtype: int64 

1665 

1666 >>> ser.searchsorted(1) # doctest: +SKIP 

1667 0 # wrong result, correct would be 1 

1668 """ 

1669 if isinstance(value, ABCDataFrame): 

1670 msg = ( 

1671 "Value must be 1-D array-like or scalar, " 

1672 f"{type(value).__name__} is not supported" 

1673 ) 

1674 raise ValueError(msg) 

1675 

1676 values = self._values 

1677 if not isinstance(values, np.ndarray): 

1678 # Going through EA.searchsorted directly improves performance GH#38083 

1679 result = values.searchsorted(value, side=side, sorter=sorter) 

1680 else: 

1681 result = algorithms.searchsorted( 

1682 values, 

1683 value, 

1684 side=side, 

1685 sorter=sorter, 

1686 ) 

1687 return maybe_unbox_numpy_scalar(result) 

1688 

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

1690 duplicated = self._duplicated(keep=keep) 

1691 # error: Value of type "IndexOpsMixin" is not indexable 

1692 return self[~duplicated] # type: ignore[index] 

1693 

1694 @final 

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

1696 arr = self._values 

1697 if isinstance(arr, ExtensionArray): 

1698 return arr.duplicated(keep=keep) 

1699 return algorithms.duplicated(arr, keep=keep) 

1700 

1701 def _arith_method(self, other, op): 

1702 res_name = ops.get_op_result_name(self, other) 

1703 

1704 lvalues = self._values 

1705 if not isinstance(lvalues, ExtensionArray) or isinstance(other, range): 

1706 # For EA-backed values the warning is emitted by the EA's own 

1707 # _arith_method; the exception is ``range``, which is converted to 

1708 # an ndarray below before it reaches the EA (GH#62423). 

1709 ops.maybe_warn_listlike(other) 

1710 rvalues = extract_array(other, extract_numpy=True, extract_range=True) 

1711 rvalues = ops.maybe_prepare_scalar_for_op(rvalues, lvalues.shape) 

1712 rvalues = ensure_wrapped_if_datetimelike(rvalues) 

1713 if isinstance(rvalues, range): 

1714 rvalues = np.arange(rvalues.start, rvalues.stop, rvalues.step) 

1715 

1716 with np.errstate(all="ignore"): 

1717 result = ops.arithmetic_op(lvalues, rvalues, op) 

1718 

1719 return self._construct_result(result, name=res_name, other=other) 

1720 

1721 def _construct_result(self, result, name, other): 

1722 """ 

1723 Construct an appropriately-wrapped result from the ArrayLike result 

1724 of an arithmetic-like operation. 

1725 """ 

1726 raise AbstractMethodError(self)