easyclassifier 0.8.1__tar.gz → 0.8.2__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.
- {easyclassifier-0.8.1/src/easyclassifier.egg-info → easyclassifier-0.8.2}/PKG-INFO +16 -4
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/README.md +15 -3
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/pyproject.toml +1 -1
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/__init__.py +5 -3
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/distances.py +12 -3
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/evaluation.py +1 -1
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/help_texts.py +4 -2
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/latex_report.py +52 -23
- easyclassifier-0.8.2/src/easyclassifier/references.py +284 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/wizard.py +31 -10
- {easyclassifier-0.8.1 → easyclassifier-0.8.2/src/easyclassifier.egg-info}/PKG-INFO +16 -4
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/SOURCES.txt +2 -0
- easyclassifier-0.8.2/tests/test_references.py +77 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_report.py +21 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/LICENSE +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/setup.cfg +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/__main__.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/dataset.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/demo_data.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/diagnostics.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/figures.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/importance.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/logbook.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/models.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/preprocessing.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/recommend.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/reporting.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/target.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier/ui.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/dependency_links.txt +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/entry_points.txt +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/requires.txt +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/top_level.txt +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_figures.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_first_run.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_leakage.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_selection.py +0 -0
- {easyclassifier-0.8.1 → easyclassifier-0.8.2}/tests/test_target.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easyclassifier
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Machine learning classification without programming - a guided, menu-driven wizard.
|
|
5
5
|
Author-email: Ahmad Hassanat <ahmad.hassanat@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -40,6 +40,7 @@ Dynamic: license-file
|
|
|
40
40
|
[](https://pypi.org/project/easyclassifier/)
|
|
41
41
|
[](https://www.python.org/)
|
|
42
42
|
[](https://github.com/ahmadhassant/easyclassifier/blob/main/LICENSE)
|
|
43
|
+
[](https://doi.org/10.5281/zenodo.23122901)
|
|
43
44
|
|
|
44
45
|
**Machine Learning without Programming.**
|
|
45
46
|
|
|
@@ -48,6 +49,11 @@ evaluate machine learning classification models from a CSV or Excel file —
|
|
|
48
49
|
no Python code, no scripting, and no machine learning jargon required
|
|
49
50
|
(unless you want it).
|
|
50
51
|
|
|
52
|
+
Regression, time-series forecasting, signal classification and a Windows
|
|
53
|
+
desktop application that runs all four tasks are in
|
|
54
|
+
[EasyResearch](https://github.com/ahmadhassant/easyresearch), which is built
|
|
55
|
+
on EasyClassifier and follows the same rules.
|
|
56
|
+
|
|
51
57
|
**New to Python or the terminal? Read the step-by-step
|
|
52
58
|
[User Guide](https://github.com/ahmadhassant/easyclassifier/blob/main/docs/USER_GUIDE.md).** It covers installing Python, installing
|
|
53
59
|
EasyClassifier, running it, what each question means, and what the results
|
|
@@ -222,8 +228,14 @@ The report says whether the normal form (all values >= 0) or the signed form
|
|
|
222
228
|
(negative values present) of the formula was applied, and the citation is
|
|
223
229
|
shown on screen and saved to `citations.txt`:
|
|
224
230
|
|
|
225
|
-
> Hassanat, A. B
|
|
226
|
-
>
|
|
231
|
+
> Hassanat, A. B., Alkafaween, E., Tarawneh, A. S., & Elmougy, S. (2022).
|
|
232
|
+
> Applications review of Hassanat distance metric. In *2022 International
|
|
233
|
+
> Conference on Emerging Trends in Computing and Engineering Applications
|
|
234
|
+
> (ETCEA)*, Karak, Jordan (pp. 1–6). IEEE.
|
|
235
|
+
> https://doi.org/10.1109/ETCEA57049.2022.10009844
|
|
236
|
+
>
|
|
237
|
+
> Hassanat, A. B. (2014). Dimensionality invariant similarity measure.
|
|
238
|
+
> arXiv preprint arXiv:1409.0923. https://arxiv.org/abs/1409.0923
|
|
227
239
|
|
|
228
240
|
## Benchmarks
|
|
229
241
|
|
|
@@ -254,7 +266,7 @@ can see the whole workflow immediately.
|
|
|
254
266
|
## Citing
|
|
255
267
|
|
|
256
268
|
If you use EasyClassifier in published work, please cite it (GitHub's
|
|
257
|
-
"Cite this repository" button uses [`CITATION.cff`](CITATION.cff)); each
|
|
269
|
+
"Cite this repository" button uses [`CITATION.cff`](https://github.com/ahmadhassant/easyclassifier/blob/main/CITATION.cff)); each
|
|
258
270
|
run also writes the exact references to `citations.txt`.
|
|
259
271
|
|
|
260
272
|
## Contributing
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://pypi.org/project/easyclassifier/)
|
|
5
5
|
[](https://www.python.org/)
|
|
6
6
|
[](https://github.com/ahmadhassant/easyclassifier/blob/main/LICENSE)
|
|
7
|
+
[](https://doi.org/10.5281/zenodo.23122901)
|
|
7
8
|
|
|
8
9
|
**Machine Learning without Programming.**
|
|
9
10
|
|
|
@@ -12,6 +13,11 @@ evaluate machine learning classification models from a CSV or Excel file —
|
|
|
12
13
|
no Python code, no scripting, and no machine learning jargon required
|
|
13
14
|
(unless you want it).
|
|
14
15
|
|
|
16
|
+
Regression, time-series forecasting, signal classification and a Windows
|
|
17
|
+
desktop application that runs all four tasks are in
|
|
18
|
+
[EasyResearch](https://github.com/ahmadhassant/easyresearch), which is built
|
|
19
|
+
on EasyClassifier and follows the same rules.
|
|
20
|
+
|
|
15
21
|
**New to Python or the terminal? Read the step-by-step
|
|
16
22
|
[User Guide](https://github.com/ahmadhassant/easyclassifier/blob/main/docs/USER_GUIDE.md).** It covers installing Python, installing
|
|
17
23
|
EasyClassifier, running it, what each question means, and what the results
|
|
@@ -186,8 +192,14 @@ The report says whether the normal form (all values >= 0) or the signed form
|
|
|
186
192
|
(negative values present) of the formula was applied, and the citation is
|
|
187
193
|
shown on screen and saved to `citations.txt`:
|
|
188
194
|
|
|
189
|
-
> Hassanat, A. B
|
|
190
|
-
>
|
|
195
|
+
> Hassanat, A. B., Alkafaween, E., Tarawneh, A. S., & Elmougy, S. (2022).
|
|
196
|
+
> Applications review of Hassanat distance metric. In *2022 International
|
|
197
|
+
> Conference on Emerging Trends in Computing and Engineering Applications
|
|
198
|
+
> (ETCEA)*, Karak, Jordan (pp. 1–6). IEEE.
|
|
199
|
+
> https://doi.org/10.1109/ETCEA57049.2022.10009844
|
|
200
|
+
>
|
|
201
|
+
> Hassanat, A. B. (2014). Dimensionality invariant similarity measure.
|
|
202
|
+
> arXiv preprint arXiv:1409.0923. https://arxiv.org/abs/1409.0923
|
|
191
203
|
|
|
192
204
|
## Benchmarks
|
|
193
205
|
|
|
@@ -218,7 +230,7 @@ can see the whole workflow immediately.
|
|
|
218
230
|
## Citing
|
|
219
231
|
|
|
220
232
|
If you use EasyClassifier in published work, please cite it (GitHub's
|
|
221
|
-
"Cite this repository" button uses [`CITATION.cff`](CITATION.cff)); each
|
|
233
|
+
"Cite this repository" button uses [`CITATION.cff`](https://github.com/ahmadhassant/easyclassifier/blob/main/CITATION.cff)); each
|
|
222
234
|
run also writes the exact references to `citations.txt`.
|
|
223
235
|
|
|
224
236
|
## Contributing
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "easyclassifier"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.2"
|
|
8
8
|
description = "Machine learning classification without programming - a guided, menu-driven wizard."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -4,12 +4,14 @@ A guided, menu-driven wizard that lets non-programmers build and evaluate
|
|
|
4
4
|
classification models from a CSV file.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.8.
|
|
7
|
+
__version__ = "0.8.2"
|
|
8
8
|
__author__ = "Ahmad Hassanat"
|
|
9
9
|
|
|
10
|
+
# As in CITATION.cff and the user guide (Zenodo DOI).
|
|
10
11
|
CITATION = (
|
|
11
|
-
"Hassanat, A. (2026). EasyClassifier: Machine Learning without "
|
|
12
|
-
"Programming (Version {version}) [Software]."
|
|
12
|
+
"Hassanat, A. B. A. (2026). EasyClassifier: Machine Learning without "
|
|
13
|
+
"Programming (Version {version}) [Software]. Zenodo. "
|
|
14
|
+
"https://doi.org/10.5281/zenodo.23122901".format(version=__version__)
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
from .wizard import Wizard # noqa: E402
|
|
@@ -23,12 +23,21 @@ from sklearn.base import BaseEstimator, ClassifierMixin
|
|
|
23
23
|
from sklearn.neighbors import KNeighborsClassifier
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
# The Hassanat distance is cited with the IEEE conference paper that reviews
|
|
27
|
+
# its applications, and the arXiv preprint that introduced it.
|
|
26
28
|
HASSANAT_CITATION = (
|
|
27
|
-
"Hassanat, A. B
|
|
28
|
-
"
|
|
29
|
+
"Hassanat, A. B., Alkafaween, E., Tarawneh, A. S., & Elmougy, S. (2022). "
|
|
30
|
+
"Applications review of Hassanat distance metric. In 2022 International "
|
|
31
|
+
"Conference on Emerging Trends in Computing and Engineering Applications "
|
|
32
|
+
"(ETCEA), Karak, Jordan (pp. 1-6). IEEE. "
|
|
33
|
+
"https://doi.org/10.1109/ETCEA57049.2022.10009844"
|
|
34
|
+
)
|
|
35
|
+
HASSANAT_ARXIV_CITATION = (
|
|
36
|
+
"Hassanat, A. B. (2014). Dimensionality invariant similarity measure. "
|
|
37
|
+
"arXiv preprint arXiv:1409.0923. https://arxiv.org/abs/1409.0923"
|
|
29
38
|
)
|
|
30
39
|
|
|
31
|
-
HASSANAT_CITATIONS: List[str] = [HASSANAT_CITATION]
|
|
40
|
+
HASSANAT_CITATIONS: List[str] = [HASSANAT_CITATION, HASSANAT_ARXIV_CITATION]
|
|
32
41
|
|
|
33
42
|
# key -> (menu label, sklearn metric name or None for custom)
|
|
34
43
|
DISTANCES: Dict[str, tuple] = {
|
|
@@ -14,8 +14,10 @@ GLOSSARY = {
|
|
|
14
14
|
"items the model successfully found.",
|
|
15
15
|
"f1": "F1 is a single score that balances precision and recall. It is "
|
|
16
16
|
"high only when both are high.",
|
|
17
|
-
"specificity": "
|
|
18
|
-
"the model correctly
|
|
17
|
+
"specificity": "Macro specificity: for each class, the share of items "
|
|
18
|
+
"from the other classes that the model correctly did not "
|
|
19
|
+
"put in that class, averaged over all classes. With two "
|
|
20
|
+
"classes it equals balanced accuracy.",
|
|
19
21
|
"sensitivity": "Sensitivity is another name for recall: how many real "
|
|
20
22
|
"positives the model found.",
|
|
21
23
|
"mcc": "Matthews Correlation Coefficient summarises the whole confusion "
|
|
@@ -25,15 +25,14 @@ from typing import Dict, List, Optional, Tuple
|
|
|
25
25
|
|
|
26
26
|
import pandas as pd
|
|
27
27
|
|
|
28
|
-
from .
|
|
28
|
+
from . import references as refs
|
|
29
29
|
from .evaluation import METRICS, SELECTION_LABEL, VALIDATION
|
|
30
30
|
from .help_texts import explain
|
|
31
|
+
from .references import cite
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
BREIMAN_CITATION = ("Breiman, L. (2001). Random Forests. Machine Learning, "
|
|
36
|
-
"45, 5-32.")
|
|
33
|
+
# Kept for the EasyResearch cores, which import them from here.
|
|
34
|
+
SKLEARN_CITATION = refs.REFERENCES["sklearn"]
|
|
35
|
+
BREIMAN_CITATION = refs.REFERENCES["breiman2001"]
|
|
37
36
|
|
|
38
37
|
PERCENT_METRICS = {"accuracy", "precision", "recall", "f1",
|
|
39
38
|
"balanced_accuracy", "specificity"}
|
|
@@ -76,6 +75,11 @@ class ReportContext:
|
|
|
76
75
|
importance: Optional[pd.DataFrame] = None
|
|
77
76
|
comparison_text: str = ""
|
|
78
77
|
learning_text: str = ""
|
|
78
|
+
# Registry keys of the classifiers (same order as ``classifiers``) and the
|
|
79
|
+
# KNN distance, so that each method is cited.
|
|
80
|
+
classifier_keys: List[str] = field(default_factory=list)
|
|
81
|
+
knn_distance: Optional[str] = None
|
|
82
|
+
hassanat_signed: bool = False # signed form applied (negative values)
|
|
79
83
|
|
|
80
84
|
|
|
81
85
|
# --------------------------------------------------------------------------- #
|
|
@@ -195,7 +199,8 @@ def _methods_paragraph(c: ReportContext) -> str:
|
|
|
195
199
|
parts = [
|
|
196
200
|
f"Classification was carried out with EasyClassifier version "
|
|
197
201
|
f"{tex(c.version)} \\cite{{easyclassifier}}, which is built on "
|
|
198
|
-
f"scikit-learn \\cite{{sklearn}}
|
|
202
|
+
f"scikit-learn \\cite{{sklearn}}, NumPy \\cite{{numpy}} and pandas "
|
|
203
|
+
f"\\cite{{pandas}}.",
|
|
199
204
|
f"The dataset ({tex(c.dataset)}) contained {c.rows_loaded} rows and "
|
|
200
205
|
f"{c.cols_loaded} columns. The outcome to predict was "
|
|
201
206
|
f"\\emph{{{tex(c.target)}}} with {k} classes.",
|
|
@@ -243,14 +248,23 @@ def _methods_paragraph(c: ReportContext) -> str:
|
|
|
243
248
|
"separately within every validation split, so that no "
|
|
244
249
|
"information from test data entered model training.")
|
|
245
250
|
|
|
251
|
+
if len(c.classifier_keys) == len(c.classifiers):
|
|
252
|
+
named = [tex(n) + cite(refs.CLASSIFIER_REFERENCES.get(k, []))
|
|
253
|
+
for n, k in zip(c.classifiers, c.classifier_keys)]
|
|
254
|
+
else:
|
|
255
|
+
named = [tex(n) for n in c.classifiers]
|
|
246
256
|
parts.append(
|
|
247
257
|
"The following classifiers were used with their default parameters, "
|
|
248
|
-
"without hyperparameter tuning: " +
|
|
258
|
+
"without hyperparameter tuning: " + _join(named) + ".")
|
|
249
259
|
if c.knn_text:
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
keys = refs.DISTANCE_REFERENCES.get(c.knn_distance or "", [])
|
|
261
|
+
if c.hassanat_used:
|
|
262
|
+
keys = refs.DISTANCE_REFERENCES["hassanat"] + (
|
|
263
|
+
refs.HASSANAT_SIGNED_REFERENCES if c.hassanat_signed else [])
|
|
264
|
+
parts.append(tex(c.knn_text) + cite(keys) + ".")
|
|
252
265
|
parts.append(f"Performance was estimated with "
|
|
253
|
-
f"{tex(VALIDATION[c.validation].lower())}
|
|
266
|
+
f"{tex(VALIDATION[c.validation].lower())}"
|
|
267
|
+
f"{cite(refs.VALIDATION_REFERENCES.get(c.validation, []))}.")
|
|
254
268
|
|
|
255
269
|
if c.final is not None and c.selection == "final_test":
|
|
256
270
|
parts.append(
|
|
@@ -259,7 +273,8 @@ def _methods_paragraph(c: ReportContext) -> str:
|
|
|
259
273
|
f"{c.n_test} rows (20\\%, stratified by class) were set aside "
|
|
260
274
|
f"before any analysis; classifiers were compared on the "
|
|
261
275
|
f"remaining {c.n_dev} rows only, and the selected classifier was "
|
|
262
|
-
f"evaluated once on the untouched test rows
|
|
276
|
+
f"evaluated once on the untouched test rows"
|
|
277
|
+
f"{cite(refs.SELECTION_REFERENCES['final_test'])}.")
|
|
263
278
|
elif c.final is not None and c.selection == "nested":
|
|
264
279
|
parts.append(
|
|
265
280
|
f"The classifier with the highest {SELECTION_LABEL.lower()} was "
|
|
@@ -267,7 +282,7 @@ def _methods_paragraph(c: ReportContext) -> str:
|
|
|
267
282
|
f"performance was estimated with nested 5-fold cross-validation, "
|
|
268
283
|
f"in which the complete comparison was repeated within each "
|
|
269
284
|
f"outer training fold and the winner was evaluated on the outer "
|
|
270
|
-
f"test fold.")
|
|
285
|
+
f"test fold{cite(refs.SELECTION_REFERENCES['nested'])}.")
|
|
271
286
|
if c.importance is not None:
|
|
272
287
|
parts.append(
|
|
273
288
|
"The contribution of each predictor was estimated by permutation "
|
|
@@ -275,6 +290,21 @@ def _methods_paragraph(c: ReportContext) -> str:
|
|
|
275
290
|
f"{SELECTION_LABEL.lower()} when that column's values were "
|
|
276
291
|
"randomly shuffled, measured on the "
|
|
277
292
|
f"{tex(c.importance.attrs.get('measured_on', 'held-out rows'))}.")
|
|
293
|
+
measures = [tex(METRICS[m]) + cite(refs.METRIC_REFERENCES.get(m, []))
|
|
294
|
+
for m in c.metrics if m in METRICS]
|
|
295
|
+
parts.append(
|
|
296
|
+
f"Classifiers were ranked by {SELECTION_LABEL.lower()}"
|
|
297
|
+
f"{cite(refs.METRIC_REFERENCES['balanced_accuracy'])}"
|
|
298
|
+
+ (f"; the measures reported were {_join(measures)}" if measures
|
|
299
|
+
else "") + ".")
|
|
300
|
+
if c.figures:
|
|
301
|
+
curves = [f"{label}{cite(refs.FIGURE_REFERENCES[k])}"
|
|
302
|
+
for k, label in (("roc_curve", "ROC curves"),
|
|
303
|
+
("pr_curve", "precision-recall curves"))
|
|
304
|
+
if k in c.figures]
|
|
305
|
+
parts.append("Figures were drawn with Matplotlib \\cite{matplotlib}"
|
|
306
|
+
+ (", including " + _join(curves) if curves else "")
|
|
307
|
+
+ ".")
|
|
278
308
|
return " ".join(parts)
|
|
279
309
|
|
|
280
310
|
|
|
@@ -471,16 +501,15 @@ def build_tex(c: ReportContext) -> str:
|
|
|
471
501
|
"\\texttt{log.txt} in the Results folder.\n"
|
|
472
502
|
"\\end{itemize}\n")
|
|
473
503
|
|
|
474
|
-
# 6. References
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return "".join(L)
|
|
504
|
+
# 6. References: every reference cited above, in order of citation ----- #
|
|
505
|
+
body = "".join(L)
|
|
506
|
+
return (body + refs.bibliography(body, report_entries(c), escape=tex)
|
|
507
|
+
+ "\\end{document}\n")
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def report_entries(c: ReportContext) -> Dict[str, str]:
|
|
511
|
+
"""Report-specific bibliography entries (the software itself)."""
|
|
512
|
+
return {"easyclassifier": c.software_citation}
|
|
484
513
|
|
|
485
514
|
|
|
486
515
|
# --------------------------------------------------------------------------- #
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""References for every method a report can mention.
|
|
2
|
+
|
|
3
|
+
Each report cites, in its Methods paragraph, the software and every method
|
|
4
|
+
that was actually used: the classifiers (or other models), the KNN distance,
|
|
5
|
+
the evaluation design, the measures and the figures that need a source. The
|
|
6
|
+
bibliography and ``citations.txt`` are generated from the ``\\cite{...}``
|
|
7
|
+
commands in the finished report, so a reference is listed exactly when it is
|
|
8
|
+
cited, and nothing that is cited can be missing.
|
|
9
|
+
|
|
10
|
+
The EasyResearch cores (regression, forecasting, signal classification)
|
|
11
|
+
extend this list with the references of their own models.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from typing import Dict, Iterable, List, Optional
|
|
18
|
+
|
|
19
|
+
from .distances import HASSANAT_ARXIV_CITATION, HASSANAT_CITATION
|
|
20
|
+
|
|
21
|
+
# --------------------------------------------------------------------------- #
|
|
22
|
+
# Reference texts
|
|
23
|
+
# --------------------------------------------------------------------------- #
|
|
24
|
+
|
|
25
|
+
REFERENCES: Dict[str, str] = {
|
|
26
|
+
# Software
|
|
27
|
+
"sklearn": "Pedregosa, F., et al. (2011). Scikit-learn: Machine "
|
|
28
|
+
"Learning in Python. Journal of Machine Learning Research, "
|
|
29
|
+
"12, 2825-2830.",
|
|
30
|
+
"numpy": "Harris, C. R., Millman, K. J., van der Walt, S. J., et al. "
|
|
31
|
+
"(2020). Array programming with NumPy. Nature, 585(7825), "
|
|
32
|
+
"357-362. https://doi.org/10.1038/s41586-020-2649-2",
|
|
33
|
+
"pandas": "McKinney, W. (2010). Data structures for statistical "
|
|
34
|
+
"computing in Python. In Proceedings of the 9th Python in "
|
|
35
|
+
"Science Conference (pp. 56-61). "
|
|
36
|
+
"https://doi.org/10.25080/Majora-92bf1922-00a",
|
|
37
|
+
"matplotlib": "Hunter, J. D. (2007). Matplotlib: A 2D graphics "
|
|
38
|
+
"environment. Computing in Science & Engineering, 9(3), "
|
|
39
|
+
"90-95. https://doi.org/10.1109/MCSE.2007.55",
|
|
40
|
+
# Classifiers
|
|
41
|
+
"breiman1984": "Breiman, L., Friedman, J. H., Olshen, R. A., & Stone, "
|
|
42
|
+
"C. J. (1984). Classification and Regression Trees. "
|
|
43
|
+
"Wadsworth.",
|
|
44
|
+
"breiman2001": "Breiman, L. (2001). Random Forests. Machine Learning, "
|
|
45
|
+
"45, 5-32. https://doi.org/10.1023/A:1010933404324",
|
|
46
|
+
"cortes1995": "Cortes, C., & Vapnik, V. (1995). Support-vector "
|
|
47
|
+
"networks. Machine Learning, 20(3), 273-297. "
|
|
48
|
+
"https://doi.org/10.1007/BF00994018",
|
|
49
|
+
"platt1999": "Platt, J. C. (1999). Probabilistic outputs for support "
|
|
50
|
+
"vector machines and comparisons to regularized likelihood "
|
|
51
|
+
"methods. In A. J. Smola, P. Bartlett, B. Schölkopf, & "
|
|
52
|
+
"D. Schuurmans (Eds.), Advances in Large Margin Classifiers "
|
|
53
|
+
"(pp. 61-74). MIT Press.",
|
|
54
|
+
"cox1958": "Cox, D. R. (1958). The regression analysis of binary "
|
|
55
|
+
"sequences. Journal of the Royal Statistical Society: Series "
|
|
56
|
+
"B, 20(2), 215-242. "
|
|
57
|
+
"https://doi.org/10.1111/j.2517-6161.1958.tb00292.x",
|
|
58
|
+
"cover1967": "Cover, T., & Hart, P. (1967). Nearest neighbor pattern "
|
|
59
|
+
"classification. IEEE Transactions on Information Theory, "
|
|
60
|
+
"13(1), 21-27. https://doi.org/10.1109/TIT.1967.1053964",
|
|
61
|
+
"hand2001": "Hand, D. J., & Yu, K. (2001). Idiot's Bayes - not so "
|
|
62
|
+
"stupid after all? International Statistical Review, 69(3), "
|
|
63
|
+
"385-398. "
|
|
64
|
+
"https://doi.org/10.1111/j.1751-5823.2001.tb00465.x",
|
|
65
|
+
"chen2016": "Chen, T., & Guestrin, C. (2016). XGBoost: A scalable tree "
|
|
66
|
+
"boosting system. In Proceedings of the 22nd ACM SIGKDD "
|
|
67
|
+
"International Conference on Knowledge Discovery and Data "
|
|
68
|
+
"Mining (pp. 785-794). "
|
|
69
|
+
"https://doi.org/10.1145/2939672.2939785",
|
|
70
|
+
"ke2017": "Ke, G., Meng, Q., Finley, T., Wang, T., Chen, W., Ma, W., "
|
|
71
|
+
"Ye, Q., & Liu, T.-Y. (2017). LightGBM: A highly efficient "
|
|
72
|
+
"gradient boosting decision tree. In Advances in Neural "
|
|
73
|
+
"Information Processing Systems 30 (pp. 3146-3154).",
|
|
74
|
+
"rumelhart1986": "Rumelhart, D. E., Hinton, G. E., & Williams, R. J. "
|
|
75
|
+
"(1986). Learning representations by back-propagating "
|
|
76
|
+
"errors. Nature, 323(6088), 533-536. "
|
|
77
|
+
"https://doi.org/10.1038/323533a0",
|
|
78
|
+
"kingma2015": "Kingma, D. P., & Ba, J. (2015). Adam: A method for "
|
|
79
|
+
"stochastic optimization. In International Conference on "
|
|
80
|
+
"Learning Representations (ICLR). arXiv:1412.6980.",
|
|
81
|
+
# Distances
|
|
82
|
+
"hassanat2022": HASSANAT_CITATION,
|
|
83
|
+
"hassanat2014": HASSANAT_ARXIV_CITATION,
|
|
84
|
+
"alaydaa2026": "Alaydaa, M. S., Alotibi, G. N., Tarawneh, A. S., & "
|
|
85
|
+
"Hassanat, A. B. (2026). A sign-symmetric reformulation "
|
|
86
|
+
"of the Hassanat distance for data with negative feature "
|
|
87
|
+
"values. Symmetry, 18(7), 1225. "
|
|
88
|
+
"https://doi.org/10.3390/sym18071225",
|
|
89
|
+
"lance1966": "Lance, G. N., & Williams, W. T. (1966). Computer programs "
|
|
90
|
+
"for hierarchical polythetic classification (\"similarity "
|
|
91
|
+
"analyses\"). The Computer Journal, 9(1), 60-64. "
|
|
92
|
+
"https://doi.org/10.1093/comjnl/9.1.60",
|
|
93
|
+
"deza2009": "Deza, M. M., & Deza, E. (2009). Encyclopedia of Distances. "
|
|
94
|
+
"Springer. https://doi.org/10.1007/978-3-642-00234-2",
|
|
95
|
+
# Evaluation
|
|
96
|
+
"kohavi1995": "Kohavi, R. (1995). A study of cross-validation and "
|
|
97
|
+
"bootstrap for accuracy estimation and model selection. In "
|
|
98
|
+
"Proceedings of the 14th International Joint Conference on "
|
|
99
|
+
"Artificial Intelligence (IJCAI) (pp. 1137-1143).",
|
|
100
|
+
"lachenbruch1968": "Lachenbruch, P. A., & Mickey, M. R. (1968). "
|
|
101
|
+
"Estimation of error rates in discriminant analysis. "
|
|
102
|
+
"Technometrics, 10(1), 1-11. "
|
|
103
|
+
"https://doi.org/10.1080/00401706.1968.10490530",
|
|
104
|
+
"varma2006": "Varma, S., & Simon, R. (2006). Bias in error estimation "
|
|
105
|
+
"when using cross-validation for model selection. BMC "
|
|
106
|
+
"Bioinformatics, 7, 91. "
|
|
107
|
+
"https://doi.org/10.1186/1471-2105-7-91",
|
|
108
|
+
"cawley2010": "Cawley, G. C., & Talbot, N. L. C. (2010). On over-fitting "
|
|
109
|
+
"in model selection and subsequent selection bias in "
|
|
110
|
+
"performance evaluation. Journal of Machine Learning "
|
|
111
|
+
"Research, 11, 2079-2107.",
|
|
112
|
+
# Measures
|
|
113
|
+
"brodersen2010": "Brodersen, K. H., Ong, C. S., Stephan, K. E., & "
|
|
114
|
+
"Buhmann, J. M. (2010). The balanced accuracy and its "
|
|
115
|
+
"posterior distribution. In 20th International "
|
|
116
|
+
"Conference on Pattern Recognition (pp. 3121-3124). "
|
|
117
|
+
"IEEE. https://doi.org/10.1109/ICPR.2010.764",
|
|
118
|
+
"matthews1975": "Matthews, B. W. (1975). Comparison of the predicted and "
|
|
119
|
+
"observed secondary structure of T4 phage lysozyme. "
|
|
120
|
+
"Biochimica et Biophysica Acta - Protein Structure, "
|
|
121
|
+
"405(2), 442-451. "
|
|
122
|
+
"https://doi.org/10.1016/0005-2795(75)90109-9",
|
|
123
|
+
"cohen1960": "Cohen, J. (1960). A coefficient of agreement for nominal "
|
|
124
|
+
"scales. Educational and Psychological Measurement, 20(1), "
|
|
125
|
+
"37-46. https://doi.org/10.1177/001316446002000104",
|
|
126
|
+
"fawcett2006": "Fawcett, T. (2006). An introduction to ROC analysis. "
|
|
127
|
+
"Pattern Recognition Letters, 27(8), 861-874. "
|
|
128
|
+
"https://doi.org/10.1016/j.patrec.2005.10.010",
|
|
129
|
+
"davis2006": "Davis, J., & Goadrich, M. (2006). The relationship between "
|
|
130
|
+
"precision-recall and ROC curves. In Proceedings of the "
|
|
131
|
+
"23rd International Conference on Machine Learning "
|
|
132
|
+
"(pp. 233-240). https://doi.org/10.1145/1143844.1143874",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# What each reference is cited for (shown in citations.txt).
|
|
136
|
+
TOPICS: Dict[str, str] = {
|
|
137
|
+
"sklearn": "scikit-learn (models, preprocessing and evaluation)",
|
|
138
|
+
"numpy": "NumPy (numerical computing)",
|
|
139
|
+
"pandas": "pandas (data handling)",
|
|
140
|
+
"matplotlib": "Matplotlib (figures)",
|
|
141
|
+
"breiman1984": "Decision tree (CART)",
|
|
142
|
+
"breiman2001": "Random Forest; permutation importance",
|
|
143
|
+
"cortes1995": "Support vector machine",
|
|
144
|
+
"platt1999": "SVM probability estimates (Platt scaling)",
|
|
145
|
+
"cox1958": "Logistic regression",
|
|
146
|
+
"cover1967": "K-nearest neighbours",
|
|
147
|
+
"hand2001": "Naive Bayes",
|
|
148
|
+
"chen2016": "XGBoost",
|
|
149
|
+
"ke2017": "LightGBM; histogram-based gradient boosting",
|
|
150
|
+
"rumelhart1986": "Neural network (multilayer perceptron, backpropagation)",
|
|
151
|
+
"kingma2015": "Adam optimiser (neural network training)",
|
|
152
|
+
"hassanat2022": "Hassanat distance",
|
|
153
|
+
"hassanat2014": "Hassanat distance (original preprint)",
|
|
154
|
+
"alaydaa2026": "Signed form of the Hassanat distance (negative values)",
|
|
155
|
+
"lance1966": "Canberra distance",
|
|
156
|
+
"deza2009": "Distance measures (Euclidean, Manhattan, Chebyshev, cosine)",
|
|
157
|
+
"kohavi1995": "Stratified k-fold cross-validation",
|
|
158
|
+
"lachenbruch1968": "Leave-one-out validation",
|
|
159
|
+
"varma2006": "Nested cross-validation",
|
|
160
|
+
"cawley2010": "Selection bias and separate final evaluation",
|
|
161
|
+
"brodersen2010": "Balanced accuracy",
|
|
162
|
+
"matthews1975": "Matthews correlation coefficient (MCC)",
|
|
163
|
+
"cohen1960": "Cohen's kappa",
|
|
164
|
+
"fawcett2006": "ROC curves and ROC AUC",
|
|
165
|
+
"davis2006": "Precision-recall curves",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# --------------------------------------------------------------------------- #
|
|
169
|
+
# Which references go with which choice
|
|
170
|
+
# --------------------------------------------------------------------------- #
|
|
171
|
+
|
|
172
|
+
SOFTWARE = ["sklearn", "numpy", "pandas"]
|
|
173
|
+
|
|
174
|
+
CLASSIFIER_REFERENCES: Dict[str, List[str]] = {
|
|
175
|
+
"decision_tree": ["breiman1984"],
|
|
176
|
+
"random_forest": ["breiman2001"],
|
|
177
|
+
"svm": ["cortes1995", "platt1999"],
|
|
178
|
+
"logistic_regression": ["cox1958"],
|
|
179
|
+
"knn": ["cover1967"],
|
|
180
|
+
"naive_bayes": ["hand2001"],
|
|
181
|
+
"xgboost": ["chen2016"],
|
|
182
|
+
"lightgbm": ["ke2017"],
|
|
183
|
+
"neural_network": ["rumelhart1986", "kingma2015"],
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
DISTANCE_REFERENCES: Dict[str, List[str]] = {
|
|
187
|
+
"hassanat": ["hassanat2022", "hassanat2014"],
|
|
188
|
+
"euclidean": ["deza2009"],
|
|
189
|
+
"manhattan": ["deza2009"],
|
|
190
|
+
"chebyshev": ["deza2009"],
|
|
191
|
+
"canberra": ["lance1966"],
|
|
192
|
+
"cosine": ["deza2009"],
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Cited in addition when the signed form of the Hassanat formula was applied
|
|
196
|
+
# (some values negative).
|
|
197
|
+
HASSANAT_SIGNED_REFERENCES = ["alaydaa2026"]
|
|
198
|
+
|
|
199
|
+
VALIDATION_REFERENCES: Dict[str, List[str]] = {
|
|
200
|
+
"holdout": [],
|
|
201
|
+
"kfold5": ["kohavi1995"],
|
|
202
|
+
"kfold10": ["kohavi1995"],
|
|
203
|
+
"stratified": ["kohavi1995"],
|
|
204
|
+
"loo": ["lachenbruch1968"],
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
SELECTION_REFERENCES: Dict[str, List[str]] = {
|
|
208
|
+
"nested": ["varma2006", "cawley2010"],
|
|
209
|
+
"final_test": ["cawley2010"],
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Measures that need a source (accuracy, precision, recall, F1, specificity
|
|
213
|
+
# and log loss are standard definitions).
|
|
214
|
+
METRIC_REFERENCES: Dict[str, List[str]] = {
|
|
215
|
+
"balanced_accuracy": ["brodersen2010"],
|
|
216
|
+
"mcc": ["matthews1975"],
|
|
217
|
+
"cohen_kappa": ["cohen1960"],
|
|
218
|
+
"roc_auc": ["fawcett2006"],
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
FIGURE_REFERENCES: Dict[str, List[str]] = {
|
|
222
|
+
"roc_curve": ["fawcett2006"],
|
|
223
|
+
"pr_curve": ["davis2006"],
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# --------------------------------------------------------------------------- #
|
|
227
|
+
# Helpers
|
|
228
|
+
# --------------------------------------------------------------------------- #
|
|
229
|
+
|
|
230
|
+
_CITE = re.compile(r"\\cite\{([^}]*)\}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def cite(keys: Iterable[str]) -> str:
|
|
234
|
+
"""`` \\cite{a,b}`` for the given keys (in order, without repeats), or an
|
|
235
|
+
empty string when there are none."""
|
|
236
|
+
keys = list(dict.fromkeys(k for k in keys if k))
|
|
237
|
+
return f" \\cite{{{','.join(keys)}}}" if keys else ""
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def cited_keys(text: str) -> List[str]:
|
|
241
|
+
"""Every key cited in a LaTeX text, in order of first citation."""
|
|
242
|
+
keys: List[str] = []
|
|
243
|
+
for group in _CITE.findall(text):
|
|
244
|
+
for k in group.split(","):
|
|
245
|
+
k = k.strip()
|
|
246
|
+
if k and k not in keys:
|
|
247
|
+
keys.append(k)
|
|
248
|
+
return keys
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def lookup(key: str, extra: Optional[Dict[str, str]] = None) -> str:
|
|
252
|
+
if extra and key in extra:
|
|
253
|
+
return extra[key]
|
|
254
|
+
if key in REFERENCES:
|
|
255
|
+
return REFERENCES[key]
|
|
256
|
+
raise KeyError(f"no reference text for \\cite{{{key}}}")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def bibliography(text: str, extra: Optional[Dict[str, str]] = None,
|
|
260
|
+
escape=lambda s: s) -> str:
|
|
261
|
+
"""The ``thebibliography`` block for every key cited in ``text``, in
|
|
262
|
+
order of first citation. ``extra`` holds report-specific entries (the
|
|
263
|
+
software itself, the dataset). A cited key without a text raises an
|
|
264
|
+
error, so a report can never cite a reference it does not list."""
|
|
265
|
+
keys = cited_keys(text)
|
|
266
|
+
lines = ["\\begin{thebibliography}{99}\n"]
|
|
267
|
+
lines += [f"\\bibitem{{{k}}} {escape(lookup(k, extra))}\n" for k in keys]
|
|
268
|
+
lines.append("\\end{thebibliography}\n")
|
|
269
|
+
return "".join(lines)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def citation_lines(text: str, extra: Optional[Dict[str, str]] = None,
|
|
273
|
+
topics: Optional[Dict[str, str]] = None) -> List[str]:
|
|
274
|
+
"""``citations.txt``: every reference cited in the report, with what it
|
|
275
|
+
is cited for."""
|
|
276
|
+
topics = dict(TOPICS, **(topics or {}))
|
|
277
|
+
lines = ["Please cite the following if you publish these results.",
|
|
278
|
+
"They are the references of the report (report.tex), in the "
|
|
279
|
+
"order they are cited there.", ""]
|
|
280
|
+
for i, k in enumerate(cited_keys(text), 1):
|
|
281
|
+
what = topics.get(k)
|
|
282
|
+
lines.append(f"[{i}] " + (f"{what}:" if what else ""))
|
|
283
|
+
lines.append(" " + lookup(k, extra))
|
|
284
|
+
return lines
|
|
@@ -29,7 +29,9 @@ from .diagnostics import (
|
|
|
29
29
|
learning_curve_data,
|
|
30
30
|
)
|
|
31
31
|
from .importance import honest_importance
|
|
32
|
-
from .latex_report import ReportContext,
|
|
32
|
+
from .latex_report import (ReportContext, build_tex, report_entries,
|
|
33
|
+
write_and_compile)
|
|
34
|
+
from .references import citation_lines
|
|
33
35
|
from .help_texts import explain
|
|
34
36
|
from .logbook import LogBook
|
|
35
37
|
from .models import build_registry
|
|
@@ -1076,14 +1078,22 @@ class Wizard:
|
|
|
1076
1078
|
saved.append(rep.save_model(
|
|
1077
1079
|
model, os.path.join(out_dir, "trained_model.pkl")))
|
|
1078
1080
|
|
|
1081
|
+
# citations.txt lists exactly the references cited in the report.
|
|
1082
|
+
try:
|
|
1083
|
+
ctx = self._report_context(best, results, final, X, y,
|
|
1084
|
+
fig_files, importance)
|
|
1085
|
+
except Exception as exc: # noqa: BLE001
|
|
1086
|
+
ctx = None
|
|
1087
|
+
self.log.add(f"Report context FAILED: {exc}")
|
|
1079
1088
|
saved.append(self.save_citations(os.path.join(out_dir,
|
|
1080
|
-
"citations.txt")))
|
|
1089
|
+
"citations.txt"), ctx))
|
|
1081
1090
|
|
|
1082
1091
|
# Report: report.tex, compiled to report.pdf if LaTeX is installed.
|
|
1083
1092
|
ui.info("Writing the report ...")
|
|
1084
1093
|
try:
|
|
1085
|
-
ctx
|
|
1086
|
-
|
|
1094
|
+
if ctx is None:
|
|
1095
|
+
raise RuntimeError("the report information could not be "
|
|
1096
|
+
"collected (see the log)")
|
|
1087
1097
|
tex_path, pdf_path, msg = write_and_compile(ctx, out_dir)
|
|
1088
1098
|
saved.append(tex_path)
|
|
1089
1099
|
if pdf_path:
|
|
@@ -1212,6 +1222,10 @@ class Wizard:
|
|
|
1212
1222
|
comparison_text=self.comparison_text,
|
|
1213
1223
|
learning_text=self.learning_text,
|
|
1214
1224
|
importance=importance,
|
|
1225
|
+
classifier_keys=list(self._classifiers),
|
|
1226
|
+
knn_distance=self.knn_distance,
|
|
1227
|
+
hassanat_signed=bool(self.hassanat_form
|
|
1228
|
+
and self.hassanat_form["form"] == "signed"),
|
|
1215
1229
|
)
|
|
1216
1230
|
|
|
1217
1231
|
def _pipeline_spec(self, key: str, X):
|
|
@@ -1223,13 +1237,20 @@ class Wizard:
|
|
|
1223
1237
|
factory=lambda: pp.build_pipeline(X, cfg, spec.factory(), scale),
|
|
1224
1238
|
)
|
|
1225
1239
|
|
|
1226
|
-
def save_citations(self, path: str) -> str:
|
|
1227
|
-
|
|
1228
|
-
|
|
1240
|
+
def save_citations(self, path: str, ctx=None) -> str:
|
|
1241
|
+
if ctx is not None:
|
|
1242
|
+
lines = citation_lines(build_tex(ctx), report_entries(ctx),
|
|
1243
|
+
{"easyclassifier": "EasyClassifier "
|
|
1244
|
+
"(this software)"})
|
|
1245
|
+
else:
|
|
1246
|
+
lines = ["Please cite the following if you publish these "
|
|
1247
|
+
"results:", "", "Software:", " " + CITATION]
|
|
1229
1248
|
if self.hassanat_form:
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1249
|
+
if ctx is None:
|
|
1250
|
+
lines += ["", "Hassanat distance (used by KNN):"]
|
|
1251
|
+
lines += [" " + c for c in HASSANAT_CITATIONS]
|
|
1252
|
+
lines += ["", "Hassanat distance, formula form applied: "
|
|
1253
|
+
+ self.hassanat_form["text"]]
|
|
1233
1254
|
with open(path, "w", encoding="utf-8") as fh:
|
|
1234
1255
|
fh.write("\n".join(lines) + "\n")
|
|
1235
1256
|
return path
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easyclassifier
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Machine learning classification without programming - a guided, menu-driven wizard.
|
|
5
5
|
Author-email: Ahmad Hassanat <ahmad.hassanat@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -40,6 +40,7 @@ Dynamic: license-file
|
|
|
40
40
|
[](https://pypi.org/project/easyclassifier/)
|
|
41
41
|
[](https://www.python.org/)
|
|
42
42
|
[](https://github.com/ahmadhassant/easyclassifier/blob/main/LICENSE)
|
|
43
|
+
[](https://doi.org/10.5281/zenodo.23122901)
|
|
43
44
|
|
|
44
45
|
**Machine Learning without Programming.**
|
|
45
46
|
|
|
@@ -48,6 +49,11 @@ evaluate machine learning classification models from a CSV or Excel file —
|
|
|
48
49
|
no Python code, no scripting, and no machine learning jargon required
|
|
49
50
|
(unless you want it).
|
|
50
51
|
|
|
52
|
+
Regression, time-series forecasting, signal classification and a Windows
|
|
53
|
+
desktop application that runs all four tasks are in
|
|
54
|
+
[EasyResearch](https://github.com/ahmadhassant/easyresearch), which is built
|
|
55
|
+
on EasyClassifier and follows the same rules.
|
|
56
|
+
|
|
51
57
|
**New to Python or the terminal? Read the step-by-step
|
|
52
58
|
[User Guide](https://github.com/ahmadhassant/easyclassifier/blob/main/docs/USER_GUIDE.md).** It covers installing Python, installing
|
|
53
59
|
EasyClassifier, running it, what each question means, and what the results
|
|
@@ -222,8 +228,14 @@ The report says whether the normal form (all values >= 0) or the signed form
|
|
|
222
228
|
(negative values present) of the formula was applied, and the citation is
|
|
223
229
|
shown on screen and saved to `citations.txt`:
|
|
224
230
|
|
|
225
|
-
> Hassanat, A. B
|
|
226
|
-
>
|
|
231
|
+
> Hassanat, A. B., Alkafaween, E., Tarawneh, A. S., & Elmougy, S. (2022).
|
|
232
|
+
> Applications review of Hassanat distance metric. In *2022 International
|
|
233
|
+
> Conference on Emerging Trends in Computing and Engineering Applications
|
|
234
|
+
> (ETCEA)*, Karak, Jordan (pp. 1–6). IEEE.
|
|
235
|
+
> https://doi.org/10.1109/ETCEA57049.2022.10009844
|
|
236
|
+
>
|
|
237
|
+
> Hassanat, A. B. (2014). Dimensionality invariant similarity measure.
|
|
238
|
+
> arXiv preprint arXiv:1409.0923. https://arxiv.org/abs/1409.0923
|
|
227
239
|
|
|
228
240
|
## Benchmarks
|
|
229
241
|
|
|
@@ -254,7 +266,7 @@ can see the whole workflow immediately.
|
|
|
254
266
|
## Citing
|
|
255
267
|
|
|
256
268
|
If you use EasyClassifier in published work, please cite it (GitHub's
|
|
257
|
-
"Cite this repository" button uses [`CITATION.cff`](CITATION.cff)); each
|
|
269
|
+
"Cite this repository" button uses [`CITATION.cff`](https://github.com/ahmadhassant/easyclassifier/blob/main/CITATION.cff)); each
|
|
258
270
|
run also writes the exact references to `citations.txt`.
|
|
259
271
|
|
|
260
272
|
## Contributing
|
|
@@ -16,6 +16,7 @@ src/easyclassifier/logbook.py
|
|
|
16
16
|
src/easyclassifier/models.py
|
|
17
17
|
src/easyclassifier/preprocessing.py
|
|
18
18
|
src/easyclassifier/recommend.py
|
|
19
|
+
src/easyclassifier/references.py
|
|
19
20
|
src/easyclassifier/reporting.py
|
|
20
21
|
src/easyclassifier/target.py
|
|
21
22
|
src/easyclassifier/ui.py
|
|
@@ -29,6 +30,7 @@ src/easyclassifier.egg-info/top_level.txt
|
|
|
29
30
|
tests/test_figures.py
|
|
30
31
|
tests/test_first_run.py
|
|
31
32
|
tests/test_leakage.py
|
|
33
|
+
tests/test_references.py
|
|
32
34
|
tests/test_report.py
|
|
33
35
|
tests/test_selection.py
|
|
34
36
|
tests/test_target.py
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Every method the tool can use is cited in the report, and the
|
|
2
|
+
bibliography lists exactly the cited references."""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from easyclassifier import references as refs
|
|
7
|
+
from easyclassifier.distances import DISTANCES
|
|
8
|
+
from easyclassifier.evaluation import METRICS, SELECTION, VALIDATION
|
|
9
|
+
from easyclassifier.latex_report import build_tex
|
|
10
|
+
from easyclassifier.models import build_registry
|
|
11
|
+
|
|
12
|
+
# Measures with a standard definition that need no source.
|
|
13
|
+
STANDARD_MEASURES = {"accuracy", "precision", "recall", "f1", "specificity",
|
|
14
|
+
"log_loss"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_every_choice_has_its_references():
|
|
18
|
+
assert set(build_registry()) <= set(refs.CLASSIFIER_REFERENCES)
|
|
19
|
+
assert all(refs.CLASSIFIER_REFERENCES[k] for k in build_registry())
|
|
20
|
+
assert set(DISTANCES) == set(refs.DISTANCE_REFERENCES)
|
|
21
|
+
assert set(VALIDATION) == set(refs.VALIDATION_REFERENCES)
|
|
22
|
+
assert set(SELECTION) - {"auto"} == set(refs.SELECTION_REFERENCES)
|
|
23
|
+
assert set(METRICS) == set(refs.METRIC_REFERENCES) | STANDARD_MEASURES
|
|
24
|
+
tables = [refs.CLASSIFIER_REFERENCES, refs.DISTANCE_REFERENCES,
|
|
25
|
+
refs.VALIDATION_REFERENCES, refs.SELECTION_REFERENCES,
|
|
26
|
+
refs.METRIC_REFERENCES, refs.FIGURE_REFERENCES]
|
|
27
|
+
keys = {k for t in tables for v in t.values() for k in v} | set(refs.SOFTWARE)
|
|
28
|
+
assert keys <= set(refs.REFERENCES)
|
|
29
|
+
assert set(refs.REFERENCES) <= set(refs.TOPICS)
|
|
30
|
+
assert not any("Journal of American Science" in r
|
|
31
|
+
for r in refs.REFERENCES.values())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_bibliography_lists_exactly_the_cited_references():
|
|
35
|
+
text = r"A \cite{sklearn,breiman2001} B \cite{own} C \cite{breiman2001}."
|
|
36
|
+
bib = refs.bibliography(text, {"own": "Own, A. (2026). Data."})
|
|
37
|
+
assert re.findall(r"\\bibitem\{([^}]*)\}", bib) == \
|
|
38
|
+
["sklearn", "breiman2001", "own"]
|
|
39
|
+
try:
|
|
40
|
+
refs.bibliography(r"\cite{nowhere}")
|
|
41
|
+
except KeyError as exc:
|
|
42
|
+
assert "nowhere" in str(exc)
|
|
43
|
+
else:
|
|
44
|
+
raise AssertionError("an unknown citation must not pass silently")
|
|
45
|
+
lines = refs.citation_lines(text, {"own": "Own, A. (2026). Data."})
|
|
46
|
+
assert sum(line.startswith("[") for line in lines) == 3
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_report_cites_each_classifier_measure_and_evaluation():
|
|
50
|
+
from test_report import _context
|
|
51
|
+
c = _context()
|
|
52
|
+
c.classifiers = ["Decision Tree", "XGBoost", "LightGBM",
|
|
53
|
+
"KNN (Canberra, k=5)"]
|
|
54
|
+
c.classifier_keys = ["decision_tree", "xgboost", "lightgbm", "knn"]
|
|
55
|
+
c.knn_text, c.knn_distance = "K-nearest neighbours used k = 5", "canberra"
|
|
56
|
+
c.metrics = ["accuracy", "balanced_accuracy", "mcc", "cohen_kappa"]
|
|
57
|
+
c.selection = "nested"
|
|
58
|
+
c.final = c.results[0]
|
|
59
|
+
doc = build_tex(c)
|
|
60
|
+
cited = refs.cited_keys(doc.split(r"\begin{thebibliography}")[0])
|
|
61
|
+
for key in ("breiman1984", "chen2016", "ke2017", "cover1967", "lance1966",
|
|
62
|
+
"kohavi1995", "varma2006", "brodersen2010", "matthews1975",
|
|
63
|
+
"cohen1960", "sklearn", "numpy", "pandas"):
|
|
64
|
+
assert key in cited, key
|
|
65
|
+
assert re.findall(r"\\bibitem\{([^}]*)\}", doc) == cited
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_signed_hassanat_form_cites_the_sign_symmetric_paper():
|
|
69
|
+
from test_report import _context
|
|
70
|
+
for signed in (False, True):
|
|
71
|
+
c = _context()
|
|
72
|
+
c.classifiers, c.classifier_keys = ["KNN (Hassanat, k=5)"], ["knn"]
|
|
73
|
+
c.knn_text, c.knn_distance = "K-nearest neighbours used k = 5", "hassanat"
|
|
74
|
+
c.hassanat_used, c.hassanat_signed = True, signed
|
|
75
|
+
cited = refs.cited_keys(build_tex(c))
|
|
76
|
+
assert {"hassanat2022", "hassanat2014"} <= set(cited)
|
|
77
|
+
assert ("alaydaa2026" in cited) == signed
|
|
@@ -108,3 +108,24 @@ def test_report_compiles_to_pdf(tmp_path):
|
|
|
108
108
|
assert pdf_path is not None, msg
|
|
109
109
|
assert (tmp_path / "report.pdf").stat().st_size > 10_000
|
|
110
110
|
assert not (tmp_path / "report.aux").exists() # cleaned up
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_specificity_is_macro_average():
|
|
114
|
+
"""Reported specificity is one-versus-rest specificity averaged over
|
|
115
|
+
classes; with two classes it equals balanced accuracy."""
|
|
116
|
+
import numpy as np
|
|
117
|
+
from sklearn.metrics import balanced_accuracy_score
|
|
118
|
+
from easyclassifier.evaluation import METRICS, _specificity
|
|
119
|
+
from easyclassifier.help_texts import GLOSSARY
|
|
120
|
+
|
|
121
|
+
y_true = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])
|
|
122
|
+
y_pred = np.array([0, 0, 1, 1, 1, 1, 1, 1, 1, 0])
|
|
123
|
+
assert np.isclose(_specificity(y_true, y_pred, [0, 1]),
|
|
124
|
+
balanced_accuracy_score(y_true, y_pred))
|
|
125
|
+
|
|
126
|
+
y3 = np.array([0, 0, 1, 1, 2, 2])
|
|
127
|
+
p3 = np.array([0, 1, 1, 1, 2, 0])
|
|
128
|
+
# one-vs-rest specificities: class 0: 3/4, class 1: 3/4, class 2: 4/4
|
|
129
|
+
assert np.isclose(_specificity(y3, p3, [0, 1, 2]), (0.75 + 0.75 + 1) / 3)
|
|
130
|
+
assert METRICS["specificity"] == "Macro specificity"
|
|
131
|
+
assert "averaged over all classes" in GLOSSARY["specificity"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{easyclassifier-0.8.1 → easyclassifier-0.8.2}/src/easyclassifier.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|