diff --git a/docs/build/html/_modules/quapy/method/aggregative.html b/docs/build/html/_modules/quapy/method/aggregative.html
index 09ac4fa..abfac5c 100644
--- a/docs/build/html/_modules/quapy/method/aggregative.html
+++ b/docs/build/html/_modules/quapy/method/aggregative.html
@@ -388,6 +388,7 @@
from quapy.classification.svmperf import SVMperf
from quapy.data import LabelledCollection
from quapy.method.base import BaseQuantifier, BinaryQuantifier, OneVsAllGeneric
+from quapy.method._energy import _EnergyDistanceCore
from quapy.method._helper import (
_get_abstention_calibrators,
_get_cvxpy,
@@ -2279,6 +2280,115 @@
+
+
[docs]
+
class EDy(_EnergyDistanceCore, AggregativeSoftQuantifier):
+
"""
+
Energy Distance y (EDy), a posterior-space distribution-matching quantifier
+
based on energy distance.
+
+
The method represents each class by the posterior-probability vectors
+
produced by a probabilistic classifier on validation data, and estimates the
+
test prevalence vector by matching the test posterior distribution against
+
the class-conditional validation distributions through an energy-distance
+
objective solved as a quadratic program. The method is therefore another
+
instance of the general mixture-matching view of quantification, but it
+
operates directly on posterior vectors rather than on histogram summaries.
+
+
This implementation works for binary and multiclass single-label
+
quantification and relies on the optional ``quadprog`` dependency. It was
+
adapted to QuaPy's current aggregative API from the original implementation
+
available in `quantificationlib <https://github.com/AICGijon/quantificationlib>`_,
+
and now shares its numerical core with the classifier-free
+
:class:`quapy.method.non_aggregative.EDx` variant.
+
+
The current implementation follows the energy-distance formulation discussed
+
in:
+
+
* Alberto Castaño, Laura Morán-Fernández, Jaime Alonso,
+
Verónica Bolón-Canedo, Amparo Alonso-Betanzos, and Juan José del Coz.
+
*An analysis of quantification methods based on matching distributions*.
+
* Hideko Kawakubo, Marthinus Christoffel du Plessis, and Masashi Sugiyama
+
(2016). *Computationally efficient class-prior estimation under class
+
balance change using energy distance*. IEICE Transactions on Information
+
and Systems, 99(1):176-186.
+
+
:param classifier: a scikit-learn ``BaseEstimator``, or ``None`` to use
+
``qp.environ['DEFAULT_CLS']``
+
:param fit_classifier: whether to train the learner (default ``True``).
+
Set to ``False`` if the learner has already been trained outside the
+
quantifier
+
:param val_split: specification of the data used for generating validation
+
posterior probabilities. This can be an integer (default ``5``) for
+
k-fold cross-validation, a float in ``(0, 1)`` for a held-out split,
+
or a tuple ``(X, y)`` with explicit validation data
+
:param distance: distance used to compare posterior vectors. Valid string
+
aliases are ``'manhattan'`` (default) and ``'euclidean'``; a custom
+
callable compatible with pairwise-distance signatures can also be used
+
:param n_jobs: number of parallel workers (default ``None``, meaning the
+
value is taken from the environment)
+
"""
+
+
def __init__(
+
self,
+
classifier: BaseEstimator = None,
+
fit_classifier: bool = True,
+
val_split=5,
+
distance: Union[str, Callable] = 'manhattan',
+
n_jobs=None,
+
):
+
super().__init__(classifier, fit_classifier, val_split)
+
self.distance = distance
+
self.n_jobs = qp._get_njobs(n_jobs)
+
self.train_n_cls_i_ = None
+
self.train_distrib_ = None
+
self.K_ = None
+
self.G_ = None
+
self.C_ = None
+
self.b_ = None
+
self.a_ = None
+
+
def _check_init_parameters(self):
+
self._check_ed_init_parameters()
+
+
+
[docs]
+
def aggregation_fit(self, classif_predictions, labels):
+
"""
+
Estimate the class-conditional posterior distributions on validation
+
data and pre-compute the quadratic-program parameters that depend only
+
on the training side.
+
+
In EDy, the validation posteriors are not discretized into histograms.
+
Instead, each class is represented by the cloud of posterior vectors
+
observed for that class, and these clouds are then compared through the
+
selected pairwise distance.
+
+
:param classif_predictions: posterior probabilities returned by the
+
classifier on validation data
+
:param labels: true labels associated to each posterior vector
+
"""
+
posteriors = np.asarray(classif_predictions, dtype=float)
+
labels = np.asarray(labels)
+
train_distrib = [posteriors[labels == class_] for class_ in self.classes_]
+
return self._fit_energy_model(train_distrib)
+
+
+
+
[docs]
+
def aggregate(self, posteriors: np.ndarray):
+
"""Estimate the prevalence vector for a test sample.
+
+
:param posteriors: posterior probabilities returned by the classifier
+
for the instances in the test sample
+
:return: a prevalence vector of shape ``(n_classes,)``
+
"""
+
posteriors = np.asarray(posteriors, dtype=float)
+
return self._predict_energy(posteriors)
+
+
+
+
# ---------------------------------------------------------------
# imports
# ---------------------------------------------------------------
@@ -2297,9 +2407,6 @@
KDEyHD = _kdey.KDEyHD
KDEyCS = _kdey.KDEyCS
-from . import _edy
-
-EDy = _edy.EDy
# ---------------------------------------------------------------
# aliases
diff --git a/docs/build/html/_modules/quapy/method/non_aggregative.html b/docs/build/html/_modules/quapy/method/non_aggregative.html
index 813b2c7..f403508 100644
--- a/docs/build/html/_modules/quapy/method/non_aggregative.html
+++ b/docs/build/html/_modules/quapy/method/non_aggregative.html
@@ -382,9 +382,11 @@
from quapy.functional import get_divergence
from quapy.method.base import BaseQuantifier, BinaryQuantifier
from quapy.method._helper import _labels_to_indices
+from quapy.method._energy import _EnergyDistanceCore
import quapy.functional as F
from scipy.optimize import lsq_linear
from scipy import sparse
+import quapy as qp
@@ -553,6 +555,79 @@
+
+
[docs]
+
class EDx(_EnergyDistanceCore, BaseQuantifier):
+
"""
+
Energy Distance x (EDx), a covariate-space distribution-matching
+
quantifier based on energy distance.
+
+
EDx is the classifier-free counterpart of :class:`quapy.method.aggregative.EDy`.
+
Instead of representing each class through posterior-probability vectors, it
+
represents each class by the cloud of raw feature vectors observed in the
+
training set and estimates the test prevalence vector by solving the same
+
energy-distance quadratic program directly in feature space.
+
+
This implementation works for binary and multiclass single-label
+
quantification and relies on the optional ``quadprog`` dependency. The
+
current QuaPy adaptation shares its numerical core with EDy and keeps
+
credit to the original implementation available in
+
`quantificationlib <https://github.com/AICGijon/quantificationlib>`_.
+
+
The formulation follows the same references as EDy, namely:
+
+
* Alberto Castaño, Laura Morán-Fernández, Jaime Alonso,
+
Verónica Bolón-Canedo, Amparo Alonso-Betanzos, and Juan José del Coz.
+
*An analysis of quantification methods based on matching distributions*.
+
* Hideko Kawakubo, Marthinus Christoffel du Plessis, and Masashi Sugiyama
+
(2016). *Computationally efficient class-prior estimation under class
+
balance change using energy distance*. IEICE Transactions on Information
+
and Systems, 99(1):176-186.
+
+
:param distance: distance used to compare feature vectors. Valid string
+
aliases are ``'manhattan'`` (default) and ``'euclidean'``; a custom
+
callable compatible with pairwise-distance signatures can also be used
+
:param n_jobs: number of parallel workers (default ``None``, meaning the
+
value is taken from the environment)
+
"""
+
+
def __init__(self, distance: Union[str, Callable] = 'manhattan', n_jobs=None):
+
self.distance = distance
+
self.n_jobs = qp._get_njobs(n_jobs)
+
self.classes_ = None
+
self.n_features_in_ = None
+
self.train_distrib_ = None
+
self.train_n_cls_i_ = None
+
self.K_ = None
+
self.G_ = None
+
self.C_ = None
+
self.b_ = None
+
self.a_ = None
+
+
+
[docs]
+
def fit(self, X, y):
+
"""Fit class-conditional feature-space distributions from training data."""
+
self._check_ed_init_parameters()
+
labels = np.asarray(y)
+
self.classes_ = np.unique(labels)
+
self.n_features_in_ = X.shape[1]
+
train_distrib = [X[labels == class_] for class_ in self.classes_]
+
return self._fit_energy_model(train_distrib)
+
+
+
+
[docs]
+
def predict(self, X):
+
"""Estimate class prevalences for a test sample of raw instances."""
+
assert X.shape[1] == self.n_features_in_, (
+
f'wrong shape; expected {self.n_features_in_}, found {X.shape[1]}'
+
)
+
return self._predict_energy(X)
+
+
+
+
[docs]
class ReadMe(BaseQuantifier, WithConfidenceABC):
@@ -741,8 +816,10 @@
# aliases
#---------------------------------------------------------------
+
HDx = DMx.HDx
DistributionMatchingX = DMx
+
EnergyDistanceX = EDx
HellingerDistanceX = HDx