From 90c25188b932872a2663df9b16ee2bfe9ba502ca Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Aug 2022 15:59:16 -0700 Subject: [PATCH 1/2] REF: Move Series logic from Index._get_values_for_loc --- pandas/core/indexes/base.py | 25 +++++++++++++++++++++---- pandas/core/indexes/multi.py | 32 ++++++++++++++++---------------- pandas/core/series.py | 10 +++++++++- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 78e1f713644dd..1c2ba77f65359 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -5949,6 +5949,9 @@ def get_value(self, series: Series, key): else: raise + if is_integer(loc): + return series._values[loc] + return self._get_values_for_loc(series, loc, key) def _check_indexing_error(self, key): @@ -5964,19 +5967,33 @@ def _should_fallback_to_positional(self) -> bool: """ return not self.holds_integer() and not self.is_boolean() + def get_loc_result_index(self, loc, key): + """ + Find the index to attach to the result of a Series.loc lookup. + + Assumes `loc` is not an integer. + + key is included for MultiIndex compat. + """ + return self[loc] + + # TODO(2.0): remove once get_value deprecation is enforced + @final def _get_values_for_loc(self, series: Series, loc, key): """ Do a positional lookup on the given Series, returning either a scalar or a Series. - Assumes that `series.index is self` + Assumes that `series.index is self` and that `loc` is not an integer, + the result of self.get_loc(key) key is included for MultiIndex compat. """ - if is_integer(loc): - return series._values[loc] + new_values = series._values[loc] - return series.iloc[loc] + new_index = self.get_loc_result_index(loc, key) + new_ser = series._constructor(new_values, index=new_index, name=series.name) + return new_ser.__finalize__(series) @final def set_value(self, arr, key, value) -> None: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 13c3b9200371f..7d30ea4f41e94 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2597,25 +2597,16 @@ def _should_fallback_to_positional(self) -> bool: # GH#33355 return self.levels[0]._should_fallback_to_positional - def _get_values_for_loc(self, series: Series, loc, key): + def get_loc_result_index(self, loc, key): """ - Do a positional lookup on the given Series, returning either a scalar - or a Series. + Find the index to attach to the result of a Series.loc lookup. - Assumes that `series.index is self` - """ - new_values = series._values[loc] - if is_scalar(loc): - return new_values - - if len(new_values) == 1 and not self.nlevels > 1: - # If more than one level left, we can not return a scalar - return new_values[0] + Assumes `loc` is not an integer. - new_index = self[loc] - new_index = maybe_droplevels(new_index, key) - new_ser = series._constructor(new_values, index=new_index, name=series.name) - return new_ser.__finalize__(series) + key is included for MultiIndex compat. + """ + new_index = super().get_loc_result_index(loc, key) + return maybe_droplevels(new_index, key) def _get_indexer_strict( self, key, axis_name: str @@ -2922,6 +2913,15 @@ def _maybe_to_slice(loc): if not isinstance(key, tuple): loc = self._get_level_indexer(key, level=0) + if ( + self.nlevels == 1 + and isinstance(loc, slice) + and loc.stop - loc.start == 1 + and loc.step is None + ): + # e.g. test_multiindex_at_get_one_level + # TODO: is this the right level at which to do this check? + return loc.start return _maybe_to_slice(loc) keylen = len(key) diff --git a/pandas/core/series.py b/pandas/core/series.py index 167590331754d..da60d34e4094e 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1086,9 +1086,17 @@ def _get_value(self, label, takeable: bool = False): if takeable: return self._values[label] + index = self.index + # Similar to Index.get_value, but we do not fall back to positional loc = self.index.get_loc(label) - return self.index._get_values_for_loc(self, loc, label) + res_values = self._values[loc] + if is_integer(loc): + return res_values + + res_index = index.get_loc_result_index(loc, label) + result = self._constructor(res_values, index=res_index, name=self.name) + return result.__finalize__(self) def __setitem__(self, key, value) -> None: check_deprecated_indexers(key) From 0af361b10e0980a94de50b00f3f87c82ab22d59b Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 20 Oct 2022 09:28:26 -0700 Subject: [PATCH 2/2] try again --- pandas/core/indexes/base.py | 25 ++++--------------------- pandas/core/indexes/multi.py | 32 ++++++++++++++++---------------- pandas/core/series.py | 24 +++++++++++++++++------- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index d7fd246239db0..cde221c77fe9b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6018,9 +6018,6 @@ def get_value(self, series: Series, key): else: raise - if is_integer(loc): - return series._values[loc] - return self._get_values_for_loc(series, loc, key) def _check_indexing_error(self, key): @@ -6036,33 +6033,19 @@ def _should_fallback_to_positional(self) -> bool: """ return not self.holds_integer() - def get_loc_result_index(self, loc, key): - """ - Find the index to attach to the result of a Series.loc lookup. - - Assumes `loc` is not an integer. - - key is included for MultiIndex compat. - """ - return self[loc] - - # TODO(2.0): remove once get_value deprecation is enforced - @final def _get_values_for_loc(self, series: Series, loc, key): """ Do a positional lookup on the given Series, returning either a scalar or a Series. - Assumes that `series.index is self` and that `loc` is not an integer, - the result of self.get_loc(key) + Assumes that `series.index is self` key is included for MultiIndex compat. """ - new_values = series._values[loc] + if is_integer(loc): + return series._values[loc] - new_index = self.get_loc_result_index(loc, key) - new_ser = series._constructor(new_values, index=new_index, name=series.name) - return new_ser.__finalize__(series) + return series.iloc[loc] @final def set_value(self, arr, key, value) -> None: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index daa087bf2ba5a..cab2c929fae42 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2633,16 +2633,25 @@ def _should_fallback_to_positional(self) -> bool: # GH#33355 return self.levels[0]._should_fallback_to_positional - def get_loc_result_index(self, loc, key): + def _get_values_for_loc(self, series: Series, loc, key): """ - Find the index to attach to the result of a Series.loc lookup. + Do a positional lookup on the given Series, returning either a scalar + or a Series. - Assumes `loc` is not an integer. - - key is included for MultiIndex compat. + Assumes that `series.index is self` """ - new_index = super().get_loc_result_index(loc, key) - return maybe_droplevels(new_index, key) + new_values = series._values[loc] + if is_scalar(loc): + return new_values + + if len(new_values) == 1 and not self.nlevels > 1: + # If more than one level left, we can not return a scalar + return new_values[0] + + new_index = self[loc] + new_index = maybe_droplevels(new_index, key) + new_ser = series._constructor(new_values, index=new_index, name=series.name) + return new_ser.__finalize__(series) def _get_indexer_strict( self, key, axis_name: str @@ -2949,15 +2958,6 @@ def _maybe_to_slice(loc): if not isinstance(key, tuple): loc = self._get_level_indexer(key, level=0) - if ( - self.nlevels == 1 - and isinstance(loc, slice) - and loc.stop - loc.start == 1 - and loc.step is None - ): - # e.g. test_multiindex_at_get_one_level - # TODO: is this the right level at which to do this check? - return loc.start return _maybe_to_slice(loc) keylen = len(key) diff --git a/pandas/core/series.py b/pandas/core/series.py index ee9a47b5c89c7..4b47d71c453c8 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -140,6 +140,7 @@ ensure_index, ) import pandas.core.indexes.base as ibase +from pandas.core.indexes.multi import maybe_droplevels from pandas.core.indexing import ( check_bool_indexer, check_deprecated_indexers, @@ -1060,17 +1061,26 @@ def _get_value(self, label, takeable: bool = False): if takeable: return self._values[label] - index = self.index - # Similar to Index.get_value, but we do not fall back to positional loc = self.index.get_loc(label) - res_values = self._values[loc] + if is_integer(loc): - return res_values + return self._values[loc] - res_index = index.get_loc_result_index(loc, label) - result = self._constructor(res_values, index=res_index, name=self.name) - return result.__finalize__(self) + if isinstance(self.index, MultiIndex): + mi = self.index + new_values = self._values[loc] + if len(new_values) == 1 and mi.nlevels == 1: + # If more than one level left, we can not return a scalar + return new_values[0] + + new_index = mi[loc] + new_index = maybe_droplevels(new_index, label) + new_ser = self._constructor(new_values, index=new_index, name=self.name) + return new_ser.__finalize__(self) + + else: + return self.iloc[loc] def __setitem__(self, key, value) -> None: check_deprecated_indexers(key)