Repository: databricks/koalas

Issue:
Different behaviour for mod between pandas and Koalas
pandas and Koalas are using different way for calculating the `mod` for negative values like the below.

```python
>>> pser
0    10
dtype: int64

>>> kser
0    10
Name: 0, dtype: int64

>>> pser.mod(-3)
0   -2
dtype: int64

>>> kser.mod(-3)
0    1
Name: 0, dtype: int64
```

I think It would be better to match this behaviour to the pandas.


Discussion:

Resolving patch:
diff --git a/databricks/koalas/base.py b/databricks/koalas/base.py
--- a/databricks/koalas/base.py
+++ b/databricks/koalas/base.py
@@ -23,7 +23,7 @@
 
 import numpy as np
 import pandas as pd
-from pandas.api.types import is_list_like
+from pandas.api.types import is_list_like, is_scalar
 from pyspark import sql as spark
 from pyspark.sql import functions as F, Window
 from pyspark.sql.types import DoubleType, FloatType, LongType, StringType, TimestampType
@@ -186,7 +186,13 @@ def __sub__(self, other):
     __mul__ = _column_op(spark.Column.__mul__)
     __div__ = _numpy_column_op(spark.Column.__div__)
     __truediv__ = _numpy_column_op(spark.Column.__truediv__)
-    __mod__ = _column_op(spark.Column.__mod__)
+
+    def __mod__(self, other):
+        if is_scalar(other):
+            return self._with_new_scol((self._scol % other + other) % other)
+        else:
+            result_spark = _column_op(spark.Column.__mod__)(self, other)
+            return _column_op(spark.Column.__mod__)(result_spark + other, other)
 
     def __radd__(self, other):
         # Handle 'literal' + df['col']
@@ -210,7 +216,13 @@ def __rfloordiv__(self, other):
             F.floor(_numpy_column_op(spark.Column.__rdiv__)(self, other)._scol)
         )
 
-    __rmod__ = _column_op(spark.Column.__rmod__)
+    def __rmod__(self, other):
+        if is_scalar(other):
+            return self._with_new_scol((other % self._scol + self._scol) % self._scol)
+        else:
+            result_spark = _column_op(spark.Column.__mod__)(other, self)
+            return _column_op(spark.Column.__mod__)(result_spark + self, self)
+
     __pow__ = _column_op(spark.Column.__pow__)
     __rpow__ = _column_op(spark.Column.__rpow__)
 


Test patch:
diff --git a/databricks/koalas/tests/test_series.py b/databricks/koalas/tests/test_series.py
--- a/databricks/koalas/tests/test_series.py
+++ b/databricks/koalas/tests/test_series.py
@@ -1373,6 +1373,22 @@ def test_take(self):
         self.assertRaises(ValueError, lambda: kser.take({1, 2}))
         self.assertRaises(ValueError, lambda: kser.take({1: None, 2: None}))
 
+    def test_mod(self):
+        pser = pd.Series([100, None, -300, None, 500, -700], name="Koalas")
+        kser = ks.from_pandas(pser)
+
+        self.assert_eq(repr(kser.mod(-150)), repr(pser.mod(-150)))
+        self.assert_eq(repr(kser.mod(0)), repr(pser.mod(0)))
+        self.assert_eq(repr(kser.mod(150)), repr(pser.mod(150)))
+
+    def test_rmod(self):
+        pser = pd.Series([100, None, -300, None, 500, -700], name="Koalas")
+        kser = ks.from_pandas(pser)
+
+        self.assert_eq(repr(kser.rmod(-150)), repr(pser.rmod(-150)))
+        self.assert_eq(repr(kser.rmod(0)), repr(pser.rmod(0)))
+        self.assert_eq(repr(kser.rmod(150)), repr(pser.rmod(150)))
+
     def test_asof(self):
         pser = pd.Series([1, 2, np.nan, 4], index=[10, 20, 30, 40], name="Koalas")
         kser = ks.from_pandas(pser)