The ROCCurve class template computes Receiver Operating Characteristic (ROC) curves and Area Under the Curve (AUC) for binary classifiers. It operates on raw classifier scores rather than hard predictions, making it threshold-agnostic. Multi-class problems are supported via one-vs-rest.
Template parameters
Floating-point type of classifier output (e.g., float, double). Higher scores indicate higher confidence in the positive class.
Integer-like binary label type. Positive class = 1, negative class = 0 (or any other value).
Nested types
Point
struct Point {
double fpr; // False positive rate = FP / (FP + TN)
double tpr; // True positive rate = TP / (TP + FN)
};
Represents a single point on the ROC curve.
Constructor
explicit ROCCurve(const std::vector<Score>& scores,
const std::vector<Label>& labels,
Label pos_label = Label(1));
Computes the ROC curve for a binary classification problem.
scores
const std::vector<Score>&
Raw classifier outputs, length n_samples. Higher values indicate higher confidence in the positive class.
labels
const std::vector<Label>&
Ground-truth binary labels, length n_samples.
Value in labels that denotes the positive class.
Methods
curve
[[nodiscard]] const std::vector<Point>& curve() const noexcept;
Returns the computed ROC curve points.
return
const std::vector<Point>&
Ordered curve points from (0,0) to (1,1)
The curve is built by sorting samples in descending score order and sweeping the decision threshold from +∞ to -∞. Samples with tied scores are processed as a batch before emitting a curve point.
auc
[[nodiscard]] double auc() const noexcept;
Returns the Area Under the ROC Curve.
AUC score in range [0.0, 1.0]
Computed via the trapezoidal rule over curve points. AUC represents the probability that the classifier ranks a random positive sample higher than a random negative sample (Wilcoxon-Mann-Whitney statistic).
n_pos
[[nodiscard]] std::size_t n_pos() const noexcept;
Number of positive samples in the dataset
n_neg
[[nodiscard]] std::size_t n_neg() const noexcept;
Number of negative samples in the dataset
optimal_threshold
[[nodiscard]] Score optimal_threshold() const noexcept;
Returns the score threshold that maximizes the Youden J statistic.
This is the point on the curve closest to (FPR=0, TPR=1) by Euclidean distance, computed as argmax_t (TPR(t) - FPR(t)).
Static methods for multi-class
roc_ovr
[[nodiscard]] static std::vector<ROCCurve>
roc_ovr(const std::vector<std::vector<Score>>& scores,
const std::vector<Label>& labels,
std::size_t n_classes);
Computes one-vs-rest ROC curves for a multi-class problem.
scores
const std::vector<std::vector<Score>>&
Score matrix, shape (n_samples, n_classes). scores[i][k] is the model’s confidence that sample i belongs to class k.
labels
const std::vector<Label>&
Integer class labels, length n_samples.
Vector of K ROCCurve objects, one per class
For each class k, constructs a binary problem treating class k as positive and all other classes as negative.
macro_auc
[[nodiscard]] static double
macro_auc(const std::vector<std::vector<Score>>& scores,
const std::vector<Label>& labels,
std::size_t n_classes);
Computes the macro-average AUC across all classes.
scores
const std::vector<std::vector<Score>>&
Score matrix, shape (n_samples, n_classes).
labels
const std::vector<Label>&
Integer class labels, length n_samples.
Unweighted mean of per-class AUC scores
Example usage
Binary classification
#include <mlpp/model_validation/roc_curve.hpp>
using namespace mlpp::model_validation;
// Classifier scores and true labels
std::vector<double> scores = {0.9, 0.8, 0.7, 0.6, 0.55, 0.4, 0.3, 0.2};
std::vector<int> labels = {1, 1, 0, 1, 0, 0, 1, 0};
// Compute ROC curve
ROCCurve<double, int> roc(scores, labels, 1);
std::cout << "AUC: " << roc.auc() << std::endl;
std::cout << "Optimal threshold: " << roc.optimal_threshold() << std::endl;
std::cout << "Positive samples: " << roc.n_pos() << std::endl;
std::cout << "Negative samples: " << roc.n_neg() << std::endl;
// Print curve points
for (const auto& pt : roc.curve()) {
std::cout << "FPR: " << pt.fpr << ", TPR: " << pt.tpr << std::endl;
}
Multi-class classification (one-vs-rest)
#include <mlpp/model_validation/roc_curve.hpp>
using namespace mlpp::model_validation;
// Score matrix: each row is a sample, each column is a class
std::vector<std::vector<double>> scores = {
{0.7, 0.2, 0.1}, // sample 0: class 0 most confident
{0.1, 0.8, 0.1}, // sample 1: class 1 most confident
{0.2, 0.1, 0.7}, // sample 2: class 2 most confident
{0.6, 0.3, 0.1},
{0.1, 0.7, 0.2}
};
std::vector<int> labels = {0, 1, 2, 0, 1};
// Compute per-class ROC curves
auto roc_curves = ROCCurve<double, int>::roc_ovr(scores, labels, 3);
for (size_t k = 0; k < roc_curves.size(); ++k) {
std::cout << "Class " << k << " AUC: " << roc_curves[k].auc() << std::endl;
}
// Compute macro-average AUC
double macro = ROCCurve<double, int>::macro_auc(scores, labels, 3);
std::cout << "Macro-average AUC: " << macro << std::endl;
Implementation notes
Tie handling: All samples with the same score are processed as a batch before emitting a curve point. This matches scikit-learn’s convention and prevents artificially jagged curves.
AUC computation: Uses the trapezoidal rule. For continuous scores with no ties, this is exact. For discrete scores, it provides linear interpolation between breakpoints.
The AUC is equivalent to the Wilcoxon-Mann-Whitney statistic: AUC = P(score(positive) > score(negative)). This interpretation is independent of class balance.