Repository: modin-project/modin

Issue:
`DataFrame.dot` and `Series.dot` aren't fully covered
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Red Hat Enterprise Linux Server 7.2 (Maipo)
- **Modin version** (`modin.__version__`): 0.7.3
- **Python version**:
- **Code we can use to reproduce**: 3.7.4

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
<!-- Describe the problem clearly here. -->
`DataFrame.dot` and `Series.dot` methods doesn't fully cover itself functional. The examples are below.

### Source code / logs
<!-- Include any logs or source code that would be helpful to diagnose the problem. If including tracebacks, please include the full traceback. Large logs and files should be attached. Try to provide a reproducible test case that is the bare minimum necessary to generate the problem. -->

```python
import modin.pandas as pd
import pandas
# 1st example
mdf = pd.DataFrame([1,2,3,4,5])
mdf.dot(mdf.T)
    0
0  55
pdf = pandas.DataFrame([1,2,3,4,5])
pdf.dot(pdf.T)
   0   1   2   3   4
0  1   2   3   4   5
1  2   4   6   8  10
2  3   6   9  12  15
3  4   8  12  16  20
4  5  10  15  20  25
# 2nd example
pd.DataFrame([1]).dot(mdf.T)
AttributeError: 'numpy.int64' object has no attribute 'dot'
pandas.DataFrame([1]).dot(pdf.T)
   0  1  2  3  4
0  1  2  3  4  5
# 3rd example
pd.Series([1]).dot(mdf.T)
AttributeError: 'numpy.int64' object has no attribute 'dot'
pandas.Series([1]).dot(pdf.T)
0    1
1    2
2    3
3    4
4    5
dtype: int64
```


Discussion:

Resolving patch:
diff --git a/modin/backends/pandas/query_compiler.py b/modin/backends/pandas/query_compiler.py
--- a/modin/backends/pandas/query_compiler.py
+++ b/modin/backends/pandas/query_compiler.py
@@ -784,34 +784,50 @@ def clip(self, lower, upper, **kwargs):
             new_modin_frame = self._modin_frame._map(lambda df: df.clip(**kwargs))
         return self.__constructor__(new_modin_frame)
 
-    def dot(self, other):
-        """Computes the matrix multiplication of self and other.
+    def dot(self, other, squeeze_self=None, squeeze_other=None):
+        """
+        Computes the matrix multiplication of self and other.
 
-        Args:
-            other: The other query compiler or other array-like to matrix
-            multiply with self.
+        Parameters
+        ----------
+            other : PandasQueryCompiler or NumPy array
+                The other query compiler or NumPy array to matrix multiply with self.
+            squeeze_self : boolean
+                The flag to squeeze self.
+            squeeze_other : boolean
+                The flag to squeeze other (this flag is applied if other is query compiler).
 
-        Returns:
-            Returns the result of the matrix multiply.
+        Returns
+        -------
+        PandasQueryCompiler
+            A new query compiler that contains result of the matrix multiply.
         """
         if isinstance(other, PandasQueryCompiler):
-            other = other.to_pandas().squeeze()
+            other = (
+                other.to_pandas().squeeze(axis=1)
+                if squeeze_other
+                else other.to_pandas()
+            )
 
-        def map_func(df, other=other):
-            result = df.squeeze().dot(other)
+        def map_func(df, other=other, squeeze_self=squeeze_self):
+            result = df.squeeze(axis=1).dot(other) if squeeze_self else df.dot(other)
             if is_list_like(result):
                 return pandas.DataFrame(result)
             else:
                 return pandas.DataFrame([result])
 
-        num_cols = other.shape[1] if len(other.shape) > 1 else None
+        num_cols = other.shape[1] if len(other.shape) > 1 else 1
         if len(self.columns) == 1:
-            new_index = ["__reduced__"] if num_cols is None else None
-            new_columns = ["__reduced__"] if num_cols is not None else None
+            new_index = (
+                ["__reduced__"]
+                if (len(self.index) == 1 or squeeze_self) and num_cols == 1
+                else None
+            )
+            new_columns = ["__reduced__"] if squeeze_self and num_cols == 1 else None
             axis = 0
         else:
-            new_index = None
-            new_columns = ["__reduced__"] if num_cols is None else None
+            new_index = self.index
+            new_columns = ["__reduced__"] if num_cols == 1 else None
             axis = 1
 
         new_modin_frame = self._modin_frame._apply_full_axis(
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -654,10 +654,16 @@ def dot(self, other):
 
             qc = other.reindex(index=common)._query_compiler
             if isinstance(other, DataFrame):
-                return self.__constructor__(query_compiler=self._query_compiler.dot(qc))
+                return self.__constructor__(
+                    query_compiler=self._query_compiler.dot(
+                        qc, squeeze_self=False, squeeze_other=False
+                    )
+                )
             else:
                 return self._reduce_dimension(
-                    query_compiler=self._query_compiler.dot(qc)
+                    query_compiler=self._query_compiler.dot(
+                        qc, squeeze_self=False, squeeze_other=True
+                    )
                 )
 
         other = np.asarray(other)
@@ -667,9 +673,13 @@ def dot(self, other):
             )
 
         if len(other.shape) > 1:
-            return self.__constructor__(query_compiler=self._query_compiler.dot(other))
+            return self.__constructor__(
+                query_compiler=self._query_compiler.dot(other, squeeze_self=False)
+            )
 
-        return self._reduce_dimension(query_compiler=self._query_compiler.dot(other))
+        return self._reduce_dimension(
+            query_compiler=self._query_compiler.dot(other, squeeze_self=False)
+        )
 
     def eq(self, other, axis="columns", level=None):
         return self._binary_op(
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -622,10 +622,16 @@ def dot(self, other):
             qc = other.reindex(index=common)._query_compiler
             if isinstance(other, Series):
                 return self._reduce_dimension(
-                    query_compiler=self._query_compiler.dot(qc)
+                    query_compiler=self._query_compiler.dot(
+                        qc, squeeze_self=True, squeeze_other=True
+                    )
                 )
             else:
-                return self.__constructor__(query_compiler=self._query_compiler.dot(qc))
+                return self.__constructor__(
+                    query_compiler=self._query_compiler.dot(
+                        qc, squeeze_self=True, squeeze_other=False
+                    )
+                )
 
         other = np.asarray(other)
         if self.shape[0] != other.shape[0]:
@@ -634,9 +640,13 @@ def dot(self, other):
             )
 
         if len(other.shape) > 1:
-            return self._query_compiler.dot(other).to_numpy().squeeze()
+            return (
+                self._query_compiler.dot(other, squeeze_self=True).to_numpy().squeeze()
+            )
 
-        return self._reduce_dimension(query_compiler=self._query_compiler.dot(other))
+        return self._reduce_dimension(
+            query_compiler=self._query_compiler.dot(other, squeeze_self=True)
+        )
 
     def drop_duplicates(self, keep="first", inplace=False):
         return super(Series, self).drop_duplicates(keep=keep, inplace=inplace)


Test patch:
diff --git a/modin/pandas/test/test_dataframe.py b/modin/pandas/test/test_dataframe.py
--- a/modin/pandas/test/test_dataframe.py
+++ b/modin/pandas/test/test_dataframe.py
@@ -2170,6 +2170,20 @@ def test_dot(self, data):
         with pytest.raises(ValueError):
             modin_result = modin_df.dot(pd.Series(np.arange(col_len)))
 
+        # Test case when left dataframe has size (n x 1)
+        # and right dataframe has size (1 x n)
+        modin_df = pd.DataFrame(modin_series)
+        pandas_df = pandas.DataFrame(pandas_series)
+        modin_result = modin_df.dot(modin_df.T)
+        pandas_result = pandas_df.dot(pandas_df.T)
+        df_equals(modin_result, pandas_result)
+
+        # Test case when left dataframe has size (1 x 1)
+        # and right dataframe has size (1 x n)
+        modin_result = pd.DataFrame([1]).dot(modin_df.T)
+        pandas_result = pandas.DataFrame([1]).dot(pandas_df.T)
+        df_equals(modin_result, pandas_result)
+
     @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
     def test_matmul(self, data):
         modin_df = pd.DataFrame(data)
diff --git a/modin/pandas/test/test_series.py b/modin/pandas/test/test_series.py
--- a/modin/pandas/test/test_series.py
+++ b/modin/pandas/test/test_series.py
@@ -1195,7 +1195,7 @@ def test_divide(data):
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_dot(data):
-    modin_series, pandas_series = create_test_series(data)  # noqa: F841
+    modin_series, pandas_series = create_test_series(data)
     ind_len = len(modin_series)
 
     # Test 1D array input
@@ -1236,6 +1236,12 @@ def test_dot(data):
             )
         )
 
+    # Test case when left series has size (1 x 1)
+    # and right dataframe has size (1 x n)
+    modin_result = pd.Series([1]).dot(pd.DataFrame(modin_series).T)
+    pandas_result = pandas.Series([1]).dot(pandas.DataFrame(pandas_series).T)
+    df_equals(modin_result, pandas_result)
+
 
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_matmul(data):