///|
pub fn ThresholdPoint::threshold(self : ThresholdPoint) -> Double {
self.score_threshold
}
///|
pub fn ThresholdPoint::true_positives(self : ThresholdPoint) -> Int {
self.accepted_true_positives
}
///|
pub fn ThresholdPoint::false_positives(self : ThresholdPoint) -> Int {
self.accepted_false_positives
}
///|
pub fn ThresholdPoint::true_negatives(self : ThresholdPoint) -> Int {
self.rejected_true_negatives
}
///|
pub fn ThresholdPoint::false_negatives(self : ThresholdPoint) -> Int {
self.rejected_false_negatives
}
///|
pub fn ThresholdPoint::true_positive_rate(self : ThresholdPoint) -> Double {
self.true_positive_rate_value
}
///|
pub fn ThresholdPoint::false_positive_rate(self : ThresholdPoint) -> Double {
self.false_positive_rate_value
}
///|
pub fn ThresholdPoint::precision(self : ThresholdPoint) -> Double {
self.precision_value
}
///|
pub fn ThresholdPoint::recall(self : ThresholdPoint) -> Double {
self.recall_value
}
///|
pub fn BinaryThresholdAnalysis::points(
self : BinaryThresholdAnalysis,
) -> Array[ThresholdPoint] {
self.threshold_points.copy()
}
///|
pub fn BinaryThresholdAnalysis::positive_count(
self : BinaryThresholdAnalysis,
) -> Int {
self.observed_positive_count
}
///|
pub fn BinaryThresholdAnalysis::negative_count(
self : BinaryThresholdAnalysis,
) -> Int {
self.observed_negative_count
}
///|
pub fn BinaryThresholdAnalysis::roc_auc(
self : BinaryThresholdAnalysis,
) -> Double {
self.roc_area
}
///|
pub fn BinaryThresholdAnalysis::average_precision(
self : BinaryThresholdAnalysis,
) -> Double {
self.average_precision_value
}
///|
fn descending_score_indices(scores : Array[Double]) -> Array[Int] {
let indices : Array[Int] = []
for index = 0; index < scores.length(); index = index + 1 {
let mut position = 0
while position < indices.length() &&
scores[indices[position]] >= scores[index] {
position = position + 1
}
indices.push(index)
let mut cursor = indices.length() - 1
while cursor > position {
indices[cursor] = indices[cursor - 1]
cursor = cursor - 1
}
indices[position] = index
}
indices
}
///|
/// Builds tied-threshold confusion evidence and deterministic curve areas.
pub fn binary_threshold_analysis(
labels : Array[Int],
scores : Array[Double],
positive_class : Int,
) -> Result[BinaryThresholdAnalysis, SvmError] {
if labels.length() != scores.length() {
return Err(MetricLengthMismatch(labels.length(), scores.length()))
}
if labels.is_empty() {
return Err(EmptyMetricInput)
}
let mut positives = 0
let mut negatives = 0
for index, score in scores {
if !finite_double(score) {
return Err(NonFiniteReportValue("decision score \{index}"))
}
if labels[index] == positive_class {
positives = positives + 1
} else {
negatives = negatives + 1
}
}
if positives == 0 || negatives == 0 {
return Err(SingleClassDataset)
}
let order = descending_score_indices(scores)
let points : Array[ThresholdPoint] = []
let mut true_positives = 0
let mut false_positives = 0
let mut cursor = 0
let mut previous_tpr = 0.0
let mut previous_fpr = 0.0
let mut previous_recall = 0.0
let mut roc_area = 0.0
let mut average_precision = 0.0
while cursor < order.length() {
let threshold = scores[order[cursor]]
while cursor < order.length() && scores[order[cursor]] == threshold {
if labels[order[cursor]] == positive_class {
true_positives = true_positives + 1
} else {
false_positives = false_positives + 1
}
cursor = cursor + 1
}
let false_negatives = positives - true_positives
let true_negatives = negatives - false_positives
let tpr = true_positives.to_double() / positives.to_double()
let fpr = false_positives.to_double() / negatives.to_double()
let precision = true_positives.to_double() /
(true_positives + false_positives).to_double()
roc_area = roc_area + (fpr - previous_fpr) * (tpr + previous_tpr) / 2.0
average_precision = average_precision + (tpr - previous_recall) * precision
points.push({
score_threshold: threshold,
accepted_true_positives: true_positives,
accepted_false_positives: false_positives,
rejected_true_negatives: true_negatives,
rejected_false_negatives: false_negatives,
true_positive_rate_value: tpr,
false_positive_rate_value: fpr,
precision_value: precision,
recall_value: tpr,
})
previous_tpr = tpr
previous_fpr = fpr
previous_recall = tpr
}
Ok({
threshold_points: points,
observed_positive_count: positives,
observed_negative_count: negatives,
roc_area,
average_precision_value: average_precision,
})
}