diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst index f6ad3a800283d..9c78c8b2cbebc 100644 --- a/doc/source/whatsnew/v1.1.0.rst +++ b/doc/source/whatsnew/v1.1.0.rst @@ -13,6 +13,15 @@ including other versions of pandas. Enhancements ~~~~~~~~~~~~ +.. _whatsnew_110.specify_missing_labels: + +KeyErrors raised by loc specify missing labels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Previously, if labels were missing for a loc call, a KeyError was raised stating that this was no longer supported. + +Now the error message also includes a list of the missing labels (max 10 items, display width 80 characters). See :issue:`34272`. + + .. _whatsnew_110.astype_string: All dtypes can now be converted to ``StringDtype`` diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 9c8b01003bece..3cf20b68c84f4 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2,6 +2,8 @@ import numpy as np +from pandas._config.config import option_context + from pandas._libs.indexing import _NDFrameIndexerBase from pandas._libs.lib import item_from_zerodim from pandas.errors import AbstractMethodError, InvalidIndexError @@ -1283,7 +1285,8 @@ def _validate_read_indexer( return # Count missing values: - missing = (indexer < 0).sum() + missing_mask = indexer < 0 + missing = (missing_mask).sum() if missing: if missing == len(indexer): @@ -1302,11 +1305,15 @@ def _validate_read_indexer( # code, so we want to avoid warning & then # just raising if not ax.is_categorical(): - raise KeyError( - "Passing list-likes to .loc or [] with any missing labels " - "is no longer supported, see " - "https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike" # noqa:E501 - ) + not_found = key[missing_mask] + + with option_context("display.max_seq_items", 10, "display.width", 80): + raise KeyError( + "Passing list-likes to .loc or [] with any missing labels " + "is no longer supported. " + f"The following labels were missing: {not_found}. " + "See https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike" # noqa:E501 + ) @doc(IndexingMixin.iloc) diff --git a/pandas/tests/indexing/test_indexing.py b/pandas/tests/indexing/test_indexing.py index 5c0230e75021c..b77c47f927517 100644 --- a/pandas/tests/indexing/test_indexing.py +++ b/pandas/tests/indexing/test_indexing.py @@ -1075,3 +1075,32 @@ def test_setitem_with_bool_mask_and_values_matching_n_trues_in_length(): result = ser expected = pd.Series([None] * 3 + list(range(5)) + [None] * 2).astype("object") tm.assert_series_equal(result, expected) + + +def test_missing_labels_inside_loc_matched_in_error_message(): + # GH34272 + s = pd.Series({"a": 1, "b": 2, "c": 3}) + error_message_regex = "missing_0.*missing_1.*missing_2" + with pytest.raises(KeyError, match=error_message_regex): + s.loc[["a", "b", "missing_0", "c", "missing_1", "missing_2"]] + + +def test_many_missing_labels_inside_loc_error_message_limited(): + # GH34272 + n = 10000 + missing_labels = [f"missing_{label}" for label in range(n)] + s = pd.Series({"a": 1, "b": 2, "c": 3}) + # regex checks labels between 4 and 9995 are replaced with ellipses + error_message_regex = "missing_4.*\\.\\.\\..*missing_9995" + with pytest.raises(KeyError, match=error_message_regex): + s.loc[["a", "c"] + missing_labels] + + +def test_long_text_missing_labels_inside_loc_error_message_limited(): + # GH34272 + s = pd.Series({"a": 1, "b": 2, "c": 3}) + missing_labels = [f"long_missing_label_text_{i}" * 5 for i in range(3)] + # regex checks for very long labels there are new lines between each + error_message_regex = "long_missing_label_text_0.*\\\\n.*long_missing_label_text_1" + with pytest.raises(KeyError, match=error_message_regex): + s.loc[["a", "c"] + missing_labels]