Skip to content

Commit 8b330ef

Browse files
committed
DOC: improve binary operator docs (fixes #10093)
1 parent 2bab718 commit 8b330ef

File tree

3 files changed

+126
-8
lines changed

3 files changed

+126
-8
lines changed

pandas/core/ops.py

+73-5
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def add_flex_arithmetic_methods(cls, flex_arith_method, radd_func=None,
213213
214214
Parameters
215215
----------
216-
flex_arith_method : function (optional)
216+
flex_arith_method : function
217217
factory for special arithmetic methods, with op string:
218218
f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs)
219219
radd_func : function (optional)
@@ -703,12 +703,35 @@ def _radd_compat(left, right):
703703

704704
return output
705705

706+
_op_descriptions = {'add': {'op': '+', 'desc': 'Addition', 'reversed': False, 'reverse': 'radd'},
707+
'sub': {'op': '-', 'desc': 'Subtraction', 'reversed': False, 'reverse': 'rsub'},
708+
'mul': {'op': '*', 'desc': 'Multiplication', 'reversed': False, 'reverse': 'rmul'},
709+
'mod': {'op': '%', 'desc': 'Modulo', 'reversed': False, 'reverse': 'rmod'},
710+
'pow': {'op': '**', 'desc': 'Exponential power', 'reversed': False, 'reverse': 'rpow'},
711+
'truediv': {'op': '/', 'desc': 'Floating division', 'reversed': False, 'reverse': 'rtruediv'},
712+
'floordiv': {'op': '//', 'desc': 'Integer division', 'reversed': False, 'reverse': 'rfloordiv'}}
713+
714+
_op_names = list(_op_descriptions.keys())
715+
for k in _op_names:
716+
reverse_op = _op_descriptions[k]['reverse']
717+
_op_descriptions[reverse_op] = _op_descriptions[k].copy()
718+
_op_descriptions[reverse_op]['reversed'] = True
719+
_op_descriptions[reverse_op]['reverse'] = k
706720

707721
def _flex_method_SERIES(op, name, str_rep, default_axis=None,
708722
fill_zeros=None, **eval_kwargs):
723+
op_name = name.replace('__', '')
724+
op_desc = _op_descriptions[op_name]
725+
if op_desc['reversed']:
726+
equiv = 'other ' + op_desc['op'] + ' series'
727+
else:
728+
equiv = 'series ' + op_desc['op'] + ' other'
729+
709730
doc = """
710-
Binary operator %s with support to substitute a fill_value for missing data
711-
in one of the inputs
731+
%s of series and other, element-wise (binary operator `%s`).
732+
733+
Equivalent to ``%s``, but with support to substitute a fill_value for
734+
missing data in one of the inputs.
712735
713736
Parameters
714737
----------
@@ -723,7 +746,11 @@ def _flex_method_SERIES(op, name, str_rep, default_axis=None,
723746
Returns
724747
-------
725748
result : Series
726-
""" % name
749+
750+
See also
751+
--------
752+
Series.%s
753+
""" % (op_desc['desc'], op_name, equiv, op_desc['reverse'])
727754

728755
@Appender(doc)
729756
def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
@@ -813,7 +840,48 @@ def na_op(x, y):
813840

814841
return result
815842

816-
@Appender(_arith_doc_FRAME % name)
843+
if name in _op_descriptions:
844+
op_name = name.replace('__', '')
845+
op_desc = _op_descriptions[op_name]
846+
if op_desc['reversed']:
847+
equiv = 'other ' + op_desc['op'] + ' dataframe'
848+
else:
849+
equiv = 'dataframe ' + op_desc['op'] + ' other'
850+
851+
doc = """
852+
%s of dataframe and other, element-wise (binary operator `%s`).
853+
854+
Equivalent to ``%s``, but with support to substitute a fill_value for
855+
missing data in one of the inputs.
856+
857+
Parameters
858+
----------
859+
other : Series, DataFrame, or constant
860+
axis : {0, 1, 'index', 'columns'}
861+
For Series input, axis to match Series index on
862+
fill_value : None or float value, default None
863+
Fill missing (NaN) values with this value. If both DataFrame locations are
864+
missing, the result will be missing
865+
level : int or name
866+
Broadcast across a level, matching Index values on the
867+
passed MultiIndex level
868+
869+
Notes
870+
-----
871+
Mismatched indices will be unioned together
872+
873+
Returns
874+
-------
875+
result : DataFrame
876+
877+
See also
878+
--------
879+
DataFrame.%s
880+
""" % (op_desc['desc'], op_name, equiv, op_desc['reverse'])
881+
else:
882+
doc = _arith_doc_FRAME % name
883+
884+
@Appender(doc)
817885
def f(self, other, axis=default_axis, level=None, fill_value=None):
818886
if isinstance(other, pd.DataFrame): # Another DataFrame
819887
return self._combine_frame(other, na_op, fill_value, level)

pandas/core/panel.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import pandas.core.ops as ops
2929
import pandas.computation.expressions as expressions
3030
from pandas import lib
31+
from pandas.core.ops import _op_descriptions
32+
3133

3234
_shared_doc_kwargs = dict(
3335
axes='items, major_axis, minor_axis',
@@ -1435,7 +1437,7 @@ def _add_aggregate_operations(cls, use_numexpr=True):
14351437
----------
14361438
other : %s or %s""" % (cls._constructor_sliced.__name__, cls.__name__) + """
14371439
axis : {""" + ', '.join(cls._AXIS_ORDERS) + "}" + """
1438-
Axis to broadcast over
1440+
Axis to broadcast over
14391441
14401442
Returns
14411443
-------
@@ -1457,8 +1459,36 @@ def na_op(x, y):
14571459
result = com._fill_zeros(result, x, y, name, fill_zeros)
14581460
return result
14591461

1460-
@Substitution(name)
1461-
@Appender(_agg_doc)
1462+
if name in _op_descriptions:
1463+
op_name = name.replace('__', '')
1464+
op_desc = _op_descriptions[op_name]
1465+
if op_desc['reversed']:
1466+
equiv = 'other ' + op_desc['op'] + ' panel'
1467+
else:
1468+
equiv = 'panel ' + op_desc['op'] + ' other'
1469+
1470+
_op_doc = """
1471+
%%s of series and other, element-wise (binary operator `%%s`).
1472+
Equivalent to ``%%s``.
1473+
1474+
Parameters
1475+
----------
1476+
other : %s or %s""" % (cls._constructor_sliced.__name__, cls.__name__) + """
1477+
axis : {""" + ', '.join(cls._AXIS_ORDERS) + "}" + """
1478+
Axis to broadcast over
1479+
1480+
Returns
1481+
-------
1482+
""" + cls.__name__ + """
1483+
1484+
See also
1485+
--------
1486+
""" + cls.__name__ + ".%s\n"
1487+
doc = _op_doc % (op_desc['desc'], op_name, equiv, op_desc['reverse'])
1488+
else:
1489+
doc = _agg_doc % name
1490+
1491+
@Appender(doc)
14621492
def f(self, other, axis=0):
14631493
return self._combine(other, na_op, axis=axis)
14641494
f.__name__ = name

pandas/tests/test_base.py

+20
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,26 @@ def check_ops_properties(self, props, filter=None, ignore_failures=False):
244244
else:
245245
self.assertRaises(AttributeError, lambda : getattr(o,op))
246246

247+
def test_binary_ops_docs(self):
248+
from pandas import DataFrame, Panel
249+
op_map = {'add': '+',
250+
'sub': '-',
251+
'mul': '*',
252+
'mod': '%',
253+
'pow': '**',
254+
'truediv': '/',
255+
'floordiv': '//'}
256+
for op_name in ['add', 'sub', 'mul', 'mod', 'pow', 'truediv', 'floordiv']:
257+
for klass in [Series, DataFrame, Panel]:
258+
operand1 = klass.__name__.lower()
259+
operand2 = 'other'
260+
op = op_map[op_name]
261+
expected_str = ' '.join([operand1, op, operand2])
262+
self.assertTrue(expected_str in getattr(klass, op_name).__doc__)
263+
264+
# reverse version of the binary ops
265+
expected_str = ' '.join([operand2, op, operand1])
266+
self.assertTrue(expected_str in getattr(klass, 'r' + op_name).__doc__)
247267

248268
class TestIndexOps(Ops):
249269

0 commit comments

Comments
 (0)