docstr: Tile dataframes using tree reduction
docstr_tok: ["Tile","dataframes","using","tree","reduction"]
code: def _tile_dataframe(cls, op: "DataFrameMemoryUsage"):
      """
      Tile dataframes using tree reduction
      """
      df = op.inputs[0]
      output = op.outputs[0]
      is_range_index = isinstance(
          df.index_value.value, IndexValue.RangeIndex
      )
  
      # produce map chunks
      # allocate matrix of chunks
      chunks_to_reduce = np.empty(shape=df.chunk_shape, dtype=object)
      for c in df.chunks:
          new_op = op.copy().reset_key()
          new_op.stage = OperandStage.map
  
          if op.index and is_range_index:
              # when the index is ``pd.RangeIndex``, the size should be included
              # after all computations are done
              new_op.index = False
          else:
              # when the chunk is not the first chunk in the row, index size is not needed
              new_op.index = op.index and c.index[-1] == 0
  
          new_shape = (
              (c.shape[-1] + 1,)
              if c.index[-1] == 0 and op.index
              else (c.shape[-1],)
          )
  
          chunks_to_reduce[c.index] = new_op.new_chunk(
              [c],
              index=(c.index[-1],),
              dtype=output.dtype,
              shape=new_shape,
              index_value=op._adapt_index(c.columns_value, c.index[-1]),
          )
  
      # reduce chunks using tree reduction
      combine_size = options.combine_size
      while chunks_to_reduce.shape[0] > 1:
          # allocate matrix of chunks
          new_chunks_to_reduce = np.empty(
              (
                  ceildiv(chunks_to_reduce.shape[0], combine_size),
                  chunks_to_reduce.shape[1],
              ),
              dtype=object,
          )
          for idx in range(0, chunks_to_reduce.shape[0], combine_size):
              for idx2 in range(chunks_to_reduce.shape[1]):
                  new_op = op.copy().reset_key()
                  new_op.stage = OperandStage.reduce
                  chunks = list(
                      chunks_to_reduce[idx : idx + combine_size, idx2]
                  )
  
                  new_chunks_to_reduce[
                      idx // combine_size, idx2
                  ] = new_op.new_chunk(
                      chunks,
                      index=(idx2,),
                      dtype=output.dtype,
                      shape=chunks[0].shape,
                      index_value=chunks[0].index_value,
                  )
  
          chunks_to_reduce = new_chunks_to_reduce
  
      # handle RangeIndex at final outputs
      if op.index and is_range_index:
          chunks_to_reduce[
              0, 0
          ].op.range_index_size = (
              df.index_value.to_pandas().memory_usage()
          )
  
      # return series with chunks and nsplits
      new_op = op.copy().reset_key()
      return new_op.new_series(
          [df],
          dtype=output.dtype,
          shape=output.shape,
          index_value=output.index_value,
          chunks=list(chunks_to_reduce[0, :]),
          nsplits=op._adapt_nsplits(df.nsplits),
      )
  
imports: import operator
  from functools import reduce
  import numpy
  import pandas
  from ...None import opcodes
  from ...config import options
  from ...core.operand import OperandStage
  from ...serialization.serializables import BoolField, Int64Field
  from ...utils import ceildiv, lazy_import
  from ..operands import DataFrameOperand, DataFrameOperandMixin
  from ..core import IndexValue
  from ..utils import parse_index
label_desc: Use image node_id 11 for calling the DataFrameMemoryUsage obj's underlying member method code with example usage: obj._tile_dataframe(cls, op) and returns: new_op