Skip to content

API/BUG: Fix Series ops inconsistencies #13894

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 25, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions doc/source/whatsnew/v0.19.0.txt
Original file line number Diff line number Diff line change
@@ -475,6 +475,143 @@ New Behavior:

type(s.tolist()[0])

.. _whatsnew_0190.api.series_ops:

``Series`` operators for different indexes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Following ``Series`` operators has been changed to make all operators consistent,
including ``DataFrame`` (:issue:`1134`, :issue:`4581`, :issue:`13538`)

- ``Series`` comparison operators now raise ``ValueError`` when ``index`` are different.
- ``Series`` logical operators align both ``index``.

.. warning::
Until 0.18.1, comparing ``Series`` with the same length has been succeeded even if
these ``index`` are different (the result ignores ``index``). As of 0.19.0, it raises ``ValueError`` to be more strict. This section also describes how to keep previous behaviour or align different indexes using flexible comparison methods like ``.eq``.


As a result, ``Series`` and ``DataFrame`` operators behave as below:

Arithmetic operators
""""""""""""""""""""

Arithmetic operators align both ``index`` (no changes).

.. ipython:: python

s1 = pd.Series([1, 2, 3], index=list('ABC'))
s2 = pd.Series([2, 2, 2], index=list('ABD'))
s1 + s2

df1 = pd.DataFrame([1, 2, 3], index=list('ABC'))
df2 = pd.DataFrame([2, 2, 2], index=list('ABD'))
df1 + df2

Comparison operators
""""""""""""""""""""

Comparison operators raise ``ValueError`` when ``index`` are different.

Previous Behavior (``Series``):

``Series`` compares values ignoring ``index`` as long as both lengthes are the same.

.. code-block:: ipython

In [1]: s1 == s2
Out[1]:
A False
B True
C False
dtype: bool

New Behavior (``Series``):

.. code-block:: ipython

In [2]: s1 == s2
Out[2]:
ValueError: Can only compare identically-labeled Series objects

.. note::
To achieve the same result as previous versions (compare values based on locations ignoring ``index``), compare both ``.values``.

.. ipython:: python

s1.values == s2.values

If you want to compare ``Series`` aligning its ``index``, see flexible comparison methods section below.

Current Behavior (``DataFrame``, no change):

.. code-block:: ipython

In [3]: df1 == df2
Out[3]:
ValueError: Can only compare identically-labeled DataFrame objects

Logical operators
"""""""""""""""""

Logical operators align both ``index``.

Previous Behavior (``Series``):

Only left hand side ``index`` is kept.

.. code-block:: ipython

In [4]: s1 = pd.Series([True, False, True], index=list('ABC'))
In [5]: s2 = pd.Series([True, True, True], index=list('ABD'))
In [6]: s1 & s2
Out[6]:
A True
B False
C False
dtype: bool

New Behavior (``Series``):

.. ipython:: python

s1 = pd.Series([True, False, True], index=list('ABC'))
s2 = pd.Series([True, True, True], index=list('ABD'))
s1 & s2

.. note::
``Series`` logical operators fill ``NaN`` result with ``False``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inconsistent with how DataFrame behaves?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see #13896.


.. note::
To achieve the same result as previous versions (compare values based on locations ignoring ``index``), compare both ``.values``.

.. ipython:: python

s1.values & s2.values

Current Behavior (``DataFrame``, no change):

.. ipython:: python

df1 = pd.DataFrame([True, False, True], index=list('ABC'))
df2 = pd.DataFrame([True, True, True], index=list('ABD'))
df1 & df2

Flexible comparison methods
"""""""""""""""""""""""""""

``Series`` flexible comparison methods like ``eq``, ``ne``, ``le``, ``lt``, ``ge`` and ``gt`` now align both ``index``. Use these operators if you want to compare two ``Series``
which has the different ``index``.

.. ipython:: python

s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([2, 2, 2], index=['b', 'c', 'd'])
s1.eq(s2)
s1.ge(s2)

Previously, it worked as the same as comparison operators (see above).

.. _whatsnew_0190.api.promote:

``Series`` type promotion on assignment
@@ -1069,6 +1206,7 @@ Bug Fixes
- Bug in using NumPy ufunc with ``PeriodIndex`` to add or subtract integer raise ``IncompatibleFrequency``. Note that using standard operator like ``+`` or ``-`` is recommended, because standard operators use more efficient path (:issue:`13980`)

- Bug in operations on ``NaT`` returning ``float`` instead of ``datetime64[ns]`` (:issue:`12941`)
- Bug in ``Series`` flexible arithmetic methods (like ``.add()``) raises ``ValueError`` when ``axis=None`` (:issue:`13894`)

- Bug in ``pd.read_csv`` in Python 2.x with non-UTF8 encoded, multi-character separated data (:issue:`3404`)

85 changes: 66 additions & 19 deletions pandas/core/ops.py
Original file line number Diff line number Diff line change
@@ -311,17 +311,6 @@ def get_op(cls, left, right, name, na_op):
is_datetime_lhs = (is_datetime64_dtype(left) or
is_datetime64tz_dtype(left))

if isinstance(left, ABCSeries) and isinstance(right, ABCSeries):
# avoid repated alignment
if not left.index.equals(right.index):
left, right = left.align(right, copy=False)

index, lidx, ridx = left.index.join(right.index, how='outer',
return_indexers=True)
# if DatetimeIndex have different tz, convert to UTC
left.index = index
right.index = index

if not (is_datetime_lhs or is_timedelta_lhs):
return _Op(left, right, name, na_op)
else:
@@ -603,6 +592,33 @@ def _is_offset(self, arr_or_obj):
return False


def _align_method_SERIES(left, right, align_asobject=False):
""" align lhs and rhs Series """

# ToDo: Different from _align_method_FRAME, list, tuple and ndarray
# are not coerced here
# because Series has inconsistencies described in #13637

if isinstance(right, ABCSeries):
# avoid repeated alignment
if not left.index.equals(right.index):

if align_asobject:
# to keep original value's dtype for bool ops
left = left.astype(object)
right = right.astype(object)

left, right = left.align(right, copy=False)

index, lidx, ridx = left.index.join(right.index, how='outer',
return_indexers=True)
# if DatetimeIndex have different tz, convert to UTC
left.index = index
right.index = index

return left, right


def _arith_method_SERIES(op, name, str_rep, fill_zeros=None, default_axis=None,
**eval_kwargs):
"""
@@ -654,6 +670,8 @@ def wrapper(left, right, name=name, na_op=na_op):
if isinstance(right, pd.DataFrame):
return NotImplemented

left, right = _align_method_SERIES(left, right)

converted = _Op.get_op(left, right, name, na_op)

left, right = converted.left, converted.right
@@ -761,8 +779,9 @@ def wrapper(self, other, axis=None):

if isinstance(other, ABCSeries):
name = _maybe_match_name(self, other)
if len(self) != len(other):
raise ValueError('Series lengths must match to compare')
if not self._indexed_same(other):
msg = 'Can only compare identically-labeled Series objects'
raise ValueError(msg)
return self._constructor(na_op(self.values, other.values),
index=self.index, name=name)
elif isinstance(other, pd.DataFrame): # pragma: no cover
@@ -784,6 +803,7 @@ def wrapper(self, other, axis=None):

return self._constructor(na_op(self.values, np.asarray(other)),
index=self.index).__finalize__(self)

elif isinstance(other, pd.Categorical):
if not is_categorical_dtype(self):
msg = ("Cannot compare a Categorical for op {op} with Series "
@@ -856,9 +876,10 @@ def wrapper(self, other):
fill_int = lambda x: x.fillna(0)
fill_bool = lambda x: x.fillna(False).astype(bool)

self, other = _align_method_SERIES(self, other, align_asobject=True)

if isinstance(other, ABCSeries):
name = _maybe_match_name(self, other)
other = other.reindex_like(self)
is_other_int_dtype = is_integer_dtype(other.dtype)
other = fill_int(other) if is_other_int_dtype else fill_bool(other)

@@ -908,7 +929,32 @@ def wrapper(self, other):
'floordiv': {'op': '//',
'desc': 'Integer division',
'reversed': False,
'reverse': 'rfloordiv'}}
'reverse': 'rfloordiv'},

'eq': {'op': '==',
'desc': 'Equal to',
'reversed': False,
'reverse': None},
'ne': {'op': '!=',
'desc': 'Not equal to',
'reversed': False,
'reverse': None},
'lt': {'op': '<',
'desc': 'Less than',
'reversed': False,
'reverse': None},
'le': {'op': '<=',
'desc': 'Less than or equal to',
'reversed': False,
'reverse': None},
'gt': {'op': '>',
'desc': 'Greater than',
'reversed': False,
'reverse': None},
'ge': {'op': '>=',
'desc': 'Greater than or equal to',
'reversed': False,
'reverse': None}}

_op_names = list(_op_descriptions.keys())
for k in _op_names:
@@ -959,10 +1005,11 @@ def _flex_method_SERIES(op, name, str_rep, default_axis=None, fill_zeros=None,
@Appender(doc)
def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
# validate axis
self._get_axis_number(axis)
if axis is not None:
self._get_axis_number(axis)
if isinstance(other, ABCSeries):
return self._binop(other, op, level=level, fill_value=fill_value)
elif isinstance(other, (np.ndarray, ABCSeries, list, tuple)):
elif isinstance(other, (np.ndarray, list, tuple)):
if len(other) != len(self):
raise ValueError('Lengths must be equal')
return self._binop(self._constructor(other, self.index), op,
@@ -971,15 +1018,15 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
if fill_value is not None:
self = self.fillna(fill_value)

return self._constructor(op(self.values, other),
return self._constructor(op(self, other),
self.index).__finalize__(self)

flex_wrapper.__name__ = name
return flex_wrapper


series_flex_funcs = dict(flex_arith_method=_flex_method_SERIES,
flex_comp_method=_comp_method_SERIES)
flex_comp_method=_flex_method_SERIES)

series_special_funcs = dict(arith_method=_arith_method_SERIES,
comp_method=_comp_method_SERIES,
34 changes: 18 additions & 16 deletions pandas/io/tests/json/test_ujson.py
Original file line number Diff line number Diff line change
@@ -1306,43 +1306,45 @@ def testSeries(self):

# column indexed
outp = Series(ujson.decode(ujson.encode(s))).sort_values()
self.assertTrue((s == outp).values.all())
exp = Series([10, 20, 30, 40, 50, 60],
index=['6', '7', '8', '9', '10', '15'])
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(ujson.encode(s), numpy=True)).sort_values()
self.assertTrue((s == outp).values.all())
tm.assert_series_equal(outp, exp)

dec = _clean_dict(ujson.decode(ujson.encode(s, orient="split")))
outp = Series(**dec)
self.assertTrue((s == outp).values.all())
self.assertTrue(s.name == outp.name)
tm.assert_series_equal(outp, s)

dec = _clean_dict(ujson.decode(ujson.encode(s, orient="split"),
numpy=True))
outp = Series(**dec)
self.assertTrue((s == outp).values.all())
self.assertTrue(s.name == outp.name)

outp = Series(ujson.decode(ujson.encode(
s, orient="records"), numpy=True))
self.assertTrue((s == outp).values.all())
outp = Series(ujson.decode(ujson.encode(s, orient="records"),
numpy=True))
exp = Series([10, 20, 30, 40, 50, 60])
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(ujson.encode(s, orient="records")))
self.assertTrue((s == outp).values.all())
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(
ujson.encode(s, orient="values"), numpy=True))
self.assertTrue((s == outp).values.all())
outp = Series(ujson.decode(ujson.encode(s, orient="values"),
numpy=True))
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(ujson.encode(s, orient="values")))
self.assertTrue((s == outp).values.all())
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(ujson.encode(
s, orient="index"))).sort_values()
self.assertTrue((s == outp).values.all())
exp = Series([10, 20, 30, 40, 50, 60],
index=['6', '7', '8', '9', '10', '15'])
tm.assert_series_equal(outp, exp)

outp = Series(ujson.decode(ujson.encode(
s, orient="index"), numpy=True)).sort_values()
self.assertTrue((s == outp).values.all())
tm.assert_series_equal(outp, exp)

def testSeriesNested(self):
s = Series([10, 20, 30, 40, 50, 60], name="series",
3 changes: 2 additions & 1 deletion pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
@@ -676,7 +676,8 @@ def test_equals_op(self):
index_a == series_d
with tm.assertRaisesRegexp(ValueError, "Lengths must match"):
index_a == array_d
with tm.assertRaisesRegexp(ValueError, "Series lengths must match"):
msg = "Can only compare identically-labeled Series objects"
with tm.assertRaisesRegexp(ValueError, msg):
series_a == series_d
with tm.assertRaisesRegexp(ValueError, "Lengths must match"):
series_a == array_d
240 changes: 226 additions & 14 deletions pandas/tests/series/test_operators.py
Original file line number Diff line number Diff line change
@@ -43,8 +43,9 @@ def test_comparisons(self):
s2 = Series([False, True, False])

# it works!
s == s2
s2 == s
exp = Series([False, False, False])
tm.assert_series_equal(s == s2, exp)
tm.assert_series_equal(s2 == s, exp)

def test_op_method(self):
def check(series, other, check_reverse=False):
@@ -1080,27 +1081,27 @@ def test_comparison_label_based(self):
a = Series([True, False, True], list('bca'))
b = Series([False, True, False], list('abc'))

expected = Series([True, False, False], list('bca'))
expected = Series([False, True, False], list('abc'))
result = a & b
assert_series_equal(result, expected)

expected = Series([True, False, True], list('bca'))
expected = Series([True, True, False], list('abc'))
result = a | b
assert_series_equal(result, expected)

expected = Series([False, False, True], list('bca'))
expected = Series([True, False, False], list('abc'))
result = a ^ b
assert_series_equal(result, expected)

# rhs is bigger
a = Series([True, False, True], list('bca'))
b = Series([False, True, False, True], list('abcd'))

expected = Series([True, False, False], list('bca'))
expected = Series([False, True, False, False], list('abcd'))
result = a & b
assert_series_equal(result, expected)

expected = Series([True, False, True], list('bca'))
expected = Series([True, True, False, False], list('abcd'))
result = a | b
assert_series_equal(result, expected)

@@ -1117,20 +1118,28 @@ def test_comparison_label_based(self):

# vs non-matching
result = a & Series([1], ['z'])
expected = Series([False, False, False], list('bca'))
expected = Series([False, False, False, False], list('abcz'))
assert_series_equal(result, expected)

result = a | Series([1], ['z'])
expected = Series([True, False, True], list('bca'))
expected = Series([True, True, False, False], list('abcz'))
assert_series_equal(result, expected)

# identity
# we would like s[s|e] == s to hold for any e, whether empty or not
for e in [Series([]), Series([1], ['z']), Series(['z']),
for e in [Series([]), Series([1], ['z']),
Series(np.nan, b.index), Series(np.nan, a.index)]:
result = a[a | e]
assert_series_equal(result, a[a])

for e in [Series(['z'])]:
if compat.PY3:
with tm.assert_produces_warning(RuntimeWarning):
result = a[a | e]
else:
result = a[a | e]
assert_series_equal(result, a[a])

# vs scalars
index = list('bca')
t = Series([True, False, True])
@@ -1160,6 +1169,76 @@ def test_comparison_label_based(self):
for v in [np.nan]:
self.assertRaises(TypeError, lambda: t & v)

def test_comparison_flex_basic(self):
left = pd.Series(np.random.randn(10))
right = pd.Series(np.random.randn(10))

tm.assert_series_equal(left.eq(right), left == right)
tm.assert_series_equal(left.ne(right), left != right)
tm.assert_series_equal(left.le(right), left < right)
tm.assert_series_equal(left.lt(right), left <= right)
tm.assert_series_equal(left.gt(right), left > right)
tm.assert_series_equal(left.ge(right), left >= right)

# axis
for axis in [0, None, 'index']:
tm.assert_series_equal(left.eq(right, axis=axis), left == right)
tm.assert_series_equal(left.ne(right, axis=axis), left != right)
tm.assert_series_equal(left.le(right, axis=axis), left < right)
tm.assert_series_equal(left.lt(right, axis=axis), left <= right)
tm.assert_series_equal(left.gt(right, axis=axis), left > right)
tm.assert_series_equal(left.ge(right, axis=axis), left >= right)

#
msg = 'No axis named 1 for object type'
for op in ['eq', 'ne', 'le', 'le', 'gt', 'ge']:
with tm.assertRaisesRegexp(ValueError, msg):
getattr(left, op)(right, axis=1)

def test_comparison_flex_alignment(self):
left = Series([1, 3, 2], index=list('abc'))
right = Series([2, 2, 2], index=list('bcd'))

exp = pd.Series([False, False, True, False], index=list('abcd'))
tm.assert_series_equal(left.eq(right), exp)

exp = pd.Series([True, True, False, True], index=list('abcd'))
tm.assert_series_equal(left.ne(right), exp)

exp = pd.Series([False, False, True, False], index=list('abcd'))
tm.assert_series_equal(left.le(right), exp)

exp = pd.Series([False, False, False, False], index=list('abcd'))
tm.assert_series_equal(left.lt(right), exp)

exp = pd.Series([False, True, True, False], index=list('abcd'))
tm.assert_series_equal(left.ge(right), exp)

exp = pd.Series([False, True, False, False], index=list('abcd'))
tm.assert_series_equal(left.gt(right), exp)

def test_comparison_flex_alignment_fill(self):
left = Series([1, 3, 2], index=list('abc'))
right = Series([2, 2, 2], index=list('bcd'))

exp = pd.Series([False, False, True, True], index=list('abcd'))
tm.assert_series_equal(left.eq(right, fill_value=2), exp)

exp = pd.Series([True, True, False, False], index=list('abcd'))
tm.assert_series_equal(left.ne(right, fill_value=2), exp)

exp = pd.Series([False, False, True, True], index=list('abcd'))
tm.assert_series_equal(left.le(right, fill_value=0), exp)

exp = pd.Series([False, False, False, True], index=list('abcd'))
tm.assert_series_equal(left.lt(right, fill_value=0), exp)

exp = pd.Series([True, True, True, False], index=list('abcd'))
tm.assert_series_equal(left.ge(right, fill_value=0), exp)

exp = pd.Series([True, True, False, False], index=list('abcd'))
tm.assert_series_equal(left.gt(right, fill_value=0), exp)

def test_operators_bitwise(self):
# GH 9016: support bitwise op for integer types
index = list('bca')
@@ -1195,11 +1274,11 @@ def test_operators_bitwise(self):
s_a0b1c0 = Series([1], list('b'))

res = s_tft & s_a0b1c0
expected = s_tff
expected = s_tff.reindex(list('abc'))
assert_series_equal(res, expected)

res = s_tft | s_a0b1c0
expected = s_tft
expected = s_tft.reindex(list('abc'))
assert_series_equal(res, expected)

n0 = 0
@@ -1236,9 +1315,25 @@ def test_operators_bitwise(self):
self.assertRaises(TypeError, lambda: s_0123 & [0.1, 4, 3.14, 2])

# s_0123 will be all false now because of reindexing like s_tft
assert_series_equal(s_tft & s_0123, Series([False] * 3, list('bca')))
if compat.PY3:
# unable to sort incompatible object via .union.
exp = Series([False] * 7, index=['b', 'c', 'a', 0, 1, 2, 3])
with tm.assert_produces_warning(RuntimeWarning):
assert_series_equal(s_tft & s_0123, exp)
else:
exp = Series([False] * 7, index=[0, 1, 2, 3, 'a', 'b', 'c'])
assert_series_equal(s_tft & s_0123, exp)

# s_tft will be all false now because of reindexing like s_0123
assert_series_equal(s_0123 & s_tft, Series([False] * 4))
if compat.PY3:
# unable to sort incompatible object via .union.
exp = Series([False] * 7, index=[0, 1, 2, 3, 'b', 'c', 'a'])
with tm.assert_produces_warning(RuntimeWarning):
assert_series_equal(s_0123 & s_tft, exp)
else:
exp = Series([False] * 7, index=[0, 1, 2, 3, 'a', 'b', 'c'])
assert_series_equal(s_0123 & s_tft, exp)

assert_series_equal(s_0123 & False, Series([False] * 4))
assert_series_equal(s_0123 ^ False, Series([False, True, True, True]))
assert_series_equal(s_0123 & [False], Series([False] * 4))
@@ -1322,6 +1417,123 @@ def _check_op(arr, op):
_check_op(arr, operator.truediv)
_check_op(arr, operator.floordiv)

def test_arith_ops_df_compat(self):
# GH 1134
s1 = pd.Series([1, 2, 3], index=list('ABC'), name='x')
s2 = pd.Series([2, 2, 2], index=list('ABD'), name='x')

exp = pd.Series([3.0, 4.0, np.nan, np.nan],
index=list('ABCD'), name='x')
tm.assert_series_equal(s1 + s2, exp)
tm.assert_series_equal(s2 + s1, exp)

exp = pd.DataFrame({'x': [3.0, 4.0, np.nan, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s1.to_frame() + s2.to_frame(), exp)
tm.assert_frame_equal(s2.to_frame() + s1.to_frame(), exp)

# different length
s3 = pd.Series([1, 2, 3], index=list('ABC'), name='x')
s4 = pd.Series([2, 2, 2, 2], index=list('ABCD'), name='x')

exp = pd.Series([3, 4, 5, np.nan],
index=list('ABCD'), name='x')
tm.assert_series_equal(s3 + s4, exp)
tm.assert_series_equal(s4 + s3, exp)

exp = pd.DataFrame({'x': [3, 4, 5, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s3.to_frame() + s4.to_frame(), exp)
tm.assert_frame_equal(s4.to_frame() + s3.to_frame(), exp)

def test_comp_ops_df_compat(self):
# GH 1134
s1 = pd.Series([1, 2, 3], index=list('ABC'), name='x')
s2 = pd.Series([2, 2, 2], index=list('ABD'), name='x')

s3 = pd.Series([1, 2, 3], index=list('ABC'), name='x')
s4 = pd.Series([2, 2, 2, 2], index=list('ABCD'), name='x')

for l, r in [(s1, s2), (s2, s1), (s3, s4), (s4, s3)]:

msg = "Can only compare identically-labeled Series objects"
with tm.assertRaisesRegexp(ValueError, msg):
l == r

with tm.assertRaisesRegexp(ValueError, msg):
l != r

with tm.assertRaisesRegexp(ValueError, msg):
l < r

msg = "Can only compare identically-labeled DataFrame objects"
with tm.assertRaisesRegexp(ValueError, msg):
l.to_frame() == r.to_frame()

with tm.assertRaisesRegexp(ValueError, msg):
l.to_frame() != r.to_frame()

with tm.assertRaisesRegexp(ValueError, msg):
l.to_frame() < r.to_frame()

def test_bool_ops_df_compat(self):
# GH 1134
s1 = pd.Series([True, False, True], index=list('ABC'), name='x')
s2 = pd.Series([True, True, False], index=list('ABD'), name='x')

exp = pd.Series([True, False, False, False],
index=list('ABCD'), name='x')
tm.assert_series_equal(s1 & s2, exp)
tm.assert_series_equal(s2 & s1, exp)

# True | np.nan => True
exp = pd.Series([True, True, True, False],
index=list('ABCD'), name='x')
tm.assert_series_equal(s1 | s2, exp)
# np.nan | True => np.nan, filled with False
exp = pd.Series([True, True, False, False],
index=list('ABCD'), name='x')
tm.assert_series_equal(s2 | s1, exp)

# DataFrame doesn't fill nan with False
exp = pd.DataFrame({'x': [True, False, np.nan, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s1.to_frame() & s2.to_frame(), exp)
tm.assert_frame_equal(s2.to_frame() & s1.to_frame(), exp)

exp = pd.DataFrame({'x': [True, True, np.nan, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s1.to_frame() | s2.to_frame(), exp)
tm.assert_frame_equal(s2.to_frame() | s1.to_frame(), exp)

# different length
s3 = pd.Series([True, False, True], index=list('ABC'), name='x')
s4 = pd.Series([True, True, True, True], index=list('ABCD'), name='x')

exp = pd.Series([True, False, True, False],
index=list('ABCD'), name='x')
tm.assert_series_equal(s3 & s4, exp)
tm.assert_series_equal(s4 & s3, exp)

# np.nan | True => np.nan, filled with False
exp = pd.Series([True, True, True, False],
index=list('ABCD'), name='x')
tm.assert_series_equal(s3 | s4, exp)
# True | np.nan => True
exp = pd.Series([True, True, True, True],
index=list('ABCD'), name='x')
tm.assert_series_equal(s4 | s3, exp)

exp = pd.DataFrame({'x': [True, False, True, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s3.to_frame() & s4.to_frame(), exp)
tm.assert_frame_equal(s4.to_frame() & s3.to_frame(), exp)

exp = pd.DataFrame({'x': [True, True, True, np.nan]},
index=list('ABCD'))
tm.assert_frame_equal(s3.to_frame() | s4.to_frame(), exp)
tm.assert_frame_equal(s4.to_frame() | s3.to_frame(), exp)

def test_series_frame_radd_bug(self):
# GH 353
vals = Series(tm.rands_array(5, 10))