diff --git a/pandas/core/ops.py b/pandas/core/ops.py index a4c9bff3dd97f..0b62eb1e53ddb 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -213,7 +213,7 @@ def add_flex_arithmetic_methods(cls, flex_arith_method, radd_func=None, Parameters ---------- - flex_arith_method : function (optional) + flex_arith_method : function factory for special arithmetic methods, with op string: f(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs) radd_func : function (optional) @@ -703,12 +703,35 @@ def _radd_compat(left, right): return output +_op_descriptions = {'add': {'op': '+', 'desc': 'Addition', 'reversed': False, 'reverse': 'radd'}, + 'sub': {'op': '-', 'desc': 'Subtraction', 'reversed': False, 'reverse': 'rsub'}, + 'mul': {'op': '*', 'desc': 'Multiplication', 'reversed': False, 'reverse': 'rmul'}, + 'mod': {'op': '%', 'desc': 'Modulo', 'reversed': False, 'reverse': 'rmod'}, + 'pow': {'op': '**', 'desc': 'Exponential power', 'reversed': False, 'reverse': 'rpow'}, + 'truediv': {'op': '/', 'desc': 'Floating division', 'reversed': False, 'reverse': 'rtruediv'}, + 'floordiv': {'op': '//', 'desc': 'Integer division', 'reversed': False, 'reverse': 'rfloordiv'}} + +_op_names = list(_op_descriptions.keys()) +for k in _op_names: + reverse_op = _op_descriptions[k]['reverse'] + _op_descriptions[reverse_op] = _op_descriptions[k].copy() + _op_descriptions[reverse_op]['reversed'] = True + _op_descriptions[reverse_op]['reverse'] = k def _flex_method_SERIES(op, name, str_rep, default_axis=None, fill_zeros=None, **eval_kwargs): + op_name = name.replace('__', '') + op_desc = _op_descriptions[op_name] + if op_desc['reversed']: + equiv = 'other ' + op_desc['op'] + ' series' + else: + equiv = 'series ' + op_desc['op'] + ' other' + doc = """ - Binary operator %s with support to substitute a fill_value for missing data - in one of the inputs + %s of series and other, element-wise (binary operator `%s`). + + Equivalent to ``%s``, but with support to substitute a fill_value for + missing data in one of the inputs. Parameters ---------- @@ -723,7 +746,11 @@ def _flex_method_SERIES(op, name, str_rep, default_axis=None, Returns ------- result : Series - """ % name + + See also + -------- + Series.%s + """ % (op_desc['desc'], op_name, equiv, op_desc['reverse']) @Appender(doc) def flex_wrapper(self, other, level=None, fill_value=None, axis=0): @@ -813,7 +840,48 @@ def na_op(x, y): return result - @Appender(_arith_doc_FRAME % name) + if name in _op_descriptions: + op_name = name.replace('__', '') + op_desc = _op_descriptions[op_name] + if op_desc['reversed']: + equiv = 'other ' + op_desc['op'] + ' dataframe' + else: + equiv = 'dataframe ' + op_desc['op'] + ' other' + + doc = """ + %s of dataframe and other, element-wise (binary operator `%s`). + + Equivalent to ``%s``, but with support to substitute a fill_value for + missing data in one of the inputs. + + Parameters + ---------- + other : Series, DataFrame, or constant + axis : {0, 1, 'index', 'columns'} + For Series input, axis to match Series index on + fill_value : None or float value, default None + Fill missing (NaN) values with this value. If both DataFrame locations are + missing, the result will be missing + level : int or name + Broadcast across a level, matching Index values on the + passed MultiIndex level + + Notes + ----- + Mismatched indices will be unioned together + + Returns + ------- + result : DataFrame + + See also + -------- + DataFrame.%s + """ % (op_desc['desc'], op_name, equiv, op_desc['reverse']) + else: + doc = _arith_doc_FRAME % name + + @Appender(doc) def f(self, other, axis=default_axis, level=None, fill_value=None): if isinstance(other, pd.DataFrame): # Another DataFrame return self._combine_frame(other, na_op, fill_value, level) diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 1215efe3a4db6..778f6a22e558c 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -28,6 +28,8 @@ import pandas.core.ops as ops import pandas.computation.expressions as expressions from pandas import lib +from pandas.core.ops import _op_descriptions + _shared_doc_kwargs = dict( axes='items, major_axis, minor_axis', @@ -1435,7 +1437,7 @@ def _add_aggregate_operations(cls, use_numexpr=True): ---------- other : %s or %s""" % (cls._constructor_sliced.__name__, cls.__name__) + """ axis : {""" + ', '.join(cls._AXIS_ORDERS) + "}" + """ -Axis to broadcast over + Axis to broadcast over Returns ------- @@ -1457,8 +1459,36 @@ def na_op(x, y): result = com._fill_zeros(result, x, y, name, fill_zeros) return result - @Substitution(name) - @Appender(_agg_doc) + if name in _op_descriptions: + op_name = name.replace('__', '') + op_desc = _op_descriptions[op_name] + if op_desc['reversed']: + equiv = 'other ' + op_desc['op'] + ' panel' + else: + equiv = 'panel ' + op_desc['op'] + ' other' + + _op_doc = """ + %%s of series and other, element-wise (binary operator `%%s`). + Equivalent to ``%%s``. + + Parameters + ---------- + other : %s or %s""" % (cls._constructor_sliced.__name__, cls.__name__) + """ + axis : {""" + ', '.join(cls._AXIS_ORDERS) + "}" + """ + Axis to broadcast over + + Returns + ------- + """ + cls.__name__ + """ + + See also + -------- + """ + cls.__name__ + ".%s\n" + doc = _op_doc % (op_desc['desc'], op_name, equiv, op_desc['reverse']) + else: + doc = _agg_doc % name + + @Appender(doc) def f(self, other, axis=0): return self._combine(other, na_op, axis=axis) f.__name__ = name diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py index b91c46377267a..e9526f9fad1ac 100644 --- a/pandas/tests/test_base.py +++ b/pandas/tests/test_base.py @@ -244,6 +244,26 @@ def check_ops_properties(self, props, filter=None, ignore_failures=False): else: self.assertRaises(AttributeError, lambda : getattr(o,op)) + def test_binary_ops_docs(self): + from pandas import DataFrame, Panel + op_map = {'add': '+', + 'sub': '-', + 'mul': '*', + 'mod': '%', + 'pow': '**', + 'truediv': '/', + 'floordiv': '//'} + for op_name in ['add', 'sub', 'mul', 'mod', 'pow', 'truediv', 'floordiv']: + for klass in [Series, DataFrame, Panel]: + operand1 = klass.__name__.lower() + operand2 = 'other' + op = op_map[op_name] + expected_str = ' '.join([operand1, op, operand2]) + self.assertTrue(expected_str in getattr(klass, op_name).__doc__) + + # reverse version of the binary ops + expected_str = ' '.join([operand2, op, operand1]) + self.assertTrue(expected_str in getattr(klass, 'r' + op_name).__doc__) class TestIndexOps(Ops):