acfx 0.3.7.dev1__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.
Files changed (32) hide show
  1. {acfx-0.3.7.dev1 → acfx-0.3.8}/LICENSE +1 -1
  2. acfx-0.3.8/PKG-INFO +221 -0
  3. acfx-0.3.8/README.md +153 -0
  4. acfx-0.3.8/pyproject.toml +132 -0
  5. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/ACFX.py +2 -0
  6. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/AcfxCustom.py +2 -0
  7. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/abstract/ModelBasedCounterOptimizer.py +1 -1
  8. acfx-0.3.8/src/acfx/evaluation/LogisticRegressionCounterOptimizer.py +72 -0
  9. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/__init__.py +2 -2
  10. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/bayesian_model.py +32 -9
  11. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/ccfs.py +8 -3
  12. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/loss.py +12 -3
  13. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/multi_dataset_evaluation.py +3 -2
  14. acfx-0.3.8/src/acfx.egg-info/PKG-INFO +221 -0
  15. acfx-0.3.8/src/acfx.egg-info/SOURCES.txt +25 -0
  16. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx.egg-info/requires.txt +35 -9
  17. acfx-0.3.7.dev1/PKG-INFO +0 -48
  18. acfx-0.3.7.dev1/acfx/evaluation/LogisticRegressionCounterOptimizer.py +0 -53
  19. acfx-0.3.7.dev1/acfx.egg-info/PKG-INFO +0 -48
  20. acfx-0.3.7.dev1/acfx.egg-info/SOURCES.txt +0 -24
  21. acfx-0.3.7.dev1/pyproject.toml +0 -67
  22. {acfx-0.3.7.dev1 → acfx-0.3.8}/setup.cfg +0 -0
  23. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/AcfxEBM.py +0 -0
  24. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/AcfxLinear.py +0 -0
  25. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/RandomSearchCounterOptimizer.py +0 -0
  26. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/__init__.py +0 -0
  27. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/abstract/OptimizerType.py +0 -0
  28. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/abstract/__init__.py +0 -0
  29. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/EBMCounterOptimizer.py +0 -0
  30. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx/evaluation/utils.py +0 -0
  31. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx.egg-info/dependency_links.txt +0 -0
  32. {acfx-0.3.7.dev1 → acfx-0.3.8/src}/acfx.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Szymon Bobek
3
+ Copyright (c) 2026 Szymon Bobek
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
+ [![PyPI version](https://img.shields.io/pypi/v/acfx.svg)](https://pypi.org/project/acfx/)
76
+ [![Python versions](https://img.shields.io/pypi/pyversions/acfx.svg)](https://pypi.org/project/acfx/)
77
+ [![License](https://img.shields.io/github/license/sbobek/acfx.svg)](https://github.com/sbobek/acfx/blob/main/LICENSE)
78
+ [![Documentation Status](https://readthedocs.org/projects/acfx/badge/?version=latest)](https://acfx.readthedocs.io/en/latest/)
79
+ [![GitHub Stars](https://img.shields.io/github/stars/sbobek/acfx.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/acfx.svg)](https://pypi.org/project/acfx/)
8
+ [![Python versions](https://img.shields.io/pypi/pyversions/acfx.svg)](https://pypi.org/project/acfx/)
9
+ [![License](https://img.shields.io/github/license/sbobek/acfx.svg)](https://github.com/sbobek/acfx/blob/main/LICENSE)
10
+ [![Documentation Status](https://readthedocs.org/projects/acfx/badge/?version=latest)](https://acfx.readthedocs.io/en/latest/)
11
+ [![GitHub Stars](https://img.shields.io/github/stars/sbobek/acfx.svg)](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
 
@@ -19,4 +19,4 @@ class ModelBasedCounterOptimizer(ABC):
19
19
  -------
20
20
  Dictionary of feature names and their optimized values
21
21
  """
22
- pass
22
+ pass # pragma: no cover - abstract
@@ -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]