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

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

959 statements  

1from __future__ import annotations 

2 

3from contextlib import suppress 

4import sys 

5from typing import ( 

6 TYPE_CHECKING, 

7 Any, 

8 Self, 

9 cast, 

10 final, 

11) 

12import warnings 

13 

14import numpy as np 

15 

16from pandas._libs.indexing import NDFrameIndexerBase 

17from pandas._libs.lib import item_from_zerodim 

18from pandas.compat import CHAINED_WARNING_DISABLED 

19from pandas.compat._constants import REF_COUNT_IDX 

20from pandas.errors import ( 

21 AbstractMethodError, 

22 ChainedAssignmentError, 

23 IndexingError, 

24 InvalidIndexError, 

25 LossySetitemError, 

26) 

27from pandas.errors.cow import _chained_assignment_msg 

28from pandas.util._decorators import ( 

29 doc, 

30) 

31 

32from pandas.core.dtypes.cast import ( 

33 can_hold_element, 

34 maybe_promote, 

35) 

36from pandas.core.dtypes.common import ( 

37 is_array_like, 

38 is_bool_dtype, 

39 is_hashable, 

40 is_integer, 

41 is_iterator, 

42 is_list_like, 

43 is_numeric_dtype, 

44 is_object_dtype, 

45 is_scalar, 

46 is_sequence, 

47) 

48from pandas.core.dtypes.concat import concat_compat 

49from pandas.core.dtypes.dtypes import ExtensionDtype 

50from pandas.core.dtypes.generic import ( 

51 ABCDataFrame, 

52 ABCSeries, 

53) 

54from pandas.core.dtypes.missing import ( 

55 construct_1d_array_from_inferred_fill_value, 

56 infer_fill_value, 

57 is_valid_na_for_dtype, 

58 isna, 

59 na_value_for_dtype, 

60) 

61 

62from pandas.core import algorithms as algos 

63import pandas.core.common as com 

64from pandas.core.construction import ( 

65 array as pd_array, 

66 extract_array, 

67) 

68from pandas.core.indexers import ( 

69 check_array_indexer, 

70 is_list_like_indexer, 

71 is_scalar_indexer, 

72 length_of_indexer, 

73) 

74from pandas.core.indexes.api import ( 

75 Index, 

76 MultiIndex, 

77) 

78 

79if TYPE_CHECKING: 

80 from collections.abc import ( 

81 Hashable, 

82 Sequence, 

83 ) 

84 

85 from pandas._typing import ( 

86 Axis, 

87 AxisInt, 

88 T, 

89 npt, 

90 ) 

91 

92 from pandas import ( 

93 DataFrame, 

94 Series, 

95 ) 

96 

97# "null slice" 

98_NS = slice(None, None) 

99_one_ellipsis_message = "indexer may only contain one '...' entry" 

100 

101 

102# the public IndexSlicerMaker 

103class _IndexSlice: 

104 """ 

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

106 

107 See Also 

108 -------- 

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

110 

111 Notes 

112 ----- 

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

114 for further info on slicing a MultiIndex. 

115 

116 Examples 

117 -------- 

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

119 >>> columns = ["foo", "bar"] 

120 >>> dfmi = pd.DataFrame( 

121 ... np.arange(16).reshape((len(midx), len(columns))), 

122 ... index=midx, 

123 ... columns=columns, 

124 ... ) 

125 

126 Using the default slice command: 

127 

128 >>> dfmi.loc[(slice(None), slice("B0", "B1")), :] 

129 foo bar 

130 A0 B0 0 1 

131 B1 2 3 

132 A1 B0 8 9 

133 B1 10 11 

134 

135 Using the IndexSlice class for a more intuitive command: 

136 

137 >>> idx = pd.IndexSlice 

138 >>> dfmi.loc[idx[:, "B0":"B1"], :] 

139 foo bar 

140 A0 B0 0 1 

141 B1 2 3 

142 A1 B0 8 9 

143 B1 10 11 

144 """ 

145 

146 def __getitem__(self, arg): 

147 return arg 

148 

149 

150IndexSlice = _IndexSlice() 

151IndexSlice.__module__ = "pandas" 

152 

153 

154class IndexingMixin: 

155 """ 

156 Mixin for adding .loc/.iloc/.at/.iat to Dataframes and Series. 

157 """ 

158 

159 @property 

160 def iloc(self) -> _iLocIndexer: 

161 """ 

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

163 

164 .. versionchanged:: 3.0 

165 

166 Callables which return a tuple are deprecated as input. 

167 

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

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

170 array. 

171 

172 Allowed inputs are: 

173 

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

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

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

177 - A boolean array. 

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

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

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

181 calling object, but would like to base your selection on 

182 some value. 

183 - A tuple of row and column indexes. The tuple elements consist of one of the 

184 above inputs, e.g. ``(0, 1)``. 

185 

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

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

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

189 

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

191 

192 See Also 

193 -------- 

194 DataFrame.iat : Fast integer location scalar accessor. 

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

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

197 selection by position. 

198 

199 Examples 

200 -------- 

201 >>> mydict = [ 

202 ... {"a": 1, "b": 2, "c": 3, "d": 4}, 

203 ... {"a": 100, "b": 200, "c": 300, "d": 400}, 

204 ... {"a": 1000, "b": 2000, "c": 3000, "d": 4000}, 

205 ... ] 

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

207 >>> df 

208 a b c d 

209 0 1 2 3 4 

210 1 100 200 300 400 

211 2 1000 2000 3000 4000 

212 

213 **Indexing just the rows** 

214 

215 With a scalar integer. 

216 

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

218 <class 'pandas.Series'> 

219 >>> df.iloc[0] 

220 a 1 

221 b 2 

222 c 3 

223 d 4 

224 Name: 0, dtype: int64 

225 

226 With a list of integers. 

227 

228 >>> df.iloc[[0]] 

229 a b c d 

230 0 1 2 3 4 

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

232 <class 'pandas.DataFrame'> 

233 

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

235 a b c d 

236 0 1 2 3 4 

237 1 100 200 300 400 

238 

239 With a `slice` object. 

240 

241 >>> df.iloc[:3] 

242 a b c d 

243 0 1 2 3 4 

244 1 100 200 300 400 

245 2 1000 2000 3000 4000 

246 

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

248 

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

250 a b c d 

251 0 1 2 3 4 

252 2 1000 2000 3000 4000 

253 

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

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

256 the rows whose index label even. 

257 

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

259 a b c d 

260 0 1 2 3 4 

261 2 1000 2000 3000 4000 

262 

263 **Indexing both axes** 

264 

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

266 select the entire axis. 

267 

268 With scalar integers. 

269 

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

271 np.int64(2) 

272 

273 With lists of integers. 

274 

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

276 b d 

277 0 2 4 

278 2 2000 4000 

279 

280 With `slice` objects. 

281 

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

283 a b c 

284 1 100 200 300 

285 2 1000 2000 3000 

286 

287 With a boolean array whose length matches the columns. 

288 

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

290 a c 

291 0 1 3 

292 1 100 300 

293 2 1000 3000 

294 

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

296 

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

298 a c 

299 0 1 3 

300 1 100 300 

301 2 1000 3000 

302 """ 

303 return _iLocIndexer("iloc", self) 

304 

305 @property 

306 def loc(self) -> _LocIndexer: 

307 """ 

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

309 

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

311 boolean array. 

312 

313 Allowed inputs are: 

314 

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

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

317 integer position along the index). 

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

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

320 

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

322 start and the stop are included 

323 

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

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

326 - An alignable boolean Series. The index of the key will be aligned before 

327 masking. 

328 - An alignable Index. The Index of the returned selection will be the input. 

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

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

331 

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

333 

334 Raises 

335 ------ 

336 KeyError 

337 If any items are not found. 

338 IndexingError 

339 If an indexed key is passed and its index is unalignable to the frame index. 

340 

341 See Also 

342 -------- 

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

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

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

346 Series/DataFrame. 

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

348 

349 Examples 

350 -------- 

351 **Getting values** 

352 

353 >>> df = pd.DataFrame( 

354 ... [[1, 2], [4, 5], [7, 8]], 

355 ... index=["cobra", "viper", "sidewinder"], 

356 ... columns=["max_speed", "shield"], 

357 ... ) 

358 >>> df 

359 max_speed shield 

360 cobra 1 2 

361 viper 4 5 

362 sidewinder 7 8 

363 

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

365 

366 >>> df.loc["viper"] 

367 max_speed 4 

368 shield 5 

369 Name: viper, dtype: int64 

370 

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

372 

373 >>> df.loc[["viper", "sidewinder"]] 

374 max_speed shield 

375 viper 4 5 

376 sidewinder 7 8 

377 

378 Single label for row and column 

379 

380 >>> df.loc["cobra", "shield"] 

381 np.int64(2) 

382 

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

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

385 

386 >>> df.loc["cobra":"viper", "max_speed"] 

387 cobra 1 

388 viper 4 

389 Name: max_speed, dtype: int64 

390 

391 Boolean list with the same length as the row axis 

392 

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

394 max_speed shield 

395 sidewinder 7 8 

396 

397 Alignable boolean Series: 

398 

399 >>> df.loc[ 

400 ... pd.Series([False, True, False], index=["viper", "sidewinder", "cobra"]) 

401 ... ] 

402 max_speed shield 

403 sidewinder 7 8 

404 

405 Index (same behavior as ``df.reindex``) 

406 

407 >>> df.loc[pd.Index(["cobra", "viper"], name="foo")] 

408 max_speed shield 

409 foo 

410 cobra 1 2 

411 viper 4 5 

412 

413 Conditional that returns a boolean Series 

414 

415 >>> df.loc[df["shield"] > 6] 

416 max_speed shield 

417 sidewinder 7 8 

418 

419 Conditional that returns a boolean Series with column labels specified 

420 

421 >>> df.loc[df["shield"] > 6, ["max_speed"]] 

422 max_speed 

423 sidewinder 7 

424 

425 Multiple conditional using ``&`` that returns a boolean Series 

426 

427 >>> df.loc[(df["max_speed"] > 1) & (df["shield"] < 8)] 

428 max_speed shield 

429 viper 4 5 

430 

431 Multiple conditional using ``|`` that returns a boolean Series 

432 

433 >>> df.loc[(df["max_speed"] > 4) | (df["shield"] < 5)] 

434 max_speed shield 

435 cobra 1 2 

436 sidewinder 7 8 

437 

438 Please ensure that each condition is wrapped in parentheses ``()``. 

439 See the :ref:`user guide<indexing.boolean>` 

440 for more details and explanations of Boolean indexing. 

441 

442 .. note:: 

443 If you find yourself using 3 or more conditionals in ``.loc[]``, 

444 consider using :ref:`advanced indexing<advanced.advanced_hierarchical>`. 

445 

446 See below for using ``.loc[]`` on MultiIndex DataFrames. 

447 

448 Callable that returns a boolean Series 

449 

450 >>> df.loc[lambda df: df["shield"] == 8] 

451 max_speed shield 

452 sidewinder 7 8 

453 

454 **Setting values** 

455 

456 Set value for all items matching the list of labels 

457 

458 >>> df.loc[["viper", "sidewinder"], ["shield"]] = 50 

459 >>> df 

460 max_speed shield 

461 cobra 1 2 

462 viper 4 50 

463 sidewinder 7 50 

464 

465 Set value for an entire row 

466 

467 >>> df.loc["cobra"] = 10 

468 >>> df 

469 max_speed shield 

470 cobra 10 10 

471 viper 4 50 

472 sidewinder 7 50 

473 

474 Set value for an entire column 

475 

476 >>> df.loc[:, "max_speed"] = 30 

477 >>> df 

478 max_speed shield 

479 cobra 30 10 

480 viper 30 50 

481 sidewinder 30 50 

482 

483 Set value for rows matching callable condition 

484 

485 >>> df.loc[df["shield"] > 35] = 0 

486 >>> df 

487 max_speed shield 

488 cobra 30 10 

489 viper 0 0 

490 sidewinder 0 0 

491 

492 Add value matching location 

493 

494 >>> df.loc["viper", "shield"] += 5 

495 >>> df 

496 max_speed shield 

497 cobra 30 10 

498 viper 0 5 

499 sidewinder 0 0 

500 

501 Setting using a ``Series`` or a ``DataFrame`` sets the values matching the 

502 index labels, not the index positions. 

503 

504 >>> shuffled_df = df.loc[["viper", "cobra", "sidewinder"]] 

505 >>> df.loc[:] += shuffled_df 

506 >>> df 

507 max_speed shield 

508 cobra 60 20 

509 viper 0 10 

510 sidewinder 0 0 

511 

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

513 

514 Another example using integers for the index 

515 

516 >>> df = pd.DataFrame( 

517 ... [[1, 2], [4, 5], [7, 8]], 

518 ... index=[7, 8, 9], 

519 ... columns=["max_speed", "shield"], 

520 ... ) 

521 >>> df 

522 max_speed shield 

523 7 1 2 

524 8 4 5 

525 9 7 8 

526 

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

528 the start and stop of the slice are included. 

529 

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

531 max_speed shield 

532 7 1 2 

533 8 4 5 

534 9 7 8 

535 

536 **Getting values with a MultiIndex** 

537 

538 A number of examples using a DataFrame with a MultiIndex 

539 

540 >>> tuples = [ 

541 ... ("cobra", "mark i"), 

542 ... ("cobra", "mark ii"), 

543 ... ("sidewinder", "mark i"), 

544 ... ("sidewinder", "mark ii"), 

545 ... ("viper", "mark ii"), 

546 ... ("viper", "mark iii"), 

547 ... ] 

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

549 >>> values = [[12, 2], [0, 4], [10, 20], [1, 4], [7, 1], [16, 36]] 

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

551 >>> df 

552 max_speed shield 

553 cobra mark i 12 2 

554 mark ii 0 4 

555 sidewinder mark i 10 20 

556 mark ii 1 4 

557 viper mark ii 7 1 

558 mark iii 16 36 

559 

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

561 

562 >>> df.loc["cobra"] 

563 max_speed shield 

564 mark i 12 2 

565 mark ii 0 4 

566 

567 Single index tuple. Note this returns a Series. 

568 

569 >>> df.loc[("cobra", "mark ii")] 

570 max_speed 0 

571 shield 4 

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

573 

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

575 returns a Series. 

576 

577 >>> df.loc["cobra", "mark i"] 

578 max_speed 12 

579 shield 2 

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

581 

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

583 

584 >>> df.loc[[("cobra", "mark ii")]] 

585 max_speed shield 

586 cobra mark ii 0 4 

587 

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

589 

590 >>> df.loc[("cobra", "mark i"), "shield"] 

591 np.int64(2) 

592 

593 Slice from index tuple to single label 

594 

595 >>> df.loc[("cobra", "mark i") : "viper"] 

596 max_speed shield 

597 cobra mark i 12 2 

598 mark ii 0 4 

599 sidewinder mark i 10 20 

600 mark ii 1 4 

601 viper mark ii 7 1 

602 mark iii 16 36 

603 

604 Slice from index tuple to index tuple 

605 

606 >>> df.loc[("cobra", "mark i") : ("viper", "mark ii")] 

607 max_speed shield 

608 cobra mark i 12 2 

609 mark ii 0 4 

610 sidewinder mark i 10 20 

611 mark ii 1 4 

612 viper mark ii 7 1 

613 

614 Please see the :ref:`user guide<advanced.advanced_hierarchical>` 

615 for more details and explanations of advanced indexing. 

616 

617 **Assignment with Series** 

618 

619 When assigning a Series to .loc[row_indexer, col_indexer], pandas aligns 

620 the Series by index labels, not by order or position. 

621 

622 Series assignment with .loc and index alignment: 

623 

624 >>> df = pd.DataFrame({"A": [1, 2, 3]}, index=[0, 1, 2]) 

625 >>> s = pd.Series([10, 20], index=[1, 0]) # Note reversed order 

626 >>> df.loc[:, "B"] = s # Aligns by index, not order 

627 >>> df 

628 A B 

629 0 1 20.0 

630 1 2 10.0 

631 2 3 NaN 

632 """ 

633 return _LocIndexer("loc", self) 

634 

635 @property 

636 def at(self) -> _AtIndexer: 

637 """ 

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

639 

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

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

642 or Series. 

643 

644 Raises 

645 ------ 

646 KeyError 

647 If getting a value and 'label' does not exist in a DataFrame or Series. 

648 

649 ValueError 

650 If row/column label pair is not a tuple or if any label 

651 from the pair is not a scalar for DataFrame. 

652 If label is list-like (*excluding* NamedTuple) for Series. 

653 

654 See Also 

655 -------- 

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

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

658 position. 

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

660 DataFrame.iloc : Access a group of rows and columns by integer 

661 position(s). 

662 Series.at : Access a single value by label. 

663 Series.iat : Access a single value by integer position. 

664 Series.loc : Access a group of rows by label(s). 

665 Series.iloc : Access a group of rows by integer position(s). 

666 

667 Notes 

668 ----- 

669 See :ref:`Fast scalar value getting and setting <indexing.basics.get_value>` 

670 for more details. 

671 

672 Examples 

673 -------- 

674 >>> df = pd.DataFrame( 

675 ... [[0, 2, 3], [0, 4, 1], [10, 20, 30]], 

676 ... index=[4, 5, 6], 

677 ... columns=["A", "B", "C"], 

678 ... ) 

679 >>> df 

680 A B C 

681 4 0 2 3 

682 5 0 4 1 

683 6 10 20 30 

684 

685 Get value at specified row/column pair 

686 

687 >>> df.at[4, "B"] 

688 np.int64(2) 

689 

690 Set value at specified row/column pair 

691 

692 >>> df.at[4, "B"] = 10 

693 >>> df.at[4, "B"] 

694 np.int64(10) 

695 

696 Get value within a Series 

697 

698 >>> df.loc[5].at["B"] 

699 np.int64(4) 

700 """ 

701 return _AtIndexer("at", self) 

702 

703 @property 

704 def iat(self) -> _iAtIndexer: 

705 """ 

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

707 

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

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

710 or Series. 

711 

712 Raises 

713 ------ 

714 IndexError 

715 When integer position is out of bounds. 

716 

717 See Also 

718 -------- 

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

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

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

722 

723 Examples 

724 -------- 

725 >>> df = pd.DataFrame( 

726 ... [[0, 2, 3], [0, 4, 1], [10, 20, 30]], columns=["A", "B", "C"] 

727 ... ) 

728 >>> df 

729 A B C 

730 0 0 2 3 

731 1 0 4 1 

732 2 10 20 30 

733 

734 Get value at specified row/column pair 

735 

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

737 np.int64(1) 

738 

739 Set value at specified row/column pair 

740 

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

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

743 np.int64(10) 

744 

745 Get value within a series 

746 

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

748 np.int64(2) 

749 """ 

750 return _iAtIndexer("iat", self) 

751 

752 

753class _LocationIndexer(NDFrameIndexerBase): 

754 _valid_types: str 

755 axis: AxisInt | None = None 

756 

757 # sub-classes need to set _takeable 

758 _takeable: bool 

759 

760 @final 

761 def __call__(self, axis: Axis | None = None) -> Self: 

762 # we need to return a copy of ourselves 

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

764 

765 if axis is not None: 

766 axis_int_none = self.obj._get_axis_number(axis) 

767 else: 

768 axis_int_none = axis 

769 new_self.axis = axis_int_none 

770 return new_self 

771 

772 def _get_setitem_indexer(self, key): 

773 """ 

774 Convert a potentially-label-based key into a positional indexer. 

775 """ 

776 if self.name == "loc": 

777 # always holds here bc iloc overrides _get_setitem_indexer 

778 self._ensure_listlike_indexer(key, axis=self.axis) 

779 

780 if isinstance(key, tuple): 

781 for x in key: 

782 check_dict_or_set_indexers(x) 

783 

784 if self.axis is not None: 

785 key = _tupleize_axis_indexer(self.ndim, self.axis, key) 

786 

787 ax = self.obj._get_axis(0) 

788 

789 if ( 

790 isinstance(ax, MultiIndex) 

791 and self.name != "iloc" 

792 and is_hashable(key, allow_slice=False) 

793 ): 

794 with suppress(KeyError, InvalidIndexError): 

795 # TypeError e.g. passed a bool 

796 return ax.get_loc(key) 

797 

798 if isinstance(key, tuple): 

799 with suppress(IndexingError): 

800 # suppress "Too many indexers" 

801 return self._convert_tuple(key) 

802 

803 if isinstance(key, range): 

804 # GH#45479 test_loc_setitem_range_key 

805 key = list(key) 

806 

807 return self._convert_to_indexer(key, axis=0) 

808 

809 @final 

810 def _maybe_mask_setitem_value(self, indexer, value): 

811 """ 

812 If we have obj.iloc[mask] = series_or_frame and series_or_frame has the 

813 same length as obj, we treat this as obj.iloc[mask] = series_or_frame[mask], 

814 similar to Series.__setitem__. 

815 

816 Note this is only for loc, not iloc. 

817 """ 

818 

819 if ( 

820 isinstance(indexer, tuple) 

821 and len(indexer) == 2 

822 and isinstance(value, (ABCSeries, ABCDataFrame)) 

823 ): 

824 pi, icols = indexer 

825 ndim = value.ndim 

826 if com.is_bool_indexer(pi) and len(value) == len(pi): 

827 newkey = pi.nonzero()[0] 

828 

829 if is_scalar_indexer(icols, self.ndim - 1) and ndim == 1: 

830 # e.g. test_loc_setitem_boolean_mask_allfalse 

831 if len(newkey) == 0: 

832 value = value.iloc[:0] 

833 else: 

834 # test_loc_setitem_ndframe_values_alignment 

835 value = self.obj.iloc._align_series(indexer, value) 

836 indexer = (newkey, icols) 

837 

838 elif ( 

839 isinstance(icols, np.ndarray) 

840 and icols.dtype.kind == "i" 

841 and len(icols) == 1 

842 ): 

843 if ndim == 1: 

844 # We implicitly broadcast, though numpy does not, see 

845 # github.com/pandas-dev/pandas/pull/45501#discussion_r789071825 

846 # test_loc_setitem_ndframe_values_alignment 

847 value = self.obj.iloc._align_series(indexer, value) 

848 indexer = (newkey, icols) 

849 

850 elif ndim == 2 and value.shape[1] == 1: 

851 if len(newkey) == 0: 

852 value = value.iloc[:0] 

853 else: 

854 # test_loc_setitem_ndframe_values_alignment 

855 value = self.obj.iloc._align_frame(indexer, value) 

856 indexer = (newkey, icols) 

857 elif com.is_bool_indexer(indexer): 

858 indexer = indexer.nonzero()[0] 

859 

860 return indexer, value 

861 

862 @final 

863 def _ensure_listlike_indexer(self, key, axis=None, value=None) -> None: 

864 """ 

865 Ensure that a list-like of column labels are all present by adding them if 

866 they do not already exist. 

867 

868 Parameters 

869 ---------- 

870 key : list-like of column labels 

871 Target labels. 

872 axis : key axis if known 

873 """ 

874 column_axis = 1 

875 

876 # column only exists in 2-dimensional DataFrame 

877 if self.ndim != 2: 

878 return 

879 

880 if isinstance(key, tuple) and len(key) > 1: 

881 # key may be a tuple if we are .loc 

882 # if length of key is > 1 set key to column part 

883 # unless axis is already specified, then go with that 

884 if axis is None: 

885 axis = column_axis 

886 key = key[axis] 

887 

888 if ( 

889 axis == column_axis 

890 and not isinstance(self.obj.columns, MultiIndex) 

891 and is_list_like_indexer(key) 

892 and not com.is_bool_indexer(key) 

893 and all(is_hashable(k) for k in key) 

894 ): 

895 # GH#38148 

896 keys = self.obj.columns.union(key, sort=False) 

897 diff = Index(key, copy=False).difference(self.obj.columns, sort=False) 

898 

899 if len(diff): 

900 # e.g. if we are doing df.loc[:, ["A", "B"]] = 7 and "B" 

901 # is a new column, add the new columns with dtype=np.void 

902 # so that later when we go through setitem_single_column 

903 # we will use isetitem. Without this, the reindex_axis 

904 # below would create float64 columns in this example, which 

905 # would successfully hold 7, so we would end up with the wrong 

906 # dtype. 

907 indexer = np.arange(len(keys), dtype=np.intp) 

908 indexer[len(self.obj.columns) :] = -1 

909 new_mgr = self.obj._mgr.reindex_indexer( 

910 keys, indexer=indexer, axis=0, only_slice=True, use_na_proxy=True 

911 ) 

912 self.obj._mgr = new_mgr 

913 return 

914 

915 self.obj._mgr = self.obj._mgr.reindex_axis(keys, axis=0, only_slice=True) 

916 

917 @final 

918 def __setitem__(self, key, value) -> None: 

919 if not CHAINED_WARNING_DISABLED: 

920 if sys.getrefcount(self.obj) <= REF_COUNT_IDX: 

921 warnings.warn( 

922 _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 

923 ) 

924 

925 check_dict_or_set_indexers(key) 

926 if isinstance(key, tuple): 

927 key = (list(x) if is_iterator(x) else x for x in key) 

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

929 else: 

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

931 key = self._raise_callable_usage(key, maybe_callable) 

932 indexer = self._get_setitem_indexer(key) 

933 self._has_valid_setitem_indexer(key) 

934 

935 iloc: _iLocIndexer = ( 

936 cast("_iLocIndexer", self) if self.name == "iloc" else self.obj.iloc 

937 ) 

938 iloc._setitem_with_indexer(indexer, value, self.name) 

939 

940 def _validate_key(self, key, axis: AxisInt) -> None: 

941 """ 

942 Ensure that key is valid for current indexer. 

943 

944 Parameters 

945 ---------- 

946 key : scalar, slice or list-like 

947 Key requested. 

948 axis : int 

949 Dimension on which the indexing is being made. 

950 

951 Raises 

952 ------ 

953 TypeError 

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

955 IndexError 

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

957 KeyError 

958 If the key was not found. 

959 """ 

960 raise AbstractMethodError(self) 

961 

962 @final 

963 def _expand_ellipsis(self, tup: tuple) -> tuple: 

964 """ 

965 If a tuple key includes an Ellipsis, replace it with an appropriate 

966 number of null slices. 

967 """ 

968 if any(x is Ellipsis for x in tup): 

969 if tup.count(Ellipsis) > 1: 

970 raise IndexingError(_one_ellipsis_message) 

971 

972 if len(tup) == self.ndim: 

973 # It is unambiguous what axis this Ellipsis is indexing, 

974 # treat as a single null slice. 

975 i = tup.index(Ellipsis) 

976 # FIXME: this assumes only one Ellipsis 

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

978 return new_key 

979 

980 # TODO: other cases? only one test gets here, and that is covered 

981 # by _validate_key_length 

982 return tup 

983 

984 @final 

985 def _validate_tuple_indexer(self, key: tuple) -> tuple: 

986 """ 

987 Check the key for valid keys across my indexer. 

988 """ 

989 key = self._validate_key_length(key) 

990 key = self._expand_ellipsis(key) 

991 for i, k in enumerate(key): 

992 try: 

993 self._validate_key(k, i) 

994 except ValueError as err: 

995 raise ValueError( 

996 f"Location based indexing can only have [{self._valid_types}] types" 

997 ) from err 

998 return key 

999 

1000 @final 

1001 def _is_nested_tuple_indexer(self, tup: tuple) -> bool: 

1002 """ 

1003 Returns 

1004 ------- 

1005 bool 

1006 """ 

1007 if any(isinstance(ax, MultiIndex) for ax in self.obj.axes): 

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

1009 return False 

1010 

1011 @final 

1012 def _convert_tuple(self, key: tuple) -> tuple: 

1013 # Note: we assume _tupleize_axis_indexer has been called, if necessary. 

1014 self._validate_key_length(key) 

1015 keyidx = [self._convert_to_indexer(k, axis=i) for i, k in enumerate(key)] 

1016 return tuple(keyidx) 

1017 

1018 @final 

1019 def _validate_key_length(self, key: tuple) -> tuple: 

1020 if len(key) > self.ndim: 

1021 if key[0] is Ellipsis: 

1022 # e.g. Series.iloc[..., 3] reduces to just Series.iloc[3] 

1023 key = key[1:] 

1024 if Ellipsis in key: 

1025 raise IndexingError(_one_ellipsis_message) 

1026 return self._validate_key_length(key) 

1027 raise IndexingError("Too many indexers") 

1028 return key 

1029 

1030 @final 

1031 def _getitem_tuple_same_dim(self, tup: tuple): 

1032 """ 

1033 Index with indexers that should return an object of the same dimension 

1034 as self.obj. 

1035 

1036 This is only called after a failed call to _getitem_lowerdim. 

1037 """ 

1038 retval = self.obj 

1039 # Selecting columns before rows is significantly faster 

1040 start_val = (self.ndim - len(tup)) + 1 

1041 for i, key in enumerate(reversed(tup)): 

1042 i = self.ndim - i - start_val 

1043 if com.is_null_slice(key): 

1044 continue 

1045 

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

1047 # We should never have retval.ndim < self.ndim, as that should 

1048 # be handled by the _getitem_lowerdim call above. 

1049 assert retval.ndim == self.ndim 

1050 

1051 if retval is self.obj: 

1052 # if all axes were a null slice (`df.loc[:, :]`), ensure we still 

1053 # return a new object (https://github.com/pandas-dev/pandas/pull/49469) 

1054 retval = retval.copy(deep=False) 

1055 

1056 return retval 

1057 

1058 @final 

1059 def _getitem_lowerdim(self, tup: tuple): 

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

1061 if self.axis is not None: 

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

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

1064 

1065 # we may have a nested tuples indexer here 

1066 if self._is_nested_tuple_indexer(tup): 

1067 return self._getitem_nested_tuple(tup) 

1068 

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

1070 ax0 = self.obj._get_axis(0) 

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

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

1073 if ( 

1074 isinstance(ax0, MultiIndex) 

1075 and self.name != "iloc" 

1076 and not any(isinstance(x, slice) for x in tup) 

1077 ): 

1078 # Note: in all extant test cases, replacing the slice condition with 

1079 # `all(is_hashable(x) or com.is_null_slice(x) for x in tup)` 

1080 # is equivalent. 

1081 # (see the other place where we call _handle_lowerdim_multi_index_axis0) 

1082 with suppress(IndexingError): 

1083 return cast(_LocIndexer, self)._handle_lowerdim_multi_index_axis0(tup) 

1084 

1085 tup = self._validate_key_length(tup) 

1086 

1087 # Reverse tuple so that we are indexing along columns before rows 

1088 # and avoid unintended dtype inference. # GH60600 

1089 for i, key in zip(range(len(tup) - 1, -1, -1), reversed(tup), strict=True): 

1090 if is_label_like(key) or is_list_like(key): 

1091 # We don't need to check for tuples here because those are 

1092 # caught by the _is_nested_tuple_indexer check above. 

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

1094 

1095 # We should never have a scalar section here, because 

1096 # _getitem_lowerdim is only called after a check for 

1097 # is_scalar_access, which that would be. 

1098 if section.ndim == self.ndim: 

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

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

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

1102 

1103 else: 

1104 # Note: the section.ndim == self.ndim check above 

1105 # rules out having DataFrame here, so we dont need to worry 

1106 # about transposing. 

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

1108 

1109 if len(new_key) == 1: 

1110 new_key = new_key[0] 

1111 

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

1113 # slice returns a new object. 

1114 if com.is_null_slice(new_key): 

1115 return section 

1116 # This is an elided recursive call to iloc/loc 

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

1118 

1119 raise IndexingError("not applicable") 

1120 

1121 @final 

1122 def _getitem_nested_tuple(self, tup: tuple): 

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

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

1125 

1126 for key in tup: 

1127 check_dict_or_set_indexers(key) 

1128 

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

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

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

1132 if len(tup) > self.ndim: 

1133 if self.name != "loc": 

1134 # This should never be reached, but let's be explicit about it 

1135 raise ValueError("Too many indices") # pragma: no cover 

1136 if all( 

1137 is_hashable(x, allow_slice=False) or com.is_null_slice(x) for x in tup 

1138 ): 

1139 # GH#10521 Series should reduce MultiIndex dimensions instead of 

1140 # DataFrame, IndexingError is not raised when slice(None,None,None) 

1141 # with one row. 

1142 with suppress(IndexingError): 

1143 return cast(_LocIndexer, self)._handle_lowerdim_multi_index_axis0( 

1144 tup 

1145 ) 

1146 elif isinstance(self.obj, ABCSeries) and any( 

1147 isinstance(k, tuple) for k in tup 

1148 ): 

1149 # GH#35349 Raise if tuple in tuple for series 

1150 # Do this after the all-hashable-or-null-slice check so that 

1151 # we are only getting non-hashable tuples, in particular ones 

1152 # that themselves contain a slice entry 

1153 # See test_loc_series_getitem_too_many_dimensions 

1154 raise IndexingError("Too many indexers") 

1155 

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

1157 # selectors 

1158 axis = self.axis or 0 

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

1160 

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

1162 # this is iterative 

1163 obj = self.obj 

1164 # GH#41369 Loop in reverse order ensures indexing along columns before rows 

1165 # which selects only necessary blocks which avoids dtype conversion if possible 

1166 axis = len(tup) - 1 

1167 for key in reversed(tup): 

1168 if com.is_null_slice(key): 

1169 axis -= 1 

1170 continue 

1171 

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

1173 axis -= 1 

1174 

1175 # if we have a scalar, we are done 

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

1177 break 

1178 

1179 return obj 

1180 

1181 def _convert_to_indexer(self, key, axis: AxisInt): 

1182 raise AbstractMethodError(self) 

1183 

1184 def _raise_callable_usage(self, key: Any, maybe_callable: T) -> T: 

1185 # GH53533 

1186 if self.name == "iloc" and callable(key) and isinstance(maybe_callable, tuple): 

1187 raise ValueError( 

1188 "Returning a tuple from a callable with iloc is not allowed.", 

1189 ) 

1190 return maybe_callable 

1191 

1192 @final 

1193 def __getitem__(self, key): 

1194 check_dict_or_set_indexers(key) 

1195 if type(key) is tuple: 

1196 key = (list(x) if is_iterator(x) else x for x in key) 

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

1198 if self._is_scalar_access(key): 

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

1200 return self._getitem_tuple(key) 

1201 else: 

1202 # we by definition only have the 0th axis 

1203 axis = self.axis or 0 

1204 

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

1206 maybe_callable = self._raise_callable_usage(key, maybe_callable) 

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

1208 

1209 def _is_scalar_access(self, key: tuple): 

1210 raise NotImplementedError 

1211 

1212 def _getitem_tuple(self, tup: tuple): 

1213 raise AbstractMethodError(self) 

1214 

1215 def _getitem_axis(self, key, axis: AxisInt): 

1216 raise NotImplementedError 

1217 

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

1219 raise AbstractMethodError(self) 

1220 

1221 @final 

1222 def _getbool_axis(self, key, axis: AxisInt): 

1223 # caller is responsible for ensuring non-None axis 

1224 labels = self.obj._get_axis(axis) 

1225 key = check_bool_indexer(labels, key) 

1226 inds = key.nonzero()[0] 

1227 return self.obj.take(inds, axis=axis) 

1228 

1229 

1230@doc(IndexingMixin.loc) 

1231class _LocIndexer(_LocationIndexer): 

1232 _takeable: bool = False 

1233 _valid_types = ( 

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

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

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

1237 ) 

1238 

1239 # ------------------------------------------------------------------- 

1240 # Key Checks 

1241 

1242 @doc(_LocationIndexer._validate_key) 

1243 def _validate_key(self, key, axis: Axis) -> None: 

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

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

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

1247 # boolean not in slice and with boolean index 

1248 ax = self.obj._get_axis(axis) 

1249 if isinstance(key, bool) and not ( 

1250 is_bool_dtype(ax.dtype) 

1251 or ax.dtype.name == "boolean" 

1252 or ( 

1253 isinstance(ax, MultiIndex) 

1254 and is_bool_dtype(ax.get_level_values(0).dtype) 

1255 ) 

1256 ): 

1257 raise KeyError( 

1258 f"{key}: boolean label can not be used without a boolean index" 

1259 ) 

1260 

1261 if isinstance(key, slice) and ( 

1262 isinstance(key.start, bool) or isinstance(key.stop, bool) 

1263 ): 

1264 raise TypeError(f"{key}: boolean values can not be used in a slice") 

1265 

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

1267 return True 

1268 

1269 def _is_scalar_access(self, key: tuple) -> bool: 

1270 """ 

1271 Returns 

1272 ------- 

1273 bool 

1274 """ 

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

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

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

1278 # b) provide a performant path 

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

1280 return False 

1281 

1282 for i, k in enumerate(key): 

1283 if not is_scalar(k): 

1284 return False 

1285 

1286 ax = self.obj.axes[i] 

1287 if isinstance(ax, MultiIndex): 

1288 return False 

1289 

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

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

1292 # should not be considered scalar 

1293 return False 

1294 

1295 if not ax._index_as_unique: 

1296 return False 

1297 

1298 return True 

1299 

1300 # ------------------------------------------------------------------- 

1301 # MultiIndex Handling 

1302 

1303 def _multi_take_opportunity(self, tup: tuple) -> bool: 

1304 """ 

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

1306 

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

1308 list-likes. 

1309 

1310 Parameters 

1311 ---------- 

1312 tup : tuple 

1313 Tuple of indexers, one per axis. 

1314 

1315 Returns 

1316 ------- 

1317 bool 

1318 Whether the current indexing, 

1319 can be passed through `_multi_take`. 

1320 """ 

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

1322 return False 

1323 

1324 # just too complicated 

1325 return not any(com.is_bool_indexer(x) for x in tup) 

1326 

1327 def _multi_take(self, tup: tuple): 

1328 """ 

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

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

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

1332 Improving efficiency. 

1333 

1334 Parameters 

1335 ---------- 

1336 tup : tuple 

1337 Tuple of indexers, one per axis. 

1338 

1339 Returns 

1340 ------- 

1341 values: same type as the object being indexed 

1342 """ 

1343 # GH 836 

1344 d = { 

1345 axis: self._get_listlike_indexer(key, axis) 

1346 for (key, axis) in zip(tup, self.obj._AXIS_ORDERS, strict=True) 

1347 } 

1348 return self.obj._reindex_with_indexers(d, allow_dups=True) 

1349 

1350 # ------------------------------------------------------------------- 

1351 

1352 def _getitem_iterable(self, key, axis: AxisInt): 

1353 """ 

1354 Index current object with an iterable collection of keys. 

1355 

1356 Parameters 

1357 ---------- 

1358 key : iterable 

1359 Targeted labels. 

1360 axis : int 

1361 Dimension on which the indexing is being made. 

1362 

1363 Raises 

1364 ------ 

1365 KeyError 

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

1367 keys were found. 

1368 

1369 Returns 

1370 ------- 

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

1372 """ 

1373 # we assume that not com.is_bool_indexer(key), as that is 

1374 # handled before we get here. 

1375 self._validate_key(key, axis) 

1376 

1377 # A collection of keys 

1378 keyarr, indexer = self._get_listlike_indexer(key, axis) 

1379 return self.obj._reindex_with_indexers( 

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

1381 ) 

1382 

1383 def _getitem_tuple(self, tup: tuple): 

1384 with suppress(IndexingError): 

1385 tup = self._expand_ellipsis(tup) 

1386 return self._getitem_lowerdim(tup) 

1387 

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

1389 tup = self._validate_tuple_indexer(tup) 

1390 

1391 # ugly hack for GH #836 

1392 if self._multi_take_opportunity(tup): 

1393 return self._multi_take(tup) 

1394 

1395 return self._getitem_tuple_same_dim(tup) 

1396 

1397 def _get_label(self, label, axis: AxisInt): 

1398 # GH#5567 this will fail if the label is not present in the axis. 

1399 return self.obj.xs(label, axis=axis) 

1400 

1401 def _handle_lowerdim_multi_index_axis0(self, tup: tuple): 

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

1403 axis = self.axis or 0 

1404 try: 

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

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

1407 

1408 except KeyError as ek: 

1409 # raise KeyError if number of indexers match 

1410 # else IndexingError will be raised 

1411 if self.ndim < len(tup) <= self.obj.index.nlevels: 

1412 raise ek 

1413 raise IndexingError("No label returned") from ek 

1414 

1415 def _getitem_axis(self, key, axis: AxisInt): 

1416 key = item_from_zerodim(key) 

1417 if is_iterator(key): 

1418 key = list(key) 

1419 if key is Ellipsis: 

1420 key = slice(None) 

1421 

1422 labels = self.obj._get_axis(axis) 

1423 

1424 if isinstance(key, tuple) and isinstance(labels, MultiIndex): 

1425 key = tuple(key) 

1426 

1427 if isinstance(key, slice): 

1428 self._validate_key(key, axis) 

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

1430 elif com.is_bool_indexer(key): 

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

1432 elif is_list_like_indexer(key): 

1433 # an iterable multi-selection 

1434 if not (isinstance(key, tuple) and isinstance(labels, MultiIndex)): 

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

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

1437 

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

1439 

1440 # nested tuple slicing 

1441 if is_nested_tuple(key, labels): 

1442 locs = labels.get_locs(key) 

1443 indexer: list[slice | npt.NDArray[np.intp]] = [slice(None)] * self.ndim 

1444 indexer[axis] = locs 

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

1446 

1447 # fall thru to straight lookup 

1448 self._validate_key(key, axis) 

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

1450 

1451 def _get_slice_axis(self, slice_obj: slice, axis: AxisInt): 

1452 """ 

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

1454 """ 

1455 # caller is responsible for ensuring non-None axis 

1456 obj = self.obj 

1457 if not need_slice(slice_obj): 

1458 return obj.copy(deep=False) 

1459 

1460 labels = obj._get_axis(axis) 

1461 indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step) 

1462 

1463 if isinstance(indexer, slice): 

1464 return self.obj._slice(indexer, axis=axis) 

1465 else: 

1466 # DatetimeIndex overrides Index.slice_indexer and may 

1467 # return a DatetimeIndex instead of a slice object. 

1468 return self.obj.take(indexer, axis=axis) 

1469 

1470 def _convert_to_indexer(self, key, axis: AxisInt): 

1471 """ 

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

1473 indexing on an ndarray. 

1474 

1475 Examples 

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

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

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

1479 

1480 Going by Zen of Python? 

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

1482 raise AmbiguousIndexError with integer labels? 

1483 - No, prefer label-based indexing 

1484 """ 

1485 labels = self.obj._get_axis(axis) 

1486 

1487 if isinstance(key, slice): 

1488 return labels._convert_slice_indexer(key, kind="loc") 

1489 

1490 if ( 

1491 isinstance(key, tuple) 

1492 and not isinstance(labels, MultiIndex) 

1493 and self.ndim < 2 

1494 and len(key) > 1 

1495 ): 

1496 raise IndexingError("Too many indexers") 

1497 

1498 # Slices are not valid keys passed in by the user, 

1499 # even though they are hashable in Python 3.12 

1500 contains_slice = False 

1501 if isinstance(key, tuple): 

1502 contains_slice = any(isinstance(v, slice) for v in key) 

1503 

1504 if is_scalar(key) or ( 

1505 isinstance(labels, MultiIndex) and is_hashable(key) and not contains_slice 

1506 ): 

1507 # Otherwise get_loc will raise InvalidIndexError 

1508 

1509 # if we are a label return me 

1510 try: 

1511 return labels.get_loc(key) 

1512 except LookupError: 

1513 if isinstance(key, tuple) and isinstance(labels, MultiIndex): 

1514 if len(key) == labels.nlevels: 

1515 return {"key": key} 

1516 raise 

1517 except InvalidIndexError: 

1518 # GH35015, using datetime as column indices raises exception 

1519 if not isinstance(labels, MultiIndex): 

1520 raise 

1521 except ValueError: 

1522 if not is_integer(key): 

1523 raise 

1524 return {"key": key} 

1525 

1526 if is_nested_tuple(key, labels): 

1527 if self.ndim == 1 and any(isinstance(k, tuple) for k in key): 

1528 # GH#35349 Raise if tuple in tuple for series 

1529 raise IndexingError("Too many indexers") 

1530 return labels.get_locs(key) 

1531 

1532 elif is_list_like_indexer(key): 

1533 if is_iterator(key): 

1534 key = list(key) 

1535 

1536 if com.is_bool_indexer(key): 

1537 key = check_bool_indexer(labels, key) 

1538 return key 

1539 else: 

1540 return self._get_listlike_indexer(key, axis)[1] 

1541 else: 

1542 try: 

1543 return labels.get_loc(key) 

1544 except LookupError: 

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

1546 if not is_list_like_indexer(key): 

1547 return {"key": key} 

1548 raise 

1549 

1550 def _get_listlike_indexer(self, key, axis: AxisInt): 

1551 """ 

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

1553 

1554 Parameters 

1555 ---------- 

1556 key : list-like 

1557 Targeted labels. 

1558 axis: int 

1559 Dimension on which the indexing is being made. 

1560 

1561 Raises 

1562 ------ 

1563 KeyError 

1564 If at least one key was requested but none was found. 

1565 

1566 Returns 

1567 ------- 

1568 keyarr: Index 

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

1570 values : array-like 

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

1572 """ 

1573 ax = self.obj._get_axis(axis) 

1574 axis_name = self.obj._get_axis_name(axis) 

1575 

1576 keyarr, indexer = ax._get_indexer_strict(key, axis_name) 

1577 

1578 return keyarr, indexer 

1579 

1580 

1581@doc(IndexingMixin.iloc) 

1582class _iLocIndexer(_LocationIndexer): 

1583 _valid_types = ( 

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

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

1586 ) 

1587 _takeable = True 

1588 

1589 # ------------------------------------------------------------------- 

1590 # Key Checks 

1591 

1592 def _validate_key(self, key, axis: AxisInt) -> None: 

1593 if com.is_bool_indexer(key): 

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

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

1596 return 

1597 raise ValueError( 

1598 "iLocation based boolean indexing cannot use an indexable as a mask" 

1599 ) 

1600 return 

1601 

1602 if isinstance(key, slice): 

1603 return 

1604 elif is_integer(key): 

1605 self._validate_integer(key, axis) 

1606 elif isinstance(key, tuple): 

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

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

1609 raise IndexingError("Too many indexers") 

1610 elif is_list_like_indexer(key): 

1611 if isinstance(key, ABCSeries): 

1612 arr = key._values 

1613 elif is_array_like(key): 

1614 arr = key 

1615 else: 

1616 arr = np.array(key) 

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

1618 

1619 # check that the key has a numeric dtype 

1620 if not is_numeric_dtype(arr.dtype): 

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

1622 

1623 if len(arr): 

1624 if isinstance(arr.dtype, ExtensionDtype): 

1625 arr_max = arr._reduce("max") 

1626 arr_min = arr._reduce("min") 

1627 else: 

1628 arr_max = np.max(arr) 

1629 arr_min = np.min(arr) 

1630 

1631 # check that the key does not exceed the maximum size 

1632 if arr_max >= len_axis or arr_min < -len_axis: 

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

1634 else: 

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

1636 

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

1638 """ 

1639 Validate that a positional indexer cannot enlarge its target 

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

1641 

1642 Returns 

1643 ------- 

1644 bool 

1645 """ 

1646 if isinstance(indexer, dict): 

1647 raise IndexError("iloc cannot enlarge its target object") 

1648 

1649 if isinstance(indexer, ABCDataFrame): 

1650 raise TypeError( 

1651 "DataFrame indexer for .iloc is not supported. " 

1652 "Consider using .loc with a DataFrame indexer for automatic alignment.", 

1653 ) 

1654 

1655 if not isinstance(indexer, tuple): 

1656 indexer = _tuplify(self.ndim, indexer) 

1657 

1658 for ax, i in zip(self.obj.axes, indexer, strict=False): 

1659 if isinstance(i, slice): 

1660 # should check the stop slice? 

1661 pass 

1662 elif is_list_like_indexer(i): 

1663 # should check the elements? 

1664 pass 

1665 elif is_integer(i): 

1666 if i >= len(ax): 

1667 raise IndexError("iloc cannot enlarge its target object") 

1668 elif isinstance(i, dict): 

1669 raise IndexError("iloc cannot enlarge its target object") 

1670 

1671 return True 

1672 

1673 def _is_scalar_access(self, key: tuple) -> bool: 

1674 """ 

1675 Returns 

1676 ------- 

1677 bool 

1678 """ 

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

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

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

1682 # b) provide a performant path 

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

1684 return False 

1685 

1686 return all(is_integer(k) for k in key) 

1687 

1688 def _validate_integer(self, key: int | np.integer, axis: AxisInt) -> None: 

1689 """ 

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

1691 

1692 Parameters 

1693 ---------- 

1694 key : int 

1695 Requested position. 

1696 axis : int 

1697 Desired axis. 

1698 

1699 Raises 

1700 ------ 

1701 IndexError 

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

1703 """ 

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

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

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

1707 

1708 # ------------------------------------------------------------------- 

1709 

1710 def _getitem_tuple(self, tup: tuple): 

1711 tup = self._validate_tuple_indexer(tup) 

1712 with suppress(IndexingError): 

1713 return self._getitem_lowerdim(tup) 

1714 

1715 return self._getitem_tuple_same_dim(tup) 

1716 

1717 def _get_list_axis(self, key, axis: AxisInt): 

1718 """ 

1719 Return Series values by list or array of integers. 

1720 

1721 Parameters 

1722 ---------- 

1723 key : list-like positional indexer 

1724 axis : int 

1725 

1726 Returns 

1727 ------- 

1728 Series object 

1729 

1730 Notes 

1731 ----- 

1732 `axis` can only be zero. 

1733 """ 

1734 try: 

1735 return self.obj.take(key, axis=axis) 

1736 except IndexError as err: 

1737 # re-raise with different error message, e.g. test_getitem_ndarray_3d 

1738 raise IndexError("positional indexers are out-of-bounds") from err 

1739 

1740 def _getitem_axis(self, key, axis: AxisInt): 

1741 if key is Ellipsis: 

1742 key = slice(None) 

1743 elif isinstance(key, ABCDataFrame): 

1744 raise IndexError( 

1745 "DataFrame indexer is not allowed for .iloc\n" 

1746 "Consider using .loc for automatic alignment." 

1747 ) 

1748 

1749 if isinstance(key, slice): 

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

1751 

1752 if is_iterator(key): 

1753 key = list(key) 

1754 

1755 if isinstance(key, list): 

1756 key = np.asarray(key) 

1757 

1758 if com.is_bool_indexer(key): 

1759 self._validate_key(key, axis) 

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

1761 

1762 # a list of integers 

1763 elif is_list_like_indexer(key): 

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

1765 

1766 # a single integer 

1767 else: 

1768 key = item_from_zerodim(key) 

1769 if not is_integer(key): 

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

1771 

1772 # validate the location 

1773 self._validate_integer(key, axis) 

1774 

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

1776 

1777 def _get_slice_axis(self, slice_obj: slice, axis: AxisInt): 

1778 # caller is responsible for ensuring non-None axis 

1779 obj = self.obj 

1780 

1781 if not need_slice(slice_obj): 

1782 return obj.copy(deep=False) 

1783 

1784 labels = obj._get_axis(axis) 

1785 labels._validate_positional_slice(slice_obj) 

1786 return self.obj._slice(slice_obj, axis=axis) 

1787 

1788 def _convert_to_indexer(self, key: T, axis: AxisInt) -> T: 

1789 """ 

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

1791 """ 

1792 return key 

1793 

1794 def _get_setitem_indexer(self, key): 

1795 # GH#32257 Fall through to let numpy do validation 

1796 if is_iterator(key): 

1797 key = list(key) 

1798 

1799 if self.axis is not None: 

1800 key = _tupleize_axis_indexer(self.ndim, self.axis, key) 

1801 

1802 return key 

1803 

1804 # ------------------------------------------------------------------- 

1805 

1806 def _decide_split_path(self, indexer, value) -> bool: 

1807 """ 

1808 Decide whether we will take a block-by-block path. 

1809 """ 

1810 take_split_path = not self.obj._mgr.is_single_block 

1811 

1812 if not take_split_path and isinstance(value, ABCDataFrame): 

1813 # Avoid cast of values 

1814 take_split_path = not value._mgr.is_single_block 

1815 

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

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

1818 if not take_split_path and len(self.obj._mgr.blocks) and self.ndim > 1: 

1819 # in case of dict, keys are indices 

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

1821 arr = self.obj._mgr.blocks[0].values 

1822 take_split_path = not can_hold_element( 

1823 arr, extract_array(val, extract_numpy=True) 

1824 ) 

1825 

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

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

1828 # GH 10360, GH 27841 

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

1830 for i, ax in zip(indexer, self.obj.axes, strict=True): 

1831 if isinstance(ax, MultiIndex) and not ( 

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

1833 ): 

1834 take_split_path = True 

1835 break 

1836 

1837 return take_split_path 

1838 

1839 def _setitem_new_column(self, indexer, key, value, name: str) -> None: 

1840 """ 

1841 _setitem_with_indexer cases that can go through DataFrame.__setitem__. 

1842 """ 

1843 # add the new item, and set the value 

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

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

1846 # list-like 

1847 if not len(self.obj): 

1848 if not is_list_like_indexer(value): 

1849 raise ValueError( 

1850 "cannot set a frame with no defined index and a scalar" 

1851 ) 

1852 self.obj[key] = value 

1853 return 

1854 

1855 # add a new item with the dtype setup 

1856 if com.is_null_slice(indexer[0]): 

1857 # We are setting an entire column 

1858 self.obj[key] = value 

1859 return 

1860 elif is_array_like(value): 

1861 # GH#42099 

1862 arr = extract_array(value, extract_numpy=True) 

1863 taker = -1 * np.ones(len(self.obj), dtype=np.intp) 

1864 empty_value = algos.take_nd(arr, taker) 

1865 if not isinstance(value, ABCSeries): 

1866 # if not Series (in which case we need to align), 

1867 # we can short-circuit 

1868 if isinstance(arr, np.ndarray) and arr.ndim == 1 and len(arr) == 1: 

1869 # NumPy 1.25 deprecation: https://github.com/numpy/numpy/pull/10615 

1870 arr = arr[0, ...] 

1871 empty_value[indexer[0]] = arr 

1872 self.obj[key] = empty_value 

1873 return 

1874 

1875 self.obj[key] = empty_value 

1876 elif not is_list_like(value): 

1877 self.obj[key] = construct_1d_array_from_inferred_fill_value( 

1878 value, len(self.obj) 

1879 ) 

1880 else: 

1881 # FIXME: GH#42099#issuecomment-864326014 

1882 self.obj[key] = infer_fill_value(value) 

1883 

1884 new_indexer = convert_from_missing_indexer_tuple(indexer, self.obj.axes) 

1885 self._setitem_with_indexer(new_indexer, value, name) 

1886 

1887 return 

1888 

1889 def _setitem_with_indexer(self, indexer, value, name: str = "iloc") -> None: 

1890 """ 

1891 _setitem_with_indexer is for setting values on a Series/DataFrame 

1892 using positional indexers. 

1893 

1894 If the relevant keys are not present, the Series/DataFrame may be 

1895 expanded. 

1896 """ 

1897 info_axis = self.obj._info_axis_number 

1898 take_split_path = self._decide_split_path(indexer, value) 

1899 

1900 if isinstance(indexer, tuple): 

1901 nindexer = [] 

1902 for i, idx in enumerate(indexer): 

1903 idx, missing = convert_missing_indexer(idx) 

1904 if missing: 

1905 # reindex the axis to the new value 

1906 # and set inplace 

1907 key = idx 

1908 

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

1910 # path first 

1911 # this correctly sets the dtype 

1912 # essentially this separates out the block that is needed 

1913 # to possibly be modified 

1914 if self.ndim > 1 and i == info_axis: 

1915 self._setitem_new_column(indexer, key, value, name=name) 

1916 return 

1917 

1918 # reindex the axis 

1919 index = self.obj._get_axis(i) 

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

1921 

1922 # We are expanding the Series/DataFrame values to match 

1923 # the length of the new index `labels`. GH#40096 ensure 

1924 # this is valid even if the index has duplicates. 

1925 taker = np.arange(len(index) + 1, dtype=np.intp) 

1926 taker[-1] = -1 

1927 reindexers = {i: (labels, taker)} 

1928 new_obj = self.obj._reindex_with_indexers( 

1929 reindexers, allow_dups=True 

1930 ) 

1931 self.obj._mgr = new_obj._mgr 

1932 

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

1934 

1935 else: 

1936 nindexer.append(idx) 

1937 

1938 indexer = tuple(nindexer) 

1939 else: 

1940 indexer, missing = convert_missing_indexer(indexer) 

1941 

1942 if missing: 

1943 self._setitem_with_indexer_missing(indexer, value) 

1944 return 

1945 

1946 if name == "loc": 

1947 # must come after setting of missing 

1948 indexer, value = self._maybe_mask_setitem_value(indexer, value) 

1949 

1950 # align and set the values 

1951 if take_split_path: 

1952 # We have to operate column-wise 

1953 self._setitem_with_indexer_split_path(indexer, value, name) 

1954 else: 

1955 self._setitem_single_block(indexer, value, name) 

1956 

1957 def _setitem_with_indexer_split_path(self, indexer, value, name: str): 

1958 """ 

1959 Setitem column-wise. 

1960 """ 

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

1962 assert self.ndim == 2 

1963 

1964 if not isinstance(indexer, tuple): 

1965 indexer = _tuplify(self.ndim, indexer) 

1966 if len(indexer) > self.ndim: 

1967 raise IndexError("too many indices for array") 

1968 if isinstance(indexer[0], np.ndarray) and indexer[0].ndim > 2: 

1969 raise ValueError(r"Cannot set values with ndim > 2") 

1970 

1971 if (isinstance(value, ABCSeries) and name != "iloc") or isinstance(value, dict): 

1972 from pandas import Series 

1973 

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

1975 

1976 # Ensure we have something we can iterate over 

1977 info_axis = indexer[1] 

1978 ilocs = self._ensure_iterable_column_indexer(info_axis) 

1979 

1980 pi = indexer[0] 

1981 lplane_indexer = length_of_indexer(pi, self.obj.index) 

1982 # lplane_indexer gives the expected length of obj[indexer[0]] 

1983 

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

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

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

1987 if isinstance(value, ABCDataFrame): 

1988 self._setitem_with_indexer_frame_value(indexer, value, name) 

1989 

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

1991 # TODO: avoid np.ndim call in case it isn't an ndarray, since 

1992 # that will construct an ndarray, which will be wasteful 

1993 self._setitem_with_indexer_2d_value(indexer, value) 

1994 

1995 elif len(ilocs) == 1 and lplane_indexer == len(value) and not is_scalar(pi): 

1996 # We are setting multiple rows in a single column. 

1997 self._setitem_single_column(ilocs[0], value, pi) 

1998 

1999 elif len(ilocs) == 1 and 0 != lplane_indexer != len(value): 

2000 # We are trying to set N values into M entries of a single 

2001 # column, which is invalid for N != M 

2002 # Exclude zero-len for e.g. boolean masking that is all-false 

2003 

2004 if len(value) == 1 and not is_integer(info_axis): 

2005 # This is a case like df.iloc[:3, [1]] = [0] 

2006 # where we treat as df.iloc[:3, 1] = 0 

2007 return self._setitem_with_indexer((pi, info_axis[0]), value[0]) 

2008 

2009 raise ValueError( 

2010 "Must have equal len keys and value when setting with an iterable" 

2011 ) 

2012 

2013 elif lplane_indexer == 0 and len(value) == len(self.obj.index): 

2014 # We get here in one case via .loc with an all-False mask 

2015 pass 

2016 

2017 elif self._is_scalar_access(indexer) and is_object_dtype( 

2018 self.obj.dtypes._values[ilocs[0]] 

2019 ): 

2020 # We are setting nested data, only possible for object dtype data 

2021 self._setitem_single_column(indexer[1], value, pi) 

2022 

2023 elif len(ilocs) == len(value): 

2024 # We are setting multiple columns in a single row. 

2025 for loc, v in zip(ilocs, value, strict=True): 

2026 self._setitem_single_column(loc, v, pi) 

2027 

2028 elif len(ilocs) == 1 and com.is_null_slice(pi) and len(self.obj) == 0: 

2029 # This is a setitem-with-expansion, see 

2030 # test_loc_setitem_empty_append_expands_rows_mixed_dtype 

2031 # e.g. df = DataFrame(columns=["x", "y"]) 

2032 # df["x"] = df["x"].astype(np.int64) 

2033 # df.loc[:, "x"] = [1, 2, 3] 

2034 self._setitem_single_column(ilocs[0], value, pi) 

2035 

2036 else: 

2037 raise ValueError( 

2038 "Must have equal len keys and value when setting with an iterable" 

2039 ) 

2040 

2041 else: 

2042 # scalar value 

2043 for loc in ilocs: 

2044 self._setitem_single_column(loc, value, pi) 

2045 

2046 def _setitem_with_indexer_2d_value(self, indexer, value) -> None: 

2047 # We get here with np.ndim(value) == 2, excluding DataFrame, 

2048 # which goes through _setitem_with_indexer_frame_value 

2049 pi = indexer[0] 

2050 

2051 ilocs = self._ensure_iterable_column_indexer(indexer[1]) 

2052 

2053 if not is_array_like(value): 

2054 # cast lists to array 

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

2056 if len(ilocs) != value.shape[1]: 

2057 raise ValueError( 

2058 "Must have equal len keys and value when setting with an ndarray" 

2059 ) 

2060 

2061 for i, loc in enumerate(ilocs): 

2062 value_col = value[:, i] 

2063 if is_object_dtype(value_col.dtype): 

2064 # casting to list so that we do type inference in setitem_single_column 

2065 value_col = value_col.tolist() 

2066 self._setitem_single_column(loc, value_col, pi) 

2067 

2068 def _setitem_with_indexer_frame_value( 

2069 self, indexer, value: DataFrame, name: str 

2070 ) -> None: 

2071 ilocs = self._ensure_iterable_column_indexer(indexer[1]) 

2072 

2073 sub_indexer = list(indexer) 

2074 pi = indexer[0] 

2075 

2076 multiindex_indexer = isinstance(self.obj.columns, MultiIndex) 

2077 

2078 unique_cols = value.columns.is_unique 

2079 

2080 # We do not want to align the value in case of iloc GH#37728 

2081 if name == "iloc": 

2082 for i, loc in enumerate(ilocs): 

2083 val = value.iloc[:, i] 

2084 self._setitem_single_column(loc, val, pi) 

2085 

2086 elif not unique_cols and value.columns.equals(self.obj.columns): 

2087 # We assume we are already aligned, see 

2088 # test_iloc_setitem_frame_duplicate_columns_multiple_blocks 

2089 for loc in ilocs: 

2090 item = self.obj.columns[loc] 

2091 if item in value: 

2092 sub_indexer[1] = item 

2093 val = self._align_series( 

2094 tuple(sub_indexer), 

2095 value.iloc[:, loc], 

2096 multiindex_indexer, 

2097 ) 

2098 else: 

2099 val = np.nan 

2100 

2101 self._setitem_single_column(loc, val, pi) 

2102 

2103 elif not unique_cols: 

2104 raise ValueError("Setting with non-unique columns is not allowed.") 

2105 

2106 else: 

2107 for loc in ilocs: 

2108 item = self.obj.columns[loc] 

2109 if item in value: 

2110 sub_indexer[1] = item 

2111 val = self._align_series( 

2112 tuple(sub_indexer), 

2113 value[item], 

2114 multiindex_indexer, 

2115 using_cow=True, 

2116 ) 

2117 else: 

2118 val = np.nan 

2119 

2120 self._setitem_single_column(loc, val, pi) 

2121 

2122 def _setitem_single_column(self, loc: int, value, plane_indexer) -> None: 

2123 """ 

2124 

2125 Parameters 

2126 ---------- 

2127 loc : int 

2128 Indexer for column position 

2129 plane_indexer : int, slice, listlike[int] 

2130 The indexer we use for setitem along axis=0. 

2131 """ 

2132 pi = plane_indexer 

2133 

2134 is_full_setter = com.is_null_slice(pi) or com.is_full_slice(pi, len(self.obj)) 

2135 

2136 is_null_setter = com.is_empty_slice(pi) or (is_array_like(pi) and len(pi) == 0) 

2137 

2138 if is_null_setter: 

2139 # no-op, don't cast dtype later 

2140 return 

2141 

2142 elif is_full_setter: 

2143 try: 

2144 self.obj._mgr.column_setitem( 

2145 loc, plane_indexer, value, inplace_only=True 

2146 ) 

2147 except (ValueError, TypeError, LossySetitemError) as exc: 

2148 # If we're setting an entire column and we can't do it inplace, 

2149 # then we can use value's dtype (or inferred dtype) 

2150 # instead of object 

2151 dtype = self.obj.dtypes.iloc[loc] 

2152 if dtype not in (np.void, object) and not self.obj.empty: 

2153 # - Exclude np.void, as that is a special case for expansion. 

2154 # We want to raise for 

2155 # df = pd.DataFrame({'a': [1, 2]}) 

2156 # df.loc[:, 'a'] = .3 

2157 # but not for 

2158 # df = pd.DataFrame({'a': [1, 2]}) 

2159 # df.loc[:, 'b'] = .3 

2160 # - Exclude `object`, as then no upcasting happens. 

2161 # - Exclude empty initial object with enlargement, 

2162 # as then there's nothing to be inconsistent with. 

2163 raise TypeError( 

2164 f"Invalid value '{value}' for dtype '{dtype}'" 

2165 ) from exc 

2166 self.obj.isetitem(loc, value) 

2167 else: 

2168 # set value into the column (first attempting to operate inplace, then 

2169 # falling back to casting if necessary) 

2170 dtype = self.obj.dtypes.iloc[loc] 

2171 if dtype == np.void: 

2172 # This means we're expanding, with multiple columns, e.g. 

2173 # df = pd.DataFrame({'A': [1,2,3], 'B': [4,5,6]}) 

2174 # df.loc[df.index <= 2, ['F', 'G']] = (1, 'abc') 

2175 # Columns F and G will initially be set to np.void. 

2176 # Here, we replace those temporary `np.void` columns with 

2177 # columns of the appropriate dtype, based on `value`. 

2178 self.obj.iloc[:, loc] = construct_1d_array_from_inferred_fill_value( 

2179 value, len(self.obj) 

2180 ) 

2181 self.obj._mgr.column_setitem(loc, plane_indexer, value) 

2182 

2183 def _setitem_single_block(self, indexer, value, name: str) -> None: 

2184 """ 

2185 _setitem_with_indexer for the case when we have a single Block. 

2186 """ 

2187 from pandas import Series 

2188 

2189 if (isinstance(value, ABCSeries) and name != "iloc") or isinstance(value, dict): 

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

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

2192 # if it's worth supporting that. 

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

2194 

2195 info_axis = self.obj._info_axis_number 

2196 item_labels = self.obj._get_axis(info_axis) 

2197 if isinstance(indexer, tuple): 

2198 # if we are setting on the info axis ONLY 

2199 # set using those methods to avoid block-splitting 

2200 # logic here 

2201 if ( 

2202 self.ndim == len(indexer) == 2 

2203 and is_integer(indexer[1]) 

2204 and com.is_null_slice(indexer[0]) 

2205 ): 

2206 col = item_labels[indexer[info_axis]] 

2207 if len(item_labels.get_indexer_for([col])) == 1: 

2208 # e.g. test_loc_setitem_empty_append_expands_rows 

2209 loc = item_labels.get_loc(col) 

2210 self._setitem_single_column(loc, value, indexer[0]) 

2211 return 

2212 

2213 indexer = maybe_convert_ix(*indexer) # e.g. test_setitem_frame_align 

2214 

2215 if isinstance(value, ABCDataFrame) and name != "iloc": 

2216 value = self._align_frame(indexer, value)._values 

2217 

2218 # actually do the set 

2219 self.obj._mgr = self.obj._mgr.setitem(indexer=indexer, value=value) 

2220 

2221 def _setitem_with_indexer_missing(self, indexer, value): 

2222 """ 

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

2224 """ 

2225 from pandas import Series 

2226 

2227 # reindex the axis to the new value 

2228 # and set inplace 

2229 if self.ndim == 1: 

2230 index = self.obj.index 

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

2232 

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

2234 # that matches in an int64 Index, so 

2235 # we will not create a duplicate index, rather 

2236 # index to that element 

2237 # e.g. 0.0 -> 0 

2238 # GH#12246 

2239 if index.is_unique: 

2240 # pass new_index[-1:] instead if [new_index[-1]] 

2241 # so that we retain dtype 

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

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

2244 # We get only here with loc, so can hard code 

2245 return self._setitem_with_indexer(new_indexer, value, "loc") 

2246 

2247 # this preserves dtype of the value and of the object 

2248 if not is_scalar(value): 

2249 new_dtype = None 

2250 

2251 elif is_valid_na_for_dtype(value, self.obj.dtype): 

2252 if not is_object_dtype(self.obj.dtype): 

2253 # Every NA value is suitable for object, no conversion needed 

2254 value = na_value_for_dtype(self.obj.dtype, compat=False) 

2255 

2256 new_dtype = maybe_promote(self.obj.dtype, value)[0] 

2257 

2258 elif isna(value): 

2259 new_dtype = None 

2260 elif not self.obj.empty and not is_object_dtype(self.obj.dtype): 

2261 # We should not cast, if we have object dtype because we can 

2262 # set timedeltas into object series 

2263 curr_dtype = self.obj.dtype 

2264 curr_dtype = getattr(curr_dtype, "numpy_dtype", curr_dtype) 

2265 new_dtype = maybe_promote(curr_dtype, value)[0] 

2266 else: 

2267 new_dtype = None 

2268 

2269 new_values = Series([value], dtype=new_dtype)._values 

2270 

2271 if len(self.obj._values): 

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

2273 # does incorrectly 

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

2275 self.obj._mgr = self.obj._constructor( 

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

2277 )._mgr 

2278 

2279 elif self.ndim == 2: 

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

2281 # no columns and scalar 

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

2283 

2284 has_dtype = hasattr(value, "dtype") 

2285 if isinstance(value, ABCSeries): 

2286 # append a Series 

2287 value = value.reindex(index=self.obj.columns) 

2288 value.name = indexer 

2289 elif isinstance(value, dict): 

2290 value = Series( 

2291 value, index=self.obj.columns, name=indexer, dtype=object 

2292 ) 

2293 else: 

2294 # a list-list 

2295 if is_list_like_indexer(value): 

2296 # must have conforming columns 

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

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

2299 

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

2301 

2302 if not len(self.obj): 

2303 # We will ignore the existing dtypes instead of using 

2304 # internals.concat logic 

2305 df = value.to_frame().T 

2306 

2307 idx = self.obj.index 

2308 if isinstance(idx, MultiIndex): 

2309 name = idx.names 

2310 else: 

2311 name = idx.name 

2312 

2313 df.index = Index([indexer], name=name) 

2314 if not has_dtype: 

2315 # i.e. if we already had a Series or ndarray, keep that 

2316 # dtype. But if we had a list or dict, then do inference 

2317 df = df.infer_objects() 

2318 self.obj._mgr = df._mgr 

2319 else: 

2320 self.obj._mgr = self.obj._append_internal(value)._mgr 

2321 

2322 def _ensure_iterable_column_indexer(self, column_indexer): 

2323 """ 

2324 Ensure that our column indexer is something that can be iterated over. 

2325 """ 

2326 ilocs: Sequence[int | np.integer] | np.ndarray | range 

2327 if is_integer(column_indexer): 

2328 ilocs = [column_indexer] 

2329 elif isinstance(column_indexer, slice): 

2330 ilocs = range(len(self.obj.columns))[column_indexer] 

2331 elif ( 

2332 isinstance(column_indexer, np.ndarray) and column_indexer.dtype.kind == "b" 

2333 ): 

2334 ilocs = np.arange(len(column_indexer))[column_indexer] 

2335 else: 

2336 ilocs = column_indexer 

2337 return ilocs 

2338 

2339 def _align_series( 

2340 self, 

2341 indexer, 

2342 ser: Series, 

2343 multiindex_indexer: bool = False, 

2344 using_cow: bool = False, 

2345 ): 

2346 """ 

2347 Parameters 

2348 ---------- 

2349 indexer : tuple, slice, scalar 

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

2351 ser : pd.Series 

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

2353 multiindex_indexer : bool, optional 

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

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

2356 

2357 Returns 

2358 ------- 

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

2360 to the locations selected by `indexer` 

2361 """ 

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

2363 indexer = (indexer,) 

2364 

2365 if isinstance(indexer, tuple): 

2366 # flatten np.ndarray indexers 

2367 if ( 

2368 len(indexer) == 2 

2369 and isinstance(indexer[1], np.ndarray) 

2370 and indexer[1].dtype == np.bool_ 

2371 ): 

2372 indexer = (indexer[0], np.where(indexer[1])[0]) 

2373 

2374 def ravel(i): 

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

2376 

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

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

2379 sum_aligners = sum(aligners) 

2380 single_aligner = sum_aligners == 1 

2381 is_frame = self.ndim == 2 

2382 obj = self.obj 

2383 

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

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

2386 # hence need to align to a single axis dimension 

2387 # rather that find all valid dims 

2388 

2389 # frame 

2390 if is_frame: 

2391 single_aligner = single_aligner and aligners[0] 

2392 

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

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

2395 if all(is_sequence(_) or isinstance(_, slice) for _ in indexer): 

2396 ser_values = ser.reindex(obj.axes[0][indexer[0]])._values 

2397 

2398 # single indexer 

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

2400 if isinstance(indexer[1], slice): 

2401 len_indexer = len(obj.axes[1][indexer[1]]) 

2402 else: 

2403 len_indexer = len(indexer[1]) 

2404 ser_values = ( 

2405 np.tile(ser_values, len_indexer).reshape(len_indexer, -1).T 

2406 ) 

2407 

2408 return ser_values 

2409 

2410 for i, idx in enumerate(indexer): 

2411 ax = obj.axes[i] 

2412 

2413 # multiple aligners (or null slices) 

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

2415 if single_aligner and com.is_null_slice(idx): 

2416 continue 

2417 new_ix = ax[idx] 

2418 if not is_list_like_indexer(new_ix): 

2419 new_ix = Index([new_ix]) 

2420 else: 

2421 new_ix = Index(new_ix) 

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

2423 if using_cow: 

2424 return ser 

2425 return ser._values.copy() 

2426 

2427 return ser.reindex(new_ix)._values 

2428 

2429 # 2 dims 

2430 elif single_aligner: 

2431 # reindex along index 

2432 ax = self.obj.axes[1] 

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

2434 return ser._values.copy() 

2435 return ser.reindex(ax)._values 

2436 

2437 elif is_integer(indexer) and self.ndim == 1: 

2438 if is_object_dtype(self.obj.dtype): 

2439 return ser 

2440 ax = self.obj._get_axis(0) 

2441 

2442 if ser.index.equals(ax): 

2443 return ser._values.copy() 

2444 

2445 return ser.reindex(ax)._values[indexer] 

2446 

2447 elif is_integer(indexer): 

2448 ax = self.obj._get_axis(1) 

2449 

2450 if ser.index.equals(ax): 

2451 return ser._values.copy() 

2452 

2453 return ser.reindex(ax)._values 

2454 

2455 raise ValueError("Incompatible indexer with Series") 

2456 

2457 def _align_frame(self, indexer, df: DataFrame) -> DataFrame: 

2458 is_frame = self.ndim == 2 

2459 

2460 if isinstance(indexer, tuple): 

2461 idx, cols = None, None 

2462 sindexers = [] 

2463 for i, ix in enumerate(indexer): 

2464 ax = self.obj.axes[i] 

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

2466 if isinstance(ix, np.ndarray): 

2467 ix = ix.reshape(-1) 

2468 if idx is None: 

2469 idx = ax[ix] 

2470 elif cols is None: 

2471 cols = ax[ix] 

2472 else: 

2473 break 

2474 else: 

2475 sindexers.append(i) 

2476 

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

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

2479 val = df.copy() 

2480 else: 

2481 val = df.reindex(idx, columns=cols) 

2482 return val 

2483 

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

2485 ax = self.obj.index[indexer] 

2486 if df.index.equals(ax): 

2487 val = df.copy() 

2488 else: 

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

2490 # with a particular, level GH3738 

2491 if ( 

2492 isinstance(ax, MultiIndex) 

2493 and isinstance(df.index, MultiIndex) 

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

2495 ): 

2496 raise TypeError( 

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

2498 "specifying the join levels" 

2499 ) 

2500 

2501 val = df.reindex(index=ax) 

2502 return val 

2503 

2504 raise ValueError("Incompatible indexer with DataFrame") 

2505 

2506 

2507class _ScalarAccessIndexer(NDFrameIndexerBase): 

2508 """ 

2509 Access scalars quickly. 

2510 """ 

2511 

2512 # sub-classes need to set _takeable 

2513 _takeable: bool 

2514 

2515 def _convert_key(self, key): 

2516 raise AbstractMethodError(self) 

2517 

2518 def __getitem__(self, key): 

2519 if not isinstance(key, tuple): 

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

2521 if not is_list_like_indexer(key): 

2522 key = (key,) 

2523 else: 

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

2525 

2526 key = self._convert_key(key) 

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

2528 

2529 def __setitem__(self, key, value) -> None: 

2530 if isinstance(key, tuple): 

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

2532 else: 

2533 # scalar callable may return tuple 

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

2535 

2536 if not isinstance(key, tuple): 

2537 key = _tuplify(self.ndim, key) 

2538 key = list(self._convert_key(key)) 

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

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

2541 

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

2543 

2544 

2545@doc(IndexingMixin.at) 

2546class _AtIndexer(_ScalarAccessIndexer): 

2547 _takeable = False 

2548 

2549 def _convert_key(self, key): 

2550 """ 

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

2552 fallback) 

2553 """ 

2554 # GH 26989 

2555 # For series, unpacking key needs to result in the label. 

2556 # This is already the case for len(key) == 1; e.g. (1,) 

2557 if self.ndim == 1 and len(key) > 1: 

2558 key = (key,) 

2559 

2560 return key 

2561 

2562 @property 

2563 def _axes_are_unique(self) -> bool: 

2564 # Only relevant for self.ndim == 2 

2565 assert self.ndim == 2 

2566 return self.obj.index.is_unique and self.obj.columns.is_unique 

2567 

2568 def __getitem__(self, key): 

2569 if self.ndim == 2 and not self._axes_are_unique: 

2570 # GH#33041 fall back to .loc 

2571 if not isinstance(key, tuple) or not all(is_scalar(x) for x in key): 

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

2573 return self.obj.loc[key] 

2574 

2575 return super().__getitem__(key) 

2576 

2577 def __setitem__(self, key, value) -> None: 

2578 if not CHAINED_WARNING_DISABLED: 

2579 if sys.getrefcount(self.obj) <= REF_COUNT_IDX: 

2580 warnings.warn( 

2581 _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 

2582 ) 

2583 

2584 if self.ndim == 2 and not self._axes_are_unique: 

2585 # GH#33041 fall back to .loc 

2586 if not isinstance(key, tuple) or not all(is_scalar(x) for x in key): 

2587 raise ValueError("Invalid call for scalar access (setting)!") 

2588 

2589 self.obj.loc[key] = value 

2590 return 

2591 

2592 return super().__setitem__(key, value) 

2593 

2594 

2595@doc(IndexingMixin.iat) 

2596class _iAtIndexer(_ScalarAccessIndexer): 

2597 _takeable = True 

2598 

2599 def _convert_key(self, key): 

2600 """ 

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

2602 """ 

2603 for i in key: 

2604 if not is_integer(i): 

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

2606 return key 

2607 

2608 def __setitem__(self, key, value) -> None: 

2609 if not CHAINED_WARNING_DISABLED: 

2610 if sys.getrefcount(self.obj) <= REF_COUNT_IDX: 

2611 warnings.warn( 

2612 _chained_assignment_msg, ChainedAssignmentError, stacklevel=2 

2613 ) 

2614 

2615 return super().__setitem__(key, value) 

2616 

2617 

2618def _tuplify(ndim: int, loc: Hashable) -> tuple[Hashable | slice, ...]: 

2619 """ 

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

2621 for indexing over all dimensions. 

2622 

2623 Parameters 

2624 ---------- 

2625 ndim : int 

2626 loc : object 

2627 

2628 Returns 

2629 ------- 

2630 tuple 

2631 """ 

2632 _tup: list[Hashable | slice] 

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

2634 _tup[0] = loc 

2635 return tuple(_tup) 

2636 

2637 

2638def _tupleize_axis_indexer(ndim: int, axis: AxisInt, key) -> tuple: 

2639 """ 

2640 If we have an axis, adapt the given key to be axis-independent. 

2641 """ 

2642 new_key = [slice(None)] * ndim 

2643 new_key[axis] = key 

2644 return tuple(new_key) 

2645 

2646 

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

2648 """ 

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

2650 perform reindexing or conversion if needed. 

2651 

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

2653 

2654 Parameters 

2655 ---------- 

2656 index : Index 

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

2658 key : list-like 

2659 Boolean indexer to check. 

2660 

2661 Returns 

2662 ------- 

2663 np.array 

2664 Resulting key. 

2665 

2666 Raises 

2667 ------ 

2668 IndexError 

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

2670 IndexingError 

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

2672 """ 

2673 result = key 

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

2675 indexer = result.index.get_indexer_for(index) 

2676 if -1 in indexer: 

2677 raise IndexingError( 

2678 "Unalignable boolean Series provided as " 

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

2680 "the indexed object do not match)." 

2681 ) 

2682 

2683 result = result.take(indexer) 

2684 

2685 # fall through for boolean 

2686 if not isinstance(result.dtype, ExtensionDtype): 

2687 return result.astype(bool)._values 

2688 

2689 if is_object_dtype(key): 

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

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

2692 elif not is_array_like(result): 

2693 # GH 33924 

2694 # key may contain nan elements, check_array_indexer needs bool array 

2695 result = pd_array(result, dtype=bool) 

2696 return check_array_indexer(index, result) 

2697 

2698 

2699def convert_missing_indexer(indexer): 

2700 """ 

2701 Reverse convert a missing indexer, which is a dict 

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

2703 """ 

2704 if isinstance(indexer, dict): 

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

2706 indexer = indexer["key"] 

2707 

2708 if isinstance(indexer, bool): 

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

2710 return indexer, True 

2711 

2712 return indexer, False 

2713 

2714 

2715def convert_from_missing_indexer_tuple(indexer: tuple, axes: list[Index]) -> tuple: 

2716 """ 

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

2718 """ 

2719 

2720 def get_indexer(_i, _idx): 

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

2722 

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

2724 

2725 

2726def maybe_convert_ix(*args): 

2727 """ 

2728 We likely want to take the cross-product. 

2729 """ 

2730 for arg in args: 

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

2732 return args 

2733 return np.ix_(*args) 

2734 

2735 

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

2737 """ 

2738 Returns 

2739 ------- 

2740 bool 

2741 """ 

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

2743 if not isinstance(tup, tuple): 

2744 return False 

2745 

2746 for k in tup: 

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

2748 return isinstance(labels, MultiIndex) 

2749 

2750 return False 

2751 

2752 

2753def is_label_like(key) -> bool: 

2754 """ 

2755 Returns 

2756 ------- 

2757 bool 

2758 """ 

2759 # select a label or row 

2760 return ( 

2761 not isinstance(key, slice) 

2762 and not is_list_like_indexer(key) 

2763 and key is not Ellipsis 

2764 ) 

2765 

2766 

2767def need_slice(obj: slice) -> bool: 

2768 """ 

2769 Returns 

2770 ------- 

2771 bool 

2772 """ 

2773 return ( 

2774 obj.start is not None 

2775 or obj.stop is not None 

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

2777 ) 

2778 

2779 

2780def check_dict_or_set_indexers(key) -> None: 

2781 """ 

2782 Check if the indexer is or contains a dict or set, which is no longer allowed. 

2783 """ 

2784 if isinstance(key, set) or ( 

2785 isinstance(key, tuple) and any(isinstance(x, set) for x in key) 

2786 ): 

2787 raise TypeError( 

2788 "Passing a set as an indexer is not supported. Use a list instead." 

2789 ) 

2790 

2791 if isinstance(key, dict) or ( 

2792 isinstance(key, tuple) and any(isinstance(x, dict) for x in key) 

2793 ): 

2794 raise TypeError( 

2795 "Passing a dict as an indexer is not supported. Use a list instead." 

2796 )