class_docstr: Confidence estimation block, uses inductive conformal predictors (ICPs) for model agnosticity 
class_docstr_tok: ["Confidence","estimation","block",",","uses","inductive","conformal","predictors","(","ICPs",")","for","model","agnosticity"]
code: def analyze(
      self, info: Dict[str, object], **kwargs
  ) -> Dict[str, object]:
      ns = SimpleNamespace(**kwargs)
  
      data_type = ns.dtype_dict[ns.target]
      output = {"icp": {"__mdb_active": False}}
  
      if "confidence" in ns.normal_predictions.columns:
          #  bypass calibrator if model already outputs confidence
          output["result_df"] = ns.normal_predictions[
              ["confidence", "lower", "upper"]
          ]
          return {**info, **output}
  
      fit_params = {
          "horizon": ns.tss.horizon or 0,
          "columns_to_ignore": [],
      }
      fit_params["columns_to_ignore"].extend(
          [f"timestep_{i}" for i in range(1, fit_params["horizon"])]
      )
  
      if ns.is_classification:
          if ns.predictor.supports_proba:
              all_cat_cols = [
                  col
                  for col in ns.normal_predictions.columns
                  if "__mdb_proba" in col
                  and "__mdb_unknown_cat" not in col
              ]
              all_classes = np.array(
                  [
                      col.replace("__mdb_proba_", "")
                      for col in all_cat_cols
                  ]
              )
          else:
              class_keys = sorted(
                  ns.encoded_val_data.encoders[ns.target].rev_map.keys()
              )
              all_classes = np.array(
                  [
                      ns.encoded_val_data.encoders[ns.target].rev_map[
                          idx
                      ]
                      for idx in class_keys
                  ]
              )
  
          if data_type != dtype.tags:
              enc = OneHotEncoder(sparse=False, handle_unknown="ignore")
              enc.fit(all_classes.reshape(-1, 1))
              output[
                  "label_encoders"
              ] = enc  # needed to repr cat labels inside nonconformist
          else:
              output["label_encoders"] = None
  
          adapter = CachedClassifierAdapter
          nc_function = MarginErrFunc()
          nc_class = ClassifierNc
          icp_class = IcpClassifier
      elif ns.is_multi_ts:
          adapter = CachedTSAdapter
          nc_function = TSAbsErrorErrFunc(horizon_length=ns.tss.horizon)
          nc_class = TSNc
          icp_class = IcpTSRegressor
      else:
          adapter = CachedRegressorAdapter
          nc_function = BoostedAbsErrorErrFunc()
          nc_class = RegressorNc
          icp_class = IcpRegressor
  
      result_df = pd.DataFrame()
  
      if ns.is_numerical or (
          ns.is_classification and data_type != dtype.tags
      ):
          model = adapter(ns.predictor)
  
          norm_params = {
              "target": ns.target,
              "dtype_dict": ns.dtype_dict,
              "predictor": ns.predictor,
              "encoders": ns.encoded_val_data.encoders,
              "is_multi_ts": ns.is_multi_ts,
              "stop_after": 1e2,
          }
          if self.confidence_normalizer:
              normalizer = Normalizer(fit_params=norm_params)
              normalizer.fit(ns.train_data)
              normalizer.prediction_cache = normalizer(
                  ns.encoded_val_data, args=PredictionArguments()
              )
          else:
              normalizer = None
  
          # instance the ICP
          nc = nc_class(model, nc_function, normalizer=normalizer)
          if (
              "horizon_length"
              in inspect.signature(icp_class).parameters
          ):
              icp = icp_class(
                  nc,
                  horizon_length=ns.tss.horizon,
                  cal_size=self.validation_size,
              )
          else:
              icp = icp_class(nc, cal_size=self.validation_size)
  
          output["icp"]["__default"] = icp
          icp_df = deepcopy(ns.data)
  
          # setup prediction cache to avoid additional .predict() calls
          try:
              pred_is_list = isinstance(
                  ns.normal_predictions["prediction"][0], list
              )
          except KeyError:
              pred_is_list = False
  
          if ns.is_classification:
              if ns.predictor.supports_proba:
                  icp.nc_function.model.prediction_cache = (
                      ns.normal_predictions[all_cat_cols].values
                  )
              else:
                  if ns.is_multi_ts:
                      preds = np.array(
                          [
                              p[0]
                              for p in ns.normal_predictions[
                                  "prediction"
                              ]
                          ]
                      )
                  else:
                      preds = ns.normal_predictions["prediction"].values
                  predicted_classes = output[
                      "label_encoders"
                  ].transform(
                      preds.reshape(-1, 1)
                  )  # inflate OHE
                  icp.nc_function.model.prediction_cache = (
                      predicted_classes
                  )
  
          elif ns.is_multi_ts or pred_is_list:
              icp.nc_function.model.prediction_cache = np.array(
                  [
                      np.array(p)
                      for p in ns.normal_predictions["prediction"]
                  ]
              )
          else:
              icp.nc_function.model.prediction_cache = np.array(
                  ns.normal_predictions["prediction"]
              )
  
          if not ns.is_classification:
              output["df_target_stddev"] = {
                  "__default": ns.stats_info.df_target_stddev
              }
  
          # fit additional ICPs in time series tasks with grouped columns
          if ns.tss.is_timeseries and ns.tss.group_by:
              # generate a multiindex
              midx = pd.MultiIndex.from_frame(
                  icp_df[
                      [
                          *ns.tss.group_by,
                          f"__mdb_original_{ns.tss.order_by}",
                      ]
                  ]
              )
              icp_df.index = midx
              result_df.index = midx
  
              # create an ICP for each possible group
              group_info = ns.data[ns.tss.group_by].to_dict("list")
              all_group_combinations = get_ts_groups(ns.data, ns.tss)
              all_group_combinations.remove("__default")
              output["icp"]["__mdb_groups"] = all_group_combinations
              output["icp"]["__mdb_group_keys"] = [
                  x for x in group_info.keys()
              ]
  
              for combination in all_group_combinations:
                  output["icp"][tuple(combination)] = deepcopy(icp)
  
          # calibrate ICP
          icp_df = deepcopy(ns.data)
          icp_df, y = clean_df(
              icp_df, ns, output.get("label_encoders", None)
          )
          if ns.tss.is_timeseries and ns.tss.group_by:
              icp_df.index = midx
          output["icp"]["__default"].index = icp_df.columns
          output["icp"]["__default"].calibrate(icp_df.values, y)
  
          # get confidence estimation for validation dataset
          conf, ranges = set_conf_range(
              icp_df,
              icp,
              ns.dtype_dict[ns.target],
              output,
              positive_domain=ns.stats_info.positive_domain,
              significance=self.fixed_significance,
          )
          if not ns.is_classification:
              result_df = pd.DataFrame(
                  index=icp_df.index,
                  columns=["confidence", "lower", "upper"],
                  dtype=float,
              )
              result_df.loc[icp_df.index, "lower"] = ranges[:, 0]
              result_df.loc[icp_df.index, "upper"] = ranges[:, 1]
          else:
              result_df = pd.DataFrame(
                  index=icp_df.index,
                  columns=["confidence"],
                  dtype=float,
              )
  
          result_df.loc[icp_df.index, "confidence"] = conf
  
          # calibrate additional grouped ICPs
          if ns.tss.is_timeseries and ns.tss.group_by:
              icps = output["icp"]
              group_keys = icps["__mdb_group_keys"]
  
              # add all predictions to DF
              icps_df = deepcopy(ns.data)
              midx = pd.MultiIndex.from_frame(
                  icps_df[
                      [
                          *ns.tss.group_by,
                          f"__mdb_original_{ns.tss.order_by}",
                      ]
                  ]
              )
              icps_df.index = midx
              if ns.is_multi_ts or pred_is_list:
                  icps_df[f"__predicted_{ns.target}"] = [
                      np.array(p)
                      for p in ns.normal_predictions["prediction"]
                  ]
              else:
                  icps_df[f"__predicted_{ns.target}"] = np.array(
                      ns.normal_predictions["prediction"]
                  )
  
              for group in icps["__mdb_groups"]:
                  icp_df = icps_df
  
                  # filter irrelevant rows for each group combination
                  icp_df["__mdb_norm_index"] = np.arange(len(icp_df))
                  for key, val in zip(group_keys, group):
                      icp_df = icp_df[icp_df[key] == val]
  
                  if (
                      icps[tuple(group)].nc_function.normalizer
                      is not None
                  ):
                      group_normalizer = icps[
                          tuple(group)
                      ].nc_function.normalizer
                      norm_input_df = (
                          ns.encoded_val_data.data_frame.iloc[
                              icp_df.pop("__mdb_norm_index")
                          ]
                      )
                      norm_input = EncodedDs(
                          ns.encoded_val_data.encoders,
                          norm_input_df,
                          ns.target,
                      )
                      norm_cache = group_normalizer(
                          norm_input, args=PredictionArguments()
                      )
                      icp_df[f"__norm_{ns.target}"] = norm_cache
  
                  # save relevant predictions in the caches, then calibrate the ICP
                  pred_cache = icp_df.pop(
                      f"__predicted_{ns.target}"
                  ).values
                  if ns.is_multi_ts and ns.is_classification:
                      pred_cache = output["label_encoders"].transform(
                          [[p[0] for p in pred_cache]]
                      )
                  elif ns.is_multi_ts:
                      pred_cache = np.array(
                          [np.array(p) for p in pred_cache]
                      )
                  elif ns.is_classification:
                      pred_cache = output["label_encoders"].transform(
                          pred_cache.reshape(-1, 1)
                      )
  
                  icps[
                      tuple(group)
                  ].nc_function.model.prediction_cache = pred_cache
                  icp_df, y = clean_df(
                      icp_df, ns, output.get("label_encoders", None)
                  )
                  if (
                      icps[tuple(group)].nc_function.normalizer
                      is not None
                  ):
                      icps[
                          tuple(group)
                      ].nc_function.normalizer.prediction_cache = icp_df.pop(
                          f"__norm_{ns.target}"
                      ).values
  
                  icps[
                      tuple(group)
                  ].index = (
                      icp_df.columns
                  )  # important at inference time
                  icps[tuple(group)].calibrate(icp_df.values, y)
  
                  # save training std() for bounds width selection
                  if not ns.is_classification:
                      icp_train_df = ns.data
                      for key, val in zip(group_keys, group):
                          icp_train_df = icp_train_df[
                              icp_train_df[key] == val
                          ]
                      y_train = icp_train_df[ns.target].values
                      output["df_target_stddev"][
                          tuple(group)
                      ] = y_train.std()
  
                  # get bounds for relevant rows in validation dataset
                  conf, group_ranges = set_conf_range(
                      icp_df,
                      icps[tuple(group)],
                      ns.dtype_dict[ns.target],
                      output,
                      group=tuple(group),
                      positive_domain=ns.stats_info.positive_domain,
                      significance=self.fixed_significance,
                  )
                  # save group bounds
                  if not ns.is_classification:
                      result_df.loc[
                          icp_df.index, "lower"
                      ] = group_ranges[:, 0]
                      result_df.loc[
                          icp_df.index, "upper"
                      ] = group_ranges[:, 1]
  
                  result_df.loc[icp_df.index, "confidence"] = conf
  
          # consolidate all groups here
          output["icp"]["__mdb_active"] = True
  
      result_df.index = ns.data.index
      output["result_df"] = result_df
  
      info = {**info, **output}
      return info
  
imports: import inspect
  from copy import deepcopy
  from typing import Dict, Tuple, Optional
  from types import SimpleNamespace
  import numpy
  import pandas
  from sklearn.preprocessing import OneHotEncoder
  from type_infer.dtype import dtype
  from lightwood.api.types import PredictionArguments
  from lightwood.helpers.ts import add_tn_num_conf_bounds, add_tn_cat_conf_bounds, get_ts_groups
  from lightwood.data import EncodedDs
  from lightwood.analysis.base import BaseAnalysisBlock
  from lightwood.analysis.nc.norm import Normalizer
  from lightwood.analysis.nc.icp import IcpRegressor, IcpClassifier, IcpTSRegressor
  from lightwood.analysis.nc.base import CachedRegressorAdapter, CachedClassifierAdapter, CachedTSAdapter
  from lightwood.analysis.nc.nc import BoostedAbsErrorErrFunc, RegressorNc, ClassifierNc, MarginErrFunc, TSNc, TSAbsErrorErrFunc
  from lightwood.analysis.nc.util import clean_df, set_conf_range, get_numeric_conf_range, get_categorical_conf, get_anomalies, get_ts_conf_range
label_desc: Use image node_id 2 for calling the ICP obj's underlying member method code with example usage: obj.analyze(info) and returns: info, dict