Repository: modin-project/modin

Issue:
DataFrame.loc not ordering columns correctly
<!--
General questions should be asked on the mailing list modin-dev@googlegroups.com.

Before submitting an issue, please fill out the following form.
-->

### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**:
- **Modin installed from (source or binary)**: source
- **Modin version**: current master 
- **Python version**: python 3.6
- **Exact command to reproduce**:

<!--
You can obtain the Modin version with

python -c "import modin; print(modin.__version__)"
-->

### Describe the problem
<!-- Describe the problem clearly here. -->
When passing in a list of columns to `loc`, the columns don't get ordered correctly, although the labels appear in the right order. In the example i was working with, the columns were reversed, yet the labels were in the correct order 

### 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. -->
This is the line of code triggering the problem:
```python
data2 = data.loc[:,['degree_spondylolisthesis','pelvic_radius']]
```



Discussion:
Thanks @mcs526!

It looks isolated to `loc`. It is working correctly on `data[['degree_spondylolisthesis','pelvic_radius']]`

Resolving patch:
diff --git a/modin/engines/base/block_partitions.py b/modin/engines/base/block_partitions.py
--- a/modin/engines/base/block_partitions.py
+++ b/modin/engines/base/block_partitions.py
@@ -909,20 +909,31 @@ def apply_func_to_indices_both_axis(
             it must use `row_internal_indices, col_internal_indices` as keyword
             arguments.
         """
+        if keep_remaining:
+            row_partitions_list = self._get_dict_of_block_index(1, row_indices).items()
+            col_partitions_list = self._get_dict_of_block_index(0, col_indices).items()
+        else:
+            row_partitions_list = self._get_dict_of_block_index(
+                1, row_indices, ordered=True
+            )
+            col_partitions_list = self._get_dict_of_block_index(
+                0, col_indices, ordered=True
+            )
+            result = np.empty(
+                (len(row_partitions_list), len(col_partitions_list)), dtype=type(self)
+            )
+
         if not mutate:
             partition_copy = self.partitions.copy()
         else:
             partition_copy = self.partitions
 
-        operation_mask = np.full(self.partitions.shape, False)
         row_position_counter = 0
-        for row_blk_idx, row_internal_idx in self._get_dict_of_block_index(
-            1, row_indices
-        ).items():
+        for row_idx, row_values in enumerate(row_partitions_list):
+            row_blk_idx, row_internal_idx = row_values
             col_position_counter = 0
-            for col_blk_idx, col_internal_idx in self._get_dict_of_block_index(
-                0, col_indices
-            ).items():
+            for col_idx, col_values in enumerate(col_partitions_list):
+                col_blk_idx, col_internal_idx = col_values
                 remote_part = partition_copy[row_blk_idx, col_blk_idx]
 
                 if item_to_distribute is not None:
@@ -937,30 +948,31 @@ def apply_func_to_indices_both_axis(
                     item = {}
 
                 if lazy:
-                    result = remote_part.add_to_apply_calls(
+                    block_result = remote_part.add_to_apply_calls(
                         func,
                         row_internal_indices=row_internal_idx,
                         col_internal_indices=col_internal_idx,
                         **item
                     )
                 else:
-                    result = remote_part.apply(
+                    block_result = remote_part.apply(
                         func,
                         row_internal_indices=row_internal_idx,
                         col_internal_indices=col_internal_idx,
                         **item
                     )
-                partition_copy[row_blk_idx, col_blk_idx] = result
-                operation_mask[row_blk_idx, col_blk_idx] = True
+                if keep_remaining:
+                    partition_copy[row_blk_idx, col_blk_idx] = block_result
+                else:
+                    result[row_idx][col_idx] = block_result
                 col_position_counter += len(col_internal_idx)
 
             row_position_counter += len(row_internal_idx)
 
-        column_idx = np.where(np.any(operation_mask, axis=0))[0]
-        row_idx = np.where(np.any(operation_mask, axis=1))[0]
-        if not keep_remaining:
-            partition_copy = partition_copy[row_idx][:, column_idx]
-        return self.__constructor__(partition_copy)
+        if keep_remaining:
+            return self.__constructor__(partition_copy)
+        else:
+            return self.__constructor__(result)
 
     def inter_data_operation(self, axis, func, other):
         """Apply a function that requires two BaseBlockPartitions objects.


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
@@ -2442,6 +2442,10 @@ def test_loc(request, data):
         # df_equals(modin_df.loc[[1, 2], ['col1']], pandas_df.loc[[1, 2], ['col1']])
         df_equals(modin_df.loc[1:2, key1:key2], pandas_df.loc[1:2, key1:key2])
 
+        # From issue #421
+        df_equals(modin_df.loc[:, [key2, key1]], pandas_df.loc[:, [key2, key1]])
+        df_equals(modin_df.loc[[2, 1], :], pandas_df.loc[[2, 1], :])
+
         # Write Item
         modin_df_copy = modin_df.copy()
         pandas_df_copy = pandas_df.copy()