code: def test_threshold_optimization_equalized_odds(
      score_transform, y_transform, sensitive_features_transform
  ):
      y = y_transform(labels_ex)
      sensitive_features = sensitive_features_transform(
          sensitive_features_ex1
      )
      # PassThroughPredictor takes scores_ex as input in predict and
      # returns score_transform(scores_ex) as output
      estimator = ThresholdOptimizer(
          estimator=PassThroughPredictor(score_transform),
          constraints="equalized_odds",
          flip=True,
          predict_method="predict",
      )
      estimator.fit(
          pd.DataFrame(scores_ex),
          y,
          sensitive_features=sensitive_features,
      )
  
      def prob_pred(sensitive_features, scores):
          return estimator._pmf_predict(
              pd.DataFrame(scores),
              sensitive_features=sensitive_features,
          )[0, 1]
  
      # For Equalized Odds we need to factor in that the output is calculated by
      # p_ignore * prediction_constant + (1 - p_ignore) * (p0 * pred0(x) + p1 * pred1(x))
      # with p_ignore != 0 and prediction_constant != 0 for at least some sensitive feature values.
      prediction_constant = 0.334
  
      # sensitive feature value A
      # p_ignore is almost 0 which means there's almost no adjustment
      p_ignore = 0.001996007984031716
      base_value = prediction_constant * p_ignore
      value_for_less_than_2_5 = base_value + (1 - p_ignore) * 0.668
  
      assert np.isclose(
          value_for_less_than_2_5,
          prob_pred([sensitive_feature_names_ex1[0]], [0]),
      )
      assert np.isclose(
          value_for_less_than_2_5,
          prob_pred([sensitive_feature_names_ex1[0]], [2.499]),
      )
      assert base_value == prob_pred(
          [sensitive_feature_names_ex1[0]], [2.5]
      )
      assert base_value == prob_pred(
          [sensitive_feature_names_ex1[0]], [100]
      )
  
      # sensitive feature value B
      # p_ignore is the largest among the three classes indicating a large adjustment
      p_ignore = 0.1991991991991991
      base_value = prediction_constant * p_ignore
      value_for_less_than_0_5 = base_value + (1 - p_ignore) * 0.001
      assert np.isclose(
          value_for_less_than_0_5,
          prob_pred([sensitive_feature_names_ex1[1]], [0]),
      )
      assert np.isclose(
          value_for_less_than_0_5,
          prob_pred([sensitive_feature_names_ex1[1]], [0.5]),
      )
      assert base_value + 1 - p_ignore == prob_pred(
          [sensitive_feature_names_ex1[1]], [0.51]
      )
      assert base_value + 1 - p_ignore == prob_pred(
          [sensitive_feature_names_ex1[1]], [1]
      )
      assert base_value + 1 - p_ignore == prob_pred(
          [sensitive_feature_names_ex1[1]], [100]
      )
  
      # sensitive feature value C
      # p_ignore is 0 which means there's no adjustment
      p_ignore = 0
      base_value = prediction_constant * p_ignore
      value_between_0_5_and_1_5 = base_value + (1 - p_ignore) * 0.501
      assert base_value == prob_pred(
          [sensitive_feature_names_ex1[2]], [0]
      )
      assert base_value == prob_pred(
          [sensitive_feature_names_ex1[2]], [0.5]
      )
      assert np.isclose(
          value_between_0_5_and_1_5,
          prob_pred([sensitive_feature_names_ex1[2]], [0.51]),
      )
      assert np.isclose(
          value_between_0_5_and_1_5,
          prob_pred([sensitive_feature_names_ex1[2]], [1]),
      )
      assert np.isclose(
          value_between_0_5_and_1_5,
          prob_pred([sensitive_feature_names_ex1[2]], [1.5]),
      )
      assert base_value + 1 - p_ignore == prob_pred(
          [sensitive_feature_names_ex1[2]], [1.51]
      )
      assert base_value + 1 - p_ignore == prob_pred(
          [sensitive_feature_names_ex1[2]], [100]
      )
  
      # Assert Equalized Odds actually holds
      predictions_by_sensitive_feature = (
          _get_predictions_by_sensitive_feature(
              prob_pred, sensitive_features_ex1, scores_ex, labels_ex
          )
      )
  
      def _average_prediction_for_label(
          label,
          sensitive_feature_value,
          predictions_by_sensitive_feature,
      ):
          relevant_predictions = predictions_by_sensitive_feature[
              sensitive_feature_value
          ]
          predictions_for_label = [
              lp.prediction
              for lp in relevant_predictions
              if lp.label == label
          ]
          sum_of_predictions_for_label = np.sum(predictions_for_label)
          n_predictions_for_label = len(
              [lp for lp in relevant_predictions if lp.label == label]
          )
          return sum_of_predictions_for_label / n_predictions_for_label
  
      predictions_based_on_label = {0: [], 1: []}
      for label in [0, 1]:
          for sensitive_feature_value in sorted(
              predictions_by_sensitive_feature
          ):
              predictions_based_on_label[label].append(
                  _average_prediction_for_label(
                      label,
                      sensitive_feature_value,
                      predictions_by_sensitive_feature,
                  )
              )
  
      # assert counts of positive predictions for negative labels
      assert np.isclose(
          predictions_based_on_label[0], [0.334] * 3
      ).all()
      # assert counts of positive predictions for positive labels
      assert np.isclose(
          predictions_based_on_label[1], [0.66733333] * 3
      ).all()
  
imports: from copy import deepcopy
  from test.unit.input_convertors import _map_into_single_column
  import numpy
  import pandas
  import pytest
  from sklearn.base import BaseEstimator, ClassifierMixin
  from sklearn.datasets import make_classification
  from fairlearn.postprocessing import ThresholdOptimizer
  from fairlearn.postprocessing._threshold_optimizer import BASE_ESTIMATOR_NONE_ERROR_MESSAGE, NOT_SUPPORTED_CONSTRAINTS_ERROR_MESSAGE
  from fairlearn.postprocessing._tradeoff_curve_utilities import DEGENERATE_LABELS_ERROR_MESSAGE
  from fairlearn.utils._input_validation import _LABELS_NOT_0_1_ERROR_MESSAGE, _MESSAGE_SENSITIVE_FEATURES_NONE, _MESSAGE_Y_NONE
  from .conftest import ExamplePredictor, X_ex, _get_predictions_by_sensitive_feature, candidate_A_transforms, candidate_X_transforms, candidate_Y_transforms, degenerate_labels_ex, is_invalid_transformation, labels_ex, scores_ex, sensitive_feature_names_ex1, sensitive_features_ex1
label_desc: Use image node_id 9 for calling a global function with example usage: test_threshold_optimization_equalized_odds(score_transform, y_transform, sensitive_features_transform) and returns: estimator, unknown