Skip to content

Commit 32a9036

Browse files
jbrockmendelluckyvs1
authored andcommitted
TST: implement tm.check_setitem_equivalents (pandas-dev#38688)
1 parent 080fc1d commit 32a9036

File tree

5 files changed

+154
-71
lines changed

5 files changed

+154
-71
lines changed

pandas/_testing/__init__.py

+20
Original file line numberDiff line numberDiff line change
@@ -1581,3 +1581,23 @@ def get_op_from_name(op_name: str) -> Callable:
15811581
op = lambda x, y: rop(y, x)
15821582

15831583
return op
1584+
1585+
1586+
# -----------------------------------------------------------------------------
1587+
# Indexing test helpers
1588+
1589+
1590+
def getitem(x):
1591+
return x
1592+
1593+
1594+
def setitem(x):
1595+
return x
1596+
1597+
1598+
def loc(x):
1599+
return x.loc
1600+
1601+
1602+
def iloc(x):
1603+
return x.iloc

pandas/conftest.py

+16
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,19 @@ def names(request):
14461446
A 3-tuple of names, the first two for operands, the last for a result.
14471447
"""
14481448
return request.param
1449+
1450+
1451+
@pytest.fixture(params=[tm.setitem, tm.loc, tm.iloc])
1452+
def indexer_sli(request):
1453+
"""
1454+
Parametrize over __setitem__, loc.__setitem__, iloc.__setitem__
1455+
"""
1456+
return request.param
1457+
1458+
1459+
@pytest.fixture(params=[tm.setitem, tm.iloc])
1460+
def indexer_si(request):
1461+
"""
1462+
Parametrize over __setitem__, iloc.__setitem__
1463+
"""
1464+
return request.param

pandas/tests/indexing/test_indexing.py

+10-27
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,6 @@
1717

1818
from .test_floats import gen_obj
1919

20-
21-
def getitem(x):
22-
return x
23-
24-
25-
def setitem(x):
26-
return x
27-
28-
29-
def loc(x):
30-
return x.loc
31-
32-
33-
def iloc(x):
34-
return x.iloc
35-
36-
3720
# ------------------------------------------------------------------------
3821
# Indexing test cases
3922

@@ -72,7 +55,7 @@ def test_setitem_ndarray_1d(self):
7255
with pytest.raises(ValueError, match=msg):
7356
df[2:5] = np.arange(1, 4) * 1j
7457

75-
@pytest.mark.parametrize("idxr", [getitem, loc, iloc])
58+
@pytest.mark.parametrize("idxr", [tm.getitem, tm.loc, tm.iloc])
7659
def test_getitem_ndarray_3d(self, index, frame_or_series, idxr):
7760
# GH 25567
7861
obj = gen_obj(frame_or_series, index)
@@ -95,7 +78,7 @@ def test_getitem_ndarray_3d(self, index, frame_or_series, idxr):
9578
with tm.assert_produces_warning(DeprecationWarning, check_stacklevel=False):
9679
idxr[nd3]
9780

98-
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
81+
@pytest.mark.parametrize("indexer", [tm.setitem, tm.loc, tm.iloc])
9982
def test_setitem_ndarray_3d(self, index, frame_or_series, indexer):
10083
# GH 25567
10184
obj = gen_obj(frame_or_series, index)
@@ -297,7 +280,7 @@ def test_dups_fancy_indexing2(self):
297280
result = df.loc[[1, 2], ["a", "b"]]
298281
tm.assert_frame_equal(result, expected)
299282

300-
@pytest.mark.parametrize("case", [getitem, loc])
283+
@pytest.mark.parametrize("case", [tm.getitem, tm.loc])
301284
def test_duplicate_int_indexing(self, case):
302285
# GH 17347
303286
s = Series(range(3), index=[1, 1, 3])
@@ -592,7 +575,7 @@ def test_astype_assignment(self):
592575
expected = DataFrame({"A": [1, 2, 3, 4]})
593576
tm.assert_frame_equal(df, expected)
594577

595-
@pytest.mark.parametrize("indexer", [getitem, loc])
578+
@pytest.mark.parametrize("indexer", [tm.getitem, tm.loc])
596579
def test_index_type_coercion(self, indexer):
597580

598581
# GH 11836
@@ -965,7 +948,7 @@ def test_none_coercion_mixed_dtypes(self):
965948

966949

967950
class TestDatetimelikeCoercion:
968-
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
951+
@pytest.mark.parametrize("indexer", [tm.setitem, tm.loc, tm.iloc])
969952
def test_setitem_dt64_string_scalar(self, tz_naive_fixture, indexer):
970953
# dispatching _can_hold_element to underling DatetimeArray
971954
tz = tz_naive_fixture
@@ -991,12 +974,12 @@ def test_setitem_dt64_string_scalar(self, tz_naive_fixture, indexer):
991974
@pytest.mark.parametrize(
992975
"key", [[0, 1], slice(0, 2), np.array([True, True, False])]
993976
)
994-
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
977+
@pytest.mark.parametrize("indexer", [tm.setitem, tm.loc, tm.iloc])
995978
def test_setitem_dt64_string_values(self, tz_naive_fixture, indexer, key, box):
996979
# dispatching _can_hold_element to underling DatetimeArray
997980
tz = tz_naive_fixture
998981

999-
if isinstance(key, slice) and indexer is loc:
982+
if isinstance(key, slice) and indexer is tm.loc:
1000983
key = slice(0, 1)
1001984

1002985
dti = date_range("2016-01-01", periods=3, tz=tz)
@@ -1017,7 +1000,7 @@ def test_setitem_dt64_string_values(self, tz_naive_fixture, indexer, key, box):
10171000
assert ser._values is values
10181001

10191002
@pytest.mark.parametrize("scalar", ["3 Days", offsets.Hour(4)])
1020-
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
1003+
@pytest.mark.parametrize("indexer", [tm.setitem, tm.loc, tm.iloc])
10211004
def test_setitem_td64_scalar(self, indexer, scalar):
10221005
# dispatching _can_hold_element to underling TimedeltaArray
10231006
tdi = timedelta_range("1 Day", periods=3)
@@ -1033,10 +1016,10 @@ def test_setitem_td64_scalar(self, indexer, scalar):
10331016
@pytest.mark.parametrize(
10341017
"key", [[0, 1], slice(0, 2), np.array([True, True, False])]
10351018
)
1036-
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
1019+
@pytest.mark.parametrize("indexer", [tm.setitem, tm.loc, tm.iloc])
10371020
def test_setitem_td64_string_values(self, indexer, key, box):
10381021
# dispatching _can_hold_element to underling TimedeltaArray
1039-
if isinstance(key, slice) and indexer is loc:
1022+
if isinstance(key, slice) and indexer is tm.loc:
10401023
key = slice(0, 1)
10411024

10421025
tdi = timedelta_range("1 Day", periods=3)

pandas/tests/series/indexing/test_indexing.py

-26
Original file line numberDiff line numberDiff line change
@@ -271,32 +271,6 @@ def test_setitem(datetime_series, string_series):
271271
tm.assert_series_equal(s, expected)
272272

273273

274-
def test_setitem_dtypes():
275-
# change dtypes
276-
# GH 4463
277-
expected = Series([np.nan, 2, 3])
278-
279-
s = Series([1, 2, 3])
280-
s.iloc[0] = np.nan
281-
tm.assert_series_equal(s, expected)
282-
283-
s = Series([1, 2, 3])
284-
s.loc[0] = np.nan
285-
tm.assert_series_equal(s, expected)
286-
287-
s = Series([1, 2, 3])
288-
s[0] = np.nan
289-
tm.assert_series_equal(s, expected)
290-
291-
s = Series([False])
292-
s.loc[0] = np.nan
293-
tm.assert_series_equal(s, Series([np.nan]))
294-
295-
s = Series([False, True])
296-
s.loc[0] = np.nan
297-
tm.assert_series_equal(s, Series([np.nan, 1.0]))
298-
299-
300274
def test_setslice(datetime_series):
301275
sl = datetime_series[5:20]
302276
assert len(sl) == len(sl.index)

pandas/tests/series/indexing/test_setitem.py

+108-18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pandas import (
77
DatetimeIndex,
8+
Index,
89
MultiIndex,
910
NaT,
1011
Series,
@@ -238,24 +239,113 @@ def test_setitem_callable_other(self):
238239
tm.assert_series_equal(ser, expected)
239240

240241

241-
class TestSetitemCasting:
242-
def test_setitem_nan_casts(self):
243-
# these induce dtype changes
244-
expected = Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan])
245-
ser = Series([2, 3, 4, 5, 6, 7, 8, 9, 10])
246-
ser[::2] = np.nan
247-
tm.assert_series_equal(ser, expected)
248-
249-
# gets coerced to float, right?
250-
expected = Series([np.nan, 1, np.nan, 0])
251-
ser = Series([True, True, False, False])
252-
ser[::2] = np.nan
253-
tm.assert_series_equal(ser, expected)
254-
255-
expected = Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9])
256-
ser = Series(np.arange(10))
257-
ser[:5] = np.nan
258-
tm.assert_series_equal(ser, expected)
242+
@pytest.mark.parametrize(
243+
"obj,expected,key",
244+
[
245+
(
246+
# these induce dtype changes
247+
Series([2, 3, 4, 5, 6, 7, 8, 9, 10]),
248+
Series([np.nan, 3, np.nan, 5, np.nan, 7, np.nan, 9, np.nan]),
249+
slice(None, None, 2),
250+
),
251+
(
252+
# gets coerced to float, right?
253+
Series([True, True, False, False]),
254+
Series([np.nan, 1, np.nan, 0]),
255+
slice(None, None, 2),
256+
),
257+
(
258+
# these induce dtype changes
259+
Series(np.arange(10)),
260+
Series([np.nan, np.nan, np.nan, np.nan, np.nan, 5, 6, 7, 8, 9]),
261+
slice(None, 5),
262+
),
263+
(
264+
# changes dtype GH#4463
265+
Series([1, 2, 3]),
266+
Series([np.nan, 2, 3]),
267+
0,
268+
),
269+
(
270+
# changes dtype GH#4463
271+
Series([False]),
272+
Series([np.nan]),
273+
0,
274+
),
275+
(
276+
# changes dtype GH#4463
277+
Series([False, True]),
278+
Series([np.nan, 1.0]),
279+
0,
280+
),
281+
],
282+
)
283+
class TestSetitemCastingEquivalents:
284+
"""
285+
Check each of several methods that _should_ be equivalent to `obj[key] = np.nan`
286+
287+
We assume that
288+
- obj.index is the default Index(range(len(obj)))
289+
- the setitem does not expand the obj
290+
"""
291+
292+
def test_int_key(self, obj, key, expected, indexer_sli):
293+
if not isinstance(key, int):
294+
return
295+
296+
obj = obj.copy()
297+
indexer_sli(obj)[key] = np.nan
298+
tm.assert_series_equal(obj, expected)
299+
300+
def test_slice_key(self, obj, key, expected, indexer_si):
301+
# Note: no .loc because that handles slice edges differently
302+
obj = obj.copy()
303+
indexer_si(obj)[key] = np.nan
304+
tm.assert_series_equal(obj, expected)
305+
306+
def test_intlist_key(self, obj, key, expected, indexer_sli):
307+
ilkey = list(range(len(obj)))[key]
308+
309+
obj = obj.copy()
310+
indexer_sli(obj)[ilkey] = np.nan
311+
tm.assert_series_equal(obj, expected)
312+
313+
def test_mask_key(self, obj, key, expected, indexer_sli):
314+
# setitem with boolean mask
315+
mask = np.zeros(obj.shape, dtype=bool)
316+
mask[key] = True
317+
318+
obj = obj.copy()
319+
indexer_sli(obj)[mask] = np.nan
320+
tm.assert_series_equal(obj, expected)
321+
322+
def test_series_where(self, obj, key, expected):
323+
mask = np.zeros(obj.shape, dtype=bool)
324+
mask[key] = True
325+
326+
obj = obj.copy()
327+
res = obj.where(~mask, np.nan)
328+
tm.assert_series_equal(res, expected)
329+
330+
def test_index_where(self, obj, key, expected, request):
331+
if obj.dtype == bool:
332+
msg = "Index/Series casting behavior inconsistent GH#38692"
333+
mark = pytest.xfail(reason=msg)
334+
request.node.add_marker(mark)
335+
336+
mask = np.zeros(obj.shape, dtype=bool)
337+
mask[key] = True
338+
339+
res = Index(obj).where(~mask, np.nan)
340+
tm.assert_index_equal(res, Index(expected))
341+
342+
@pytest.mark.xfail(reason="Index/Series casting behavior inconsistent GH#38692")
343+
def test_index_putmask(self, obj, key, expected):
344+
mask = np.zeros(obj.shape, dtype=bool)
345+
mask[key] = True
346+
347+
res = Index(obj).putmask(mask, np.nan)
348+
tm.assert_index_equal(res, Index(expected))
259349

260350

261351
class TestSetitemWithExpansion:

0 commit comments

Comments
 (0)