use ndarray::Array1;
use solow_core::error::{Error, Result};
#[derive(Clone, Debug)]
pub struct SurvfuncRight {
pub surv_times: Array1<f64>,
pub surv_prob: Array1<f64>,
pub surv_prob_se: Array1<f64>,
pub n_risk: Array1<f64>,
pub n_events: Array1<f64>,
}
impl SurvfuncRight {
pub fn new(time: &[f64], status: &[f64]) -> Result<Self> {
if time.len() != status.len() {
return Err(Error::Shape("time and status length differ".into()));
}
if time.is_empty() {
return Err(Error::Shape("empty time vector".into()));
}
let mut order: Vec<usize> = (0..time.len()).collect();
order.sort_by(|&a, &b| time[a].total_cmp(&time[b]));
let mut utime: Vec<f64> = Vec::new();
let mut rtime: Vec<usize> = vec![0; time.len()];
for &i in &order {
if utime.is_empty() || time[i] != *utime.last().unwrap() {
utime.push(time[i]);
}
rtime[i] = utime.len() - 1;
}
let ml = utime.len();
let mut d = vec![0.0_f64; ml];
let mut raw_n = vec![0.0_f64; ml];
for i in 0..time.len() {
let k = rtime[i];
raw_n[k] += 1.0;
if status[i].round() as i64 == 1 {
d[k] += 1.0;
}
}
let mut n = vec![0.0_f64; ml];
let mut acc = 0.0;
for k in (0..ml).rev() {
acc += raw_n[k];
n[k] = acc;
}
let keep: Vec<usize> = (0..ml).filter(|&k| d[k] > 0.0).collect();
let nk = keep.len();
let dk: Vec<f64> = keep.iter().map(|&k| d[k]).collect();
let nrisk: Vec<f64> = keep.iter().map(|&k| n[k]).collect();
let times: Vec<f64> = keep.iter().map(|&k| utime[k]).collect();
let mut sp = vec![0.0_f64; nk];
let mut zero_flag = vec![false; nk];
let mut log_cumsum = 0.0;
for j in 0..nk {
let mut frac = 1.0 - dk[j] / nrisk[j];
if frac < 1e-16 {
frac = 1e-16;
zero_flag[j] = true;
}
log_cumsum += frac.ln();
sp[j] = log_cumsum.exp();
if zero_flag[j] {
sp[j] = 0.0;
}
}
let mut se = vec![0.0_f64; nk];
let mut cum = 0.0;
for j in 0..nk {
let denom = (nrisk[j] * (nrisk[j] - dk[j])).max(1e-12);
let mut term = dk[j] / denom;
if nrisk[j] == dk[j] || nrisk[j] == 0.0 {
term = f64::NAN;
}
cum += term;
let s = cum.sqrt();
if s.is_finite() || sp[j] != 0.0 {
se[j] = s * sp[j];
} else {
se[j] = f64::NAN;
}
}
Ok(SurvfuncRight {
surv_times: Array1::from_vec(times),
surv_prob: Array1::from_vec(sp),
surv_prob_se: Array1::from_vec(se),
n_risk: Array1::from_vec(nrisk),
n_events: Array1::from_vec(dk),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_events_no_censoring() {
let time = [1.0, 2.0, 3.0, 4.0];
let status = [1.0, 1.0, 1.0, 1.0];
let s = SurvfuncRight::new(&time, &status).unwrap();
assert_eq!(s.surv_times.to_vec(), vec![1.0, 2.0, 3.0, 4.0]);
let exp = [0.75, 0.5, 0.25, 0.0];
for (i, &e) in exp.iter().enumerate() {
assert!((s.surv_prob[i] - e).abs() < 1e-12);
}
assert!(s.surv_prob_se[3].is_nan());
}
#[test]
fn censored_times_excluded_from_surv_times() {
let time = [1.0, 2.0, 3.0];
let status = [1.0, 0.0, 1.0];
let s = SurvfuncRight::new(&time, &status).unwrap();
assert_eq!(s.surv_times.to_vec(), vec![1.0, 3.0]);
assert!((s.surv_prob[0] - (2.0 / 3.0)).abs() < 1e-12);
assert!((s.surv_prob[1]).abs() < 1e-12);
}
#[test]
fn mismatched_lengths_error() {
assert!(SurvfuncRight::new(&[1.0, 2.0], &[1.0]).is_err());
}
}