///|
pub struct Neighbor {
sample : String
index : Int
separation : Double
}
///|
pub fn Neighbor::sample_name(self : Neighbor) -> String {
self.sample
}
///|
pub fn Neighbor::sample_index(self : Neighbor) -> Int {
self.index
}
///|
pub fn Neighbor::distance(self : Neighbor) -> Double {
self.separation
}
///|
/// Full symmetric distance matrix, bounded to avoid accidental quadratic memory.
pub fn pairwise_distances(
data : Dataset,
) -> Array[Array[Double]] raise CodaError {
if data.row_count() > 512 {
raise Budget("pairwise matrix limited to 512 observations")
}
let n = data.row_count()
let out = zero_matrix(n, n)
for i in 0.. Array[Neighbor] raise CodaError {
if count < 1 || count > data.row_count() {
raise Invalid("neighbor count outside 1..N")
}
let out : Array[Neighbor] = []
for i, row in data.cells {
let item = Neighbor::{
sample: data.samples[i],
index: i,
separation: distance(query, row),
}
let mut pos = out.length()
out.push(item)
while pos > 0 && out[pos - 1].separation > item.separation {
out[pos] = out[pos - 1]
pos -= 1
}
out[pos] = item
if out.length() > count {
ignore(out.pop())
}
}
out
}
///|
/// Observed medoid minimizes total squared Aitchison separation.
pub fn medoid(data : Dataset) -> Medoid raise CodaError {
let distances = pairwise_distances(data)
let mut best = 0
let mut objective = 1.0e300
for i, row in distances {
let mut s = 0.0
for v in row {
s += v * v
}
if s < objective {
objective = s
best = i
}
}
{ sample: data.samples[best], index: best, objective, }
}
///|
pub struct Medoid {
sample : String
index : Int
objective : Double
}
///|
pub fn Medoid::sample_name(self : Medoid) -> String {
self.sample
}
///|
pub fn Medoid::sample_index(self : Medoid) -> Int {
self.index
}
///|
/// Sum of squared distances, deliberately not called a distance.
pub fn Medoid::sum_squared_distance(self : Medoid) -> Double {
self.objective
}