///|
fn observation_weights(
weights : Array[Double],
n : Int,
) -> Array[Double] raise CodaError {
if weights.length() != n {
raise Shape("observation weight count mismatch")
}
real_vector(weights)
let mut largest = 0.0
for w in weights {
if w < 0.0 {
raise Invalid("negative observation weight")
}
if w > largest {
largest = w
}
}
if largest == 0.0 {
raise Invalid("all-zero observation weights")
}
let mut total = 0.0
for w in weights {
total += w / largest
}
weights.map(w => w / largest / total)
}
///|
pub fn weighted_center(
data : Dataset,
weights : Array[Double],
) -> Array[Double] raise CodaError {
let w = observation_weights(weights, data.row_count())
let logs = data.clr_rows()
let center = Array::make(data.part_count(), 0.0)
for r, row in logs {
for j, v in row {
center[j] += w[r] * v
}
}
clr_inverse(center)
}
///|
pub fn center(data : Dataset) -> Array[Double] raise CodaError {
weighted_center(data, Array::make(data.row_count(), 1.0))
}
///|
/// Remove the geometric center by inverse perturbation; labels are retained.
pub fn centralize(data : Dataset) -> Dataset raise CodaError {
let c = center(data)
dataset(
data.features,
data.samples,
data.cells.map(r => perturb_inverse(r, c)),
)
}
///|
pub fn weighted_dispersion(
data : Dataset,
weights : Array[Double],
) -> Double raise CodaError {
let w = observation_weights(weights, data.row_count())
let c = weighted_center(data, weights)
let mut sum = 0.0
for i, row in data.cells {
let d = distance(row, c)
sum += w[i] * d * d
}
sum
}
///|
/// Between-center distance is descriptive, not a hypothesis test.
pub fn center_distance(
left : Dataset,
right : Dataset,
) -> Double raise CodaError {
if left.features != right.features {
raise Shape("feature labels/order mismatch")
}
distance(center(left), center(right))
}