On this page

Time to event analysis – method notes

The decisions behind the time-to-event analysis module – which estimator, threshold, refusal or fallback stands behind each card, and the numbers that validate them, each computed on the ovarian trial table printed on the survival concepts page. Sections follow the manual’s.

Variable roles

Five censored tokens are pre-assigned, and the remaining levels take the cause roles in sorted order. 0, false, no, censored and alive, matched case-insensitively after trimming, are the spellings of “nothing happened” that survival data arrives with – the 0/1 coding of most packages, the yes/no and alive/dead of a registry export – so a file coded any of those ways runs without a recode. Every other level is read as an event, and because the module supports competing risks the defaults hand out distinct causes rather than collapsing everything onto Event: the levels are sorted – numerically when every level parses as a number, so 2 precedes 10, by locale otherwise – and take Event, cause 2, cause 3 and cause 4 in that order, a fifth and later collapsing onto cause 4 because the competing-risks cores accept at most four causes. A two-level indicator therefore comes out as one censored and one event role, and a three-level cause code as two causes, which is what unlocks the competing-risks methods for such a file without a click; a user who wants a single-event reading of a multi-level code reassigns the extra levels to Event. The picks are kept per event variable so that switching variables and back does not lose them.

Validation. On the ovarian table status is pre-assigned alive → Censored and died → Event, and the run reports 12 events among 26 women.

Role assignment stops at 20 distinct levels. A role row per level is the whole design, and an event indicator is a small discrete set; a variable with more levels than that is almost always a continuous one picked into the wrong slot – a follow-up time, an identifier – and a hundred dropdowns would neither help nor be answered sensibly. The cap is a UI guard, not a statistical one: the message names the count and asks for a discrete indicator or a recode.

An entry time is honoured where the estimator takes a counting-process response, ignored with a warning where it does not, and refused where ignoring it would go unnoticed. survfit, coxph, survreg and eha::phreg all take Surv(entry, time, event), so the curves, the Cox and parametric fits and the cause-specific competing-risks fits honour delayed entry. survdiff does not support a counting-process Surv – on some versions it silently reads the entry column as a follow-up time, on others it errors – so the log-rank family and its pairwise tests always run on the two-argument Surv, and the card warns that the test ignored the entry time while the curves did not. cmprsk::cuminc has no left-truncation argument, so cumulative incidence ignores the variable and warns that every subject was treated as followed from time zero. cmprsk::crr takes no entry time either, but a Fine-Gray fit has no curve beside it to show the discrepancy, so that combination is refused before the run rather than reported unadjusted; and Surv(type = "interval2") cannot carry a left-truncation time at all, so every interval-censored path refuses it. A row whose entry time is not strictly earlier than its event or censoring time is dropped in JavaScript before binding, because Surv turns it into NA with only an R console warning and the exclusion would otherwise go undisclosed.

The grouping variable is a stratum where the estimator stratifies, a factor covariate where it cannot, and ignored where it would be silently absorbed. coxph takes strata(), which gives each level its own baseline and estimates no coefficient for it – the right reading of “group” in a Cox model, since a group whose hazard is not proportional to the others’ is then no longer required to be. survreg and phreg have no strata, so the parametric fit adds the variable as factor(grp) on the right-hand side, spending one coefficient per level after the first, which the EPV check counts. icenReg::ic_sp accepts strata() in a formula and absorbs it as an ordinary covariate without erroring, so the interval-censored Cox path adds the variable as a factor explicitly and prints a note saying so, rather than let a stratified fit be read where none was made. cmprsk::crr has neither strata nor factor handling of its own, so Fine-Gray ignores the variable with a note and the manual points at the covariates instead. Where the variable is already in the fit, a covariate naming it is refused before the run: beside strata() its coefficient is aliased with the strata and comes back NA, and beside factor(grp) it is the same term twice, one copy NA. Fine-Gray, which leaves the variable out, accepts it as a covariate.

The covariate picker is hidden for the curve-only methods rather than left to discard the pick. Non-parametric curves and cumulative incidence pass no covariates to their cores, so a covariate selected under either would be silently unused; hiding the accordion says so without a message, and the selection is kept so that switching to a regression method restores it.

Censoring

Interval censoring routes each method to a different estimator, and the methods with none are locked before the run. survfit has no non-parametric estimator for interval-censored data, so the survival curve becomes Turnbull’s NPMLE from Icens::EMICM, fitted once per stratum; coxph has no partial likelihood for brackets, so Cox becomes icenReg::ic_sp, a semiparametric proportional-hazards fit whose every standard error is a bootstrap quantity; survreg takes Surv(lower, upper, type = "interval2") directly, so the AFT families fit as they are. eha::phreg takes no interval-censored response, so Gompertz is refused when chosen and dropped from Compare all; cmprsk has no interval-censored path at all, so the Interval-censored option is disabled while cumulative incidence or competing-risks regression is selected and the dropdown snaps back to right-censored – a lock at the control rather than a message after Calculate. An event row whose upper bound is missing or below its lower bound is dropped before binding, since interval2 would read it as a different censoring type; a censored row’s upper bound is set to NA, which the cores translate to Inf.

Turnbull’s NPMLE reports a curve and a median and nothing else. Icens::EMICM returns the mass on each support interval and no variance, so no confidence band is drawn whatever the toggle says; the cumulative hazard would be −log S of a step function identified only on those intervals, so it is not plotted; survdiff has no interval-censored form, so no group comparison runs; and summary(fit, times = …) has no Icens counterpart, so no time-point table is produced. The card carries a note naming the first three. The median is the right endpoint of the first support interval with S ≤ 0.5, not interpolated, since the NPMLE is identified only on its support; a stratum whose EM iteration fails to converge is named in a warning and has no curve.

Analysis methods

Competing-risks methods unlock at two non-censored roles and accept at most four causes. A competing-risks analysis needs at least two causes by definition, so the two methods are disabled in the dropdown until the role table holds two non-censored roles, and the handlers check again for a keyboard-driven entry. Four is the ceiling because the role list offers Event and three numbered causes: the cause-coding path maps each non-censored role to an integer 1 to 4 for cmprsk, and a fifth cause would need a fifth role.

Method-specific options

Fleming–Harrington is survfit’s type = "fleming-harrington", and the cumulative-hazard plot reads fit$cumhaz under either estimator. The estimator setting reaches survfit as its type argument, which changes the survival column alone: fit$cumhaz is the Nelson–Aalen estimate in both cases and fit$std.chaz its standard error, so the cumulative-hazard plot and its band are the same whichever survival estimator was chosen. The band on the cumulative hazard is built from std.chaz so that it is centred on the plotted curve rather than transformed from the survival band.

Validation. On the ovarian table, regimen A at 638 days has a Nelson–Aalen H(t) of 0.787 and a Kaplan–Meier S(t) of 0.431; Fleming–Harrington’s exp(−0.787) = 0.455 lies above it, as the concept page’s arithmetic predicts.

The three weights are survdiff’s ρ; the pairwise tests rerun survdiff per pair with the same ρ and are adjusted as the global setting says, None included. The G-ρ family of Harrington and Fleming weights each event time by S(t)^ρ: ρ = 0 is the log-rank test, ρ = 1 Peto and Peto’s generalisation of the Wilcoxon test, and ρ = 0.5 the compromise the same authors proposed, which is why the control offers exactly those three and no test. With three or more groups the omnibus test says only that the curves differ somewhere, so every pair is retested by subsetting to the two groups and calling survdiff with the same ρ, and the raw p-values go through p.adjust with the method the p-value adjustment setting names – the family-wise choice every other module honours. A setting of None is honoured too rather than overridden: the table is then reported raw and a warning says the family was left uncorrected, because a correction the user switched off and got anyway is a result they cannot reproduce. The RMST differences form their own family, adjusted separately, since they answer a different question from the rank tests.

Validation. On the ovarian table the log-rank test of A against B gives χ²(1) = 1.06, p = .30, and Peto–Peto χ²(1) = 1.68, p = .19 – the early-weighted test reacts to the five early deaths on A.

An empty time-point field produces no table, and a requested time past a stratum’s follow-up is carried forward and greyed. The core builds the table only when times are given – an empty field is not filled with the quartiles: three rows per stratum nobody asked for would print on every run, and the times at which S crosses 0.75, 0.5 and 0.25 belong to the median row and the curve. summary(fit, times, extend = TRUE) is used so that a time beyond a stratum’s last observation still returns a row, carrying the last estimate forward; the row is flagged and greyed on the card, since the value there is not an observation. Entries are parsed on spaces and commas alike, because “12, 24” is what a user types unprompted and a whitespace-only split would turn every value but the last into NaN. Under Turnbull no table is produced.

Efron is the default tie method. coxph’s own default is Efron, and it is the right one for almost every dataset: it approximates the exact partial likelihood closely at negligible cost, where Breslow’s simpler approximation is biased towards zero when ties are common and the exact method is expensive in time. The three are offered because a paper may need to match a package whose default is Breslow, and the exact method serves the rare discrete-time data where ties are the rule.

Validation. The ovarian table has no tied death times, so Efron and Breslow return identical coefficients – 0.1470 for age and −0.8026 for regimen B – as they must when nothing is tied.

The sandwich variance clusters on the subject once a time-varying coefficient has split the follow-up. survSplit turns one subject into one row per period, and those rows are not independent observations; a robust variance treating them as such would understate every standard error, so the time-varying fit passes id = tv_id beside robust = TRUE and coxph clusters on it.

cox.zph runs on identity-transformed time. Its default transform is the Kaplan–Meier scale, which would put a transformed time on the Schoenfeld plot’s x-axis while the axis is labelled in the time variable’s units; transform = "identity" keeps the residuals against raw time so that the plot reads as labelled, at the cost of the slightly better power the KM transform has under heavy censoring. The smoother over the residuals is a loess of span 0.75. A fit with no estimable coefficient – a covariate aliased with the strata under another name, or a constant one – stops cox.zph with a subscript error, so its failure is caught and the card reports the test as not computable rather than losing the run; the martingale, dfbetas and baseline-survival diagnostics tolerate an NA coefficient and need no such fallback.

The log-log plot comes from a Kaplan–Meier fit on the strata, not from the Cox fit. log(−log S(t)) per group is a check of the proportional-hazards assumption for the grouping variable, and the curves have to be estimated without assuming it – a stratified Cox baseline would build the assumption in for the covariates and estimate nothing for the strata. So the plot fits survfit(Surv ~ stratumVar) on the same rows and draws its curves; it is offered only when a grouping variable is set, since it has nothing to compare otherwise.

Martingale residuals are drawn for continuous covariates only, and dfbetas are standardised. A functional-form check asks whether a covariate enters linearly, which a factor cannot fail, so a fit whose covariates are all categorical draws no panel. Influence is reported as residuals(fit, type = "dfbetas") – dfbeta divided by the coefficient’s standard error – so that the values are comparable across coefficients on different scales, 2⁄√n being the usual reference. residuals() returns a bare vector for a single-term fit and no column names, so the columns are named from the coefficient table in coefficient order.

A time-varying coefficient is a piecewise-constant interaction on survSplit episodes, cut at the event-time tertiles by default, with unusable cuts dropped. The covariate leaves the main effects and re-enters as one coefficient per period, interacted with a period factor on the counting-process data survSplit builds; the period factor carries no main effect of its own, because every subject at risk at a given time is in the same period and it cancels out of the partial likelihood. The default cuts are the tertiles of the observed event times – three periods with roughly equal event counts, the smallest split that can show an effect rising or falling – and a requested or default cut is dropped when it would open or close a period holding no events, since such a period estimates nothing; when no cut survives the card says so instead of reporting a fit. Splitting at fixed times leaves every risk set intact, so the unsplit fit is exactly the nested proportional-hazards null of the interval model, and the likelihood-ratio test between them is the test of proportionality the card reports. The EPV check counts the extra coefficients before fitting.

Validation. The ovarian table’s twelve death times split at 329 and 464 days, five, four and three deaths to a period.

Gompertz is eha::phreg, which takes no interval-censored response. The survival package has no Gompertz family, so the one proportional-hazards parametric model is fitted through eha; phreg accepts right-censored and left-truncated Surv objects but not interval2, so Gompertz is refused when chosen directly under interval censoring and dropped from Compare all, whose ranking then covers the five AFT families. In compare mode the EPV check uses Gompertz’s two distribution parameters as the worst case, so the threshold protects the least parsimonious family in the bake-off.

The Kaplan–Meier overlay is withheld under interval censoring. The reference curve is a survfit Kaplan–Meier, which has no interval-censored form; an overlay against Turnbull’s NPMLE would need the Icens fit beside every parametric run, so the card prints a note that the overlay is unavailable instead. For the smooth curve numeric covariates are evaluated at their mean and factor covariates at their reference level, one curve per group when a grouping variable is set.

Cause-specific fits read the Cox card’s ties and robust controls, and Fine-Gray takes a pre-encoded matrix. A cause-specific fit is coxph once per cause with the other causes coded as censored, so it is offered the same ties and variance controls under a card retitled Cause-specific Cox options; the diagnostics stay out, since each is a per-model check and the approach fits one model per cause, and a cause whose fit fails to converge appears as a warning row instead of a table. cmprsk::crr takes no formula – a numeric design matrix only – so the Fine-Gray branch dummy-codes each factor covariate in JavaScript, sorting the levels as R’s factor() would (numerically when every level is a number, by locale collation otherwise) so that the reference level is the one the cause-specific fit would pick, and binds the columns by name.

Sample size and EPV checks

The EPV guard refuses a fit below one event per parameter and warns below ten, counting the parameters the fit will spend. The information about a survival coefficient comes from the events, not the rows, so the guard divides the events the model will see by the coefficients it will estimate. Below one event per parameter the partial likelihood is singular – infinitely many coefficient vectors fit equally well – so the run is refused before R is called; between one and ten it proceeds under a warning, ten per parameter being the conventional working rule for stable estimates. The count is the fit’s own: one per numeric covariate and one per level after the first for a factor; the grouping variable is counted where it enters as a factor covariate – survreg, phreg and ic_sp – and not where coxph takes it as strata(), which estimates no coefficient; a time-varying coefficient’s period coefficients are counted before the fit, from the cuts timeVaryingCuts will keep; a parametric family adds its distribution parameters – one, two for Gompertz, none for the exponential – and Compare all takes Gompertz’s two whether or not Gompertz will be in the ranking, as the Gompertz note says; competing-risks regression is checked on the cause with the fewest events, since each cause is its own model and the sparsest is the one that fails first. A fit that estimates no parameter – a covariate-free exponential fit – is not checked.

Validation. On the ovarian table the Cox model with age and treatment spends two parameters on twelve deaths, six per parameter, and runs under the warning; freeing age as a time-varying coefficient at the default cuts of 329 and 464 days adds two period coefficients – four parameters, three deaths each – and still runs.

Reading results

RMST is restricted to the largest horizon every stratum reaches, and its differences are z-tests between independent strata. summary(fit, rmean = tau) supplies the restricted mean and its standard error per stratum, and τ is the smallest of the strata’s last observed times: rmean = "common" would truncate at the largest time over all strata and carry a shorter curve flat past its own follow-up, extrapolating the arm with the least data. One τ per card is what keeps the strata comparable, and the card names it. A difference between two strata is the difference of their restricted means with a standard error that is the root sum of squares of theirs, since the strata are independent samples; its z and p follow, and with more than one pair the p-values go through p.adjust as the setting says – a family of their own, apart from the pairwise rank tests, since the two answer different questions. The median is quantile(fit, probs = 0.5, conf.int = TRUE), so its interval is at the fit’s confidence level; the median is NA, printed –, when the curve never falls to 0.5, and a bound whose confidence band never falls to 0.5 is NA too, which blanks the interval line.

Validation. On the ovarian table τ is 1106 days, regimen A’s last observation; A averages 649.6 event-free days (SE 120.1) and B 820.3 (SE 97.5), and their difference of −170.7 days has SE √(120.1² + 97.5²) = 154.7, z = −1.10, p = .27. Regimen A’s median is 638 days with a lower limit of 268 and no upper limit, so its interval line is blank; regimen B’s median is not reached.

The exact-time baseline curve is survfit.coxph’s default reference – the model-matrix means, factors included – and the interval-censored baseline is built at the reference levels. survfit(fit) with no newdata evaluates the curve at the column means of the model matrix, which for a factor is the mean of its dummy columns – the sample’s level mix – so the curve is the survival of an average subject rather than of the reference level, and the note under the plot says so rather than let the reader assume the reference level; one curve per stratum falls out of the stratified fit, each with the band survfit supplies at the run’s confidence level. icenReg::getSCurves needs an explicit newdata, so the interval-censored path builds one row per grouping level with numeric covariates at their means and factor covariates at their first level, and survCIs refuses a semiparametric fit, so that curve has no band. The two cards’ reference notes differ because the two references do.

Parametric coefficients are Wald intervals on the log scale, read from survreg’s summary table or from phreg’s coefficient vector. survreg’s summary gives every row – the covariates, the intercept and Log(scale) – a value, a standard error, z and p, and the interval is exp(value ± z·SE) at the run’s confidence level, which is why the intercept and the scale row carry an exponentiated column too: for Log(scale) it is the scale itself, for the intercept the time scale at zero covariates. eha::phreg supplies no summary table, so the Gompertz rows are read from $coefficients with sqrt(diag($var)) as their standard errors, its log(scale) and log(shape) among them. Under interval censoring the event count is taken from the interval2 response’s status column, since the JavaScript event flag is loose there.

The overlay’s smooth curve is the fit’s own quantile function, and the AIC bars carry ΔAIC. For an AFT family the curve is predict(type = "quantile") over survival probabilities 0.01 to 0.99, so it runs as far as the fitted family says 1% survive – often well past the follow-up – and for Gompertz it is S₀(t) from eha::Hgompertz in the canonical parameterisation, which is the one phreg fits, raised to exp of the linear predictor; both at numeric covariates’ means and factor covariates’ reference levels, one curve per grouping level, over a survfit Kaplan–Meier on the same rows. The bars carry ΔAIC rather than AIC because a log-likelihood above zero – routine on short times – makes AIC negative, and a bar anchored at an axis left of it has no length; a family whose fit failed keeps its row in the ranking with ✗ and R’s message under the table, so a missing family is never silent.

Validation. Ranking the six families on the ovarian table with age as covariate and regimen as grouping variable puts the log-normal first at AIC 183.36, the log-logistic 0.77 behind it, the Weibull 2.20, the exponential 5.08, the Gompertz 5.82 and the Gaussian 15.05 – the concept page’s ordering.

CIF confidence bounds are built on the complementary log-log scale, and the pairwise Gray tests are adjusted within each cause. cuminc returns each curve’s estimate and variance, and the bounds are formed on θ = log(−log(1 − F)) with Var(θ) = V ⁄ ((1 − F) log(1 − F))² and mapped back, so they stay inside [0, 1] and widen asymmetrically towards the middle; a point where F is 0 or 1 or the variance is 0 has no transform and collapses to F itself. Gray’s test is cuminc’s own, and with three or more groups it is re-run per pair, each cause’s pairwise p-values adjusted as a family of their own – the same partition as the omnibus tests above them, so a correction never spans causes.

Dropped rows and missing data

Every exclusion is counted in JavaScript before binding, and the module reads no missing-data setting. Each drop rule in the manual’s list runs while the columns are assembled, so the count on the card is exact and every reason is disclosed: Surv would turn an entry time at or after the follow-up time, or a bracket whose upper bound is below its lower, into NA with only a console warning, and the card’s N would then be unexplained. The global missing-data pass has already acted on the loaded data by the time the module reads it, and the module applies no substitution of its own, since an imputed follow-up time or event level would be an observation that never happened.