Repository: modin-project/modin

Issue:
Series `ravel` loses category dtype
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Windows 10 1809
- **Modin version** (`modin.__version__`): 0.7.3+129.g793eafd.dirty
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
if __name__ == "__main__":
    import modin.pandas as pd
    import pandas

    kwargs = {
        "data": [1, 2, 3, 4, 5],
        "dtype": "category",
    }

    md_series, pd_series = pd.Series(**kwargs), pandas.Series(**kwargs)

    print("Modin ravel:\n", md_series.ravel())
    print("Pandas ravel:\n", pd_series.ravel())

```
<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The output of the code above would be like:
```
Modin ravel:
 [1 2 3 4 5]
Pandas ravel:
 [1, 2, 3, 4, 5]
Categories (5, int64): [1, 2, 3, 4, 5]
```
So, Pandas returns us a `pandas.Categorical` when modin returns just a `numpy.ndarray` with `dtype=int64`.
### Source code / logs
The problem is that in a current `ravel` implementation we translate our Series to numpy, and that's the place where we are losing our categorical dtype. To get pandas behavior we should apply `ravel` function to every partition and then combine its results.
https://github.com/modin-project/modin/blob/4c772ab5a97276709ae9e035ead5198caaa7f2bf/modin/pandas/series.py#L941-L955

Series `ravel` loses category dtype
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Windows 10 1809
- **Modin version** (`modin.__version__`): 0.7.3+129.g793eafd.dirty
- **Python version**: 3.7.5
- **Code we can use to reproduce**:
```python
if __name__ == "__main__":
    import modin.pandas as pd
    import pandas

    kwargs = {
        "data": [1, 2, 3, 4, 5],
        "dtype": "category",
    }

    md_series, pd_series = pd.Series(**kwargs), pandas.Series(**kwargs)

    print("Modin ravel:\n", md_series.ravel())
    print("Pandas ravel:\n", pd_series.ravel())

```
<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
The output of the code above would be like:
```
Modin ravel:
 [1 2 3 4 5]
Pandas ravel:
 [1, 2, 3, 4, 5]
Categories (5, int64): [1, 2, 3, 4, 5]
```
So, Pandas returns us a `pandas.Categorical` when modin returns just a `numpy.ndarray` with `dtype=int64`.
### Source code / logs
The problem is that in a current `ravel` implementation we translate our Series to numpy, and that's the place where we are losing our categorical dtype. To get pandas behavior we should apply `ravel` function to every partition and then combine its results.
https://github.com/modin-project/modin/blob/4c772ab5a97276709ae9e035ead5198caaa7f2bf/modin/pandas/series.py#L941-L955



Discussion:

Resolving patch:
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -952,7 +952,11 @@ def ravel(self, order="C"):
             Flattened data of the Series.
 
         """
-        return self._query_compiler.to_numpy().flatten(order=order)
+        data = self._query_compiler.to_numpy().flatten(order=order)
+        if isinstance(self.dtype, pandas.CategoricalDtype):
+            data = pandas.Categorical(data, dtype=self.dtype)
+
+        return data
 
     def reindex(self, index=None, **kwargs):
         method = kwargs.pop("method", None)


Test patch:
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
@@ -52,6 +52,7 @@
     int_arg_keys,
     int_arg_values,
     encoding_types,
+    categories_equals,
 )
 
 pd.DEFAULT_NPARTITIONS = 4
@@ -169,7 +170,7 @@ def create_test_series(vals):
     if isinstance(vals, dict):
         modin_series = pd.Series(vals[next(iter(vals.keys()))])
         pandas_series = pandas.Series(vals[next(iter(vals.keys()))])
-    elif isinstance(vals, list):
+    else:
         modin_series = pd.Series(vals)
         pandas_series = pandas.Series(vals)
     return modin_series, pandas_series
@@ -2116,6 +2117,40 @@ def test_ravel(data, order):
     )
 
 
+# TODO: remove xfail mark then #1628 will be fixed
+@pytest.mark.xfail(
+    reason="Modin Series with category dtype is buggy for now. See #1628 for more details."
+)
+@pytest.mark.parametrize(
+    "data",
+    [
+        pandas.Categorical(np.arange(1000), ordered=True),
+        pandas.Categorical(np.arange(1000), ordered=False),
+        pandas.Categorical(np.arange(1000), categories=np.arange(500), ordered=True),
+        pandas.Categorical(np.arange(1000), categories=np.arange(500), ordered=False),
+    ],
+)
+@pytest.mark.parametrize("order", [None, "C", "F", "A", "K"])
+def test_ravel_category(data, order):
+    modin_series, pandas_series = create_test_series(data)
+    categories_equals(modin_series.ravel(order=order), pandas_series.ravel(order=order))
+
+
+@pytest.mark.parametrize(
+    "data",
+    [
+        pandas.Categorical(np.arange(10), ordered=True),
+        pandas.Categorical(np.arange(10), ordered=False),
+        pandas.Categorical(np.arange(10), categories=np.arange(5), ordered=True),
+        pandas.Categorical(np.arange(10), categories=np.arange(5), ordered=False),
+    ],
+)
+@pytest.mark.parametrize("order", [None, "C", "F", "A", "K"])
+def test_ravel_simple_category(data, order):
+    modin_series, pandas_series = create_test_series(data)
+    categories_equals(modin_series.ravel(order=order), pandas_series.ravel(order=order))
+
+
 @pytest.mark.parametrize("data", test_data_values, ids=test_data_keys)
 def test_rdiv(data):
     modin_series, pandas_series = create_test_series(data)
diff --git a/modin/pandas/test/utils.py b/modin/pandas/test/utils.py
--- a/modin/pandas/test/utils.py
+++ b/modin/pandas/test/utils.py
@@ -373,6 +373,12 @@
 ]
 
 
+def categories_equals(left, right):
+    assert (left.ordered and right.ordered) or (not left.ordered and not right.ordered)
+    is_category_ordered = left.ordered
+    assert_categorical_equal(left, right, check_category_order=is_category_ordered)
+
+
 def df_categories_equals(df1, df2):
     categories_columns = df1.select_dtypes(include="category").columns