Repository: modin-project/modin

Issue:
OmniSci engine incorrectly treats N/A first element in a column as a marker of unsupported column
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:
- **Modin version** (`modin.__version__`): 0.9.1
- **Python version**: 3.7
- **Code we can use to reproduce**:

```python
import pandas
from modin.experimental.engines.omnisci_on_ray.frame.partition_manager import OmnisciOnRayFrameManager
print('none-first', OmnisciOnRayFrameManager._get_unsupported_cols(pandas.DataFrame({'col': [None, 'a', 'b']})))
print('value-first', OmnisciOnRayFrameManager._get_unsupported_cols(pandas.DataFrame({'col': ['a', 'a', 'b']})))
```

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem

Current output:
```
none-first (None, ['col'])
value-first (pyarrow.Table
col: string, [])
```

Expected output:
```
none-first (pyarrow.Table
col: string, [])
value-first (pyarrow.Table
col: string, [])
```
<!-- Describe the problem clearly here. -->
As can be seen here:

https://github.com/modin-project/modin/blob/6bc748e6e835ecfb6e9779aa93cbd47c2f412511/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py#L105-L113

`OmnisciOnRayFrameManager` only ever checks first element in each `object` column, and treats the whole column as "unsupported" if that value is not a string. However, N/A values (at least `None`) are perfectly fine, as the second `print()` shows.

This mis-treatment can cause other routines to unnecessary "default-to-pandas" reducing the performance.



Discussion:

Resolving patch:
diff --git a/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py b/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py
--- a/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py
+++ b/modin/experimental/engines/omnisci_on_ray/frame/partition_manager.py
@@ -11,6 +11,7 @@
 # ANY KIND, either express or implied. See the License for the specific language
 # governing permissions and limitations under the License.
 
+from modin.pandas.utils import is_scalar
 import numpy as np
 
 from modin.engines.ray.generic.frame.partition_manager import RayFrameManager
@@ -109,7 +110,10 @@ def _get_unsupported_cols(cls, obj):
             type_samples = obj.iloc[0][cols]
 
             unsupported_cols = [
-                name for name, col in type_samples.items() if not isinstance(col, str)
+                name
+                for name, col in type_samples.items()
+                if not isinstance(col, str)
+                and not (is_scalar(col) and pandas.isna(col))
             ]
 
             if len(unsupported_cols) > 0:
@@ -117,7 +121,7 @@ def _get_unsupported_cols(cls, obj):
 
             try:
                 at = pyarrow.Table.from_pandas(obj)
-            except pyarrow.lib.ArrowTypeError as e:
+            except (pyarrow.lib.ArrowTypeError, pyarrow.lib.ArrowInvalid) as e:
                 regex = r"Conversion failed for column ([^\W]*)"
                 unsupported_cols = []
                 for msg in e.args:


Test patch:
diff --git a/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py b/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
--- a/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
+++ b/modin/experimental/engines/omnisci_on_ray/test/test_dataframe.py
@@ -35,6 +35,10 @@
     eval_io,
 )
 
+from modin.experimental.engines.omnisci_on_ray.frame.partition_manager import (
+    OmnisciOnRayFrameManager,
+)
+
 
 def set_execution_mode(frame, mode, recursive=False):
     if isinstance(frame, (pd.Series, pd.DataFrame)):
@@ -1709,5 +1713,24 @@ def applier(df, *args, **kwargs):
         run_and_compare(applier, data=self.data)
 
 
+class TestUnsupportedColumns:
+    @pytest.mark.parametrize(
+        "data,is_good",
+        [
+            [["1", "2", None, "2", "1"], True],
+            [[None, "3", None, "2", "1"], True],
+            [[1, "2", None, "2", "1"], False],
+            [[None, 3, None, "2", "1"], False],
+        ],
+    )
+    def test_unsupported_columns(self, data, is_good):
+        pandas_df = pandas.DataFrame({"col": data})
+        obj, bad_cols = OmnisciOnRayFrameManager._get_unsupported_cols(pandas_df)
+        if is_good:
+            assert obj and not bad_cols
+        else:
+            assert not obj and bad_cols == ["col"]
+
+
 if __name__ == "__main__":
     pytest.main(["-v", __file__])