# Buggy code
def decide_dataframe_chunk_sizes(shape, chunk_size, memory_usage):
    """
    Decide how a given DataFrame can be split into chunk.

    :param shape: DataFrame's shape
    :param chunk_size: if dict provided, it's dimension id to chunk size;
                       if provided, it's the chunk size for each dimension.
    :param memory_usage: pandas Series in which each column's memory usage
    :type memory_usage: pandas.Series
    :return: the calculated chunk size for each dimension
    :rtype: tuple
    """
    from ..config import options

    chunk_size = dictify_chunk_size(shape, chunk_size)
    average_memory_usage = memory_usage / shape[0]

    nleft = len(shape) - len(chunk_size)
    if nleft < 0:
        raise ValueError("chunks have more than two dimensions")
    if nleft == 0:
        return normalize_chunk_sizes(
            shape, tuple(chunk_size[j] for j in range(len(shape)))
        )

    max_chunk_size = options.chunk_store_limit

    # for the row side, along axis 0
    if 0 not in chunk_size:
        row_chunk_size = []
        row_left_size = shape[0]
    else:
        row_chunk_size = normalize_chunk_sizes((shape[0],), (chunk_size[0],))[0]
        row_left_size = 0
    # for the column side, along axis 1
    if 1 not in chunk_size:
        col_chunk_size = []
        col_chunk_store = []
        col_left_size = shape[1]
    else:
        col_chunk_size = normalize_chunk_sizes((shape[1],), (chunk_size[1],))[0]
        acc = [0] + np.cumsum(col_chunk_size).tolist()
        col_chunk_store = [
            average_memory_usage[acc[i] : acc[i + 1]].sum()
            for i in range(len(col_chunk_size))
        ]
        col_left_size = 0

    while True:
        nbytes_occupied = np.prod(
            [max(it) for it in (row_chunk_size, col_chunk_store) if it]
        )
        dim_size = np.maximum(
            int(np.power(max_chunk_size / nbytes_occupied, 1 / float(nleft))), 1
        )
        # check col first
        if col_left_size > 0:
            cs = min(col_left_size, dim_size)
            col_chunk_size.append(cs)
            start = int(np.sum(col_chunk_size[:-1]))
            col_chunk_store.append(average_memory_usage.iloc[start : start + cs].sum())
            col_left_size -= cs
        if row_left_size > 0:
            max_col_chunk_store = max(col_chunk_store)
            cs = min(row_left_size, int(max_chunk_size / max_col_chunk_store))
            row_chunk_size.append(cs)
            row_left_size -= cs

        if col_left_size == 0 and row_left_size == 0:
            break

    return tuple(row_chunk_size), tuple(col_chunk_size)


# Traceback (TilesError)
In [1]: import pandas as pd

In [2]: a = pd.DataFrame(columns=list('ab'))

In [3]: import mars.dataframe as md

In [4]: md.DataFrame(a).iloc[:2].execute()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-4-426db4e594a4> in <module>
----> 1 md.DataFrame(a).iloc[:2].execute()

~/Documents/mars_dev/mars/mars/core.py in execute(self, session, **kw)
576
577     def execute(self, session=None, **kw):
--> 578         self._data.execute(session, **kw)
579         return self
580

~/Documents/mars_dev/mars/mars/core.py in execute(self, session, **kw)
364
365         # no more fetch, thus just fire run
--> 366         session.run(self, **kw)
367         # return Tileable or ExecutableTuple itself
368         return self

~/Documents/mars_dev/mars/mars/session.py in run(self, *tileables, **kw)
478         tileables = tuple(mt.tensor(t) if not isinstance(t, (Entity, Base)) else t
479                           for t in tileables)
--> 480         result = self._sess.run(*tileables, **kw)
481
482         for t in tileables:

~/Documents/mars_dev/mars/mars/session.py in run(self, *tileables, **kw)
105             # set number of running cores
106             self.context.set_ncores(kw['n_parallel'])
--> 107             res = self._executor.execute_tileables(tileables, **kw)
108             return res
109

~/Documents/mars_dev/mars/mars/utils.py in _wrapped(*args, **kwargs)
406                 _kernel_mode.eager = False
407             _kernel_mode.eager_count = enter_eager_count + 1
--> 408             return func(*args, **kwargs)
409         finally:
410             _kernel_mode.eager_count -= 1

~/Documents/mars_dev/mars/mars/utils.py in inner(*args, **kwargs)
500     def inner(*args, **kwargs):
501         with build_mode():
--> 502             return func(*args, **kwargs)
503     return inner
504

~/Documents/mars_dev/mars/mars/executor.py in execute_tileables(self, tileables, fetch, n_parallel, n_thread, print_progress, mock, compose, name)
862                 # build chunk graph, tile will be done during building
863                 chunk_graph = chunk_graph_builder.build(
--> 864                     tileables, tileable_graph=tileable_graph)
865                 tileable_graph = chunk_graph_builder.prev_tileable_graph
866                 temp_result_keys = set(result_keys)

~/Documents/mars_dev/mars/mars/utils.py in _wrapped(*args, **kwargs)
406                 _kernel_mode.eager = False
407             _kernel_mode.eager_count = enter_eager_count + 1
--> 408             return func(*args, **kwargs)
409         finally:
410             _kernel_mode.eager_count -= 1

~/Documents/mars_dev/mars/mars/utils.py in inner(*args, **kwargs)
500     def inner(*args, **kwargs):
501         with build_mode():
--> 502             return func(*args, **kwargs)
503     return inner
504

~/Documents/mars_dev/mars/mars/tiles.py in build(self, tileables, tileable_graph)
348
349         chunk_graph = super().build(
--> 350             tileables, tileable_graph=tileable_graph)
351         self._iterative_chunk_graphs.append(chunk_graph)
352         if len(self._interrupted_ops) == 0:

~/Documents/mars_dev/mars/mars/utils.py in _wrapped(*args, **kwargs)
406                 _kernel_mode.eager = False
407             _kernel_mode.eager_count = enter_eager_count + 1
--> 408             return func(*args, **kwargs)
409         finally:
410             _kernel_mode.eager_count -= 1

~/Documents/mars_dev/mars/mars/utils.py in inner(*args, **kwargs)
500     def inner(*args, **kwargs):
501         with build_mode():
--> 502             return func(*args, **kwargs)
503     return inner
504

~/Documents/mars_dev/mars/mars/tiles.py in build(self, tileables, tileable_graph)
261                     # for further execution
262                     partial_tiled_chunks = \
--> 263                         self._on_tile_failure(tileable_data.op, exc_info)
264                     if partial_tiled_chunks is not None and \
265                             len(partial_tiled_chunks) > 0:

~/Documents/mars_dev/mars/mars/tiles.py in inner(op, exc_info)
300                     on_tile_failure(op, exc_info)
301                 else:
--> 302                     raise exc_info[1].with_traceback(exc_info[2]) from None
303         return inner
304

~/Documents/mars_dev/mars/mars/tiles.py in build(self, tileables, tileable_graph)
241                 continue
242             try:
--> 243                 tiled = self._tile(tileable_data, tileable_graph)
244                 tiled_op.add(tileable_data.op)
245                 for t, td in zip(tileable_data.op.outputs, tiled):

~/Documents/mars_dev/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
336         if any(inp.op in self._interrupted_ops for inp in tileable_data.inputs):
337             raise TilesError('Tile fail due to failure of inputs')
--> 338         return super()._tile(tileable_data, tileable_graph)
339
340     @kernel_mode

~/Documents/mars_dev/mars/mars/tiles.py in _tile(self, tileable_data, tileable_graph)
199                 t._nsplits = o.nsplits
200         elif on_tile is None:
--> 201             tds[0]._inplace_tile()
202         else:
203             tds = on_tile(tileable_data.op.outputs, tds)

~/Documents/mars_dev/mars/mars/core.py in _inplace_tile(self)
161
162     def _inplace_tile(self):
--> 163         return handler.inplace_tile(self)
164
165     def __getattr__(self, attr):

~/Documents/mars_dev/mars/mars/tiles.py in inplace_tile(self, to_tile)
134         if not to_tile.is_coarse():
135             return to_tile
--> 136         dispatched = self.dispatch(to_tile.op)
137         self._assign_to([d.data for d in dispatched], to_tile.op.outputs)
138         return to_tile

~/Documents/mars_dev/mars/mars/utils.py in _wrapped(*args, **kwargs)
406                 _kernel_mode.eager = False
407             _kernel_mode.eager_count = enter_eager_count + 1
--> 408             return func(*args, **kwargs)
409         finally:
410             _kernel_mode.eager_count -= 1

~/Documents/mars_dev/mars/mars/tiles.py in dispatch(self, op)
117         else:
118             try:
--> 119                 tiled = op_cls.tile(op)
120             except NotImplementedError as ex:
121                 cause = ex

~/Documents/mars_dev/mars/mars/dataframe/indexing/iloc.py in tile(cls, op)
339
340         handler = DataFrameIlocIndexesHandler()
--> 341         return [handler.handle(op)]
342
343     @classmethod

~/Documents/mars_dev/mars/mars/tensor/indexing/index_lib.py in handle(self, op, return_context)
896
897         self._preprocess(context, index_infos)
--> 898         self._process(context, index_infos)
899         self._postprocess(context, index_infos)
900

~/Documents/mars_dev/mars/mars/tensor/indexing/index_lib.py in _process(cls, context, index_infos)
923         index_to_shape = OrderedDict(sorted([(c.index, c.shape) for c in out_chunks],
924                                             key=itemgetter(0)))
--> 925         context.out_nsplits = calc_nsplits(index_to_shape)
926
927     @classmethod

~/Documents/mars_dev/mars/mars/utils.py in calc_nsplits(chunk_idx_to_shape)
664     :return: nsplits
665     """
--> 666     ndim = len(next(iter(chunk_idx_to_shape)))
667     tileable_nsplits = []
668     # for each dimension, record chunk shape whose index is zero on other dimensions

StopIteration:

# Fixed code
def decide_dataframe_chunk_sizes(shape, chunk_size, memory_usage):
    """
    Decide how a given DataFrame can be split into chunk.

    :param shape: DataFrame's shape
    :param chunk_size: if dict provided, it's dimension id to chunk size;
                       if provided, it's the chunk size for each dimension.
    :param memory_usage: pandas Series in which each column's memory usage
    :type memory_usage: pandas.Series
    :return: the calculated chunk size for each dimension
    :rtype: tuple
    """
    from ..config import options

    chunk_size = dictify_chunk_size(shape, chunk_size)
    average_memory_usage = memory_usage / shape[0]

    nleft = len(shape) - len(chunk_size)
    if nleft < 0:
        raise ValueError("chunks have more than two dimensions")
    if nleft == 0:
        return normalize_chunk_sizes(
            shape, tuple(chunk_size[j] for j in range(len(shape)))
        )

    max_chunk_size = options.chunk_store_limit

    # for the row side, along axis 0
    if 0 not in chunk_size:
        row_chunk_size = []
        row_left_size = shape[0]
    else:
        row_chunk_size = normalize_chunk_sizes((shape[0],), (chunk_size[0],))[0]
        row_left_size = -1
    # for the column side, along axis 1
    if 1 not in chunk_size:
        col_chunk_size = []
        col_chunk_store = []
        col_left_size = shape[1]
    else:
        col_chunk_size = normalize_chunk_sizes((shape[1],), (chunk_size[1],))[0]
        acc = [0] + np.cumsum(col_chunk_size).tolist()
        col_chunk_store = [
            average_memory_usage[acc[i] : acc[i + 1]].sum()
            for i in range(len(col_chunk_size))
        ]
        col_left_size = -1

    while True:
        nbytes_occupied = np.prod(
            [max(it) for it in (row_chunk_size, col_chunk_store) if it]
        )
        dim_size = np.maximum(
            int(np.power(max_chunk_size / nbytes_occupied, 1 / float(nleft))), 1
        )

        if col_left_size == 0:
            col_chunk_size.append(0)

        if row_left_size == 0:
            row_chunk_size.append(0)

        # check col first
        if col_left_size > 0:
            cs = min(col_left_size, dim_size)
            col_chunk_size.append(cs)
            start = int(np.sum(col_chunk_size[:-1]))
            col_chunk_store.append(average_memory_usage.iloc[start : start + cs].sum())
            col_left_size -= cs
        if row_left_size > 0:
            max_col_chunk_store = max(col_chunk_store)
            cs = min(row_left_size, int(max_chunk_size / max_col_chunk_store))
            row_chunk_size.append(cs)
            row_left_size -= cs

        if col_left_size <= 0 and row_left_size <= 0:
            break

    return tuple(row_chunk_size), tuple(col_chunk_size)