Summary
Four statistical functions from scikit-learn and scipy can be implemented in numpy, which is already a core dependency. Doing so removes scikit-learn from the project entirely and reduces docsplit to pandas alone.
I verified all four numerically before filing, so this is a scoped refactor rather than a research task.
The four functions
| function |
source |
used by |
roc_auc_score |
sklearn.metrics |
AUROCMetric (confidence/metrics.py:102) |
rand_score |
sklearn.metrics |
packet_evaluation_metrics.py:135 |
homogeneity_completeness_v_measure |
sklearn.metrics |
packet_evaluation_metrics.py:134 |
kendalltau |
scipy.stats |
packet_evaluation_metrics.py:167 |
That is the entire scikit-learn and scipy surface outside comparators/semantic.py (which uses scipy.spatial and stays behind the semantic extra).
All four are rank or contingency-table statistics with closed forms. AUROC is the normalized Mann-Whitney U statistic; Rand index and V-measure are contingency-table sums; Kendall tau is a concordant/discordant pair count.
Verified equivalence
numpy implementations compared against the reference implementations:
| function |
cases |
max absolute difference |
rand_score |
1500 random clusterings |
0.0 (bitwise identical) |
homogeneity_completeness_v_measure |
1500 random clusterings |
7.2e-16 |
kendalltau |
1500 random rankings |
1.1e-16 |
roc_auc_score |
4420 (random, heavy ties, all-equal scores, perfect and inverted separation) |
1.1e-16 |
The nonzero differences are floating-point summation order, at most 3 ULPs. Rounded to 12 decimals, AUROC was identical in 1960/1960 cases, which is far beyond any precision these metrics are reported at.
Tie handling is the only subtle part in AUROC and Kendall tau, which is why the AUROC sweep specifically included score vectors with only three distinct values, and all-identical score vectors.
Payoff
|
today |
after |
scikit-learn in the project |
core dependency |
removed |
scipy |
core (via sklearn) + semantic |
semantic only |
docsplit extra |
pandas + scipy + scikit-learn |
pandas only |
| install size |
~217 MB with docsplit |
~87 MB |
It also removes the reason the scikit-learn floor was relaxed to >=1.7.2 (1.8.0 requires Python 3.11+, which conflicted with the 3.10 floor from #201). With sklearn gone, that constraint disappears rather than being worked around.
Approach
- Add the four numpy implementations, probably in a small internal module (
stickler/utils/statistics.py or similar) so both confidence/metrics.py and doc_split/ use one copy.
- Keep the reference implementations as test oracles. Where
scikit-learn/scipy are installed (the dev group), assert the numpy version matches to a tight tolerance across randomized inputs including ties and degenerate cases. That is how the equivalence above was established, and it makes a strong regression guard.
- Remove
scikit-learn from [project.dependencies] and from the docsplit extra.
pandas stays for docsplit: it is used for real input handling (_to_dataframe, CSV reading, groupby), not one function, so replacing it is a genuine refactor and not worth it here.
Notes and caveats
- The Kendall tau implementation I validated is O(n²) pairwise. That is fine for pages within a document group, but a merge-sort variant is needed if it ever sees large inputs. Worth deciding deliberately rather than by accident.
- Degenerate cases already handled by the current code must keep working: AUROC returns
{"value": None} for a single-class input, and kendalltau returning NaN is coerced to 0 at packet_evaluation_metrics.py:170.
- This changes metric implementations, so it wants its own PR and its own review rather than riding along with a packaging change.
Sequencing
This follows the decision to make confidence metrics core (see #207): scikit-learn returns to [project.dependencies] now so calibration metrics work on a bare pip install stickler-eval. This issue then removes that dependency by implementing the maths directly, and the same work lets document-packet evaluation move into the core with only pandas behind an extra.
Related: #201 (dependency slimming), #207 (the PR that scoped the extras).
Summary
Four statistical functions from
scikit-learnandscipycan be implemented innumpy, which is already a core dependency. Doing so removesscikit-learnfrom the project entirely and reducesdocsplittopandasalone.I verified all four numerically before filing, so this is a scoped refactor rather than a research task.
The four functions
roc_auc_scoresklearn.metricsAUROCMetric(confidence/metrics.py:102)rand_scoresklearn.metricspacket_evaluation_metrics.py:135homogeneity_completeness_v_measuresklearn.metricspacket_evaluation_metrics.py:134kendalltauscipy.statspacket_evaluation_metrics.py:167That is the entire
scikit-learnandscipysurface outsidecomparators/semantic.py(which usesscipy.spatialand stays behind thesemanticextra).All four are rank or contingency-table statistics with closed forms. AUROC is the normalized Mann-Whitney U statistic; Rand index and V-measure are contingency-table sums; Kendall tau is a concordant/discordant pair count.
Verified equivalence
numpy implementations compared against the reference implementations:
rand_scorehomogeneity_completeness_v_measurekendalltauroc_auc_scoreThe nonzero differences are floating-point summation order, at most 3 ULPs. Rounded to 12 decimals, AUROC was identical in 1960/1960 cases, which is far beyond any precision these metrics are reported at.
Tie handling is the only subtle part in AUROC and Kendall tau, which is why the AUROC sweep specifically included score vectors with only three distinct values, and all-identical score vectors.
Payoff
scikit-learnin the projectscipysemanticsemanticonlydocsplitextraIt also removes the reason the
scikit-learnfloor was relaxed to>=1.7.2(1.8.0 requires Python 3.11+, which conflicted with the 3.10 floor from #201). With sklearn gone, that constraint disappears rather than being worked around.Approach
stickler/utils/statistics.pyor similar) so bothconfidence/metrics.pyanddoc_split/use one copy.scikit-learn/scipyare installed (the dev group), assert the numpy version matches to a tight tolerance across randomized inputs including ties and degenerate cases. That is how the equivalence above was established, and it makes a strong regression guard.scikit-learnfrom[project.dependencies]and from thedocsplitextra.pandasstays fordocsplit: it is used for real input handling (_to_dataframe, CSV reading, groupby), not one function, so replacing it is a genuine refactor and not worth it here.Notes and caveats
{"value": None}for a single-class input, andkendalltaureturningNaNis coerced to0atpacket_evaluation_metrics.py:170.Sequencing
This follows the decision to make confidence metrics core (see #207):
scikit-learnreturns to[project.dependencies]now so calibration metrics work on a barepip install stickler-eval. This issue then removes that dependency by implementing the maths directly, and the same work lets document-packet evaluation move into the core with onlypandasbehind an extra.Related: #201 (dependency slimming), #207 (the PR that scoped the extras).