Ports the "hard" histogram variant of HistNetQ (from
https://github.com/pglez84/histnetq) into quapy/method/_histnet.py, dropping
that repo's quantificationlib-backed bag generators in favor of QuaPy's own
sampling protocols (UPP by default). Implemented as a BaseQuantifier,
alongside QuaNet, since it trains end-to-end on samples of known prevalence
rather than following the classify-then-aggregate pattern.
- HistNetQ.fit(X, y): resamples training/validation bags from a
LabelledCollection via a configurable protocol (UPP by default; fresh
random bags each training epoch, a fixed reproducible sequence for
validation).
- HistNetQ.fit_from_samples(protocol, val_protocol=None, mix_bags=False):
trains directly from a protocol that already yields bags (e.g. LeQua's
SamplesFromDir), with an optional mixer to synthesize extra
intermediate-prevalence bags from the given ones.
- Aliased in meta.py (torch-optional, mirroring the existing QuaNet guard)
and registered in META_METHODS.
- Adds test_histnetq covering both entry points on binary and multiclass
synthetic data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not having jax/numpyro/pystan installed is the expected, common case
for a plain `pip install quapy`; warning about it every import just
nags users who never asked for the bayes extra. Kept at debug level
so it's still available to diagnose a broken/partial bayes install.
stan.plugins.get_plugins() calls pkg_resources.iter_entry_points()
from several call sites during model building, each re-emitting
setuptools' deprecation notice since Python's default warning dedup
is keyed per call site, not per message. Not actionable upstream
noise, so filter it by message instead.
Correctness:
- OneVsAllAggregative.aggregation_fit: fix undefined variable and call to
the nonexistent aggregate_fit (should be aggregation_fit)
- solve_adjustment: stop mutating the caller's fitted arrays in place for
method='invariant-ratio'
- LabelledCollection.join(): fix classes always being None due to
ndarray.sort() returning None; now unions each collection's own classes_
so a class absent from a particular join is kept at zero prevalence
- Rename the duplicate newSVMKLD (nkld variant) to newSVMNKLD so both loss
variants are reachable
- EMQ/DyS/DMy: resolve n_jobs via qp._get_njobs() like the other methods,
so qp.environ['N_JOBS'] is respected
- AggregativeMedianEstimator: drop backend='threading' (global np.random
state mutated via temp_seed is not thread-safe); use the safe process
based default instead
- NeuralClassifier: default device now 'cpu', matching its own docstring
- ConfidenceEllipseSimplex: narrow bare except to np.linalg.LinAlgError
- SVMperf: stop merging stderr into stdout so failures report the actual
subprocess error instead of crashing with AttributeError
- ConfidenceRegionABC: replace @lru_cache on bound methods (leaked every
instance for the process lifetime) with per-instance caching
Style/quality:
- Replace print() with warnings.warn()/logging across aggregative.py,
base.py, meta.py, model_selection.py, classification/neural.py,
method/_neural.py, classification/svmperf.py, data/reader.py,
data/datasets.py; also fixes a `raise RuntimeWarning(...)` in EMQ that
would have crashed instead of warning
- Remove dead duplicate class MedianEstimator2 in meta.py
- Rename misleading _compute_tpr(TP, FP) parameter to FN, matching what
callers actually pass
- Replace argparse.ArgumentError misuse with ValueError
- Remove commented-out dead code in protocol.py
- _lequa.py: fix CSV-parse failure raising an unrelated UnboundLocalError
instead of a clear ValueError
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>