Repository: mars-project/mars

Issue:
[BUG] df.loc failed when df is empty: RuntimeError: generator raised StopIteration
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

df.loc failed when df is empty: RuntimeError: generator raised StopIteration.

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use
3. Versions of crucial packages, such as numpy, scipy and pandas
4. Full stack of the error.
5. Minimized code to reproduce the error.

```
In [8]: from mars.dataframe import DataFrame 
   ...: df = DataFrame(pd.DataFrame({'a': [], 'b': []})) 
   ...: df.set_index('a') 
   ...: print(df.loc[df.index.to_tensor().to_numpy()].execute())                                                                                                                                            
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100.0/100 [00:00<00:00, 1274.28it/s]
  0%|                                                                                                                                                                               | 0/100 [00:00<?, ?it/s]Unexpected error happens in <function TaskProcessor.get_next_stage_processor at 0x7fd82e1b1050>
Traceback (most recent call last):
  File "/Users/qinxuye/Workspace/mars/mars/tensor/indexing/index_lib.py", line 937, in _process
    context.out_nsplits = calc_nsplits(index_to_shape)
  File "/Users/qinxuye/Workspace/mars/mars/utils.py", line 569, in calc_nsplits
    ndim = len(next(iter(chunk_idx_to_shape)))
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/processor.py", line 52, in inner
    return await func(processor, *args, **kwargs)
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/processor.py", line 300, in get_next_stage_processor
    chunk_graph = await self._get_next_chunk_graph(self._chunk_graph_iter)
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/processor.py", line 235, in _get_next_chunk_graph
    chunk_graph = await fut
  File "/Users/qinxuye/Workspace/mars/mars/lib/aio/_threads.py", line 36, in to_thread
    return await loop.run_in_executor(None, func_call)
  File "/Users/qinxuye/miniconda3/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/processor.py", line 230, in next_chunk_graph
    return next(chunk_graph_iter)
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/preprocessor.py", line 141, in tile
    for chunk_graph in chunk_graph_builder.build():
  File "/Users/qinxuye/Workspace/mars/mars/core/graph/builder/chunk.py", line 237, in build
    yield from self._build()
  File "/Users/qinxuye/Workspace/mars/mars/core/graph/builder/chunk.py", line 233, in _build
    yield from self.tiler
  File "/Users/qinxuye/Workspace/mars/mars/services/task/supervisor/preprocessor.py", line 69, in __iter__
    to_update_tileables = self._iter()
  File "/Users/qinxuye/Workspace/mars/mars/core/graph/builder/chunk.py", line 179, in _iter
    next_tileable_handlers, to_update_tileables, visited)
  File "/Users/qinxuye/Workspace/mars/mars/core/graph/builder/chunk.py", line 94, in _tile
    need_process = next(tile_handler)
  File "/Users/qinxuye/Workspace/mars/mars/core/graph/builder/chunk.py", line 70, in _tile_handler
    tiled_tileables = yield from handler.tile(tiled_tileables)
  File "/Users/qinxuye/Workspace/mars/mars/core/entity/tileables.py", line 77, in tile
    tiled_result = yield from tile_handler(op)
  File "/Users/qinxuye/Workspace/mars/mars/dataframe/indexing/loc.py", line 333, in tile
    return [(yield from handler.handle(op))]
  File "/Users/qinxuye/Workspace/mars/mars/tensor/indexing/index_lib.py", line 906, in handle
    yield from self._process(context, index_infos)
RuntimeError: generator raised StopIteration
  0%|                                                            
```



Discussion:

Resolving patch:
diff --git a/mars/dataframe/indexing/index_lib.py b/mars/dataframe/indexing/index_lib.py
--- a/mars/dataframe/indexing/index_lib.py
+++ b/mars/dataframe/indexing/index_lib.py
@@ -786,8 +786,8 @@ def process(self,
             chunk_labels = chunk_index_to_labels[i]
             size = chunk_labels.size
 
-            if size == 0:
-                # not effected
+            if size == 0 and tileable.shape[0] > 0:
+                # not effected when tileable not empty and no index chosen
                 del context.chunk_index_to_info[chunk_index]
                 continue
 


Test patch:
diff --git a/mars/dataframe/indexing/tests/test_indexing_execution.py b/mars/dataframe/indexing/tests/test_indexing_execution.py
--- a/mars/dataframe/indexing/tests/test_indexing_execution.py
+++ b/mars/dataframe/indexing/tests/test_indexing_execution.py
@@ -282,12 +282,14 @@ def test_loc_getitem(setup):
     # index that is timestamp
     raw5 = raw1.copy()
     raw5.index = pd.date_range('2020-1-1', periods=5)
+    raw6 = raw1[:0]
 
     df1 = md.DataFrame(raw1, chunk_size=2)
     df2 = md.DataFrame(raw2, chunk_size=2)
     df3 = md.DataFrame(raw3, chunk_size=2)
     df4 = md.DataFrame(raw4, chunk_size=2)
     df5 = md.DataFrame(raw5, chunk_size=2)
+    df6 = md.DataFrame(raw6)
 
     df = df2.loc[3, 'b']
     result = df.execute().fetch()
@@ -382,6 +384,12 @@ def test_loc_getitem(setup):
     expected = raw5.loc['2020-1-1', 'c']
     assert result == expected
 
+    # test empty df
+    df = df6.loc[[]]
+    result = df.execute().fetch()
+    expected = raw6.loc[[]]
+    pd.testing.assert_frame_equal(result, expected)
+
 
 def test_dataframe_getitem(setup):
     data = pd.DataFrame(np.random.rand(10, 5), columns=['c1', 'c2', 'c3', 'c4', 'c5'])