///|
/// Segment-level diagnostics used to decide whether a change is actionable.
pub struct SegmentQuality {
count : Int
mean : Double
deviation : Double
stability : Double
completeness : Double
score : Double
}
///|
pub fn SegmentQuality::empty() -> SegmentQuality {
{
count: 0,
mean: 0.0,
deviation: 0.0,
stability: 0.0,
completeness: 0.0,
score: 0.0,
}
}
///|
pub fn segment_quality(values : Array[Double]) -> SegmentQuality {
if values.length() == 0 {
return SegmentQuality::empty()
}
let valid = values.filter(fn(value) { is_finite(value) })
if valid.length() == 0 {
return SegmentQuality::empty()
}
let deviation = standard_deviation(valid)
let stability = 1.0 / (1.0 + deviation)
let completeness = valid.length().to_double() / values.length().to_double()
let score = clamp_probability(stability * completeness)
{
count: valid.length(),
mean: mean(valid),
deviation,
stability,
completeness,
score,
}
}
///|
pub fn compare_segment_quality(
left : Array[Double],
right : Array[Double],
) -> Double {
let a = segment_quality(left)
let b = segment_quality(right)
absolute(a.score - b.score)
}
///|
pub fn segment_means(
values : Array[Double],
boundaries : Array[Int],
) -> Array[Double] {
let result : Array[Double] = []
let mut start = 0
for boundary in boundaries {
let end = if boundary < start {
start
} else if boundary > values.length() {
values.length()
} else {
boundary
}
let part : Array[Double] = []
for i in start.. Array[Double] {
let result : Array[Double] = []
let mut start = 0
for boundary in boundaries {
let end = if boundary < start {
start
} else if boundary > values.length() {
values.length()
} else {
boundary
}
let part : Array[Double] = []
for i in start.. Double {
let safe = if minimum_segment < 1 { 1 } else { minimum_segment }
if boundary < safe || values.length() - boundary < safe {
return 0.0
}
let left : Array[Double] = []
let right : Array[Double] = []
for i in 0.. Int {
let safe = if minimum_segment < 1 { 1 } else { minimum_segment }
if values.length() < safe * 2 {
return -1
}
let mut best = safe
let mut best_score = boundary_quality(values, safe, minimum_segment=safe)
for boundary in safe..<(values.length() - safe + 1) {
let score = boundary_quality(values, boundary, minimum_segment=safe)
if score > best_score {
best = boundary
best_score = score
}
}
best
}
///|
pub fn segment_profile(
values : Array[Double],
boundaries : Array[Int],
) -> Array[StatsSummary] {
let result : Array[StatsSummary] = []
let mut start = 0
for boundary in boundaries {
let end = if boundary > values.length() {
values.length()
} else if boundary < start {
start
} else {
boundary
}
let part : Array[Double] = []
for i in start.. Bool {
segment_quality(values).score >= clamp_probability(minimum_score)
}