Survival analysis
Time-to-event estimation that respects censoring — Kaplan-Meier with confidence bands, Nelson-Aalen, a kernel-smoothed hazard rate, Cox regression with Efron ties
Six kernel families for the question "how long until it fails, and what does the data actually support". They run as compiled kernels inside the runtime that ships with the SDK — nothing to install separately and nothing to start.
Survival data is not ordinary data. Some subjects have not failed yet, and dropping them biases every estimate toward short lives while treating them as failures biases it the same way twice. Right-censoring is therefore in the signature of every call, and it is validated rather than assumed:
from algenta import MojoRuntime
runtime = MojoRuntime(mode="local")
times = [2.0, 3.0, 4.0, 4.0, 5.0, 6.0, 8.0, 9.0]
event = [1, 0, 1, 1, 0, 1, 0, 1] # 1 = observed failure, 0 = still under observation
fit = runtime.execute("survival_analysis.km", "km_fit", [times, event, 0.95])["result"]
fit["time"] # [2.0, 4.0, 6.0, 9.0] — one row per distinct failure time
fit["at_risk"] # [8, 6, 3, 1] — counts the censored rows still at risk
fit["survival"] # [0.875, 0.5833333, 0.3888889, 0.0]
fit["std_error"] # [0.1169268, 0.1855610, 0.2012691, 0.0]
fit["has_std_error"] # [True, True, True, False] <- the last one does not exist
fit["median_time"] # 6.0Read the fourth entry of has_std_error. At the last failure time the entire remaining risk set fails at once, and the Greenwood variance of the estimate has a zero denominator there. The value slot holds 0.0 because a response has to be representable, and the flag says that 0.0 is a placeholder rather than a measurement of perfect certainty. Every surface in this package works that way: a quantity that could not be computed is reported absent, distinctly from a computed zero.
The modules
survival_analysis.km
Kaplan-Meier product-limit estimate with Greenwood standard errors and a log-log confidence band that cannot leave [0, 1], restricted mean survival time, step-function lookup, and the median under both tie conventions in circulation so you know which one you are holding
survival_analysis.hazard
Nelson-Aalen cumulative hazard with the Aalen variance and a band on log H, plus a kernel-smoothed hazard rate with a per-point flag for the boundary region where the estimate is biased toward zero
survival_analysis.cox
Cox proportional-hazards regression by Newton-Raphson on the Efron partial likelihood, with standard errors from the observed information, a likelihood-ratio statistic, the baseline cumulative hazard, and per-subject linear predictors and relative risks
survival_analysis.competing
Aalen-Johansen cumulative incidence for many causes of failure, the cause-specific cumulative hazards with their standard errors, and the partition identity checked rather than assumed
survival_analysis.parametric
Weibull and lognormal maximum likelihood with censoring, each returning the identities that define its own solution so the fit can be checked without re-deriving it, plus a goodness-of-fit distance against the nonparametric curve
survival_analysis.base
the shared censoring contract: one status vocabulary, the validation every entry point passes through, canonical tie ordering, the risk table every estimator is built from, and non-wrapping work budgets
Competing risks is a different question, and the wrong answer is plausible
With more than one cause of failure, running a survival curve for cause 1 while treating cause 2 as censoring and reporting 1 - S(t) does not answer "what fraction fail from cause 1 by time t". It answers it in a world where cause 2 does not exist, and it is biased upward — often badly:
The correct cause-1 incidence at t = 6 is 7/12 = 0.583. The 1 - S(t) shortcut returns 1.0 on this sample — a 71% overstatement. That is why cause is an integer code whose values are validated: a status column recorded as 1/2/3 and passed with two declared causes is refused, not quietly folded into the censored bucket.
A fit is refused when there is nothing to fit
Parametric survival models are usually presented as always available. They are not. If every observed failure shares one recorded time — which is what inspection-interval logging produces — and no censored observation lies above it, the lognormal likelihood has no maximum: it grows without bound as the scale shrinks. An estimator that iterates anyway returns whatever its stopping rule reached, and the answer moves with the tolerance:
The same sample with one censored observation moved above the tied failures has a maximum, and is fitted. Weibull refuses it too, through its own bracket test, so a model comparison across the two families cannot be won by whichever one failed to notice.
Ties, and why the Cox default matters
When several subjects fail at the same recorded time, the Cox partial likelihood is ambiguous and the two standard treatments give different coefficients. The Efron approximation is used, and on an eight-row sample with two tied failure times the two conventions differ by 0.033 in the first coefficient — larger than the standard error's third digit, and enough to move a borderline conclusion. The tie rule is stated rather than left to be inferred:
A Cox fit is refused rather than reported when the data carry no information about a coefficient: a covariate that never varies, a covariate that separates the failures perfectly (the estimate is at infinity, not at a large finite value), a likelihood whose curvature has collapsed, and an information matrix that cannot be inverted. In particular a design with as many covariates as rows is refused — the alternative was publishing a coefficient of exactly zero, which reads as "no effect" when it means "no answer".
What is deliberately not here
Refusals
Every entry point returns a typed status rather than raising, and every status names one defect:
empty_observations
no observations were supplied
censoring_length_mismatch
the censoring column is not exactly as long as the time column — a shorter one would read past the end of the data and a longer one would silently describe a different dataset
non_finite_time, negative_time, nonpositive_time
a time that no estimator can use; the last is specific to the two log-domain parametric fits, which is why a time of exactly zero is accepted by the curve estimators and refused by them
event_code_not_zero_or_one
a censoring indicator outside {0, 1}, which is how a 1/2-coded status column is caught instead of being read as a failure
cause_code_out_of_range, too_many_causes, selected_cause_absent
a competing-risks cause code that is not one of the declared causes
confidence_level_out_of_range
a confidence level outside [0.5, 0.999999], refused rather than turned into a band of meaningless width
truncation_time_out_of_range
a restricted-mean horizon past the last observation, or not positive
covariate_shape_mismatch, too_many_covariates, covariate_is_constant
a Cox design that does not describe the sample, or a covariate carrying no information
information_matrix_not_positive_definite, likelihood_flat_or_separating
a Cox likelihood with no finite maximum in some direction
not_converged, iteration_limit_out_of_range, tolerance_out_of_range
an iterative fit that did not reach a solution, or was asked for one with impossible settings
likelihood_unbounded_no_mle_exists
the sample has no maximum-likelihood estimate at all, at any tolerance
em_log_likelihood_decreased
an internal invariant of the lognormal fit was violated, so no estimate is returned
work_budget_exceeded
the request is structurally valid but would take longer than a single synchronous call should; the cost is counted before any of it is performed, not estimated
survival_curve_not_monotone, cumulative_incidence_partition_violated
an algebraic law the estimate must satisfy did not hold, checked on every call
See also
Evaluation Calibration — scoring and calibrating the models a survival fit feeds
Streaming Sketches — summarising event streams before they reach a survival model
Runtime Library Catalog — every module and function
Last updated
Was this helpful?