///|
/// Midrank policy keeps ties equal; Ordinal is stable input-order tie breaking.
pub(all) enum TiePolicy {
Midrank
Ordinal
RejectTies
} derive(Debug)
///|
pub extend TiePolicy with @debug.Debug::{to_repr}
///|
/// One-based ranks divided by n+1, ensuring every output is interior.
pub fn ranks(
values : Array[Double],
policy~ : TiePolicy = Midrank,
) -> Array[Double] raise CopulaError {
if values.length() < 1 {
raise InvalidInput("empty rank input")
}
if values.length() > 4096 {
raise BudgetExceeded("rank input exceeds 4096")
}
for value in values {
finite(value, "rank input")
}
let order = Array::makei(values.length(), fn(i) { i })
order.sort_by(fn(a, b) {
let cmp = values[a].compare(values[b])
if cmp == 0 {
a.compare(b)
} else {
cmp
}
})
let result = Array::make(values.length(), 0.0)
let denominator = (values.length() + 1).to_double()
let mut start = 0
while start < order.length() {
let mut end = start + 1
while end < order.length() && values[order[end]] == values[order[start]] {
end = end + 1
}
match policy {
RejectTies => {
if end - start > 1 {
raise InvalidInput("ties forbidden by policy")
}
result[order[start]] = (start + 1).to_double() / denominator
}
Midrank => {
let rank = (start + 1 + end).to_double() / 2.0 / denominator
for k in start..
for k in start.. Array[Pair] raise CopulaError {
validate_pairs(data, 2)
let xs = data.map(fn(p) { p.x })
let ys = data.map(fn(p) { p.y })
let u = ranks(xs, policy~)
let v = ranks(ys, policy~)
Array::makei(data.length(), fn(i) { { x: u[i], y: v[i], } })
}
///|
/// Count excess observations tied with an earlier occurrence, per coordinate.
pub fn tie_counts(data : Array[Pair]) -> (Int32, Int32) raise CopulaError {
validate_pairs(data, 1)
let mut tx = 0
let mut ty = 0
for i in 0..