Skip to content

Commit ba69f95

Browse files
TomAugspurgerjreback
authored andcommitted
Additional tests for ufunc(Series) (#26951)
1 parent f2aea09 commit ba69f95

File tree

2 files changed

+254
-1
lines changed

2 files changed

+254
-1
lines changed

doc/source/whatsnew/v0.25.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ Sparse
774774
- Bug in :class:`SparseFrame` constructor where passing ``None`` as the data would cause ``default_fill_value`` to be ignored (:issue:`16807`)
775775
- Bug in :class:`SparseDataFrame` when adding a column in which the length of values does not match length of index, ``AssertionError`` is raised instead of raising ``ValueError`` (:issue:`25484`)
776776
- Introduce a better error message in :meth:`Series.sparse.from_coo` so it returns a ``TypeError`` for inputs that are not coo matrices (:issue:`26554`)
777-
- Bug in :func:`numpy.modf` on a :class:`SparseArray`. Now a tuple of :class:`SparseArray` is returned.
777+
- Bug in :func:`numpy.modf` on a :class:`SparseArray`. Now a tuple of :class:`SparseArray` is returned (:issue:`26946`).
778778

779779
Other
780780
^^^^^

pandas/tests/series/test_ufunc.py

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import string
2+
3+
import numpy as np
4+
import pytest
5+
6+
import pandas as pd
7+
import pandas.util.testing as tm
8+
9+
UNARY_UFUNCS = [np.positive, np.floor, np.exp]
10+
BINARY_UFUNCS = [
11+
np.add, # dunder op
12+
np.logaddexp,
13+
]
14+
SPARSE = [
15+
pytest.param(True,
16+
marks=pytest.mark.xfail(reason="Series.__array_ufunc__")),
17+
False,
18+
]
19+
SPARSE_IDS = ['sparse', 'dense']
20+
SHUFFLE = [
21+
pytest.param(True, marks=pytest.mark.xfail(reason="GH-26945",
22+
strict=False)),
23+
False
24+
]
25+
26+
27+
@pytest.fixture
28+
def arrays_for_binary_ufunc():
29+
"""
30+
A pair of random, length-100 integer-dtype arrays, that are mostly 0.
31+
"""
32+
a1 = np.random.randint(0, 10, 100, dtype='int64')
33+
a2 = np.random.randint(0, 10, 100, dtype='int64')
34+
a1[::3] = 0
35+
a2[::4] = 0
36+
return a1, a2
37+
38+
39+
@pytest.mark.parametrize("ufunc", UNARY_UFUNCS)
40+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
41+
def test_unary_ufunc(ufunc, sparse):
42+
# Test that ufunc(Series) == Series(ufunc)
43+
array = np.random.randint(0, 10, 10, dtype='int64')
44+
array[::2] = 0
45+
if sparse:
46+
array = pd.SparseArray(array, dtype=pd.SparseDtype('int', 0))
47+
48+
index = list(string.ascii_letters[:10])
49+
name = "name"
50+
series = pd.Series(array, index=index, name=name)
51+
52+
result = ufunc(series)
53+
expected = pd.Series(ufunc(array), index=index, name=name)
54+
tm.assert_series_equal(result, expected)
55+
56+
57+
@pytest.mark.parametrize("ufunc", BINARY_UFUNCS)
58+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
59+
@pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight'])
60+
def test_binary_ufunc_with_array(flip, sparse, ufunc, arrays_for_binary_ufunc):
61+
# Test that ufunc(Series(a), array) == Series(ufunc(a, b))
62+
a1, a2 = arrays_for_binary_ufunc
63+
if sparse:
64+
a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0))
65+
a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0))
66+
67+
name = "name" # op(Series, array) preserves the name.
68+
series = pd.Series(a1, name=name)
69+
other = a2
70+
71+
array_args = (a1, a2)
72+
series_args = (series, other) # ufunc(series, array)
73+
74+
if flip:
75+
array_args = reversed(array_args)
76+
series_args = reversed(series_args) # ufunc(array, series)
77+
78+
expected = pd.Series(ufunc(*array_args), name=name)
79+
result = ufunc(*series_args)
80+
tm.assert_series_equal(result, expected)
81+
82+
83+
@pytest.mark.parametrize("ufunc", BINARY_UFUNCS)
84+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
85+
@pytest.mark.parametrize("flip", [
86+
pytest.param(True, marks=pytest.mark.xfail(reason="Index should defer")),
87+
False
88+
], ids=['flipped', 'straight'])
89+
def test_binary_ufunc_with_index(flip, sparse, ufunc, arrays_for_binary_ufunc):
90+
# Test that
91+
# * func(Series(a), Series(b)) == Series(ufunc(a, b))
92+
# * ufunc(Index, Series) dispatches to Series (returns a Series)
93+
a1, a2 = arrays_for_binary_ufunc
94+
if sparse:
95+
a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0))
96+
a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0))
97+
98+
name = "name" # op(Series, array) preserves the name.
99+
series = pd.Series(a1, name=name)
100+
other = pd.Index(a2, name=name).astype("int64")
101+
102+
array_args = (a1, a2)
103+
series_args = (series, other) # ufunc(series, array)
104+
105+
if flip:
106+
array_args = reversed(array_args)
107+
series_args = reversed(series_args) # ufunc(array, series)
108+
109+
expected = pd.Series(ufunc(*array_args), name=name)
110+
result = ufunc(*series_args)
111+
tm.assert_series_equal(result, expected)
112+
113+
114+
@pytest.mark.parametrize("ufunc", BINARY_UFUNCS)
115+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
116+
@pytest.mark.parametrize("shuffle", [True, False], ids=['unaligned',
117+
'aligned'])
118+
@pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight'])
119+
def test_binary_ufunc_with_series(flip, shuffle, sparse, ufunc,
120+
arrays_for_binary_ufunc):
121+
# Test that
122+
# * func(Series(a), Series(b)) == Series(ufunc(a, b))
123+
# with alignment between the indices
124+
125+
if flip and shuffle:
126+
pytest.xfail(reason="Fix with Series.__array_ufunc__")
127+
128+
a1, a2 = arrays_for_binary_ufunc
129+
if sparse:
130+
a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0))
131+
a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0))
132+
133+
name = "name" # op(Series, array) preserves the name.
134+
series = pd.Series(a1, name=name)
135+
other = pd.Series(a2, name=name)
136+
137+
idx = np.random.permutation(len(a1))
138+
139+
if shuffle:
140+
other = other.take(idx)
141+
a2 = a2.take(idx)
142+
# alignment, so the expected index is the first index in the op.
143+
if flip:
144+
index = other.align(series)[0].index
145+
else:
146+
index = series.align(other)[0].index
147+
else:
148+
index = series.index
149+
150+
array_args = (a1, a2)
151+
series_args = (series, other) # ufunc(series, array)
152+
153+
if flip:
154+
array_args = tuple(reversed(array_args))
155+
series_args = tuple(reversed(series_args)) # ufunc(array, series)
156+
157+
expected = pd.Series(ufunc(*array_args), index=index, name=name)
158+
result = ufunc(*series_args)
159+
tm.assert_series_equal(result, expected)
160+
161+
162+
@pytest.mark.parametrize("ufunc", BINARY_UFUNCS)
163+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
164+
@pytest.mark.parametrize("flip", [True, False])
165+
def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc):
166+
# Test that
167+
# * ufunc(Series, scalar) == Series(ufunc(array, scalar))
168+
# * ufunc(Series, scalar) == ufunc(scalar, Series)
169+
array, _ = arrays_for_binary_ufunc
170+
if sparse:
171+
array = pd.SparseArray(array)
172+
other = 2
173+
series = pd.Series(array, name="name")
174+
175+
series_args = (series, other)
176+
array_args = (array, other)
177+
178+
if flip:
179+
series_args = tuple(reversed(series_args))
180+
array_args = tuple(reversed(array_args))
181+
182+
expected = pd.Series(ufunc(*array_args), name="name")
183+
result = ufunc(*series_args)
184+
185+
tm.assert_series_equal(result, expected)
186+
187+
188+
@pytest.mark.parametrize("ufunc", [np.divmod]) # any others?
189+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
190+
@pytest.mark.parametrize("shuffle", SHUFFLE)
191+
@pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning")
192+
def test_multiple_ouput_binary_ufuncs(ufunc, sparse, shuffle,
193+
arrays_for_binary_ufunc):
194+
# Test that
195+
# the same conditions from binary_ufunc_scalar apply to
196+
# ufuncs with multiple outputs.
197+
if sparse and ufunc is np.divmod:
198+
pytest.skip("sparse divmod not implemented.")
199+
200+
a1, a2 = arrays_for_binary_ufunc
201+
202+
if sparse:
203+
a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0))
204+
a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0))
205+
206+
s1 = pd.Series(a1)
207+
s2 = pd.Series(a2)
208+
209+
if shuffle:
210+
# ensure we align before applying the ufunc
211+
s2 = s2.sample(frac=1)
212+
213+
expected = ufunc(a1, a2)
214+
assert isinstance(expected, tuple)
215+
216+
result = ufunc(s1, s2)
217+
assert isinstance(result, tuple)
218+
tm.assert_series_equal(result[0], pd.Series(expected[0]))
219+
tm.assert_series_equal(result[1], pd.Series(expected[1]))
220+
221+
222+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
223+
def test_multiple_ouput_ufunc(sparse, arrays_for_binary_ufunc):
224+
# Test that the same conditions from unary input apply to multi-output
225+
# ufuncs
226+
array, _ = arrays_for_binary_ufunc
227+
228+
if sparse:
229+
array = pd.SparseArray(array)
230+
231+
series = pd.Series(array, name="name")
232+
result = np.modf(series)
233+
expected = np.modf(array)
234+
235+
assert isinstance(result, tuple)
236+
assert isinstance(expected, tuple)
237+
238+
tm.assert_series_equal(result[0], pd.Series(expected[0], name="name"))
239+
tm.assert_series_equal(result[1], pd.Series(expected[1], name="name"))
240+
241+
242+
@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS)
243+
@pytest.mark.parametrize("ufunc", BINARY_UFUNCS)
244+
@pytest.mark.xfail(reason="Series.__array_ufunc__")
245+
def test_binary_ufunc_drops_series_name(ufunc, sparse,
246+
arrays_for_binary_ufunc):
247+
# Drop the names when they differ.
248+
a1, a2 = arrays_for_binary_ufunc
249+
s1 = pd.Series(a1, name='a')
250+
s2 = pd.Series(a2, name='b')
251+
252+
result = ufunc(s1, s2)
253+
assert result.name is None

0 commit comments

Comments
 (0)