Repository: modin-project/modin

Issue:
``dataframe.loc()`` doesn`t work correctly at MODIN_STORAGE_FORMAT=omnisci
When I run:
```
import modin.pandas as pd
import modin.config as cfg
cfg.StorageFormat.put('omnisci')

modin_df = pd.DataFrame(index=range(5))
modin_df.loc[1]
```
I have error:
```
KeyError: 1
```


Discussion:
@modin-project/modin-omnisci  could someone please check if this still happens?

Resolving patch:
diff --git a/modin/experimental/core/execution/native/implementations/hdk_on_native/dataframe/dataframe.py b/modin/experimental/core/execution/native/implementations/hdk_on_native/dataframe/dataframe.py
--- a/modin/experimental/core/execution/native/implementations/hdk_on_native/dataframe/dataframe.py
+++ b/modin/experimental/core/execution/native/implementations/hdk_on_native/dataframe/dataframe.py
@@ -214,7 +214,7 @@ def __init__(
         if self._has_arrow_table() and self._partitions.size > 0:
             assert self._partitions.size == 1
             table = self._partitions[0][0].get()
-            if table.column_names[0] != f"F_{self._table_cols[0]}":
+            if len(table) > 0 and table.column_names[0] != f"F_{self._table_cols[0]}":
                 new_names = [f"F_{col}" for col in table.column_names]
                 new_table = table.rename_columns(new_names)
                 self._partitions[0][
@@ -359,7 +359,7 @@ def _has_arrow_table(self):
         """
         if not isinstance(self._op, FrameNode):
             return False
-        return all(p.arrow_table for p in self._partitions.flatten())
+        return all(p.arrow_table is not None for p in self._partitions.flatten())
 
     def _dtypes_for_cols(self, new_index, new_columns):
         """
@@ -2380,17 +2380,21 @@ def from_pandas(cls, df):
         new_index = df.index
         new_columns = df.columns
         # If there is non-trivial index, we put it into columns.
+        # If the index is trivial, but there are no columns, we put
+        # it into columns either because, otherwise, we don't know
+        # the number of rows and, thus, unable to restore the index.
         # That's what we usually have for arrow tables and execution
         # result. Unnamed index is renamed to __index__. Also all
-        # columns get 'F_' prefix to handle names unsupported in
-        # HDK.
-        if cls._is_trivial_index(df.index):
+        # columns get 'F_' prefix to handle names unsupported in HDK.
+        if len(new_index) == 0 or (
+            len(new_columns) != 0 and cls._is_trivial_index(new_index)
+        ):
             index_cols = None
         else:
-            orig_index_names = df.index.names
+            orig_index_names = new_index.names
             orig_df = df
 
-            index_cols = cls._mangle_index_names(df.index.names)
+            index_cols = cls._mangle_index_names(new_index.names)
             df.index.names = index_cols
             df = df.reset_index()
 
diff --git a/modin/experimental/core/execution/native/implementations/hdk_on_native/partitioning/partition_manager.py b/modin/experimental/core/execution/native/implementations/hdk_on_native/partitioning/partition_manager.py
--- a/modin/experimental/core/execution/native/implementations/hdk_on_native/partitioning/partition_manager.py
+++ b/modin/experimental/core/execution/native/implementations/hdk_on_native/partitioning/partition_manager.py
@@ -74,33 +74,30 @@ def from_pandas(cls, df, return_dims=False):
             Tuple holding array of partitions, list of columns with unsupported
             data and optionally partitions' dimensions.
         """
-
-        def tuple_wrapper(obj):
-            """
-            Wrap non-tuple object into a tuple.
-
-            Parameters
-            ----------
-            obj : Any.
-                Wrapped object.
-
-            Returns
-            -------
-            tuple
-            """
-            if not isinstance(obj, tuple):
-                obj = (obj,)
-            return obj
-
-        if df.empty:
-            return (*tuple_wrapper(super().from_pandas(df, return_dims)), [])
-
         at, unsupported_cols = cls._get_unsupported_cols(df)
 
         if len(unsupported_cols) > 0:
             # Putting pandas frame into partitions instead of arrow table, because we know
             # that all of operations with this frame will be default to pandas and don't want
             # unnecessaries conversion pandas->arrow->pandas
+
+            def tuple_wrapper(obj):
+                """
+                Wrap non-tuple object into a tuple.
+
+                Parameters
+                ----------
+                obj : Any.
+                    Wrapped object.
+
+                Returns
+                -------
+                tuple
+                """
+                if not isinstance(obj, tuple):
+                    obj = (obj,)
+                return obj
+
             return (
                 *tuple_wrapper(super().from_pandas(df, return_dims)),
                 unsupported_cols,
@@ -163,21 +160,24 @@ def _get_unsupported_cols(cls, obj):
         if isinstance(obj, (pandas.Series, pandas.DataFrame)):
             # picking first rows from cols with `dtype="object"` to check its actual type,
             # in case of homogen columns that saves us unnecessary convertion to arrow table
-            cols = [name for name, col in obj.dtypes.items() if col == "object"]
-            type_samples = obj.iloc[0][cols]
 
-            unsupported_cols = [
-                name
-                for name, col in type_samples.items()
-                if not isinstance(col, str)
-                and not (is_scalar(col) and pandas.isna(col))
-            ]
+            if obj.empty:
+                unsupported_cols = []
+            else:
+                cols = [name for name, col in obj.dtypes.items() if col == "object"]
+                type_samples = obj.iloc[0][cols]
+                unsupported_cols = [
+                    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:
                 return None, unsupported_cols
 
             try:
-                at = pyarrow.Table.from_pandas(obj)
+                at = pyarrow.Table.from_pandas(obj, preserve_index=False)
             except (pyarrow.lib.ArrowTypeError, pyarrow.lib.ArrowInvalid) as err:
                 regex = r"Conversion failed for column ([^\W]*)"
                 unsupported_cols = []
@@ -197,6 +197,7 @@ def is_supported_dtype(dtype):
                 pyarrow.types.is_string(dtype)
                 or pyarrow.types.is_time(dtype)
                 or pyarrow.types.is_dictionary(dtype)
+                or pyarrow.types.is_null(dtype)
             ):
                 return True
             try:


Test patch:
diff --git a/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py b/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
--- a/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
+++ b/modin/experimental/core/execution/native/implementations/hdk_on_native/test/test_dataframe.py
@@ -2214,6 +2214,17 @@ def test_str(self):
         pds = pandas.Series(data[next(iter(data.keys()))])
         assert str(mds) == str(pds)
 
+    def test_no_cols(self):
+        def run_cols(df, **kwargs):
+            return df.loc[1]
+
+        run_and_compare(
+            fn=run_cols,
+            data=None,
+            constructor_kwargs={"index": range(5)},
+            force_lazy=False,
+        )
+
 
 if __name__ == "__main__":
     pytest.main(["-v", __file__])