Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion pandas/core/dtypes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1386,7 +1386,7 @@ def is_bool_dtype(arr_or_dtype) -> bool:
# we don't have a boolean Index class
# so its object, we need to infer to
# guess this
return arr_or_dtype.is_object and arr_or_dtype.inferred_type == "boolean"
return arr_or_dtype.is_object() and arr_or_dtype.inferred_type == "boolean"
elif is_extension_array_dtype(arr_or_dtype):
return getattr(dtype, "_is_boolean", False)

Expand Down
13 changes: 9 additions & 4 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,12 +961,17 @@ class _LocIndexer(_LocationIndexer):

@doc(_LocationIndexer._validate_key)
def _validate_key(self, key, axis: int):

# valid for a collection of labels (we check their presence later)
# slice of labels (where start-end in labels)
# slice of integers (only if in the labels)
# boolean
pass
# boolean not in slice and with boolean index
if not is_bool_dtype(self.obj.index) and isinstance(key, bool):
raise TypeError("Boolean label can not be used without a boolean index")

if isinstance(key, slice) and (
isinstance(key.start, bool) or isinstance(key.stop, bool)
):
raise TypeError("Boolean values can not be used in a slice")

def _has_valid_setitem_indexer(self, indexer) -> bool:
return True
Expand Down Expand Up @@ -2366,7 +2371,7 @@ def convert_missing_indexer(indexer):
indexer = indexer["key"]

if isinstance(indexer, bool):
raise KeyError("cannot use a single bool to index into setitem")
raise TypeError("cannot use a single bool to index into setitem")
return indexer, True

return indexer, False
Expand Down
10 changes: 5 additions & 5 deletions pandas/tests/frame/indexing/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,16 +947,16 @@ def test_getitem_ix_boolean_duplicates_multiple(self):
def test_getitem_setitem_ix_bool_keyerror(self):
# #2199
df = DataFrame({"a": [1, 2, 3]})

with pytest.raises(KeyError, match=r"^False$"):
message = "Boolean label can not be used without a boolean index"
with pytest.raises(TypeError, match=message):
df.loc[False]
with pytest.raises(KeyError, match=r"^True$"):
with pytest.raises(TypeError, match=message):
df.loc[True]

msg = "cannot use a single bool to index into setitem"
with pytest.raises(KeyError, match=msg):
with pytest.raises(TypeError, match=msg):
df.loc[False] = 0
with pytest.raises(KeyError, match=msg):
with pytest.raises(TypeError, match=msg):
df.loc[True] = 0

# TODO: rename? remove?
Expand Down
57 changes: 57 additions & 0 deletions pandas/tests/indexing/test_loc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
DatetimeIndex,
Index,
IndexSlice,
Int64Index,
MultiIndex,
Period,
RangeIndex,
Series,
SparseDtype,
Timedelta,
Timestamp,
UInt64Index,
date_range,
timedelta_range,
to_datetime,
Expand Down Expand Up @@ -2085,6 +2088,60 @@ def test_loc_getitem_slice_columns_mixed_dtype(self):
tm.assert_frame_equal(df.loc[:, 1:], expected)


class TestLocBooleanLabelsAndSlices(Base):
@pytest.mark.parametrize(
"index",
[
RangeIndex(4),
Int64Index(range(4)),
UInt64Index(range(4)),
pd.Float64Index(range(4)),
CategoricalIndex(range(4)),
date_range(0, periods=4, freq="ns"),
timedelta_range(0, periods=4, freq="ns"),
pd.interval_range(0, periods=4),
Index([0, 1, 2, 3], dtype=object),
MultiIndex.from_product([[0, 1], [0, 1]]),
pd.period_range("2018Q1", freq="Q", periods=4),
],
)
def test_loc_bool_incompatible_index_raises(self, index, frame_or_series):
# GH20432
message = "Boolean label can not be used without a boolean index"
obj = frame_or_series(range(4), index=index)
with pytest.raises(TypeError, match=message):
obj.loc[True]

@pytest.mark.parametrize("index", [Index([True, False], dtype="boolean")])
def test_loc_bool_should_not_raise(self, index, frame_or_series):
obj = frame_or_series(range(2), index=index)
obj.loc[True]

@pytest.mark.parametrize(
"index",
[
RangeIndex(4),
Int64Index(range(4)),
UInt64Index(range(4)),
pd.Float64Index(range(4)),
CategoricalIndex(range(4)),
date_range(0, periods=4, freq="ns"),
timedelta_range(0, periods=4, freq="ns"),
pd.interval_range(0, periods=4),
Index([0, 1, 2, 3], dtype=object),
Index([True, True, False, False], dtype=object),
MultiIndex.from_product([[0, 1], [0, 1]]),
pd.period_range("2018Q1", freq="Q", periods=4),
],
)
def test_loc_bool_slice_raises(self, index, frame_or_series):
# GH20432
message = "Boolean values can not be used in a slice"
obj = frame_or_series(range(4), index=index)
with pytest.raises(TypeError, match=message):
obj.loc[True:False]


class TestLocBooleanMask:
def test_loc_setitem_bool_mask_timedeltaindex(self):
# GH#14946
Expand Down