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 Hashable, List, Tuple, Union 

2 

3import numpy as np 

4 

5from pandas._libs.indexing import _NDFrameIndexerBase 

6from pandas._libs.lib import item_from_zerodim 

7from pandas.errors import AbstractMethodError 

8from pandas.util._decorators import Appender 

9 

10from pandas.core.dtypes.common import ( 

11 is_float, 

12 is_integer, 

13 is_iterator, 

14 is_list_like, 

15 is_numeric_dtype, 

16 is_object_dtype, 

17 is_scalar, 

18 is_sequence, 

19) 

20from pandas.core.dtypes.concat import concat_compat 

21from pandas.core.dtypes.generic import ABCDataFrame, ABCMultiIndex, ABCSeries 

22from pandas.core.dtypes.missing import _infer_fill_value, isna 

23 

24import pandas.core.common as com 

25from pandas.core.indexers import ( 

26 check_array_indexer, 

27 is_list_like_indexer, 

28 length_of_indexer, 

29) 

30from pandas.core.indexes.api import Index, InvalidIndexError 

31 

32# "null slice" 

33_NS = slice(None, None) 

34 

35 

36# the public IndexSlicerMaker 

37class _IndexSlice: 

38 """ 

39 Create an object to more easily perform multi-index slicing. 

40 

41 See Also 

42 -------- 

43 MultiIndex.remove_unused_levels : New MultiIndex with no unused levels. 

44 

45 Notes 

46 ----- 

47 See :ref:`Defined Levels <advanced.shown_levels>` 

48 for further info on slicing a MultiIndex. 

49 

50 Examples 

51 -------- 

52 

53 >>> midx = pd.MultiIndex.from_product([['A0','A1'], ['B0','B1','B2','B3']]) 

54 >>> columns = ['foo', 'bar'] 

55 >>> dfmi = pd.DataFrame(np.arange(16).reshape((len(midx), len(columns))), 

56 index=midx, columns=columns) 

57 

58 Using the default slice command: 

59 

60 >>> dfmi.loc[(slice(None), slice('B0', 'B1')), :] 

61 foo bar 

62 A0 B0 0 1 

63 B1 2 3 

64 A1 B0 8 9 

65 B1 10 11 

66 

67 Using the IndexSlice class for a more intuitive command: 

68 

69 >>> idx = pd.IndexSlice 

70 >>> dfmi.loc[idx[:, 'B0':'B1'], :] 

71 foo bar 

72 A0 B0 0 1 

73 B1 2 3 

74 A1 B0 8 9 

75 B1 10 11 

76 """ 

77 

78 def __getitem__(self, arg): 

79 return arg 

80 

81 

82IndexSlice = _IndexSlice() 

83 

84 

85class IndexingError(Exception): 

86 pass 

87 

88 

89class IndexingMixin: 

90 """Mixin for adding .loc/.iloc/.at/.iat to Datafames and Series. 

91 """ 

92 

93 @property 

94 def iloc(self) -> "_iLocIndexer": 

95 """ 

96 Purely integer-location based indexing for selection by position. 

97 

98 ``.iloc[]`` is primarily integer position based (from ``0`` to 

99 ``length-1`` of the axis), but may also be used with a boolean 

100 array. 

101 

102 Allowed inputs are: 

103 

104 - An integer, e.g. ``5``. 

105 - A list or array of integers, e.g. ``[4, 3, 0]``. 

106 - A slice object with ints, e.g. ``1:7``. 

107 - A boolean array. 

108 - A ``callable`` function with one argument (the calling Series or 

109 DataFrame) and that returns valid output for indexing (one of the above). 

110 This is useful in method chains, when you don't have a reference to the 

111 calling object, but would like to base your selection on some value. 

112 

113 ``.iloc`` will raise ``IndexError`` if a requested indexer is 

114 out-of-bounds, except *slice* indexers which allow out-of-bounds 

115 indexing (this conforms with python/numpy *slice* semantics). 

116 

117 See more at :ref:`Selection by Position <indexing.integer>`. 

118 

119 See Also 

120 -------- 

121 DataFrame.iat : Fast integer location scalar accessor. 

122 DataFrame.loc : Purely label-location based indexer for selection by label. 

123 Series.iloc : Purely integer-location based indexing for 

124 selection by position. 

125 

126 Examples 

127 -------- 

128 

129 >>> mydict = [{'a': 1, 'b': 2, 'c': 3, 'd': 4}, 

130 ... {'a': 100, 'b': 200, 'c': 300, 'd': 400}, 

131 ... {'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }] 

132 >>> df = pd.DataFrame(mydict) 

133 >>> df 

134 a b c d 

135 0 1 2 3 4 

136 1 100 200 300 400 

137 2 1000 2000 3000 4000 

138 

139 **Indexing just the rows** 

140 

141 With a scalar integer. 

142 

143 >>> type(df.iloc[0]) 

144 <class 'pandas.core.series.Series'> 

145 >>> df.iloc[0] 

146 a 1 

147 b 2 

148 c 3 

149 d 4 

150 Name: 0, dtype: int64 

151 

152 With a list of integers. 

153 

154 >>> df.iloc[[0]] 

155 a b c d 

156 0 1 2 3 4 

157 >>> type(df.iloc[[0]]) 

158 <class 'pandas.core.frame.DataFrame'> 

159 

160 >>> df.iloc[[0, 1]] 

161 a b c d 

162 0 1 2 3 4 

163 1 100 200 300 400 

164 

165 With a `slice` object. 

166 

167 >>> df.iloc[:3] 

168 a b c d 

169 0 1 2 3 4 

170 1 100 200 300 400 

171 2 1000 2000 3000 4000 

172 

173 With a boolean mask the same length as the index. 

174 

175 >>> df.iloc[[True, False, True]] 

176 a b c d 

177 0 1 2 3 4 

178 2 1000 2000 3000 4000 

179 

180 With a callable, useful in method chains. The `x` passed 

181 to the ``lambda`` is the DataFrame being sliced. This selects 

182 the rows whose index label even. 

183 

184 >>> df.iloc[lambda x: x.index % 2 == 0] 

185 a b c d 

186 0 1 2 3 4 

187 2 1000 2000 3000 4000 

188 

189 **Indexing both axes** 

190 

191 You can mix the indexer types for the index and columns. Use ``:`` to 

192 select the entire axis. 

193 

194 With scalar integers. 

195 

196 >>> df.iloc[0, 1] 

197 2 

198 

199 With lists of integers. 

200 

201 >>> df.iloc[[0, 2], [1, 3]] 

202 b d 

203 0 2 4 

204 2 2000 4000 

205 

206 With `slice` objects. 

207 

208 >>> df.iloc[1:3, 0:3] 

209 a b c 

210 1 100 200 300 

211 2 1000 2000 3000 

212 

213 With a boolean array whose length matches the columns. 

214 

215 >>> df.iloc[:, [True, False, True, False]] 

216 a c 

217 0 1 3 

218 1 100 300 

219 2 1000 3000 

220 

221 With a callable function that expects the Series or DataFrame. 

222 

223 >>> df.iloc[:, lambda df: [0, 2]] 

224 a c 

225 0 1 3 

226 1 100 300 

227 2 1000 3000 

228 """ 

229 return _iLocIndexer("iloc", self) 

230 

231 @property 

232 def loc(self) -> "_LocIndexer": 

233 """ 

234 Access a group of rows and columns by label(s) or a boolean array. 

235 

236 ``.loc[]`` is primarily label based, but may also be used with a 

237 boolean array. 

238 

239 Allowed inputs are: 

240 

241 - A single label, e.g. ``5`` or ``'a'``, (note that ``5`` is 

242 interpreted as a *label* of the index, and **never** as an 

243 integer position along the index). 

244 - A list or array of labels, e.g. ``['a', 'b', 'c']``. 

245 - A slice object with labels, e.g. ``'a':'f'``. 

246 

247 .. warning:: Note that contrary to usual python slices, **both** the 

248 start and the stop are included 

249 

250 - A boolean array of the same length as the axis being sliced, 

251 e.g. ``[True, False, True]``. 

252 - A ``callable`` function with one argument (the calling Series or 

253 DataFrame) and that returns valid output for indexing (one of the above) 

254 

255 See more at :ref:`Selection by Label <indexing.label>` 

256 

257 Raises 

258 ------ 

259 KeyError 

260 If any items are not found. 

261 

262 See Also 

263 -------- 

264 DataFrame.at : Access a single value for a row/column label pair. 

265 DataFrame.iloc : Access group of rows and columns by integer position(s). 

266 DataFrame.xs : Returns a cross-section (row(s) or column(s)) from the 

267 Series/DataFrame. 

268 Series.loc : Access group of values using labels. 

269 

270 Examples 

271 -------- 

272 **Getting values** 

273 

274 >>> df = pd.DataFrame([[1, 2], [4, 5], [7, 8]], 

275 ... index=['cobra', 'viper', 'sidewinder'], 

276 ... columns=['max_speed', 'shield']) 

277 >>> df 

278 max_speed shield 

279 cobra 1 2 

280 viper 4 5 

281 sidewinder 7 8 

282 

283 Single label. Note this returns the row as a Series. 

284 

285 >>> df.loc['viper'] 

286 max_speed 4 

287 shield 5 

288 Name: viper, dtype: int64 

289 

290 List of labels. Note using ``[[]]`` returns a DataFrame. 

291 

292 >>> df.loc[['viper', 'sidewinder']] 

293 max_speed shield 

294 viper 4 5 

295 sidewinder 7 8 

296 

297 Single label for row and column 

298 

299 >>> df.loc['cobra', 'shield'] 

300 2 

301 

302 Slice with labels for row and single label for column. As mentioned 

303 above, note that both the start and stop of the slice are included. 

304 

305 >>> df.loc['cobra':'viper', 'max_speed'] 

306 cobra 1 

307 viper 4 

308 Name: max_speed, dtype: int64 

309 

310 Boolean list with the same length as the row axis 

311 

312 >>> df.loc[[False, False, True]] 

313 max_speed shield 

314 sidewinder 7 8 

315 

316 Conditional that returns a boolean Series 

317 

318 >>> df.loc[df['shield'] > 6] 

319 max_speed shield 

320 sidewinder 7 8 

321 

322 Conditional that returns a boolean Series with column labels specified 

323 

324 >>> df.loc[df['shield'] > 6, ['max_speed']] 

325 max_speed 

326 sidewinder 7 

327 

328 Callable that returns a boolean Series 

329 

330 >>> df.loc[lambda df: df['shield'] == 8] 

331 max_speed shield 

332 sidewinder 7 8 

333 

334 **Setting values** 

335 

336 Set value for all items matching the list of labels 

337 

338 >>> df.loc[['viper', 'sidewinder'], ['shield']] = 50 

339 >>> df 

340 max_speed shield 

341 cobra 1 2 

342 viper 4 50 

343 sidewinder 7 50 

344 

345 Set value for an entire row 

346 

347 >>> df.loc['cobra'] = 10 

348 >>> df 

349 max_speed shield 

350 cobra 10 10 

351 viper 4 50 

352 sidewinder 7 50 

353 

354 Set value for an entire column 

355 

356 >>> df.loc[:, 'max_speed'] = 30 

357 >>> df 

358 max_speed shield 

359 cobra 30 10 

360 viper 30 50 

361 sidewinder 30 50 

362 

363 Set value for rows matching callable condition 

364 

365 >>> df.loc[df['shield'] > 35] = 0 

366 >>> df 

367 max_speed shield 

368 cobra 30 10 

369 viper 0 0 

370 sidewinder 0 0 

371 

372 **Getting values on a DataFrame with an index that has integer labels** 

373 

374 Another example using integers for the index 

375 

376 >>> df = pd.DataFrame([[1, 2], [4, 5], [7, 8]], 

377 ... index=[7, 8, 9], columns=['max_speed', 'shield']) 

378 >>> df 

379 max_speed shield 

380 7 1 2 

381 8 4 5 

382 9 7 8 

383 

384 Slice with integer labels for rows. As mentioned above, note that both 

385 the start and stop of the slice are included. 

386 

387 >>> df.loc[7:9] 

388 max_speed shield 

389 7 1 2 

390 8 4 5 

391 9 7 8 

392 

393 **Getting values with a MultiIndex** 

394 

395 A number of examples using a DataFrame with a MultiIndex 

396 

397 >>> tuples = [ 

398 ... ('cobra', 'mark i'), ('cobra', 'mark ii'), 

399 ... ('sidewinder', 'mark i'), ('sidewinder', 'mark ii'), 

400 ... ('viper', 'mark ii'), ('viper', 'mark iii') 

401 ... ] 

402 >>> index = pd.MultiIndex.from_tuples(tuples) 

403 >>> values = [[12, 2], [0, 4], [10, 20], 

404 ... [1, 4], [7, 1], [16, 36]] 

405 >>> df = pd.DataFrame(values, columns=['max_speed', 'shield'], index=index) 

406 >>> df 

407 max_speed shield 

408 cobra mark i 12 2 

409 mark ii 0 4 

410 sidewinder mark i 10 20 

411 mark ii 1 4 

412 viper mark ii 7 1 

413 mark iii 16 36 

414 

415 Single label. Note this returns a DataFrame with a single index. 

416 

417 >>> df.loc['cobra'] 

418 max_speed shield 

419 mark i 12 2 

420 mark ii 0 4 

421 

422 Single index tuple. Note this returns a Series. 

423 

424 >>> df.loc[('cobra', 'mark ii')] 

425 max_speed 0 

426 shield 4 

427 Name: (cobra, mark ii), dtype: int64 

428 

429 Single label for row and column. Similar to passing in a tuple, this 

430 returns a Series. 

431 

432 >>> df.loc['cobra', 'mark i'] 

433 max_speed 12 

434 shield 2 

435 Name: (cobra, mark i), dtype: int64 

436 

437 Single tuple. Note using ``[[]]`` returns a DataFrame. 

438 

439 >>> df.loc[[('cobra', 'mark ii')]] 

440 max_speed shield 

441 cobra mark ii 0 4 

442 

443 Single tuple for the index with a single label for the column 

444 

445 >>> df.loc[('cobra', 'mark i'), 'shield'] 

446 2 

447 

448 Slice from index tuple to single label 

449 

450 >>> df.loc[('cobra', 'mark i'):'viper'] 

451 max_speed shield 

452 cobra mark i 12 2 

453 mark ii 0 4 

454 sidewinder mark i 10 20 

455 mark ii 1 4 

456 viper mark ii 7 1 

457 mark iii 16 36 

458 

459 Slice from index tuple to index tuple 

460 

461 >>> df.loc[('cobra', 'mark i'):('viper', 'mark ii')] 

462 max_speed shield 

463 cobra mark i 12 2 

464 mark ii 0 4 

465 sidewinder mark i 10 20 

466 mark ii 1 4 

467 viper mark ii 7 1 

468 """ 

469 return _LocIndexer("loc", self) 

470 

471 @property 

472 def at(self) -> "_AtIndexer": 

473 """ 

474 Access a single value for a row/column label pair. 

475 

476 Similar to ``loc``, in that both provide label-based lookups. Use 

477 ``at`` if you only need to get or set a single value in a DataFrame 

478 or Series. 

479 

480 Raises 

481 ------ 

482 KeyError 

483 If 'label' does not exist in DataFrame. 

484 

485 See Also 

486 -------- 

487 DataFrame.iat : Access a single value for a row/column pair by integer 

488 position. 

489 DataFrame.loc : Access a group of rows and columns by label(s). 

490 Series.at : Access a single value using a label. 

491 

492 Examples 

493 -------- 

494 >>> df = pd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], 

495 ... index=[4, 5, 6], columns=['A', 'B', 'C']) 

496 >>> df 

497 A B C 

498 4 0 2 3 

499 5 0 4 1 

500 6 10 20 30 

501 

502 Get value at specified row/column pair 

503 

504 >>> df.at[4, 'B'] 

505 2 

506 

507 Set value at specified row/column pair 

508 

509 >>> df.at[4, 'B'] = 10 

510 >>> df.at[4, 'B'] 

511 10 

512 

513 Get value within a Series 

514 

515 >>> df.loc[5].at['B'] 

516 4 

517 """ 

518 return _AtIndexer("at", self) 

519 

520 @property 

521 def iat(self) -> "_iAtIndexer": 

522 """ 

523 Access a single value for a row/column pair by integer position. 

524 

525 Similar to ``iloc``, in that both provide integer-based lookups. Use 

526 ``iat`` if you only need to get or set a single value in a DataFrame 

527 or Series. 

528 

529 Raises 

530 ------ 

531 IndexError 

532 When integer position is out of bounds. 

533 

534 See Also 

535 -------- 

536 DataFrame.at : Access a single value for a row/column label pair. 

537 DataFrame.loc : Access a group of rows and columns by label(s). 

538 DataFrame.iloc : Access a group of rows and columns by integer position(s). 

539 

540 Examples 

541 -------- 

542 >>> df = pd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]], 

543 ... columns=['A', 'B', 'C']) 

544 >>> df 

545 A B C 

546 0 0 2 3 

547 1 0 4 1 

548 2 10 20 30 

549 

550 Get value at specified row/column pair 

551 

552 >>> df.iat[1, 2] 

553 1 

554 

555 Set value at specified row/column pair 

556 

557 >>> df.iat[1, 2] = 10 

558 >>> df.iat[1, 2] 

559 10 

560 

561 Get value within a series 

562 

563 >>> df.loc[0].iat[1] 

564 2 

565 """ 

566 return _iAtIndexer("iat", self) 

567 

568 

569class _NDFrameIndexer(_NDFrameIndexerBase): 

570 _valid_types: str 

571 axis = None 

572 

573 def __call__(self, axis=None): 

574 # we need to return a copy of ourselves 

575 new_self = type(self)(self.name, self.obj) 

576 

577 if axis is not None: 

578 axis = self.obj._get_axis_number(axis) 

579 new_self.axis = axis 

580 return new_self 

581 

582 # TODO: remove once geopandas no longer needs this 

583 def __getitem__(self, key): 

584 # Used in ix and downstream in geopandas _CoordinateIndexer 

585 if type(key) is tuple: 

586 # Note: we check the type exactly instead of with isinstance 

587 # because NamedTuple is checked separately. 

588 key = tuple(com.apply_if_callable(x, self.obj) for x in key) 

589 try: 

590 values = self.obj._get_value(*key) 

591 except (KeyError, TypeError, InvalidIndexError, AttributeError): 

592 # TypeError occurs here if the key has non-hashable entries, 

593 # generally slice or list. 

594 # TODO(ix): most/all of the TypeError cases here are for ix, 

595 # so this check can be removed once ix is removed. 

596 # The InvalidIndexError is only catched for compatibility 

597 # with geopandas, see 

598 # https://github.com/pandas-dev/pandas/issues/27258 

599 # TODO: The AttributeError is for IntervalIndex which 

600 # incorrectly implements get_value, see 

601 # https://github.com/pandas-dev/pandas/issues/27865 

602 pass 

603 else: 

604 if is_scalar(values): 

605 return values 

606 

607 return self._getitem_tuple(key) 

608 else: 

609 # we by definition only have the 0th axis 

610 axis = self.axis or 0 

611 

612 key = com.apply_if_callable(key, self.obj) 

613 return self._getitem_axis(key, axis=axis) 

614 

615 def _get_label(self, label, axis: int): 

616 if self.ndim == 1: 

617 # for perf reasons we want to try _xs first 

618 # as its basically direct indexing 

619 # but will fail when the index is not present 

620 # see GH5667 

621 return self.obj._xs(label, axis=axis) 

622 elif isinstance(label, tuple) and isinstance(label[axis], slice): 

623 raise IndexingError("no slices here, handle elsewhere") 

624 

625 return self.obj._xs(label, axis=axis) 

626 

627 def _get_loc(self, key: int, axis: int): 

628 return self.obj._ixs(key, axis=axis) 

629 

630 def _slice(self, obj, axis: int, kind=None): 

631 return self.obj._slice(obj, axis=axis, kind=kind) 

632 

633 def _get_setitem_indexer(self, key): 

634 if self.axis is not None: 

635 return self._convert_tuple(key) 

636 

637 ax = self.obj._get_axis(0) 

638 

639 if isinstance(ax, ABCMultiIndex) and self.name != "iloc": 

640 try: 

641 return ax.get_loc(key) 

642 except (TypeError, KeyError, InvalidIndexError): 

643 # TypeError e.g. passed a bool 

644 pass 

645 

646 if isinstance(key, tuple): 

647 try: 

648 return self._convert_tuple(key) 

649 except IndexingError: 

650 pass 

651 

652 if isinstance(key, range): 

653 return list(key) 

654 

655 axis = self.axis or 0 

656 try: 

657 return self._convert_to_indexer(key, axis=axis) 

658 except TypeError as e: 

659 

660 # invalid indexer type vs 'other' indexing errors 

661 if "cannot do" in str(e): 

662 raise 

663 raise IndexingError(key) 

664 

665 def __setitem__(self, key, value): 

666 if isinstance(key, tuple): 

667 key = tuple(com.apply_if_callable(x, self.obj) for x in key) 

668 else: 

669 key = com.apply_if_callable(key, self.obj) 

670 indexer = self._get_setitem_indexer(key) 

671 self._setitem_with_indexer(indexer, value) 

672 

673 def _validate_key(self, key, axis: int): 

674 """ 

675 Ensure that key is valid for current indexer. 

676 

677 Parameters 

678 ---------- 

679 key : scalar, slice or list-like 

680 Key requested. 

681 axis : int 

682 Dimension on which the indexing is being made. 

683 

684 Raises 

685 ------ 

686 TypeError 

687 If the key (or some element of it) has wrong type. 

688 IndexError 

689 If the key (or some element of it) is out of bounds. 

690 KeyError 

691 If the key was not found. 

692 """ 

693 raise AbstractMethodError(self) 

694 

695 def _has_valid_tuple(self, key: Tuple): 

696 """ 

697 Check the key for valid keys across my indexer. 

698 """ 

699 for i, k in enumerate(key): 

700 if i >= self.ndim: 

701 raise IndexingError("Too many indexers") 

702 try: 

703 self._validate_key(k, i) 

704 except ValueError: 

705 raise ValueError( 

706 "Location based indexing can only have " 

707 f"[{self._valid_types}] types" 

708 ) 

709 

710 def _is_nested_tuple_indexer(self, tup: Tuple) -> bool: 

711 """ 

712 Returns 

713 ------- 

714 bool 

715 """ 

716 if any(isinstance(ax, ABCMultiIndex) for ax in self.obj.axes): 

717 return any(is_nested_tuple(tup, ax) for ax in self.obj.axes) 

718 return False 

719 

720 def _convert_tuple(self, key): 

721 keyidx = [] 

722 if self.axis is not None: 

723 axis = self.obj._get_axis_number(self.axis) 

724 for i in range(self.ndim): 

725 if i == axis: 

726 keyidx.append(self._convert_to_indexer(key, axis=axis)) 

727 else: 

728 keyidx.append(slice(None)) 

729 else: 

730 for i, k in enumerate(key): 

731 if i >= self.ndim: 

732 raise IndexingError("Too many indexers") 

733 idx = self._convert_to_indexer(k, axis=i) 

734 keyidx.append(idx) 

735 return tuple(keyidx) 

736 

737 def _convert_scalar_indexer(self, key, axis: int): 

738 # if we are accessing via lowered dim, use the last dim 

739 ax = self.obj._get_axis(min(axis, self.ndim - 1)) 

740 # a scalar 

741 return ax._convert_scalar_indexer(key, kind=self.name) 

742 

743 def _convert_slice_indexer(self, key: slice, axis: int): 

744 # if we are accessing via lowered dim, use the last dim 

745 ax = self.obj._get_axis(min(axis, self.ndim - 1)) 

746 return ax._convert_slice_indexer(key, kind=self.name) 

747 

748 def _has_valid_setitem_indexer(self, indexer) -> bool: 

749 return True 

750 

751 def _has_valid_positional_setitem_indexer(self, indexer) -> bool: 

752 """ 

753 Validate that a positional indexer cannot enlarge its target 

754 will raise if needed, does not modify the indexer externally. 

755 

756 Returns 

757 ------- 

758 bool 

759 """ 

760 if isinstance(indexer, dict): 

761 raise IndexError(f"{self.name} cannot enlarge its target object") 

762 else: 

763 if not isinstance(indexer, tuple): 

764 indexer = _tuplify(self.ndim, indexer) 

765 for ax, i in zip(self.obj.axes, indexer): 

766 if isinstance(i, slice): 

767 # should check the stop slice? 

768 pass 

769 elif is_list_like_indexer(i): 

770 # should check the elements? 

771 pass 

772 elif is_integer(i): 

773 if i >= len(ax): 

774 raise IndexError( 

775 f"{self.name} cannot enlarge its target object" 

776 ) 

777 elif isinstance(i, dict): 

778 raise IndexError(f"{self.name} cannot enlarge its target object") 

779 

780 return True 

781 

782 def _setitem_with_indexer(self, indexer, value): 

783 self._has_valid_setitem_indexer(indexer) 

784 

785 # also has the side effect of consolidating in-place 

786 from pandas import Series 

787 

788 info_axis = self.obj._info_axis_number 

789 

790 # maybe partial set 

791 take_split_path = self.obj._is_mixed_type 

792 

793 # if there is only one block/type, still have to take split path 

794 # unless the block is one-dimensional or it can hold the value 

795 if not take_split_path and self.obj._data.blocks: 

796 (blk,) = self.obj._data.blocks 

797 if 1 < blk.ndim: # in case of dict, keys are indices 

798 val = list(value.values()) if isinstance(value, dict) else value 

799 take_split_path = not blk._can_hold_element(val) 

800 

801 # if we have any multi-indexes that have non-trivial slices 

802 # (not null slices) then we must take the split path, xref 

803 # GH 10360, GH 27841 

804 if isinstance(indexer, tuple) and len(indexer) == len(self.obj.axes): 

805 for i, ax in zip(indexer, self.obj.axes): 

806 if isinstance(ax, ABCMultiIndex) and not ( 

807 is_integer(i) or com.is_null_slice(i) 

808 ): 

809 take_split_path = True 

810 break 

811 

812 if isinstance(indexer, tuple): 

813 nindexer = [] 

814 for i, idx in enumerate(indexer): 

815 if isinstance(idx, dict): 

816 

817 # reindex the axis to the new value 

818 # and set inplace 

819 key, _ = convert_missing_indexer(idx) 

820 

821 # if this is the items axes, then take the main missing 

822 # path first 

823 # this correctly sets the dtype and avoids cache issues 

824 # essentially this separates out the block that is needed 

825 # to possibly be modified 

826 if self.ndim > 1 and i == self.obj._info_axis_number: 

827 

828 # add the new item, and set the value 

829 # must have all defined axes if we have a scalar 

830 # or a list-like on the non-info axes if we have a 

831 # list-like 

832 len_non_info_axes = ( 

833 len(_ax) for _i, _ax in enumerate(self.obj.axes) if _i != i 

834 ) 

835 if any(not l for l in len_non_info_axes): 

836 if not is_list_like_indexer(value): 

837 raise ValueError( 

838 "cannot set a frame with no " 

839 "defined index and a scalar" 

840 ) 

841 self.obj[key] = value 

842 return self.obj 

843 

844 # add a new item with the dtype setup 

845 self.obj[key] = _infer_fill_value(value) 

846 

847 new_indexer = convert_from_missing_indexer_tuple( 

848 indexer, self.obj.axes 

849 ) 

850 self._setitem_with_indexer(new_indexer, value) 

851 

852 return self.obj 

853 

854 # reindex the axis 

855 # make sure to clear the cache because we are 

856 # just replacing the block manager here 

857 # so the object is the same 

858 index = self.obj._get_axis(i) 

859 labels = index.insert(len(index), key) 

860 self.obj._data = self.obj.reindex(labels, axis=i)._data 

861 self.obj._maybe_update_cacher(clear=True) 

862 self.obj._is_copy = None 

863 

864 nindexer.append(labels.get_loc(key)) 

865 

866 else: 

867 nindexer.append(idx) 

868 

869 indexer = tuple(nindexer) 

870 else: 

871 

872 indexer, missing = convert_missing_indexer(indexer) 

873 

874 if missing: 

875 return self._setitem_with_indexer_missing(indexer, value) 

876 

877 # set 

878 item_labels = self.obj._get_axis(info_axis) 

879 

880 # align and set the values 

881 if take_split_path: 

882 # Above we only set take_split_path to True for 2D cases 

883 assert self.ndim == 2 

884 assert info_axis == 1 

885 

886 if not isinstance(indexer, tuple): 

887 indexer = _tuplify(self.ndim, indexer) 

888 

889 if isinstance(value, ABCSeries): 

890 value = self._align_series(indexer, value) 

891 

892 info_idx = indexer[info_axis] 

893 if is_integer(info_idx): 

894 info_idx = [info_idx] 

895 labels = item_labels[info_idx] 

896 

897 # if we have a partial multiindex, then need to adjust the plane 

898 # indexer here 

899 if len(labels) == 1 and isinstance( 

900 self.obj[labels[0]].axes[0], ABCMultiIndex 

901 ): 

902 item = labels[0] 

903 obj = self.obj[item] 

904 index = obj.index 

905 idx = indexer[:info_axis][0] 

906 

907 plane_indexer = tuple([idx]) + indexer[info_axis + 1 :] 

908 lplane_indexer = length_of_indexer(plane_indexer[0], index) 

909 

910 # require that we are setting the right number of values that 

911 # we are indexing 

912 if ( 

913 is_list_like_indexer(value) 

914 and np.iterable(value) 

915 and lplane_indexer != len(value) 

916 ): 

917 

918 if len(obj[idx]) != len(value): 

919 raise ValueError( 

920 "cannot set using a multi-index " 

921 "selection indexer with a different " 

922 "length than the value" 

923 ) 

924 

925 # make sure we have an ndarray 

926 value = getattr(value, "values", value).ravel() 

927 

928 # we can directly set the series here 

929 # as we select a slice indexer on the mi 

930 if isinstance(idx, slice): 

931 idx = index._convert_slice_indexer(idx) 

932 obj._consolidate_inplace() 

933 obj = obj.copy() 

934 obj._data = obj._data.setitem(indexer=tuple([idx]), value=value) 

935 self.obj[item] = obj 

936 return 

937 

938 # non-mi 

939 else: 

940 plane_indexer = indexer[:info_axis] + indexer[info_axis + 1 :] 

941 plane_axis = self.obj.axes[:info_axis][0] 

942 lplane_indexer = length_of_indexer(plane_indexer[0], plane_axis) 

943 

944 def setter(item, v): 

945 s = self.obj[item] 

946 pi = plane_indexer[0] if lplane_indexer == 1 else plane_indexer 

947 

948 # perform the equivalent of a setitem on the info axis 

949 # as we have a null slice or a slice with full bounds 

950 # which means essentially reassign to the columns of a 

951 # multi-dim object 

952 # GH6149 (null slice), GH10408 (full bounds) 

953 if isinstance(pi, tuple) and all( 

954 com.is_null_slice(idx) or com.is_full_slice(idx, len(self.obj)) 

955 for idx in pi 

956 ): 

957 s = v 

958 else: 

959 # set the item, possibly having a dtype change 

960 s._consolidate_inplace() 

961 s = s.copy() 

962 s._data = s._data.setitem(indexer=pi, value=v) 

963 s._maybe_update_cacher(clear=True) 

964 

965 # reset the sliced object if unique 

966 self.obj[item] = s 

967 

968 # we need an iterable, with a ndim of at least 1 

969 # eg. don't pass through np.array(0) 

970 if is_list_like_indexer(value) and getattr(value, "ndim", 1) > 0: 

971 

972 # we have an equal len Frame 

973 if isinstance(value, ABCDataFrame): 

974 sub_indexer = list(indexer) 

975 multiindex_indexer = isinstance(labels, ABCMultiIndex) 

976 

977 for item in labels: 

978 if item in value: 

979 sub_indexer[info_axis] = item 

980 v = self._align_series( 

981 tuple(sub_indexer), value[item], multiindex_indexer 

982 ) 

983 else: 

984 v = np.nan 

985 

986 setter(item, v) 

987 

988 # we have an equal len ndarray/convertible to our labels 

989 # hasattr first, to avoid coercing to ndarray without reason. 

990 # But we may be relying on the ndarray coercion to check ndim. 

991 # Why not just convert to an ndarray earlier on if needed? 

992 elif np.ndim(value) == 2: 

993 

994 # note that this coerces the dtype if we are mixed 

995 # GH 7551 

996 value = np.array(value, dtype=object) 

997 if len(labels) != value.shape[1]: 

998 raise ValueError( 

999 "Must have equal len keys and value " 

1000 "when setting with an ndarray" 

1001 ) 

1002 

1003 for i, item in enumerate(labels): 

1004 

1005 # setting with a list, recoerces 

1006 setter(item, value[:, i].tolist()) 

1007 

1008 # we have an equal len list/ndarray 

1009 elif _can_do_equal_len( 

1010 labels, value, plane_indexer, lplane_indexer, self.obj 

1011 ): 

1012 setter(labels[0], value) 

1013 

1014 # per label values 

1015 else: 

1016 

1017 if len(labels) != len(value): 

1018 raise ValueError( 

1019 "Must have equal len keys and value " 

1020 "when setting with an iterable" 

1021 ) 

1022 

1023 for item, v in zip(labels, value): 

1024 setter(item, v) 

1025 else: 

1026 

1027 # scalar 

1028 for item in labels: 

1029 setter(item, value) 

1030 

1031 else: 

1032 if isinstance(indexer, tuple): 

1033 indexer = maybe_convert_ix(*indexer) 

1034 

1035 # if we are setting on the info axis ONLY 

1036 # set using those methods to avoid block-splitting 

1037 # logic here 

1038 if ( 

1039 len(indexer) > info_axis 

1040 and is_integer(indexer[info_axis]) 

1041 and all( 

1042 com.is_null_slice(idx) 

1043 for i, idx in enumerate(indexer) 

1044 if i != info_axis 

1045 ) 

1046 and item_labels.is_unique 

1047 ): 

1048 self.obj[item_labels[indexer[info_axis]]] = value 

1049 return 

1050 

1051 if isinstance(value, (ABCSeries, dict)): 

1052 # TODO(EA): ExtensionBlock.setitem this causes issues with 

1053 # setting for extensionarrays that store dicts. Need to decide 

1054 # if it's worth supporting that. 

1055 value = self._align_series(indexer, Series(value)) 

1056 

1057 elif isinstance(value, ABCDataFrame): 

1058 value = self._align_frame(indexer, value) 

1059 

1060 # check for chained assignment 

1061 self.obj._check_is_chained_assignment_possible() 

1062 

1063 # actually do the set 

1064 self.obj._consolidate_inplace() 

1065 self.obj._data = self.obj._data.setitem(indexer=indexer, value=value) 

1066 self.obj._maybe_update_cacher(clear=True) 

1067 

1068 def _setitem_with_indexer_missing(self, indexer, value): 

1069 """ 

1070 Insert new row(s) or column(s) into the Series or DataFrame. 

1071 """ 

1072 from pandas import Series 

1073 

1074 # reindex the axis to the new value 

1075 # and set inplace 

1076 if self.ndim == 1: 

1077 index = self.obj.index 

1078 new_index = index.insert(len(index), indexer) 

1079 

1080 # we have a coerced indexer, e.g. a float 

1081 # that matches in an Int64Index, so 

1082 # we will not create a duplicate index, rather 

1083 # index to that element 

1084 # e.g. 0.0 -> 0 

1085 # GH#12246 

1086 if index.is_unique: 

1087 new_indexer = index.get_indexer([new_index[-1]]) 

1088 if (new_indexer != -1).any(): 

1089 return self._setitem_with_indexer(new_indexer, value) 

1090 

1091 # this preserves dtype of the value 

1092 new_values = Series([value])._values 

1093 if len(self.obj._values): 

1094 # GH#22717 handle casting compatibility that np.concatenate 

1095 # does incorrectly 

1096 new_values = concat_compat([self.obj._values, new_values]) 

1097 self.obj._data = self.obj._constructor( 

1098 new_values, index=new_index, name=self.obj.name 

1099 )._data 

1100 self.obj._maybe_update_cacher(clear=True) 

1101 return self.obj 

1102 

1103 elif self.ndim == 2: 

1104 

1105 if not len(self.obj.columns): 

1106 # no columns and scalar 

1107 raise ValueError("cannot set a frame with no defined columns") 

1108 

1109 if isinstance(value, ABCSeries): 

1110 # append a Series 

1111 value = value.reindex(index=self.obj.columns, copy=True) 

1112 value.name = indexer 

1113 

1114 else: 

1115 # a list-list 

1116 if is_list_like_indexer(value): 

1117 # must have conforming columns 

1118 if len(value) != len(self.obj.columns): 

1119 raise ValueError("cannot set a row with mismatched columns") 

1120 

1121 value = Series(value, index=self.obj.columns, name=indexer) 

1122 

1123 self.obj._data = self.obj.append(value)._data 

1124 self.obj._maybe_update_cacher(clear=True) 

1125 return self.obj 

1126 

1127 def _align_series(self, indexer, ser: ABCSeries, multiindex_indexer: bool = False): 

1128 """ 

1129 Parameters 

1130 ---------- 

1131 indexer : tuple, slice, scalar 

1132 Indexer used to get the locations that will be set to `ser`. 

1133 ser : pd.Series 

1134 Values to assign to the locations specified by `indexer`. 

1135 multiindex_indexer : boolean, optional 

1136 Defaults to False. Should be set to True if `indexer` was from 

1137 a `pd.MultiIndex`, to avoid unnecessary broadcasting. 

1138 

1139 Returns 

1140 ------- 

1141 `np.array` of `ser` broadcast to the appropriate shape for assignment 

1142 to the locations selected by `indexer` 

1143 """ 

1144 if isinstance(indexer, (slice, np.ndarray, list, Index)): 

1145 indexer = tuple([indexer]) 

1146 

1147 if isinstance(indexer, tuple): 

1148 

1149 # flatten np.ndarray indexers 

1150 def ravel(i): 

1151 return i.ravel() if isinstance(i, np.ndarray) else i 

1152 

1153 indexer = tuple(map(ravel, indexer)) 

1154 

1155 aligners = [not com.is_null_slice(idx) for idx in indexer] 

1156 sum_aligners = sum(aligners) 

1157 single_aligner = sum_aligners == 1 

1158 is_frame = self.ndim == 2 

1159 obj = self.obj 

1160 

1161 # are we a single alignable value on a non-primary 

1162 # dim (e.g. panel: 1,2, or frame: 0) ? 

1163 # hence need to align to a single axis dimension 

1164 # rather that find all valid dims 

1165 

1166 # frame 

1167 if is_frame: 

1168 single_aligner = single_aligner and aligners[0] 

1169 

1170 # we have a frame, with multiple indexers on both axes; and a 

1171 # series, so need to broadcast (see GH5206) 

1172 if sum_aligners == self.ndim and all(is_sequence(_) for _ in indexer): 

1173 ser = ser.reindex(obj.axes[0][indexer[0]], copy=True)._values 

1174 

1175 # single indexer 

1176 if len(indexer) > 1 and not multiindex_indexer: 

1177 len_indexer = len(indexer[1]) 

1178 ser = np.tile(ser, len_indexer).reshape(len_indexer, -1).T 

1179 

1180 return ser 

1181 

1182 for i, idx in enumerate(indexer): 

1183 ax = obj.axes[i] 

1184 

1185 # multiple aligners (or null slices) 

1186 if is_sequence(idx) or isinstance(idx, slice): 

1187 if single_aligner and com.is_null_slice(idx): 

1188 continue 

1189 new_ix = ax[idx] 

1190 if not is_list_like_indexer(new_ix): 

1191 new_ix = Index([new_ix]) 

1192 else: 

1193 new_ix = Index(new_ix) 

1194 if ser.index.equals(new_ix) or not len(new_ix): 

1195 return ser._values.copy() 

1196 

1197 return ser.reindex(new_ix)._values 

1198 

1199 # 2 dims 

1200 elif single_aligner: 

1201 

1202 # reindex along index 

1203 ax = self.obj.axes[1] 

1204 if ser.index.equals(ax) or not len(ax): 

1205 return ser._values.copy() 

1206 return ser.reindex(ax)._values 

1207 

1208 elif is_scalar(indexer): 

1209 ax = self.obj._get_axis(1) 

1210 

1211 if ser.index.equals(ax): 

1212 return ser._values.copy() 

1213 

1214 return ser.reindex(ax)._values 

1215 

1216 raise ValueError("Incompatible indexer with Series") 

1217 

1218 def _align_frame(self, indexer, df: ABCDataFrame): 

1219 is_frame = self.ndim == 2 

1220 

1221 if isinstance(indexer, tuple): 

1222 

1223 idx, cols = None, None 

1224 sindexers = [] 

1225 for i, ix in enumerate(indexer): 

1226 ax = self.obj.axes[i] 

1227 if is_sequence(ix) or isinstance(ix, slice): 

1228 if isinstance(ix, np.ndarray): 

1229 ix = ix.ravel() 

1230 if idx is None: 

1231 idx = ax[ix] 

1232 elif cols is None: 

1233 cols = ax[ix] 

1234 else: 

1235 break 

1236 else: 

1237 sindexers.append(i) 

1238 

1239 if idx is not None and cols is not None: 

1240 

1241 if df.index.equals(idx) and df.columns.equals(cols): 

1242 val = df.copy()._values 

1243 else: 

1244 val = df.reindex(idx, columns=cols)._values 

1245 return val 

1246 

1247 elif (isinstance(indexer, slice) or is_list_like_indexer(indexer)) and is_frame: 

1248 ax = self.obj.index[indexer] 

1249 if df.index.equals(ax): 

1250 val = df.copy()._values 

1251 else: 

1252 

1253 # we have a multi-index and are trying to align 

1254 # with a particular, level GH3738 

1255 if ( 

1256 isinstance(ax, ABCMultiIndex) 

1257 and isinstance(df.index, ABCMultiIndex) 

1258 and ax.nlevels != df.index.nlevels 

1259 ): 

1260 raise TypeError( 

1261 "cannot align on a multi-index with out " 

1262 "specifying the join levels" 

1263 ) 

1264 

1265 val = df.reindex(index=ax)._values 

1266 return val 

1267 

1268 raise ValueError("Incompatible indexer with DataFrame") 

1269 

1270 def _getitem_tuple(self, tup: Tuple): 

1271 try: 

1272 return self._getitem_lowerdim(tup) 

1273 except IndexingError: 

1274 pass 

1275 

1276 # no multi-index, so validate all of the indexers 

1277 self._has_valid_tuple(tup) 

1278 

1279 # ugly hack for GH #836 

1280 if self._multi_take_opportunity(tup): 

1281 return self._multi_take(tup) 

1282 

1283 # no shortcut needed 

1284 retval = self.obj 

1285 for i, key in enumerate(tup): 

1286 if com.is_null_slice(key): 

1287 continue 

1288 

1289 retval = getattr(retval, self.name)._getitem_axis(key, axis=i) 

1290 

1291 return retval 

1292 

1293 def _multi_take_opportunity(self, tup: Tuple) -> bool: 

1294 """ 

1295 Check whether there is the possibility to use ``_multi_take``. 

1296 

1297 Currently the limit is that all axes being indexed, must be indexed with 

1298 list-likes. 

1299 

1300 Parameters 

1301 ---------- 

1302 tup : tuple 

1303 Tuple of indexers, one per axis. 

1304 

1305 Returns 

1306 ------- 

1307 bool 

1308 Whether the current indexing, 

1309 can be passed through `_multi_take`. 

1310 """ 

1311 if not all(is_list_like_indexer(x) for x in tup): 

1312 return False 

1313 

1314 # just too complicated 

1315 if any(com.is_bool_indexer(x) for x in tup): 

1316 return False 

1317 

1318 return True 

1319 

1320 def _multi_take(self, tup: Tuple): 

1321 """ 

1322 Create the indexers for the passed tuple of keys, and 

1323 executes the take operation. This allows the take operation to be 

1324 executed all at once, rather than once for each dimension. 

1325 Improving efficiency. 

1326 

1327 Parameters 

1328 ---------- 

1329 tup : tuple 

1330 Tuple of indexers, one per axis. 

1331 

1332 Returns 

1333 ------- 

1334 values: same type as the object being indexed 

1335 """ 

1336 # GH 836 

1337 o = self.obj 

1338 d = { 

1339 axis: self._get_listlike_indexer(key, axis) 

1340 for (key, axis) in zip(tup, o._AXIS_ORDERS) 

1341 } 

1342 return o._reindex_with_indexers(d, copy=True, allow_dups=True) 

1343 

1344 def _convert_for_reindex(self, key, axis: int): 

1345 return key 

1346 

1347 def _handle_lowerdim_multi_index_axis0(self, tup: Tuple): 

1348 # we have an axis0 multi-index, handle or raise 

1349 axis = self.axis or 0 

1350 try: 

1351 # fast path for series or for tup devoid of slices 

1352 return self._get_label(tup, axis=axis) 

1353 except TypeError: 

1354 # slices are unhashable 

1355 pass 

1356 except KeyError as ek: 

1357 # raise KeyError if number of indexers match 

1358 # else IndexingError will be raised 

1359 if len(tup) <= self.obj.index.nlevels and len(tup) > self.ndim: 

1360 raise ek 

1361 

1362 return None 

1363 

1364 def _getitem_lowerdim(self, tup: Tuple): 

1365 

1366 # we can directly get the axis result since the axis is specified 

1367 if self.axis is not None: 

1368 axis = self.obj._get_axis_number(self.axis) 

1369 return self._getitem_axis(tup, axis=axis) 

1370 

1371 # we may have a nested tuples indexer here 

1372 if self._is_nested_tuple_indexer(tup): 

1373 return self._getitem_nested_tuple(tup) 

1374 

1375 # we maybe be using a tuple to represent multiple dimensions here 

1376 ax0 = self.obj._get_axis(0) 

1377 # ...but iloc should handle the tuple as simple integer-location 

1378 # instead of checking it as multiindex representation (GH 13797) 

1379 if isinstance(ax0, ABCMultiIndex) and self.name != "iloc": 

1380 result = self._handle_lowerdim_multi_index_axis0(tup) 

1381 if result is not None: 

1382 return result 

1383 

1384 if len(tup) > self.ndim: 

1385 raise IndexingError("Too many indexers. handle elsewhere") 

1386 

1387 for i, key in enumerate(tup): 

1388 if is_label_like(key) or isinstance(key, tuple): 

1389 section = self._getitem_axis(key, axis=i) 

1390 

1391 # we have yielded a scalar ? 

1392 if not is_list_like_indexer(section): 

1393 return section 

1394 

1395 elif section.ndim == self.ndim: 

1396 # we're in the middle of slicing through a MultiIndex 

1397 # revise the key wrt to `section` by inserting an _NS 

1398 new_key = tup[:i] + (_NS,) + tup[i + 1 :] 

1399 

1400 else: 

1401 new_key = tup[:i] + tup[i + 1 :] 

1402 

1403 # unfortunately need an odious kludge here because of 

1404 # DataFrame transposing convention 

1405 if ( 

1406 isinstance(section, ABCDataFrame) 

1407 and i > 0 

1408 and len(new_key) == 2 

1409 ): 

1410 a, b = new_key 

1411 new_key = b, a 

1412 

1413 if len(new_key) == 1: 

1414 new_key = new_key[0] 

1415 

1416 # Slices should return views, but calling iloc/loc with a null 

1417 # slice returns a new object. 

1418 if com.is_null_slice(new_key): 

1419 return section 

1420 # This is an elided recursive call to iloc/loc/etc' 

1421 return getattr(section, self.name)[new_key] 

1422 

1423 raise IndexingError("not applicable") 

1424 

1425 def _getitem_nested_tuple(self, tup: Tuple): 

1426 # we have a nested tuple so have at least 1 multi-index level 

1427 # we should be able to match up the dimensionality here 

1428 

1429 # we have too many indexers for our dim, but have at least 1 

1430 # multi-index dimension, try to see if we have something like 

1431 # a tuple passed to a series with a multi-index 

1432 if len(tup) > self.ndim: 

1433 result = self._handle_lowerdim_multi_index_axis0(tup) 

1434 if result is not None: 

1435 return result 

1436 

1437 # this is a series with a multi-index specified a tuple of 

1438 # selectors 

1439 axis = self.axis or 0 

1440 return self._getitem_axis(tup, axis=axis) 

1441 

1442 # handle the multi-axis by taking sections and reducing 

1443 # this is iterative 

1444 obj = self.obj 

1445 axis = 0 

1446 for i, key in enumerate(tup): 

1447 

1448 if com.is_null_slice(key): 

1449 axis += 1 

1450 continue 

1451 

1452 current_ndim = obj.ndim 

1453 obj = getattr(obj, self.name)._getitem_axis(key, axis=axis) 

1454 axis += 1 

1455 

1456 # if we have a scalar, we are done 

1457 if is_scalar(obj) or not hasattr(obj, "ndim"): 

1458 break 

1459 

1460 # has the dim of the obj changed? 

1461 # GH 7199 

1462 if obj.ndim < current_ndim: 

1463 axis -= 1 

1464 

1465 return obj 

1466 

1467 # TODO: remove once geopandas no longer needs __getitem__ 

1468 def _getitem_axis(self, key, axis: int): 

1469 if is_iterator(key): 

1470 key = list(key) 

1471 self._validate_key(key, axis) 

1472 

1473 labels = self.obj._get_axis(axis) 

1474 if isinstance(key, slice): 

1475 return self._get_slice_axis(key, axis=axis) 

1476 elif is_list_like_indexer(key) and not ( 

1477 isinstance(key, tuple) and isinstance(labels, ABCMultiIndex) 

1478 ): 

1479 

1480 if hasattr(key, "ndim") and key.ndim > 1: 

1481 raise ValueError("Cannot index with multidimensional key") 

1482 

1483 return self._getitem_iterable(key, axis=axis) 

1484 else: 

1485 

1486 # maybe coerce a float scalar to integer 

1487 key = labels._maybe_cast_indexer(key) 

1488 

1489 if is_integer(key): 

1490 if axis == 0 and isinstance(labels, ABCMultiIndex): 

1491 try: 

1492 return self._get_label(key, axis=axis) 

1493 except (KeyError, TypeError): 

1494 if self.obj.index.levels[0].is_integer(): 

1495 raise 

1496 

1497 # this is the fallback! (for a non-float, non-integer index) 

1498 if not labels.is_floating() and not labels.is_integer(): 

1499 return self._get_loc(key, axis=axis) 

1500 

1501 return self._get_label(key, axis=axis) 

1502 

1503 def _get_listlike_indexer(self, key, axis: int, raise_missing: bool = False): 

1504 """ 

1505 Transform a list-like of keys into a new index and an indexer. 

1506 

1507 Parameters 

1508 ---------- 

1509 key : list-like 

1510 Targeted labels. 

1511 axis: int 

1512 Dimension on which the indexing is being made. 

1513 raise_missing: bool, default False 

1514 Whether to raise a KeyError if some labels were not found. 

1515 Will be removed in the future, and then this method will always behave as 

1516 if ``raise_missing=True``. 

1517 

1518 Raises 

1519 ------ 

1520 KeyError 

1521 If at least one key was requested but none was found, and 

1522 raise_missing=True. 

1523 

1524 Returns 

1525 ------- 

1526 keyarr: Index 

1527 New index (coinciding with 'key' if the axis is unique). 

1528 values : array-like 

1529 Indexer for the return object, -1 denotes keys not found. 

1530 """ 

1531 o = self.obj 

1532 ax = o._get_axis(axis) 

1533 

1534 # Have the index compute an indexer or return None 

1535 # if it cannot handle: 

1536 indexer, keyarr = ax._convert_listlike_indexer(key, kind=self.name) 

1537 # We only act on all found values: 

1538 if indexer is not None and (indexer != -1).all(): 

1539 self._validate_read_indexer(key, indexer, axis, raise_missing=raise_missing) 

1540 return ax[indexer], indexer 

1541 

1542 if ax.is_unique and not getattr(ax, "is_overlapping", False): 

1543 # If we are trying to get actual keys from empty Series, we 

1544 # patiently wait for a KeyError later on - otherwise, convert 

1545 if len(ax) or not len(key): 

1546 key = self._convert_for_reindex(key, axis) 

1547 indexer = ax.get_indexer_for(key) 

1548 keyarr = ax.reindex(keyarr)[0] 

1549 else: 

1550 keyarr, indexer, new_indexer = ax._reindex_non_unique(keyarr) 

1551 

1552 self._validate_read_indexer( 

1553 keyarr, indexer, o._get_axis_number(axis), raise_missing=raise_missing 

1554 ) 

1555 return keyarr, indexer 

1556 

1557 def _getitem_iterable(self, key, axis: int): 

1558 """ 

1559 Index current object with an an iterable key. 

1560 

1561 The iterable key can be a boolean indexer or a collection of keys. 

1562 

1563 Parameters 

1564 ---------- 

1565 key : iterable 

1566 Targeted labels or boolean indexer. 

1567 axis: int 

1568 Dimension on which the indexing is being made. 

1569 

1570 Raises 

1571 ------ 

1572 KeyError 

1573 If no key was found. Will change in the future to raise if not all 

1574 keys were found. 

1575 IndexingError 

1576 If the boolean indexer is unalignable with the object being 

1577 indexed. 

1578 

1579 Returns 

1580 ------- 

1581 scalar, DataFrame, or Series: indexed value(s). 

1582 """ 

1583 # caller is responsible for ensuring non-None axis 

1584 self._validate_key(key, axis) 

1585 

1586 labels = self.obj._get_axis(axis) 

1587 

1588 if com.is_bool_indexer(key): 

1589 # A boolean indexer 

1590 key = check_bool_indexer(labels, key) 

1591 (inds,) = key.nonzero() 

1592 return self.obj._take_with_is_copy(inds, axis=axis) 

1593 else: 

1594 # A collection of keys 

1595 keyarr, indexer = self._get_listlike_indexer(key, axis, raise_missing=False) 

1596 return self.obj._reindex_with_indexers( 

1597 {axis: [keyarr, indexer]}, copy=True, allow_dups=True 

1598 ) 

1599 

1600 def _validate_read_indexer( 

1601 self, key, indexer, axis: int, raise_missing: bool = False 

1602 ): 

1603 """ 

1604 Check that indexer can be used to return a result. 

1605 

1606 e.g. at least one element was found, 

1607 unless the list of keys was actually empty. 

1608 

1609 Parameters 

1610 ---------- 

1611 key : list-like 

1612 Targeted labels (only used to show correct error message). 

1613 indexer: array-like of booleans 

1614 Indices corresponding to the key, 

1615 (with -1 indicating not found). 

1616 axis: int 

1617 Dimension on which the indexing is being made. 

1618 raise_missing: bool 

1619 Whether to raise a KeyError if some labels are not found. Will be 

1620 removed in the future, and then this method will always behave as 

1621 if raise_missing=True. 

1622 

1623 Raises 

1624 ------ 

1625 KeyError 

1626 If at least one key was requested but none was found, and 

1627 raise_missing=True. 

1628 """ 

1629 ax = self.obj._get_axis(axis) 

1630 

1631 if len(key) == 0: 

1632 return 

1633 

1634 # Count missing values: 

1635 missing = (indexer < 0).sum() 

1636 

1637 if missing: 

1638 if missing == len(indexer): 

1639 axis_name = self.obj._get_axis_name(axis) 

1640 raise KeyError(f"None of [{key}] are in the [{axis_name}]") 

1641 

1642 # We (temporarily) allow for some missing keys with .loc, except in 

1643 # some cases (e.g. setting) in which "raise_missing" will be False 

1644 if not (self.name == "loc" and not raise_missing): 

1645 not_found = list(set(key) - set(ax)) 

1646 raise KeyError(f"{not_found} not in index") 

1647 

1648 # we skip the warning on Categorical/Interval 

1649 # as this check is actually done (check for 

1650 # non-missing values), but a bit later in the 

1651 # code, so we want to avoid warning & then 

1652 # just raising 

1653 if not (ax.is_categorical() or ax.is_interval()): 

1654 raise KeyError( 

1655 "Passing list-likes to .loc or [] with any missing labels " 

1656 "is no longer supported, see " 

1657 "https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike" # noqa:E501 

1658 ) 

1659 

1660 def _convert_to_indexer(self, obj, axis: int, raise_missing: bool = False): 

1661 """ 

1662 Convert indexing key into something we can use to do actual fancy 

1663 indexing on a ndarray. 

1664 

1665 Examples 

1666 ix[:5] -> slice(0, 5) 

1667 ix[[1,2,3]] -> [1,2,3] 

1668 ix[['foo', 'bar', 'baz']] -> [i, j, k] (indices of foo, bar, baz) 

1669 

1670 Going by Zen of Python? 

1671 'In the face of ambiguity, refuse the temptation to guess.' 

1672 raise AmbiguousIndexError with integer labels? 

1673 - No, prefer label-based indexing 

1674 """ 

1675 labels = self.obj._get_axis(axis) 

1676 

1677 if isinstance(obj, slice): 

1678 return self._convert_slice_indexer(obj, axis) 

1679 

1680 # try to find out correct indexer, if not type correct raise 

1681 try: 

1682 obj = self._convert_scalar_indexer(obj, axis) 

1683 except TypeError: 

1684 # but we will allow setting 

1685 pass 

1686 

1687 # see if we are positional in nature 

1688 is_int_index = labels.is_integer() 

1689 is_int_positional = is_integer(obj) and not is_int_index 

1690 

1691 # if we are a label return me 

1692 try: 

1693 return labels.get_loc(obj) 

1694 except LookupError: 

1695 if isinstance(obj, tuple) and isinstance(labels, ABCMultiIndex): 

1696 if len(obj) == labels.nlevels: 

1697 return {"key": obj} 

1698 raise 

1699 except TypeError: 

1700 pass 

1701 except ValueError: 

1702 if not is_int_positional: 

1703 raise 

1704 

1705 # a positional 

1706 if is_int_positional: 

1707 

1708 # if we are setting and its not a valid location 

1709 # its an insert which fails by definition 

1710 

1711 if self.name == "loc": 

1712 # always valid 

1713 return {"key": obj} 

1714 

1715 if obj >= self.obj.shape[axis] and not isinstance(labels, ABCMultiIndex): 

1716 # a positional 

1717 raise ValueError("cannot set by positional indexing with enlargement") 

1718 

1719 return obj 

1720 

1721 if is_nested_tuple(obj, labels): 

1722 return labels.get_locs(obj) 

1723 

1724 elif is_list_like_indexer(obj): 

1725 

1726 if com.is_bool_indexer(obj): 

1727 obj = check_bool_indexer(labels, obj) 

1728 (inds,) = obj.nonzero() 

1729 return inds 

1730 else: 

1731 # When setting, missing keys are not allowed, even with .loc: 

1732 return self._get_listlike_indexer(obj, axis, raise_missing=True)[1] 

1733 else: 

1734 try: 

1735 return labels.get_loc(obj) 

1736 except LookupError: 

1737 # allow a not found key only if we are a setter 

1738 if not is_list_like_indexer(obj): 

1739 return {"key": obj} 

1740 raise 

1741 

1742 def _get_slice_axis(self, slice_obj: slice, axis: int): 

1743 # caller is responsible for ensuring non-None axis 

1744 obj = self.obj 

1745 

1746 if not need_slice(slice_obj): 

1747 return obj.copy(deep=False) 

1748 

1749 indexer = self._convert_slice_indexer(slice_obj, axis) 

1750 return self._slice(indexer, axis=axis, kind="iloc") 

1751 

1752 

1753class _LocationIndexer(_NDFrameIndexer): 

1754 def __getitem__(self, key): 

1755 if type(key) is tuple: 

1756 key = tuple(com.apply_if_callable(x, self.obj) for x in key) 

1757 if self._is_scalar_access(key): 

1758 try: 

1759 return self._getitem_scalar(key) 

1760 except (KeyError, IndexError, AttributeError): 

1761 pass 

1762 return self._getitem_tuple(key) 

1763 else: 

1764 # we by definition only have the 0th axis 

1765 axis = self.axis or 0 

1766 

1767 maybe_callable = com.apply_if_callable(key, self.obj) 

1768 return self._getitem_axis(maybe_callable, axis=axis) 

1769 

1770 def _is_scalar_access(self, key: Tuple): 

1771 raise NotImplementedError() 

1772 

1773 def _getitem_scalar(self, key): 

1774 raise NotImplementedError() 

1775 

1776 def _getitem_axis(self, key, axis: int): 

1777 raise NotImplementedError() 

1778 

1779 def _getbool_axis(self, key, axis: int): 

1780 # caller is responsible for ensuring non-None axis 

1781 labels = self.obj._get_axis(axis) 

1782 key = check_bool_indexer(labels, key) 

1783 inds = key.nonzero()[0] 

1784 return self.obj._take_with_is_copy(inds, axis=axis) 

1785 

1786 def _get_slice_axis(self, slice_obj: slice, axis: int): 

1787 """ 

1788 This is pretty simple as we just have to deal with labels. 

1789 """ 

1790 # caller is responsible for ensuring non-None axis 

1791 obj = self.obj 

1792 if not need_slice(slice_obj): 

1793 return obj.copy(deep=False) 

1794 

1795 labels = obj._get_axis(axis) 

1796 indexer = labels.slice_indexer( 

1797 slice_obj.start, slice_obj.stop, slice_obj.step, kind=self.name 

1798 ) 

1799 

1800 if isinstance(indexer, slice): 

1801 return self._slice(indexer, axis=axis, kind="iloc") 

1802 else: 

1803 # DatetimeIndex overrides Index.slice_indexer and may 

1804 # return a DatetimeIndex instead of a slice object. 

1805 return self.obj._take_with_is_copy(indexer, axis=axis) 

1806 

1807 

1808@Appender(IndexingMixin.loc.__doc__) 

1809class _LocIndexer(_LocationIndexer): 

1810 _valid_types = ( 

1811 "labels (MUST BE IN THE INDEX), slices of labels (BOTH " 

1812 "endpoints included! Can be slices of integers if the " 

1813 "index is integers), listlike of labels, boolean" 

1814 ) 

1815 

1816 @Appender(_NDFrameIndexer._validate_key.__doc__) 

1817 def _validate_key(self, key, axis: int): 

1818 

1819 # valid for a collection of labels (we check their presence later) 

1820 # slice of labels (where start-end in labels) 

1821 # slice of integers (only if in the labels) 

1822 # boolean 

1823 

1824 if isinstance(key, slice): 

1825 return 

1826 

1827 if com.is_bool_indexer(key): 

1828 return 

1829 

1830 if not is_list_like_indexer(key): 

1831 self._convert_scalar_indexer(key, axis) 

1832 

1833 def _is_scalar_access(self, key: Tuple) -> bool: 

1834 """ 

1835 Returns 

1836 ------- 

1837 bool 

1838 """ 

1839 # this is a shortcut accessor to both .loc and .iloc 

1840 # that provide the equivalent access of .at and .iat 

1841 # a) avoid getting things via sections and (to minimize dtype changes) 

1842 # b) provide a performant path 

1843 if len(key) != self.ndim: 

1844 return False 

1845 

1846 for i, k in enumerate(key): 

1847 if not is_scalar(k): 

1848 return False 

1849 

1850 ax = self.obj.axes[i] 

1851 if isinstance(ax, ABCMultiIndex): 

1852 return False 

1853 

1854 if isinstance(k, str) and ax._supports_partial_string_indexing: 

1855 # partial string indexing, df.loc['2000', 'A'] 

1856 # should not be considered scalar 

1857 return False 

1858 

1859 if not ax.is_unique: 

1860 return False 

1861 

1862 return True 

1863 

1864 def _getitem_scalar(self, key): 

1865 # a fast-path to scalar access 

1866 # if not, raise 

1867 values = self.obj._get_value(*key) 

1868 return values 

1869 

1870 def _get_partial_string_timestamp_match_key(self, key, labels): 

1871 """ 

1872 Translate any partial string timestamp matches in key, returning the 

1873 new key. 

1874 

1875 (GH 10331) 

1876 """ 

1877 if isinstance(labels, ABCMultiIndex): 

1878 if ( 

1879 isinstance(key, str) 

1880 and labels.levels[0]._supports_partial_string_indexing 

1881 ): 

1882 # Convert key '2016-01-01' to 

1883 # ('2016-01-01'[, slice(None, None, None)]+) 

1884 key = tuple([key] + [slice(None)] * (len(labels.levels) - 1)) 

1885 

1886 if isinstance(key, tuple): 

1887 # Convert (..., '2016-01-01', ...) in tuple to 

1888 # (..., slice('2016-01-01', '2016-01-01', None), ...) 

1889 new_key = [] 

1890 for i, component in enumerate(key): 

1891 if ( 

1892 isinstance(component, str) 

1893 and labels.levels[i]._supports_partial_string_indexing 

1894 ): 

1895 new_key.append(slice(component, component, None)) 

1896 else: 

1897 new_key.append(component) 

1898 key = tuple(new_key) 

1899 

1900 return key 

1901 

1902 def _getitem_axis(self, key, axis: int): 

1903 key = item_from_zerodim(key) 

1904 if is_iterator(key): 

1905 key = list(key) 

1906 

1907 labels = self.obj._get_axis(axis) 

1908 key = self._get_partial_string_timestamp_match_key(key, labels) 

1909 

1910 if isinstance(key, slice): 

1911 self._validate_key(key, axis) 

1912 return self._get_slice_axis(key, axis=axis) 

1913 elif com.is_bool_indexer(key): 

1914 return self._getbool_axis(key, axis=axis) 

1915 elif is_list_like_indexer(key): 

1916 

1917 # convert various list-like indexers 

1918 # to a list of keys 

1919 # we will use the *values* of the object 

1920 # and NOT the index if its a PandasObject 

1921 if isinstance(labels, ABCMultiIndex): 

1922 

1923 if isinstance(key, (ABCSeries, np.ndarray)) and key.ndim <= 1: 

1924 # Series, or 0,1 ndim ndarray 

1925 # GH 14730 

1926 key = list(key) 

1927 elif isinstance(key, ABCDataFrame): 

1928 # GH 15438 

1929 raise NotImplementedError( 

1930 "Indexing a MultiIndex with a " 

1931 "DataFrame key is not " 

1932 "implemented" 

1933 ) 

1934 elif hasattr(key, "ndim") and key.ndim > 1: 

1935 raise NotImplementedError( 

1936 "Indexing a MultiIndex with a " 

1937 "multidimensional key is not " 

1938 "implemented" 

1939 ) 

1940 

1941 if ( 

1942 not isinstance(key, tuple) 

1943 and len(key) 

1944 and not isinstance(key[0], tuple) 

1945 ): 

1946 key = tuple([key]) 

1947 

1948 # an iterable multi-selection 

1949 if not (isinstance(key, tuple) and isinstance(labels, ABCMultiIndex)): 

1950 

1951 if hasattr(key, "ndim") and key.ndim > 1: 

1952 raise ValueError("Cannot index with multidimensional key") 

1953 

1954 return self._getitem_iterable(key, axis=axis) 

1955 

1956 # nested tuple slicing 

1957 if is_nested_tuple(key, labels): 

1958 locs = labels.get_locs(key) 

1959 indexer = [slice(None)] * self.ndim 

1960 indexer[axis] = locs 

1961 return self.obj.iloc[tuple(indexer)] 

1962 

1963 # fall thru to straight lookup 

1964 self._validate_key(key, axis) 

1965 return self._get_label(key, axis=axis) 

1966 

1967 

1968@Appender(IndexingMixin.iloc.__doc__) 

1969class _iLocIndexer(_LocationIndexer): 

1970 _valid_types = ( 

1971 "integer, integer slice (START point is INCLUDED, END " 

1972 "point is EXCLUDED), listlike of integers, boolean array" 

1973 ) 

1974 _get_slice_axis = _NDFrameIndexer._get_slice_axis 

1975 

1976 def _validate_key(self, key, axis: int): 

1977 if com.is_bool_indexer(key): 

1978 if hasattr(key, "index") and isinstance(key.index, Index): 

1979 if key.index.inferred_type == "integer": 

1980 raise NotImplementedError( 

1981 "iLocation based boolean " 

1982 "indexing on an integer type " 

1983 "is not available" 

1984 ) 

1985 raise ValueError( 

1986 "iLocation based boolean indexing cannot use " 

1987 "an indexable as a mask" 

1988 ) 

1989 return 

1990 

1991 if isinstance(key, slice): 

1992 return 

1993 elif is_integer(key): 

1994 self._validate_integer(key, axis) 

1995 elif isinstance(key, tuple): 

1996 # a tuple should already have been caught by this point 

1997 # so don't treat a tuple as a valid indexer 

1998 raise IndexingError("Too many indexers") 

1999 elif is_list_like_indexer(key): 

2000 arr = np.array(key) 

2001 len_axis = len(self.obj._get_axis(axis)) 

2002 

2003 # check that the key has a numeric dtype 

2004 if not is_numeric_dtype(arr.dtype): 

2005 raise IndexError(f".iloc requires numeric indexers, got {arr}") 

2006 

2007 # check that the key does not exceed the maximum size of the index 

2008 if len(arr) and (arr.max() >= len_axis or arr.min() < -len_axis): 

2009 raise IndexError("positional indexers are out-of-bounds") 

2010 else: 

2011 raise ValueError(f"Can only index by location with a [{self._valid_types}]") 

2012 

2013 def _has_valid_setitem_indexer(self, indexer): 

2014 self._has_valid_positional_setitem_indexer(indexer) 

2015 

2016 def _is_scalar_access(self, key: Tuple) -> bool: 

2017 """ 

2018 Returns 

2019 ------- 

2020 bool 

2021 """ 

2022 # this is a shortcut accessor to both .loc and .iloc 

2023 # that provide the equivalent access of .at and .iat 

2024 # a) avoid getting things via sections and (to minimize dtype changes) 

2025 # b) provide a performant path 

2026 if len(key) != self.ndim: 

2027 return False 

2028 

2029 for i, k in enumerate(key): 

2030 if not is_integer(k): 

2031 return False 

2032 

2033 ax = self.obj.axes[i] 

2034 if not ax.is_unique: 

2035 return False 

2036 

2037 return True 

2038 

2039 def _getitem_scalar(self, key): 

2040 # a fast-path to scalar access 

2041 # if not, raise 

2042 values = self.obj._get_value(*key, takeable=True) 

2043 return values 

2044 

2045 def _validate_integer(self, key: int, axis: int) -> None: 

2046 """ 

2047 Check that 'key' is a valid position in the desired axis. 

2048 

2049 Parameters 

2050 ---------- 

2051 key : int 

2052 Requested position. 

2053 axis : int 

2054 Desired axis. 

2055 

2056 Raises 

2057 ------ 

2058 IndexError 

2059 If 'key' is not a valid position in axis 'axis'. 

2060 """ 

2061 len_axis = len(self.obj._get_axis(axis)) 

2062 if key >= len_axis or key < -len_axis: 

2063 raise IndexError("single positional indexer is out-of-bounds") 

2064 

2065 def _getitem_tuple(self, tup: Tuple): 

2066 

2067 self._has_valid_tuple(tup) 

2068 try: 

2069 return self._getitem_lowerdim(tup) 

2070 except IndexingError: 

2071 pass 

2072 

2073 retval = self.obj 

2074 axis = 0 

2075 for i, key in enumerate(tup): 

2076 if com.is_null_slice(key): 

2077 axis += 1 

2078 continue 

2079 

2080 retval = getattr(retval, self.name)._getitem_axis(key, axis=axis) 

2081 

2082 # if the dim was reduced, then pass a lower-dim the next time 

2083 if retval.ndim < self.ndim: 

2084 # TODO: this is never reached in tests; can we confirm that 

2085 # it is impossible? 

2086 axis -= 1 

2087 

2088 # try to get for the next axis 

2089 axis += 1 

2090 

2091 return retval 

2092 

2093 def _get_list_axis(self, key, axis: int): 

2094 """ 

2095 Return Series values by list or array of integers. 

2096 

2097 Parameters 

2098 ---------- 

2099 key : list-like positional indexer 

2100 axis : int 

2101 

2102 Returns 

2103 ------- 

2104 Series object 

2105 

2106 Notes 

2107 ----- 

2108 `axis` can only be zero. 

2109 """ 

2110 try: 

2111 return self.obj._take_with_is_copy(key, axis=axis) 

2112 except IndexError: 

2113 # re-raise with different error message 

2114 raise IndexError("positional indexers are out-of-bounds") 

2115 

2116 def _getitem_axis(self, key, axis: int): 

2117 if isinstance(key, slice): 

2118 return self._get_slice_axis(key, axis=axis) 

2119 

2120 if isinstance(key, list): 

2121 key = np.asarray(key) 

2122 

2123 if com.is_bool_indexer(key): 

2124 self._validate_key(key, axis) 

2125 return self._getbool_axis(key, axis=axis) 

2126 

2127 # a list of integers 

2128 elif is_list_like_indexer(key): 

2129 return self._get_list_axis(key, axis=axis) 

2130 

2131 # a single integer 

2132 else: 

2133 key = item_from_zerodim(key) 

2134 if not is_integer(key): 

2135 raise TypeError("Cannot index by location index with a non-integer key") 

2136 

2137 # validate the location 

2138 self._validate_integer(key, axis) 

2139 

2140 return self._get_loc(key, axis=axis) 

2141 

2142 # raise_missing is included for compat with the parent class signature 

2143 def _convert_to_indexer(self, obj, axis: int, raise_missing: bool = False): 

2144 """ 

2145 Much simpler as we only have to deal with our valid types. 

2146 """ 

2147 # make need to convert a float key 

2148 if isinstance(obj, slice): 

2149 return self._convert_slice_indexer(obj, axis) 

2150 

2151 elif is_float(obj): 

2152 return self._convert_scalar_indexer(obj, axis) 

2153 

2154 try: 

2155 self._validate_key(obj, axis) 

2156 return obj 

2157 except ValueError: 

2158 raise ValueError(f"Can only index by location with a [{self._valid_types}]") 

2159 

2160 

2161class _ScalarAccessIndexer(_NDFrameIndexerBase): 

2162 """ 

2163 Access scalars quickly. 

2164 """ 

2165 

2166 def _convert_key(self, key, is_setter: bool = False): 

2167 raise AbstractMethodError(self) 

2168 

2169 def __getitem__(self, key): 

2170 if not isinstance(key, tuple): 

2171 

2172 # we could have a convertible item here (e.g. Timestamp) 

2173 if not is_list_like_indexer(key): 

2174 key = tuple([key]) 

2175 else: 

2176 raise ValueError("Invalid call for scalar access (getting)!") 

2177 

2178 key = self._convert_key(key) 

2179 return self.obj._get_value(*key, takeable=self._takeable) 

2180 

2181 def __setitem__(self, key, value): 

2182 if isinstance(key, tuple): 

2183 key = tuple(com.apply_if_callable(x, self.obj) for x in key) 

2184 else: 

2185 # scalar callable may return tuple 

2186 key = com.apply_if_callable(key, self.obj) 

2187 

2188 if not isinstance(key, tuple): 

2189 key = _tuplify(self.ndim, key) 

2190 if len(key) != self.ndim: 

2191 raise ValueError("Not enough indexers for scalar access (setting)!") 

2192 key = list(self._convert_key(key, is_setter=True)) 

2193 key.append(value) 

2194 self.obj._set_value(*key, takeable=self._takeable) 

2195 

2196 

2197@Appender(IndexingMixin.at.__doc__) 

2198class _AtIndexer(_ScalarAccessIndexer): 

2199 _takeable = False 

2200 

2201 def _convert_key(self, key, is_setter: bool = False): 

2202 """ 

2203 Require they keys to be the same type as the index. (so we don't 

2204 fallback) 

2205 """ 

2206 # allow arbitrary setting 

2207 if is_setter: 

2208 return list(key) 

2209 

2210 for ax, i in zip(self.obj.axes, key): 

2211 if ax.is_integer(): 

2212 if not is_integer(i): 

2213 raise ValueError( 

2214 "At based indexing on an integer index " 

2215 "can only have integer indexers" 

2216 ) 

2217 else: 

2218 if is_integer(i) and not ax.holds_integer(): 

2219 raise ValueError( 

2220 "At based indexing on an non-integer " 

2221 "index can only have non-integer " 

2222 "indexers" 

2223 ) 

2224 return key 

2225 

2226 

2227@Appender(IndexingMixin.iat.__doc__) 

2228class _iAtIndexer(_ScalarAccessIndexer): 

2229 _takeable = True 

2230 

2231 def _convert_key(self, key, is_setter: bool = False): 

2232 """ 

2233 Require integer args. (and convert to label arguments) 

2234 """ 

2235 for a, i in zip(self.obj.axes, key): 

2236 if not is_integer(i): 

2237 raise ValueError("iAt based indexing can only have integer indexers") 

2238 return key 

2239 

2240 

2241def _tuplify(ndim: int, loc: Hashable) -> Tuple[Union[Hashable, slice], ...]: 

2242 """ 

2243 Given an indexer for the first dimension, create an equivalent tuple 

2244 for indexing over all dimensions. 

2245 

2246 Parameters 

2247 ---------- 

2248 ndim : int 

2249 loc : object 

2250 

2251 Returns 

2252 ------- 

2253 tuple 

2254 """ 

2255 _tup: List[Union[Hashable, slice]] 

2256 _tup = [slice(None, None) for _ in range(ndim)] 

2257 _tup[0] = loc 

2258 return tuple(_tup) 

2259 

2260 

2261def convert_to_index_sliceable(obj, key): 

2262 """ 

2263 If we are index sliceable, then return my slicer, otherwise return None. 

2264 """ 

2265 idx = obj.index 

2266 if isinstance(key, slice): 

2267 return idx._convert_slice_indexer(key, kind="getitem") 

2268 

2269 elif isinstance(key, str): 

2270 

2271 # we are an actual column 

2272 if key in obj._data.items: 

2273 return None 

2274 

2275 # We might have a datetimelike string that we can translate to a 

2276 # slice here via partial string indexing 

2277 if idx._supports_partial_string_indexing: 

2278 try: 

2279 return idx._get_string_slice(key) 

2280 except (KeyError, ValueError, NotImplementedError): 

2281 return None 

2282 

2283 return None 

2284 

2285 

2286def check_bool_indexer(index: Index, key) -> np.ndarray: 

2287 """ 

2288 Check if key is a valid boolean indexer for an object with such index and 

2289 perform reindexing or conversion if needed. 

2290 

2291 This function assumes that is_bool_indexer(key) == True. 

2292 

2293 Parameters 

2294 ---------- 

2295 index : Index 

2296 Index of the object on which the indexing is done. 

2297 key : list-like 

2298 Boolean indexer to check. 

2299 

2300 Returns 

2301 ------- 

2302 np.array 

2303 Resulting key. 

2304 

2305 Raises 

2306 ------ 

2307 IndexError 

2308 If the key does not have the same length as index. 

2309 IndexingError 

2310 If the index of the key is unalignable to index. 

2311 """ 

2312 result = key 

2313 if isinstance(key, ABCSeries) and not key.index.equals(index): 

2314 result = result.reindex(index) 

2315 mask = isna(result._values) 

2316 if mask.any(): 

2317 raise IndexingError( 

2318 "Unalignable boolean Series provided as " 

2319 "indexer (index of the boolean Series and of " 

2320 "the indexed object do not match)." 

2321 ) 

2322 result = result.astype(bool)._values 

2323 elif is_object_dtype(key): 

2324 # key might be object-dtype bool, check_array_indexer needs bool array 

2325 result = np.asarray(result, dtype=bool) 

2326 result = check_array_indexer(index, result) 

2327 else: 

2328 result = check_array_indexer(index, result) 

2329 

2330 return result 

2331 

2332 

2333def convert_missing_indexer(indexer): 

2334 """ 

2335 Reverse convert a missing indexer, which is a dict 

2336 return the scalar indexer and a boolean indicating if we converted 

2337 """ 

2338 if isinstance(indexer, dict): 

2339 

2340 # a missing key (but not a tuple indexer) 

2341 indexer = indexer["key"] 

2342 

2343 if isinstance(indexer, bool): 

2344 raise KeyError("cannot use a single bool to index into setitem") 

2345 return indexer, True 

2346 

2347 return indexer, False 

2348 

2349 

2350def convert_from_missing_indexer_tuple(indexer, axes): 

2351 """ 

2352 Create a filtered indexer that doesn't have any missing indexers. 

2353 """ 

2354 

2355 def get_indexer(_i, _idx): 

2356 return axes[_i].get_loc(_idx["key"]) if isinstance(_idx, dict) else _idx 

2357 

2358 return tuple(get_indexer(_i, _idx) for _i, _idx in enumerate(indexer)) 

2359 

2360 

2361def maybe_convert_ix(*args): 

2362 """ 

2363 We likely want to take the cross-product. 

2364 """ 

2365 ixify = True 

2366 for arg in args: 

2367 if not isinstance(arg, (np.ndarray, list, ABCSeries, Index)): 

2368 ixify = False 

2369 

2370 if ixify: 

2371 return np.ix_(*args) 

2372 else: 

2373 return args 

2374 

2375 

2376def is_nested_tuple(tup, labels) -> bool: 

2377 """ 

2378 Returns 

2379 ------- 

2380 bool 

2381 """ 

2382 # check for a compatible nested tuple and multiindexes among the axes 

2383 if not isinstance(tup, tuple): 

2384 return False 

2385 

2386 for i, k in enumerate(tup): 

2387 

2388 if is_list_like(k) or isinstance(k, slice): 

2389 return isinstance(labels, ABCMultiIndex) 

2390 

2391 return False 

2392 

2393 

2394def is_label_like(key) -> bool: 

2395 """ 

2396 Returns 

2397 ------- 

2398 bool 

2399 """ 

2400 # select a label or row 

2401 return not isinstance(key, slice) and not is_list_like_indexer(key) 

2402 

2403 

2404def need_slice(obj) -> bool: 

2405 """ 

2406 Returns 

2407 ------- 

2408 bool 

2409 """ 

2410 return ( 

2411 obj.start is not None 

2412 or obj.stop is not None 

2413 or (obj.step is not None and obj.step != 1) 

2414 ) 

2415 

2416 

2417def _non_reducing_slice(slice_): 

2418 """ 

2419 Ensurse that a slice doesn't reduce to a Series or Scalar. 

2420 

2421 Any user-paseed `subset` should have this called on it 

2422 to make sure we're always working with DataFrames. 

2423 """ 

2424 # default to column slice, like DataFrame 

2425 # ['A', 'B'] -> IndexSlices[:, ['A', 'B']] 

2426 kinds = (ABCSeries, np.ndarray, Index, list, str) 

2427 if isinstance(slice_, kinds): 

2428 slice_ = IndexSlice[:, slice_] 

2429 

2430 def pred(part) -> bool: 

2431 """ 

2432 Returns 

2433 ------- 

2434 bool 

2435 True if slice does *not* reduce, 

2436 False if `part` is a tuple. 

2437 """ 

2438 # true when slice does *not* reduce, False when part is a tuple, 

2439 # i.e. MultiIndex slice 

2440 return (isinstance(part, slice) or is_list_like(part)) and not isinstance( 

2441 part, tuple 

2442 ) 

2443 

2444 if not is_list_like(slice_): 

2445 if not isinstance(slice_, slice): 

2446 # a 1-d slice, like df.loc[1] 

2447 slice_ = [[slice_]] 

2448 else: 

2449 # slice(a, b, c) 

2450 slice_ = [slice_] # to tuplize later 

2451 else: 

2452 slice_ = [part if pred(part) else [part] for part in slice_] 

2453 return tuple(slice_) 

2454 

2455 

2456def _maybe_numeric_slice(df, slice_, include_bool=False): 

2457 """ 

2458 Want nice defaults for background_gradient that don't break 

2459 with non-numeric data. But if slice_ is passed go with that. 

2460 """ 

2461 if slice_ is None: 

2462 dtypes = [np.number] 

2463 if include_bool: 

2464 dtypes.append(bool) 

2465 slice_ = IndexSlice[:, df.select_dtypes(include=dtypes).columns] 

2466 return slice_ 

2467 

2468 

2469def _can_do_equal_len(labels, value, plane_indexer, lplane_indexer, obj) -> bool: 

2470 """ 

2471 Returns 

2472 ------- 

2473 bool 

2474 True if we have an equal len settable. 

2475 """ 

2476 if not len(labels) == 1 or not np.iterable(value) or is_scalar(plane_indexer[0]): 

2477 return False 

2478 

2479 item = labels[0] 

2480 index = obj[item].index 

2481 

2482 values_len = len(value) 

2483 # equal len list/ndarray 

2484 if len(index) == values_len: 

2485 return True 

2486 elif lplane_indexer == values_len: 

2487 return True 

2488 

2489 return False