Skip to content

BUG: fix .loc.__setitem__ not raising when using too many indexers #44656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.5.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ Indexing
- Bug in :meth:`DataFrame.iloc` where indexing a single row on a :class:`DataFrame` with a single ExtensionDtype column gave a copy instead of a view on the underlying data (:issue:`45241`)
- Bug in setting a NA value (``None`` or ``np.nan``) into a :class:`Series` with int-based :class:`IntervalDtype` incorrectly casting to object dtype instead of a float-based :class:`IntervalDtype` (:issue:`45568`)
- Bug in :meth:`Series.__setitem__` with a non-integer :class:`Index` when using an integer key to set a value that cannot be set inplace where a ``ValueError`` was raised insead of casting to a common dtype (:issue:`45070`)
- Bug in :meth:`Series.loc.__setitem__` and :meth:`Series.loc.__getitem__` not raising when using multiple keys without using a :class:`MultiIndex` (:issue:`13831`)
- Bug when setting a value too large for a :class:`Series` dtype failing to coerce to a common type (:issue:`26049`, :issue:`32878`)
- Bug in :meth:`loc.__setitem__` treating ``range`` keys as positional instead of label-based (:issue:`45479`)
- Bug in :meth:`Series.__setitem__` when setting ``boolean`` dtype values containing ``NA`` incorrectly raising instead of casting to ``boolean`` dtype (:issue:`45462`)
Expand Down
8 changes: 8 additions & 0 deletions pandas/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1734,6 +1734,14 @@ def indexer_sli(request):
return request.param


@pytest.fixture(params=[tm.loc, tm.iloc])
def indexer_li(request):
"""
Parametrize over loc.__getitem__, iloc.__getitem__
"""
return request.param


@pytest.fixture(params=[tm.setitem, tm.iloc])
def indexer_si(request):
"""
Expand Down
12 changes: 10 additions & 2 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -934,7 +934,7 @@ def _getitem_nested_tuple(self, tup: tuple):
# we are only getting non-hashable tuples, in particular ones
# that themselves contain a slice entry
# See test_loc_series_getitem_too_many_dimensions
raise ValueError("Too many indices")
raise IndexingError("Too many indexers")

# this is a series with a multi-index specified a tuple of
# selectors
Expand Down Expand Up @@ -1260,6 +1260,14 @@ def _convert_to_indexer(self, key, axis: int):
is_int_index = labels.is_integer()
is_int_positional = is_integer(key) and not is_int_index

if (
isinstance(key, tuple)
and not isinstance(labels, MultiIndex)
and self.ndim < 2
and len(key) > 1
):
raise IndexingError("Too many indexers")

if is_scalar(key) or (isinstance(labels, MultiIndex) and is_hashable(key)):
# Otherwise get_loc will raise InvalidIndexError

Expand Down Expand Up @@ -1291,7 +1299,7 @@ def _convert_to_indexer(self, key, axis: int):
if is_nested_tuple(key, labels):
if self.ndim == 1 and any(isinstance(k, tuple) for k in key):
# GH#35349 Raise if tuple in tuple for series
raise ValueError("Too many indices")
raise IndexingError("Too many indexers")
return labels.get_locs(key)

elif is_list_like_indexer(key):
Expand Down
29 changes: 29 additions & 0 deletions pandas/tests/indexing/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
import pandas._testing as tm
from pandas.core.api import Float64Index
from pandas.core.indexing import IndexingError
from pandas.tests.indexing.common import _mklbl
from pandas.tests.indexing.test_floats import gen_obj

Expand Down Expand Up @@ -981,3 +982,31 @@ def test_extension_array_cross_section_converts():

result = df.iloc[0]
tm.assert_series_equal(result, expected)


@pytest.mark.parametrize(
"ser, keys",
[(Series([10]), (0, 0)), (Series([1, 2, 3], index=list("abc")), (0, 1))],
)
def test_ser_tup_indexer_exceeds_dimensions(ser, keys, indexer_li):
# GH#13831
exp_err, exp_msg = IndexingError, "Too many indexers"
with pytest.raises(exp_err, match=exp_msg):
indexer_li(ser)[keys]

if indexer_li == tm.iloc:
# For iloc.__setitem__ we let numpy handle the error reporting.
exp_err, exp_msg = IndexError, "too many indices for array"

with pytest.raises(exp_err, match=exp_msg):
indexer_li(ser)[keys] = 0


def test_ser_list_indexer_exceeds_dimensions(indexer_li):
# GH#13831
# Make sure an exception is raised when a tuple exceeds the dimension of the series,
# but not list when a list is used.
ser = Series([10])
res = indexer_li(ser)[[0, 0]]
exp = Series([10, 10], index=Index([0, 0]))
tm.assert_series_equal(res, exp)
18 changes: 15 additions & 3 deletions pandas/tests/indexing/test_loc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2704,6 +2704,18 @@ def test_loc_with_period_index_indexer():
tm.assert_frame_equal(df, df.loc[list(idx)])


def test_loc_setitem_multiindex_timestamp():
# GH#13831
vals = np.random.randn(8, 6)
idx = date_range("1/1/2000", periods=8)
cols = ["A", "B", "C", "D", "E", "F"]
exp = DataFrame(vals, index=idx, columns=cols)
exp.loc[exp.index[1], ("A", "B")] = np.nan
vals[1][0:2] = np.nan
res = DataFrame(vals, index=idx, columns=cols)
tm.assert_frame_equal(res, exp)


def test_loc_getitem_multiindex_tuple_level():
# GH#27591
lev1 = ["a", "b", "c"]
Expand Down Expand Up @@ -2959,11 +2971,11 @@ def test_loc_series_getitem_too_many_dimensions(self, indexer):
index=MultiIndex.from_tuples([("A", "0"), ("A", "1"), ("B", "0")]),
data=[21, 22, 23],
)
msg = "Too many indices"
with pytest.raises(ValueError, match=msg):
msg = "Too many indexers"
with pytest.raises(IndexingError, match=msg):
ser.loc[indexer, :]

with pytest.raises(ValueError, match=msg):
with pytest.raises(IndexingError, match=msg):
ser.loc[indexer, :] = 1

def test_loc_setitem(self, string_series):
Expand Down