code: def estimate_shapley_values(
      set_func: Callable[[np.ndarray], Union[float, np.ndarray]],
      num_players: int,
      shapley_config: Optional[ShapleyConfig] = None,
  ) -> np.ndarray:
      """Estimates the Shapley values based on the provided set function. A set function here is defined by taking a
      (subset) of players and returning a certain utility value. This is in the context of attributing the
      value of the i-th player to a subset of players S by evaluating v(S u {i}) - v(S), where v is the
      set function and i is not in S. While we use the term 'player' here, this is often a certain feature/variable.
  
      The input of the set function is a binary vector indicating which player is part of the set. For instance, given 4
      players (1,2,3,4) and a subset only contains players 1,2,4, then this is indicated by the vector [1, 1, 0, 1]. The
      function is expected to return a numeric value based on this input.
  
      Note: The set function can be arbitrary and can resemble computationally complex operations. Keep in mind
      that the estimation of Shapley values can become computationally expensive and requires a lot of memory. If the
      runtime is too slow, consider changing the default config.
  
      :param set_func: A set function that expects a binary vector as input which specifies which player is part of the
                       subset.
      :param num_players: Total number of players.
      :param shapley_config: A config object for indicating the approximation method and other parameters. If None is
                             given, a default config is used. For faster runtime or more accurate results, consider
                             creating a custom config.
      :return: A numpy array representing the Shapley values for each player, i.e. there are as many Shapley values as
               num_players. The i-th entry belongs to the i-th player. Here, the set function defines which index belongs
               to which player and is responsible to keep it consistent.
      """
      if shapley_config is None:
          shapley_config = ShapleyConfig()
  
      approximation_method = shapley_config.approximation_method
      if approximation_method == ShapleyApproximationMethods.AUTO:
          if num_players <= 5:
              approximation_method = ShapleyApproximationMethods.EXACT
          else:
              approximation_method = (
                  ShapleyApproximationMethods.EARLY_STOPPING
              )
  
      if approximation_method == ShapleyApproximationMethods.EXACT:
          return _estimate_shapley_values_exact(
              set_func=set_func,
              num_players=num_players,
              n_jobs=shapley_config.n_jobs,
          )
      elif (
          approximation_method
          == ShapleyApproximationMethods.PERMUTATION
      ):
          return _approximate_shapley_values_via_permutation_sampling(
              set_func=set_func,
              num_players=num_players,
              num_permutations=shapley_config.num_permutations,
              n_jobs=shapley_config.n_jobs,
          )
      elif (
          approximation_method
          == ShapleyApproximationMethods.EARLY_STOPPING
      ):
          return _approximate_shapley_values_via_early_stopping(
              set_func=set_func,
              num_players=num_players,
              max_num_permutations=shapley_config.num_permutations,
              min_percentage_change_threshold=shapley_config.min_percentage_change_threshold,
              n_jobs=shapley_config.n_jobs,
          )
      elif (
          approximation_method
          == ShapleyApproximationMethods.SUBSET_SAMPLING
      ):
          return _approximate_shapley_values_via_least_squares_regression(
              set_func=set_func,
              num_players=num_players,
              use_subset_approximation=True,
              num_samples_for_approximation=shapley_config.num_subset_samples,
              n_jobs=shapley_config.n_jobs,
          )
      elif (
          approximation_method == ShapleyApproximationMethods.EXACT_FAST
      ):
          return (
              _approximate_shapley_values_via_least_squares_regression(
                  set_func=set_func,
                  num_players=num_players,
                  use_subset_approximation=False,
                  num_samples_for_approximation=-1,
                  n_jobs=shapley_config.n_jobs,
              )
          )
      else:
          raise ValueError("Unknown method for Shapley approximation!")
  
imports: import itertools
  import math
  from enum import Enum
  from typing import Callable, Dict, List, Optional, Set, Tuple, Union
  import numpy
  import scipy
  from joblib import Parallel, delayed
  from scipy.special import comb
  from scipy.stats._qmc import Halton
  from sklearn.linear_model import LinearRegression
  from tqdm import tqdm
  import dowhy.gcm.config
  from dowhy.gcm.util.general import set_random_seed
label_desc: Use image node_id 1 for calling a global function with example usage: estimate_shapley_values(set_func, num_players, shapley_config) and returns: _estimate_shapley_values_exact, _approximate_shapley_values_via_permutation_sampling, _approximate_shapley_values_via_early_stopping, _approximate_shapley_values_via_least_squares_regression, _approximate_shapley_values_via_least_squares_regression