From 0d6a663ff896b4eca24cbbce3a433ed2de15cbcd Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 19 Jun 2019 09:52:24 -0500 Subject: [PATCH 1/9] TST: Additional tests for Series ufuncs This adds a set of tests for ufuncs on Series. The goal is to establish the correct behavior prior to implementing `Series.__array_ufunc__`. There are two kinds of xfails right now 1. Series[Sparse] fails because `Series.__array_ufunc__` doesn't yet dispatch to `Series.array.__array_ufunc__` 2. `ufunc(series, series)` when the two series are unaligned. It's been determined that these should align, but isn't currently implemented. --- pandas/tests/series/test_ufunc.py | 152 ++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 pandas/tests/series/test_ufunc.py diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py new file mode 100644 index 0000000000000..406fb950b2bf8 --- /dev/null +++ b/pandas/tests/series/test_ufunc.py @@ -0,0 +1,152 @@ +import string + +import numpy as np +import pytest + +import pandas as pd +import pandas.util.testing as tm + +UNARY_UFUNCS = [np.positive, np.floor, np.exp] +BINARY_UFUNCS = [np.add, np.logaddexp] # -> dunder op +SPARSE = [ + pytest.param(True, + marks=pytest.mark.xfail(reason="Series.__array_ufunc__")), + False, +] +SPARSE_IDS = ['sparse', 'dense'] +SHUFFLE = [ + pytest.param(True, marks=pytest.mark.xfail(reason="GH-26945")), + False +] + + +@pytest.fixture +def arrays_for_binary_ufunc(): + """ + A pair of random, length-100 integer-dtype arrays, that are mostly 0. + """ + a1 = np.random.randint(0, 10, 100) + a2 = np.random.randint(0, 10, 100) + a1[::3] = 0 + a2[::4] = 0 + return a1, a2 + + +@pytest.mark.parametrize("ufunc", UNARY_UFUNCS) +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +def test_unary_ufunc(ufunc, sparse): + array = np.random.randint(0, 10, 10) + array[::2] = 0 + if sparse: + array = pd.SparseArray(array, dtype=pd.SparseDtype('int', 0)) + + index = list(string.ascii_letters[:10]) + name = "name" + series = pd.Series(array, index=index, name=name) + + result = ufunc(series) + expected = pd.Series(ufunc(array), index=index, name=name) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ufunc", BINARY_UFUNCS) +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("shuffle", SHUFFLE) +@pytest.mark.parametrize("box_other", [True, False]) +def test_binary_ufunc(ufunc, sparse, shuffle, box_other, + arrays_for_binary_ufunc): + # Check the invariant that + # ufunc(Series(a), Series(b)) == Series(ufunc(a, b)) + # with alignment. + a1, a2 = arrays_for_binary_ufunc + if sparse: + a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0)) + a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) + + name = "name" + # TODO: verify name when the differ? Take the first? Drop? + s1 = pd.Series(a1, name=name) + s2 = pd.Series(a2, name=name) + + # handle shufling / alignment + # If boxing -- ufunc(series, series) -- then we don't need to shuffle + # the other array for the expected, since we align. + # If not boxing -- ufunc(series, array) -- then we do need to shuffle + # the other array, since we *dont'* align + idx = np.random.permutation(len(s1)) + if box_other and shuffle: + # ensure we align before applying the ufunc + s2 = s2.take(idx) + elif shuffle: + a2 = a2.take(idx) + + result = ufunc(s1, s2) + expected = pd.Series(ufunc(a1, a2), name=name) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ufunc", BINARY_UFUNCS) +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("flip", [True, False]) +def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc): + array, _ = arrays_for_binary_ufunc + if sparse: + array = pd.SparseArray(array) + other = 2 + series = pd.Series(array, name="name") + + a, b = series, other + c, d = array, other + if flip: + c, d = b, c + a, b = b, a + + expected = pd.Series(ufunc(a, b), name="name") + result = pd.Series(ufunc(c, d), name="name") + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ufunc", [np.divmod]) # any others? +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("shuffle", SHUFFLE) +@pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning") +def test_multiple_ouput_binary_ufuncs(ufunc, sparse, shuffle, + arrays_for_binary_ufunc): + a1, a2 = arrays_for_binary_ufunc + + if sparse: + a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0)) + a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) + + s1 = pd.Series(a1) + s2 = pd.Series(a2) + + if shuffle: + # ensure we align before applying the ufunc + s2 = s2.sample(frac=1) + + expected = ufunc(a1, a2) + assert isinstance(expected, tuple) + + result = ufunc(s1, s2) + assert isinstance(result, tuple) + tm.assert_series_equal(result[0], pd.Series(expected[0])) + tm.assert_series_equal(result[1], pd.Series(expected[1])) + + +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +def test_multiple_ouput_ufunc(sparse, arrays_for_binary_ufunc): + array, _ = arrays_for_binary_ufunc + + if sparse: + array = pd.SparseArray(array) + + series = pd.Series(array, name="name") + result = np.modf(series) + expected = np.modf(array) + + assert isinstance(result, tuple) + assert isinstance(expected, tuple) + + tm.assert_series_equal(result[0], pd.Series(expected[0], name="name")) + tm.assert_series_equal(result[1], pd.Series(expected[1], name="name")) From 8f46391a0ce923010fcc5ab78268efd3181a13cf Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 19 Jun 2019 13:00:55 -0500 Subject: [PATCH 2/9] fixup release note --- doc/source/whatsnew/v0.25.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index e6bc422b52e89..a11e6dce7a9a3 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -769,7 +769,7 @@ Sparse - Bug in :class:`SparseFrame` constructor where passing ``None`` as the data would cause ``default_fill_value`` to be ignored (:issue:`16807`) - 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`) - 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`) -- Bug in :func:`numpy.modf` on a :class:`SparseArray`. Now a tuple of :class:`SparseArray` is returned. +- Bug in :func:`numpy.modf` on a :class:`SparseArray`. Now a tuple of :class:`SparseArray` is returned (:issue:`26946`). Other ^^^^^ From 44e3c7ebe49839028443c2bdf05b7613f40a889a Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 19 Jun 2019 14:33:08 -0500 Subject: [PATCH 3/9] fixups --- pandas/tests/series/test_ufunc.py | 35 +++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index 406fb950b2bf8..c45a5144d45e8 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -7,7 +7,10 @@ import pandas.util.testing as tm UNARY_UFUNCS = [np.positive, np.floor, np.exp] -BINARY_UFUNCS = [np.add, np.logaddexp] # -> dunder op +BINARY_UFUNCS = [ + np.add, # dunder op + np.logaddexp, +] SPARSE = [ pytest.param(True, marks=pytest.mark.xfail(reason="Series.__array_ufunc__")), @@ -52,8 +55,12 @@ def test_unary_ufunc(ufunc, sparse): @pytest.mark.parametrize("ufunc", BINARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) @pytest.mark.parametrize("shuffle", SHUFFLE) -@pytest.mark.parametrize("box_other", [True, False]) +@pytest.mark.parametrize("box_other", [True, False], + ids=['other-boxed', 'other-raw']) +@pytest.mark.parametrize("flip", [True, False], + ids=['flipped', 'straight']) def test_binary_ufunc(ufunc, sparse, shuffle, box_other, + flip, arrays_for_binary_ufunc): # Check the invariant that # ufunc(Series(a), Series(b)) == Series(ufunc(a, b)) @@ -80,8 +87,15 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, elif shuffle: a2 = a2.take(idx) - result = ufunc(s1, s2) - expected = pd.Series(ufunc(a1, a2), name=name) + a, b = s1, s2 + c, d = a1, a2 + + if flip: + a, b = b, a + c, d = d, c + + result = ufunc(a, b) + expected = pd.Series(ufunc(c, d), name=name) tm.assert_series_equal(result, expected) @@ -150,3 +164,16 @@ def test_multiple_ouput_ufunc(sparse, arrays_for_binary_ufunc): tm.assert_series_equal(result[0], pd.Series(expected[0], name="name")) tm.assert_series_equal(result[1], pd.Series(expected[1], name="name")) + + +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("ufunc", BINARY_UFUNCS) +@pytest.mark.xfail(reason="Series.__array_ufunc__") +def test_binary_ufunc_drops_series_name(ufunc, sparse, + arrays_for_binary_ufunc): + a1, a2 = arrays_for_binary_ufunc + s1 = pd.Series(a1, name='a') + s2 = pd.Series(a2, name='b') + + result = ufunc(s1, s2) + assert result.name is None From e1799138e7bcb2b6907b25ee47ac78d687251181 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 19 Jun 2019 16:07:31 -0500 Subject: [PATCH 4/9] remove stale comment --- pandas/tests/series/test_ufunc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index c45a5144d45e8..beb29d4ee81d3 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -71,7 +71,6 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) name = "name" - # TODO: verify name when the differ? Take the first? Drop? s1 = pd.Series(a1, name=name) s2 = pd.Series(a2, name=name) @@ -171,6 +170,7 @@ def test_multiple_ouput_ufunc(sparse, arrays_for_binary_ufunc): @pytest.mark.xfail(reason="Series.__array_ufunc__") def test_binary_ufunc_drops_series_name(ufunc, sparse, arrays_for_binary_ufunc): + # Drop the names when they differ. a1, a2 = arrays_for_binary_ufunc s1 = pd.Series(a1, name='a') s2 = pd.Series(a2, name='b') From 0b1e745c05af118849a05e3112ec86d6eb244e7a Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 20 Jun 2019 11:45:02 -0500 Subject: [PATCH 5/9] xfail ufunc(series, index) --- pandas/tests/series/test_ufunc.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index beb29d4ee81d3..d230c1e93e9ac 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -55,8 +55,7 @@ def test_unary_ufunc(ufunc, sparse): @pytest.mark.parametrize("ufunc", BINARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) @pytest.mark.parametrize("shuffle", SHUFFLE) -@pytest.mark.parametrize("box_other", [True, False], - ids=['other-boxed', 'other-raw']) +@pytest.mark.parametrize("box_other", ['series', 'index', 'raw']) @pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight']) def test_binary_ufunc(ufunc, sparse, shuffle, box_other, @@ -72,7 +71,13 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, name = "name" s1 = pd.Series(a1, name=name) - s2 = pd.Series(a2, name=name) + if box_other == 'series': + s2 = pd.Series(a2, name=name) + elif box_other == 'index': + # Index should defer to Series + s2 = pd.Index(a2, naame=name) + else: + s2 = a2 # handle shufling / alignment # If boxing -- ufunc(series, series) -- then we don't need to shuffle @@ -95,6 +100,8 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, result = ufunc(a, b) expected = pd.Series(ufunc(c, d), name=name) + if box_other == 'index' and flip: + raise pytest.xfail("Index should defer to Series") tm.assert_series_equal(result, expected) From 9be1dfff323a30283c330e3f2438a033ecee0542 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 20 Jun 2019 13:50:41 -0500 Subject: [PATCH 6/9] 32-bit compat (cherry picked from commit ba5cb55e511ea484bf4a63d4c489767e5bed87a2) --- pandas/tests/series/test_ufunc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index d230c1e93e9ac..5695028a0fad0 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -28,8 +28,8 @@ def arrays_for_binary_ufunc(): """ A pair of random, length-100 integer-dtype arrays, that are mostly 0. """ - a1 = np.random.randint(0, 10, 100) - a2 = np.random.randint(0, 10, 100) + a1 = np.random.randint(0, 10, 100, dtype='int64') + a2 = np.random.randint(0, 10, 100, dtype='int64') a1[::3] = 0 a2[::4] = 0 return a1, a2 @@ -38,7 +38,7 @@ def arrays_for_binary_ufunc(): @pytest.mark.parametrize("ufunc", UNARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) def test_unary_ufunc(ufunc, sparse): - array = np.random.randint(0, 10, 10) + array = np.random.randint(0, 10, 10, dtype='int64') array[::2] = 0 if sparse: array = pd.SparseArray(array, dtype=pd.SparseDtype('int', 0)) From 775c2efde0f84dc7f1a2c214bc6d8a0bd21f1d4d Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 20 Jun 2019 15:03:24 -0500 Subject: [PATCH 7/9] fixup --- pandas/tests/series/test_ufunc.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index 5695028a0fad0..f91d648464a3d 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -18,7 +18,8 @@ ] SPARSE_IDS = ['sparse', 'dense'] SHUFFLE = [ - pytest.param(True, marks=pytest.mark.xfail(reason="GH-26945")), + pytest.param(True, marks=pytest.mark.xfail(reason="GH-26945", + strict=False)), False ] @@ -79,17 +80,15 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, else: s2 = a2 - # handle shufling / alignment - # If boxing -- ufunc(series, series) -- then we don't need to shuffle - # the other array for the expected, since we align. - # If not boxing -- ufunc(series, array) -- then we do need to shuffle - # the other array, since we *dont'* align idx = np.random.permutation(len(s1)) - if box_other and shuffle: - # ensure we align before applying the ufunc + + if shuffle: s2 = s2.take(idx) - elif shuffle: - a2 = a2.take(idx) + if box_other != 'series': + # when other is a Series, we align, so we don't + # need to shuffle the array for expected. In all + # other cases, we do. + a2 = a2.take(idx) a, b = s1, s2 c, d = a1, a2 From 64d8908f1a67780d3d952acab676a99c3d4c02dc Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 21 Jun 2019 09:20:20 -0500 Subject: [PATCH 8/9] more --- pandas/tests/series/test_ufunc.py | 83 +++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index f91d648464a3d..2e9e331c7542f 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -53,10 +53,71 @@ def test_unary_ufunc(ufunc, sparse): tm.assert_series_equal(result, expected) +@pytest.mark.parametrize("ufunc", BINARY_UFUNCS) +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight']) +def test_binary_ufunc_with_array(flip, sparse, ufunc, arrays_for_binary_ufunc): + # Test that ufunc(Series(a), array) == Series(ufunc(a, b)) + a1, a2 = arrays_for_binary_ufunc + if sparse: + a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0)) + a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) + + name = "name" # op(Series, array) preserves the name. + series = pd.Series(a1, name=name) + other = a2 + + array_args = (a1, a2) + series_args = (series, other) # ufunc(series, array) + + if flip: + array_args = reversed(array_args) + series_args = reversed(series_args) # ufunc(array, series) + + expected = pd.Series(ufunc(*array_args), name=name) + result = ufunc(*series_args) + tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("ufunc", BINARY_UFUNCS) +@pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) +@pytest.mark.parametrize("flip", [ + pytest.param(True, marks=pytest.mark.xfail(reason="Index should defer")), + False +], ids=['flipped', 'straight']) +def test_binary_ufunc_with_index(flip, sparse, ufunc, arrays_for_binary_ufunc): + # Test that + # * func(Series(a), Series(b)) == Series(ufunc(a, b)) + # * ufunc(Index, Series) dispatches to Series (returns a Series) + a1, a2 = arrays_for_binary_ufunc + if sparse: + a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0)) + a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) + + name = "name" # op(Series, array) preserves the name. + series = pd.Series(a1, name=name) + other = pd.Index(a2, name=name).astype("int64") + + array_args = (a1, a2) + series_args = (series, other) # ufunc(series, array) + + if flip: + array_args = reversed(array_args) + series_args = reversed(series_args) # ufunc(array, series) + + expected = pd.Series(ufunc(*array_args), name=name) + result = ufunc(*series_args) + tm.assert_series_equal(result, expected) + + +def test_binary_ufunc_with_series(): + pass + + @pytest.mark.parametrize("ufunc", BINARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) @pytest.mark.parametrize("shuffle", SHUFFLE) -@pytest.mark.parametrize("box_other", ['series', 'index', 'raw']) +@pytest.mark.parametrize("box_other", ['series', 'index', 'ndarray']) @pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight']) def test_binary_ufunc(ufunc, sparse, shuffle, box_other, @@ -76,7 +137,9 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, s2 = pd.Series(a2, name=name) elif box_other == 'index': # Index should defer to Series - s2 = pd.Index(a2, naame=name) + # astype for https://github.com/pandas-dev/pandas/issues/26972 + s2 = pd.Index(a2, name=name).astype('int64') + else: s2 = a2 @@ -98,7 +161,13 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, c, d = d, c result = ufunc(a, b) - expected = pd.Series(ufunc(c, d), name=name) + if shuffle and box_other != 'series': + index = s1.index + else: + # shuffle & union or no alignment + index = np.arange(len(s1)) + + expected = pd.Series(ufunc(c, d), name=name, index=index) if box_other == 'index' and flip: raise pytest.xfail("Index should defer to Series") tm.assert_series_equal(result, expected) @@ -120,8 +189,9 @@ def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc): c, d = b, c a, b = b, a - expected = pd.Series(ufunc(a, b), name="name") - result = pd.Series(ufunc(c, d), name="name") + expected = pd.Series(ufunc(c, d), name="name") + result = ufunc(a, b) + tm.assert_series_equal(result, expected) @@ -131,6 +201,9 @@ def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc): @pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning") def test_multiple_ouput_binary_ufuncs(ufunc, sparse, shuffle, arrays_for_binary_ufunc): + if sparse and ufunc is np.divmod: + pytest.skip("sparse divmod not implemented.") + a1, a2 = arrays_for_binary_ufunc if sparse: From d1788b01c0be9de6b1a8ed71f47004a860322a2b Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Fri, 21 Jun 2019 10:15:58 -0500 Subject: [PATCH 9/9] lint --- pandas/tests/series/test_ufunc.py | 99 +++++++++++++++---------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index 2e9e331c7542f..05d19452b1eac 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -39,6 +39,7 @@ def arrays_for_binary_ufunc(): @pytest.mark.parametrize("ufunc", UNARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) def test_unary_ufunc(ufunc, sparse): + # Test that ufunc(Series) == Series(ufunc) array = np.random.randint(0, 10, 10, dtype='int64') array[::2] = 0 if sparse: @@ -110,66 +111,51 @@ def test_binary_ufunc_with_index(flip, sparse, ufunc, arrays_for_binary_ufunc): tm.assert_series_equal(result, expected) -def test_binary_ufunc_with_series(): - pass - - @pytest.mark.parametrize("ufunc", BINARY_UFUNCS) @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) -@pytest.mark.parametrize("shuffle", SHUFFLE) -@pytest.mark.parametrize("box_other", ['series', 'index', 'ndarray']) -@pytest.mark.parametrize("flip", [True, False], - ids=['flipped', 'straight']) -def test_binary_ufunc(ufunc, sparse, shuffle, box_other, - flip, - arrays_for_binary_ufunc): - # Check the invariant that - # ufunc(Series(a), Series(b)) == Series(ufunc(a, b)) - # with alignment. +@pytest.mark.parametrize("shuffle", [True, False], ids=['unaligned', + 'aligned']) +@pytest.mark.parametrize("flip", [True, False], ids=['flipped', 'straight']) +def test_binary_ufunc_with_series(flip, shuffle, sparse, ufunc, + arrays_for_binary_ufunc): + # Test that + # * func(Series(a), Series(b)) == Series(ufunc(a, b)) + # with alignment between the indices + + if flip and shuffle: + pytest.xfail(reason="Fix with Series.__array_ufunc__") + a1, a2 = arrays_for_binary_ufunc if sparse: a1 = pd.SparseArray(a1, dtype=pd.SparseDtype('int', 0)) a2 = pd.SparseArray(a2, dtype=pd.SparseDtype('int', 0)) - name = "name" - s1 = pd.Series(a1, name=name) - if box_other == 'series': - s2 = pd.Series(a2, name=name) - elif box_other == 'index': - # Index should defer to Series - # astype for https://github.com/pandas-dev/pandas/issues/26972 - s2 = pd.Index(a2, name=name).astype('int64') - - else: - s2 = a2 + name = "name" # op(Series, array) preserves the name. + series = pd.Series(a1, name=name) + other = pd.Series(a2, name=name) - idx = np.random.permutation(len(s1)) + idx = np.random.permutation(len(a1)) if shuffle: - s2 = s2.take(idx) - if box_other != 'series': - # when other is a Series, we align, so we don't - # need to shuffle the array for expected. In all - # other cases, we do. - a2 = a2.take(idx) + other = other.take(idx) + a2 = a2.take(idx) + # alignment, so the expected index is the first index in the op. + if flip: + index = other.align(series)[0].index + else: + index = series.align(other)[0].index + else: + index = series.index - a, b = s1, s2 - c, d = a1, a2 + array_args = (a1, a2) + series_args = (series, other) # ufunc(series, array) if flip: - a, b = b, a - c, d = d, c - - result = ufunc(a, b) - if shuffle and box_other != 'series': - index = s1.index - else: - # shuffle & union or no alignment - index = np.arange(len(s1)) + array_args = tuple(reversed(array_args)) + series_args = tuple(reversed(series_args)) # ufunc(array, series) - expected = pd.Series(ufunc(c, d), name=name, index=index) - if box_other == 'index' and flip: - raise pytest.xfail("Index should defer to Series") + expected = pd.Series(ufunc(*array_args), index=index, name=name) + result = ufunc(*series_args) tm.assert_series_equal(result, expected) @@ -177,20 +163,24 @@ def test_binary_ufunc(ufunc, sparse, shuffle, box_other, @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) @pytest.mark.parametrize("flip", [True, False]) def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc): + # Test that + # * ufunc(Series, scalar) == Series(ufunc(array, scalar)) + # * ufunc(Series, scalar) == ufunc(scalar, Series) array, _ = arrays_for_binary_ufunc if sparse: array = pd.SparseArray(array) other = 2 series = pd.Series(array, name="name") - a, b = series, other - c, d = array, other + series_args = (series, other) + array_args = (array, other) + if flip: - c, d = b, c - a, b = b, a + series_args = tuple(reversed(series_args)) + array_args = tuple(reversed(array_args)) - expected = pd.Series(ufunc(c, d), name="name") - result = ufunc(a, b) + expected = pd.Series(ufunc(*array_args), name="name") + result = ufunc(*series_args) tm.assert_series_equal(result, expected) @@ -201,6 +191,9 @@ def test_binary_ufunc_scalar(ufunc, sparse, flip, arrays_for_binary_ufunc): @pytest.mark.filterwarnings("ignore:divide by zero:RuntimeWarning") def test_multiple_ouput_binary_ufuncs(ufunc, sparse, shuffle, arrays_for_binary_ufunc): + # Test that + # the same conditions from binary_ufunc_scalar apply to + # ufuncs with multiple outputs. if sparse and ufunc is np.divmod: pytest.skip("sparse divmod not implemented.") @@ -228,6 +221,8 @@ def test_multiple_ouput_binary_ufuncs(ufunc, sparse, shuffle, @pytest.mark.parametrize("sparse", SPARSE, ids=SPARSE_IDS) def test_multiple_ouput_ufunc(sparse, arrays_for_binary_ufunc): + # Test that the same conditions from unary input apply to multi-output + # ufuncs array, _ = arrays_for_binary_ufunc if sparse: