Repository: mars-project/mars

Issue:
Support modulo for DataFrame
On arithmetic operands, pandas supports +, -, *, /, //, %, **, however, % is now missing in Mars.


Discussion:

Resolving patch:
diff --git a/mars/dataframe/arithmetic/__init__.py b/mars/dataframe/arithmetic/__init__.py
--- a/mars/dataframe/arithmetic/__init__.py
+++ b/mars/dataframe/arithmetic/__init__.py
@@ -24,6 +24,7 @@
 from .multiply import mul, rmul, DataFrameMul
 from .floordiv import floordiv, rfloordiv, DataFrameFloorDiv
 from .truediv import truediv, rtruediv, DataFrameTrueDiv
+from .mod import mod, rmod, DataFrameMod
 from .power import power, rpower, DataFramePower
 from .equal import eq, DataFrameEqual
 from .not_equal import ne, DataFrameNotEqual
@@ -143,6 +144,11 @@ def _install():
         setattr(entity, 'div', truediv)
         setattr(entity, 'rdiv', rtruediv)
 
+        setattr(entity, '__mod__', wrap_notimplemented_exception(mod))
+        setattr(entity, '__rmod__', wrap_notimplemented_exception(rmod))
+        setattr(entity, 'mod', mod)
+        setattr(entity, 'rmod', rmod)
+
         setattr(entity, '__pow__', wrap_notimplemented_exception(power))
         setattr(entity, '__rpow__', wrap_notimplemented_exception(rpower))
         setattr(entity, 'pow', power)
@@ -166,18 +172,18 @@ def _install():
 
         setattr(entity, '__and__', wrap_notimplemented_exception(logical_and))
         setattr(entity, '__rand__', wrap_notimplemented_exception(logical_rand))
-        setattr(entity, 'and', wrap_notimplemented_exception(logical_and))
-        setattr(entity, 'rand', wrap_notimplemented_exception(logical_rand))
+        setattr(entity, 'and', logical_and)
+        setattr(entity, 'rand', logical_rand)
 
         setattr(entity, '__or__', wrap_notimplemented_exception(logical_or))
         setattr(entity, '__ror__', wrap_notimplemented_exception(logical_ror))
-        setattr(entity, 'or', wrap_notimplemented_exception(logical_or))
-        setattr(entity, 'ror', wrap_notimplemented_exception(logical_ror))
+        setattr(entity, 'or', logical_or)
+        setattr(entity, 'ror', logical_ror)
 
         setattr(entity, '__xor__', wrap_notimplemented_exception(logical_xor))
         setattr(entity, '__rxor__', wrap_notimplemented_exception(logical_rxor))
-        setattr(entity, 'xor', wrap_notimplemented_exception(logical_xor))
-        setattr(entity, 'rxor', wrap_notimplemented_exception(logical_rxor))
+        setattr(entity, 'xor', logical_xor)
+        setattr(entity, 'rxor', logical_rxor)
 
     for entity in INDEX_TYPE:
         setattr(entity, '__eq__', _wrap_eq())
diff --git a/mars/dataframe/arithmetic/mod.py b/mars/dataframe/arithmetic/mod.py
new file mode 100644
--- /dev/null
+++ b/mars/dataframe/arithmetic/mod.py
@@ -0,0 +1,40 @@
+# Copyright 1999-2020 Alibaba Group Holding Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import operator
+
+from ... import opcodes as OperandDef
+from ...utils import classproperty
+from .core import DataFrameBinOpMixin, DataFrameBinOp
+
+
+class DataFrameMod(DataFrameBinOp, DataFrameBinOpMixin):
+    _op_type_ = OperandDef.MOD
+
+    _func_name = 'mod'
+    _rfunc_name = 'rmod'
+
+    @classproperty
+    def _operator(self):
+        return operator.mul
+
+
+def mod(df, other, axis='columns', level=None, fill_value=None):
+    op = DataFrameMod(axis=axis, level=level, fill_value=fill_value, lhs=df, rhs=other)
+    return op(df, other)
+
+
+def rmod(df, other, axis='columns', level=None, fill_value=None):
+    op = DataFrameMod(axis=axis, level=level, fill_value=fill_value, lhs=other, rhs=df)
+    return op.rcall(df, other)


Test patch:
diff --git a/mars/dataframe/arithmetic/tests/test_arithmetic.py b/mars/dataframe/arithmetic/tests/test_arithmetic.py
--- a/mars/dataframe/arithmetic/tests/test_arithmetic.py
+++ b/mars/dataframe/arithmetic/tests/test_arithmetic.py
@@ -28,7 +28,7 @@
 from mars.dataframe.datasource.dataframe import from_pandas, DataFrameDataSource
 from mars.dataframe.datasource.series import from_pandas as from_pandas_series, SeriesDataSource
 from mars.dataframe.arithmetic import DataFrameAbs, DataFrameAdd, DataFrameSubtract, \
-    DataFrameMul, DataFrameFloorDiv, DataFrameTrueDiv, DataFramePower, \
+    DataFrameMul, DataFrameFloorDiv, DataFrameTrueDiv, DataFrameMod, DataFramePower, \
     DataFrameEqual, DataFrameNotEqual, DataFrameGreater, DataFrameLess, \
     DataFrameGreaterEqual, DataFrameLessEqual, DataFrameNot, \
     DataFrameAnd, DataFrameOr, DataFrameXor
@@ -54,6 +54,8 @@ def inner(_, lhs, rhs):
                   func_name='floordiv', rfunc_name='rfloordiv'),
     truediv=dict(func=operator.truediv, op=DataFrameTrueDiv,
                  func_name='truediv', rfunc_name='rtruediv'),
+    mod=dict(func=operator.mod, op=DataFrameMod,
+             func_name='mod', rfunc_name='rmod'),
     power=dict(func=operator.pow, op=DataFramePower,
                func_name='pow', rfunc_name='rpow'),
     equal=dict(func=comp_func('eq', 'eq'), op=DataFrameEqual,
diff --git a/mars/dataframe/arithmetic/tests/test_arithmetic_execution.py b/mars/dataframe/arithmetic/tests/test_arithmetic_execution.py
--- a/mars/dataframe/arithmetic/tests/test_arithmetic_execution.py
+++ b/mars/dataframe/arithmetic/tests/test_arithmetic_execution.py
@@ -35,6 +35,7 @@
                   rfunc_name='rfloordiv'),
     truediv=dict(func=operator.truediv, func_name='truediv',
                  rfunc_name='rtruediv'),
+    mod=dict(func=operator.mod, func_name='mod', rfunc_name='rmod'),
     power=dict(func=operator.pow, func_name='pow', rfunc_name='rpow'),
     equal=dict(func=comp_func('eq', 'eq'), func_name='eq', rfunc_name='eq'),
     not_equal=dict(func=comp_func('ne', 'ne'), func_name='ne', rfunc_name='ne'),