From 016eafa20a7409ca154ab1a4909ae70d284105e0 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 29 Jan 2020 08:14:49 -0800 Subject: [PATCH 1/2] REF: do all convert_tolerance casting inside Index.get_loc --- pandas/core/indexes/base.py | 4 ++++ pandas/core/indexes/datetimes.py | 23 ++++++++++--------- pandas/core/indexes/timedeltas.py | 7 ------ pandas/tests/series/indexing/test_datetime.py | 10 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 8ac8e51795ddc..35ed102c604d3 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2900,6 +2900,10 @@ def get_loc(self, key, method=None, tolerance=None): return self._engine.get_loc(key) except KeyError: return self._engine.get_loc(self._maybe_cast_indexer(key)) + + if tolerance is not None: + tolerance = self._convert_tolerance(tolerance, np.asarray(key)) + indexer = self.get_indexer([key], method=method, tolerance=tolerance) if indexer.ndim > 1 or indexer.size > 1: raise TypeError("get_loc requires scalar valued input") diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 3afd1ff35806d..f036b4b6a42db 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -658,18 +658,13 @@ def get_loc(self, key, method=None, tolerance=None): if not is_scalar(key): raise InvalidIndexError(key) + orig_key = key if is_valid_nat_for_dtype(key, self.dtype): key = NaT - if tolerance is not None: - # try converting tolerance now, so errors don't get swallowed by - # the try/except clauses below - tolerance = self._convert_tolerance(tolerance, np.asarray(key)) - if isinstance(key, (datetime, np.datetime64)): # needed to localize naive datetimes key = self._maybe_cast_for_get_loc(key) - return Index.get_loc(self, key, method, tolerance) elif isinstance(key, str): try: @@ -678,9 +673,8 @@ def get_loc(self, key, method=None, tolerance=None): pass try: - stamp = self._maybe_cast_for_get_loc(key) - return Index.get_loc(self, stamp, method, tolerance) - except (KeyError, ValueError): + key = self._maybe_cast_for_get_loc(key) + except ValueError: raise KeyError(key) elif isinstance(key, timedelta): @@ -689,14 +683,21 @@ def get_loc(self, key, method=None, tolerance=None): f"Cannot index {type(self).__name__} with {type(key).__name__}" ) - if isinstance(key, time): + elif isinstance(key, time): if method is not None: raise NotImplementedError( "cannot yet lookup inexact labels when key is a time object" ) return self.indexer_at_time(key) - return Index.get_loc(self, key, method, tolerance) + else: + # unrecognized type + raise KeyError(key) + + try: + return Index.get_loc(self, key, method, tolerance) + except KeyError: + raise KeyError(orig_key) def _maybe_cast_for_get_loc(self, key): # needed to localize naive datetimes diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 1257e410b4125..1f08d4803e74c 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -1,7 +1,5 @@ """ implement the TimedeltaIndex """ -import numpy as np - from pandas._libs import NaT, Timedelta, index as libindex from pandas.util._decorators import Appender @@ -270,11 +268,6 @@ def get_loc(self, key, method=None, tolerance=None): else: raise KeyError(key) - if tolerance is not None: - # try converting tolerance now, so errors don't get swallowed by - # the try/except clauses below - tolerance = self._convert_tolerance(tolerance, np.asarray(key)) - return Index.get_loc(self, key, method, tolerance) def _maybe_cast_slice_bound(self, label, side, kind): diff --git a/pandas/tests/series/indexing/test_datetime.py b/pandas/tests/series/indexing/test_datetime.py index 77085ef547690..813c195b36f67 100644 --- a/pandas/tests/series/indexing/test_datetime.py +++ b/pandas/tests/series/indexing/test_datetime.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +import re import numpy as np import pytest @@ -380,7 +381,7 @@ def test_datetime_indexing(): s = Series(len(index), index=index) stamp = Timestamp("1/8/2000") - with pytest.raises(KeyError, match=r"^947289600000000000$"): + with pytest.raises(KeyError, match=re.escape(repr(stamp))): s[stamp] s[stamp] = 0 assert s[stamp] == 0 @@ -389,7 +390,7 @@ def test_datetime_indexing(): s = Series(len(index), index=index) s = s[::-1] - with pytest.raises(KeyError, match=r"^947289600000000000$"): + with pytest.raises(KeyError, match=re.escape(repr(stamp))): s[stamp] s[stamp] = 0 assert s[stamp] == 0 @@ -495,8 +496,9 @@ def test_duplicate_dates_indexing(dups): expected = Series(np.where(mask, 0, ts), index=ts.index) tm.assert_series_equal(cp, expected) - with pytest.raises(KeyError, match=r"^947116800000000000$"): - ts[datetime(2000, 1, 6)] + key = datetime(2000, 1, 6) + with pytest.raises(KeyError, match=re.escape(repr(key))): + ts[key] # new index ts[datetime(2000, 1, 6)] = 0 From a4c7a8ca792b9ae48fc9cdbcdf80dec83ae92705 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Thu, 30 Jan 2020 21:04:18 -0800 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.1.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 54175fada6e56..d64ec558fc5a8 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -149,6 +149,7 @@ Indexing - Bug in slicing on a :class:`DatetimeIndex` with a partial-timestamp dropping high-resolution indices near the end of a year, quarter, or month (:issue:`31064`) - Bug in :meth:`PeriodIndex.get_loc` treating higher-resolution strings differently from :meth:`PeriodIndex.get_value` (:issue:`31172`) - Bug in :meth:`Series.at` and :meth:`DataFrame.at` not matching ``.loc`` behavior when looking up an integer in a :class:`Float64Index` (:issue:`31329`) +- Bug in :meth:`DatetimeIndex.get_loc` raising ``KeyError`` with converted-integer key instead of the user-passed key (:issue:`31425`) Missing ^^^^^^^