Skip to content

Commit 4612312

Browse files
TST: restructure internal extension arrays tests (split between /arrays and /extension) (#22026)
1 parent 46abe18 commit 4612312

File tree

10 files changed

+413
-313
lines changed

10 files changed

+413
-313
lines changed

pandas/tests/extension/integer/test_integer.py renamed to pandas/tests/arrays/test_integer.py

+64-244
Large diffs are not rendered by default.

pandas/tests/arrays/test_interval.py

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# -*- coding: utf-8 -*-
2+
import pytest
3+
import numpy as np
4+
5+
from pandas import Index, IntervalIndex, date_range, timedelta_range
6+
from pandas.core.arrays import IntervalArray
7+
import pandas.util.testing as tm
8+
9+
10+
@pytest.fixture(params=[
11+
(Index([0, 2, 4]), Index([1, 3, 5])),
12+
(Index([0., 1., 2.]), Index([1., 2., 3.])),
13+
(timedelta_range('0 days', periods=3),
14+
timedelta_range('1 day', periods=3)),
15+
(date_range('20170101', periods=3), date_range('20170102', periods=3)),
16+
(date_range('20170101', periods=3, tz='US/Eastern'),
17+
date_range('20170102', periods=3, tz='US/Eastern'))],
18+
ids=lambda x: str(x[0].dtype))
19+
def left_right_dtypes(request):
20+
"""
21+
Fixture for building an IntervalArray from various dtypes
22+
"""
23+
return request.param
24+
25+
26+
class TestMethods(object):
27+
28+
@pytest.mark.parametrize('repeats', [0, 1, 5])
29+
def test_repeat(self, left_right_dtypes, repeats):
30+
left, right = left_right_dtypes
31+
result = IntervalArray.from_arrays(left, right).repeat(repeats)
32+
expected = IntervalArray.from_arrays(
33+
left.repeat(repeats), right.repeat(repeats))
34+
tm.assert_extension_array_equal(result, expected)
35+
36+
@pytest.mark.parametrize('bad_repeats, msg', [
37+
(-1, 'negative dimensions are not allowed'),
38+
('foo', r'invalid literal for (int|long)\(\) with base 10')])
39+
def test_repeat_errors(self, bad_repeats, msg):
40+
array = IntervalArray.from_breaks(range(4))
41+
with tm.assert_raises_regex(ValueError, msg):
42+
array.repeat(bad_repeats)
43+
44+
@pytest.mark.parametrize('new_closed', [
45+
'left', 'right', 'both', 'neither'])
46+
def test_set_closed(self, closed, new_closed):
47+
# GH 21670
48+
array = IntervalArray.from_breaks(range(10), closed=closed)
49+
result = array.set_closed(new_closed)
50+
expected = IntervalArray.from_breaks(range(10), closed=new_closed)
51+
tm.assert_extension_array_equal(result, expected)
52+
53+
54+
class TestSetitem(object):
55+
56+
def test_set_na(self, left_right_dtypes):
57+
left, right = left_right_dtypes
58+
result = IntervalArray.from_arrays(left, right)
59+
result[0] = np.nan
60+
61+
expected_left = Index([left._na_value] + list(left[1:]))
62+
expected_right = Index([right._na_value] + list(right[1:]))
63+
expected = IntervalArray.from_arrays(expected_left, expected_right)
64+
65+
tm.assert_extension_array_equal(result, expected)
66+
67+
68+
def test_repr_matches():
69+
idx = IntervalIndex.from_breaks([1, 2, 3])
70+
a = repr(idx)
71+
b = repr(idx.values)
72+
assert a.replace("Index", "Array") == b

pandas/tests/extension/base/methods.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,11 @@ def test_combine_add(self, data_repeated):
127127
s1 = pd.Series(orig_data1)
128128
s2 = pd.Series(orig_data2)
129129
result = s1.combine(s2, lambda x1, x2: x1 + x2)
130-
expected = pd.Series(
131-
orig_data1._from_sequence([a + b for (a, b) in
132-
zip(list(orig_data1),
133-
list(orig_data2))]))
130+
with np.errstate(over='ignore'):
131+
expected = pd.Series(
132+
orig_data1._from_sequence([a + b for (a, b) in
133+
zip(list(orig_data1),
134+
list(orig_data2))]))
134135
self.assert_series_equal(result, expected)
135136

136137
val = s1.iloc[0]

pandas/tests/extension/base/ops.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ def get_op_from_name(self, op_name):
2323
def check_opname(self, s, op_name, other, exc=NotImplementedError):
2424
op = self.get_op_from_name(op_name)
2525

26-
self._check_op(s, op, other, exc)
26+
self._check_op(s, op, other, op_name, exc)
2727

28-
def _check_op(self, s, op, other, exc=NotImplementedError):
28+
def _check_op(self, s, op, other, op_name, exc=NotImplementedError):
2929
if exc is None:
3030
result = op(s, other)
3131
expected = s.combine(other, op)
@@ -69,7 +69,8 @@ def test_arith_series_with_array(self, data, all_arithmetic_operators):
6969
# ndarray & other series
7070
op_name = all_arithmetic_operators
7171
s = pd.Series(data)
72-
self.check_opname(s, op_name, [s.iloc[0]] * len(s), exc=TypeError)
72+
self.check_opname(s, op_name, pd.Series([s.iloc[0]] * len(s)),
73+
exc=TypeError)
7374

7475
def test_divmod(self, data):
7576
s = pd.Series(data)
@@ -113,5 +114,5 @@ def test_compare_scalar(self, data, all_compare_operators):
113114
def test_compare_array(self, data, all_compare_operators):
114115
op_name = all_compare_operators
115116
s = pd.Series(data)
116-
other = [0] * len(data)
117+
other = pd.Series([data[0]] * len(data))
117118
self._compare_other(s, data, op_name, other)

pandas/tests/extension/category/__init__.py

Whitespace-only changes.

pandas/tests/extension/integer/__init__.py

Whitespace-only changes.

pandas/tests/extension/interval/__init__.py

Whitespace-only changes.

pandas/tests/extension/category/test_categorical.py renamed to pandas/tests/extension/test_categorical.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""
2+
This file contains a minimal set of tests for compliance with the extension
3+
array interface test suite, and should contain no other tests.
4+
The test suite for the full functionality of the array is located in
5+
`pandas/tests/arrays/`.
6+
7+
The tests in this file are inherited from the BaseExtensionTests, and only
8+
minimal tweaks should be applied to get the tests passing (by overwriting a
9+
parent method).
10+
11+
Additional tests should either be added to one of the BaseExtensionTests
12+
classes (if they are relevant for the extension interface for all dtypes), or
13+
be added to the array-specific tests in `pandas/tests/arrays/`.
14+
15+
"""
116
import string
217

318
import pytest
@@ -204,10 +219,14 @@ class TestComparisonOps(base.BaseComparisonOpsTests):
204219
def _compare_other(self, s, data, op_name, other):
205220
op = self.get_op_from_name(op_name)
206221
if op_name == '__eq__':
207-
assert not op(data, other).all()
222+
result = op(s, other)
223+
expected = s.combine(other, lambda x, y: x == y)
224+
assert (result == expected).all()
208225

209226
elif op_name == '__ne__':
210-
assert op(data, other).all()
227+
result = op(s, other)
228+
expected = s.combine(other, lambda x, y: x != y)
229+
assert (result == expected).all()
211230

212231
else:
213232
with pytest.raises(TypeError):
+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
"""
2+
This file contains a minimal set of tests for compliance with the extension
3+
array interface test suite, and should contain no other tests.
4+
The test suite for the full functionality of the array is located in
5+
`pandas/tests/arrays/`.
6+
7+
The tests in this file are inherited from the BaseExtensionTests, and only
8+
minimal tweaks should be applied to get the tests passing (by overwriting a
9+
parent method).
10+
11+
Additional tests should either be added to one of the BaseExtensionTests
12+
classes (if they are relevant for the extension interface for all dtypes), or
13+
be added to the array-specific tests in `pandas/tests/arrays/`.
14+
15+
"""
16+
import numpy as np
17+
import pandas as pd
18+
import pytest
19+
20+
from pandas.tests.extension import base
21+
from pandas.core.dtypes.common import is_extension_array_dtype
22+
23+
from pandas.core.arrays import IntegerArray, integer_array
24+
from pandas.core.arrays.integer import (
25+
Int8Dtype, Int16Dtype, Int32Dtype, Int64Dtype,
26+
UInt8Dtype, UInt16Dtype, UInt32Dtype, UInt64Dtype)
27+
28+
29+
def make_data():
30+
return (list(range(1, 9)) + [np.nan] + list(range(10, 98))
31+
+ [np.nan] + [99, 100])
32+
33+
34+
@pytest.fixture(params=[Int8Dtype, Int16Dtype, Int32Dtype, Int64Dtype,
35+
UInt8Dtype, UInt16Dtype, UInt32Dtype, UInt64Dtype])
36+
def dtype(request):
37+
return request.param()
38+
39+
40+
@pytest.fixture
41+
def data(dtype):
42+
return integer_array(make_data(), dtype=dtype)
43+
44+
45+
@pytest.fixture
46+
def data_missing(dtype):
47+
return integer_array([np.nan, 1], dtype=dtype)
48+
49+
50+
@pytest.fixture
51+
def data_repeated(data):
52+
def gen(count):
53+
for _ in range(count):
54+
yield data
55+
yield gen
56+
57+
58+
@pytest.fixture
59+
def data_for_sorting(dtype):
60+
return integer_array([1, 2, 0], dtype=dtype)
61+
62+
63+
@pytest.fixture
64+
def data_missing_for_sorting(dtype):
65+
return integer_array([1, np.nan, 0], dtype=dtype)
66+
67+
68+
@pytest.fixture
69+
def na_cmp():
70+
# we are np.nan
71+
return lambda x, y: np.isnan(x) and np.isnan(y)
72+
73+
74+
@pytest.fixture
75+
def na_value():
76+
return np.nan
77+
78+
79+
@pytest.fixture
80+
def data_for_grouping(dtype):
81+
b = 1
82+
a = 0
83+
c = 2
84+
na = np.nan
85+
return integer_array([b, b, na, na, a, a, b, c], dtype=dtype)
86+
87+
88+
class TestDtype(base.BaseDtypeTests):
89+
90+
@pytest.mark.skip(reason="using multiple dtypes")
91+
def test_is_dtype_unboxes_dtype(self):
92+
# we have multiple dtypes, so skip
93+
pass
94+
95+
def test_array_type_with_arg(self, data, dtype):
96+
assert dtype.construct_array_type() is IntegerArray
97+
98+
99+
class TestArithmeticOps(base.BaseArithmeticOpsTests):
100+
101+
def check_opname(self, s, op_name, other, exc=None):
102+
# overwriting to indicate ops don't raise an error
103+
super(TestArithmeticOps, self).check_opname(s, op_name,
104+
other, exc=None)
105+
106+
def _check_op(self, s, op, other, op_name, exc=NotImplementedError):
107+
if exc is None:
108+
if s.dtype.is_unsigned_integer and (op_name == '__rsub__'):
109+
# TODO see https://github.com/pandas-dev/pandas/issues/22023
110+
pytest.skip("unsigned subtraction gives negative values")
111+
112+
if (hasattr(other, 'dtype')
113+
and not is_extension_array_dtype(other.dtype)
114+
and pd.api.types.is_integer_dtype(other.dtype)):
115+
# other is np.int64 and would therefore always result in
116+
# upcasting, so keeping other as same numpy_dtype
117+
other = other.astype(s.dtype.numpy_dtype)
118+
119+
result = op(s, other)
120+
expected = s.combine(other, op)
121+
122+
if op_name == '__rdiv__':
123+
# combine is not giving the correct result for this case
124+
pytest.skip("skipping reverse div in python 2")
125+
elif op_name in ('__rtruediv__', '__truediv__', '__div__'):
126+
expected = expected.astype(float)
127+
if op_name == '__rtruediv__':
128+
# TODO reverse operators result in object dtype
129+
result = result.astype(float)
130+
elif op_name.startswith('__r'):
131+
# TODO reverse operators result in object dtype
132+
# see https://github.com/pandas-dev/pandas/issues/22024
133+
expected = expected.astype(s.dtype)
134+
result = result.astype(s.dtype)
135+
else:
136+
# combine method result in 'biggest' (int64) dtype
137+
expected = expected.astype(s.dtype)
138+
pass
139+
if (op_name == '__rpow__') and isinstance(other, pd.Series):
140+
# TODO pow on Int arrays gives different result with NA
141+
# see https://github.com/pandas-dev/pandas/issues/22022
142+
result = result.fillna(1)
143+
144+
self.assert_series_equal(result, expected)
145+
else:
146+
with pytest.raises(exc):
147+
op(s, other)
148+
149+
def _check_divmod_op(self, s, op, other, exc=None):
150+
super(TestArithmeticOps, self)._check_divmod_op(s, op, other, None)
151+
152+
@pytest.mark.skip(reason="intNA does not error on ops")
153+
def test_error(self, data, all_arithmetic_operators):
154+
# other specific errors tested in the integer array specific tests
155+
pass
156+
157+
158+
class TestComparisonOps(base.BaseComparisonOpsTests):
159+
160+
def check_opname(self, s, op_name, other, exc=None):
161+
super(TestComparisonOps, self).check_opname(s, op_name,
162+
other, exc=None)
163+
164+
def _compare_other(self, s, data, op_name, other):
165+
self.check_opname(s, op_name, other)
166+
167+
168+
class TestInterface(base.BaseInterfaceTests):
169+
pass
170+
171+
172+
class TestConstructors(base.BaseConstructorsTests):
173+
pass
174+
175+
176+
class TestReshaping(base.BaseReshapingTests):
177+
pass
178+
179+
# for test_concat_mixed_dtypes test
180+
# concat of an Integer and Int coerces to object dtype
181+
# TODO(jreback) once integrated this would
182+
183+
184+
class TestGetitem(base.BaseGetitemTests):
185+
pass
186+
187+
188+
class TestMissing(base.BaseMissingTests):
189+
pass
190+
191+
192+
class TestMethods(base.BaseMethodsTests):
193+
194+
@pytest.mark.parametrize('dropna', [True, False])
195+
def test_value_counts(self, all_data, dropna):
196+
all_data = all_data[:10]
197+
if dropna:
198+
other = np.array(all_data[~all_data.isna()])
199+
else:
200+
other = all_data
201+
202+
result = pd.Series(all_data).value_counts(dropna=dropna).sort_index()
203+
expected = pd.Series(other).value_counts(
204+
dropna=dropna).sort_index()
205+
expected.index = expected.index.astype(all_data.dtype)
206+
207+
self.assert_series_equal(result, expected)
208+
209+
210+
class TestCasting(base.BaseCastingTests):
211+
pass
212+
213+
214+
class TestGroupby(base.BaseGroupbyTests):
215+
216+
@pytest.mark.xfail(reason="groupby not working", strict=True)
217+
def test_groupby_extension_no_sort(self, data_for_grouping):
218+
super(TestGroupby, self).test_groupby_extension_no_sort(
219+
data_for_grouping)
220+
221+
@pytest.mark.parametrize('as_index', [
222+
pytest.param(True,
223+
marks=pytest.mark.xfail(reason="groupby not working",
224+
strict=True)),
225+
False
226+
])
227+
def test_groupby_extension_agg(self, as_index, data_for_grouping):
228+
super(TestGroupby, self).test_groupby_extension_agg(
229+
as_index, data_for_grouping)

0 commit comments

Comments
 (0)