acfx 0.3.7.dev2__tar.gz → 0.3.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acfx-0.3.7.dev2 → acfx-0.3.8}/LICENSE +1 -1
- acfx-0.3.8/PKG-INFO +221 -0
- acfx-0.3.8/README.md +153 -0
- acfx-0.3.8/pyproject.toml +132 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/ACFX.py +2 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/AcfxCustom.py +2 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/abstract/ModelBasedCounterOptimizer.py +1 -1
- acfx-0.3.8/src/acfx/evaluation/LogisticRegressionCounterOptimizer.py +72 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/__init__.py +2 -2
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/bayesian_model.py +32 -9
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/ccfs.py +8 -3
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/loss.py +13 -21
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/multi_dataset_evaluation.py +3 -2
- acfx-0.3.8/src/acfx.egg-info/PKG-INFO +221 -0
- acfx-0.3.8/src/acfx.egg-info/SOURCES.txt +25 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx.egg-info/requires.txt +35 -9
- acfx-0.3.7.dev2/PKG-INFO +0 -48
- acfx-0.3.7.dev2/acfx/evaluation/LogisticRegressionCounterOptimizer.py +0 -53
- acfx-0.3.7.dev2/acfx.egg-info/PKG-INFO +0 -48
- acfx-0.3.7.dev2/acfx.egg-info/SOURCES.txt +0 -24
- acfx-0.3.7.dev2/pyproject.toml +0 -67
- {acfx-0.3.7.dev2 → acfx-0.3.8}/setup.cfg +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/AcfxEBM.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/AcfxLinear.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/RandomSearchCounterOptimizer.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/__init__.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/abstract/OptimizerType.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/abstract/__init__.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/EBMCounterOptimizer.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx/evaluation/utils.py +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx.egg-info/dependency_links.txt +0 -0
- {acfx-0.3.7.dev2 → acfx-0.3.8/src}/acfx.egg-info/top_level.txt +0 -0
acfx-0.3.8/PKG-INFO
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: acfx
|
|
3
|
+
Version: 0.3.8
|
|
4
|
+
Summary: Actionable Counterfactual eXplanations
|
|
5
|
+
Author-email: Szymon Bobek <szymon.bobek@gmail.com>, Piotr Kubacki <piotr.kubacki00@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sbobek/acfx
|
|
8
|
+
Project-URL: Documentation, https://acfx.readthedocs.org
|
|
9
|
+
Project-URL: Issues, https://github.com/sbobek/acfx/issues
|
|
10
|
+
Keywords: xai,tabular data,explainability,model-agnostic,counterfactual,causal
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: <3.15,>=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy==1.26.0; python_version < "3.12"
|
|
21
|
+
Requires-Dist: numpy<3,>=1.26.0; python_version >= "3.12"
|
|
22
|
+
Requires-Dist: pandas==1.5.3; python_version < "3.12"
|
|
23
|
+
Requires-Dist: pandas<4,>=1.5.3; python_version >= "3.12"
|
|
24
|
+
Requires-Dist: scipy==1.11.4; python_version < "3.12"
|
|
25
|
+
Requires-Dist: scipy<2,>=1.11.4; python_version >= "3.12"
|
|
26
|
+
Requires-Dist: scikit-learn==1.6.1; python_version < "3.12"
|
|
27
|
+
Requires-Dist: scikit-learn<2,>=1.6.1; python_version >= "3.12"
|
|
28
|
+
Requires-Dist: optuna==4.2.0; python_version < "3.12"
|
|
29
|
+
Requires-Dist: optuna<6,>=4.2.0; python_version >= "3.12"
|
|
30
|
+
Requires-Dist: pgmpy==1.0.0
|
|
31
|
+
Requires-Dist: interpret==0.7.3; python_version < "3.12"
|
|
32
|
+
Requires-Dist: interpret<0.8,>=0.7.3; python_version >= "3.12"
|
|
33
|
+
Requires-Dist: interpret-core==0.7.3; python_version < "3.12"
|
|
34
|
+
Requires-Dist: interpret-core<0.8,>=0.7.3; python_version >= "3.12"
|
|
35
|
+
Requires-Dist: overrides==7.4.0; python_version < "3.12"
|
|
36
|
+
Requires-Dist: overrides<8,>=7.4.0; python_version >= "3.12"
|
|
37
|
+
Provides-Extra: benchmark
|
|
38
|
+
Requires-Dist: tensorflow==2.14.0; python_version <= "3.11" and extra == "benchmark"
|
|
39
|
+
Requires-Dist: lingam==1.9.1; python_version <= "3.11" and extra == "benchmark"
|
|
40
|
+
Requires-Dist: openml==0.15.1; python_version <= "3.11" and extra == "benchmark"
|
|
41
|
+
Requires-Dist: alibi==0.9.6; python_version <= "3.11" and extra == "benchmark"
|
|
42
|
+
Requires-Dist: lux-explainer==1.3.2; python_version <= "3.11" and extra == "benchmark"
|
|
43
|
+
Requires-Dist: networkx==3.4.2; python_version <= "3.11" and extra == "benchmark"
|
|
44
|
+
Requires-Dist: cfnow==0.0.6; python_version <= "3.11" and extra == "benchmark"
|
|
45
|
+
Requires-Dist: dice-ml==0.11; python_version <= "3.11" and extra == "benchmark"
|
|
46
|
+
Requires-Dist: pydotplus==2.0.2; python_version <= "3.11" and extra == "benchmark"
|
|
47
|
+
Requires-Dist: deap==1.4.2; python_version <= "3.11" and extra == "benchmark"
|
|
48
|
+
Requires-Dist: pydot==3.0.4; python_version <= "3.11" and extra == "benchmark"
|
|
49
|
+
Provides-Extra: streamlit-app
|
|
50
|
+
Requires-Dist: streamlit==1.48.0; python_version < "3.12" and extra == "streamlit-app"
|
|
51
|
+
Requires-Dist: streamlit<2,>=1.48.0; python_version >= "3.12" and extra == "streamlit-app"
|
|
52
|
+
Requires-Dist: streamlit-sortables==0.3.1; extra == "streamlit-app"
|
|
53
|
+
Requires-Dist: pyarrow<25; python_version < "3.12" and extra == "streamlit-app"
|
|
54
|
+
Requires-Dist: networkx==3.4.2; python_version < "3.12" and extra == "streamlit-app"
|
|
55
|
+
Requires-Dist: networkx<4,>=3.4.2; python_version >= "3.12" and extra == "streamlit-app"
|
|
56
|
+
Requires-Dist: lingam==1.9.1; python_version < "3.12" and extra == "streamlit-app"
|
|
57
|
+
Requires-Dist: lingam<1.13,>=1.9.1; python_version >= "3.12" and extra == "streamlit-app"
|
|
58
|
+
Requires-Dist: semopy>=2.3; python_version >= "3.12" and extra == "streamlit-app"
|
|
59
|
+
Requires-Dist: pyvis==0.3.2; extra == "streamlit-app"
|
|
60
|
+
Provides-Extra: test
|
|
61
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
62
|
+
Requires-Dist: pytest-cov>=5; extra == "test"
|
|
63
|
+
Requires-Dist: networkx==3.4.2; python_version < "3.12" and extra == "test"
|
|
64
|
+
Requires-Dist: networkx<4,>=3.4.2; python_version >= "3.12" and extra == "test"
|
|
65
|
+
Requires-Dist: lingam==1.9.1; python_version < "3.12" and extra == "test"
|
|
66
|
+
Requires-Dist: lingam<1.13,>=1.9.1; python_version >= "3.12" and extra == "test"
|
|
67
|
+
Dynamic: license-file
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ACFX — Actionable Counterfactual eXplainer
|
|
74
|
+
|
|
75
|
+
[](https://pypi.org/project/acfx/)
|
|
76
|
+
[](https://pypi.org/project/acfx/)
|
|
77
|
+
[](https://github.com/sbobek/acfx/blob/main/LICENSE)
|
|
78
|
+
[](https://acfx.readthedocs.io/en/latest/)
|
|
79
|
+
[](https://github.com/sbobek/acfx/stargazers)
|
|
80
|
+
|
|
81
|
+
**ACFX (Actionable Counterfactual eXplainer)** is a **model-agnostic Explainable AI (XAI)** framework for generating **actionable counterfactual explanations** for machine learning models.
|
|
82
|
+
|
|
83
|
+
It answers the question:
|
|
84
|
+
|
|
85
|
+
> *What minimal and feasible changes to the input would lead to a desired prediction outcome?*
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Key Features
|
|
90
|
+
|
|
91
|
+
* Model-agnostic counterfactual explanations
|
|
92
|
+
* Actionability and feasibility constraints
|
|
93
|
+
* Support for causal structures and expert knowledge
|
|
94
|
+
* Built-in benchmarking framework
|
|
95
|
+
* Python API and graphical user interface (GUI)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
### From PyPI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
conda create --name acfx_env python=3.11
|
|
105
|
+
conda activate acfx_env
|
|
106
|
+
conda install pip
|
|
107
|
+
pip install acfx
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### From source
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/sbobek/acfx
|
|
114
|
+
cd acfx/src
|
|
115
|
+
pip install .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Note**: The `requirements.txt` file includes additional packages that are not required for standard use. It is provided for development purposes and to support reproducibility of the complete development environment. Direct runtime dependencies are specified in `pyproject.toml`.
|
|
119
|
+
|
|
120
|
+
## Quick Start
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from acfx import AcfxEBM
|
|
124
|
+
from interpret.glassbox import ExplainableBoostingClassifier
|
|
125
|
+
from sklearn.datasets import load_iris
|
|
126
|
+
from sklearn.model_selection import train_test_split
|
|
127
|
+
import numpy as np
|
|
128
|
+
|
|
129
|
+
# Initialize model and explainer
|
|
130
|
+
model = ExplainableBoostingClassifier()
|
|
131
|
+
explainer = AcfxEBM(model)
|
|
132
|
+
|
|
133
|
+
# Load sample data
|
|
134
|
+
data = load_iris(as_frame=True)
|
|
135
|
+
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)
|
|
136
|
+
|
|
137
|
+
# Define feature bounds
|
|
138
|
+
pbounds = {col: (X_train[col].min(), X_train[col].max()) for col in X_train.columns}
|
|
139
|
+
|
|
140
|
+
# Example adjacency matrix and causal order
|
|
141
|
+
adjacency_matrix = np.array([
|
|
142
|
+
[0.0, 0.0, 0.0, 0.0],
|
|
143
|
+
[0.8, 0.0, 0.0, 0.0],
|
|
144
|
+
[0.0, 0.6, 0.0, 0.0],
|
|
145
|
+
[0.5, 0.0, 0.7, 0.0]
|
|
146
|
+
])
|
|
147
|
+
causal_order = [0, 1, 2, 3]
|
|
148
|
+
|
|
149
|
+
# Fit explainer
|
|
150
|
+
explainer.fit(
|
|
151
|
+
X=X_train,
|
|
152
|
+
adjacency_matrix=adjacency_matrix,
|
|
153
|
+
causal_order=causal_order,
|
|
154
|
+
pbounds=pbounds,
|
|
155
|
+
y=y_train,
|
|
156
|
+
features_order=X_train.columns.tolist()
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Generate counterfactual
|
|
160
|
+
query_instance = X_test.iloc[0].values
|
|
161
|
+
original_class = model.predict([query_instance])[0]
|
|
162
|
+
cf = explainer.counterfactual(desired_class=original_class, query_instance=query_instance)
|
|
163
|
+
print(cf)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
> ⚠️ The adjacency matrix above is a simple example. In practice, it can be provided by expert knowledge or learned using tools like DirectLiNGAM.
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## Usage Tutorials
|
|
171
|
+
|
|
172
|
+
Learn more and explore advanced examples:
|
|
173
|
+
|
|
174
|
+
* **Documentation**: [ACFX Docs](https://acfx.readthedocs.io/en/latest/)
|
|
175
|
+
* **Python API (Colab Notebooks)**: [Interactive Examples](https://colab.research.google.com/drive/1Hj6yH4UIrAp1Jp6B1U542vcdSkXuZHUd?usp=sharing)
|
|
176
|
+
* **Graphical User Interface (GUI)**: [ACFX GUI](https://acfx-dashboard.streamlit.app/)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Testing
|
|
181
|
+
|
|
182
|
+
The library and the Streamlit GUI are covered by an automated pytest suite with code coverage measurement:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/sbobek/acfx
|
|
186
|
+
cd acfx/src
|
|
187
|
+
pip install -e ".[test,streamlit-app]"
|
|
188
|
+
pytest --cov
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
See [TESTING.md](https://github.com/sbobek/acfx/blob/main/TESTING.md) for details (fast mode, HTML coverage report, what is excluded from coverage and why).
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
## Benchmarking
|
|
196
|
+
|
|
197
|
+
### Install dependencies
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install acfx[benchmark]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Run benchmark
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
git clone https://github.com/sbobek/acfx
|
|
207
|
+
cd acfx/src
|
|
208
|
+
python -m acfx.benchmark.main
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
> ⚠️ Always run as a module from `acfx/src` using `python -m`. Running directly or from another folder may cause import or path errors.
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
## Citation
|
|
216
|
+
|
|
217
|
+
TBD
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
acfx-0.3.8/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# ACFX — Actionable Counterfactual eXplainer
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/acfx/)
|
|
8
|
+
[](https://pypi.org/project/acfx/)
|
|
9
|
+
[](https://github.com/sbobek/acfx/blob/main/LICENSE)
|
|
10
|
+
[](https://acfx.readthedocs.io/en/latest/)
|
|
11
|
+
[](https://github.com/sbobek/acfx/stargazers)
|
|
12
|
+
|
|
13
|
+
**ACFX (Actionable Counterfactual eXplainer)** is a **model-agnostic Explainable AI (XAI)** framework for generating **actionable counterfactual explanations** for machine learning models.
|
|
14
|
+
|
|
15
|
+
It answers the question:
|
|
16
|
+
|
|
17
|
+
> *What minimal and feasible changes to the input would lead to a desired prediction outcome?*
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Key Features
|
|
22
|
+
|
|
23
|
+
* Model-agnostic counterfactual explanations
|
|
24
|
+
* Actionability and feasibility constraints
|
|
25
|
+
* Support for causal structures and expert knowledge
|
|
26
|
+
* Built-in benchmarking framework
|
|
27
|
+
* Python API and graphical user interface (GUI)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### From PyPI
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
conda create --name acfx_env python=3.11
|
|
37
|
+
conda activate acfx_env
|
|
38
|
+
conda install pip
|
|
39
|
+
pip install acfx
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### From source
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/sbobek/acfx
|
|
46
|
+
cd acfx/src
|
|
47
|
+
pip install .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Note**: The `requirements.txt` file includes additional packages that are not required for standard use. It is provided for development purposes and to support reproducibility of the complete development environment. Direct runtime dependencies are specified in `pyproject.toml`.
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from acfx import AcfxEBM
|
|
56
|
+
from interpret.glassbox import ExplainableBoostingClassifier
|
|
57
|
+
from sklearn.datasets import load_iris
|
|
58
|
+
from sklearn.model_selection import train_test_split
|
|
59
|
+
import numpy as np
|
|
60
|
+
|
|
61
|
+
# Initialize model and explainer
|
|
62
|
+
model = ExplainableBoostingClassifier()
|
|
63
|
+
explainer = AcfxEBM(model)
|
|
64
|
+
|
|
65
|
+
# Load sample data
|
|
66
|
+
data = load_iris(as_frame=True)
|
|
67
|
+
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)
|
|
68
|
+
|
|
69
|
+
# Define feature bounds
|
|
70
|
+
pbounds = {col: (X_train[col].min(), X_train[col].max()) for col in X_train.columns}
|
|
71
|
+
|
|
72
|
+
# Example adjacency matrix and causal order
|
|
73
|
+
adjacency_matrix = np.array([
|
|
74
|
+
[0.0, 0.0, 0.0, 0.0],
|
|
75
|
+
[0.8, 0.0, 0.0, 0.0],
|
|
76
|
+
[0.0, 0.6, 0.0, 0.0],
|
|
77
|
+
[0.5, 0.0, 0.7, 0.0]
|
|
78
|
+
])
|
|
79
|
+
causal_order = [0, 1, 2, 3]
|
|
80
|
+
|
|
81
|
+
# Fit explainer
|
|
82
|
+
explainer.fit(
|
|
83
|
+
X=X_train,
|
|
84
|
+
adjacency_matrix=adjacency_matrix,
|
|
85
|
+
causal_order=causal_order,
|
|
86
|
+
pbounds=pbounds,
|
|
87
|
+
y=y_train,
|
|
88
|
+
features_order=X_train.columns.tolist()
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Generate counterfactual
|
|
92
|
+
query_instance = X_test.iloc[0].values
|
|
93
|
+
original_class = model.predict([query_instance])[0]
|
|
94
|
+
cf = explainer.counterfactual(desired_class=original_class, query_instance=query_instance)
|
|
95
|
+
print(cf)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> ⚠️ The adjacency matrix above is a simple example. In practice, it can be provided by expert knowledge or learned using tools like DirectLiNGAM.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## Usage Tutorials
|
|
103
|
+
|
|
104
|
+
Learn more and explore advanced examples:
|
|
105
|
+
|
|
106
|
+
* **Documentation**: [ACFX Docs](https://acfx.readthedocs.io/en/latest/)
|
|
107
|
+
* **Python API (Colab Notebooks)**: [Interactive Examples](https://colab.research.google.com/drive/1Hj6yH4UIrAp1Jp6B1U542vcdSkXuZHUd?usp=sharing)
|
|
108
|
+
* **Graphical User Interface (GUI)**: [ACFX GUI](https://acfx-dashboard.streamlit.app/)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## Testing
|
|
113
|
+
|
|
114
|
+
The library and the Streamlit GUI are covered by an automated pytest suite with code coverage measurement:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/sbobek/acfx
|
|
118
|
+
cd acfx/src
|
|
119
|
+
pip install -e ".[test,streamlit-app]"
|
|
120
|
+
pytest --cov
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
See [TESTING.md](https://github.com/sbobek/acfx/blob/main/TESTING.md) for details (fast mode, HTML coverage report, what is excluded from coverage and why).
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## Benchmarking
|
|
128
|
+
|
|
129
|
+
### Install dependencies
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install acfx[benchmark]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Run benchmark
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/sbobek/acfx
|
|
139
|
+
cd acfx/src
|
|
140
|
+
python -m acfx.benchmark.main
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
> ⚠️ Always run as a module from `acfx/src` using `python -m`. Running directly or from another folder may cause import or path errors.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Citation
|
|
148
|
+
|
|
149
|
+
TBD
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "acfx"
|
|
7
|
+
version = "0.3.8"
|
|
8
|
+
description = "Actionable Counterfactual eXplanations"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Szymon Bobek", email = "szymon.bobek@gmail.com" },
|
|
12
|
+
{ name = "Piotr Kubacki", email = "piotr.kubacki00@gmail.com"}
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
keywords = ["xai", "tabular data","explainability", "model-agnostic", "counterfactual", "causal"]
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
"numpy==1.26.0; python_version < '3.12'",
|
|
19
|
+
"numpy>=1.26.0,<3; python_version >= '3.12'",
|
|
20
|
+
"pandas==1.5.3; python_version < '3.12'",
|
|
21
|
+
"pandas>=1.5.3,<4; python_version >= '3.12'",
|
|
22
|
+
"scipy==1.11.4; python_version < '3.12'",
|
|
23
|
+
"scipy>=1.11.4,<2; python_version >= '3.12'",
|
|
24
|
+
"scikit-learn==1.6.1; python_version < '3.12'",
|
|
25
|
+
"scikit-learn>=1.6.1,<2; python_version >= '3.12'",
|
|
26
|
+
|
|
27
|
+
"optuna==4.2.0; python_version < '3.12'",
|
|
28
|
+
"optuna>=4.2.0,<6; python_version >= '3.12'",
|
|
29
|
+
"pgmpy==1.0.0",
|
|
30
|
+
"interpret==0.7.3; python_version < '3.12'",
|
|
31
|
+
"interpret>=0.7.3,<0.8; python_version >= '3.12'",
|
|
32
|
+
"interpret-core==0.7.3; python_version < '3.12'",
|
|
33
|
+
"interpret-core>=0.7.3,<0.8; python_version >= '3.12'",
|
|
34
|
+
"overrides==7.4.0; python_version < '3.12'",
|
|
35
|
+
"overrides>=7.4.0,<8; python_version >= '3.12'"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
requires-python = ">=3.10,<3.15"
|
|
39
|
+
|
|
40
|
+
classifiers = [
|
|
41
|
+
"Programming Language :: Python :: 3",
|
|
42
|
+
"Programming Language :: Python :: 3.11",
|
|
43
|
+
"Programming Language :: Python :: 3.12",
|
|
44
|
+
"Programming Language :: Python :: 3.13",
|
|
45
|
+
"Programming Language :: Python :: 3.14",
|
|
46
|
+
"Operating System :: OS Independent"
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
benchmark = [
|
|
52
|
+
"tensorflow==2.14.0; python_version <= '3.11'",
|
|
53
|
+
"lingam==1.9.1; python_version <= '3.11'",
|
|
54
|
+
"openml==0.15.1; python_version <= '3.11'",
|
|
55
|
+
"alibi==0.9.6; python_version <= '3.11'",
|
|
56
|
+
"lux-explainer==1.3.2; python_version <= '3.11'",
|
|
57
|
+
"networkx==3.4.2; python_version <= '3.11'",
|
|
58
|
+
"cfnow==0.0.6; python_version <= '3.11'",
|
|
59
|
+
"dice-ml==0.11; python_version <= '3.11'",
|
|
60
|
+
"pydotplus==2.0.2; python_version <= '3.11'",
|
|
61
|
+
"deap==1.4.2; python_version <= '3.11'",
|
|
62
|
+
"pydot==3.0.4; python_version <= '3.11'"
|
|
63
|
+
]
|
|
64
|
+
streamlit-app = [
|
|
65
|
+
"streamlit==1.48.0; python_version < '3.12'",
|
|
66
|
+
"streamlit>=1.48.0,<2; python_version >= '3.12'",
|
|
67
|
+
"streamlit-sortables==0.3.1",
|
|
68
|
+
# numpy is pinned to 1.26 for Python<3.12: pyarrow 25 segfaults with it and pyarrow>=26 requires numpy>=2
|
|
69
|
+
"pyarrow<25; python_version < '3.12'",
|
|
70
|
+
"networkx==3.4.2; python_version < '3.12'",
|
|
71
|
+
"networkx>=3.4.2,<4; python_version >= '3.12'",
|
|
72
|
+
"lingam==1.9.1; python_version < '3.12'",
|
|
73
|
+
# lingam>=1.13 pins scipy<=1.13.1, which has no wheels for Python>=3.13
|
|
74
|
+
"lingam>=1.9.1,<1.13; python_version >= '3.12'",
|
|
75
|
+
# semopy<2 needs portmin, which is sdist-only and fails to build on Python>=3.14
|
|
76
|
+
"semopy>=2.3; python_version >= '3.12'",
|
|
77
|
+
"pyvis==0.3.2"
|
|
78
|
+
]
|
|
79
|
+
test = [
|
|
80
|
+
"pytest>=8",
|
|
81
|
+
"pytest-cov>=5",
|
|
82
|
+
"networkx==3.4.2; python_version < '3.12'",
|
|
83
|
+
"networkx>=3.4.2,<4; python_version >= '3.12'",
|
|
84
|
+
"lingam==1.9.1; python_version < '3.12'",
|
|
85
|
+
"lingam>=1.9.1,<1.13; python_version >= '3.12'"
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.setuptools.packages.find]
|
|
89
|
+
where = ["src"]
|
|
90
|
+
include = ["acfx*"]
|
|
91
|
+
exclude = ["acfx.benchmark*", "acfx.tests*"]
|
|
92
|
+
|
|
93
|
+
[tool.pytest.ini_options]
|
|
94
|
+
# Run from the src/ directory: `pytest`
|
|
95
|
+
testpaths = ["src/acfx/tests", "src/app/tests"]
|
|
96
|
+
pythonpath = ["src", "src/app"]
|
|
97
|
+
addopts = "--import-mode=importlib"
|
|
98
|
+
markers = [
|
|
99
|
+
"slow: end-to-end tests running the full counterfactual optimisation (deselect with -m 'not slow')"
|
|
100
|
+
]
|
|
101
|
+
filterwarnings = [
|
|
102
|
+
"ignore::DeprecationWarning",
|
|
103
|
+
"ignore::FutureWarning"
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.coverage.run]
|
|
107
|
+
source = ["acfx", "app"]
|
|
108
|
+
branch = true
|
|
109
|
+
omit = [
|
|
110
|
+
"acfx/benchmark/*",
|
|
111
|
+
"*/tests/*",
|
|
112
|
+
"acfx/evaluation/utils.py",
|
|
113
|
+
"acfx/evaluation/multi_dataset_evaluation.py",
|
|
114
|
+
"app/Welcome to the ACFX.py"
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[tool.coverage.report]
|
|
118
|
+
show_missing = true
|
|
119
|
+
# regression gate: the full suite (including slow tests)
|
|
120
|
+
fail_under = 90
|
|
121
|
+
exclude_also = [
|
|
122
|
+
"if __name__ == .__main__.:",
|
|
123
|
+
"raise NotImplementedError"
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
[tool.coverage.html]
|
|
127
|
+
directory = "htmlcov"
|
|
128
|
+
|
|
129
|
+
[project.urls]
|
|
130
|
+
Homepage = "https://github.com/sbobek/acfx"
|
|
131
|
+
Documentation = "https://acfx.readthedocs.org"
|
|
132
|
+
Issues = "https://github.com/sbobek/acfx/issues"
|
|
@@ -84,6 +84,8 @@ class ACFX(ABC, BaseEstimator, TransformerMixin):
|
|
|
84
84
|
|
|
85
85
|
bayesian_model:
|
|
86
86
|
optionally, provide pre-fitted discrete bayesian model, if bayesian_causality is True.
|
|
87
|
+
It must be trained on the same X with the same num_bins, because counterfactual candidates are discretized
|
|
88
|
+
with the bins of X (this is not validated).
|
|
87
89
|
If it is not provided and bayesian_causality is True, then it will be initialized internally and
|
|
88
90
|
fitted with the num_bins param
|
|
89
91
|
|
|
@@ -82,6 +82,8 @@ class AcfxCustom(ACFX):
|
|
|
82
82
|
|
|
83
83
|
bayesian_model:
|
|
84
84
|
optionally, provide pre-fitted discrete bayesian model, if bayesian_causality is True.
|
|
85
|
+
It must be trained on the same X with the same num_bins, because counterfactual candidates are discretized
|
|
86
|
+
with the bins of X (this is not validated).
|
|
85
87
|
If it is not provided and bayesian_causality is True, then it will be initialized internally and
|
|
86
88
|
fitted with the num_bins param
|
|
87
89
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import numbers
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from overrides import overrides
|
|
7
|
+
from sklearn.linear_model._base import LinearClassifierMixin
|
|
8
|
+
|
|
9
|
+
from ..abstract import ModelBasedCounterOptimizer
|
|
10
|
+
|
|
11
|
+
class LogisticRegressionCounterOptimizer(ModelBasedCounterOptimizer):
|
|
12
|
+
def __init__(self, model:LinearClassifierMixin, X: pd.DataFrame, feature_bounds:dict):
|
|
13
|
+
if not hasattr(model, 'coef_'):
|
|
14
|
+
raise AttributeError('model.coef_ must be set')
|
|
15
|
+
if feature_bounds is None:
|
|
16
|
+
raise ValueError("feature_bounds must be set")
|
|
17
|
+
if not isinstance(feature_bounds, dict) or not all(
|
|
18
|
+
isinstance(k, str) and
|
|
19
|
+
isinstance(v, tuple) and
|
|
20
|
+
len(v) == 2 and
|
|
21
|
+
all(isinstance(i, numbers.Number) for i in v)
|
|
22
|
+
for k, v in feature_bounds.items()
|
|
23
|
+
):
|
|
24
|
+
raise AttributeError("feature_bounds must be a dict with string keys and tuple of two floats as values")
|
|
25
|
+
|
|
26
|
+
self.__feature_bounds = feature_bounds
|
|
27
|
+
self.model = model
|
|
28
|
+
self.X = X
|
|
29
|
+
|
|
30
|
+
@overrides
|
|
31
|
+
def optimize_proba(self, target_class: int, feature_masked: List[str]) -> Dict[str, float]:
|
|
32
|
+
# v2 - lepiej bez iterrows
|
|
33
|
+
df_optimized = self.X.copy()
|
|
34
|
+
coefficients = self.model.coef_[target_class]
|
|
35
|
+
direction = np.sign(coefficients)
|
|
36
|
+
model_features = list(self.X.columns)
|
|
37
|
+
|
|
38
|
+
for feature_name in feature_masked:
|
|
39
|
+
if feature_name in self.__feature_bounds and feature_name in model_features:
|
|
40
|
+
feature_idx = model_features.index(feature_name)
|
|
41
|
+
min_val, max_val = self.__feature_bounds[feature_name]
|
|
42
|
+
|
|
43
|
+
if direction[feature_idx] > 0:
|
|
44
|
+
df_optimized[feature_name] = max_val
|
|
45
|
+
else:
|
|
46
|
+
df_optimized[feature_name] = min_val
|
|
47
|
+
|
|
48
|
+
avg_optimized = df_optimized.mean().to_dict()
|
|
49
|
+
return avg_optimized
|
|
50
|
+
|
|
51
|
+
# optimized_instances = []
|
|
52
|
+
#
|
|
53
|
+
# for index, instance in self.X.iterrows():
|
|
54
|
+
# coefficients = self.model.coef_[target_class]
|
|
55
|
+
# direction = np.sign(coefficients)
|
|
56
|
+
# optimized_instance = instance.copy()
|
|
57
|
+
#
|
|
58
|
+
# for i, feature_name in enumerate(self.X.columns):
|
|
59
|
+
# if feature_name not in feature_masked:
|
|
60
|
+
# continue
|
|
61
|
+
# if feature_name in self.__feature_bounds:
|
|
62
|
+
# min_val, max_val = self.__feature_bounds[feature_name]
|
|
63
|
+
# if direction[i] > 0:
|
|
64
|
+
# optimized_instance[feature_name] = max_val
|
|
65
|
+
# else:
|
|
66
|
+
# optimized_instance[feature_name] = min_val
|
|
67
|
+
#
|
|
68
|
+
# optimized_instances.append(optimized_instance)
|
|
69
|
+
#
|
|
70
|
+
# df_optimized = pd.DataFrame(optimized_instances)
|
|
71
|
+
# avg_optimized = df_optimized.mean().to_dict()
|
|
72
|
+
# return avg_optimized
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .ccfs import generate_cfs, generate_cfs_bayesian
|
|
2
2
|
from .EBMCounterOptimizer import EBMCounterOptimizer
|
|
3
3
|
from .LogisticRegressionCounterOptimizer import LogisticRegressionCounterOptimizer
|
|
4
|
-
from .bayesian_model import train_bayesian_model, discretize_dataframe, discretize_ndarray
|
|
4
|
+
from .bayesian_model import train_bayesian_model, discretize_dataframe, discretize_ndarray, compute_bin_edges
|
|
5
5
|
__all__ = [LogisticRegressionCounterOptimizer, EBMCounterOptimizer, generate_cfs, generate_cfs_bayesian,
|
|
6
|
-
train_bayesian_model, discretize_dataframe, discretize_ndarray]
|
|
6
|
+
train_bayesian_model, discretize_dataframe, discretize_ndarray, compute_bin_edges]
|