code: def _tile_tree(cls, op: "DataFrameAggregate"):
      in_df = op.inputs[0]
      out_df = op.outputs[0]
      combine_size = op.combine_size
      axis = op.axis
  
      input_index_to_output = dict()
      output_index_to_input = []
      axis_func_infos = []
      dtypes_list = []
      if len(in_df.chunk_shape) > 1:
          for col_idx in range(in_df.chunk_shape[1 - axis]):
              compiler = ReductionCompiler(axis=op.axis)
              idx_chunk = (
                  in_df.cix[0, col_idx]
                  if axis == 0
                  else in_df.cix[col_idx, 0]
              )
              new_dtypes = cls._filter_dtypes(op, idx_chunk.dtypes)
              if not cls._add_functions(
                  op, compiler, cols=list(new_dtypes.index)
              ):
                  continue
              input_index_to_output[col_idx] = len(axis_func_infos)
              output_index_to_input.append(col_idx)
              axis_func_infos.append(compiler.compile())
              dtypes_list.append(new_dtypes)
      else:
          compiler = ReductionCompiler(axis=op.axis)
          cls._add_functions(op, compiler)
          input_index_to_output[0] = 0
          axis_func_infos.append(compiler.compile())
  
      chunks = cls._gen_map_chunks(
          op, in_df, out_df, axis_func_infos, input_index_to_output
      )
      while chunks.shape[axis] > combine_size:
          if axis == 0:
              new_chunks_shape = (
                  ceildiv(chunks.shape[0], combine_size),
                  chunks.shape[1],
              )
          else:
              new_chunks_shape = (
                  chunks.shape[0],
                  ceildiv(chunks.shape[1], combine_size),
              )
  
          new_chunks = np.empty(new_chunks_shape, dtype=object)
          for idx0, i in enumerate(
              range(0, chunks.shape[axis], combine_size)
          ):
              for idx1 in range(chunks.shape[1 - axis]):
                  func_info = axis_func_infos[idx1]
                  if axis == 0:
                      chks = chunks[i : i + combine_size, idx1]
                      chunk_index = (idx0, idx1)
                      if chks[0].ndim == 1:
                          concat_shape = (len(chks),)
                          agg_shape = (1,)
                      else:
                          concat_shape = (len(chks), chks[0].shape[1])
                          agg_shape = (chks[0].shape[1], 1)
                  else:
                      chks = chunks[idx1, i : i + combine_size]
                      chunk_index = (idx1, idx0)
                      concat_shape = (chks[0].shape[0], len(chks))
                      agg_shape = (chks[0].shape[0], 1)
  
                  chks = chks.reshape((chks.shape[0],)).tolist()
                  if len(chks) == 1:
                      chk = chks[0]
                  else:
                      concat_op = DataFrameConcat(
                          output_types=[OutputType.dataframe], axis=axis
                      )
                      # Change index for concatenate
                      for j, c in enumerate(chks):
                          c._index = (j, 0) if axis == 0 else (0, j)
                      chk = concat_op.new_chunk(
                          chks,
                          dtypes=dtypes_list[idx1]
                          if dtypes_list
                          else None,
                          shape=concat_shape,
                          index_value=chks[0].index_value,
                      )
                  chunk_op = op.copy().reset_key()
                  chunk_op.output_types = [OutputType.dataframe]
                  chunk_op.stage = OperandStage.combine
                  chunk_op.agg_funcs = func_info.agg_funcs
  
                  if axis == 0:
                      new_chunks[chunk_index] = chunk_op.new_chunk(
                          [chk],
                          index=chunk_index,
                          shape=agg_shape,
                          index_value=chks[0].index_value,
                      )
                  else:
                      new_chunks[chunk_index] = chunk_op.new_chunk(
                          [chk],
                          index=chunk_index,
                          shape=agg_shape,
                          index_value=chks[0].columns_value,
                      )
          chunks = new_chunks
  
      agg_chunks = []
      for idx in range(chunks.shape[1 - axis]):
          func_info = axis_func_infos[idx]
  
          concat_op = DataFrameConcat(
              output_types=[OutputType.dataframe], axis=axis
          )
          if axis == 0:
              chks = chunks[:, idx]
              if chks[0].ndim == 1:
                  concat_shape = (len(chks),)
              else:
                  concat_shape = (len(chks), chks[0].shape[1])
          else:
              chks = chunks[idx, :]
              concat_shape = (chks[0].shape[0], len(chks))
          chks = chks.reshape((chks.shape[0],)).tolist()
          chk = concat_op.new_chunk(
              chks,
              dtypes=dtypes_list[idx] if dtypes_list else None,
              shape=concat_shape,
              index_value=chks[0].index_value,
          )
          chunk_op = op.copy().reset_key()
          chunk_op.stage = OperandStage.agg
          chunk_op.agg_funcs = func_info.agg_funcs
          chunk_op.post_funcs = func_info.post_funcs
  
          kw = out_df.params.copy()
          if op.output_types[0] == OutputType.dataframe:
              if axis == 0:
                  src_col_chunk = in_df.cix[
                      0, output_index_to_input[idx]
                  ]
                  valid_cols = [
                      c
                      for pre in func_info.pre_funcs
                      for c in pre.columns or ()
                  ]
                  if not valid_cols:
                      columns_value = src_col_chunk.columns_value
                      shape_len = src_col_chunk.shape[1]
                  else:
                      col_index = pd.Index(valid_cols).unique()
                      columns_value = parse_index(
                          col_index, store_data=True
                      )
                      shape_len = len(col_index)
                  kw.update(
                      dict(
                          shape=(out_df.shape[0], shape_len),
                          columns_value=columns_value,
                          index=(0, idx),
                          dtypes=out_df.dtypes[
                              columns_value.to_pandas()
                          ],
                      )
                  )
              else:
                  src_col_chunk = in_df.cix[
                      output_index_to_input[idx], 0
                  ]
                  kw.update(
                      dict(
                          index=(idx, 0),
                          index_value=src_col_chunk.index_value,
                          shape=(
                              src_col_chunk.shape[0],
                              out_df.shape[1],
                          ),
                          dtypes=out_df.dtypes,
                      )
                  )
          else:
              if op.output_types[0] == OutputType.series:
                  if in_df.ndim == 1:
                      index_value, shape = (
                          out_df.index_value,
                          out_df.shape,
                      )
                  elif axis == 0:
                      out_dtypes = dtypes_list[idx]
                      index_value = parse_index(
                          out_dtypes.index, store_data=True
                      )
                      shape = (len(out_dtypes),)
                  else:
                      src_chunk = in_df.cix[
                          output_index_to_input[idx], 0
                      ]
                      index_value, shape = (
                          src_chunk.index_value,
                          (src_chunk.shape[0],),
                      )
                  kw.update(
                      dict(
                          name=out_df.name,
                          dtype=out_df.dtype,
                          index=(idx,),
                          index_value=index_value,
                          shape=shape,
                      )
                  )
              elif op.output_types[0] == OutputType.tensor:
                  kw.update(
                      dict(
                          index=(0,),
                          shape=(np.nan,),
                          dtype=out_df.dtype,
                      )
                  )
              else:
                  kw.update(
                      dict(index=(), shape=(), dtype=out_df.dtype)
                  )
          agg_chunks.append(chunk_op.new_chunk([chk], **kw))
  
      new_op = op.copy()
      if op.output_types[0] == OutputType.dataframe:
          if axis == 0:
              nsplits = (
                  (out_df.shape[0],),
                  tuple(c.shape[1] for c in agg_chunks),
              )
          else:
              nsplits = (
                  tuple(c.shape[0] for c in agg_chunks),
                  (out_df.shape[1],),
              )
          return new_op.new_tileables(
              op.inputs,
              chunks=agg_chunks,
              nsplits=nsplits,
              dtypes=out_df.dtypes,
              shape=out_df.shape,
              index_value=out_df.index_value,
              columns_value=out_df.columns_value,
          )
      elif op.output_types[0] == OutputType.series:
          nsplits = (tuple(c.shape[0] for c in agg_chunks),)
          return new_op.new_tileables(
              op.inputs,
              chunks=agg_chunks,
              nsplits=nsplits,
              dtype=out_df.dtype,
              shape=out_df.shape,
              index_value=out_df.index_value,
              name=out_df.name,
          )
      elif op.output_types[0] == OutputType.tensor:  # unique
          return new_op.new_tileables(
              op.inputs,
              chunks=agg_chunks,
              dtype=out_df.dtype,
              shape=out_df.shape,
              nsplits=((np.nan,),),
          )
      else:  # scalar
          return new_op.new_tileables(
              op.inputs,
              chunks=agg_chunks,
              dtype=out_df.dtype,
              shape=(),
              nsplits=(),
          )
  
imports: import copy
  import functools
  import itertools
  from collections import OrderedDict
  from collections.abc import Iterable
  from typing import List, Dict
  import numpy
  import pandas
  from ...None import opcodes, tensor
  from ...config import options
  from ...core import OutputType, ENTITY_TYPE, enter_mode, recursive_tile
  from ...core.custom_log import redirect_custom_log
  from ...core.operand import OperandStage
  from ...serialization.serializables import BoolField, AnyField, Int32Field, ListField, DictField
  from ...utils import ceildiv, lazy_import, enter_current_session, pd_release_version
  from ..core import INDEX_CHUNK_TYPE
  from ..merge import DataFrameConcat
  from ..operands import DataFrameOperand, DataFrameOperandMixin
  from ..utils import build_df, build_empty_df, build_series, parse_index, validate_axis
  from .core import CustomReduction, ReductionCompiler, ReductionSteps, ReductionAggStep
label_desc: Use image node_id 9 for calling the DataFrameAggregate obj's underlying member method code with example usage: obj._tile_tree(cls, op) and returns: new_op, new_op, new_op, new_op