From 37c02468e29fec259ff02eccd7a40c1074140984 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 5 Feb 2020 13:49:47 -0800 Subject: [PATCH 1/3] API/BUG: make .at raise same exceptions as .loc --- doc/source/whatsnew/v1.1.0.rst | 3 ++- pandas/core/indexing.py | 20 +++++------------ pandas/tests/indexing/test_scalar.py | 32 ++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 64f0cb3f2e26d..864a5c129e2b7 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -63,7 +63,8 @@ Backwards incompatible API changes - :meth:`DataFrameGroupby.mean` and :meth:`SeriesGroupby.mean` (and similarly for :meth:`~DataFrameGroupby.median`, :meth:`~DataFrameGroupby.std`` and :meth:`~DataFrameGroupby.var``) now raise a ``TypeError`` if a not-accepted keyword argument is passed into it. Previously a ``UnsupportedFunctionCall`` was raised (``AssertionError`` if ``min_count`` passed into :meth:`~DataFrameGroupby.median``) (:issue:`31485`) - +- :meth:`DataFrame.at` and :meth:`Series.at` will raise a ``TypeError`` instead of a ``ValueError`` if an incompatible key is passed, matching the behavior of ``.loc[]`` (:issue:`31722`) +- .. --------------------------------------------------------------------------- diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index acbf05a74d118..4969403c8ba40 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2100,21 +2100,11 @@ def _convert_key(self, key, is_setter: bool = False): if is_setter: return list(key) - for ax, i in zip(self.obj.axes, key): - if ax.is_integer(): - if not is_integer(i): - raise ValueError( - "At based indexing on an integer index " - "can only have integer indexers" - ) - else: - if is_integer(i) and not (ax.holds_integer() or ax.is_floating()): - raise ValueError( - "At based indexing on an non-integer " - "index can only have non-integer " - "indexers" - ) - return key + lkey = list(key) + for n, (ax, i) in enumerate(zip(self.obj.axes, key)): + lkey[n] = ax._convert_scalar_indexer(i, kind="loc") + + return tuple(lkey) @Appender(IndexingMixin.iat.__doc__) diff --git a/pandas/tests/indexing/test_scalar.py b/pandas/tests/indexing/test_scalar.py index 9e6446ebc8de7..cfa25cee1c6db 100644 --- a/pandas/tests/indexing/test_scalar.py +++ b/pandas/tests/indexing/test_scalar.py @@ -136,30 +136,48 @@ def test_at_to_fail(self): result = s.at["a"] assert result == 1 msg = ( - "At based indexing on an non-integer index can only have " - "non-integer indexers" + "cannot do label indexing on " + r"with these indexers \[0\] of " ) - with pytest.raises(ValueError, match=msg): + with pytest.raises(TypeError, match=msg): s.at[0] + with pytest.raises(TypeError, match=msg): + # .at should match .loc + s.loc[0] df = DataFrame({"A": [1, 2, 3]}, index=list("abc")) result = df.at["a", "A"] assert result == 1 - with pytest.raises(ValueError, match=msg): + with pytest.raises(TypeError, match=msg): df.at["a", 0] + with pytest.raises(TypeError, match=msg): + # .at should match .loc + df.loc["a", 0] s = Series([1, 2, 3], index=[3, 2, 1]) result = s.at[1] assert result == 3 - msg = "At based indexing on an integer index can only have integer indexers" - with pytest.raises(ValueError, match=msg): + + with pytest.raises(KeyError, match="a"): s.at["a"] + with pytest.raises(KeyError, match="a"): + # .at should match .loc + s.loc["a"] df = DataFrame({0: [1, 2, 3]}, index=[3, 2, 1]) result = df.at[1, 0] assert result == 3 - with pytest.raises(ValueError, match=msg): + with pytest.raises(KeyError, match="a"): df.at["a", 0] + with pytest.raises(KeyError, match="a"): + # .at should match .loc + df.loc["a", 0] + + with pytest.raises(KeyError, match="a"): + df.at[1, "a"] + with pytest.raises(KeyError, match="a"): + # .at should match .loc + df.loc[1, "a"] # GH 13822, incorrect error string with non-unique columns when missing # column is accessed From 630b874c7abfdf5f47ffb751b65c121f9ba15056 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 5 Feb 2020 17:50:43 -0800 Subject: [PATCH 2/3] add KeyError to whatsnew --- doc/source/whatsnew/v1.1.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index 864a5c129e2b7..aea5695a96388 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -63,7 +63,7 @@ Backwards incompatible API changes - :meth:`DataFrameGroupby.mean` and :meth:`SeriesGroupby.mean` (and similarly for :meth:`~DataFrameGroupby.median`, :meth:`~DataFrameGroupby.std`` and :meth:`~DataFrameGroupby.var``) now raise a ``TypeError`` if a not-accepted keyword argument is passed into it. Previously a ``UnsupportedFunctionCall`` was raised (``AssertionError`` if ``min_count`` passed into :meth:`~DataFrameGroupby.median``) (:issue:`31485`) -- :meth:`DataFrame.at` and :meth:`Series.at` will raise a ``TypeError`` instead of a ``ValueError`` if an incompatible key is passed, matching the behavior of ``.loc[]`` (:issue:`31722`) +- :meth:`DataFrame.at` and :meth:`Series.at` will raise a ``TypeError`` instead of a ``ValueError`` if an incompatible key is passed, and ``KeyError`` if a missing key is passed, matching the behavior of ``.loc[]`` (:issue:`31722`) - .. --------------------------------------------------------------------------- From 37feaf6329f3d350a09a3e58e6aca4cccc1d486b Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 5 Feb 2020 18:00:15 -0800 Subject: [PATCH 3/3] targetd tests --- pandas/tests/indexing/test_scalar.py | 49 ++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/pandas/tests/indexing/test_scalar.py b/pandas/tests/indexing/test_scalar.py index cfa25cee1c6db..312a0c6531cfb 100644 --- a/pandas/tests/indexing/test_scalar.py +++ b/pandas/tests/indexing/test_scalar.py @@ -129,56 +129,79 @@ def test_imethods_with_dups(self): result = df.iat[2, 0] assert result == 2 - def test_at_to_fail(self): + def test_series_at_raises_type_error(self): # at should not fallback # GH 7814 - s = Series([1, 2, 3], index=list("abc")) - result = s.at["a"] + # GH#31724 .at should match .loc + ser = Series([1, 2, 3], index=list("abc")) + result = ser.at["a"] assert result == 1 + result = ser.loc["a"] + assert result == 1 + msg = ( "cannot do label indexing on " r"with these indexers \[0\] of " ) with pytest.raises(TypeError, match=msg): - s.at[0] + ser.at[0] with pytest.raises(TypeError, match=msg): - # .at should match .loc - s.loc[0] + ser.loc[0] + def test_frame_raises_type_error(self): + # GH#31724 .at should match .loc df = DataFrame({"A": [1, 2, 3]}, index=list("abc")) result = df.at["a", "A"] assert result == 1 + result = df.loc["a", "A"] + assert result == 1 + + msg = ( + "cannot do label indexing on " + r"with these indexers \[0\] of " + ) with pytest.raises(TypeError, match=msg): df.at["a", 0] with pytest.raises(TypeError, match=msg): - # .at should match .loc df.loc["a", 0] - s = Series([1, 2, 3], index=[3, 2, 1]) - result = s.at[1] + def test_series_at_raises_key_error(self): + # GH#31724 .at should match .loc + + ser = Series([1, 2, 3], index=[3, 2, 1]) + result = ser.at[1] + assert result == 3 + result = ser.loc[1] assert result == 3 with pytest.raises(KeyError, match="a"): - s.at["a"] + ser.at["a"] with pytest.raises(KeyError, match="a"): # .at should match .loc - s.loc["a"] + ser.loc["a"] + + def test_frame_at_raises_key_error(self): + # GH#31724 .at should match .loc df = DataFrame({0: [1, 2, 3]}, index=[3, 2, 1]) + result = df.at[1, 0] assert result == 3 + result = df.loc[1, 0] + assert result == 3 + with pytest.raises(KeyError, match="a"): df.at["a", 0] with pytest.raises(KeyError, match="a"): - # .at should match .loc df.loc["a", 0] with pytest.raises(KeyError, match="a"): df.at[1, "a"] with pytest.raises(KeyError, match="a"): - # .at should match .loc df.loc[1, "a"] + # TODO: belongs somewhere else? + def test_getitem_list_missing_key(self): # GH 13822, incorrect error string with non-unique columns when missing # column is accessed df = DataFrame({"x": [1.0], "y": [2.0], "z": [3.0]})