Repository: modin-project/modin

Issue:
Fix numerical functions that can only use numeric inputs (var, skew, mean but not max, min)
### Describe the problem
Uses only numeric values even when `numeric_only=False`. We should be consistent with pandas and throw `TypeError`s

### Source code / logs
```
import modin.pandas as pd
data = {
        "col1": 1.0,
        "col2": np.datetime64("2011-06-15T00:00"),
        "col3": np.array([3] * 4, dtype="int32"),
        "col4": "foo",
        "col5": True,
}
modin_df = pd.DataFrame(data)
modin_df.median(axis='rows', skipna = False, numeric_only = False)
```
Should throw a `TypeError` but returns 
```
col1    1.0
col3    3.0
col5    1.0
dtype: float64
```
as if `numeric_only=True`


Discussion:

Resolving patch:
diff --git a/modin/data_management/data_manager.py b/modin/data_management/data_manager.py
--- a/modin/data_management/data_manager.py
+++ b/modin/data_management/data_manager.py
@@ -1214,6 +1214,20 @@ def last_valid_index_builder(df):
 
         return self.index[first_result.max()]
 
+    def median(self, **kwargs):
+        """Returns median of each column or row.
+
+        Returns:
+            Series containing the median of each column or row.
+        """
+        # Pandas default is 0 (though not mentioned in docs)
+        axis = kwargs.get("axis", 0)
+        result, data_manager = self.numeric_function_clean_dataframe(axis)
+        if result is not None:
+            return result
+        func = self._prepare_method(pandas.DataFrame.median, **kwargs)
+        return data_manager.full_axis_reduce(func, axis)
+
     def memory_usage(self, **kwargs):
         """Returns the memory usage of each column.
 
@@ -1238,6 +1252,62 @@ def nunique(self, **kwargs):
         func = self._prepare_method(pandas.DataFrame.nunique, **kwargs)
         return self.full_axis_reduce(func, axis)
 
+    def quantile_for_single_value(self, **kwargs):
+        """Returns quantile of each column or row.
+
+        Returns:
+            Series containing the quantile of each column or row.
+        """
+        axis = kwargs.get("axis", 0)
+        q = kwargs.get("q", 0.5)
+        numeric_only = kwargs.get("numeric_only", True)
+        assert type(q) is float
+        if numeric_only:
+            result, data_manager = self.numeric_function_clean_dataframe(axis)
+            if result is not None:
+                return result
+        else:
+            data_manager = self
+
+        def quantile_builder(df, **kwargs):
+            try:
+                return pandas.DataFrame.quantile(df, **kwargs)
+            except ValueError:
+                return pandas.Series()
+
+        func = self._prepare_method(quantile_builder, **kwargs)
+        result = data_manager.full_axis_reduce(func, axis)
+        result.name = q
+        return result
+
+    def skew(self, **kwargs):
+        """Returns skew of each column or row.
+
+        Returns:
+            Series containing the skew of each column or row.
+        """
+        # Pandas default is 0 (though not mentioned in docs)
+        axis = kwargs.get("axis", 0)
+        result, data_manager = self.numeric_function_clean_dataframe(axis)
+        if result is not None:
+            return result
+        func = self._prepare_method(pandas.DataFrame.skew, **kwargs)
+        return data_manager.full_axis_reduce(func, axis)
+
+    def std(self, **kwargs):
+        """Returns standard deviation of each column or row.
+
+        Returns:
+            Series containing the standard deviation of each column or row.
+        """
+        # Pandas default is 0 (though not mentioned in docs)
+        axis = kwargs.get("axis", 0)
+        result, data_manager = self.numeric_function_clean_dataframe(axis)
+        if result is not None:
+            return result
+        func = self._prepare_method(pandas.DataFrame.std, **kwargs)
+        return data_manager.full_axis_reduce(func, axis)
+
     def to_datetime(self, **kwargs):
         """Converts the Manager to a Series of DateTime objects.
 
@@ -1253,6 +1323,20 @@ def to_datetime_builder(df, **kwargs):
         func = self._prepare_method(to_datetime_builder, **kwargs)
         return self.full_axis_reduce(func, 1)
 
+    def var(self, **kwargs):
+        """Returns varience of each column or row.
+
+        Returns:
+            Series containing the varience of each column or row.
+        """
+        # Pandas default is 0 (though not mentioned in docs)
+        axis = kwargs.get("axis", 0)
+        result, data_manager = self.numeric_function_clean_dataframe(axis)
+        if result is not None:
+            return result
+        func = data_manager._prepare_method(pandas.DataFrame.var, **kwargs)
+        return data_manager.full_axis_reduce(func, axis)
+
     # END Column/Row partitions reduce operations
 
     # Column/Row partitions reduce operations over select indices
@@ -1323,103 +1407,6 @@ def describe_builder(df, **kwargs):
             )
         return self.__constructor__(new_data, new_index, new_columns, new_dtypes)
 
-    def median(self, **kwargs):
-        """Returns median of each column or row.
-
-        Returns:
-            Series containing the median of each column or row.
-        """
-        # Pandas default is 0 (though not mentioned in docs)
-        axis = kwargs.get("axis", 0)
-        result, data_manager = self.numeric_function_clean_dataframe(axis)
-        if result is not None:
-            return result
-
-        def median_builder(df, **kwargs):
-            return pandas.DataFrame.median(df, **kwargs)
-
-        func = self._prepare_method(median_builder, **kwargs)
-        return data_manager.full_axis_reduce(func, axis)
-
-    def skew(self, **kwargs):
-        """Returns skew of each column or row.
-
-        Returns:
-            Series containing the skew of each column or row.
-        """
-        # Pandas default is 0 (though not mentioned in docs)
-        axis = kwargs.get("axis", 0)
-        result, data_manager = self.numeric_function_clean_dataframe(axis)
-        if result is not None:
-            return result
-
-        def skew_builder(df, **kwargs):
-            return pandas.DataFrame.skew(df, **kwargs)
-
-        func = self._prepare_method(skew_builder, **kwargs)
-        return data_manager.full_axis_reduce(func, axis)
-
-    def std(self, **kwargs):
-        """Returns standard deviation of each column or row.
-
-        Returns:
-            Series containing the standard deviation of each column or row.
-        """
-        # Pandas default is 0 (though not mentioned in docs)
-        axis = kwargs.get("axis", 0)
-        result, data_manager = self.numeric_function_clean_dataframe(axis)
-        if result is not None:
-            return result
-
-        def std_builder(df, **kwargs):
-            return pandas.DataFrame.std(df, **kwargs)
-
-        func = self._prepare_method(std_builder, **kwargs)
-        return data_manager.full_axis_reduce(func, axis)
-
-    def var(self, **kwargs):
-        """Returns varience of each column or row.
-
-        Returns:
-            Series containing the varience of each column or row.
-        """
-        # Pandas default is 0 (though not mentioned in docs)
-        axis = kwargs.get("axis", 0)
-        result, data_manager = self.numeric_function_clean_dataframe(axis)
-        if result is not None:
-            return result
-
-        func = data_manager._prepare_method(pandas.DataFrame.var, **kwargs)
-        return data_manager.full_axis_reduce(func, axis)
-
-    def quantile_for_single_value(self, **kwargs):
-        """Returns quantile of each column or row.
-
-        Returns:
-            Series containing the quantile of each column or row.
-        """
-        axis = kwargs.get("axis", 0)
-        q = kwargs.get("q", 0.5)
-        numeric_only = kwargs.get("numeric_only", True)
-        assert type(q) is float
-        if numeric_only:
-            result, data_manager = self.numeric_function_clean_dataframe(axis)
-            if result is not None:
-                return result
-        else:
-            data_manager = self
-
-        def quantile_builder(df, **kwargs):
-            try:
-                return pandas.DataFrame.quantile(df, **kwargs)
-            except ValueError:
-                return pandas.Series()
-
-        func = self._prepare_method(quantile_builder, **kwargs)
-        result = data_manager.full_axis_reduce(func, axis)
-        result.name = q
-        return result
-
     # END Column/Row partitions reduce operations over select indices
 
     # Map across rows/columns
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -2176,6 +2176,9 @@ def median(self, axis=None, skipna=None, level=None, numeric_only=None, **kwargs
             The median of the DataFrame. (Pandas series)
         """
         axis = pandas.DataFrame()._get_axis_number(axis) if axis is not None else 0
+        if numeric_only is not None and not numeric_only:
+            self._validate_dtypes(numeric_only=True)
+
         return self._data_manager.median(
             axis=axis, skipna=skipna, level=level, numeric_only=numeric_only, **kwargs
         )
@@ -2644,6 +2647,7 @@ def quantile(self, q=0.5, axis=0, numeric_only=True, interpolation="linear"):
                     index is the columns of self and the values
                     are the quantiles.
         """
+        axis = pandas.DataFrame()._get_axis_number(axis) if axis is not None else 0
 
         def check_dtype(t):
             return is_numeric_dtype(t) or is_datetime_or_timedelta_dtype(t)
@@ -3410,6 +3414,10 @@ def skew(self, axis=None, skipna=None, level=None, numeric_only=None, **kwargs):
         Returns:
             skew : Series or DataFrame (if level specified)
         """
+        axis = pandas.DataFrame()._get_axis_number(axis) if axis is not None else 0
+        if numeric_only is not None and not numeric_only:
+            self._validate_dtypes(numeric_only=True)
+
         return self._data_manager.skew(
             axis=axis, skipna=skipna, level=level, numeric_only=numeric_only, **kwargs
         )
@@ -3551,6 +3559,9 @@ def std(
             The std of the DataFrame (Pandas Series)
         """
         axis = pandas.DataFrame()._get_axis_number(axis) if axis is not None else 0
+        if numeric_only is not None and not numeric_only:
+            self._validate_dtypes(numeric_only=True)
+
         return self._data_manager.std(
             axis=axis,
             skipna=skipna,
@@ -4109,6 +4120,9 @@ def var(
             The variance of the DataFrame.
         """
         axis = pandas.DataFrame()._get_axis_number(axis) if axis is not None else 0
+        if numeric_only is not None and not numeric_only:
+            self._validate_dtypes(numeric_only=True)
+
         return self._data_manager.var(
             axis=axis,
             skipna=skipna,
@@ -4628,3 +4642,14 @@ def _validate_other(self, other, axis):
                         "given {1}".format(len(self.columns), len(other))
                     )
         return other
+
+    def _validate_dtypes(self, numeric_only=False):
+        """Helper method to check that all the dtypes are the same"""
+        dtype = self.dtypes[0]
+        for t in self.dtypes:
+            if numeric_only and not is_numeric_dtype(t):
+                raise TypeError("{0} is not a numeric data type".format(t))
+            elif not numeric_only and t != dtype:
+                raise TypeError(
+                    "Cannot compare type '{0}' with type '{1}'".format(t, dtype)
+                )


Test patch: