Skip to main content

PertCF

PyPI version Python versions CI License: MIT Paper

PertCF is a perturbation-based counterfactual explanation method that combines SHAP feature attribution with nearest-neighbour search to generate high-quality, stable counterfactuals for tabular classification models.

What is a counterfactual explanation?
Given a model's prediction for an instance x, a counterfactual x' is the minimal change to x that would flip the prediction. For example: "If Leo earned $500 more per month, his loan application would be accepted."


Why PertCF?

Feature PertCF DiCE CF-SHAP
Multi-class support ✅ ❌ ❌
SHAP-weighted distance ✅ ❌ Partial
Custom domain knowledge ✅ ❌ ❌
Works with sklearn, PyTorch, Keras ✅ Partial ❌
No external server needed ✅ ✅ ✅

PertCF outperforms DiCE and CF-SHAP on dissimilarity and instability across both benchmark datasets (South German Credit, User Knowledge Modeling). See the paper for full results.


Installation

pip install pertcf

For PyTorch or Keras model support:

pip install pertcf[torch]      # + PyTorch adapter
pip install pertcf[tensorflow] # + Keras/TF adapter
pip install pertcf[viz]        # + matplotlib/seaborn for plots

Requirements: Python ≥ 3.9, numpy, pandas, scikit-learn, shap.
No Java. No REST server. No external frameworks.


Quick Start (30 seconds)

import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import train_test_split
from pertcf import PertCFExplainer

# 1. Load data and train a model (example: German Credit dataset)
df = pd.read_csv("german_credit.csv")
X = df.drop(columns=["credit_risk"])
y = df["credit_risk"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf = GradientBoostingClassifier(random_state=42).fit(X_train, y_train)

# 2. Create and fit the explainer
explainer = PertCFExplainer(
    model=clf,
    X_train=X_train,
    y_train=y_train,
    categorical_features=["purpose", "personal_status", "housing"],
    label="credit_risk",
    num_iter=5,
    coef=5,
)
explainer.fit()

# 3. Explain a prediction
instance = X_test.iloc[0].copy()
instance["credit_risk"] = clf.predict(X_test.iloc[[0]])[0]

counterfactual = explainer.explain(instance)
print("Original:       ", instance.to_dict())
print("Counterfactual: ", counterfactual.to_dict())

Feature Highlights

Works with any classifier

# scikit-learn (auto-detected)
from sklearn.ensemble import RandomForestClassifier
explainer = PertCFExplainer(model=RandomForestClassifier().fit(X, y), ...)

# PyTorch
from pertcf import PertCFExplainer
explainer = PertCFExplainer(
    model=my_torch_model,
    class_names=["bad", "good"],
    ...
)

# Keras / TensorFlow
explainer = PertCFExplainer(
    model=my_keras_model,
    class_names=["bad", "good"],
    ...
)

# Any callable
explainer = PertCFExplainer(
    model=my_model,
    predict_fn=lambda X: my_model.predict(X),
    predict_proba_fn=lambda X: my_model.predict_proba(X),
    class_names=["bad", "good"],
    ...
)

Domain knowledge via custom similarity matrices

# Model the relationship between credit purposes
explainer = PertCFExplainer(
    model=clf,
    ...
    similarity_matrices={
        "purpose": {
            ("car", "furniture"): 0.7,   # similar purposes
            ("car", "education"): 0.2,   # less similar
        }
    }
)

Pre-computed SHAP values (for large datasets)

import shap
# Compute SHAP once, reuse across experiments
shap_exp = shap.TreeExplainer(clf)
shap_vals = shap_exp.shap_values(X_train)
# … build shap_df with shape (n_classes, n_features) …

explainer = PertCFExplainer(
    model=clf, shap_values=shap_df, ...
)
explainer.fit()  # skips SHAP computation

Built-in benchmark

# Reproduce the paper's Table 2 results
results = explainer.benchmark(X_test, n=100, coef=5, verbose=True)
# Results (n=100/100):
#   dissimilarity       : 0.0517
#   sparsity            : 0.7983
#   runtime_mean        : 0.4069

Evaluation metrics

from pertcf import metrics

print(metrics.dissimilarity(query, cf, explainer.sim_fn, cf_class))
print(metrics.sparsity(query, cf))
print(metrics.instability(query, cf, explainer))

# All at once:
results = metrics.evaluate(queries, counterfactuals, explainer)

How PertCF Works

1. Compute SHAP values per class → class-specific feature importance weights
2. For query x:
   a. Find Nearest Unlike Neighbour (NUN) using SHAP-weighted similarity
   b. Perturb x toward NUN using SHAP weights:
      - Numeric:     p_f = x_f + shap_target_f * (nun_f - x_f)
      - Categorical: p_f = nun_f  if sim(x_f, nun_f) < 0.5  else  x_f
   c. If perturbed instance flips class → refine (approach source)
   d. If not → push harder (approach target)
   e. Terminate when step size < threshold or max iterations reached

See the paper for full algorithmic details.


Examples

Notebook Dataset Description
quickstart_german_credit.ipynb South German Credit Basic usage, benchmark, comparison to DiCE
quickstart_knowledge.ipynb User Knowledge Modeling Multi-class classification
custom_similarity.ipynb German Credit Domain knowledge with custom similarity matrices

Launch in Colab: Open in Colab


Parameter Guide

Parameter Default Description
num_iter 10 Max perturbation iterations. Higher → better quality, slower.
coef 5 Step-size threshold coefficient. Higher → finer convergence.

Recommended settings from the paper:

Dataset num_iter coef Notes
South German Credit 5 5 Most categorical features
User Knowledge Modeling 5 3 All numeric features

Citation

If you use PertCF in your research, please cite:

@inproceedings{bayrak2023pertcf,
  title     = {PertCF: A Perturbation-Based Counterfactual Generation Approach},
  author    = {Bayrak, Bet{\"u}l and Bach, Kerstin},
  booktitle = {Artificial Intelligence XXXVIII},
  series    = {Lecture Notes in Computer Science},
  volume    = {14381},
  pages     = {174--187},
  year      = {2023},
  publisher = {Springer, Cham},
  doi       = {10.1007/978-3-031-47994-6_13}
}

License

MIT © Betül Bayrak, NTNU

This work was supported by the Research Council of Norway through the EXAIGON project (ID 304843).

Metadata

Release files for pertcf 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pertcf 1.0.0
File Size Uploaded Provenance Actions
pertcf-1.0.0.tar.gz 36.6 kB With provenance Details for pertcf-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pertcf 1.0.0
File Interpreter ABI Platform Actions
pertcf-1.0.0-py3-none-any.whl Python 3 none any Details for pertcf-1.0.0-py3-none-any.whl

Total release size: 54.9 kB

Release files / pertcf-1.0.0.tar.gz

Download URL pertcf-1.0.0.tar.gz
Size 36.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1ea11ce4dbd200138bc01bdd1d73d1b70e3b81e817d967fd4343337967613356
BLAKE2b-256 checksum
How to use checksums
75799a6bc6fca3a72ebf055f3437378d710074059936af8a2a83c8a36ecec31d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

Release files / pertcf-1.0.0-py3-none-any.whl

Download URL pertcf-1.0.0-py3-none-any.whl
Size 18.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e7e8192b9bd6e4fc90bb5c3ebf78a8719c8d4db1c30f8b0025554ca678ea888e
BLAKE2b-256 checksum
How to use checksums
6e66b7c835631ea7ed722f2f4e186541701e795df6c1f102c4184329d683d702
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

Security for pertcf 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pertcf-1.0.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page