Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/indexing.py : 13%

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
3import numpy as np
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
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
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
32# "null slice"
33_NS = slice(None, None)
36# the public IndexSlicerMaker
37class _IndexSlice:
38 """
39 Create an object to more easily perform multi-index slicing.
41 See Also
42 --------
43 MultiIndex.remove_unused_levels : New MultiIndex with no unused levels.
45 Notes
46 -----
47 See :ref:`Defined Levels <advanced.shown_levels>`
48 for further info on slicing a MultiIndex.
50 Examples
51 --------
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)
58 Using the default slice command:
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
67 Using the IndexSlice class for a more intuitive command:
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 """
78 def __getitem__(self, arg):
79 return arg
82IndexSlice = _IndexSlice()
85class IndexingError(Exception):
86 pass
89class IndexingMixin:
90 """Mixin for adding .loc/.iloc/.at/.iat to Datafames and Series.
91 """
93 @property
94 def iloc(self) -> "_iLocIndexer":
95 """
96 Purely integer-location based indexing for selection by position.
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.
102 Allowed inputs are:
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.
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).
117 See more at :ref:`Selection by Position <indexing.integer>`.
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.
126 Examples
127 --------
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
139 **Indexing just the rows**
141 With a scalar integer.
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
152 With a list of integers.
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'>
160 >>> df.iloc[[0, 1]]
161 a b c d
162 0 1 2 3 4
163 1 100 200 300 400
165 With a `slice` object.
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
173 With a boolean mask the same length as the index.
175 >>> df.iloc[[True, False, True]]
176 a b c d
177 0 1 2 3 4
178 2 1000 2000 3000 4000
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.
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
189 **Indexing both axes**
191 You can mix the indexer types for the index and columns. Use ``:`` to
192 select the entire axis.
194 With scalar integers.
196 >>> df.iloc[0, 1]
197 2
199 With lists of integers.
201 >>> df.iloc[[0, 2], [1, 3]]
202 b d
203 0 2 4
204 2 2000 4000
206 With `slice` objects.
208 >>> df.iloc[1:3, 0:3]
209 a b c
210 1 100 200 300
211 2 1000 2000 3000
213 With a boolean array whose length matches the columns.
215 >>> df.iloc[:, [True, False, True, False]]
216 a c
217 0 1 3
218 1 100 300
219 2 1000 3000
221 With a callable function that expects the Series or DataFrame.
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)
231 @property
232 def loc(self) -> "_LocIndexer":
233 """
234 Access a group of rows and columns by label(s) or a boolean array.
236 ``.loc[]`` is primarily label based, but may also be used with a
237 boolean array.
239 Allowed inputs are:
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'``.
247 .. warning:: Note that contrary to usual python slices, **both** the
248 start and the stop are included
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)
255 See more at :ref:`Selection by Label <indexing.label>`
257 Raises
258 ------
259 KeyError
260 If any items are not found.
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.
270 Examples
271 --------
272 **Getting values**
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
283 Single label. Note this returns the row as a Series.
285 >>> df.loc['viper']
286 max_speed 4
287 shield 5
288 Name: viper, dtype: int64
290 List of labels. Note using ``[[]]`` returns a DataFrame.
292 >>> df.loc[['viper', 'sidewinder']]
293 max_speed shield
294 viper 4 5
295 sidewinder 7 8
297 Single label for row and column
299 >>> df.loc['cobra', 'shield']
300 2
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.
305 >>> df.loc['cobra':'viper', 'max_speed']
306 cobra 1
307 viper 4
308 Name: max_speed, dtype: int64
310 Boolean list with the same length as the row axis
312 >>> df.loc[[False, False, True]]
313 max_speed shield
314 sidewinder 7 8
316 Conditional that returns a boolean Series
318 >>> df.loc[df['shield'] > 6]
319 max_speed shield
320 sidewinder 7 8
322 Conditional that returns a boolean Series with column labels specified
324 >>> df.loc[df['shield'] > 6, ['max_speed']]
325 max_speed
326 sidewinder 7
328 Callable that returns a boolean Series
330 >>> df.loc[lambda df: df['shield'] == 8]
331 max_speed shield
332 sidewinder 7 8
334 **Setting values**
336 Set value for all items matching the list of labels
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
345 Set value for an entire row
347 >>> df.loc['cobra'] = 10
348 >>> df
349 max_speed shield
350 cobra 10 10
351 viper 4 50
352 sidewinder 7 50
354 Set value for an entire column
356 >>> df.loc[:, 'max_speed'] = 30
357 >>> df
358 max_speed shield
359 cobra 30 10
360 viper 30 50
361 sidewinder 30 50
363 Set value for rows matching callable condition
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
372 **Getting values on a DataFrame with an index that has integer labels**
374 Another example using integers for the index
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
384 Slice with integer labels for rows. As mentioned above, note that both
385 the start and stop of the slice are included.
387 >>> df.loc[7:9]
388 max_speed shield
389 7 1 2
390 8 4 5
391 9 7 8
393 **Getting values with a MultiIndex**
395 A number of examples using a DataFrame with a MultiIndex
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
415 Single label. Note this returns a DataFrame with a single index.
417 >>> df.loc['cobra']
418 max_speed shield
419 mark i 12 2
420 mark ii 0 4
422 Single index tuple. Note this returns a Series.
424 >>> df.loc[('cobra', 'mark ii')]
425 max_speed 0
426 shield 4
427 Name: (cobra, mark ii), dtype: int64
429 Single label for row and column. Similar to passing in a tuple, this
430 returns a Series.
432 >>> df.loc['cobra', 'mark i']
433 max_speed 12
434 shield 2
435 Name: (cobra, mark i), dtype: int64
437 Single tuple. Note using ``[[]]`` returns a DataFrame.
439 >>> df.loc[[('cobra', 'mark ii')]]
440 max_speed shield
441 cobra mark ii 0 4
443 Single tuple for the index with a single label for the column
445 >>> df.loc[('cobra', 'mark i'), 'shield']
446 2
448 Slice from index tuple to single label
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
459 Slice from index tuple to index tuple
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)
471 @property
472 def at(self) -> "_AtIndexer":
473 """
474 Access a single value for a row/column label pair.
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.
480 Raises
481 ------
482 KeyError
483 If 'label' does not exist in DataFrame.
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.
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
502 Get value at specified row/column pair
504 >>> df.at[4, 'B']
505 2
507 Set value at specified row/column pair
509 >>> df.at[4, 'B'] = 10
510 >>> df.at[4, 'B']
511 10
513 Get value within a Series
515 >>> df.loc[5].at['B']
516 4
517 """
518 return _AtIndexer("at", self)
520 @property
521 def iat(self) -> "_iAtIndexer":
522 """
523 Access a single value for a row/column pair by integer position.
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.
529 Raises
530 ------
531 IndexError
532 When integer position is out of bounds.
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).
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
550 Get value at specified row/column pair
552 >>> df.iat[1, 2]
553 1
555 Set value at specified row/column pair
557 >>> df.iat[1, 2] = 10
558 >>> df.iat[1, 2]
559 10
561 Get value within a series
563 >>> df.loc[0].iat[1]
564 2
565 """
566 return _iAtIndexer("iat", self)
569class _NDFrameIndexer(_NDFrameIndexerBase):
570 _valid_types: str
571 axis = None
573 def __call__(self, axis=None):
574 # we need to return a copy of ourselves
575 new_self = type(self)(self.name, self.obj)
577 if axis is not None:
578 axis = self.obj._get_axis_number(axis)
579 new_self.axis = axis
580 return new_self
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
607 return self._getitem_tuple(key)
608 else:
609 # we by definition only have the 0th axis
610 axis = self.axis or 0
612 key = com.apply_if_callable(key, self.obj)
613 return self._getitem_axis(key, axis=axis)
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")
625 return self.obj._xs(label, axis=axis)
627 def _get_loc(self, key: int, axis: int):
628 return self.obj._ixs(key, axis=axis)
630 def _slice(self, obj, axis: int, kind=None):
631 return self.obj._slice(obj, axis=axis, kind=kind)
633 def _get_setitem_indexer(self, key):
634 if self.axis is not None:
635 return self._convert_tuple(key)
637 ax = self.obj._get_axis(0)
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
646 if isinstance(key, tuple):
647 try:
648 return self._convert_tuple(key)
649 except IndexingError:
650 pass
652 if isinstance(key, range):
653 return list(key)
655 axis = self.axis or 0
656 try:
657 return self._convert_to_indexer(key, axis=axis)
658 except TypeError as e:
660 # invalid indexer type vs 'other' indexing errors
661 if "cannot do" in str(e):
662 raise
663 raise IndexingError(key)
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)
673 def _validate_key(self, key, axis: int):
674 """
675 Ensure that key is valid for current indexer.
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.
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)
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 )
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
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)
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)
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)
748 def _has_valid_setitem_indexer(self, indexer) -> bool:
749 return True
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.
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")
780 return True
782 def _setitem_with_indexer(self, indexer, value):
783 self._has_valid_setitem_indexer(indexer)
785 # also has the side effect of consolidating in-place
786 from pandas import Series
788 info_axis = self.obj._info_axis_number
790 # maybe partial set
791 take_split_path = self.obj._is_mixed_type
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)
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
812 if isinstance(indexer, tuple):
813 nindexer = []
814 for i, idx in enumerate(indexer):
815 if isinstance(idx, dict):
817 # reindex the axis to the new value
818 # and set inplace
819 key, _ = convert_missing_indexer(idx)
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:
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
844 # add a new item with the dtype setup
845 self.obj[key] = _infer_fill_value(value)
847 new_indexer = convert_from_missing_indexer_tuple(
848 indexer, self.obj.axes
849 )
850 self._setitem_with_indexer(new_indexer, value)
852 return self.obj
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
864 nindexer.append(labels.get_loc(key))
866 else:
867 nindexer.append(idx)
869 indexer = tuple(nindexer)
870 else:
872 indexer, missing = convert_missing_indexer(indexer)
874 if missing:
875 return self._setitem_with_indexer_missing(indexer, value)
877 # set
878 item_labels = self.obj._get_axis(info_axis)
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
886 if not isinstance(indexer, tuple):
887 indexer = _tuplify(self.ndim, indexer)
889 if isinstance(value, ABCSeries):
890 value = self._align_series(indexer, value)
892 info_idx = indexer[info_axis]
893 if is_integer(info_idx):
894 info_idx = [info_idx]
895 labels = item_labels[info_idx]
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]
907 plane_indexer = tuple([idx]) + indexer[info_axis + 1 :]
908 lplane_indexer = length_of_indexer(plane_indexer[0], index)
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 ):
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 )
925 # make sure we have an ndarray
926 value = getattr(value, "values", value).ravel()
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
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)
944 def setter(item, v):
945 s = self.obj[item]
946 pi = plane_indexer[0] if lplane_indexer == 1 else plane_indexer
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)
965 # reset the sliced object if unique
966 self.obj[item] = s
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:
972 # we have an equal len Frame
973 if isinstance(value, ABCDataFrame):
974 sub_indexer = list(indexer)
975 multiindex_indexer = isinstance(labels, ABCMultiIndex)
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
986 setter(item, v)
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:
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 )
1003 for i, item in enumerate(labels):
1005 # setting with a list, recoerces
1006 setter(item, value[:, i].tolist())
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)
1014 # per label values
1015 else:
1017 if len(labels) != len(value):
1018 raise ValueError(
1019 "Must have equal len keys and value "
1020 "when setting with an iterable"
1021 )
1023 for item, v in zip(labels, value):
1024 setter(item, v)
1025 else:
1027 # scalar
1028 for item in labels:
1029 setter(item, value)
1031 else:
1032 if isinstance(indexer, tuple):
1033 indexer = maybe_convert_ix(*indexer)
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
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))
1057 elif isinstance(value, ABCDataFrame):
1058 value = self._align_frame(indexer, value)
1060 # check for chained assignment
1061 self.obj._check_is_chained_assignment_possible()
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)
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
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)
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)
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
1103 elif self.ndim == 2:
1105 if not len(self.obj.columns):
1106 # no columns and scalar
1107 raise ValueError("cannot set a frame with no defined columns")
1109 if isinstance(value, ABCSeries):
1110 # append a Series
1111 value = value.reindex(index=self.obj.columns, copy=True)
1112 value.name = indexer
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")
1121 value = Series(value, index=self.obj.columns, name=indexer)
1123 self.obj._data = self.obj.append(value)._data
1124 self.obj._maybe_update_cacher(clear=True)
1125 return self.obj
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.
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])
1147 if isinstance(indexer, tuple):
1149 # flatten np.ndarray indexers
1150 def ravel(i):
1151 return i.ravel() if isinstance(i, np.ndarray) else i
1153 indexer = tuple(map(ravel, indexer))
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
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
1166 # frame
1167 if is_frame:
1168 single_aligner = single_aligner and aligners[0]
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
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
1180 return ser
1182 for i, idx in enumerate(indexer):
1183 ax = obj.axes[i]
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()
1197 return ser.reindex(new_ix)._values
1199 # 2 dims
1200 elif single_aligner:
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
1208 elif is_scalar(indexer):
1209 ax = self.obj._get_axis(1)
1211 if ser.index.equals(ax):
1212 return ser._values.copy()
1214 return ser.reindex(ax)._values
1216 raise ValueError("Incompatible indexer with Series")
1218 def _align_frame(self, indexer, df: ABCDataFrame):
1219 is_frame = self.ndim == 2
1221 if isinstance(indexer, tuple):
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)
1239 if idx is not None and cols is not None:
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
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:
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 )
1265 val = df.reindex(index=ax)._values
1266 return val
1268 raise ValueError("Incompatible indexer with DataFrame")
1270 def _getitem_tuple(self, tup: Tuple):
1271 try:
1272 return self._getitem_lowerdim(tup)
1273 except IndexingError:
1274 pass
1276 # no multi-index, so validate all of the indexers
1277 self._has_valid_tuple(tup)
1279 # ugly hack for GH #836
1280 if self._multi_take_opportunity(tup):
1281 return self._multi_take(tup)
1283 # no shortcut needed
1284 retval = self.obj
1285 for i, key in enumerate(tup):
1286 if com.is_null_slice(key):
1287 continue
1289 retval = getattr(retval, self.name)._getitem_axis(key, axis=i)
1291 return retval
1293 def _multi_take_opportunity(self, tup: Tuple) -> bool:
1294 """
1295 Check whether there is the possibility to use ``_multi_take``.
1297 Currently the limit is that all axes being indexed, must be indexed with
1298 list-likes.
1300 Parameters
1301 ----------
1302 tup : tuple
1303 Tuple of indexers, one per axis.
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
1314 # just too complicated
1315 if any(com.is_bool_indexer(x) for x in tup):
1316 return False
1318 return True
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.
1327 Parameters
1328 ----------
1329 tup : tuple
1330 Tuple of indexers, one per axis.
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)
1344 def _convert_for_reindex(self, key, axis: int):
1345 return key
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
1362 return None
1364 def _getitem_lowerdim(self, tup: Tuple):
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)
1371 # we may have a nested tuples indexer here
1372 if self._is_nested_tuple_indexer(tup):
1373 return self._getitem_nested_tuple(tup)
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
1384 if len(tup) > self.ndim:
1385 raise IndexingError("Too many indexers. handle elsewhere")
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)
1391 # we have yielded a scalar ?
1392 if not is_list_like_indexer(section):
1393 return section
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 :]
1400 else:
1401 new_key = tup[:i] + tup[i + 1 :]
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
1413 if len(new_key) == 1:
1414 new_key = new_key[0]
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]
1423 raise IndexingError("not applicable")
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
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
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)
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):
1448 if com.is_null_slice(key):
1449 axis += 1
1450 continue
1452 current_ndim = obj.ndim
1453 obj = getattr(obj, self.name)._getitem_axis(key, axis=axis)
1454 axis += 1
1456 # if we have a scalar, we are done
1457 if is_scalar(obj) or not hasattr(obj, "ndim"):
1458 break
1460 # has the dim of the obj changed?
1461 # GH 7199
1462 if obj.ndim < current_ndim:
1463 axis -= 1
1465 return obj
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)
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 ):
1480 if hasattr(key, "ndim") and key.ndim > 1:
1481 raise ValueError("Cannot index with multidimensional key")
1483 return self._getitem_iterable(key, axis=axis)
1484 else:
1486 # maybe coerce a float scalar to integer
1487 key = labels._maybe_cast_indexer(key)
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
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)
1501 return self._get_label(key, axis=axis)
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.
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``.
1518 Raises
1519 ------
1520 KeyError
1521 If at least one key was requested but none was found, and
1522 raise_missing=True.
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)
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
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)
1552 self._validate_read_indexer(
1553 keyarr, indexer, o._get_axis_number(axis), raise_missing=raise_missing
1554 )
1555 return keyarr, indexer
1557 def _getitem_iterable(self, key, axis: int):
1558 """
1559 Index current object with an an iterable key.
1561 The iterable key can be a boolean indexer or a collection of keys.
1563 Parameters
1564 ----------
1565 key : iterable
1566 Targeted labels or boolean indexer.
1567 axis: int
1568 Dimension on which the indexing is being made.
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.
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)
1586 labels = self.obj._get_axis(axis)
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 )
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.
1606 e.g. at least one element was found,
1607 unless the list of keys was actually empty.
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.
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)
1631 if len(key) == 0:
1632 return
1634 # Count missing values:
1635 missing = (indexer < 0).sum()
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}]")
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")
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 )
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.
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)
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)
1677 if isinstance(obj, slice):
1678 return self._convert_slice_indexer(obj, axis)
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
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
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
1705 # a positional
1706 if is_int_positional:
1708 # if we are setting and its not a valid location
1709 # its an insert which fails by definition
1711 if self.name == "loc":
1712 # always valid
1713 return {"key": obj}
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")
1719 return obj
1721 if is_nested_tuple(obj, labels):
1722 return labels.get_locs(obj)
1724 elif is_list_like_indexer(obj):
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
1742 def _get_slice_axis(self, slice_obj: slice, axis: int):
1743 # caller is responsible for ensuring non-None axis
1744 obj = self.obj
1746 if not need_slice(slice_obj):
1747 return obj.copy(deep=False)
1749 indexer = self._convert_slice_indexer(slice_obj, axis)
1750 return self._slice(indexer, axis=axis, kind="iloc")
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
1767 maybe_callable = com.apply_if_callable(key, self.obj)
1768 return self._getitem_axis(maybe_callable, axis=axis)
1770 def _is_scalar_access(self, key: Tuple):
1771 raise NotImplementedError()
1773 def _getitem_scalar(self, key):
1774 raise NotImplementedError()
1776 def _getitem_axis(self, key, axis: int):
1777 raise NotImplementedError()
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)
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)
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 )
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)
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 )
1816 @Appender(_NDFrameIndexer._validate_key.__doc__)
1817 def _validate_key(self, key, axis: int):
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
1824 if isinstance(key, slice):
1825 return
1827 if com.is_bool_indexer(key):
1828 return
1830 if not is_list_like_indexer(key):
1831 self._convert_scalar_indexer(key, axis)
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
1846 for i, k in enumerate(key):
1847 if not is_scalar(k):
1848 return False
1850 ax = self.obj.axes[i]
1851 if isinstance(ax, ABCMultiIndex):
1852 return False
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
1859 if not ax.is_unique:
1860 return False
1862 return True
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
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.
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))
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)
1900 return key
1902 def _getitem_axis(self, key, axis: int):
1903 key = item_from_zerodim(key)
1904 if is_iterator(key):
1905 key = list(key)
1907 labels = self.obj._get_axis(axis)
1908 key = self._get_partial_string_timestamp_match_key(key, labels)
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):
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):
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 )
1941 if (
1942 not isinstance(key, tuple)
1943 and len(key)
1944 and not isinstance(key[0], tuple)
1945 ):
1946 key = tuple([key])
1948 # an iterable multi-selection
1949 if not (isinstance(key, tuple) and isinstance(labels, ABCMultiIndex)):
1951 if hasattr(key, "ndim") and key.ndim > 1:
1952 raise ValueError("Cannot index with multidimensional key")
1954 return self._getitem_iterable(key, axis=axis)
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)]
1963 # fall thru to straight lookup
1964 self._validate_key(key, axis)
1965 return self._get_label(key, axis=axis)
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
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
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))
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}")
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}]")
2013 def _has_valid_setitem_indexer(self, indexer):
2014 self._has_valid_positional_setitem_indexer(indexer)
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
2029 for i, k in enumerate(key):
2030 if not is_integer(k):
2031 return False
2033 ax = self.obj.axes[i]
2034 if not ax.is_unique:
2035 return False
2037 return True
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
2045 def _validate_integer(self, key: int, axis: int) -> None:
2046 """
2047 Check that 'key' is a valid position in the desired axis.
2049 Parameters
2050 ----------
2051 key : int
2052 Requested position.
2053 axis : int
2054 Desired axis.
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")
2065 def _getitem_tuple(self, tup: Tuple):
2067 self._has_valid_tuple(tup)
2068 try:
2069 return self._getitem_lowerdim(tup)
2070 except IndexingError:
2071 pass
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
2080 retval = getattr(retval, self.name)._getitem_axis(key, axis=axis)
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
2088 # try to get for the next axis
2089 axis += 1
2091 return retval
2093 def _get_list_axis(self, key, axis: int):
2094 """
2095 Return Series values by list or array of integers.
2097 Parameters
2098 ----------
2099 key : list-like positional indexer
2100 axis : int
2102 Returns
2103 -------
2104 Series object
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")
2116 def _getitem_axis(self, key, axis: int):
2117 if isinstance(key, slice):
2118 return self._get_slice_axis(key, axis=axis)
2120 if isinstance(key, list):
2121 key = np.asarray(key)
2123 if com.is_bool_indexer(key):
2124 self._validate_key(key, axis)
2125 return self._getbool_axis(key, axis=axis)
2127 # a list of integers
2128 elif is_list_like_indexer(key):
2129 return self._get_list_axis(key, axis=axis)
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")
2137 # validate the location
2138 self._validate_integer(key, axis)
2140 return self._get_loc(key, axis=axis)
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)
2151 elif is_float(obj):
2152 return self._convert_scalar_indexer(obj, axis)
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}]")
2161class _ScalarAccessIndexer(_NDFrameIndexerBase):
2162 """
2163 Access scalars quickly.
2164 """
2166 def _convert_key(self, key, is_setter: bool = False):
2167 raise AbstractMethodError(self)
2169 def __getitem__(self, key):
2170 if not isinstance(key, tuple):
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)!")
2178 key = self._convert_key(key)
2179 return self.obj._get_value(*key, takeable=self._takeable)
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)
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)
2197@Appender(IndexingMixin.at.__doc__)
2198class _AtIndexer(_ScalarAccessIndexer):
2199 _takeable = False
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)
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
2227@Appender(IndexingMixin.iat.__doc__)
2228class _iAtIndexer(_ScalarAccessIndexer):
2229 _takeable = True
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
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.
2246 Parameters
2247 ----------
2248 ndim : int
2249 loc : object
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)
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")
2269 elif isinstance(key, str):
2271 # we are an actual column
2272 if key in obj._data.items:
2273 return None
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
2283 return None
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.
2291 This function assumes that is_bool_indexer(key) == True.
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.
2300 Returns
2301 -------
2302 np.array
2303 Resulting key.
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)
2330 return result
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):
2340 # a missing key (but not a tuple indexer)
2341 indexer = indexer["key"]
2343 if isinstance(indexer, bool):
2344 raise KeyError("cannot use a single bool to index into setitem")
2345 return indexer, True
2347 return indexer, False
2350def convert_from_missing_indexer_tuple(indexer, axes):
2351 """
2352 Create a filtered indexer that doesn't have any missing indexers.
2353 """
2355 def get_indexer(_i, _idx):
2356 return axes[_i].get_loc(_idx["key"]) if isinstance(_idx, dict) else _idx
2358 return tuple(get_indexer(_i, _idx) for _i, _idx in enumerate(indexer))
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
2370 if ixify:
2371 return np.ix_(*args)
2372 else:
2373 return args
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
2386 for i, k in enumerate(tup):
2388 if is_list_like(k) or isinstance(k, slice):
2389 return isinstance(labels, ABCMultiIndex)
2391 return False
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)
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 )
2417def _non_reducing_slice(slice_):
2418 """
2419 Ensurse that a slice doesn't reduce to a Series or Scalar.
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_]
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 )
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_)
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_
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
2479 item = labels[0]
2480 index = obj[item].index
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
2489 return False