# Buggy code
def _tile_chunks(cls, op, in_tensor, w, v, vi):
    out_tensor = op.outputs[0]
    extra_inputs = []
    for val in [w, v, vi]:
        if val is not None:
            extra_inputs.append(val.chunks[0])

    n = in_tensor.shape[0]
    aggregate_size = op.aggregate_size
    if aggregate_size is None:
        aggregate_size = (
            np.ceil(
                out_tensor.size * out_tensor.dtype.itemsize / options.chunk_store_limit
            )
            .astype(int)
            .item()
        )
    out_sizes = [out_tensor.size // aggregate_size for _ in range(aggregate_size)]
    for i in range(out_tensor.size % aggregate_size):
        out_sizes[i] += 1

    chunk_size = in_tensor.chunk_shape[0]
    map_chunks = []
    axis_0_cum_size = np.cumsum(in_tensor.nsplits[0])
    for i in range(chunk_size):
        for j in range(i, chunk_size):
            kw = {
                "stage": OperandStage.map,
                "a": in_tensor.cix[i, 0],
                "a_offset": axis_0_cum_size[i - 1] if i > 0 else 0,
                "out_sizes": tuple(out_sizes),
                "n": n,
                "metric": op.metric,
                "p": op.p,
                "w": w.chunks[0] if w is not None else None,
                "v": v.chunks[0] if v is not None else None,
                "vi": vi.chunks[0] if vi is not None else None,
                "dtype": out_tensor.dtype,
            }
            if i != j:
                kw["b"] = in_tensor.cix[j, 0]
                kw["b_offset"] = axis_0_cum_size[j - 1] if j > 0 else 0
            map_op = TensorPdist(**kw)
            map_chunk_inputs = [kw["a"]]
            if "b" in kw:
                map_chunk_inputs.append(kw["b"])
            if kw["w"] is not None:
                map_chunk_inputs.append(kw["w"])
            if kw["v"] is not None:
                map_chunk_inputs.append(kw["v"])
            if kw["vi"] is not None:
                map_chunk_inputs.append(kw["vi"])
            # calc chunk shape
            if i == j:
                a_axis_0_size = kw["a"].shape[0]
                chunk_shape = (a_axis_0_size * (a_axis_0_size - 1) // 2,)
            else:
                chunk_shape = (kw["a"].shape[0] * kw["b"].shape[0],)
            map_chunk = map_op.new_chunk(
                map_chunk_inputs,
                shape=chunk_shape,
                order=out_tensor.order,
                index=(i * chunk_size + j,),
            )
            map_chunks.append(map_chunk)

    proxy_chunk = TensorShuffleProxy(dtype=out_tensor.dtype).new_chunk(
        map_chunks, shape=()
    )

    reduce_chunks = []
    for p in range(aggregate_size):
        reduce_chunk_op = TensorPdist(
            stage=OperandStage.reduce, shuffle_key=str(p), dtype=out_tensor.dtype
        )
        reduce_chunk = reduce_chunk_op.new_chunk(
            [proxy_chunk], shape=(out_sizes[p],), order=out_tensor.order, index=(p,)
        )
        reduce_chunks.append(reduce_chunk)

    new_op = op.copy()
    return new_op.new_tensors(
        op.inputs,
        shape=out_tensor.shape,
        order=out_tensor.order,
        nsplits=(tuple(out_sizes),),
        chunks=reduce_chunks,
    )


# 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_chunks(cls, op, in_tensor, w, v, vi):
    out_tensor = op.outputs[0]
    extra_inputs = []
    for val in [w, v, vi]:
        if val is not None:
            extra_inputs.append(val.chunks[0])

    n = in_tensor.shape[0]
    aggregate_size = op.aggregate_size
    if aggregate_size is None:
        aggregate_size = (
            np.ceil(
                out_tensor.size * out_tensor.dtype.itemsize / options.chunk_store_limit
            )
            .astype(int)
            .item()
        )
    out_sizes = [out_tensor.size // aggregate_size for _ in range(aggregate_size)]
    for i in range(out_tensor.size % aggregate_size):
        out_sizes[i] += 1

    chunk_size = in_tensor.chunk_shape[0]
    map_chunks = []
    axis_0_cum_size = np.cumsum(in_tensor.nsplits[0]).tolist()
    for i in range(chunk_size):
        for j in range(i, chunk_size):
            kw = {
                "stage": OperandStage.map,
                "a": in_tensor.cix[i, 0],
                "a_offset": axis_0_cum_size[i - 1] if i > 0 else 0,
                "out_sizes": tuple(out_sizes),
                "n": n,
                "metric": op.metric,
                "p": op.p,
                "w": w.chunks[0] if w is not None else None,
                "v": v.chunks[0] if v is not None else None,
                "vi": vi.chunks[0] if vi is not None else None,
                "dtype": out_tensor.dtype,
            }
            if i != j:
                kw["b"] = in_tensor.cix[j, 0]
                kw["b_offset"] = axis_0_cum_size[j - 1] if j > 0 else 0
            map_op = TensorPdist(**kw)
            map_chunk_inputs = [kw["a"]]
            if "b" in kw:
                map_chunk_inputs.append(kw["b"])
            if kw["w"] is not None:
                map_chunk_inputs.append(kw["w"])
            if kw["v"] is not None:
                map_chunk_inputs.append(kw["v"])
            if kw["vi"] is not None:
                map_chunk_inputs.append(kw["vi"])
            # calc chunk shape
            if i == j:
                a_axis_0_size = kw["a"].shape[0]
                chunk_shape = (a_axis_0_size * (a_axis_0_size - 1) // 2,)
            else:
                chunk_shape = (kw["a"].shape[0] * kw["b"].shape[0],)
            map_chunk = map_op.new_chunk(
                map_chunk_inputs,
                shape=chunk_shape,
                order=out_tensor.order,
                index=(i * chunk_size + j,),
            )
            map_chunks.append(map_chunk)

    proxy_chunk = TensorShuffleProxy(dtype=out_tensor.dtype).new_chunk(
        map_chunks, shape=()
    )

    reduce_chunks = []
    for p in range(aggregate_size):
        reduce_chunk_op = TensorPdist(
            stage=OperandStage.reduce, shuffle_key=str(p), dtype=out_tensor.dtype
        )
        reduce_chunk = reduce_chunk_op.new_chunk(
            [proxy_chunk], shape=(out_sizes[p],), order=out_tensor.order, index=(p,)
        )
        reduce_chunks.append(reduce_chunk)

    new_op = op.copy()
    return new_op.new_tensors(
        op.inputs,
        shape=out_tensor.shape,
        order=out_tensor.order,
        nsplits=(tuple(out_sizes),),
        chunks=reduce_chunks,
    )