xai-cola 0.1.0
COunterfactual explanations with Limited Actions (COLA)
License: MIT
Requires Python: >=3.9
Keywords: explainable AI, XAI, counterfactual explanations, machine learning, interpretability

<p align="center">
  <img src="https://raw.githubusercontent.com/understanding-ml/COLA/COLA-4-MVP/docs/images/logo_cola.png" width="600" alt="COLA Logo">
</p>

---

# COunterfactual explanations with Limited Actions (COLA)

[![arXiv](https://img.shields.io/badge/arXiv-2410.05419-B31B1B.svg)](https://arxiv.org/pdf/2410.05419)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![PyPI](https://img.shields.io/pypi/v/xai-cola.svg)](https://pypi.org/project/xai-cola/)
[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)

---

Explainable AI (XAI) aims to make models transparent and trustworthy ([Arrieta et al., 2020](https://www.sciencedirect.com/science/article/pii/S1566253519308103?casa_token=tMxtv_87MG0AAAAA:_f_pbOfKiVGSTKWC9mN6dxKyXYuO6FiE4-OWoUubefLcRe6JDOILQlo0aqPtyuEU5j9hoPzv)).
Within XAI, counterfactual explanations (CE) show minimal feature changes that flip model outcomes ([Wachter et al., 2017](https://heinonline.org/HOL/LandingPage?handle=hein.journals/hjlt31&div=29&id=&page=)).
Given diverse goals and settings, no single CE method fits all ([Guidotti, 2022](https://link.springer.com/article/10.1007/s10618-022-00831-6)).
Objectives vary across: (i) instance-level CEs—single or multiple per case ([Mothilal et al., 2020](https://arxiv.org/pdf/1905.07697)); (ii) global/dataset-level CEs that indicate movement directions ([Rawal & Lakkaraju, 2020](https://proceedings.neurips.cc/paper/2020/hash/8ee7730e97c67473a424ccfeff49ab20-Abstract.html); [Ley et al., 2022](https://arxiv.org/abs/2204.06917); [Carrizosa et al., 2024](https://www.sciencedirect.com/science/article/pii/S037722172400002X)); and (iii) distributional CEs that shift groups while preserving shape and cost ([You et al., 2024](https://arxiv.org/pdf/2401.13112)).
Methods also differ in model assumptions (differentiable vs. tree/ensemble).

## What is COLA?

<p align="center">
  <img src="https://raw.githubusercontent.com/understanding-ml/COLA/COLA-4-MVP/docs/images/problem.png" width="800" alt="Problem description">
</p>

COLA adapts to various CE methods and ML models. Extensive simulations show that the framework produces action plans that require significantly fewer feature changes to achieve outcomes similar (or sometimes equal) to those generated by various CE algorithms. Especially, COLA is shown to have near-optimal performance under certain circumstances.


## START COLA!

### Installation

**Option 1: Install from PyPI**

```bash
pip install xai-cola
```

**Option 2: Install from source**

```bash
git clone https://github.com/understanding-ml/COLA.git
cd COLA
pip install -e .
pip install -r requirements.txt
```

### Usage Guide

COLA is a python package that helps sparsify the results of generated counterfactual explanations. We also provide built-in
counterfactual algorithms like [DiCE](https://github.com/interpretml/DiCE?tab=readme-ov-file), [DisCount](https://arxiv.org/pdf/2401.13112),
and built-in German Credit dataset for testing. **Note:** COLA only addresses tabular data with numerical and categorical features.

Do the following steps to start sparsifying counterfactuals (You have already prepared your data, preprocessor and trained model):

1. Initialize the data interface
2. Initialize the model interface
3. Generate counterfactual explanations
4. Sparsify the counterfactual explanations
5. Visualize results

**Preparation: Prepare your data, preprocessor, and trained model**

```python
from xai_cola.datasets.german_credit import GermanCreditDataset
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression

# Load the Built-in German Credit dataset
dataset = GermanCreditDataset()
X_train, y_train, X_test, _ = dataset.get_original_train_test_split()


# Define feature lists
numerical_features = ['Age', 'Credit amount', 'Duration']
categorical_features = ['Sex', 'Job', 'Housing', 'Saving accounts', 'Checking account', 'Purpose']

# Create the preprocessor for original data
preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), numerical_features),  # Scale numerical features
        ('cat', OneHotEncoder(drop='first', handle_unknown='ignore'), categorical_features)
    ],
    remainder='passthrough'
)

# Logistic Regression classifier
lr_classifier = LogisticRegression(
    max_iter=1000,
    C=1.0,  # Inverse of regularization strength
    class_weight='balanced',  # Handle class imbalance
    random_state=42,
    solver='lbfgs'  # Suitable for small datasets
)

# merge preprocessor and classifier into a pipeline
pipe = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', lr_classifier)
])


for c in categorical_features:
    X_train[c] = X_train[c].astype(str)
    X_test[c] = X_test[c].astype(str)

# Train the model
pipe.fit(X_train, y_train)
```

**Step1: Initialize the data interface**

(1) COLA can accept two kinds of data: PandasData and NumpyData(provide column names).
(2) If you don't have your personal dataset, you can use the built-in test_dataset.

```python
from xai_cola.ce_sparsifier.data import COLAData

numerical_features = ['Age', 'Credit amount', 'Duration']
categorical_features = ['Sex', 'Job', 'Housing', 'Saving accounts', 'Checking account', 'Purpose']
data = COLAData(
    factual_data=df, # dataframe with label column
    label_column='Risk',
    numerical_features=numerical_features
)
```


**Step2: Initilize the model interface**

COLA can accept two kinds of model: sklearn model and pytorch model. The model can be provided in two forms:

1. **As a pipeline** (preprocessor + classifier combined)
2. **As separate components** (preprocessor and classifier separately)

```python
from xai_cola.ce_sparsifier.models import Model

ml_model = Model(model=pipe, backend="sklearn")
```

> **Note: Alternative: If your preprocessor and classifier are NOT combined in a pipeline**
>
> If you trained your classifier separately (i.e., `lr_classifier` was trained on data that has already been processed by `preprocessor`), you can initialize the model interface as follows:
>
> ```python
> ml_model = PreprocessorWrapper(
>     model=lr_classifier,  # The classifier alone
>     backend="sklearn",
>     preprocessor=preprocessor  # Pass the preprocessor separately
> )
> ```

**Step3: Generate counterfactual explanations**

(1) You can choose DiCE(**instance-wise** counterfactual generator), DisCount(**distributional** counterfactual generator) as the counterfactual explainer.
(2) Or You can use your own explainer.

```python
from xai_cola.ce_generator import DiCE

explainer = DiCE(ml_model=ml_model)
factual, counterfactual = explainer.generate_counterfactuals(
    data=data,
    factual_class=1, # class of target column of the factual instances
    total_cfs=2, # number of counterfactuals to generate per factual instance
    features_to_keep=['Age','Sex'],
    continuous_features=numerical_features
)

# Add generated counterfactuals to the COLAData class
data.add_counterfactuals(counterfactual, with_target_column=True)
data.summary()
```

**Step4: Initialize COLA and sparsify counterfactuals**

```python
from xai_cola.ce_sparsifier import COLA

# Initialize COLA - it will automatically extract factual and counterfactual from data
sparsifier = COLA(
    data=data,
    ml_model=ml_model
)

# Set the sparsification policy
sparsifier.set_policy(
    matcher="ot", # optimal transport matcher
    attributor="pshap", # SHAP attributor
    random_state=1 # Set random seed for reproducibility
)

# Query minimum actions
limited_actions = sparsifier.query_minimum_actions()

# Sparsify counterfactuals
sparsified_counterfactuals_df = sparsifier.sparsify_counterfactuals(limited_actions=limited_actions)
display(sparsified_counterfactuals_df)
```


**Step5: Visualization**

We provide several visualization methods to help users better understand the sparsified results.
For complete visualization options, see the full documentation.

```python
factual_df, ce_style, ace_style = sparsifier.highlight_changes_final()
display(ce_style, ace_style)  # display the highlighted dataframes
# ce_style.to_html('final.html') # save to html file
```

<p align="center">
  <img src="https://raw.githubusercontent.com/understanding-ml/COLA/COLA-4-MVP/docs/images/highlight_changed_positions.png" width="600" alt="highlight_changes">
</p>

```python
# Heatmap of Change Direction(increase or decrease)
sparsifier.heatmap_direction(save_path='./results', save_mode='combined',show_axis_labels=False)
```

<p align="center">
  <img src="https://raw.githubusercontent.com/understanding-ml/COLA/COLA-4-MVP/docs/images/combined_direction_heatmap.png" width="600" alt="heatmap_direction">
</p>

```python
# Actions required to flip the target per instance: sparsified counterfactuals vs. original counterfactuals
fig = sparsifier.stacked_bar_chart(save_path='./results')
```

<p align="center">
  <img src="https://raw.githubusercontent.com/understanding-ml/COLA/COLA-4-MVP/docs/images/stacked_bar_chart.png" width="600" alt="stacked_bar_chart">
</p>



## Citing

The python library xai-cola is described in the following paper:
Lin Zhu, Lei You (2025). xai-cola: A python library for sparsifying counterfactual explanations.

What's more, the theoretical foundation of COLA is described in the following paper:
Lei You, Yijun Bian, and Lele Cao (2024). [Refining Counterfactual Explanations With Joint-Distribution-Informed Shapley Towards Actionable Minimality](https://arxiv.org/pdf/2410.05419).

## Contributing

This project welcomes contributions and suggestions. If you have some questions about it, please feel free to reach out.

* Lin Zhu (s232291@student.dtu.dk)
* Lei You (leiyo@dtu.dk)