Skip to content

Commit 5beb85d

Browse files
committed
DOC: improve binary operator docs (fixes #10093)
1 parent 3298528 commit 5beb85d

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

pandas/core/ops.py

+71-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,34 @@ 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_desc = _op_descriptions[name.replace('__', '')]
724+
if op_desc['reversed']:
725+
equiv = 'other ' + op_desc['op'] + ' series'
726+
else:
727+
equiv = 'series ' + op_desc['op'] + ' other'
728+
709729
doc = """
710-
Binary operator %s with support to substitute a fill_value for missing data
711-
in one of the inputs
730+
%s of series and other, element-wise (binary operator `%s`).
731+
732+
Equivalent to ``%s``, but with support to substitute a fill_value for
733+
missing data in one of the inputs.
712734
713735
Parameters
714736
----------
@@ -723,7 +745,11 @@ def _flex_method_SERIES(op, name, str_rep, default_axis=None,
723745
Returns
724746
-------
725747
result : Series
726-
""" % name
748+
749+
See also
750+
--------
751+
Series.%s
752+
""" % (op_desc['desc'], op_desc['op'], equiv, op_desc['reverse'])
727753

728754
@Appender(doc)
729755
def flex_wrapper(self, other, level=None, fill_value=None, axis=0):
@@ -813,7 +839,47 @@ def na_op(x, y):
813839

814840
return result
815841

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

pandas/core/panel.py

+32-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,35 @@ 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_desc = _op_descriptions[name.replace('__', '')]
1464+
if op_desc['reversed']:
1465+
equiv = 'other ' + op_desc['op'] + ' panel'
1466+
else:
1467+
equiv = 'panel ' + op_desc['op'] + ' other'
1468+
1469+
_op_doc = """
1470+
%%s of series and other, element-wise (binary operator `%%s`).
1471+
Equivalent to ``%%s``.
1472+
1473+
Parameters
1474+
----------
1475+
other : %s or %s""" % (cls._constructor_sliced.__name__, cls.__name__) + """
1476+
axis : {""" + ', '.join(cls._AXIS_ORDERS) + "}" + """
1477+
Axis to broadcast over
1478+
1479+
Returns
1480+
-------
1481+
""" + cls.__name__ + """
1482+
1483+
See also
1484+
--------
1485+
""" + cls.__name__ + ".%s\n"
1486+
doc = _op_doc % (op_desc['desc'], op_desc['op'], equiv, op_desc['reverse'])
1487+
else:
1488+
doc = _agg_doc % name
1489+
1490+
@Appender(doc)
14621491
def f(self, other, axis=0):
14631492
return self._combine(other, na_op, axis=axis)
14641493
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)