///|
pub struct Replacement {
values : Array[Double]
count : Int
mass : Double
}
///|
pub fn Replacement::composition(self : Replacement) -> Array[Double] {
self.values.copy()
}
///|
pub fn Replacement::zero_count(self : Replacement) -> Int {
self.count
}
///|
pub fn Replacement::replaced_mass(self : Replacement) -> Double {
self.mass
}
///|
/// Replace each zero by delta after closure, scaling nonzero parts by 1-z*delta.
/// Delta is a modeling choice, not an estimate of a structural zero.
pub fn multiplicative_replace(
x : Array[Double],
delta? : Double = 0.001,
) -> Replacement raise CodaError {
let c = closure(x)
if !finite(delta) || delta <= 0.0 || delta >= 1.0 {
raise Invalid("replacement delta must be in (0,1)")
}
let mut count = 0
for v in c {
if v == 0.0 {
count += 1
}
}
let mass = count.to_double() * delta
if mass >= 1.0 {
raise Invalid("replacement consumes all mass")
}
let values = c.map(v => if v == 0.0 { delta } else { v * (1.0 - mass) })
positive(values)
{ values, count, mass, }
}
///|
/// Add an explicit pseudocount in the original count unit, then close.
pub fn add_pseudocount(
x : Array[Double],
amount : Double,
) -> Array[Double] raise CodaError {
vector_size(x.length())
real_vector(x)
if !finite(amount) || amount <= 0.0 {
raise Invalid("pseudocount must be finite and positive")
}
for v in x {
if v < 0.0 {
raise Invalid("negative count")
}
}
closure(x.map(v => checked(v + amount)))
}