code: def _prune_columns(self) -> List[TileableData]:
      pruned_nodes: List[TileableData] = []
      datasource_nodes: List[TileableData] = []
  
      node_list = list(self._graph.topological_iter())
      for data in node_list:
          if self._is_skipped_type(data):
              continue
  
          op = data.op
  
          successor_required_columns = (
              self._get_successor_required_columns(data)
          )
          if (
              isinstance(op, ColumnPruneSupportedDataSourceMixin)
              and successor_required_columns is not None
              and set(successor_required_columns)
              != self._get_all_columns(data)
          ):
              op.set_pruned_columns(list(successor_required_columns))
              self.effective = True
              pruned_nodes.append(data)
              datasource_nodes.append(data)
              continue
  
          if isinstance(op, OPTIMIZABLE_OP_TYPES):
              predecessors = list(self._graph.predecessors(data))
              for predecessor in predecessors:
                  if (
                      self._is_skipped_type(predecessor)
                      or predecessor in datasource_nodes
                      # if the group by key is a series, no need to do column pruning
                      or isinstance(predecessor, BaseSeriesData)
                  ):
                      continue
  
                  pruned_columns = list(
                      self._context[data][predecessor]
                  )
                  if set(pruned_columns) == self._get_all_columns(
                      predecessor
                  ):
                      continue
  
                  # new node init
                  new_node_op = DataFrameIndex(
                      col_names=pruned_columns,
                  )
                  new_params = predecessor.params.copy()
                  new_params["shape"] = (
                      new_params["shape"][0],
                      len(pruned_columns),
                  )
                  new_params["dtypes"] = new_params["dtypes"][
                      pruned_columns
                  ]
                  new_params["columns_value"] = parse_index(
                      new_params["dtypes"].index, store_data=True
                  )
                  new_node = new_node_op.new_dataframe(
                      [predecessor], **new_params
                  ).data
  
                  # update context
                  del self._context[data][predecessor]
                  self._context[new_node] = {
                      predecessor: set(pruned_columns)
                  }
                  self._context[data][new_node] = set(pruned_columns)
  
                  # change edges and nodes
                  self._graph.remove_edge(predecessor, data)
                  self._graph.add_node(new_node)
                  self._graph.add_edge(predecessor, new_node)
                  self._graph.add_edge(new_node, data)
  
                  self._records.append_record(
                      OptimizationRecord(
                          predecessor,
                          new_node,
                          OptimizationRecordType.new,
                      )
                  )
                  # update inputs
                  data.inputs[data.inputs.index(predecessor)] = new_node
                  self.effective = True
                  pruned_nodes.extend([predecessor])
      return pruned_nodes
  
imports: from typing import List, Dict, Set, Any, Type, Union, Optional
  import pandas
  from .input_column_selector import InputColumnSelector
  from .self_column_selector import SelfColumnSelector
  from ..core import register_optimization_rule
  from ...core import OptimizationRecord, OptimizationRecordType, OptimizationRule, OptimizationRecords, Optimizer
  from .....core import TileableData
  from .....core.graph import EntityGraph
  from .....dataframe.core import parse_index, BaseSeriesData, BaseDataFrameData
  from .....dataframe.datasource.core import ColumnPruneSupportedDataSourceMixin
  from .....dataframe.groupby.aggregation import DataFrameGroupByAgg
  from .....dataframe.indexing.getitem import DataFrameIndex
  from .....dataframe.merge import DataFrameMerge
  from .....utils import implements
label_desc: Use image node_id 8 for calling the ColumnPruningRule obj's underlying member method code with example usage: obj._prune_columns() and returns: pruned_nodes