refitgap 0.1.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.
Files changed (40) hide show
  1. refitgap-0.1.0/CITATION.cff +34 -0
  2. refitgap-0.1.0/LICENSE +21 -0
  3. refitgap-0.1.0/MANIFEST.in +6 -0
  4. refitgap-0.1.0/PKG-INFO +128 -0
  5. refitgap-0.1.0/README.txt +95 -0
  6. refitgap-0.1.0/pyproject.toml +58 -0
  7. refitgap-0.1.0/setup.cfg +4 -0
  8. refitgap-0.1.0/src/refitgap/API.txt +150 -0
  9. refitgap-0.1.0/src/refitgap/__init__.py +29 -0
  10. refitgap-0.1.0/src/refitgap/__main__.py +6 -0
  11. refitgap-0.1.0/src/refitgap/analysis.py +399 -0
  12. refitgap-0.1.0/src/refitgap/cli.py +173 -0
  13. refitgap-0.1.0/src/refitgap/crossfit.py +275 -0
  14. refitgap-0.1.0/src/refitgap/example.py +96 -0
  15. refitgap-0.1.0/src/refitgap/fitters.py +114 -0
  16. refitgap-0.1.0/src/refitgap/identification.py +101 -0
  17. refitgap-0.1.0/src/refitgap/inference.py +319 -0
  18. refitgap-0.1.0/src/refitgap/plots.py +214 -0
  19. refitgap-0.1.0/src/refitgap/records.py +38 -0
  20. refitgap-0.1.0/src/refitgap/report.py +136 -0
  21. refitgap-0.1.0/src/refitgap/runs.py +53 -0
  22. refitgap-0.1.0/src/refitgap/tiny.py +68 -0
  23. refitgap-0.1.0/src/refitgap/twoscorer.py +504 -0
  24. refitgap-0.1.0/src/refitgap.egg-info/PKG-INFO +128 -0
  25. refitgap-0.1.0/src/refitgap.egg-info/SOURCES.txt +38 -0
  26. refitgap-0.1.0/src/refitgap.egg-info/dependency_links.txt +1 -0
  27. refitgap-0.1.0/src/refitgap.egg-info/entry_points.txt +2 -0
  28. refitgap-0.1.0/src/refitgap.egg-info/requires.txt +9 -0
  29. refitgap-0.1.0/src/refitgap.egg-info/top_level.txt +1 -0
  30. refitgap-0.1.0/tests/conftest.py +35 -0
  31. refitgap-0.1.0/tests/test_cli_and_report.py +441 -0
  32. refitgap-0.1.0/tests/test_crossfit.py +413 -0
  33. refitgap-0.1.0/tests/test_examples.py +107 -0
  34. refitgap-0.1.0/tests/test_external_pipeline.py +95 -0
  35. refitgap-0.1.0/tests/test_family_at.py +22 -0
  36. refitgap-0.1.0/tests/test_hand_checks.py +334 -0
  37. refitgap-0.1.0/tests/test_inputs_and_inference.py +375 -0
  38. refitgap-0.1.0/tests/test_known_answer.py +157 -0
  39. refitgap-0.1.0/tests/test_slow_examples.py +24 -0
  40. refitgap-0.1.0/tests/test_twoscorer.py +357 -0
@@ -0,0 +1,34 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use refitgap, please cite the paper below and this software."
3
+ title: "refitgap: score-based evaluation of thresholded forgetting in continual multi-label classification"
4
+ type: software
5
+ version: 0.1.0
6
+ license: MIT
7
+ repository-code: "https://github.com/ihshaish/refitgap"
8
+ authors:
9
+ - family-names: Ihshaish
10
+ given-names: Hisham
11
+ email: hisham.ihshaish@uwe.ac.uk
12
+ orcid: "https://orcid.org/0000-0001-5530-4894"
13
+ affiliation: "School of Computing and Creative Technologies, University of the West of England, Bristol, UK; Department of Computer Science, Birzeit University, Birzeit, Palestine"
14
+ keywords:
15
+ - continual learning
16
+ - multi-label classification
17
+ - backward transfer
18
+ - attainable gap
19
+ - evaluation
20
+ preferred-citation:
21
+ type: article
22
+ title: "Decomposing Backward Transfer in Continual Multi-Label Text Classification"
23
+ status: submitted
24
+ notes: "Under review."
25
+ authors:
26
+ - family-names: Ihshaish
27
+ given-names: Hisham
28
+ affiliation: "University of the West of England; Birzeit University"
29
+ - family-names: Mayhew
30
+ given-names: Peter
31
+ affiliation: "University of the West of England; GE Aerospace"
32
+ - family-names: Aydin
33
+ given-names: Mehmet
34
+ affiliation: "University of the West of England"
refitgap-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hisham Ihshaish
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,6 @@
1
+ include README.txt LICENSE CITATION.cff
2
+ recursive-include src/refitgap *.py *.txt
3
+ include tests/*.py
4
+ prune paper
5
+ prune examples
6
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: refitgap
3
+ Version: 0.1.0
4
+ Summary: Split a continual learner's thresholded forgetting into what the scores lost and what the carried rule lost, from stored scores.
5
+ Author-email: Hisham Ihshaish <hisham.ihshaish@uwe.ac.uk>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ihshaish/refitgap
8
+ Project-URL: Repository, https://github.com/ihshaish/refitgap
9
+ Project-URL: Issues, https://github.com/ihshaish/refitgap/issues
10
+ Keywords: continual learning,multi-label classification,backward transfer,thresholds,evaluation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/plain
24
+ License-File: LICENSE
25
+ Requires-Dist: numpy>=1.24
26
+ Requires-Dist: scipy>=1.10
27
+ Provides-Extra: plots
28
+ Requires-Dist: matplotlib>=3.5; extra == "plots"
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=7.0; extra == "test"
31
+ Requires-Dist: scikit-learn>=1.3; extra == "test"
32
+ Dynamic: license-file
33
+
34
+ refitgap
35
+ ========
36
+
37
+ Train a multi-label classifier period by period, score the final model on an old period, and
38
+ its macro-F1 there drops. Part of the drop sits in the scores, which now separate the labels
39
+ less well, and part in the thresholds, carried from period one and now in the wrong place.
40
+ refitgap says how much of each, from stored scores alone:
41
+
42
+ deficit B = attainable gap A + change in mismatch dR
43
+
44
+ The attainable gap compares the two models after each refits its thresholds on equal terms
45
+ (split-half cross-fitting, same partitions); the change in mismatch is what each loses by
46
+ keeping the carried rule. Same trick for two methods: replay's lead over sequential training
47
+ at the inherited thresholds, after both refit, and the change. This is the code of
48
+ "Decomposing Backward Transfer in Continual Multi-Label Text Classification" (Ihshaish,
49
+ Mayhew and Aydin, under review), with its plans, stored results and scripts.
50
+
51
+ Install
52
+ -------
53
+
54
+ pip install refitgap # Python 3.10+, numpy and scipy, nothing else
55
+ pip install "refitgap[plots,test]" # matplotlib for figures, pytest etc. for the tests
56
+
57
+ (or pip install . from the clone). The entry points below read files in this repository, so
58
+ clone it too. On the command line, refitgap evaluate | report | example runs the same
59
+ evaluator on stored arrays (refitgap --help; API.txt, "Command line").
60
+
61
+ Reproduce Figure 2
62
+ ------------------
63
+
64
+ python examples/figure2/reproduce_figure2.py # a few minutes on 4 cores
65
+ python examples/figure2/reproduce_figure2.py --from-levels # about a second
66
+
67
+ All five paired comparisons of Figure 2 (supplement Table C.1) over periods 2 to 7: lead at
68
+ the inherited thresholds, after both refit, and the change. Paired t intervals over six
69
+ DistilBERT pairs (from stored per-pair levels: their 30 score files, about 60 MB, do not ship),
70
+ percentile bootstrap over ten BiGRU pairs (from raw scores in paper/results/, or stored levels
71
+ with --from-levels). Every lead, mean and bound must match examples/figure2/expected.json to
72
+ 1e-10.
73
+
74
+ python examples/two_scorer/table1.py
75
+
76
+ Table 1: two scorers with equal AUROC (6/11), average precision (5/18) and deficit (1/26),
77
+ one all change in mismatch, the other all attainable gap. Exact fractions, no luck involved!
78
+
79
+ Compare your own models
80
+ -----------------------
81
+
82
+ python examples/your_models/compare_your_models.py [DATA_DIR] --a mine --b baseline
83
+
84
+ DATA_DIR holds three kinds of CSV (examples/your_models/data/ is a tiny synthetic set):
85
+
86
+ labels.csv period, record_id, one 0/1 column per label
87
+ scores_<method>_seed<k>.csv period, record_id, one score column per label (the final
88
+ model of <method>, trained under seed k)
89
+ thresholds.csv method, seed, the carried rule (one threshold per label)
90
+
91
+ Out come a's leads over b at the carried rule and after both refit, and the change (refitted
92
+ lead minus carried lead), per seed and with paired t intervals. In Python: refitgap.compare()
93
+ and method_contrast(); API.txt has the lot.
94
+
95
+ Interface
96
+ ---------
97
+
98
+ In: aligned scores and labels, the carried rule, and a seed for the common partitions.
99
+ - Scores: one (records, labels) array per period and model, probabilities in [0, 1]; logits go
100
+ through a sigmoid first, or declare score_scale="other" with thresholds on the same scale.
101
+ Labels: 0/1, same shape.
102
+ - Alignment: rows match by record id, columns by label name. A shape mismatch, a duplicate or
103
+ missing id, a different label set, or different labels for the two models raises. Nothing
104
+ is truncated or dropped quietly.
105
+ - Ties: a score equal to its threshold is a positive. A refitted threshold is an observed
106
+ score; of two equally good cutpoints the higher wins.
107
+ - A label with no positive in a period has F1 0 there and stays in the macro mean.
108
+ - Partitions: partition b of period id j under seed s is the b-th permutation of numpy
109
+ default_rng(7000 + 100*s + j); first half fits, second scores, then the reverse. 200 in the
110
+ paper. Both models of a pair share them, so give both the same seed and period ids. Period
111
+ ids are integers in [0, 100); the paper's periods 2 to 7 are ids 1 to 6.
112
+ - Out: carried level U, refitted level V, deficit, attainable gap and change in mismatch, per
113
+ period and averaged; for two methods, leads per seed with means and 95% intervals. Positive
114
+ means the first-named model scores higher. Numbers only: refitgap recommends nothing, and
115
+ the attainable gap describes the scores, not what the representation kept.
116
+
117
+ Repository layout
118
+ -----------------
119
+
120
+ src/refitgap/ the evaluator; src/refitgap/API.txt is the reference
121
+ examples/ Table 1, Figure 2, your own models, walkthrough.ipynb (seconds, the lot)
122
+ paper/ plans, scripts, stored results, checks; README.txt maps tables to scripts,
123
+ DATA.txt the corpora (public, not redistributed, bar a 3200-record ASRS sample)
124
+ paper/threshold_policy_survey.txt how 34 sampled studies report their thresholds
125
+ tests/ python -m pytest (fast, a minute or two); -m slow adds the full Figure 2
126
+ recomputation from raw scores (minutes)
127
+
128
+ MIT licence. Please cite the paper and the software (CITATION.cff has both). Have fun with it!
@@ -0,0 +1,95 @@
1
+ refitgap
2
+ ========
3
+
4
+ Train a multi-label classifier period by period, score the final model on an old period, and
5
+ its macro-F1 there drops. Part of the drop sits in the scores, which now separate the labels
6
+ less well, and part in the thresholds, carried from period one and now in the wrong place.
7
+ refitgap says how much of each, from stored scores alone:
8
+
9
+ deficit B = attainable gap A + change in mismatch dR
10
+
11
+ The attainable gap compares the two models after each refits its thresholds on equal terms
12
+ (split-half cross-fitting, same partitions); the change in mismatch is what each loses by
13
+ keeping the carried rule. Same trick for two methods: replay's lead over sequential training
14
+ at the inherited thresholds, after both refit, and the change. This is the code of
15
+ "Decomposing Backward Transfer in Continual Multi-Label Text Classification" (Ihshaish,
16
+ Mayhew and Aydin, under review), with its plans, stored results and scripts.
17
+
18
+ Install
19
+ -------
20
+
21
+ pip install refitgap # Python 3.10+, numpy and scipy, nothing else
22
+ pip install "refitgap[plots,test]" # matplotlib for figures, pytest etc. for the tests
23
+
24
+ (or pip install . from the clone). The entry points below read files in this repository, so
25
+ clone it too. On the command line, refitgap evaluate | report | example runs the same
26
+ evaluator on stored arrays (refitgap --help; API.txt, "Command line").
27
+
28
+ Reproduce Figure 2
29
+ ------------------
30
+
31
+ python examples/figure2/reproduce_figure2.py # a few minutes on 4 cores
32
+ python examples/figure2/reproduce_figure2.py --from-levels # about a second
33
+
34
+ All five paired comparisons of Figure 2 (supplement Table C.1) over periods 2 to 7: lead at
35
+ the inherited thresholds, after both refit, and the change. Paired t intervals over six
36
+ DistilBERT pairs (from stored per-pair levels: their 30 score files, about 60 MB, do not ship),
37
+ percentile bootstrap over ten BiGRU pairs (from raw scores in paper/results/, or stored levels
38
+ with --from-levels). Every lead, mean and bound must match examples/figure2/expected.json to
39
+ 1e-10.
40
+
41
+ python examples/two_scorer/table1.py
42
+
43
+ Table 1: two scorers with equal AUROC (6/11), average precision (5/18) and deficit (1/26),
44
+ one all change in mismatch, the other all attainable gap. Exact fractions, no luck involved!
45
+
46
+ Compare your own models
47
+ -----------------------
48
+
49
+ python examples/your_models/compare_your_models.py [DATA_DIR] --a mine --b baseline
50
+
51
+ DATA_DIR holds three kinds of CSV (examples/your_models/data/ is a tiny synthetic set):
52
+
53
+ labels.csv period, record_id, one 0/1 column per label
54
+ scores_<method>_seed<k>.csv period, record_id, one score column per label (the final
55
+ model of <method>, trained under seed k)
56
+ thresholds.csv method, seed, the carried rule (one threshold per label)
57
+
58
+ Out come a's leads over b at the carried rule and after both refit, and the change (refitted
59
+ lead minus carried lead), per seed and with paired t intervals. In Python: refitgap.compare()
60
+ and method_contrast(); API.txt has the lot.
61
+
62
+ Interface
63
+ ---------
64
+
65
+ In: aligned scores and labels, the carried rule, and a seed for the common partitions.
66
+ - Scores: one (records, labels) array per period and model, probabilities in [0, 1]; logits go
67
+ through a sigmoid first, or declare score_scale="other" with thresholds on the same scale.
68
+ Labels: 0/1, same shape.
69
+ - Alignment: rows match by record id, columns by label name. A shape mismatch, a duplicate or
70
+ missing id, a different label set, or different labels for the two models raises. Nothing
71
+ is truncated or dropped quietly.
72
+ - Ties: a score equal to its threshold is a positive. A refitted threshold is an observed
73
+ score; of two equally good cutpoints the higher wins.
74
+ - A label with no positive in a period has F1 0 there and stays in the macro mean.
75
+ - Partitions: partition b of period id j under seed s is the b-th permutation of numpy
76
+ default_rng(7000 + 100*s + j); first half fits, second scores, then the reverse. 200 in the
77
+ paper. Both models of a pair share them, so give both the same seed and period ids. Period
78
+ ids are integers in [0, 100); the paper's periods 2 to 7 are ids 1 to 6.
79
+ - Out: carried level U, refitted level V, deficit, attainable gap and change in mismatch, per
80
+ period and averaged; for two methods, leads per seed with means and 95% intervals. Positive
81
+ means the first-named model scores higher. Numbers only: refitgap recommends nothing, and
82
+ the attainable gap describes the scores, not what the representation kept.
83
+
84
+ Repository layout
85
+ -----------------
86
+
87
+ src/refitgap/ the evaluator; src/refitgap/API.txt is the reference
88
+ examples/ Table 1, Figure 2, your own models, walkthrough.ipynb (seconds, the lot)
89
+ paper/ plans, scripts, stored results, checks; README.txt maps tables to scripts,
90
+ DATA.txt the corpora (public, not redistributed, bar a 3200-record ASRS sample)
91
+ paper/threshold_policy_survey.txt how 34 sampled studies report their thresholds
92
+ tests/ python -m pytest (fast, a minute or two); -m slow adds the full Figure 2
93
+ recomputation from raw scores (minutes)
94
+
95
+ MIT licence. Please cite the paper and the software (CITATION.cff has both). Have fun with it!
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "refitgap"
7
+ dynamic = ["version"]
8
+ description = "Split a continual learner's thresholded forgetting into what the scores lost and what the carried rule lost, from stored scores."
9
+ readme = { file = "README.txt", content-type = "text/plain" }
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Hisham Ihshaish", email = "hisham.ihshaish@uwe.ac.uk" }]
14
+ keywords = ["continual learning", "multi-label classification", "backward transfer", "thresholds", "evaluation"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
26
+ "Topic :: Scientific/Engineering :: Information Analysis",
27
+ ]
28
+ dependencies = ["numpy>=1.24", "scipy>=1.10"]
29
+
30
+ [project.optional-dependencies]
31
+ plots = ["matplotlib>=3.5"]
32
+ test = ["pytest>=7.0", "scikit-learn>=1.3"]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/ihshaish/refitgap"
36
+ Repository = "https://github.com/ihshaish/refitgap"
37
+ Issues = "https://github.com/ihshaish/refitgap/issues"
38
+
39
+ [project.scripts]
40
+ refitgap = "refitgap.cli:main"
41
+
42
+ [tool.setuptools]
43
+ package-dir = { "" = "src" }
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["src"]
47
+
48
+ [tool.setuptools.package-data]
49
+ refitgap = ["*.txt"]
50
+
51
+ [tool.setuptools.dynamic]
52
+ version = { attr = "refitgap.__version__" }
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ pythonpath = ["src"]
57
+ addopts = "-p no:cacheprovider -m 'not slow'"
58
+ markers = ["slow: recomputes stored paper numbers from score files (minutes to hours; run with -m slow)"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,150 @@
1
+ refitgap API
2
+ ============
3
+
4
+ Stored per-period scores and labels in; cross-fitted macro-F1 levels, the split of a
5
+ thresholded deficit into attainable gap and change in mismatch, and seed-paired contrasts
6
+ out. numpy and scipy only; figures want matplotlib (pip install "refitgap[plots]").
7
+
8
+ The estimator
9
+ -------------
10
+
11
+ For period id j and seed s, partition b is the b-th permutation of
12
+ numpy.random.default_rng(7000 + 100*s + j); half 0 is the first n_j // 2 records. A refit
13
+ picks, per label, the F1-maximising cutpoint on one half and is scored on the other, both
14
+ ways round; a level is the mean over the partitions (200 in the paper). With U a model's
15
+ macro-F1 at the carried rule theta_1 and V its refitted (attainable) level,
16
+
17
+ B = U_ref - U_final = (V_ref - V_final) + (R_final - R_ref) = A + dR, R = V - U
18
+
19
+ U is taken on the whole period by default (U_full), or on the held-out halves (U_half).
20
+ For one rule shared across periods, R = C + M with C = V_sep - V_shared and
21
+ M = V_shared - U, exact per partition. A score equal to its threshold is a positive; a label
22
+ with no positive and no prediction has F1 0 and stays in the macro mean. Period ids are ints
23
+ in [0, 100) and seeds non-negative ints, so no two (seed, period) pairs share partitions.
24
+
25
+ Outcome rules
26
+ -------------
27
+
28
+ A contrast is a per-seed vector (method a minus method b, paired by seed). At margin 0.005:
29
+ - equivalent: TOST p < 0.05 (Holm-adjusted within a family);
30
+ - material: t p < 0.05 and |mean| >= margin;
31
+ - small non-zero: t p < 0.05 and |mean| < margin;
32
+ - unresolved: anything else, or a material / small non-zero call that the sign-flip test
33
+ (exact up to 20 seeds, Monte Carlo above) does not back;
34
+ - non-inferior / inferior (family_ni): one-sided t and sign-flip tests at -margin.
35
+ Intervals are marginal 95 % t intervals over seeds. Rows say "descriptive" unless you name
36
+ a family fixed in advance (--family NAME); the class words then describe your numbers under
37
+ the paper's rules, nothing grander.
38
+
39
+ Functions
40
+ ---------
41
+
42
+ compare(scores_ref, scores_final, labels, rule, *, seed, splits=200, record_ids=None,
43
+ label_ids=None, period_ids=None, labels_final=None, rule_name="carried", halves=False,
44
+ score_scale=None, fixed_split=None, ...)
45
+ Two scorers on the same labelled records, (n, K) each (lists for several periods), and
46
+ the carried rule `rule`, one threshold per label -> dict(carried, refitted, components,
47
+ per_period, metadata, decomposition, evaluations). rule_name is the name the vector goes by in the
48
+ returned Evaluations (evaluations["ref"], evaluations["final"]), which is what
49
+ method_contrast takes. labels_final: the labels exported with the final scorer, checked
50
+ cell by cell against `labels`. Mismatched shapes, ids or labels raise; scores or rule
51
+ outside [0, 1] raise unless score_scale="other".
52
+ method_contrast(evals_a, evals_b, rule, *, periods=None, same_rule=True, names=("a", "b"), ...)
53
+ {seed: Evaluation} for two methods, and `rule`, the name of a rule held by every
54
+ Evaluation -> terms lead_at_carried_rule (G_U), lead_after_refit (G_V) and change
55
+ (G_V - G_U, the change after refitting, as in the paper), each with per-seed values,
56
+ mean, t interval and class. "placement" is G_U - G_V, the negative of change, kept for
57
+ compatibility. same_rule=False lets each method carry its own vector under that name.
58
+ evaluate(Ps, Ys, seed, period_ids=None, rules=None, shared_periods=None, splits=200, ...)
59
+ One model's per-period scores and labels, and rules={name: (K,) vector} -> Evaluation
60
+ (levels per partition, both directions, every period); .summary() and .per_period()
61
+ give the means.
62
+ decompose(ref, final, rule, periods=None, halves=False, same_rule=True)
63
+ Two Evaluations on the same records and a rule name -> B, A, dR (and dC, dM) per period
64
+ and mean. B and dR are also returned as D and R, their earlier names.
65
+
66
+ Two methods from Python, end to end (synthetic scores, two periods, five seeds):
67
+
68
+ import numpy as np, refitgap as rg
69
+ rng = np.random.default_rng(0); Y = [(rng.random((300, 3)) < 0.3).astype(int) for _ in range(2)]
70
+ ev = lambda sep: {s: rg.evaluate([1 / (1 + np.exp(1 - sep * y - rng.normal(size=y.shape))) for y in Y], Y, s,
71
+ rules={"carried": np.full(3, 0.5)}) for s in range(5)}
72
+ mc = rg.method_contrast(ev(2.5), ev(1.5), "carried", names=("mine", "baseline"))
73
+ print({t: round(mc["terms"][t]["mean"], 4) for t in ("lead_at_carried_rule", "lead_after_refit", "change")})
74
+
75
+ prints {'lead_at_carried_rule': 0.1484, 'lead_after_refit': 0.1903, 'change': 0.0419}.
76
+ fit_exact(P, Y), fit_grid(P, Y), fit_shared_exact(Ps, Ys, w=None), fit_validation_rule(Pval, Yval)
77
+ Scores and labels -> (K,) thresholds: best cutpoint per label; the 19-point grid
78
+ 0.05..0.95 (how theta_1 was fitted); one vector for several periods; the grid on a
79
+ validation split.
80
+ f1_vec(P, Y, theta), macro_f1(P, Y, theta)
81
+ Per-label F1 and its mean at theta.
82
+ partitions(n, seed, j, splits=200)
83
+ The index permutations behind every level.
84
+ paired_stats(x), paired_contrast(a, b), family(members), family_ni(members), holm(ps)
85
+ Per-seed vectors (or {seed: value} dicts) -> mean, intervals, t / TOST / sign-flip p and
86
+ classes, as above.
87
+ analyse(runs_a, runs_b=None, rule="inherited", mean_over=None, u_convention="full", ...)
88
+ Runs (make_run, run_from_npz, run_from_arrays) -> levels, decomposition and contrast
89
+ tables; write_tables(result, dir) writes levels.csv, decomposition.csv, contrasts.csv and
90
+ result.json; plots.save_figures and report.write_report draw from the same tables.
91
+ make_run(final, labels, seed, rules, reference=None, ...), save_arrays(path, run)
92
+ A run from arrays; saved as an arrays file (scores_<j>, labels_<j>, optional
93
+ reference_<j>, rule_<name>, seed, label_names).
94
+ load_run(path), evaluate_npz(path, seed, ...)
95
+ Read or evaluate a stored run file (P_<i>_<j>: model i on period j; Y_<j>; thresholds).
96
+ align_to(reference_ids, ids, *arrays), drop_unlabelled(ids, Y, *arrays)
97
+ Reorder rows to a reference id order (missing or duplicate ids raise); drop records with
98
+ a missing (NaN) label.
99
+ identification_example()
100
+ The fourteen-record case in exact fractions: equal AUROC (6/11) and average precision
101
+ (5/18), equal deficits (1/26), one all mismatch, one all attainable gap.
102
+ f1_bounds_from_counts(tp, fp, fn, tn)
103
+ Sharp range of the best single-threshold F1 behind one confusion table
104
+ (40, 10, 10, 40 gives [0.8, 10/11]). A bound, not a confidence interval.
105
+
106
+ Command line
107
+ ------------
108
+
109
+ refitgap evaluate --npz run_s0.npz run_s1.npz --seeds 0 1 [--npz-b ...] --out DIR
110
+ refitgap evaluate --arrays a_s0.npz ... [--arrays-b b_s0.npz ...] --out DIR
111
+ refitgap report (same inputs) --out DIR # adds three figures and report.html
112
+ refitgap example [--out DIR] # the tiny example below
113
+ refitgap --version
114
+
115
+ Settings: --periods, --mean-over, --shared-periods, --shared-weights, --splits (200),
116
+ --rule (inherited), --validation, --u-convention full|half, --no-reference, --margin
117
+ (0.005), --family NAME, --names, --workers. Two methods are paired by seed; stored label
118
+ names must agree. levels.csv: V_sep, V_shared, C, U_half, U_full, R, M per method, seed,
119
+ model, period and rule. decomposition.csv: B, A, dR per period against the per-period
120
+ reference. contrasts.csv: paired leads with per-seed values, mean, interval and p.
121
+
122
+ Tiny example
123
+ ------------
124
+
125
+ Two periods of four records, two labels (a, b), seed 0, one partition, inherited thresholds 0.5.
126
+ The reference of period 1 is the final model itself.
127
+
128
+ period record labels a,b final a,b reference a,b
129
+ 0 r0 1,0 0.4,0.6 0.9,0.3
130
+ 0 r1 1,0 0.4,0.2 0.9,0.3
131
+ 0 r2 0,1 0.1,0.8 0.2,0.8
132
+ 0 r3 0,1 0.1,0.3 0.2,0.8
133
+ 1 r0, r2 1,0 0.9,0.3 same as final
134
+ 1 r1, r3 0,1 0.2,0.8 same as final
135
+
136
+ default_rng(7000).permutation(4) = [1, 2, 0, 3] and default_rng(7001) gives [3, 0, 1, 2],
137
+ so period 0 splits {r1, r2} | {r0, r3} and period 1 {r3, r0} | {r1, r2}. The reference
138
+ separates everything: V = U = 1. The final model on period 0 reaches V_sep = V_shared = 3/4
139
+ and U = 1/4 at 0.5, so C = 0 and M = R = 1/2; on period 1 it is the reference, V_sep = U = 1
140
+ and V_shared = 11/12. Period 0 thus splits B = 3/4 into A = 1/4 (all label b: positive r3 now
141
+ scores below negative r0) and dR = 1/2 (all label a: still separated, but below 0.5). The
142
+ tests check every printed value against these fractions to 1e-12.
143
+
144
+ Limits
145
+ ------
146
+
147
+ A, C, M and R describe what thresholded predictions from the stored scores can reach: a
148
+ large A says the scores lost separation, not that the representation lost the information.
149
+ Every level survives any strictly increasing per-label map of scores and rules. The package
150
+ recommends nothing; retrain-or-recalibrate needs costs the scores do not carry.
@@ -0,0 +1,29 @@
1
+ """refitgap: thresholded forgetting, split into what the scores lost and what the thresholds lost.
2
+
3
+ Stored per-period scores and labels in; cross-fitted levels, the split
4
+ deficit = attainable gap + change in mismatch, and seed-paired contrasts out.
5
+ compare() handles two scorers on the same records, method_contrast() the paired lead
6
+ of one method over another. API.txt in this directory has the details. numpy and scipy
7
+ only; figures need matplotlib (extra "plots"). Command line: refitgap.
8
+ """
9
+ from .fitters import GRID, f1_vec, fit_exact, fit_grid, fit_shared_exact, macro_f1, shared_objective
10
+ from .crossfit import (SPLITS, SPLIT_BASE, Evaluation, array_hash, decompose, evaluate, fit_validation_rule,
11
+ partitions)
12
+ from .inference import (ALPHA, MARGIN, classify, classify_unadjusted, family, family_ni, holm, paired_contrast,
13
+ paired_stats, paired_vector, dropped_seeds)
14
+ from .records import align_to, drop_unlabelled
15
+ from .runs import evaluate_npz, load_run
16
+ from .analysis import analyse, make_run, run_from_arrays, run_from_npz, save_arrays, write_tables
17
+ from .twoscorer import compare, method_contrast
18
+ from .identification import f1_bounds_from_counts, identification_example
19
+
20
+ __version__ = "0.1.0"
21
+
22
+ __all__ = [
23
+ "GRID", "f1_vec", "fit_exact", "fit_grid", "fit_shared_exact", "macro_f1", "shared_objective", "SPLITS",
24
+ "SPLIT_BASE", "Evaluation", "array_hash", "decompose", "evaluate", "fit_validation_rule", "partitions",
25
+ "ALPHA", "MARGIN", "classify", "classify_unadjusted", "family", "family_ni", "holm", "paired_contrast",
26
+ "paired_stats", "paired_vector", "dropped_seeds", "align_to", "drop_unlabelled", "evaluate_npz",
27
+ "load_run", "analyse", "make_run", "run_from_arrays", "run_from_npz", "save_arrays", "write_tables",
28
+ "compare", "method_contrast", "f1_bounds_from_counts", "identification_example", "__version__",
29
+ ]
@@ -0,0 +1,6 @@
1
+ """python -m refitgap: the command line (see cli.py)."""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ sys.exit(main())