PowerSHAP: Feature Selection using Statistical Significance of SHAP Values

repository·main·Indexed 42 days ago

https://github.com/predict-idlab/powershap

PowerSHAP (v0.1.0.1) is a wrapper-based feature selection method that identifies informative features by comparing their impact on model predictions against random noise using statistical hypothesis testing and power calculations on Shapley values. It features an automatic mode that determines the required iterations to meet a predefined power requirement (default 0.99) and supports both classification and regression tasks with any scikit-learn compatible estimator.

Tokens
9.4K
Snippets
26
Records
31
Agent score
52%

What's inside PowerSHAP

  1. How PowerShap works

    main

    PowerShap selects features by comparing their impact on model predictions against the impact of a known random feature.

    1. Iteration: The algorithm trains multiple models on different data subsets. In each iteration, a random uniform feature is added to the dataset.
    2. Shapley Calculation: For each model, it calculates the absolute Shapley values. For multi-output or multi-class tasks, it uses the maximum across outputs. These values are averaged per feature to represent impact.
    3. Statistical Testing: After all iterations, each feature's impact array is compared to the random feature's impact array using a percentile formula to generate a p-value.
    4. Selection: Features with a p-value below a specific threshold (default is 0.01) are retained.

    Automatic Mode

    To avoid manual hyperparameter tuning for the number of iterations and the threshold, PowerShap uses an automatic mode by default:

    • It starts with 10 iterations.
    • It calculates the effect size and statistical power (using a student-t power test) for each feature.
    • It determines the required iterations to meet a predefined power requirement (default is 0.99, representing a false positive probability of 0.01).
    • It continues executing additional iterations until the required power is achieved.
  2. How automatic mode and force convergence work

    main

    PowerShap provides two advanced modes for optimizing the feature selection process:

    Automatic Mode

    When automatic=True, the selector doesn't use a fixed number of power_iterations. Instead:

    1. It performs an initial run with 10 iterations.
    2. It calculates the required number of iterations needed to reach the power_req_iterations threshold for the given power_alpha.
    3. It restarts the process using this calculated number of iterations.
    4. If the required iterations exceed limit_automatic, it incrementally adds limit_incremental_iterations and re-evaluates (up to limit_recursive_automatic times).

    Force Convergence

    When force_convergence=True (typically used in high-dimensional datasets), PowerShap performs a recursive selection:

    1. It identifies significant features.
    2. It removes those features from the dataset.
    3. It reruns the selection process on the remaining features.
    4. This continues until no more significant features are found or limit_convergence_its is reached.

    This ensures that the selection is robust and that features are not being selected simply because they are masking other important variables.

  3. Deep learning model requirements

    main

    If you are using a deep learning model (e.g., TensorFlow) with PowerShap, you must account for the following:

    1. Input Size: The model must be configured to accept |features| + 1 as input size. This is because PowerShap adds a random feature during the Shapley calculation process.
    2. Required Fit Arguments: You must pass the following parameters via **fit_kwargs in the PowerShap constructor or via .fit():
      • epochs
      • optimizer
      • batch_size
      • nn_metric
      • loss
  4. Compare PowerShap with other feature selection methods

    main

    The simulation examples demonstrate how to benchmark PowerShap against common statistical and machine learning feature selection techniques:

    1. Statistical Tests: chi2 (Chi-squared), f_classif (ANOVA F-value for classification), and f_regression (F-value for regression).
    2. Model-based Selection: LogisticRegressionCV, RandomForestClassifier, and BorutaShap.
    3. SHAP-based Selection: shapicant.PandasSelector.

    When benchmarking, it is common to use synthetic datasets from sklearn.datasets.make_classification or make_regression with a known number of informative features to evaluate the accuracy (true positives vs. noise) and runtime of each method.

  5. Parse and aggregate benchmark results from Markdown tables

    main

    Benchmark results are often stored in Markdown tables. To process them for visualization, you can use pandas.read_table with a pipe | separator.

    Parsing Logic:

    1. Load: Use pd.read_table(path, sep="|", ...).
    2. Clean: Strip whitespace from column names and handle empty columns.
    3. Extract Statistics: If metrics are stored as value (std_dev) (e.g., 0.85 (0.02)), use string manipulation to separate the mean and the standard deviation into two distinct columns (metric and metric_std).
    4. Normalize Method Names: Map various string patterns (e.g., Automatic default powershap) to canonical names (e.g., powershap) for consistent plotting.
    import pandas as pd
    import re
    
    # Load markdown table
    df_t = pd.read_table("../../data/dataset.md", sep="|", header=0, index_col=1, skipinitialspace=True).dropna(axis=1, how='all').iloc[1:]
    
    # Extract mean and standard deviation from 'Value (Std)' format
    for col in ['AUC', 'MCC', 'ACC']:
        df_t[col+"_std"] = 0
        mask = df_t[col].str.contains("\(\\)")
        # Extract value inside parentheses
        df_t.loc[mask, col+"_std"] = [pd.to_numeric(s[s.find("(")+1:s.find(")")]) for s in df_t.loc[mask, col].values]
        # Extract mean value before the parenthesis
        df_t[col] = [pd.to_numeric(s[:s.find(" ")]) for s in df_t[col].values]
  6. Inspect feature selection insights

    main

    After fitting a PowerShap selector, you can inspect the underlying statistical results by accessing the ._processed_shaps_df attribute. This provides insights into the feature selection method used.

    # After selector.fit(X, y)
    insights = selector._processed_shaps_df
    print(insights)
  7. Configure PowerShap parameters

    main

    When initializing PowerShap, you can tune several parameters to control the selection process:

    ParameterTypeDefaultDescription
    modelAnyNoneThe model used for Shapley calculations. Supported: catboost, sklearn tree-based, sklearn linear, and tensorflow deep learning. If None, a CatBoost model is automatically selected based on the target y type.
    power_iterationsint10Number of shuffles/iterations. Ignored if automatic=True.
    power_alphafloat0.01Significance threshold (alpha) for the statistical test.
    val_sizefloat0.2Fractional size of the validation set (between 0 and 1).
    power_req_iterationsfloat0.95Fractional power percentage required for iterations calculation.
    include_allboolFalseIf True, analyzes all features; otherwise, only those meeting the power_alpha threshold.
    automaticboolTrueIf True, calculates required iterations dynamically before running the full selection.
    force_convergenceboolFalseIf True (used with automatic), continues deleting found features and rerunning until no more significant features are found.
    limit_convergence_itsint0Max allowed recursions when force_convergence is True. 0 means no limit.
    stratifyboolFalseWhether to perform a stratified train/test split based on y.
    cvBaseCrossValidatorNoneCross-validator to use. Will be wrapped to yield infinite splits.
    show_progressboolTrueWhether to show progress bars.
    verboseboolFalseWhether to show verbose console output.
    **fit_kwargsdict-Keyword arguments passed to the underlying model's .fit() method. For deep learning, requires epochs, optimizer, batch_size, nn_metric, and loss.
  8. Select features using PowerSHAP automatic mode results

    main

    When using the powershap model in a benchmarking workflow, you can select features based on the statistical significance of their SHAP values. This is done by reading the results from the automatic mode CSV file and filtering for features where the p_value is less than a specific threshold (e.g., 0.01).

    elif model == "powershap":
        processed_shaps_df = pd.read_csv("results/appliances_PowerSHAP_catboost_results_automatic_mode.csv")
        selected_features = processed_shaps_df[(processed_shaps_df.p_value < 0.01)]["index"].values
  9. Evaluate model performance with test_bootstrap_eval_regres

    main

    To evaluate a regression model (such as a CatBoostRegressor) using bootstrap evaluation on a test dataset, use the test_bootstrap_eval_regres function. This function requires the model, a copy of the test dataframe, the number of bootstrap iterations, a random state, the list of selected features, and the target column name.

    scores_cv_test = test_bootstrap_eval_regres(
        Model = CB_model, 
        test_df = current_db_test.copy(deep=True), 
        bootstrap_its=1000, 
        RS=1, 
        features = selected_features, 
        target_col = target_col
    )
    
    # To print the results (mean and standard deviation per metric):
    for key in scores_cv_test:
        print(f"{key}: {np.round(np.mean(scores_cv_test[key]), 3)} ({np.round(np.std(scores_cv_test[key]), 3)})")
  10. Visualize classification and regression benchmark performance

    main

    Once benchmark data is parsed, use Matplotlib bar plots to compare methods across different datasets and evaluation sets.

    For Classification:

    • Use metrics like AUC, MCC, ACC, Recall, F1, or Precision.
    • Use yerr in plt.bar() to represent the standard deviation extracted during parsing.
    • Create a grid of subplots where each subplot represents a dataset and an Eval set.

    For Regression:

    • Use metrics like R², RMSE, MAE, or ME.
    • Follow a similar subplot grid pattern to classification benchmarks.
    import matplotlib.pyplot as plt
    
    metric = "AUC"
    f, axs = plt.subplots(2, 3, figsize=(15, 6))
    
    # Iterate through unique evaluation sets and datasets to fill the grid
    for i, evalset in enumerate(end_class_df["Eval set"].unique()):
        for j, dataset in enumerate(end_class_df.dataset.unique()):
            current_df = end_class_df[(end_class_df.dataset == dataset) & (end_class_df["Eval set"] == evalset)]
            axs[i][j].bar(
                current_df.Method.values, 
                current_df[metric].values, 
                yerr=current_df[metric+"_std"].values, 
                capsize=10
            )
            axs[i][j].set_title(f"{dataset} : {evalset}")
  11. Perform a single PowerShap feature selection test

    main

    To use PowerShap for feature selection, initialize the PowerShap class by passing a machine learning model (e.g., from catboost or sklearn). You can then call .fit(X, y) to perform the selection.

    By default, PowerShap works with any model that follows the standard estimator interface. You can enable verbose=True to monitor the process.

    from powershap import PowerShap
    from catboost import CatBoostClassifier
    
    selector = PowerShap(
        model = CatBoostClassifier(verbose=0, n_estimators=250, use_best_model=True),
        verbose=True,
    )
    selector.fit(X_train, y_train)