Skip to content

Commit f558763

Browse files
jbrockmendeljreback
authored andcommitted
TST/REF: parametrize arithmetic tests, simplify parts of core.ops (#26799)
1 parent f0919f2 commit f558763

File tree

5 files changed

+81
-49
lines changed

5 files changed

+81
-49
lines changed

pandas/core/ops.py

+25-28
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ def fill_binop(left, right, fill_value):
10771077
return left, right
10781078

10791079

1080-
def mask_cmp_op(x, y, op, allowed_types):
1080+
def mask_cmp_op(x, y, op):
10811081
"""
10821082
Apply the function `op` to only non-null points in x and y.
10831083
@@ -1086,16 +1086,14 @@ def mask_cmp_op(x, y, op, allowed_types):
10861086
x : array-like
10871087
y : array-like
10881088
op : binary operation
1089-
allowed_types : class or tuple of classes
10901089
10911090
Returns
10921091
-------
10931092
result : ndarray[bool]
10941093
"""
1095-
# TODO: Can we make the allowed_types arg unnecessary?
10961094
xrav = x.ravel()
10971095
result = np.empty(x.size, dtype=bool)
1098-
if isinstance(y, allowed_types):
1096+
if isinstance(y, (np.ndarray, ABCSeries)):
10991097
yrav = y.ravel()
11001098
mask = notna(xrav) & notna(yrav)
11011099
result[mask] = op(np.array(list(xrav[mask])),
@@ -1633,39 +1631,38 @@ def _arith_method_SERIES(cls, op, special):
16331631
if op in [divmod, rdivmod] else _construct_result)
16341632

16351633
def na_op(x, y):
1636-
import pandas.core.computation.expressions as expressions
1637-
try:
1638-
result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs)
1639-
except TypeError:
1640-
result = masked_arith_op(x, y, op)
1641-
1642-
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
1643-
return result
1644-
1645-
def safe_na_op(lvalues, rvalues):
16461634
"""
1647-
return the result of evaluating na_op on the passed in values
1635+
Return the result of evaluating op on the passed in values.
16481636
1649-
try coercion to object type if the native types are not compatible
1637+
If native types are not compatible, try coersion to object dtype.
16501638
16511639
Parameters
16521640
----------
1653-
lvalues : array-like
1654-
rvalues : array-like
1641+
x : array-like
1642+
y : array-like or scalar
1643+
1644+
Returns
1645+
-------
1646+
array-like
16551647
16561648
Raises
16571649
------
1658-
TypeError: invalid operation
1650+
TypeError : invalid operation
16591651
"""
1652+
import pandas.core.computation.expressions as expressions
16601653
try:
1661-
with np.errstate(all='ignore'):
1662-
return na_op(lvalues, rvalues)
1663-
except Exception:
1664-
if is_object_dtype(lvalues):
1665-
return libalgos.arrmap_object(lvalues,
1666-
lambda x: op(x, rvalues))
1654+
result = expressions.evaluate(op, str_rep, x, y, **eval_kwargs)
1655+
except TypeError:
1656+
result = masked_arith_op(x, y, op)
1657+
except Exception: # TODO: more specific?
1658+
if is_object_dtype(x):
1659+
return libalgos.arrmap_object(x,
1660+
lambda val: op(val, y))
16671661
raise
16681662

1663+
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
1664+
return result
1665+
16691666
def wrapper(left, right):
16701667
if isinstance(right, ABCDataFrame):
16711668
return NotImplemented
@@ -1713,7 +1710,8 @@ def wrapper(left, right):
17131710
if isinstance(rvalues, ABCSeries):
17141711
rvalues = rvalues.values
17151712

1716-
result = safe_na_op(lvalues, rvalues)
1713+
with np.errstate(all='ignore'):
1714+
result = na_op(lvalues, rvalues)
17171715
return construct_result(left, result,
17181716
index=left.index, name=res_name, dtype=None)
17191717

@@ -2136,7 +2134,6 @@ def na_op(x, y):
21362134
result = masked_arith_op(x, y, op)
21372135

21382136
result = missing.fill_zeros(result, x, y, op_name, fill_zeros)
2139-
21402137
return result
21412138

21422139
if op_name in _op_descriptions:
@@ -2183,7 +2180,7 @@ def na_op(x, y):
21832180
with np.errstate(invalid='ignore'):
21842181
result = op(x, y)
21852182
except TypeError:
2186-
result = mask_cmp_op(x, y, op, (np.ndarray, ABCSeries))
2183+
result = mask_cmp_op(x, y, op)
21872184
return result
21882185

21892186
doc = _flex_comp_doc_FRAME.format(op_name=op_name,

pandas/tests/arithmetic/test_datetime64.py

+21-11
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ def assert_all(obj):
3737
# ------------------------------------------------------------------
3838
# Comparisons
3939

40+
class TestDatetime64ArrayLikeComparisons:
41+
# Comparison tests for datetime64 vectors fully parametrized over
42+
# DataFrame/Series/DatetimeIndex/DateteimeArray. Ideally all comparison
43+
# tests will eventually end up here.
44+
45+
def test_compare_zerodim(self, tz_naive_fixture, box_with_array):
46+
# Test comparison with zero-dimensional array is unboxed
47+
tz = tz_naive_fixture
48+
box = box_with_array
49+
xbox = box_with_array if box_with_array is not pd.Index else np.ndarray
50+
dti = date_range('20130101', periods=3, tz=tz)
51+
52+
other = np.array(dti.to_numpy()[0])
53+
54+
# FIXME: ValueError with transpose on tzaware
55+
dtarr = tm.box_expected(dti, box, transpose=False)
56+
result = dtarr <= other
57+
expected = np.array([True, False, False])
58+
expected = tm.box_expected(expected, xbox, transpose=False)
59+
tm.assert_equal(result, expected)
60+
4061

4162
class TestDatetime64DataFrameComparison:
4263
@pytest.mark.parametrize('timestamps', [
@@ -339,17 +360,6 @@ def test_comparison_tzawareness_compat(self, op):
339360

340361
class TestDatetimeIndexComparisons:
341362

342-
# TODO: parametrize over box
343-
def test_compare_zerodim(self, tz_naive_fixture):
344-
# Test comparison with zero-dimensional array is unboxed
345-
tz = tz_naive_fixture
346-
dti = date_range('20130101', periods=3, tz=tz)
347-
348-
other = np.array(dti.to_numpy()[0])
349-
result = dti <= other
350-
expected = np.array([True, False, False])
351-
tm.assert_numpy_array_equal(result, expected)
352-
353363
# TODO: moved from tests.indexes.test_base; parametrize and de-duplicate
354364
@pytest.mark.parametrize("op", [
355365
operator.eq, operator.ne, operator.gt, operator.lt,

pandas/tests/arithmetic/test_period.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,27 @@
2020
# Comparisons
2121

2222

23-
class TestPeriodIndexComparisons:
23+
class TestPeriodArrayLikeComparisons:
24+
# Comparison tests for PeriodDtype vectors fully parametrized over
25+
# DataFrame/Series/PeriodIndex/PeriodArray. Ideally all comparison
26+
# tests will eventually end up here.
2427

25-
# TODO: parameterize over boxes
26-
def test_compare_zerodim(self):
28+
def test_compare_zerodim(self, box_with_array):
2729
# GH#26689 make sure we unbox zero-dimensional arrays
30+
xbox = box_with_array if box_with_array is not pd.Index else np.ndarray
31+
2832
pi = pd.period_range('2000', periods=4)
2933
other = np.array(pi.to_numpy()[0])
3034

35+
pi = tm.box_expected(pi, box_with_array)
3136
result = pi <= other
3237
expected = np.array([True, False, False, False])
33-
tm.assert_numpy_array_equal(result, expected)
38+
expected = tm.box_expected(expected, xbox)
39+
tm.assert_equal(result, expected)
40+
41+
42+
class TestPeriodIndexComparisons:
43+
# TODO: parameterize over boxes
3444

3545
@pytest.mark.parametrize("other", ["2017", 2017])
3646
def test_eq(self, other):

pandas/tests/arithmetic/test_timedelta64.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,33 @@ def get_upcast_box(box, vector):
3131
# ------------------------------------------------------------------
3232
# Timedelta64[ns] dtype Comparisons
3333

34-
class TestTimedelta64ArrayComparisons:
35-
# TODO: All of these need to be parametrized over box
34+
class TestTimedelta64ArrayLikeComparisons:
35+
# Comparison tests for timedelta64[ns] vectors fully parametrized over
36+
# DataFrame/Series/TimedeltaIndex/TimedeltaArray. Ideally all comparison
37+
# tests will eventually end up here.
3638

37-
def test_compare_timedelta64_zerodim(self):
39+
def test_compare_timedelta64_zerodim(self, box_with_array):
3840
# GH#26689 should unbox when comparing with zerodim array
41+
box = box_with_array
42+
xbox = box_with_array if box_with_array is not pd.Index else np.ndarray
43+
3944
tdi = pd.timedelta_range('2H', periods=4)
4045
other = np.array(tdi.to_numpy()[0])
4146

47+
tdi = tm.box_expected(tdi, box)
4248
res = tdi <= other
4349
expected = np.array([True, False, False, False])
44-
tm.assert_numpy_array_equal(res, expected)
50+
expected = tm.box_expected(expected, xbox)
51+
tm.assert_equal(res, expected)
4552

4653
with pytest.raises(TypeError):
4754
# zero-dim of wrong dtype should still raise
4855
tdi >= np.array(4)
4956

57+
58+
class TestTimedelta64ArrayComparisons:
59+
# TODO: All of these need to be parametrized over box
60+
5061
def test_compare_timedelta_series(self):
5162
# regression test for GH#5963
5263
s = pd.Series([timedelta(days=1), timedelta(days=2)])

pandas/tests/tseries/offsets/test_offsets_properties.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ def test_on_offset_implementations(dt, offset):
7171
assert offset.onOffset(dt) == (compare == dt)
7272

7373

74-
@pytest.mark.xfail
74+
@pytest.mark.xfail(reason="res_v2 below is incorrect, needs to use the "
75+
"commented-out version with tz_localize. "
76+
"But with that fix in place, hypothesis then "
77+
"has errors in timezone generation.")
7578
@given(gen_yqm_offset, gen_date_range)
7679
def test_apply_index_implementations(offset, rng):
7780
# offset.apply_index(dti)[i] should match dti[i] + offset
@@ -82,6 +85,7 @@ def test_apply_index_implementations(offset, rng):
8285

8386
res = rng + offset
8487
res_v2 = offset.apply_index(rng)
88+
# res_v2 = offset.apply_index(rng.tz_localize(None)).tz_localize(rng.tz)
8589
assert (res == res_v2).all()
8690

8791
assert res[0] == rng[0] + offset
@@ -93,7 +97,7 @@ def test_apply_index_implementations(offset, rng):
9397
# TODO: Check randomly assorted entries, not just first/last
9498

9599

96-
@pytest.mark.xfail
100+
@pytest.mark.xfail # TODO: reason?
97101
@given(gen_yqm_offset)
98102
def test_shift_across_dst(offset):
99103
# GH#18319 check that 1) timezone is correctly normalized and

0 commit comments

Comments
 (0)