Skip to content

Commit 95376bc

Browse files
committed
Add flex comparison methods
1 parent 8406a26 commit 95376bc

File tree

3 files changed

+138
-9
lines changed

3 files changed

+138
-9
lines changed

doc/source/whatsnew/v0.19.0.txt

+34-2
Original file line numberDiff line numberDiff line change
@@ -488,8 +488,8 @@ including ``DataFrame`` (:issue:`1134`, :issue:`4581`, :issue:`13538`)
488488

489489
.. warning::
490490
Until 0.18.1, comparing ``Series`` with the same length has been succeeded even if
491-
these ``index`` are different (the result ignores ``index``).
492-
As of 0.19.1, it raises ``ValueError`` to be more strict.
491+
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``.
492+
493493

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

@@ -534,6 +534,15 @@ New Behavior (``Series``):
534534
Out[2]:
535535
ValueError: Can only compare identically-labeled Series objects
536536

537+
.. note::
538+
To achieve the same result as previous versions (compare values based on locations ignoring ``index``), compare both ``.values``.
539+
540+
.. ipython:: python
541+
542+
s1.values == s2.values
543+
544+
If you want to compare ``Series`` aligning its ``index``, see flexible comparison methods section below.
545+
537546
Current Behavior (``DataFrame``, no change):
538547

539548
.. code-block:: ipython
@@ -573,6 +582,13 @@ New Behavior (``Series``):
573582
.. note::
574583
``Series`` logical operators fill ``NaN`` result with ``False``.
575584

585+
.. note::
586+
To achieve the same result as previous versions (compare values based on locations ignoring ``index``), compare both ``.values``.
587+
588+
.. ipython:: python
589+
590+
s1.values & s2.values
591+
576592
Current Behavior (``DataFrame``, no change):
577593

578594
.. ipython:: python
@@ -581,6 +597,21 @@ Current Behavior (``DataFrame``, no change):
581597
df2 = pd.DataFrame([True, True, True], index=list('ABD'))
582598
df1 & df2
583599

600+
Flexible comparison methods
601+
"""""""""""""""""""""""""""
602+
603+
``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``
604+
which has the different ``index``.
605+
606+
.. ipython:: python
607+
608+
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
609+
s2 = pd.Series([2, 2, 2], index=['b', 'c', 'd'])
610+
s1.eq(s2)
611+
s1.ge(s2)
612+
613+
Previously, it worked as the same as comparison operators (see above).
614+
584615
.. _whatsnew_0190.api.promote:
585616

586617
``Series`` type promotion on assignment
@@ -1175,6 +1206,7 @@ Bug Fixes
11751206
- 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`)
11761207

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

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

pandas/core/ops.py

+31-5
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,32 @@ def wrapper(self, other):
929929
'floordiv': {'op': '//',
930930
'desc': 'Integer division',
931931
'reversed': False,
932-
'reverse': 'rfloordiv'}}
932+
'reverse': 'rfloordiv'},
933+
934+
'eq': {'op': '==',
935+
'desc': 'Equal to',
936+
'reversed': False,
937+
'reverse': None},
938+
'ne': {'op': '!=',
939+
'desc': 'Not equal to',
940+
'reversed': False,
941+
'reverse': None},
942+
'lt': {'op': '<',
943+
'desc': 'Less than',
944+
'reversed': False,
945+
'reverse': None},
946+
'le': {'op': '<=',
947+
'desc': 'Less than or equal to',
948+
'reversed': False,
949+
'reverse': None},
950+
'gt': {'op': '>',
951+
'desc': 'Greater than',
952+
'reversed': False,
953+
'reverse': None},
954+
'ge': {'op': '>=',
955+
'desc': 'Greater than or equal to',
956+
'reversed': False,
957+
'reverse': None}}
933958

934959
_op_names = list(_op_descriptions.keys())
935960
for k in _op_names:
@@ -980,10 +1005,11 @@ def _flex_method_SERIES(op, name, str_rep, default_axis=None, fill_zeros=None,
9801005
@Appender(doc)
9811006
def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
9821007
# validate axis
983-
self._get_axis_number(axis)
1008+
if axis is not None:
1009+
self._get_axis_number(axis)
9841010
if isinstance(other, ABCSeries):
9851011
return self._binop(other, op, level=level, fill_value=fill_value)
986-
elif isinstance(other, (np.ndarray, ABCSeries, list, tuple)):
1012+
elif isinstance(other, (np.ndarray, list, tuple)):
9871013
if len(other) != len(self):
9881014
raise ValueError('Lengths must be equal')
9891015
return self._binop(self._constructor(other, self.index), op,
@@ -992,15 +1018,15 @@ def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
9921018
if fill_value is not None:
9931019
self = self.fillna(fill_value)
9941020

995-
return self._constructor(op(self.values, other),
1021+
return self._constructor(op(self, other),
9961022
self.index).__finalize__(self)
9971023

9981024
flex_wrapper.__name__ = name
9991025
return flex_wrapper
10001026

10011027

10021028
series_flex_funcs = dict(flex_arith_method=_flex_method_SERIES,
1003-
flex_comp_method=_comp_method_SERIES)
1029+
flex_comp_method=_flex_method_SERIES)
10041030

10051031
series_special_funcs = dict(arith_method=_arith_method_SERIES,
10061032
comp_method=_comp_method_SERIES,

pandas/tests/series/test_operators.py

+73-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ def test_comparisons(self):
4343
s2 = Series([False, True, False])
4444

4545
# it works!
46-
s == s2
47-
s2 == s
46+
exp = Series([False, False, False])
47+
tm.assert_series_equal(s == s2, exp)
48+
tm.assert_series_equal(s2 == s, exp)
4849

4950
def test_op_method(self):
5051
def check(series, other, check_reverse=False):
@@ -1168,6 +1169,76 @@ def test_comparison_label_based(self):
11681169
for v in [np.nan]:
11691170
self.assertRaises(TypeError, lambda: t & v)
11701171

1172+
def test_comparison_flex_basic(self):
1173+
left = pd.Series(np.random.randn(10))
1174+
right = pd.Series(np.random.randn(10))
1175+
1176+
tm.assert_series_equal(left.eq(right), left == right)
1177+
tm.assert_series_equal(left.ne(right), left != right)
1178+
tm.assert_series_equal(left.le(right), left < right)
1179+
tm.assert_series_equal(left.lt(right), left <= right)
1180+
tm.assert_series_equal(left.gt(right), left > right)
1181+
tm.assert_series_equal(left.ge(right), left >= right)
1182+
1183+
# axis
1184+
for axis in [0, None, 'index']:
1185+
tm.assert_series_equal(left.eq(right, axis=axis), left == right)
1186+
tm.assert_series_equal(left.ne(right, axis=axis), left != right)
1187+
tm.assert_series_equal(left.le(right, axis=axis), left < right)
1188+
tm.assert_series_equal(left.lt(right, axis=axis), left <= right)
1189+
tm.assert_series_equal(left.gt(right, axis=axis), left > right)
1190+
tm.assert_series_equal(left.ge(right, axis=axis), left >= right)
1191+
1192+
#
1193+
msg = 'No axis named 1 for object type'
1194+
for op in ['eq', 'ne', 'le', 'le', 'gt', 'ge']:
1195+
with tm.assertRaisesRegexp(ValueError, msg):
1196+
getattr(left, op)(right, axis=1)
1197+
1198+
def test_comparison_flex_alignment(self):
1199+
left = Series([1, 3, 2], index=list('abc'))
1200+
right = Series([2, 2, 2], index=list('bcd'))
1201+
1202+
exp = pd.Series([False, False, True, False], index=list('abcd'))
1203+
tm.assert_series_equal(left.eq(right), exp)
1204+
1205+
exp = pd.Series([True, True, False, True], index=list('abcd'))
1206+
tm.assert_series_equal(left.ne(right), exp)
1207+
1208+
exp = pd.Series([False, False, True, False], index=list('abcd'))
1209+
tm.assert_series_equal(left.le(right), exp)
1210+
1211+
exp = pd.Series([False, False, False, False], index=list('abcd'))
1212+
tm.assert_series_equal(left.lt(right), exp)
1213+
1214+
exp = pd.Series([False, True, True, False], index=list('abcd'))
1215+
tm.assert_series_equal(left.ge(right), exp)
1216+
1217+
exp = pd.Series([False, True, False, False], index=list('abcd'))
1218+
tm.assert_series_equal(left.gt(right), exp)
1219+
1220+
def test_comparison_flex_alignment_fill(self):
1221+
left = Series([1, 3, 2], index=list('abc'))
1222+
right = Series([2, 2, 2], index=list('bcd'))
1223+
1224+
exp = pd.Series([False, False, True, True], index=list('abcd'))
1225+
tm.assert_series_equal(left.eq(right, fill_value=2), exp)
1226+
1227+
exp = pd.Series([True, True, False, False], index=list('abcd'))
1228+
tm.assert_series_equal(left.ne(right, fill_value=2), exp)
1229+
1230+
exp = pd.Series([False, False, True, True], index=list('abcd'))
1231+
tm.assert_series_equal(left.le(right, fill_value=0), exp)
1232+
1233+
exp = pd.Series([False, False, False, True], index=list('abcd'))
1234+
tm.assert_series_equal(left.lt(right, fill_value=0), exp)
1235+
1236+
exp = pd.Series([True, True, True, False], index=list('abcd'))
1237+
tm.assert_series_equal(left.ge(right, fill_value=0), exp)
1238+
1239+
exp = pd.Series([True, True, False, False], index=list('abcd'))
1240+
tm.assert_series_equal(left.gt(right, fill_value=0), exp)
1241+
11711242
def test_operators_bitwise(self):
11721243
# GH 9016: support bitwise op for integer types
11731244
index = list('bca')

0 commit comments

Comments
 (0)