code: def multiclass_classification(
      original_target,
      predicted_labels,
      sensitive_features,
      fairness_metric,
      fairness_threshold,
      privileged_groups=[],
      underprivileged_groups=[],
      previous_fairness_optimization=None,
  ):
      original_target = np.array(original_target).ravel()
      predicted_labels = np.array(predicted_labels)
      target_values = list(np.unique(original_target))
  
      fairness_metrics = {}
  
      for col in sensitive_features.columns:
          col_name = col[10:]  # skip 'senstive_'
  
          for target_value in target_values:
              # we need to reset them for each target value
              privileged_value, underprivileged_value = None, None
              for pg in privileged_groups:
                  if col_name in pg:
                      privileged_value = pg.get(col_name)
              for upg in underprivileged_groups:
                  if col_name in upg:
                      underprivileged_value = upg.get(col_name)
  
              target = np.copy(original_target)
              target[original_target == target_value] = 1
              target[original_target != target_value] = 0
  
              preds = np.copy(predicted_labels)
              preds[predicted_labels == target_value] = 1
              preds[predicted_labels != target_value] = 0
  
              accuracies = []
              selection_rates = []
              tprs = []
              fprs = []
              tnrs = []
              fnrs = []
              samples = []
              demographic_parity_diff = None
              demographic_parity_ratio = None
              equalized_odds_diff = None
              equalized_odds_ratio = None
  
              # overall
              accuracies += [accuracy(target, preds)]
              selection_rates += [selection_rate(preds)]
              tprs += [true_positive_rate(target, preds)]
              fprs += [false_positive_rate(target, preds)]
              tnrs += [true_negative_rate(target, preds)]
              fnrs += [false_negative_rate(target, preds)]
              samples += [target.shape[0]]
  
              values = sensitive_features[col].unique()
  
              for value in values:
                  accuracies += [
                      accuracy(
                          target[sensitive_features[col] == value],
                          preds[sensitive_features[col] == value],
                      )
                  ]
                  selection_rates += [
                      selection_rate(
                          preds[sensitive_features[col] == value]
                      )
                  ]
                  tprs += [
                      true_positive_rate(
                          target[sensitive_features[col] == value],
                          preds[sensitive_features[col] == value],
                      )
                  ]
                  fprs += [
                      false_positive_rate(
                          target[sensitive_features[col] == value],
                          preds[sensitive_features[col] == value],
                      )
                  ]
                  tnrs += [
                      true_negative_rate(
                          target[sensitive_features[col] == value],
                          preds[sensitive_features[col] == value],
                      )
                  ]
                  fnrs += [
                      false_negative_rate(
                          target[sensitive_features[col] == value],
                          preds[sensitive_features[col] == value],
                      )
                  ]
                  samples += [
                      np.sum([sensitive_features[col] == value])
                  ]
  
              metrics = pd.DataFrame(
                  {
                      "Samples": samples,
                      "Accuracy": accuracies,
                      "Selection Rate": selection_rates,
                      "True Positive Rate": tprs,
                      "False Negative Rate": fnrs,
                      "False Positive Rate": fprs,
                      "True Negative Rate": tnrs,
                  },
                  index=["Overall"] + list(values),
              )
  
              max_selection_rate = np.max(selection_rates[1:])
              min_selection_rate = np.min(selection_rates[1:])
  
              if privileged_value is not None:
                  for i, v in enumerate(values):
                      if v == privileged_value:
                          # starting from 1 because first selection rate is for all samples
                          max_selection_rate = selection_rates[i + 1]
  
              if underprivileged_value is not None:
                  for i, v in enumerate(values):
                      if v == underprivileged_value:
                          # starting from 1 because first selection rate is for all samples
                          min_selection_rate = selection_rates[i + 1]
  
              demographic_parity_diff = np.round(
                  max_selection_rate - min_selection_rate, 4
              )
              demographic_parity_ratio = np.round(
                  min_selection_rate / max_selection_rate, 4
              )
  
              tpr_min = np.min(tprs[1:])
              tpr_max = np.max(tprs[1:])
  
              fpr_min = np.min(fprs[1:])
              fpr_max = np.max(fprs[1:])
  
              if privileged_value is not None:
                  for i, v in enumerate(values):
                      if v == privileged_value:
                          # starting from 1 because first value is for all samples
                          tpr_max = tprs[i + 1]
                          fpr_max = fprs[i + 1]
  
              if underprivileged_value is not None:
                  for i, v in enumerate(values):
                      if v == underprivileged_value:
                          # starting from 1 because first value is for all samples
                          tpr_min = tprs[i + 1]
                          fpr_min = fprs[i + 1]
  
              equalized_odds_diff = np.round(
                  max(tpr_max - tpr_min, fpr_max - fpr_min), 4
              )
              equalized_odds_ratio = np.round(
                  min(tpr_min / tpr_max, fpr_min / fpr_max), 4
              )
  
              stats = pd.DataFrame(
                  {
                      "": [
                          demographic_parity_diff,
                          demographic_parity_ratio,
                          equalized_odds_diff,
                          equalized_odds_ratio,
                      ]
                  },
                  index=[
                      "Demographic Parity Difference",
                      "Demographic Parity Ratio",
                      "Equalized Odds Difference",
                      "Equalized Odds Ratio",
                  ],
              )
  
              fairness_metric_name = ""
              fairness_metric_value = 0
              is_fair = False
              if fairness_metric == "demographic_parity_difference":
                  fairness_metric_name = "Demographic Parity Difference"
                  fairness_metric_value = demographic_parity_diff
                  is_fair = demographic_parity_diff < fairness_threshold
              elif fairness_metric == "demographic_parity_ratio":
                  fairness_metric_name = "Demographic Parity Ratio"
                  fairness_metric_value = demographic_parity_ratio
                  is_fair = (
                      demographic_parity_ratio > fairness_threshold
                  )
              elif fairness_metric == "equalized_odds_difference":
                  fairness_metric_name = "Equalized Odds Difference"
                  fairness_metric_value = equalized_odds_diff
                  is_fair = equalized_odds_diff < fairness_threshold
              elif fairness_metric == "equalized_odds_ratio":
                  fairness_metric_name = "Equalized Odds Ratio"
                  fairness_metric_value = equalized_odds_ratio
                  is_fair = equalized_odds_ratio > fairness_threshold
  
              if "parity" in fairness_metric:
                  if privileged_value is None:
                      ind = np.argmax(selection_rates[1:])
                      privileged_value = values[ind]
                  if underprivileged_value is None:
                      ind = np.argmin(selection_rates[1:])
                      underprivileged_value = values[ind]
  
              if "odds" in fairness_metric:
                  if tpr_max - tpr_min > fpr_max - fpr_min:
                      if privileged_value is None:
                          ind = np.argmax(tprs[1:])
                          privileged_value = values[ind]
                      if underprivileged_value is None:
                          ind = np.argmin(tprs[1:])
                          underprivileged_value = values[ind]
                  else:
                      if privileged_value is None:
                          ind = np.argmax(fprs[1:])
                          privileged_value = values[ind]
                      if underprivileged_value is None:
                          ind = np.argmin(fprs[1:])
                          underprivileged_value = values[ind]
  
              fairness_metrics[f"{col_name}__{target_value}"] = {
                  "metrics": metrics,
                  "stats": stats,
                  "figures": FairnessPlots.binary_classification(
                      fairness_metric,
                      f"{col_name}__{target_value}",
                      metrics,
                      selection_rates,
                      max_selection_rate,
                      fairness_threshold,
                  ),
                  "fairness_metric_name": fairness_metric_name,
                  "fairness_metric_value": fairness_metric_value,
                  "is_fair": is_fair,
                  "privileged_value": privileged_value,
                  "underprivileged_value": underprivileged_value,
              }
  
      # fairness optimization stats
      fairness_metrics[
          "fairness_optimization"
      ] = FairnessOptimization.multiclass_classification(
          original_target,
          predicted_labels,
          sensitive_features,
          fairness_metric,
          fairness_threshold,
          privileged_groups,
          underprivileged_groups,
          previous_fairness_optimization,
      )
  
      return fairness_metrics
  
file_path: mljar-supervised/supervised/fairness/metrics.py
imports: import os
  import numpy
  import pandas
  from matplotlib import pyplot
  from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, f1_score, matthews_corrcoef, mean_absolute_error, mean_absolute_percentage_error, mean_squared_error, precision_score, r2_score, recall_score, roc_auc_score
  from supervised.fairness.optimization import FairnessOptimization
  from supervised.fairness.plots import FairnessPlots
  from supervised.fairness.utils import accuracy, false_negative_rate, false_positive_rate, selection_rate, true_negative_rate, true_positive_rate
  from supervised.utils.metric import pearson, spearman
label_desc: Use image node_id 3 for calling the FairnessMetrics obj's underlying member method code with example usage: obj.multiclass_classification(original_target, predicted_labels, sensitive_features, fairness_metric, fairness_threshold, privileged_groups, underprivileged_groups, previous_fairness_optimization) and returns: fairness_metrics