///|
pub struct Summary {
present : Int
missing : Int
coverage : Double
minimum : Double?
maximum : Double?
mean : Double?
variance : Double?
median : Double?
}
///|
fn curve_values(log : Log, column : Int) -> Array[Double] {
let values : Array[Double] = []
for row in log.rows {
if row[column] is Some(v) {
values.push(v)
}
}
values
}
///|
fn quantile_sorted(values : Array[Double], probability : Double) -> Double? {
if values.length() == 0 {
return None
}
let position = probability * (values.length() - 1).to_double()
let left = position.to_int()
let right = if left + 1 < values.length() { left + 1 } else { left }
let weight = position - left.to_double()
Some((1.0 - weight) * values[left] + weight * values[right])
}
///|
/// Type-7 linear quantile, ignoring missing samples; rejects invalid probabilities.
pub fn quantile(
log : Log,
name : String,
probability : Double,
) -> Double? raise WellError {
validate(log)
if !finite(probability) || probability < 0.0 || probability > 1.0 {
fail("probability must be in [0,1]")
}
let values = curve_values(log, curve_index(log, name))
values.sort()
quantile_sorted(values, probability)
}
///|
/// Population variance (not sample variance), with scaled Welford accumulation.
pub fn summarize(log : Log, name : String) -> Summary raise WellError {
validate(log)
let values = curve_values(log, curve_index(log, name))
values.sort()
let n = values.length()
let total = log.rows.length()
let coverage = if total == 0 {
0.0
} else {
n.to_double() / total.to_double()
}
if n == 0 {
return {
present: 0,
missing: total,
coverage,
minimum: None,
maximum: None,
mean: None,
variance: None,
median: None,
}
}
let mut scale = 0.0
for v in values {
if v.abs() > scale {
scale = v.abs()
}
}
let mut mean = 0.0
let mut m2 = 0.0
if scale > 0.0 {
for i in 0..