class_docstr: An ensemble of decision trees.
  
  This object provides a common interface to many different types of models.
class_docstr_tok: ["An","ensemble","of","decision","trees",".","This","object","provides","a","common","interface","to","many","different","types","of","models","."]
code: def __init__(
      self, model, data=None, data_missing=None, model_output=None
  ):
      self.model_type = "internal"
      self.trees = None
      self.base_offset = 0
      self.model_output = model_output
      self.objective = (
          None  # what we explain when explaining the loss of the model
      )
      self.tree_output = None  # what are the units of the values in the leaves of the trees
      self.internal_dtype = np.float64
      self.input_dtype = (
          np.float64
      )  # for sklearn we need to use np.float32 to always get exact matches to their predictions
      self.data = data
      self.data_missing = data_missing
      self.fully_defined_weighting = True  # does the background dataset land in every leaf (making it valid for the tree_path_dependent method)
      self.tree_limit = None  # used for limiting the number of trees we use by default (like from early stopping)
      self.num_stacked_models = 1  # If this is greater than 1 it means we have multiple stacked models with the same number of trees in each model (XGBoost multi-output style)
      self.cat_feature_indices = None  # If this is set it tells us which features are treated categorically
  
      # we use names like keras
      objective_name_map = {
          "mse": "squared_error",
          "variance": "squared_error",
          "friedman_mse": "squared_error",
          "reg:linear": "squared_error",
          "reg:squarederror": "squared_error",
          "regression": "squared_error",
          "regression_l2": "squared_error",
          "mae": "absolute_error",
          "gini": "binary_crossentropy",
          "entropy": "binary_crossentropy",
          "reg:logistic": "binary_crossentropy",
          "binary:logistic": "binary_crossentropy",
          "binary_logloss": "binary_crossentropy",
          "binary": "binary_crossentropy",
      }
  
      tree_output_name_map = {
          "regression": "raw_value",
          "regression_l2": "squared_error",
          "reg:linear": "raw_value",
          "reg:squarederror": "raw_value",
          "reg:logistic": "log_odds",
          "binary:logistic": "log_odds",
          "binary_logloss": "log_odds",
          "binary": "log_odds",
      }
  
      if isinstance(model, dict) and "trees" in model:
          # This allows a dictionary to be passed that represents the model.
          # this dictionary has several numerical parameters and also a list of trees
          # where each tree is a dictionary describing that tree
          if "internal_dtype" in model:
              self.internal_dtype = model["internal_dtype"]
          if "input_dtype" in model:
              self.input_dtype = model["input_dtype"]
          if "objective" in model:
              self.objective = model["objective"]
          if "tree_output" in model:
              self.tree_output = model["tree_output"]
          if "base_offset" in model:
              self.base_offset = model["base_offset"]
          self.trees = [
              SingleTree(t, data=data, data_missing=data_missing)
              for t in model["trees"]
          ]
      elif isinstance(model, list) and isinstance(
          model[0], SingleTree
      ):  # old-style direct-load format
          self.trees = model
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.RandomForestRegressor",
              "sklearn.ensemble.forest.RandomForestRegressor",
              "econml.grf._base_grf.BaseGRF",
          ],
      ):
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.IsolationForest",
              "sklearn.ensemble._iforest.IsolationForest",
          ],
      ):
          self.dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              IsoTree(
                  e.tree_,
                  f,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e, f in zip(
                  model.estimators_, model.estimators_features_
              )
          ]
          self.tree_output = "raw_value"
      elif safe_isinstance(model, ["pyod.models.iforest.IForest"]):
          self.dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              IsoTree(
                  e.tree_,
                  f,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e, f in zip(
                  model.detector_.estimators_,
                  model.detector_.estimators_features_,
              )
          ]
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model, "skopt.learning.forest.RandomForestRegressor"
      ):
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.ExtraTreesRegressor",
              "sklearn.ensemble.forest.ExtraTreesRegressor",
          ],
      ):
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model, "skopt.learning.forest.ExtraTreesRegressor"
      ):
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model,
          [
              "sklearn.tree.DecisionTreeRegressor",
              "sklearn.tree.tree.DecisionTreeRegressor",
              "econml.grf._base_grftree.GRFTree",
          ],
      ):
          self.internal_dtype = model.tree_.value.dtype.type
          self.input_dtype = np.float32
          self.trees = [
              SingleTree(
                  model.tree_, data=data, data_missing=data_missing
              )
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model,
          [
              "sklearn.tree.DecisionTreeClassifier",
              "sklearn.tree.tree.DecisionTreeClassifier",
          ],
      ):
          self.internal_dtype = model.tree_.value.dtype.type
          self.input_dtype = np.float32
          self.trees = [
              SingleTree(
                  model.tree_,
                  normalize=True,
                  data=data,
                  data_missing=data_missing,
              )
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "probability"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.RandomForestClassifier",
              "sklearn.ensemble.forest.RandomForestClassifier",
          ],
      ):
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  normalize=True,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "probability"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.ExtraTreesClassifier",
              "sklearn.ensemble.forest.ExtraTreesClassifier",
          ],
      ):  # TODO: add unit test for this case
          assert hasattr(
              model, "estimators_"
          ), "Model has no `estimators_`! Have you called `model.fit`?"
          self.internal_dtype = model.estimators_[
              0
          ].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  normalize=True,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "probability"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.GradientBoostingRegressor",
              "sklearn.ensemble.gradient_boosting.GradientBoostingRegressor",
          ],
      ):
          self.input_dtype = np.float32
  
          # currently we only support the mean and quantile estimators
          if safe_isinstance(
              model.init_,
              [
                  "sklearn.ensemble.MeanEstimator",
                  "sklearn.ensemble.gradient_boosting.MeanEstimator",
              ],
          ):
              self.base_offset = model.init_.mean
          elif safe_isinstance(
              model.init_,
              [
                  "sklearn.ensemble.QuantileEstimator",
                  "sklearn.ensemble.gradient_boosting.QuantileEstimator",
              ],
          ):
              self.base_offset = model.init_.quantile
          elif safe_isinstance(
              model.init_, "sklearn.dummy.DummyRegressor"
          ):
              self.base_offset = model.init_.constant_[0]
          else:
              emsg = f"Unsupported init model type: {type(model.init_)}"
              raise InvalidModelError(emsg)
  
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=model.learning_rate,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_[:, 0]
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model, ["sklearn.ensemble.HistGradientBoostingRegressor"]
      ):
          # cf. GH #1028 for implementation notes
          import sklearn
  
          if self.model_output == "predict":
              self.model_output = "raw"
          self.input_dtype = (
              sklearn.ensemble._hist_gradient_boosting.common.X_DTYPE
          )
          self.base_offset = model._baseline_prediction
          self.trees = []
          for p in model._predictors:
              nodes = p[0].nodes
              # each node has values: ('value', 'count', 'feature_idx', 'threshold', 'missing_go_to_left', 'left', 'right', 'gain', 'depth', 'is_leaf', 'bin_threshold')
              tree = {
                  "children_left": np.array(
                      [-1 if n[9] else n[5] for n in nodes]
                  ),
                  "children_right": np.array(
                      [-1 if n[9] else n[6] for n in nodes]
                  ),
                  "children_default": np.array(
                      [
                          -1 if n[9] else (n[5] if n[4] else n[6])
                          for n in nodes
                      ]
                  ),
                  "features": np.array(
                      [-2 if n[9] else n[2] for n in nodes]
                  ),
                  "thresholds": np.array(
                      [n[3] for n in nodes], dtype=np.float64
                  ),
                  "values": np.array(
                      [[n[0]] for n in nodes], dtype=np.float64
                  ),
                  "node_sample_weight": np.array(
                      [n[1] for n in nodes], dtype=np.float64
                  ),
              }
              self.trees.append(
                  SingleTree(tree, data=data, data_missing=data_missing)
              )
          self.objective = objective_name_map.get(model.loss, None)
          self.tree_output = "raw_value"
      elif safe_isinstance(
          model, ["sklearn.ensemble.HistGradientBoostingClassifier"]
      ):
          # cf. GH #1028 for implementation notes
          import sklearn
  
          self.base_offset = model._baseline_prediction
          has_len = hasattr(self.base_offset, "__len__")
          # Note for newer sklearn versions, the base_offset is an array even for binary classification
          if has_len and self.base_offset.shape == (1, 1):
              self.base_offset = self.base_offset[0, 0]
              has_len = False
          if has_len and self.model_output != "raw":
              emsg = (
                  "Multi-output HistGradientBoostingClassifier models are not yet supported unless "
                  'model_output="raw". See GitHub issue #1028.'
              )
              raise NotImplementedError(emsg)
          self.input_dtype = (
              sklearn.ensemble._hist_gradient_boosting.common.X_DTYPE
          )
          self.num_stacked_models = len(model._predictors[0])
          if self.model_output == "predict_proba":
              if self.num_stacked_models == 1:
                  self.model_output = "probability_doubled"  # with predict_proba we need to double the outputs to match
              else:
                  self.model_output = "probability"
          self.trees = []
          for p in model._predictors:
              for i in range(self.num_stacked_models):
                  nodes = p[i].nodes
                  # each node has values: ('value', 'count', 'feature_idx', 'threshold', 'missing_go_to_left', 'left', 'right', 'gain', 'depth', 'is_leaf', 'bin_threshold')
                  tree = {
                      "children_left": np.array(
                          [-1 if n[9] else n[5] for n in nodes]
                      ),
                      "children_right": np.array(
                          [-1 if n[9] else n[6] for n in nodes]
                      ),
                      "children_default": np.array(
                          [
                              -1 if n[9] else (n[5] if n[4] else n[6])
                              for n in nodes
                          ]
                      ),
                      "features": np.array(
                          [-2 if n[9] else n[2] for n in nodes]
                      ),
                      "thresholds": np.array(
                          [n[3] for n in nodes], dtype=np.float64
                      ),
                      "values": np.array(
                          [[n[0]] for n in nodes], dtype=np.float64
                      ),
                      "node_sample_weight": np.array(
                          [n[1] for n in nodes], dtype=np.float64
                      ),
                  }
                  self.trees.append(
                      SingleTree(
                          tree, data=data, data_missing=data_missing
                      )
                  )
          self.objective = objective_name_map.get(model.loss, None)
          self.tree_output = "log_odds"
      elif safe_isinstance(
          model,
          [
              "sklearn.ensemble.GradientBoostingClassifier",
              "sklearn.ensemble._gb.GradientBoostingClassifier",
              "sklearn.ensemble.gradient_boosting.GradientBoostingClassifier",
          ],
      ):
          self.input_dtype = np.float32
  
          # TODO: deal with estimators for each class
          if model.estimators_.shape[1] > 1:
              emsg = "GradientBoostingClassifier is only supported for binary classification right now!"
              raise InvalidModelError(emsg)
  
          # currently we only support the logs odds estimator
          if safe_isinstance(
              model.init_,
              [
                  "sklearn.ensemble.LogOddsEstimator",
                  "sklearn.ensemble.gradient_boosting.LogOddsEstimator",
              ],
          ):
              self.base_offset = model.init_.prior
              self.tree_output = "log_odds"
          elif safe_isinstance(
              model.init_, "sklearn.dummy.DummyClassifier"
          ):
              self.base_offset = scipy.special.logit(
                  model.init_.class_prior_[1]
              )  # with two classes the trees only model the second class.
              self.tree_output = "log_odds"
          else:
              emsg = f"Unsupported init model type: {type(model.init_)}"
              raise InvalidModelError(emsg)
  
          self.trees = [
              SingleTree(
                  e.tree_,
                  scaling=model.learning_rate,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_[:, 0]
          ]
          self.objective = objective_name_map.get(model.criterion, None)
      elif "pyspark.ml" in str(type(model)):
          assert_import("pyspark")
          self.model_type = "pyspark"
          # model._java_obj.getImpurity() can be gini, entropy or variance.
          self.objective = objective_name_map.get(
              model._java_obj.getImpurity(), None
          )
          if "Classification" in str(type(model)):
              normalize = True
              self.tree_output = "probability"
          else:
              normalize = False
              self.tree_output = "raw_value"
          # Spark Random forest, create 1 weighted (avg) tree per sub-model
          if safe_isinstance(
              model,
              [
                  "pyspark.ml.classification.RandomForestClassificationModel",
                  "pyspark.ml.regression.RandomForestRegressionModel",
              ],
          ):
              sum_weight = sum(
                  model.treeWeights
              )  # output is average of trees
              self.trees = [
                  SingleTree(
                      tree,
                      normalize=normalize,
                      scaling=model.treeWeights[i] / sum_weight,
                  )
                  for i, tree in enumerate(model.trees)
              ]
          # Spark GBT, create 1 weighted (learning rate) tree per sub-model
          elif safe_isinstance(
              model,
              [
                  "pyspark.ml.classification.GBTClassificationModel",
                  "pyspark.ml.regression.GBTRegressionModel",
              ],
          ):
              self.objective = (
                  "squared_error"  # GBT subtree use the variance
              )
              self.tree_output = "raw_value"
              self.trees = [
                  SingleTree(
                      tree,
                      normalize=False,
                      scaling=model.treeWeights[i],
                  )
                  for i, tree in enumerate(model.trees)
              ]
          # Spark Basic model (single tree)
          elif safe_isinstance(
              model,
              [
                  "pyspark.ml.classification.DecisionTreeClassificationModel",
                  "pyspark.ml.regression.DecisionTreeRegressionModel",
              ],
          ):
              self.trees = [
                  SingleTree(model, normalize=normalize, scaling=1)
              ]
          else:
              emsg = f"Unsupported Spark model type: {type(model)}"
              raise NotImplementedError(emsg)
      elif safe_isinstance(model, "xgboost.core.Booster"):
          self.original_model = model
          self.model_type = "xgboost"
          xgb_loader = XGBTreeModelLoader(self.original_model)
          self.trees = xgb_loader.get_trees(
              data=data, data_missing=data_missing
          )
          self.base_offset = xgb_loader.base_score
          self.objective = objective_name_map.get(
              xgb_loader.name_obj, None
          )
          self.tree_output = tree_output_name_map.get(
              xgb_loader.name_obj, None
          )
          if xgb_loader.num_class > 0:
              self.num_stacked_models = xgb_loader.num_class
      elif safe_isinstance(model, "xgboost.sklearn.XGBClassifier"):
          self.input_dtype = np.float32
          self.model_type = "xgboost"
          self.original_model = model.get_booster()
          xgb_loader = XGBTreeModelLoader(self.original_model)
          self.trees = xgb_loader.get_trees(
              data=data, data_missing=data_missing
          )
          self.base_offset = xgb_loader.base_score
          self.objective = objective_name_map.get(
              xgb_loader.name_obj, None
          )
          self.tree_output = tree_output_name_map.get(
              xgb_loader.name_obj, None
          )
          # 'best_ntree_limit' is problematic
          # https://github.com/dmlc/xgboost/issues/6615
          if hasattr(model, "best_iteration"):
              trees_per_iteration = (
                  xgb_loader.num_class
                  if xgb_loader.num_class > 0
                  else 1
              )
              self.tree_limit = (
                  getattr(model, "best_iteration", None) + 1
              ) * trees_per_iteration
          else:
              self.tree_limit = getattr(model, "best_ntree_limit", None)
          if xgb_loader.num_class > 0:
              self.num_stacked_models = xgb_loader.num_class
          if self.model_output == "predict_proba":
              if self.num_stacked_models == 1:
                  self.model_output = "probability_doubled"  # with predict_proba we need to double the outputs to match
              else:
                  self.model_output = "probability"
          # Some properties of the sklearn API are passed to a DMatrix object in xgboost
          # We need to make sure we do the same here - GH #3313
          self._xgb_dmatrix_props = get_xgboost_dmatrix_properties(
              model
          )
      elif safe_isinstance(model, "xgboost.sklearn.XGBRegressor"):
          self.original_model = model.get_booster()
          self.model_type = "xgboost"
          xgb_loader = XGBTreeModelLoader(self.original_model)
          self.trees = xgb_loader.get_trees(
              data=data, data_missing=data_missing
          )
          self.base_offset = xgb_loader.base_score
          self.objective = objective_name_map.get(
              xgb_loader.name_obj, None
          )
          self.tree_output = tree_output_name_map.get(
              xgb_loader.name_obj, None
          )
          self.tree_limit = getattr(model, "best_ntree_limit", None)
          if xgb_loader.num_class > 0:
              self.num_stacked_models = xgb_loader.num_class
          # Some properties of the sklearn API are passed to a DMatrix object in xgboost
          # We need to make sure we do the same here - GH #3313
          self._xgb_dmatrix_props = get_xgboost_dmatrix_properties(
              model
          )
      elif safe_isinstance(model, "xgboost.sklearn.XGBRanker"):
          self.original_model = model.get_booster()
          self.model_type = "xgboost"
          xgb_loader = XGBTreeModelLoader(self.original_model)
          self.trees = xgb_loader.get_trees(
              data=data, data_missing=data_missing
          )
          self.base_offset = xgb_loader.base_score
          # Note: for ranker, leaving tree_output and objective as None as they
          # are not implemented in native code yet
          self.tree_limit = getattr(model, "best_ntree_limit", None)
          if xgb_loader.num_class > 0:
              self.num_stacked_models = xgb_loader.num_class
          # Some properties of the sklearn API are passed to a DMatrix object in xgboost
          # We need to make sure we do the same here - GH #3313
          self._xgb_dmatrix_props = get_xgboost_dmatrix_properties(
              model
          )
      elif safe_isinstance(model, "lightgbm.basic.Booster"):
          assert_import("lightgbm")
          self.model_type = "lightgbm"
          self.original_model = model
          tree_info = self.original_model.dump_model()["tree_info"]
          try:
              self.trees = [
                  SingleTree(e, data=data, data_missing=data_missing)
                  for e in tree_info
              ]
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
  
          self.objective = objective_name_map.get(
              model.params.get("objective", "regression"), None
          )
          self.tree_output = tree_output_name_map.get(
              model.params.get("objective", "regression"), None
          )
  
      elif safe_isinstance(model, "gpboost.basic.Booster"):
          assert_import("gpboost")
          self.model_type = "gpboost"
          self.original_model = model
          tree_info = self.original_model.dump_model()["tree_info"]
          try:
              self.trees = [
                  SingleTree(e, data=data, data_missing=data_missing)
                  for e in tree_info
              ]
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
  
          self.objective = objective_name_map.get(
              model.params.get("objective", "regression"), None
          )
          self.tree_output = tree_output_name_map.get(
              model.params.get("objective", "regression"), None
          )
  
      elif safe_isinstance(model, "lightgbm.sklearn.LGBMRegressor"):
          assert_import("lightgbm")
          self.model_type = "lightgbm"
          self.original_model = model.booster_
          tree_info = self.original_model.dump_model()["tree_info"]
          try:
              self.trees = [
                  SingleTree(e, data=data, data_missing=data_missing)
                  for e in tree_info
              ]
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
          self.objective = objective_name_map.get(model.objective, None)
          self.tree_output = tree_output_name_map.get(
              model.objective, None
          )
          if model.objective is None:
              self.objective = "squared_error"
              self.tree_output = "raw_value"
      elif safe_isinstance(model, "lightgbm.sklearn.LGBMRanker"):
          assert_import("lightgbm")
          self.model_type = "lightgbm"
          self.original_model = model.booster_
          tree_info = self.original_model.dump_model()["tree_info"]
          try:
              self.trees = [
                  SingleTree(e, data=data, data_missing=data_missing)
                  for e in tree_info
              ]
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
          # Note: for ranker, leaving tree_output and objective as None as they
          # are not implemented in native code yet
      elif safe_isinstance(model, "lightgbm.sklearn.LGBMClassifier"):
          assert_import("lightgbm")
          self.model_type = "lightgbm"
          if model.n_classes_ > 2:
              self.num_stacked_models = model.n_classes_
          self.original_model = model.booster_
          tree_info = self.original_model.dump_model()["tree_info"]
          try:
              self.trees = [
                  SingleTree(e, data=data, data_missing=data_missing)
                  for e in tree_info
              ]
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
          self.objective = objective_name_map.get(model.objective, None)
          self.tree_output = tree_output_name_map.get(
              model.objective, None
          )
          if model.objective is None:
              self.objective = "binary_crossentropy"
              self.tree_output = "log_odds"
      elif safe_isinstance(model, "catboost.core.CatBoostRegressor"):
          assert_import("catboost")
          self.model_type = "catboost"
          self.original_model = model
          self.cat_feature_indices = model.get_cat_feature_indices()
      elif safe_isinstance(model, "catboost.core.CatBoostClassifier"):
          assert_import("catboost")
          self.model_type = "catboost"
          self.original_model = model
          self.input_dtype = np.float32
          try:
              cb_loader = CatBoostTreeModelLoader(model)
              self.trees = cb_loader.get_trees(
                  data=data, data_missing=data_missing
              )
          except Exception:
              self.trees = None  # we get here because the cext can't handle categorical splits yet
          self.tree_output = "log_odds"
          self.objective = "binary_crossentropy"
          self.cat_feature_indices = model.get_cat_feature_indices()
      elif safe_isinstance(model, "catboost.core.CatBoost"):
          assert_import("catboost")
          self.model_type = "catboost"
          self.original_model = model
          self.cat_feature_indices = model.get_cat_feature_indices()
      elif safe_isinstance(
          model,
          "imblearn.ensemble._forest.BalancedRandomForestClassifier",
      ):
          self.input_dtype = np.float32
          scaling = 1.0 / len(
              model.estimators_
          )  # output is average of trees
          self.trees = [
              SingleTree(
                  e.tree_,
                  normalize=True,
                  scaling=scaling,
                  data=data,
                  data_missing=data_missing,
              )
              for e in model.estimators_
          ]
          self.objective = objective_name_map.get(model.criterion, None)
          self.tree_output = "probability"
      elif safe_isinstance(
          model,
          [
              "ngboost.ngboost.NGBoost",
              "ngboost.api.NGBRegressor",
              "ngboost.api.NGBClassifier",
          ],
      ):
          assert (
              model.base_models
          ), "The NGBoost model has empty `base_models`! Have you called `model.fit`?"
          if self.model_output == "raw":
              param_idx = 0  # default to the first parameter of the output distribution
              warnings.warn(
                  'Translating model_output="raw" to model_output=0 for the 0-th parameter in the distribution. Use model_output=0 directly to avoid this warning.'
              )
          elif isinstance(self.model_output, int):
              param_idx = self.model_output
              self.model_output = "raw"  # note that after loading we have a new model_output type
          assert safe_isinstance(
              model.base_models[0][param_idx],
              [
                  "sklearn.tree.DecisionTreeRegressor",
                  "sklearn.tree.tree.DecisionTreeRegressor",
              ],
          ), "You must use default_tree_learner!"
          shap_trees = [trees[param_idx] for trees in model.base_models]
          self.internal_dtype = shap_trees[0].tree_.value.dtype.type
          self.input_dtype = np.float32
          scaling = -model.learning_rate * np.array(
              model.scalings
          )  # output is weighted average of trees
          # ngboost reorders the features, so we need to map them back to the original order
          missing_col_idxs = [
              [i for i in range(model.n_features) if i not in col_idx]
              for col_idx in model.col_idxs
          ]
          feature_mapping = [
              {
                  i: col_idx
                  for i, col_idx in enumerate(
                      list(col_idxs) + missing_col_idx
                  )
              }
              for col_idxs, missing_col_idx in zip(
                  model.col_idxs, missing_col_idxs
              )
          ]
          self.trees = []
          for idx, shap_tree in enumerate(shap_trees):
              tree_ = shap_tree.tree_
              values = tree_.value.reshape(
                  tree_.value.shape[0],
                  tree_.value.shape[1] * tree_.value.shape[2],
              )
              values = values * scaling[idx]
              tree = {
                  "children_left": tree_.children_left.astype(np.int32),
                  "children_right": tree_.children_right.astype(
                      np.int32
                  ),
                  "children_default": tree_.children_left,
                  "features": np.array(
                      [
                          feature_mapping[idx].get(i, i)
                          for i in tree_.feature
                      ]
                  ),
                  "thresholds": tree_.threshold.astype(np.float64),
                  "values": values,
                  "node_sample_weight": tree_.weighted_n_node_samples.astype(
                      np.float64
                  ),
              }
              self.trees.append(
                  SingleTree(tree, data=data, data_missing=data_missing)
              )
          self.objective = objective_name_map.get(
              shap_trees[0].criterion, None
          )
          self.tree_output = "raw_value"
          self.base_offset = model.init_params[param_idx]
      else:
          raise InvalidModelError(
              "Model type not yet supported by TreeExplainer: "
              + str(type(model))
          )
  
      # build a dense numpy version of all the tree objects
      if self.trees is not None and self.trees:
          max_nodes = np.max([len(t.values) for t in self.trees])
          assert (
              len(np.unique([t.values.shape[1] for t in self.trees]))
              == 1
          ), "All trees in the ensemble must have the same output dimension!"
          num_trees = len(self.trees)
          if self.num_stacked_models > 1:
              assert (
                  len(self.trees) % self.num_stacked_models == 0
              ), "Only stacked models with equal numbers of trees are supported!"
              assert (
                  self.trees[0].values.shape[1] == 1
              ), "Only stacked models with single outputs per model are supported!"
              self.num_outputs = self.num_stacked_models
          else:
              self.num_outputs = self.trees[0].values.shape[1]
  
          # important to be -1 in unused sections!! This way we can tell which entries are valid.
          self.children_left = -np.ones(
              (num_trees, max_nodes), dtype=np.int32
          )
          self.children_right = -np.ones(
              (num_trees, max_nodes), dtype=np.int32
          )
          self.children_default = -np.ones(
              (num_trees, max_nodes), dtype=np.int32
          )
          self.features = -np.ones(
              (num_trees, max_nodes), dtype=np.int32
          )
  
          self.thresholds = np.zeros(
              (num_trees, max_nodes), dtype=self.internal_dtype
          )
          self.values = np.zeros(
              (num_trees, max_nodes, self.num_outputs),
              dtype=self.internal_dtype,
          )
          self.node_sample_weight = np.zeros(
              (num_trees, max_nodes), dtype=self.internal_dtype
          )
  
          for i in range(num_trees):
              self.children_left[
                  i, : len(self.trees[i].children_left)
              ] = self.trees[i].children_left
              self.children_right[
                  i, : len(self.trees[i].children_right)
              ] = self.trees[i].children_right
              self.children_default[
                  i, : len(self.trees[i].children_default)
              ] = self.trees[i].children_default
              self.features[
                  i, : len(self.trees[i].features)
              ] = self.trees[i].features
              self.thresholds[
                  i, : len(self.trees[i].thresholds)
              ] = self.trees[i].thresholds
              if self.num_stacked_models > 1:
                  # stack_pos = int(i // (num_trees / self.num_stacked_models))
                  stack_pos = i % self.num_stacked_models
                  self.values[
                      i, : len(self.trees[i].values[:, 0]), stack_pos
                  ] = self.trees[i].values[:, 0]
              else:
                  self.values[
                      i, : len(self.trees[i].values)
                  ] = self.trees[i].values
              self.node_sample_weight[
                  i, : len(self.trees[i].node_sample_weight)
              ] = self.trees[i].node_sample_weight
  
              # ensure that the passed background dataset lands in every leaf
              if np.min(self.trees[i].node_sample_weight) <= 0:
                  self.fully_defined_weighting = False
  
          self.num_nodes = np.array(
              [len(t.values) for t in self.trees], dtype=np.int32
          )
          self.max_depth = np.max([t.max_depth for t in self.trees])
  
          # make sure the base offset is a 1D array
          if (
              not hasattr(self.base_offset, "__len__")
              or len(self.base_offset) == 0
          ):
              self.base_offset = (
                  np.ones(self.num_outputs) * self.base_offset
              ).astype(self.internal_dtype)
          self.base_offset = self.base_offset.flatten()
          assert len(self.base_offset) == self.num_outputs
  
imports: import json
  import os
  import struct
  import time
  import warnings
  import numpy
  import pandas
  import scipy.sparse
  import scipy.special
  from packaging import version
  from ..None import maskers
  from .._explanation import Explanation
  from ..utils import assert_import, record_import_error, safe_isinstance
  from ..utils._exceptions import DimensionError, ExplainerError, InvalidFeaturePerturbationError, InvalidMaskerError, InvalidModelError
  from ..utils._legacy import DenseData
  from ._explainer import Explainer
label_desc: Use image node_id 1 to create a new TreeEnsemble object  with example: obj = TreeEnsemble(model, data, data_missing, model_output)