diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 3d9069a5516f1..77003719360d9 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -566,7 +566,7 @@ def iat(self) -> "_iAtIndexer": return _iAtIndexer("iat", self) -class _NDFrameIndexer(_NDFrameIndexerBase): +class _LocationIndexer(_NDFrameIndexerBase): _valid_types: str axis = None @@ -1580,10 +1580,6 @@ def _validate_read_indexer( def _convert_to_indexer(self, key, axis: int): raise AbstractMethodError(self) - -class _LocationIndexer(_NDFrameIndexer): - _takeable: bool = False - def __getitem__(self, key): if type(key) is tuple: key = tuple(com.apply_if_callable(x, self.obj) for x in key) @@ -1614,94 +1610,17 @@ def _getbool_axis(self, key, axis: int): inds = key.nonzero()[0] return self.obj._take_with_is_copy(inds, axis=axis) - def _convert_to_indexer(self, key, axis: int): - """ - Convert indexing key into something we can use to do actual fancy - indexing on a ndarray. - - Examples - ix[:5] -> slice(0, 5) - ix[[1,2,3]] -> [1,2,3] - ix[['foo', 'bar', 'baz']] -> [i, j, k] (indices of foo, bar, baz) - - Going by Zen of Python? - 'In the face of ambiguity, refuse the temptation to guess.' - raise AmbiguousIndexError with integer labels? - - No, prefer label-based indexing - """ - labels = self.obj._get_axis(axis) - - if isinstance(key, slice): - return self._convert_slice_indexer(key, axis) - - # try to find out correct indexer, if not type correct raise - try: - key = self._convert_scalar_indexer(key, axis) - except TypeError: - # but we will allow setting - pass - - # see if we are positional in nature - is_int_index = labels.is_integer() - is_int_positional = is_integer(key) and not is_int_index - - if is_scalar(key) or isinstance(labels, ABCMultiIndex): - # Otherwise get_loc will raise InvalidIndexError - - # if we are a label return me - try: - return labels.get_loc(key) - except LookupError: - if isinstance(key, tuple) and isinstance(labels, ABCMultiIndex): - if len(key) == labels.nlevels: - return {"key": key} - raise - except TypeError: - pass - except ValueError: - if not is_int_positional: - raise - - # a positional - if is_int_positional: - - # if we are setting and its not a valid location - # its an insert which fails by definition - - # always valid - return {"key": key} - - if is_nested_tuple(key, labels): - return labels.get_locs(key) - - elif is_list_like_indexer(key): - - if com.is_bool_indexer(key): - key = check_bool_indexer(labels, key) - (inds,) = key.nonzero() - return inds - else: - # When setting, missing keys are not allowed, even with .loc: - return self._get_listlike_indexer(key, axis, raise_missing=True)[1] - else: - try: - return labels.get_loc(key) - except LookupError: - # allow a not found key only if we are a setter - if not is_list_like_indexer(key): - return {"key": key} - raise - @Appender(IndexingMixin.loc.__doc__) class _LocIndexer(_LocationIndexer): + _takeable: bool = False _valid_types = ( "labels (MUST BE IN THE INDEX), slices of labels (BOTH " "endpoints included! Can be slices of integers if the " "index is integers), listlike of labels, boolean" ) - @Appender(_NDFrameIndexer._validate_key.__doc__) + @Appender(_LocationIndexer._validate_key.__doc__) def _validate_key(self, key, axis: int): # valid for a collection of labels (we check their presence later) @@ -1867,6 +1786,84 @@ def _get_slice_axis(self, slice_obj: slice, axis: int): # return a DatetimeIndex instead of a slice object. return self.obj.take(indexer, axis=axis) + def _convert_to_indexer(self, key, axis: int): + """ + Convert indexing key into something we can use to do actual fancy + indexing on a ndarray. + + Examples + ix[:5] -> slice(0, 5) + ix[[1,2,3]] -> [1,2,3] + ix[['foo', 'bar', 'baz']] -> [i, j, k] (indices of foo, bar, baz) + + Going by Zen of Python? + 'In the face of ambiguity, refuse the temptation to guess.' + raise AmbiguousIndexError with integer labels? + - No, prefer label-based indexing + """ + labels = self.obj._get_axis(axis) + + if isinstance(key, slice): + return self._convert_slice_indexer(key, axis) + + # try to find out correct indexer, if not type correct raise + try: + key = self._convert_scalar_indexer(key, axis) + except TypeError: + # but we will allow setting + pass + + # see if we are positional in nature + is_int_index = labels.is_integer() + is_int_positional = is_integer(key) and not is_int_index + + if is_scalar(key) or isinstance(labels, ABCMultiIndex): + # Otherwise get_loc will raise InvalidIndexError + + # if we are a label return me + try: + return labels.get_loc(key) + except LookupError: + if isinstance(key, tuple) and isinstance(labels, ABCMultiIndex): + if len(key) == labels.nlevels: + return {"key": key} + raise + except TypeError: + pass + except ValueError: + if not is_int_positional: + raise + + # a positional + if is_int_positional: + + # if we are setting and its not a valid location + # its an insert which fails by definition + + # always valid + return {"key": key} + + if is_nested_tuple(key, labels): + return labels.get_locs(key) + + elif is_list_like_indexer(key): + + if com.is_bool_indexer(key): + key = check_bool_indexer(labels, key) + (inds,) = key.nonzero() + return inds + else: + # When setting, missing keys are not allowed, even with .loc: + return self._get_listlike_indexer(key, axis, raise_missing=True)[1] + else: + try: + return labels.get_loc(key) + except LookupError: + # allow a not found key only if we are a setter + if not is_list_like_indexer(key): + return {"key": key} + raise + @Appender(IndexingMixin.iloc.__doc__) class _iLocIndexer(_LocationIndexer):