1use ndarray::{Array1, Array2};
20use solow_core::error::{Error, Result};
21use solow_distributions::norm_sf;
22use solow_linalg::inv;
23use solow_optimize::newton_stationary;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub enum Ties {
28 Breslow,
31 Efron,
34}
35
36struct Surv {
40 ufailt_ix: Vec<Vec<usize>>,
43 risk_enter: Vec<Vec<usize>>,
46}
47
48#[derive(Clone, Debug)]
50pub struct PHRegTies {
51 exog_s: Array2<f64>,
53 k: usize,
55 surv_ufailt_ix: Vec<Vec<usize>>,
56 surv_risk_enter: Vec<Vec<usize>>,
57 ties: Ties,
58 maxiter: usize,
59 gtol: f64,
60}
61
62impl PHRegTies {
63 pub fn new(time: &[f64], exog: &Array2<f64>, status: &[f64], ties: Ties) -> Result<Self> {
70 let n = time.len();
71 if exog.nrows() != n || status.len() != n {
72 return Err(Error::Shape("time/exog/status length mismatch".into()));
73 }
74 if n == 0 {
75 return Err(Error::Shape("empty sample".into()));
76 }
77 let k = exog.ncols();
78
79 let has_event = (0..n).any(|i| status[i].round() as i64 == 1);
80 if !has_event {
81 return Err(Error::Convergence("no events in sample".into()));
82 }
83
84 let mut first_failure = f64::INFINITY;
88 for i in 0..n {
89 if status[i].round() as i64 == 1 && time[i] < first_failure {
90 first_failure = time[i];
91 }
92 }
93 let mut rows: Vec<usize> = (0..n).filter(|&i| time[i] >= first_failure).collect();
94
95 rows.sort_by(|&a, &b| time[a].total_cmp(&time[b]));
97
98 let m = rows.len();
99 let mut exog_s = Array2::<f64>::zeros((m, k));
100 let mut time_s = vec![0.0_f64; m];
101 let mut status_s = vec![0.0_f64; m];
102 for (new_i, &old_i) in rows.iter().enumerate() {
103 for j in 0..k {
104 exog_s[[new_i, j]] = exog[[old_i, j]];
105 }
106 time_s[new_i] = time[old_i];
107 status_s[new_i] = status[old_i];
108 }
109
110 let surv = build_surv(&time_s, &status_s);
111
112 Ok(PHRegTies {
113 exog_s,
114 k,
115 surv_ufailt_ix: surv.ufailt_ix,
116 surv_risk_enter: surv.risk_enter,
117 ties,
118 maxiter: 100,
119 gtol: 1e-10,
120 })
121 }
122
123 fn surv(&self) -> Surv {
124 Surv {
125 ufailt_ix: self.surv_ufailt_ix.clone(),
126 risk_enter: self.surv_risk_enter.clone(),
127 }
128 }
129
130 fn e_linpred(&self, params: &Array1<f64>) -> (Array1<f64>, Vec<f64>) {
132 let mut linpred = self.exog_s.dot(params);
133 let lpmax = linpred.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
134 linpred.mapv_inplace(|v| v - lpmax);
135 let e_linpred: Vec<f64> = linpred.iter().map(|&v| v.exp()).collect();
136 (linpred, e_linpred)
137 }
138
139 pub fn breslow_loglike(&self, params: &Array1<f64>) -> f64 {
141 let surv = self.surv();
142 let nuft = surv.ufailt_ix.len();
143 let (linpred, e_linpred) = self.e_linpred(params);
144
145 let mut like = 0.0;
146 let mut xp0 = 0.0;
147 for i in (0..nuft).rev() {
148 for &ix in &surv.risk_enter[i] {
149 xp0 += e_linpred[ix];
150 }
151 for &ix in &surv.ufailt_ix[i] {
152 like += linpred[ix] - xp0.ln();
153 }
154 }
155 like
156 }
157
158 pub fn efron_loglike(&self, params: &Array1<f64>) -> f64 {
160 let surv = self.surv();
161 let nuft = surv.ufailt_ix.len();
162 let (linpred, e_linpred) = self.e_linpred(params);
163
164 let mut like = 0.0;
165 let mut xp0 = 0.0;
166 for i in (0..nuft).rev() {
167 for &ix in &surv.risk_enter[i] {
168 xp0 += e_linpred[ix];
169 }
170 let fail = &surv.ufailt_ix[i];
171 let xp0f: f64 = fail.iter().map(|&ix| e_linpred[ix]).sum();
172 for &ix in fail {
173 like += linpred[ix];
174 }
175 let m = fail.len();
176 for j in 0..m {
177 let jf = j as f64 / m as f64;
178 like -= (xp0 - jf * xp0f).ln();
179 }
180 }
181 like
182 }
183
184 pub fn loglike(&self, params: &Array1<f64>) -> f64 {
186 match self.ties {
187 Ties::Breslow => self.breslow_loglike(params),
188 Ties::Efron => self.efron_loglike(params),
189 }
190 }
191
192 pub fn breslow_gradient(&self, params: &Array1<f64>) -> Array1<f64> {
194 let surv = self.surv();
195 let nuft = surv.ufailt_ix.len();
196 let (_linpred, e_linpred) = self.e_linpred(params);
197
198 let mut grad = Array1::<f64>::zeros(self.k);
199 let mut xp0 = 0.0;
200 let mut xp1 = Array1::<f64>::zeros(self.k);
201
202 for i in (0..nuft).rev() {
203 for &ix in &surv.risk_enter[i] {
204 xp0 += e_linpred[ix];
205 let row = self.exog_s.row(ix);
206 for j in 0..self.k {
207 xp1[j] += e_linpred[ix] * row[j];
208 }
209 }
210 for &ix in &surv.ufailt_ix[i] {
211 let row = self.exog_s.row(ix);
212 for j in 0..self.k {
213 grad[j] += row[j] - xp1[j] / xp0;
214 }
215 }
216 }
217 grad
218 }
219
220 pub fn efron_gradient(&self, params: &Array1<f64>) -> Array1<f64> {
222 let surv = self.surv();
223 let nuft = surv.ufailt_ix.len();
224 let (_linpred, e_linpred) = self.e_linpred(params);
225
226 let mut grad = Array1::<f64>::zeros(self.k);
227 let mut xp0 = 0.0;
228 let mut xp1 = Array1::<f64>::zeros(self.k);
229
230 for i in (0..nuft).rev() {
231 for &ix in &surv.risk_enter[i] {
232 xp0 += e_linpred[ix];
233 let row = self.exog_s.row(ix);
234 for j in 0..self.k {
235 xp1[j] += e_linpred[ix] * row[j];
236 }
237 }
238 let fail = &surv.ufailt_ix[i];
239 if fail.is_empty() {
240 continue;
241 }
242 let xp0f: f64 = fail.iter().map(|&ix| e_linpred[ix]).sum();
244 let mut xp1f = Array1::<f64>::zeros(self.k);
245 for &ix in fail {
246 let row = self.exog_s.row(ix);
247 for j in 0..self.k {
248 xp1f[j] += e_linpred[ix] * row[j];
249 grad[j] += row[j];
250 }
251 }
252 let m = fail.len();
253 for jj in 0..m {
254 let jf = jj as f64 / m as f64;
255 let denom = xp0 - jf * xp0f;
256 for j in 0..self.k {
257 grad[j] -= (xp1[j] - jf * xp1f[j]) / denom;
258 }
259 }
260 }
261 grad
262 }
263
264 pub fn gradient(&self, params: &Array1<f64>) -> Array1<f64> {
266 match self.ties {
267 Ties::Breslow => self.breslow_gradient(params),
268 Ties::Efron => self.efron_gradient(params),
269 }
270 }
271
272 pub fn breslow_hessian(&self, params: &Array1<f64>) -> Array2<f64> {
277 let surv = self.surv();
278 let nuft = surv.ufailt_ix.len();
279 let (_linpred, e_linpred) = self.e_linpred(params);
280
281 let mut hess = Array2::<f64>::zeros((self.k, self.k));
282 let mut xp0 = 0.0;
283 let mut xp1 = Array1::<f64>::zeros(self.k);
284 let mut xp2 = Array2::<f64>::zeros((self.k, self.k));
285
286 for i in (0..nuft).rev() {
287 for &ix in &surv.risk_enter[i] {
288 let el = e_linpred[ix];
289 xp0 += el;
290 let row = self.exog_s.row(ix);
291 for a in 0..self.k {
292 xp1[a] += el * row[a];
293 for b in 0..self.k {
294 xp2[[a, b]] += el * row[a] * row[b];
295 }
296 }
297 }
298 let mfail = surv.ufailt_ix[i].len() as f64;
299 for a in 0..self.k {
300 for b in 0..self.k {
301 let val = xp2[[a, b]] / xp0 - (xp1[a] * xp1[b]) / (xp0 * xp0);
302 hess[[a, b]] += mfail * val;
303 }
304 }
305 }
306 hess.mapv_inplace(|v| -v);
307 hess
308 }
309
310 pub fn efron_hessian(&self, params: &Array1<f64>) -> Array2<f64> {
315 let surv = self.surv();
316 let nuft = surv.ufailt_ix.len();
317 let (_linpred, e_linpred) = self.e_linpred(params);
318
319 let mut hess = Array2::<f64>::zeros((self.k, self.k));
320 let mut xp0 = 0.0;
321 let mut xp1 = Array1::<f64>::zeros(self.k);
322 let mut xp2 = Array2::<f64>::zeros((self.k, self.k));
323
324 for i in (0..nuft).rev() {
325 for &ix in &surv.risk_enter[i] {
326 let el = e_linpred[ix];
327 xp0 += el;
328 let row = self.exog_s.row(ix);
329 for a in 0..self.k {
330 xp1[a] += el * row[a];
331 for b in 0..self.k {
332 xp2[[a, b]] += el * row[a] * row[b];
333 }
334 }
335 }
336 let fail = &surv.ufailt_ix[i];
337 if fail.is_empty() {
338 continue;
339 }
340 let xp0f: f64 = fail.iter().map(|&ix| e_linpred[ix]).sum();
342 let mut xp1f = Array1::<f64>::zeros(self.k);
343 let mut xp2f = Array2::<f64>::zeros((self.k, self.k));
344 for &ix in fail {
345 let el = e_linpred[ix];
346 let row = self.exog_s.row(ix);
347 for a in 0..self.k {
348 xp1f[a] += el * row[a];
349 for b in 0..self.k {
350 xp2f[[a, b]] += el * row[a] * row[b];
351 }
352 }
353 }
354 let m = fail.len();
355 let mut sum_inv = 0.0;
358 let mut sum_jinv = 0.0;
359 let mut outer_acc = Array2::<f64>::zeros((self.k, self.k));
361 for jj in 0..m {
362 let jf = jj as f64 / m as f64;
363 let c0 = xp0 - jf * xp0f;
364 sum_inv += 1.0 / c0;
365 sum_jinv += jf / c0;
366 let mut matj = Array1::<f64>::zeros(self.k);
368 for a in 0..self.k {
369 matj[a] = (xp1[a] - jf * xp1f[a]) / c0;
370 }
371 for a in 0..self.k {
372 for b in 0..self.k {
373 outer_acc[[a, b]] += matj[a] * matj[b];
374 }
375 }
376 }
377 for a in 0..self.k {
378 for b in 0..self.k {
379 hess[[a, b]] +=
380 xp2[[a, b]] * sum_inv - xp2f[[a, b]] * sum_jinv - outer_acc[[a, b]];
381 }
382 }
383 }
384 hess.mapv_inplace(|v| -v);
385 hess
386 }
387
388 pub fn hessian(&self, params: &Array1<f64>) -> Array2<f64> {
390 match self.ties {
391 Ties::Breslow => self.breslow_hessian(params),
392 Ties::Efron => self.efron_hessian(params),
393 }
394 }
395
396 pub fn fit(&self) -> Result<PHRegTiesResults> {
398 let start = Array1::<f64>::zeros(self.k);
399 let opt = newton_stationary(
400 &start,
401 |b| {
402 let f = self.loglike(b);
403 let g = self.gradient(b);
404 let h = self.hessian(b);
405 (f, g, h)
406 },
407 self.maxiter,
408 self.gtol,
409 )?;
410
411 let params = opt.x;
412 let d2l = self.hessian(¶ms);
416 let info = d2l.mapv(|v| -v);
417 let cov = inv(&info)?;
418
419 let bse = Array1::from_iter((0..self.k).map(|i| cov[[i, i]].sqrt()));
420 let tvalues = Array1::from_iter((0..self.k).map(|i| params[i] / bse[i]));
421 let pvalues = Array1::from_iter((0..self.k).map(|i| 2.0 * norm_sf(tvalues[i].abs())));
422 let llf = self.loglike(¶ms);
423
424 Ok(PHRegTiesResults {
425 params,
426 bse,
427 tvalues,
428 pvalues,
429 cov_params: cov,
430 llf,
431 converged: opt.converged,
432 ties: self.ties,
433 })
434 }
435}
436
437fn build_surv(time_s: &[f64], status_s: &[f64]) -> Surv {
440 let m = time_s.len();
441
442 let mut ft: Vec<f64> = (0..m)
444 .filter(|&i| status_s[i].round() as i64 == 1)
445 .map(|i| time_s[i])
446 .collect();
447 ft.sort_by(|a, b| a.total_cmp(b));
448 let mut uft: Vec<f64> = Vec::new();
449 for &t in &ft {
450 if uft.is_empty() || t != *uft.last().unwrap() {
451 uft.push(t);
452 }
453 }
454 let nuft = uft.len();
455
456 let mut ufailt_ix: Vec<Vec<usize>> = vec![Vec::new(); nuft];
458 for (i, &t) in time_s.iter().enumerate().take(m) {
459 if status_s[i].round() as i64 == 1 {
460 let k = uft.iter().position(|&u| u == t).unwrap();
461 ufailt_ix[k].push(i);
462 }
463 }
464
465 let mut risk_enter: Vec<Vec<usize>> = vec![Vec::new(); nuft];
468 for (i, &t) in time_s.iter().enumerate().take(m) {
469 let cnt = uft.iter().filter(|&&u| u <= t).count();
470 if cnt >= 1 {
471 risk_enter[cnt - 1].push(i);
472 }
473 }
474
475 Surv {
476 ufailt_ix,
477 risk_enter,
478 }
479}
480
481#[derive(Clone, Debug)]
483pub struct PHRegTiesResults {
484 pub params: Array1<f64>,
486 pub bse: Array1<f64>,
488 pub tvalues: Array1<f64>,
490 pub pvalues: Array1<f64>,
492 pub cov_params: Array2<f64>,
494 pub llf: f64,
496 pub converged: bool,
498 pub ties: Ties,
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use ndarray::array;
506
507 #[test]
508 fn efron_gradient_zero_at_optimum() {
509 let time = [4.0, 3.0, 1.0, 1.0, 2.0, 2.0, 3.0];
510 let status = [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0];
511 let exog = array![[0.5_f64], [1.2], [-0.3], [0.8], [0.1], [-1.0], [0.4]];
512 let model = PHRegTies::new(&time, &exog, &status, Ties::Efron).unwrap();
513 let res = model.fit().unwrap();
514 assert!(res.converged);
515 let g = model.efron_gradient(&res.params);
516 assert!(g.iter().all(|&v| v.abs() < 1e-8));
517 }
518
519 #[test]
520 fn efron_hessian_matches_numeric_gradient_diff() {
521 let time = [1.0, 1.0, 2.0, 2.0, 3.0, 3.0];
523 let status = [1.0, 1.0, 1.0, 1.0, 0.0, 1.0];
524 let exog = array![[0.2_f64], [-0.5], [1.0], [0.3], [-0.8], [0.6]];
525 let model = PHRegTies::new(&time, &exog, &status, Ties::Efron).unwrap();
526 let b = array![0.15_f64];
527 let h = model.efron_hessian(&b)[[0, 0]];
528 let eps = 1e-6;
529 let gp = model.efron_gradient(&array![0.15 + eps])[0];
530 let gm = model.efron_gradient(&array![0.15 - eps])[0];
531 let num_d2l = (gp - gm) / (2.0 * eps);
532 assert!((h - num_d2l).abs() < 1e-4, "h={h}, num={num_d2l}");
533 }
534
535 #[test]
536 fn breslow_matches_existing_phreg() {
537 let time = [1.0, 2.0, 3.0, 4.0, 5.0];
540 let status = [1.0, 1.0, 0.0, 1.0, 1.0];
541 let exog = array![[0.2_f64], [-0.5], [1.0], [0.3], [-0.8]];
542 let breslow = PHRegTies::new(&time, &exog, &status, Ties::Breslow)
543 .unwrap()
544 .fit()
545 .unwrap();
546 let efron = PHRegTies::new(&time, &exog, &status, Ties::Efron)
547 .unwrap()
548 .fit()
549 .unwrap();
550 assert!((breslow.params[0] - efron.params[0]).abs() < 1e-10);
552 }
553}