code: def compute_matrix_on_dataset(
      analyzer,
      features,
      dataset,
      quantile_binning=False,
      num_bins=BIN_THRESHOLD,
  ):
      """Compute a matrix of metrics for a given set of feature names.
  
      The filters and composite filters are used to filter the data
      prior to computing the matrix.
  
      :param analyzer: The error analyzer.
      :type analyzer: BaseAnalyzer
      :param features: A list of one or two feature names to compute metrics for.
      :type features: list
      :param dataset: The dataset on which matrix view needs to be computed.
          The dataset should have the feature columns and the columns
          'true_y' and 'index'. The 'true_y' column should have the true
          target values corresponding to the test data. The 'index'
          column should have the indices. If the analyzer is of type
          PredictionsAnalyzer, then the dataset should include the column
          'pred_y' which will hold the predictions.
      :type dataset: pd.DataFrame
      :param quantile_binning: Whether to use quantile binning.
      :type quantile_binning: bool
      :param num_bins: The number of bins to use for quantile binning.
      :type num_bins: int
      :return: A dictionary representation of the computed matrix which can be
          saved to JSON.
      :rtype: dict
      """
      if num_bins <= 0:
          raise ValueError(
              "Number of bins parameter must be greater than 0 for the heatmap"
          )
      if features[0] is None and features[1] is None:
          raise ValueError(
              "One or two features must be specified to compute the heat map"
          )
  
      true_y = dataset[TRUE_Y]
      dropped_cols = [TRUE_Y, ROW_INDEX]
      is_model_analyzer = hasattr(analyzer, "model")
      if not is_model_analyzer:
          pred_y = dataset[PRED_Y]
          dropped_cols.append(PRED_Y)
      input_data = dataset.drop(columns=dropped_cols)
      is_pandas = isinstance(analyzer.dataset, pd.DataFrame)
      metric = analyzer.metric
      if is_pandas:
          true_y = true_y.to_numpy()
      else:
          input_data = input_data.to_numpy()
      if is_model_analyzer:
          pred_y = analyzer.model.predict(input_data)
      if analyzer.model_task == ModelTask.CLASSIFICATION:
          diff = pred_y != true_y
      elif analyzer.model_task == ModelTask.OBJECT_DETECTION:
          if not pytorch_installed:
              raise ModuleNotFoundError(
                  "User Error: torch & torchvision are not installed "
                  "and are needed for the Object Detection scenario."
              )
          diff = [
              len(
                  ErrorLabeling(
                      ModelTask.OBJECT_DETECTION,
                      pred_y[image_idx],
                      true_y[image_idx],
                  ).compute_error_list()
              )
              > 0
              for image_idx in range(len(true_y))
          ]
      else:
          diff = pred_y - true_y
      if not isinstance(diff, np.ndarray):
          diff = np.array(diff)
      if not isinstance(pred_y, np.ndarray):
          pred_y = np.array(pred_y)
      if not isinstance(true_y, np.ndarray):
          true_y = np.array(true_y)
      indexes = []
      for feature in features:
          if feature is None:
              continue
          if feature not in analyzer.feature_names:
              msg = "Feature {} not found in dataset. Existing features: {}"
              raise UserConfigValidationException(
                  msg.format(feature, analyzer.feature_names)
              )
          indexes.append(analyzer.feature_names.index(feature))
      if is_pandas:
          input_data = input_data.to_numpy()
      dataset_sub_features = input_data[:, indexes]
      dataset_sub_names = np.array(analyzer.feature_names)[
          np.array(indexes)
      ]
      df = pd.DataFrame(dataset_sub_features, columns=dataset_sub_names)
      # Fix for newer versions of pandas where qcut fails for object dtypes.
      # Note this bug appears in newer versions of pandas+numpy but
      # convert_dtypes method only exists in pandas>1.1.4.
      df = convert_dtypes(df)
      df_err = df.copy()
      df_err[DIFF] = diff
      if metric == Metrics.ERROR_RATE:
          df_err = df_err[df_err[DIFF]]
      else:
          df_err[TRUE_Y] = true_y
          df_err[PRED_Y] = pred_y
      df_err = convert_dtypes(df_err)
      # construct matrix
      matrix = []
      if len(dataset_sub_names) == 2:
          feat1 = dataset_sub_names[0]
          feat2 = dataset_sub_names[1]
          unique_count1 = len(df[feat1].unique())
          unique_count2 = len(df[feat2].unique())
          f1_is_cat = False
          f2_is_cat = False
          if analyzer.categorical_features is not None:
              f1_is_cat = feat1 in analyzer.categorical_features
              f2_is_cat = feat2 in analyzer.categorical_features
          if unique_count1 > num_bins and not f1_is_cat:
              tabdf1 = bin_data(
                  df, feat1, num_bins, quantile_binning=quantile_binning
              )
              categories1 = tabdf1.cat.categories
              if len(categories1) < num_bins:
                  warn_duplicate_edges(feat1)
              tabdf1_err = bin_data(
                  df_err,
                  feat1,
                  categories1,
                  quantile_binning=quantile_binning,
              )
          else:
              tabdf1 = df[feat1]
              tabdf1_err = df_err[feat1]
              categories1 = np.unique(
                  tabdf1.to_numpy(), return_counts=True
              )[0]
          if unique_count2 > num_bins and not f2_is_cat:
              tabdf2 = bin_data(
                  df, feat2, num_bins, quantile_binning=quantile_binning
              )
              categories2 = tabdf2.cat.categories
              if len(categories2) < num_bins:
                  warn_duplicate_edges(feat2)
              tabdf2_err = bin_data(
                  df_err,
                  feat2,
                  categories2,
                  quantile_binning=quantile_binning,
              )
          else:
              tabdf2 = df[feat2]
              tabdf2_err = df_err[feat2]
              categories2 = np.unique(
                  tabdf2.to_numpy(), return_counts=True
              )[0]
          if metric == Metrics.ERROR_RATE:
              matrix_total = pd.crosstab(
                  tabdf1, tabdf2, rownames=[feat1], colnames=[feat2]
              )
              matrix_error = pd.crosstab(
                  tabdf1_err,
                  tabdf2_err,
                  rownames=[feat1],
                  colnames=[feat2],
              )
          else:
              if is_multi_agg_metric(metric):
                  ordered_labels = get_ordered_classes(
                      analyzer.classes, true_y, pred_y
                  )
                  aggfunc = _MultiMetricAggFunc(
                      metric_to_func[metric], ordered_labels, metric
                  )
              else:
                  aggfunc = _AggFunc(metric_to_func[metric])
              matrix_total = pd.crosstab(
                  tabdf1, tabdf2, rownames=[feat1], colnames=[feat2]
              )
              matrix_error = pd.crosstab(
                  tabdf1_err,
                  tabdf2_err,
                  rownames=[feat1],
                  colnames=[feat2],
                  values=list(zip(df_err[TRUE_Y], df_err[PRED_Y])),
                  aggfunc=aggfunc._agg_func_pair,
              )
              fill_matrix_nulls(matrix_total, aggfunc._fill_na_value())
              fill_matrix_nulls(matrix_error, aggfunc._fill_na_value())
          matrix = matrix_2d(
              categories1,
              categories2,
              matrix_total,
              matrix_error,
              metric,
          )
      else:
          feat1 = dataset_sub_names[0]
          unique_count1 = len(df[feat1].unique())
          f1_is_cat = False
          if analyzer.categorical_features is not None:
              f1_is_cat = feat1 in analyzer.categorical_features
          if unique_count1 > num_bins and not f1_is_cat:
              cutdf = bin_data(
                  df, feat1, num_bins, quantile_binning=quantile_binning
              )
              num_categories = len(cutdf.cat.categories)
              bin_range = range(num_categories)
              if len(cutdf.cat.categories) < num_bins:
                  warn_duplicate_edges(feat1)
              catr = cutdf.cat.rename_categories(bin_range)
              catn, counts = np.unique(
                  catr.to_numpy(), return_counts=True
              )
              # fix counts to include skipped categories
              fix_counts = []
              counts_idx = 0
              for idx, _ in enumerate(cutdf.cat.categories):
                  if idx not in catn:
                      fix_counts.append(0)
                  else:
                      fix_counts.append(counts[counts_idx])
                      counts_idx += 1
              counts = fix_counts
              cut_err = bin_data(
                  df_err,
                  feat1,
                  cutdf.cat.categories,
                  quantile_binning=quantile_binning,
              )
              catr_err = cut_err.cat.rename_categories(bin_range)
              val_err, counts_err = np.unique(
                  catr_err.to_numpy(), return_counts=True
              )
              val_err = cut_err.cat.categories[val_err]
              categories = cutdf.cat.categories
          else:
              categories, counts = np.unique(
                  df[feat1].to_numpy(), return_counts=True
              )
              val_err, counts_err = np.unique(
                  df_err[feat1].to_numpy(), return_counts=True
              )
              cut_err = df_err
          # Compute the given metric for each group, if not using error rate
          if metric != Metrics.ERROR_RATE:
              if is_multi_agg_metric(metric):
                  ordered_labels = get_ordered_classes(
                      analyzer.classes, true_y, pred_y
                  )
                  aggfunc = _MultiMetricAggFunc(
                      metric_to_func[metric], ordered_labels, metric
                  )
              else:
                  aggfunc = _AggFunc(metric_to_func[metric])
              cutdf_err = pd.DataFrame(cut_err)
              cutdf_err["metric_values"] = list(
                  zip(df_err[TRUE_Y], df_err[PRED_Y])
              )
              grouped = cutdf_err.groupby([feat1])
              agg_func = {"metric_values": aggfunc._agg_func_grouped}
              counts_err = grouped.agg(agg_func)
              counts_err = counts_err.values.ravel()
          matrix = matrix_1d(
              categories, val_err, counts, counts_err, metric
          )
      return matrix
  
imports: import logging
  import math
  import warnings
  from abc import ABC, abstractmethod
  import numpy
  import pandas
  from sklearn.metrics import multilabel_confusion_matrix
  from erroranalysis._internal.cohort_filter import filter_from_cohort
  from erroranalysis._internal.constants import DIFF, PRED_Y, ROW_INDEX, TRUE_Y, MatrixParams, MetricKeys, Metrics, ModelTask, metric_to_display_name
  from erroranalysis._internal.metrics import get_ordered_classes, is_multi_agg_metric, metric_to_func
  from raiutils.exceptions import UserConfigValidationException
label_desc: Use image node_id 2 for calling a global function with example usage: compute_matrix_on_dataset(analyzer, features, dataset, quantile_binning, num_bins) and returns: matrix