# Buggy code
def _tile_via_shuffle(cls, op):
    # rechunk the axes except the axis to do unique into 1 chunk
    inp = op.inputs[0]
    if inp.ndim > 1:
        new_chunk_size = dict()
        for axis in range(inp.ndim):
            if axis == op.axis:
                continue
            if np.isnan(inp.shape[axis]):
                raise TilesError(
                    "input tensor has unknown shape on axis {}".format(axis)
                )
            new_chunk_size[axis] = inp.shape[axis]
        check_chunks_unknown_shape([inp], TilesError)
        inp = inp.rechunk(new_chunk_size)._inplace_tile()

    aggregate_size = op.aggregate_size
    if aggregate_size is None:
        aggregate_size = max(inp.chunk_shape[op.axis] // options.combine_size, 1)

    unique_on_chunk_sizes = inp.nsplits[op.axis]
    start_poses = np.cumsum((0,) + unique_on_chunk_sizes)[:-1]
    map_chunks = []
    for c in inp.chunks:
        map_op = TensorUnique(
            stage=OperandStage.map,
            return_index=op.return_index,
            return_inverse=op.return_inverse,
            return_counts=op.return_counts,
            axis=op.axis,
            aggregate_size=aggregate_size,
            start_pos=start_poses[c.index[op.axis]],
            dtype=inp.dtype,
        )
        shape = list(c.shape)
        shape[op.axis] = np.nan
        map_chunks.append(map_op.new_chunk([c], shape=tuple(shape), index=c.index))

    shuffle_chunk = TensorShuffleProxy(
        dtype=inp.dtype, _tensor_keys=[inp.op.key]
    ).new_chunk(map_chunks, shape=())

    reduce_chunks = [list() for _ in range(len(op.outputs))]
    for i in range(aggregate_size):
        reduce_op = TensorUnique(
            stage=OperandStage.reduce,
            return_index=op.return_index,
            return_inverse=op.return_inverse,
            return_counts=op.return_counts,
            axis=op.axis,
            aggregate_id=i,
            shuffle_key=str(i),
        )
        kws = cls._gen_kws(op, inp, chunk=True, chunk_index=i)
        chunks = reduce_op.new_chunks(
            [shuffle_chunk], kws=kws, order=op.outputs[0].order
        )
        for j, c in enumerate(chunks):
            reduce_chunks[j].append(c)

    if op.return_inverse:
        inverse_pos = 2 if op.return_index else 1
        map_inverse_chunks = reduce_chunks[inverse_pos]
        inverse_shuffle_chunk = TensorShuffleProxy(
            dtype=map_inverse_chunks[0].dtype
        ).new_chunk(map_inverse_chunks, shape=())
        inverse_chunks = []
        for j, cs in enumerate(unique_on_chunk_sizes):
            chunk_op = TensorUniqueInverseReduce(
                dtype=map_inverse_chunks[0].dtype, shuffle_key=str(j)
            )
            inverse_chunk = chunk_op.new_chunk(
                [inverse_shuffle_chunk], shape=(cs,), index=(j,)
            )
            inverse_chunks.append(inverse_chunk)
        reduce_chunks[inverse_pos] = inverse_chunks

    kws = [out.params for out in op.outputs]
    for kw, chunks in zip(kws, reduce_chunks):
        kw["chunks"] = chunks
    unique_nsplits = list(inp.nsplits)
    unique_nsplits[op.axis] = (np.nan,) * len(reduce_chunks[0])
    kws[0]["nsplits"] = tuple(unique_nsplits)
    i = 1
    if op.return_index:
        kws[i]["nsplits"] = ((np.nan,) * len(reduce_chunks[i]),)
        i += 1
    if op.return_inverse:
        kws[i]["nsplits"] = (inp.nsplits[op.axis],)
        i += 1
    if op.return_counts:
        kws[i]["nsplits"] = ((np.nan,) * len(reduce_chunks[i]),)

    new_op = op.copy()
    return new_op.new_tensors(op.inputs, kws=kws)


# Traceback (AttributeError)
In [3]: import pandas as pd

In [4]: data = pd.DataFrame(np.arange(20).reshape((4, 5)) + 1, columns=['a', 'b', 'c', 'd', 'e'])

In [6]: df = md.DataFrame(data)

In [7]: df.groupby(['a','b']).size().execute()
Unexpected exception occurred in enter_build_mode.<locals>.inner.
Traceback (most recent call last):
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 353, in _wrapped
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 493, in inner
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/scheduler/graph.py", line 633, in prepare_graph
self._target_tileable_datas + fetch_tileables, tileable_graph)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 399, in _wrapped
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 493, in inner
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 350, in build
tileables, tileable_graph=tileable_graph)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 399, in _wrapped
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 493, in inner
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 263, in build
self._on_tile_failure(tileable_data.op, exc_info)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 302, in inner
raise exc_info[1].with_traceback(exc_info[2]) from None
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 243, in build
tiled = self._tile(tileable_data, tileable_graph)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 338, in _tile
return super()._tile(tileable_data, tileable_graph)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 203, in _tile
tds = on_tile(tileable_data.op.outputs, tds)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/scheduler/graph.py", line 615, in on_tile
return self.context.wraps(handler.dispatch)(first.op)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/context.py", line 69, in h
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/utils.py", line 399, in _wrapped
return func(*args, **kwargs)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/tiles.py", line 119, in dispatch
tiled = op_cls.tile(op)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/dataframe/groupby/aggregation.py", line 481, in tile
return cls._tile_with_tree(op)
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/dataframe/groupby/aggregation.py", line 412, in _tile_with_tree
index = out_df.index_value.to_pandas()
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/dataframe/core.py", line 283, in to_pandas
return self._index_value.to_pandas()
File "/Users/hekaisheng/Documents/mars_dev/mars/mars/dataframe/core.py", line 197, in to_pandas
sortorder=self._sortorder, names=self._names)
AttributeError: _sortorder

# Fixed code
def _tile_via_shuffle(cls, op):
    # rechunk the axes except the axis to do unique into 1 chunk
    inp = op.inputs[0]
    if inp.ndim > 1:
        new_chunk_size = dict()
        for axis in range(inp.ndim):
            if axis == op.axis:
                continue
            if np.isnan(inp.shape[axis]):
                raise TilesError(
                    "input tensor has unknown shape on axis {}".format(axis)
                )
            new_chunk_size[axis] = inp.shape[axis]
        check_chunks_unknown_shape([inp], TilesError)
        inp = inp.rechunk(new_chunk_size)._inplace_tile()

    aggregate_size = op.aggregate_size
    if aggregate_size is None:
        aggregate_size = max(inp.chunk_shape[op.axis] // options.combine_size, 1)

    unique_on_chunk_sizes = inp.nsplits[op.axis]
    start_poses = np.cumsum((0,) + unique_on_chunk_sizes).tolist()[:-1]
    map_chunks = []
    for c in inp.chunks:
        map_op = TensorUnique(
            stage=OperandStage.map,
            return_index=op.return_index,
            return_inverse=op.return_inverse,
            return_counts=op.return_counts,
            axis=op.axis,
            aggregate_size=aggregate_size,
            start_pos=start_poses[c.index[op.axis]],
            dtype=inp.dtype,
        )
        shape = list(c.shape)
        shape[op.axis] = np.nan
        map_chunks.append(map_op.new_chunk([c], shape=tuple(shape), index=c.index))

    shuffle_chunk = TensorShuffleProxy(
        dtype=inp.dtype, _tensor_keys=[inp.op.key]
    ).new_chunk(map_chunks, shape=())

    reduce_chunks = [list() for _ in range(len(op.outputs))]
    for i in range(aggregate_size):
        reduce_op = TensorUnique(
            stage=OperandStage.reduce,
            return_index=op.return_index,
            return_inverse=op.return_inverse,
            return_counts=op.return_counts,
            axis=op.axis,
            aggregate_id=i,
            shuffle_key=str(i),
        )
        kws = cls._gen_kws(op, inp, chunk=True, chunk_index=i)
        chunks = reduce_op.new_chunks(
            [shuffle_chunk], kws=kws, order=op.outputs[0].order
        )
        for j, c in enumerate(chunks):
            reduce_chunks[j].append(c)

    if op.return_inverse:
        inverse_pos = 2 if op.return_index else 1
        map_inverse_chunks = reduce_chunks[inverse_pos]
        inverse_shuffle_chunk = TensorShuffleProxy(
            dtype=map_inverse_chunks[0].dtype
        ).new_chunk(map_inverse_chunks, shape=())
        inverse_chunks = []
        for j, cs in enumerate(unique_on_chunk_sizes):
            chunk_op = TensorUniqueInverseReduce(
                dtype=map_inverse_chunks[0].dtype, shuffle_key=str(j)
            )
            inverse_chunk = chunk_op.new_chunk(
                [inverse_shuffle_chunk], shape=(cs,), index=(j,)
            )
            inverse_chunks.append(inverse_chunk)
        reduce_chunks[inverse_pos] = inverse_chunks

    kws = [out.params for out in op.outputs]
    for kw, chunks in zip(kws, reduce_chunks):
        kw["chunks"] = chunks
    unique_nsplits = list(inp.nsplits)
    unique_nsplits[op.axis] = (np.nan,) * len(reduce_chunks[0])
    kws[0]["nsplits"] = tuple(unique_nsplits)
    i = 1
    if op.return_index:
        kws[i]["nsplits"] = ((np.nan,) * len(reduce_chunks[i]),)
        i += 1
    if op.return_inverse:
        kws[i]["nsplits"] = (inp.nsplits[op.axis],)
        i += 1
    if op.return_counts:
        kws[i]["nsplits"] = ((np.nan,) * len(reduce_chunks[i]),)

    new_op = op.copy()
    return new_op.new_tensors(op.inputs, kws=kws)