code: def multiclass_classification(
      target,
      predicted_labels,
      sensitive_features,
      fairness_metric,
      fairness_threshold,
      privileged_groups=[],
      underprivileged_groups=[],
      previous_fairness_optimization=None,
  ):
      target = np.array(target).ravel()
      preds = np.array(predicted_labels)
      target_values = list(np.unique(target))
  
      # fairness optimization stats
      sensitive_values = {}
      for col in sensitive_features.columns:
          col_name = col[10:]  # skip 'senstive_'
          values = list(sensitive_features[col].unique())
          sensitive_values[col] = values
          for v in values:
              ii = sensitive_features[col] == v
          new_sensitive_values = {}
          for k, prev_values in sensitive_values.items():
              if k == col:
                  continue
              new_sensitive_values[f"{k}@{col}"] = []
              for v in values:
                  for pv in prev_values:
                      if isinstance(pv, tuple):
                          new_sensitive_values[f"{k}@{col}"] += [
                              (*pv, v)
                          ]
                      else:
                          new_sensitive_values[f"{k}@{col}"] += [
                              (pv, v)
                          ]
  
          sensitive_values = {
              **sensitive_values,
              **new_sensitive_values,
          }
  
      sensitive_indices = {}
      for k, values_list in sensitive_values.items():
          if k.count("@") == sensitive_features.shape[1] - 1:
              cols = k.split("@")
              for values in values_list:
                  if not isinstance(values, tuple):
                      values = (values,)
  
                  ii = None
                  for i, c in enumerate(cols):
                      if ii is None:
                          ii = sensitive_features[c] == values[i]
                      else:
                          ii &= sensitive_features[c] == values[i]
  
                  key = "@".join([str(s) for s in values])
                  sensitive_indices[key] = ii
  
      cs = {}
      for t in target_values:
          cs[t] = np.sum(target == t)
      selection_rates = {}
      weights = {}
  
      for key, indices in sensitive_indices.items():
          weights[key] = []
          sv = np.sum(indices)
          selection_rates[key] = {}
          for t in target_values:
              selection_rates[key][t] = np.sum(
                  (preds == t) & indices
              ) / np.sum(indices)
  
              t_k = np.sum(indices & (target == t))
              w_k = sv / target.shape[0] * cs[t] / t_k
              weights[key] += [w_k]
  
      for t in target_values:
          values = []
          for k, v in selection_rates.items():
              values += [v[t]]
          max_selection_rate = np.max(values)
          for k, v in selection_rates.items():
              v[t] /= max_selection_rate
  
      previous_weights = {}
      if previous_fairness_optimization is not None:
          weights = previous_fairness_optimization.get("weights")
          for key, indices in sensitive_indices.items():
              previous_weights[key] = [1] * len(target_values)
              for i, t in enumerate(target_values):
                  direction = 0.0
                  if (
                      previous_fairness_optimization["selection_rates"][
                          key
                      ][t]
                      < selection_rates[key][t]
                  ):
                      direction = 1.0
                  elif selection_rates[key][t] > 0.8:
                      direction = 0.0
                  else:
                      direction = -0.5
  
                  # need to add previous weights instead 1.0
                  prev_weights = previous_fairness_optimization.get(
                      "previous_weights", {}
                  ).get(key, [1] * len(target_values))
  
                  delta_i = weights[key][i] - prev_weights[i]
  
                  previous_weights[key][i] = weights[key][i]
  
                  weights[key][i] += direction * delta_i
  
      step = None
      if previous_fairness_optimization is not None:
          step = previous_fairness_optimization.get("step")
  
      if step is None:
          step = 0
      else:
          step += 1
  
      return {
          "selection_rates": selection_rates,
          "previous_weights": previous_weights,
          "weights": weights,
          "step": step,
          "fairness_threshold": fairness_threshold,
          "target_values": target_values,
      }
  
imports: import os
  import numpy
  import pandas
  from matplotlib import pyplot
label_desc: Use image node_id 3 for calling the FairnessOptimization obj's underlying member method code with example usage: obj.multiclass_classification(target, predicted_labels, sensitive_features, fairness_metric, fairness_threshold, privileged_groups, underprivileged_groups, previous_fairness_optimization) and returns: dict