fde-tabular 1.0.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dwi Sunaryono
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: fde-tabular
3
+ Version: 1.0.0
4
+ Summary: FDE: Feasible Diverse Ensembles for tabular classification
5
+ Author: Dwi Sunaryono, Misbakhul Munir Irfan Subakti, Laras Suciningtyas, Irzal Ahmad Sabilla, Muchamad Kurniawan
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/DwiSunaryono/fde-tabular
8
+ Project-URL: Repository, https://github.com/DwiSunaryono/fde-tabular
9
+ Project-URL: Issues, https://github.com/DwiSunaryono/fde-tabular/issues
10
+ Keywords: ensemble learning,class imbalance,tabular data,classification,stacking
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.26
19
+ Requires-Dist: pandas>=2.1
20
+ Requires-Dist: scipy>=1.11
21
+ Requires-Dist: scikit-learn>=1.5
22
+ Requires-Dist: imbalanced-learn>=0.12
23
+ Requires-Dist: xgboost>=2.0
24
+ Requires-Dist: lightgbm>=4.0
25
+ Requires-Dist: catboost>=1.2
26
+ Requires-Dist: optuna>=3.6
27
+ Requires-Dist: joblib>=1.3
28
+ Requires-Dist: torch>=2.1
29
+ Requires-Dist: psutil>=5.9
30
+ Requires-Dist: rich>=13
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest>=7; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # FDE: Feasible Diverse Ensembles for tabular classification
36
+
37
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23997869.svg)](https://doi.org/10.5281/zenodo.23997869)
38
+
39
+ FDE wraps the best tuned single learner in a diverse ensemble and hands prediction to the ensemble only when the
40
+ training data support it over that learner. When they do not, the best tuned learner is used on its own.
41
+
42
+ - **Best tuned learner.** Optuna tunes twelve learner types on inner cross-validation (CatBoost, LightGBM, XGBoost,
43
+ HistGradientBoosting, random forest, extra trees, SVC, k-nearest neighbours, logistic regression, naive Bayes, MLP
44
+ and decision tree) and the highest-scoring type is kept. Above 3,000 rows, SVC is left out.
45
+ - **Members that handle class imbalance.** Three groups join the ensemble: the five next-best learner types refitted
46
+ with class weights; BalancedRandomForest, EasyEnsemble and RUSBoost; and the same five learner types refitted on
47
+ rebalanced samples, with their probabilities corrected to equal class priors.
48
+ - **Extended members.** Eight members work with a larger budget: CatBoost, LightGBM and XGBoost (ordinal or native
49
+ categorical coding, up to 5,000 trees with early stopping), an MLP with three hidden layers of 256 units, and
50
+ random forest and extra trees with 500 trees. Each is bagged over 3 folds of its training rows.
51
+ - **Combiner.** A stack (multinomial logistic regression) over the probabilities of the best learner and the
52
+ members, with a per-class rescaling of the resulting probabilities on top.
53
+ - **Cross-fitted acceptance rule.** Three outer cross-fitted folds of the training rows decide. The ensemble is kept
54
+ only if the lower 5% bootstrap quantile of its gain over the best tuned learner lies above 0, if its weight in the
55
+ all-rows fit is non-zero, and if it changes at least one outer-fold prediction. Otherwise the best tuned learner is
56
+ kept. Every imbalance level follows the same rule; on balanced data the best tuned learner is usually the one kept.
57
+
58
+ A run is scored with macro-F1 when there are two classes and the minority share is below 0.15, and with weighted-F1
59
+ in every other case.
60
+
61
+ ## Installation
62
+
63
+ Python 3.10 or later. Installing into a fresh virtual environment keeps FDE apart from other projects. FDE runs on the
64
+ CPU by default, so the CPU build of PyTorch is enough and much smaller than the GPU build. Choose your system:
65
+
66
+ <details open>
67
+ <summary><b>Linux</b></summary>
68
+
69
+ ```bash
70
+ python3 -m venv ~/fde-env
71
+ source ~/fde-env/bin/activate
72
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
73
+ pip install fde-tabular
74
+ fde demo
75
+ ```
76
+
77
+ If `python3 -m venv` reports that `ensurepip` is missing (Debian, Ubuntu), install it first with
78
+ `sudo apt install python3-venv`.
79
+ </details>
80
+
81
+ <details>
82
+ <summary><b>macOS</b></summary>
83
+
84
+ ```bash
85
+ brew install libomp
86
+ python3 -m venv ~/fde-env
87
+ source ~/fde-env/bin/activate
88
+ pip install torch
89
+ pip install fde-tabular
90
+ fde demo
91
+ ```
92
+
93
+ LightGBM and XGBoost need the OpenMP library (`libomp`) from Homebrew. On macOS the default PyTorch build already runs
94
+ on the CPU.
95
+ </details>
96
+
97
+ <details>
98
+ <summary><b>Windows (Command Prompt)</b></summary>
99
+
100
+ Type or paste **one line at a time** and press Enter after each line (if `py` is not found, use `python` instead):
101
+
102
+ ```bat
103
+ py -m venv %USERPROFILE%\fde-env
104
+ %USERPROFILE%\fde-env\Scripts\activate
105
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
106
+ pip install fde-tabular
107
+ fde demo
108
+ ```
109
+
110
+ In PowerShell, activate with `$env:USERPROFILE\fde-env\Scripts\Activate.ps1` instead (if scripts are blocked, run
111
+ `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once). The Command Prompt does not treat `#` as a comment, so do
112
+ not paste comments after a command. Commands written for Linux, such as `source` or `~/`, do not work here.
113
+ </details>
114
+
115
+ <details>
116
+ <summary><b>With a CUDA GPU (any system, optional)</b></summary>
117
+
118
+ Install the PyTorch build for your CUDA version from <https://pytorch.org> instead of the CPU build, then
119
+ `pip install fde-tabular`, and pass `--device gpu` (or `device="gpu"`). See the GPU note under Options before using it.
120
+ </details>
121
+
122
+ Each time you open a new terminal, activate the environment again (the second line above) before using `fde`.
123
+
124
+ Everything runs on the CPU unless `--device gpu` is given: each tuned learner gets one thread, each extended member
125
+ gets four (`member_threads`). Before fitting, FDE checks the free CPU cores and memory and runs that many tasks in
126
+ parallel (`n_jobs`); the progress bar shows the three stages (tuning the learners, building the members, fitting the
127
+ combiners) with an estimate of the remaining time.
128
+
129
+ ## Quick start
130
+
131
+ ```bash
132
+ fde demo # breast cancer data: the best tuned learner alone is enough
133
+ fde demo --example imbalanced # synthetic imbalanced data: the ensemble is used
134
+ ```
135
+
136
+ The first demo evaluates FDE on the breast cancer data that ships with scikit-learn, the second on synthetic data with a
137
+ 10% minority class (no file or download needed for either). The summary explains why the ensemble was used or not. The demos use
138
+ quick settings (5 Optuna trials per learner type and the light member set), so it finishes in about a minute on a
139
+ laptop; `fde demo --trials 20 --members full` runs the settings evaluated in the paper. A progress bar shows the
140
+ stage, the elapsed time and an estimate of the remaining time, and the result compares FDE with the best tuned
141
+ learner on held-out rows.
142
+
143
+ ## Use
144
+
145
+ ```python
146
+ from fde import FDEClassifier
147
+
148
+ clf = FDEClassifier(random_state=0).fit(X_train, y_train) # X_train: pandas DataFrame, y_train: labels
149
+ print(clf.report_) # best learner, acceptance rule, whether the ensemble is used
150
+ pred = clf.predict(X_test)
151
+ proba = clf.predict_proba(X_test)
152
+ clf.save("model.fde"); clf = FDEClassifier.load("model.fde")
153
+ ```
154
+
155
+ From the command line:
156
+
157
+ ```bash
158
+ fde fit --data train.csv --target label --out model.fde
159
+ fde predict --model model.fde --data new.csv --proba --out scores.csv
160
+ fde evaluate --data data.csv --target label --test-size 0.2 # holdout scores for FDE and the best tuned learner
161
+ fde fit --data train.csv --target label --out light.fde --members light # fewer members: faster prediction
162
+ ```
163
+
164
+ The command line prints a short summary: the best tuned learner, whether the ensemble was needed, the model size, the
165
+ fit time and, for `demo` and `evaluate`, the held-out scores. Add `--json` for the full report as JSON, and `--quiet`
166
+ to turn off the progress messages (they go to the standard error stream).
167
+
168
+ Text columns can be left as they are: every learner imputes and encodes them using only its own training rows, and
169
+ treats missing values the same way.
170
+
171
+ ## Examples
172
+
173
+ Each example runs as it is (`pip install fde-tabular` first). Times are for a 32-core laptop; with fewer cores they
174
+ grow roughly in proportion. Predicted labels are returned as text (for example `"0"` and `"1"`).
175
+
176
+ **Two classes, quick settings (about 25 s).** Breast cancer data, shipped with scikit-learn:
177
+
178
+ ```python
179
+ from sklearn.datasets import load_breast_cancer
180
+ from sklearn.model_selection import train_test_split
181
+ from fde import FDEClassifier
182
+
183
+ X, y = load_breast_cancer(return_X_y=True, as_frame=True)
184
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
185
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
186
+ print("accuracy:", (clf.predict(X_test) == y_test.astype(str)).mean())
187
+ print("ensemble used:", clf.uses_ensemble_, "| best learner:", clf.report_["best_learner"])
188
+ ```
189
+
190
+ **Several classes, with probabilities (about 10 s).** Wine data, shipped with scikit-learn:
191
+
192
+ ```python
193
+ from sklearn.datasets import load_wine
194
+ from sklearn.metrics import f1_score
195
+ from sklearn.model_selection import train_test_split
196
+ from fde import FDEClassifier
197
+
198
+ X, y = load_wine(return_X_y=True, as_frame=True)
199
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
200
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
201
+ print("macro-F1:", round(f1_score(y_test.astype(str), clf.predict(X_test), average="macro"), 4))
202
+ print(clf.classes_)
203
+ print(clf.predict_proba(X_test)[:3].round(3)) # one column per class, in the order of clf.classes_
204
+ ```
205
+
206
+ **Imbalanced classes, a text column and missing values; save and reload (about 15 s).** Synthetic data:
207
+
208
+ ```python
209
+ import numpy as np, pandas as pd
210
+ from sklearn.datasets import make_classification
211
+ from sklearn.metrics import balanced_accuracy_score
212
+ from sklearn.model_selection import train_test_split
213
+ from fde import FDEClassifier
214
+
215
+ X, y = make_classification(n_samples=3000, n_features=12, n_informative=5, weights=[0.95, 0.05], random_state=1)
216
+ X = pd.DataFrame(X, columns=[f"x{i}" for i in range(12)])
217
+ X["region"] = np.where(X["x0"] > 0, "north", "south") # text columns can stay as text
218
+ X.loc[::25, "x1"] = np.nan # missing values are imputed inside each learner
219
+ y = np.where(y == 1, "fraud", "normal")
220
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
221
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
222
+ print("balanced accuracy:", round(balanced_accuracy_score(y_test, clf.predict(X_test)), 4))
223
+ clf.save("fraud.fde")
224
+ clf = FDEClassifier.load("fraud.fde")
225
+ print(clf.predict(X_test.head()))
226
+ ```
227
+
228
+ **Real imbalanced data with the settings of the paper (about 50 s).** The pc4 software-defect data from OpenML
229
+ (downloaded once, about 0.1 MB; 1,458 rows, 12% defects). Here the ensemble is used:
230
+
231
+ ```python
232
+ from sklearn.datasets import fetch_openml
233
+ from sklearn.metrics import f1_score
234
+ from sklearn.model_selection import train_test_split
235
+ from fde import FDEClassifier
236
+
237
+ X, y = fetch_openml(data_id=1049, return_X_y=True, as_frame=True)
238
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
239
+ clf = FDEClassifier(verbose=1).fit(X_train, y_train)
240
+ print("macro-F1:", round(f1_score(y_test.astype(str), clf.predict(X_test), average="macro"), 4))
241
+ print("ensemble used:", clf.uses_ensemble_, "| models in predictor:", clf.report_["models_in_predictor"])
242
+ print(clf.report_["acceptance_rule"]) # lower bound of the gain on the cross-fitted training folds
243
+ ```
244
+
245
+ **Command line with your own CSV file.** This writes a CSV from the wine data, then fits, predicts and evaluates:
246
+
247
+ ```bash
248
+ python -c "from sklearn.datasets import load_wine; d = load_wine(as_frame=True).frame; d.to_csv('wine.csv', index=False)"
249
+ fde fit --data wine.csv --target target --out wine.fde --trials 5 --members light
250
+ fde predict --model wine.fde --data wine.csv --proba --out wine_scores.csv
251
+ fde evaluate --data wine.csv --target target --trials 5 --members light
252
+ ```
253
+
254
+ On Windows, run each line separately.
255
+
256
+ ## How the data are split
257
+
258
+ No row is used both to choose a model and to score it:
259
+
260
+ 1. `fde evaluate` and `fde demo` set aside a stratified share of the rows (20% by default) before fitting; these rows
261
+ are used only for the held-out scores.
262
+ 2. On the training rows, 3 outer folds decide whether the ensemble is used. The decision is made on rows that each
263
+ fold's models have not seen (the cross-fitted acceptance rule).
264
+ 3. Inside every fold, the learners are tuned and the combiner is fitted on 3 inner folds, and every imputer, encoder
265
+ and scaler is fitted on its own training rows only.
266
+
267
+ To compare FDE with another tool, score both on the same held-out rows. A score computed on rows that were also used
268
+ for tuning comes out higher than the score on new data.
269
+
270
+ ## Options
271
+
272
+ | Option | Default | Meaning |
273
+ |---|---|---|
274
+ | `random_state` (`--seed`) | 0 | seed shared by every split, search and member |
275
+ | `n_jobs` (`--jobs`) | -1 | parallel tasks; -1 chooses from the free CPU cores and memory (results do not depend on it) |
276
+ | `members` (`--members`) | `full` | `light` drops the extended members: 14 models instead of 22, several times faster at prediction, with somewhat lower accuracy |
277
+ | `trials` (`--trials`) | 20 | Optuna trials for each learner type |
278
+ | `member_threads` | 4 | threads per extended member (Python API only) |
279
+ | `device` (`--device`) | `cpu` | `gpu` trains XGBoost, CatBoost and the MLP on the first CUDA GPU (LightGBM stays on the CPU: its GPU mode was slower in our tests). Results can differ slightly from the CPU; see the note below |
280
+ | `verbose` | 0 (1 on the command line) | 1 shows the system check and a progress bar with the stage and the remaining time; 2 also prints the report |
281
+
282
+ These defaults are the configuration evaluated in the accompanying paper.
283
+
284
+ **GPU note.** The results of the accompanying paper were obtained on the CPU, and the CPU is the default. On our test
285
+ machine (32 CPU cores, one RTX 4070) fitting was faster on the CPU than with `--device gpu`: 26 s against 139 s for
286
+ `fde demo`, and 365 s against 704 s for 40,000 training rows, because many CPU tasks run at the same time while GPU
287
+ tasks wait for the one GPU.
288
+ The GPU option is offered for machines with few CPU cores; check the time on your own data. Models fitted with
289
+ `--device gpu` predict on the CPU, so they also run on machines without a GPU.
290
+
291
+ ## Cost
292
+
293
+ Fitting costs more than tuning one learner, because up to twelve learner types are tuned four times (once per outer
294
+ fold and once on all rows). Prediction is cheap when the best tuned learner is kept: one model. When the ensemble is
295
+ used, the best learner, 21 members (eight of them bagged over 3 folds, three models each) and the combiner all run,
296
+ so prediction takes longer than with a single model.
297
+
298
+ ## Citation
299
+
300
+ Please cite the accompanying article if you use FDE; the full reference will be added once it is published. The
301
+ software itself is archived on Zenodo: version 1.0.0 has DOI
302
+ [10.5281/zenodo.23997869](https://doi.org/10.5281/zenodo.23997869), and
303
+ [10.5281/zenodo.23997868](https://doi.org/10.5281/zenodo.23997868) always points to the latest version. See `CITATION.cff`.
304
+
305
+ ## License
306
+
307
+ MIT.
@@ -0,0 +1,273 @@
1
+ # FDE: Feasible Diverse Ensembles for tabular classification
2
+
3
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.23997869.svg)](https://doi.org/10.5281/zenodo.23997869)
4
+
5
+ FDE wraps the best tuned single learner in a diverse ensemble and hands prediction to the ensemble only when the
6
+ training data support it over that learner. When they do not, the best tuned learner is used on its own.
7
+
8
+ - **Best tuned learner.** Optuna tunes twelve learner types on inner cross-validation (CatBoost, LightGBM, XGBoost,
9
+ HistGradientBoosting, random forest, extra trees, SVC, k-nearest neighbours, logistic regression, naive Bayes, MLP
10
+ and decision tree) and the highest-scoring type is kept. Above 3,000 rows, SVC is left out.
11
+ - **Members that handle class imbalance.** Three groups join the ensemble: the five next-best learner types refitted
12
+ with class weights; BalancedRandomForest, EasyEnsemble and RUSBoost; and the same five learner types refitted on
13
+ rebalanced samples, with their probabilities corrected to equal class priors.
14
+ - **Extended members.** Eight members work with a larger budget: CatBoost, LightGBM and XGBoost (ordinal or native
15
+ categorical coding, up to 5,000 trees with early stopping), an MLP with three hidden layers of 256 units, and
16
+ random forest and extra trees with 500 trees. Each is bagged over 3 folds of its training rows.
17
+ - **Combiner.** A stack (multinomial logistic regression) over the probabilities of the best learner and the
18
+ members, with a per-class rescaling of the resulting probabilities on top.
19
+ - **Cross-fitted acceptance rule.** Three outer cross-fitted folds of the training rows decide. The ensemble is kept
20
+ only if the lower 5% bootstrap quantile of its gain over the best tuned learner lies above 0, if its weight in the
21
+ all-rows fit is non-zero, and if it changes at least one outer-fold prediction. Otherwise the best tuned learner is
22
+ kept. Every imbalance level follows the same rule; on balanced data the best tuned learner is usually the one kept.
23
+
24
+ A run is scored with macro-F1 when there are two classes and the minority share is below 0.15, and with weighted-F1
25
+ in every other case.
26
+
27
+ ## Installation
28
+
29
+ Python 3.10 or later. Installing into a fresh virtual environment keeps FDE apart from other projects. FDE runs on the
30
+ CPU by default, so the CPU build of PyTorch is enough and much smaller than the GPU build. Choose your system:
31
+
32
+ <details open>
33
+ <summary><b>Linux</b></summary>
34
+
35
+ ```bash
36
+ python3 -m venv ~/fde-env
37
+ source ~/fde-env/bin/activate
38
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
39
+ pip install fde-tabular
40
+ fde demo
41
+ ```
42
+
43
+ If `python3 -m venv` reports that `ensurepip` is missing (Debian, Ubuntu), install it first with
44
+ `sudo apt install python3-venv`.
45
+ </details>
46
+
47
+ <details>
48
+ <summary><b>macOS</b></summary>
49
+
50
+ ```bash
51
+ brew install libomp
52
+ python3 -m venv ~/fde-env
53
+ source ~/fde-env/bin/activate
54
+ pip install torch
55
+ pip install fde-tabular
56
+ fde demo
57
+ ```
58
+
59
+ LightGBM and XGBoost need the OpenMP library (`libomp`) from Homebrew. On macOS the default PyTorch build already runs
60
+ on the CPU.
61
+ </details>
62
+
63
+ <details>
64
+ <summary><b>Windows (Command Prompt)</b></summary>
65
+
66
+ Type or paste **one line at a time** and press Enter after each line (if `py` is not found, use `python` instead):
67
+
68
+ ```bat
69
+ py -m venv %USERPROFILE%\fde-env
70
+ %USERPROFILE%\fde-env\Scripts\activate
71
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
72
+ pip install fde-tabular
73
+ fde demo
74
+ ```
75
+
76
+ In PowerShell, activate with `$env:USERPROFILE\fde-env\Scripts\Activate.ps1` instead (if scripts are blocked, run
77
+ `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once). The Command Prompt does not treat `#` as a comment, so do
78
+ not paste comments after a command. Commands written for Linux, such as `source` or `~/`, do not work here.
79
+ </details>
80
+
81
+ <details>
82
+ <summary><b>With a CUDA GPU (any system, optional)</b></summary>
83
+
84
+ Install the PyTorch build for your CUDA version from <https://pytorch.org> instead of the CPU build, then
85
+ `pip install fde-tabular`, and pass `--device gpu` (or `device="gpu"`). See the GPU note under Options before using it.
86
+ </details>
87
+
88
+ Each time you open a new terminal, activate the environment again (the second line above) before using `fde`.
89
+
90
+ Everything runs on the CPU unless `--device gpu` is given: each tuned learner gets one thread, each extended member
91
+ gets four (`member_threads`). Before fitting, FDE checks the free CPU cores and memory and runs that many tasks in
92
+ parallel (`n_jobs`); the progress bar shows the three stages (tuning the learners, building the members, fitting the
93
+ combiners) with an estimate of the remaining time.
94
+
95
+ ## Quick start
96
+
97
+ ```bash
98
+ fde demo # breast cancer data: the best tuned learner alone is enough
99
+ fde demo --example imbalanced # synthetic imbalanced data: the ensemble is used
100
+ ```
101
+
102
+ The first demo evaluates FDE on the breast cancer data that ships with scikit-learn, the second on synthetic data with a
103
+ 10% minority class (no file or download needed for either). The summary explains why the ensemble was used or not. The demos use
104
+ quick settings (5 Optuna trials per learner type and the light member set), so it finishes in about a minute on a
105
+ laptop; `fde demo --trials 20 --members full` runs the settings evaluated in the paper. A progress bar shows the
106
+ stage, the elapsed time and an estimate of the remaining time, and the result compares FDE with the best tuned
107
+ learner on held-out rows.
108
+
109
+ ## Use
110
+
111
+ ```python
112
+ from fde import FDEClassifier
113
+
114
+ clf = FDEClassifier(random_state=0).fit(X_train, y_train) # X_train: pandas DataFrame, y_train: labels
115
+ print(clf.report_) # best learner, acceptance rule, whether the ensemble is used
116
+ pred = clf.predict(X_test)
117
+ proba = clf.predict_proba(X_test)
118
+ clf.save("model.fde"); clf = FDEClassifier.load("model.fde")
119
+ ```
120
+
121
+ From the command line:
122
+
123
+ ```bash
124
+ fde fit --data train.csv --target label --out model.fde
125
+ fde predict --model model.fde --data new.csv --proba --out scores.csv
126
+ fde evaluate --data data.csv --target label --test-size 0.2 # holdout scores for FDE and the best tuned learner
127
+ fde fit --data train.csv --target label --out light.fde --members light # fewer members: faster prediction
128
+ ```
129
+
130
+ The command line prints a short summary: the best tuned learner, whether the ensemble was needed, the model size, the
131
+ fit time and, for `demo` and `evaluate`, the held-out scores. Add `--json` for the full report as JSON, and `--quiet`
132
+ to turn off the progress messages (they go to the standard error stream).
133
+
134
+ Text columns can be left as they are: every learner imputes and encodes them using only its own training rows, and
135
+ treats missing values the same way.
136
+
137
+ ## Examples
138
+
139
+ Each example runs as it is (`pip install fde-tabular` first). Times are for a 32-core laptop; with fewer cores they
140
+ grow roughly in proportion. Predicted labels are returned as text (for example `"0"` and `"1"`).
141
+
142
+ **Two classes, quick settings (about 25 s).** Breast cancer data, shipped with scikit-learn:
143
+
144
+ ```python
145
+ from sklearn.datasets import load_breast_cancer
146
+ from sklearn.model_selection import train_test_split
147
+ from fde import FDEClassifier
148
+
149
+ X, y = load_breast_cancer(return_X_y=True, as_frame=True)
150
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
151
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
152
+ print("accuracy:", (clf.predict(X_test) == y_test.astype(str)).mean())
153
+ print("ensemble used:", clf.uses_ensemble_, "| best learner:", clf.report_["best_learner"])
154
+ ```
155
+
156
+ **Several classes, with probabilities (about 10 s).** Wine data, shipped with scikit-learn:
157
+
158
+ ```python
159
+ from sklearn.datasets import load_wine
160
+ from sklearn.metrics import f1_score
161
+ from sklearn.model_selection import train_test_split
162
+ from fde import FDEClassifier
163
+
164
+ X, y = load_wine(return_X_y=True, as_frame=True)
165
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
166
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
167
+ print("macro-F1:", round(f1_score(y_test.astype(str), clf.predict(X_test), average="macro"), 4))
168
+ print(clf.classes_)
169
+ print(clf.predict_proba(X_test)[:3].round(3)) # one column per class, in the order of clf.classes_
170
+ ```
171
+
172
+ **Imbalanced classes, a text column and missing values; save and reload (about 15 s).** Synthetic data:
173
+
174
+ ```python
175
+ import numpy as np, pandas as pd
176
+ from sklearn.datasets import make_classification
177
+ from sklearn.metrics import balanced_accuracy_score
178
+ from sklearn.model_selection import train_test_split
179
+ from fde import FDEClassifier
180
+
181
+ X, y = make_classification(n_samples=3000, n_features=12, n_informative=5, weights=[0.95, 0.05], random_state=1)
182
+ X = pd.DataFrame(X, columns=[f"x{i}" for i in range(12)])
183
+ X["region"] = np.where(X["x0"] > 0, "north", "south") # text columns can stay as text
184
+ X.loc[::25, "x1"] = np.nan # missing values are imputed inside each learner
185
+ y = np.where(y == 1, "fraud", "normal")
186
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
187
+ clf = FDEClassifier(trials=5, members="light", verbose=1).fit(X_train, y_train)
188
+ print("balanced accuracy:", round(balanced_accuracy_score(y_test, clf.predict(X_test)), 4))
189
+ clf.save("fraud.fde")
190
+ clf = FDEClassifier.load("fraud.fde")
191
+ print(clf.predict(X_test.head()))
192
+ ```
193
+
194
+ **Real imbalanced data with the settings of the paper (about 50 s).** The pc4 software-defect data from OpenML
195
+ (downloaded once, about 0.1 MB; 1,458 rows, 12% defects). Here the ensemble is used:
196
+
197
+ ```python
198
+ from sklearn.datasets import fetch_openml
199
+ from sklearn.metrics import f1_score
200
+ from sklearn.model_selection import train_test_split
201
+ from fde import FDEClassifier
202
+
203
+ X, y = fetch_openml(data_id=1049, return_X_y=True, as_frame=True)
204
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
205
+ clf = FDEClassifier(verbose=1).fit(X_train, y_train)
206
+ print("macro-F1:", round(f1_score(y_test.astype(str), clf.predict(X_test), average="macro"), 4))
207
+ print("ensemble used:", clf.uses_ensemble_, "| models in predictor:", clf.report_["models_in_predictor"])
208
+ print(clf.report_["acceptance_rule"]) # lower bound of the gain on the cross-fitted training folds
209
+ ```
210
+
211
+ **Command line with your own CSV file.** This writes a CSV from the wine data, then fits, predicts and evaluates:
212
+
213
+ ```bash
214
+ python -c "from sklearn.datasets import load_wine; d = load_wine(as_frame=True).frame; d.to_csv('wine.csv', index=False)"
215
+ fde fit --data wine.csv --target target --out wine.fde --trials 5 --members light
216
+ fde predict --model wine.fde --data wine.csv --proba --out wine_scores.csv
217
+ fde evaluate --data wine.csv --target target --trials 5 --members light
218
+ ```
219
+
220
+ On Windows, run each line separately.
221
+
222
+ ## How the data are split
223
+
224
+ No row is used both to choose a model and to score it:
225
+
226
+ 1. `fde evaluate` and `fde demo` set aside a stratified share of the rows (20% by default) before fitting; these rows
227
+ are used only for the held-out scores.
228
+ 2. On the training rows, 3 outer folds decide whether the ensemble is used. The decision is made on rows that each
229
+ fold's models have not seen (the cross-fitted acceptance rule).
230
+ 3. Inside every fold, the learners are tuned and the combiner is fitted on 3 inner folds, and every imputer, encoder
231
+ and scaler is fitted on its own training rows only.
232
+
233
+ To compare FDE with another tool, score both on the same held-out rows. A score computed on rows that were also used
234
+ for tuning comes out higher than the score on new data.
235
+
236
+ ## Options
237
+
238
+ | Option | Default | Meaning |
239
+ |---|---|---|
240
+ | `random_state` (`--seed`) | 0 | seed shared by every split, search and member |
241
+ | `n_jobs` (`--jobs`) | -1 | parallel tasks; -1 chooses from the free CPU cores and memory (results do not depend on it) |
242
+ | `members` (`--members`) | `full` | `light` drops the extended members: 14 models instead of 22, several times faster at prediction, with somewhat lower accuracy |
243
+ | `trials` (`--trials`) | 20 | Optuna trials for each learner type |
244
+ | `member_threads` | 4 | threads per extended member (Python API only) |
245
+ | `device` (`--device`) | `cpu` | `gpu` trains XGBoost, CatBoost and the MLP on the first CUDA GPU (LightGBM stays on the CPU: its GPU mode was slower in our tests). Results can differ slightly from the CPU; see the note below |
246
+ | `verbose` | 0 (1 on the command line) | 1 shows the system check and a progress bar with the stage and the remaining time; 2 also prints the report |
247
+
248
+ These defaults are the configuration evaluated in the accompanying paper.
249
+
250
+ **GPU note.** The results of the accompanying paper were obtained on the CPU, and the CPU is the default. On our test
251
+ machine (32 CPU cores, one RTX 4070) fitting was faster on the CPU than with `--device gpu`: 26 s against 139 s for
252
+ `fde demo`, and 365 s against 704 s for 40,000 training rows, because many CPU tasks run at the same time while GPU
253
+ tasks wait for the one GPU.
254
+ The GPU option is offered for machines with few CPU cores; check the time on your own data. Models fitted with
255
+ `--device gpu` predict on the CPU, so they also run on machines without a GPU.
256
+
257
+ ## Cost
258
+
259
+ Fitting costs more than tuning one learner, because up to twelve learner types are tuned four times (once per outer
260
+ fold and once on all rows). Prediction is cheap when the best tuned learner is kept: one model. When the ensemble is
261
+ used, the best learner, 21 members (eight of them bagged over 3 folds, three models each) and the combiner all run,
262
+ so prediction takes longer than with a single model.
263
+
264
+ ## Citation
265
+
266
+ Please cite the accompanying article if you use FDE; the full reference will be added once it is published. The
267
+ software itself is archived on Zenodo: version 1.0.0 has DOI
268
+ [10.5281/zenodo.23997869](https://doi.org/10.5281/zenodo.23997869), and
269
+ [10.5281/zenodo.23997868](https://doi.org/10.5281/zenodo.23997868) always points to the latest version. See `CITATION.cff`.
270
+
271
+ ## License
272
+
273
+ MIT.
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fde-tabular"
7
+ version = "1.0.0"
8
+ description = "FDE: Feasible Diverse Ensembles for tabular classification"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Dwi Sunaryono" }, { name = "Misbakhul Munir Irfan Subakti" }, { name = "Laras Suciningtyas" },
13
+ { name = "Irzal Ahmad Sabilla" }, { name = "Muchamad Kurniawan" }]
14
+ requires-python = ">=3.10"
15
+ keywords = ["ensemble learning", "class imbalance", "tabular data", "classification", "stacking"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ "Intended Audience :: Science/Research",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ dependencies = [
23
+ "numpy>=1.26",
24
+ "pandas>=2.1",
25
+ "scipy>=1.11",
26
+ "scikit-learn>=1.5",
27
+ "imbalanced-learn>=0.12",
28
+ "xgboost>=2.0",
29
+ "lightgbm>=4.0",
30
+ "catboost>=1.2",
31
+ "optuna>=3.6",
32
+ "joblib>=1.3",
33
+ "torch>=2.1",
34
+ "psutil>=5.9",
35
+ "rich>=13",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/DwiSunaryono/fde-tabular"
40
+ Repository = "https://github.com/DwiSunaryono/fde-tabular"
41
+ Issues = "https://github.com/DwiSunaryono/fde-tabular/issues"
42
+
43
+ [project.optional-dependencies]
44
+ test = ["pytest>=7"]
45
+
46
+ [project.scripts]
47
+ fde = "fde.cli:main"
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ """FDE: Feasible Diverse Ensembles for tabular classification."""
2
+ __version__ = "1.0.0"
3
+ __all__ = ["FDEClassifier", "__version__"]
4
+
5
+
6
+ def __getattr__(name):
7
+ # FDEClassifier is imported on first use, so that the command line can print its first message before the
8
+ # learner libraries load
9
+ if name == "FDEClassifier":
10
+ from .classifier import FDEClassifier
11
+ return FDEClassifier
12
+ raise AttributeError(name)