Time-to-Event Data, Censoring, and Hazard Functions

Michael BrenndoerferOctober 25, 202646 min read

Learn how survival analysis handles right-censored durations, defines hazard and survival functions, and builds the censored-data likelihood from scratch.

Machine Learning from ScratchAll chapters
Part 6 of 15Survival Analysis
Chapter 48Time-to-Event Data, Censoring, and Hazard Functions
Reading the full chapter · 10 sections · 46 min10 sections · 46 min

All 10 sections · 46 min read

Time-to-Event Data, Censoring, and Hazard FunctionsFull chapter · Sections in this chapter1 / 10 ↓
Reading tools Definitions, links & notes

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Notes & comments

Time-to-Event Data, Censoring, and Hazard Functions

A pump is still running when a maintenance study ends. Its recorded duration is twelve months, but its lifetime is not twelve months: the lifetime is greater than twelve months. Treating that record as a failure moves an event to a time when no event occurred. Dropping it loses twelve months of evidence that the pump kept working. Survival analysis starts by preserving that distinction.

Probability Distributions and Descriptive Statistics provide the tools for describing durations. If every event time were observed, we could fit a distribution or summarize the complete durations directly. With incomplete follow-up, some pumps are still running at the administrative cutoff, and we know only a lower bound on their event times. This is right censoring. Ignoring the distinction between an event and a censoring time can distort estimates of how long things last.

This chapter defines the observed duration and event indicator, then connects the survival function, density, and hazard for a positive, absolutely continuous event time. The identity S(t)=exp⁡(−H(t))S(t)=\exp(-H(t)) connects survival probabilities to cumulative hazards. Under independent censoring and the other assumptions stated below, these functions give a likelihood that uses both exact event times and survival up to a censoring time.

By the end, you should be able to interpret right-censored records, construct their parametric likelihood, and explain why ordinary regression on observed durations can target the wrong outcome. The next chapters develop non-parametric and regression estimators:

  • Kaplan–Meier and Nelson–Aalen provide the non-parametric view.
  • The Cox proportional hazards model provides a semi-parametric regression.
  • Accelerated failure-time models provide fully parametric alternatives.

Advantages

The survival framework uses incomplete observations. Under the right censoring assumptions, the likelihood weights an event record by the density f(y)f(y) and a censored record by the survival function S(y)S(y), the probability of surviving past that time. It retains censored follow-up instead of discarding those records.

A second advantage is that the framework accepts data exactly as it is collected. Subjects can have different observed durations. They observe until an administrative cut-off, until a subject drops out, or until an event occurs, whichever comes first. The pair (Y,Δ)(Y, \Delta), the observed duration and a binary event indicator, is a faithful representation of what we know, and modern estimators can consume this representation directly.

A third advantage is interpretation. A continuous-time hazard is an event rate per unit time among subjects who are still event-free. Survival probabilities answer finite-horizon questions such as the probability that a functioning pump fails within the next month. These are related quantities, but a rate is not itself a probability. A valid survival model respects the support of durations; unconstrained linear predictions of a raw duration can, for example, become negative.

Survival models also extend to covariates and time-varying effects. When the target distinguishes competing terminal event types, retain an event-type indicator and define the target, such as a cause-specific hazard or cumulative incidence. A prespecified composite first-event target can use a binary indicator; recurrent or nonterminal events may need another formulation. The binary (Y,Δ)(Y,\Delta) pair alone does not encode those distinctions.

Disadvantages

The main assumption concerns the censoring mechanism. For the likelihood in this chapter, event and censoring times are independent conditional on the covariates used in the analysis, and the censoring law has parameters distinct from the event-time parameters. If unobserved prognosis affects both times, the ordinary likelihood need not estimate the target event-time distribution correctly. Conditional independent censoring resembles a missing-at-random assumption, but it is a statement about the observation process for event times and must be justified for the study.

Interpreting survival output requires choosing the right scale. The coefficient in a Cox model is a log hazard ratio, not a change in expected duration. The same coefficient can be interpreted as a change in the log of the ratio of hazards, and translating that into "how many more months do patients live" requires additional work. If you are used to linear models, you may approach survival output cautiously.

Censoring also reduces information relative to observing the same subjects' complete event times. Precision depends on the number and timing of events, follow-up, risk-set sizes, and model assumptions, rather than on a censoring-percentage threshold alone. A sample with ninety percent censoring may still contain many observed events if the study is large; a small study with fewer censored records can remain imprecise.

Finally, the framework cannot compensate for poor study design. If the observation window is misaligned, if event times are rounded so aggressively that dates become unreliable, or if the time origin is chosen differently across subgroups, estimates can acquire bias or artificial patterns. Fitting a survival model does not automatically repair those data-construction errors. The mathematics assumes we have correctly identified the time origin, the event, the censoring mechanism, and the covariates that matter.

Why ordinary regression can misrepresent event timing

Consider treating every observed duration Yi=min⁡(Ti,Ci)Y_i=\min(T_i,C_i) as an event time. Since Yi≤TiY_i\le T_i, the observed-duration mean cannot exceed the latent event-time mean when those means exist. The inequality is strict if censoring shortens durations with positive probability. An intercept-only least-squares fit to YY therefore estimates E[Y]\mathbb{E}[Y], not E[T]\mathbb{E}[T]. For the exponential example below, counting censorings as events also inflates the fitted rate. These facts do not imply that every misspecified distribution or every regression coefficient has the same bias direction.

Dropping censored records selects subjects with T≤CT\le C. Under independent censoring, the event-time density among observed events is proportional to fT(t)Pr⁡(C≥t)f_T(t)\Pr(C\ge t), so longer event times receive no more selection weight than shorter ones. This can pull complete-case averages downward; strictness requires a nontrivial change in that weight over event times. It does not mean every censored subject lived longer than every observed event. With covariates, the selection can differ across groups, so coefficient bias needs a separate analysis. Ordinary Least Squares (OLS) residual diagnostics alone do not restore the lost observation mechanism.

If covariates affect both event and censoring times, unconditional independence may fail even when conditional independence holds. For example, groups with different prognoses may also have different follow-up schedules. Conditioning on the relevant measured covariates can make an independent-censoring analysis defensible. Dependence that remains after conditioning needs additional assumptions, models, or sensitivity analysis; the ordinary survival likelihood cannot automatically repair it.

Formula

In this section we build the notation from scratch. The destination is a single likelihood expression that correctly handles censored records, but the route matters. We first describe what a censored observation tells us, then introduce the three functions that describe a time-to-event distribution, then connect those functions through the hazard, and only then write the likelihood. Keeping that order in mind makes the formulas feel inevitable rather than arbitrary.

Event times, observation windows, and censoring

Let T>0T>0 be the true time until an event, measured from a defined origin t=0t=0. For a pump, it could be working lifetime measured from installation. We assume here that TT is finite almost surely and has an absolutely continuous distribution, with F(t)=Pr⁡(T≤t)F(t)=\Pr(T\le t) and density f(t)f(t). This excludes a point mass at zero and a permanently event-free fraction. Discrete event times and cure models require corresponding changes to the formulas.

We do not observe every subject's event time directly. Instead, each subject ii is followed over an observation window. Denote by Ci≥0C_i \ge 0 the time, measured from the same origin, at which observation of subject ii ends if the event has not yet occurred. CiC_i can come from an administrative cutoff ("the study ends in December"), from a dropout ("the patient moves to a different hospital"), or from a loss to follow-up. Whatever the mechanism, the data we record is the pair

Yi=min⁡(Ti,Ci),Δi=1{Ti≤Ci}.Y_i = \min(T_i, C_i), \qquad \Delta_i = \mathbf{1}\{T_i \le C_i\}.

where:

  • TiT_i: the true (latent) event time for subject ii, measured from the time origin t=0t = 0
  • CiC_i: the potential censoring time for subject ii, the time observation would end if no event had occurred
  • YiY_i: the observed time, the smaller of the event and censoring times, so Yi≤TiY_i \le T_i always
  • Δi\Delta_i: the event indicator, equal to one when the event is observed and zero when the record is censored
  • 1{⋅}\mathbf{1}\{\cdot\}: the indicator function, which equals one when its argument is true and zero otherwise

Here YiY_i is the observed time and Δi\Delta_i is the event indicator, equal to one if we saw the event and zero if the observation ended first. This single binary flag encodes a lot of information. Δi=1\Delta_i = 1 means we know the exact event time, and Δi=0\Delta_i = 0 means we only know Ti>YiT_i > Y_i. The pair (Yi,Δi)(Y_i,\Delta_i) is therefore the fundamental data unit of survival analysis. It is not a pair of unrelated numbers. It is a compressed statement about an event that either happened exactly at YiY_i or had not yet happened by YiY_i.

Right censoring means the event is later than a known bound. Left censoring means the event has already occurred but its exact time is unknown, so only an upper bound Ti≤yiT_i\le y_i is recorded. Interval censoring gives bounds such as ai<Ti≤bia_i<T_i\le b_i. Under an appropriate independent observation process, these contribute S(yi)S(y_i), F(yi)F(y_i), and F(bi)−F(ai)F(b_i)-F(a_i) respectively. They are different observation schemes, not interchangeable inputs to a right-censoring estimator. Ordinary missingness, with neither a known event time nor a valid bound, supplies less information than these records.

Delayed entry, or left truncation, is different from left censoring. A subject enters at age Li>0L_i>0 only if still event-free then: subjects with Ti≤LiT_i\le L_i are absent from the observed cohort. For example, a registry that enrolls only patients alive several months after diagnosis selects survivors. We observe their entry ages and subsequent follow-up, and must account for the selection. Merely starting otherwise fully observed subjects on different calendar dates is not left truncation when each is followed from its own relevant time origin.

Survival, density, and hazard functions

With the data unit fixed, we need a way to describe how durations are distributed. Three functions do the job, and each answers a different practical question. The first is the survival function

S(t)=P(T>t)=1−F(t),S(t) = P(T > t) = 1 - F(t),

This is the probability that the subject is event-free at time tt. We have S(0)=1S(0) = 1, and as t→∞t \to \infty, S(t)→0S(t) \to 0. The survival function is non-increasing. It is the natural first object in survival analysis because a censored record does not tell us when the event happened. It tells us that the event had not happened before the observation ended. The survival function is exactly the probability of that kind of statement.

The second is the probability density function f(t)=F′(t)=−S′(t)f(t) = F'(t) = -S'(t), which is the familiar object from Probability Distributions. The density is the derivative of the CDF, so integrating ff over an interval gives the probability that TT falls in that interval. It answers a different question from the survival function. Rather than "has the event happened yet?" it asks "how much probability mass sits near this particular time?" If every duration were fully observed, the density would be the most direct object to model.

The third is the hazard function

h(t)=lim⁡Δt→0+P(t≤T<t+Δt∣T≥t)Δt.h(t) = \lim_{\Delta t \to 0^{+}} \frac{P(t \le T < t + \Delta t \mid T \ge t)}{\Delta t}.

where:

  • h(t)h(t): the hazard function, the instantaneous event rate at time tt among those still at risk
  • TT: the event time for a generic subject
  • Δt\Delta t: a small positive time increment taken to zero in the limit
  • P(t≤T<t+Δt∣T≥t)P(t \le T < t + \Delta t \mid T \ge t): the conditional probability that the event occurs in the next increment, given survival to time tt
  • the denominator Δt\Delta t converts that probability into a rate per unit time

Read this out loud: the hazard is the instantaneous rate at which the event occurs in the next tiny slice of time, given that it has not occurred yet. The conditioning event {T≥t}\{T \ge t\} is what distinguishes the hazard from the density. The density measures the unconditional probability of the event near tt. The hazard measures the conditional intensity, so it captures how risky the next instant is for a subject who has survived up to tt. This conditional view is what makes censored data manageable. A censored subject contributes information about surviving up to the censoring time, and the hazard is the function that describes risk among exactly those survivors.

To express the hazard in terms of ff and SS, start from the definition of conditional probability. For small positive Δt\Delta t,

P(t≤T<t+Δt∣T≥t)=P(t≤T<t+Δt)P(T≥t).P(t \le T < t + \Delta t \mid T \ge t) = \frac{P(t \le T < t + \Delta t)}{P(T \ge t)}.

The numerator is approximately f(t) Δtf(t)\, \Delta t because the density integrates to the probability over a short interval, and the denominator is P(T≥t)=S(t)P(T \ge t) = S(t) for a continuous TT (equality holds because the point mass at tt is zero). Divide by Δt\Delta t and take the limit:

h(t)=f(t)S(t)=−S′(t)S(t)=−ddtlog⁡S(t).h(t) = \frac{f(t)}{S(t)} = \frac{-S'(t)}{S(t)} = -\frac{d}{dt} \log S(t).

These equalities hold where S(t)>0S(t)>0 and the required derivatives exist. The hazard is the negative log-derivative of the survival function. Here S′(t)S'(t) denotes the derivative of SS with respect to tt, and the final equality uses the chain rule for the logarithm, ddtlog⁡S(t)=S′(t)/S(t)\frac{d}{dt}\log S(t) = S'(t)/S(t).

Because −ddtlog⁡S(t)=h(t)-\frac{d}{dt} \log S(t) = h(t), integrating both sides from zero to tt gives

log⁡S(t)−log⁡S(0)=−∫0th(u) du.\log S(t) - \log S(0) = -\int_0^t h(u)\, du.

Since S(0)=1S(0) = 1, we have log⁡S(0)=0\log S(0) = 0 and

S(t)=exp⁡ ⁣(−∫0th(u) du).S(t) = \exp\!\left( -\int_0^t h(u)\, du \right).

Define the cumulative hazard H(t)=∫0th(u) duH(t) = \int_0^t h(u)\, du, so the identity becomes

S(t)=exp⁡(−H(t)).S(t) = \exp(-H(t)).

This identity connects survival probabilities and cumulative hazards for the continuous-time models considered here. It says that if we know the hazard at every time, we know the survival function by integration and exponentiation, and vice versa. It is also the reason hazard-based modeling is practical. We can write a model in terms of the hazard when event rates are the scientific target, and still evaluate the survival probability that the likelihood needs.

For a right-censored record, the event-time likelihood contribution is fθ(yi)f_\theta(y_i) for an observed event and Sθ(yi)S_\theta(y_i) for censoring, after removing factors from an independent censoring mechanism that do not depend on θ\theta. A density is not a point probability: for continuous times, Pr⁡(T=yi)=0\Pr(T=y_i)=0, whereas fθ(yi)f_\theta(y_i) measures density per unit time. Assuming independent subjects and the censoring conditions detailed next, the event-time likelihood, up to a parameter-free factor, is

L(θ)=∏i=1nfθ(yi)δiSθ(yi)1−δi.L(\theta) = \prod_{i=1}^n f_\theta(y_i)^{\delta_i} S_\theta(y_i)^{1 - \delta_i}.

Substituting fθ(y)=hθ(y)Sθ(y)f_\theta(y) = h_\theta(y) S_\theta(y),

L(θ)=∏i=1nhθ(yi)δiSθ(yi)=∏i=1nhθ(yi)δiexp⁡(−Hθ(yi)).L(\theta) = \prod_{i=1}^n h_\theta(y_i)^{\delta_i} S_\theta(y_i) = \prod_{i=1}^n h_\theta(y_i)^{\delta_i} \exp(-H_\theta(y_i)).

Taking logarithms turns the product into a sum:

ℓ(θ)=∑i=1n[δilog⁡hθ(yi)−Hθ(yi)].\ell(\theta) = \sum_{i=1}^n \Big[ \delta_i \log h_\theta(y_i) - H_\theta(y_i) \Big].

where:

  • ℓ(θ)\ell(\theta): the log-likelihood as a function of the parameter vector θ\theta
  • δilog⁡hθ(yi)\delta_i \log h_\theta(y_i): the event contribution, present only when δi=1\delta_i = 1
  • −Hθ(yi)-H_\theta(y_i): the negative cumulative hazard contribution, paid by every record whether event or censoring

This full log-likelihood is the starting point for the parametric right-censoring fits below; other estimators and observation schemes modify the objective. Every event contributes the log of the hazard at its event time. Every record contributes the negative cumulative hazard at its observed time, whether it was an event or a censoring. The formula is compact because the event indicator does exactly the bookkeeping we need. It switches on the density term for observed events and leaves only the survival term for censored records.

Censoring assumptions and delayed entry

The single likelihood in the previous section relies on one key factorization. If the event and censoring are independent random variables given covariates xix_i, that is,

Ti⊥ ⁣⊥Ci∣xi,T_i \perp\!\perp C_i \mid x_i,

where xix_i contains the conditioning covariates. For continuous censoring times, the joint density of (Yi,Δi)(Y_i,\Delta_i), with respect to time and the binary indicator, is

pθ(yi,δi∣xi)=[fT(yi∣xi)SC(yi∣xi)]δi[ST(yi∣xi)fC(yi∣xi)]1−δi.p_\theta(y_i,\delta_i\mid x_i)=\big[f_T(y_i\mid x_i)S_C(y_i\mid x_i)\big]^{\delta_i}\big[S_T(y_i\mid x_i)f_C(y_i\mid x_i)\big]^{1-\delta_i}.

Here fCf_C and SCS_C are the censoring density and survival function. If their parameters are distinct from θ\theta, their factor SC(yi∣xi)δifC(yi∣xi)1−δiS_C(y_i\mid x_i)^{\delta_i}f_C(y_i\mid x_i)^{1-\delta_i} can be removed when estimating θ\theta. It depends on the event indicator as well as on time. Fixed administrative censoring has atoms rather than a continuous density, but conditioning on a predetermined independent follow-up schedule gives the same event-density and event-survival contributions. The product likelihood also assumes subjects are independent. Estimation beyond the observed follow-up range remains dependent on the model and on available support.

If conditional independence fails, removing the censoring mechanism is generally unjustified. Selective loss of subjects with poor prognoses can conceal early events, but the size and direction of distortion depend on the joint process and the target. Inverse-probability-of-censoring weights require conditional independence given measured history, positive probabilities of remaining observed over the target horizon, and an adequate censoring model. They do not identify unobserved dependence automatically. Joint models or sensitivity analyses address different additional assumptions. A dropout is not automatically a competing event; a competing event must preclude the event of interest under the chosen definition.

For delayed entry, suppose a subject is included only if Ti>LiT_i>L_i, and is observed to Yi≥LiY_i\ge L_i. Under non-informative entry and censoring conditional on the relevant covariates, the event-time likelihood is conditioned on survival to entry. Its contribution is

fθ(yi)δiSθ(yi)1−δiSθ(Li),\frac{f_\theta(y_i)^{\delta_i} S_\theta(y_i)^{1 - \delta_i}}{S_\theta(L_i)},

where:

  • LiL_i: the delayed-entry (left-truncation) time at which subject ii first becomes observable, with Li>0L_i > 0
  • Sθ(Li)=Pr⁡(Ti>Li)S_\theta(L_i) = \Pr(T_i > L_i): the probability that a subject with parameters θ\theta survives to the entry time, the conditioning term
  • the numerator is the ordinary density or survival contribution evaluated at yiy_i from the original time origin; division by Sθ(Li)S_\theta(L_i) conditions it on survival to entry

The denominator conditions on the selection Ti>LiT_i>L_i. Non-parametric methods implement this by admitting subjects to risk sets only after entry. It matters even for an exponential model: S(yi)/S(Li)=exp⁡[−λ(yi−Li)]S(y_i)/S(L_i)=\exp[-\lambda(y_i-L_i)], giving exposure yi−Liy_i-L_i and, when D>0D>0 and total post-entry exposure is positive, rate estimate D/∑i(Yi−Li)D/\sum_i(Y_i-L_i), rather than D/∑iYiD/\sum_iY_i. With no events and positive exposure there is no positive interior rate estimate; zero exposure also needs a separate report. For the six durations below, entry at Li=0.5L_i=0.5 would change exposure from 2121 to 1818 and the rate from 4/214/21 to 4/184/18. Memorylessness preserves the exponential family for residual lifetime; it does not justify ignoring entry. Independence of entry and event time, possibly conditional on covariates, is an additional assumption and can fail in survivor cohorts.

Mathematical properties

Several properties follow directly from the definitions and are worth pinning down before we start estimating. They are not decoration. Each one will be used either to check an estimate or to simplify a likelihood.

  • The survival function S(t)S(t) is non-increasing, right-continuous, satisfies S(0)=1S(0) = 1, and tends to zero as t→∞t \to \infty. Accumulated failures can only reduce the probability of being event-free.
  • The hazard is non-negative where it is defined, with S(t)>0S(t)>0. At a finite endpoint where survival reaches zero, the ratio f(t)/S(t)f(t)/S(t) is not defined.
  • The cumulative hazard is non-decreasing with H(0)=0H(0)=0. As survival approaches zero, H=−log⁡SH=-\log S diverges; this can occur at a finite upper endpoint as well as in an infinite-time limit.
  • The three representations (F,S,h)(F, S, h) are equivalent. Knowing any one determines the other two via f=−S′f = -S' and h=f/Sh = f/S, or via S=exp⁡(−H)S = \exp(-H).
  • The expected event time satisfies E[T]=∫0∞S(t) dt\mathbb{E}[T] = \int_0^{\infty} S(t)\, dt, provided the integral is finite. This is the "survival-curve area" formula, and it is useful for comparing estimators.
  • For ordered discrete event times, define hs=Pr⁡(T=s∣T≥s)h_s=\Pr(T=s\mid T\ge s). Then S(t)=∏s≤t(1−hs)S(t)=\prod_{s\le t}(1-h_s). The log cumulative hazard −log⁡S(t)=∑s≤t−log⁡(1−hs)-\log S(t)=\sum_{s\le t}-\log(1-h_s) differs from the sum of discrete hazards ∑s≤ths\sum_{s\le t}h_s; one cannot substitute the latter into the continuous-time exponential identity exactly.
  • If a covariate xx acts on the hazard as h(t∣x)=h0(t)eβxh(t \mid x) = h_0(t) e^{\beta x}, where h0(t)h_0(t) is the baseline hazard (the hazard when x=0x = 0) and β\beta is a regression coefficient, then β\beta is the log hazard ratio and S(t∣x)=S0(t)eβxS(t \mid x) = S_0(t)^{e^{\beta x}}. This is the proportional hazards structure we will develop in Cox proportional hazards.

Visualizing Survival, Censoring, and Hazards

Before fitting anything, it is worth looking at the shapes involved. The three quantities S(t)S(t), h(t)h(t), and H(t)H(t) move together, and the way they move is easiest to grasp from a figure. We will use a Weibull family because it conveniently covers the three qualitative hazard shapes: constant (exponential as a special case), increasing, and decreasing.

The Weibull survival function with shape k>0k > 0 and scale λ>0\lambda > 0 is S(t)=exp⁡ ⁣(−(t/λ)k)S(t) = \exp\!\left(-(t/\lambda)^k\right), and the corresponding hazard is h(t)=kλ(tλ)k−1h(t) = \frac{k}{\lambda}\Big(\frac{t}{\lambda}\Big)^{k-1}. Here kk controls the shape of the hazard and λ\lambda is the scale parameter measured in the same time units as tt. Note that this scale λ\lambda plays a different role from the exponential rate λ\lambda used in the worked example below, where λ\lambda is the constant hazard. When k=1k = 1 the hazard is constant at 1/λ1/\lambda. When k>1k > 1 the hazard increases with time (wear-out). When k<1k < 1 the hazard decreases with time (infant mortality). Increasing, decreasing, and approximately constant hazards over particular time ranges have been observed in component reliability data. Understanding these survival curves is a first diagnostic tool.

PythonIn [3]
CollapseExpand
def weibull_survival(t, shape, scale):
    return np.exp(-((t / scale) ** shape))


def weibull_hazard(t, shape, scale):
    return (shape / scale) * (t / scale) ** (shape - 1.0)


t_grid = np.linspace(0.01, 20.0, 400)
t_grid_survival = np.linspace(0.0, 20.0, 400)

scenarios = [
    dict(name="Exponential (shape = 1)", shape=1.0, scale=5.0),
    dict(name="Weibull increasing (shape = 2)", shape=2.0, scale=5.0),
    dict(name="Weibull decreasing (shape = 0.5)", shape=0.5, scale=5.0),
]

for s in scenarios:
    s["S"] = weibull_survival(t_grid_survival, s["shape"], s["scale"])
    s["h"] = weibull_hazard(t_grid, s["shape"], s["scale"])

The next two figure cells are split purely for presentation. Both read the precomputed grids and the scenario dictionaries, and neither recomputes the data. The first shows the survival functions. The second shows the hazards.

PythonIn [4]
CollapseExpand
from book_plot_style import PALETTE, polish_axes, use_book_style


def pal(i: int):
    keys = ("primary", "amber", "teal", "rose")
    return PALETTE[keys[i % len(keys)]]


use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
for i, s in enumerate(scenarios):
    ax.plot(
        t_grid_survival,
        s["S"],
        color=pal(i),
        linewidth=1.1,
        linestyle=("-", "--", "-.")[i],
        label=s["name"],
    )
ax.set_xlabel("Time t")
ax.set_ylabel("Survival probability S(t)")
ax.set_title("Weibull survival at three shapes")
ax.set_ylim(0, 1.02)
ax.legend(loc="upper right")
polish_axes(ax)
plt.tight_layout()
plt.show()
VisualizationOut [4]
CollapseExpand
Three decreasing curves start at one and cross at time 5. The shape-0.5 curve is steepest near zero and lies highest after the crossing; the shape-2 curve lies highest before the crossing and lowest afterward.
Survival functions for three Weibull shapes with scale 5. All three curves meet at time 5 with survival exp(-1). The shape-0.5 curve falls steepest near zero and has the highest survival after time 5, while the shape-2 curve has the highest survival before time 5 and the lowest after time 5.

The survival plot makes two things visible. First, all three start at S(0)=1S(0) = 1 and eventually decay to zero, as required. Second, the shape parameter changes the shape of the decay. The decreasing-hazard case (infant mortality) drops steeply early and then flattens, so its survival curve has a long tail. The increasing-hazard case (wear-out) stays near one for a while and then falls rapidly. These are two ends of the stated monotone-hazard spectrum. Real data require their own hazard diagnostics.

PythonIn [5]
CollapseExpand
import matplotlib.pyplot as plt
from book_plot_style import PALETTE, polish_axes, use_book_style

use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
for i, s in enumerate(scenarios):
    ax.plot(
        t_grid,
        s["h"],
        color=pal(i),
        linewidth=1.1,
        linestyle=("-", "--", "-.")[i],
        label=s["name"],
    )
ax.set_xlabel("Time t")
ax.set_ylabel("Hazard h(t)")
ax.set_title("Weibull hazards at three shapes")
ax.set_ylim(0, 2.4)
ax.legend(loc="upper right")
polish_axes(ax)
plt.tight_layout()
plt.show()
VisualizationOut [5]
CollapseExpand
One flat horizontal line, one line rising steadily from near zero, and one line dropping sharply from a high value and flattening near zero.
Hazard functions for the same three Weibull scenarios, with constant exponential, rising shape-2, and falling shape-0.5 hazards. The constant line reflects a memoryless exponential lifetime, the rising shape-2 hazard describes wear-out, and the falling shape-0.5 hazard describes infant mortality followed by lower risk among survivors.

A constant hazard means the same instantaneous failure rate at each age; for an exponential lifetime it also gives a memoryless residual lifetime. A rising hazard can describe wear-out, while a falling hazard can describe early failures followed by lower risk among survivors. A decreasing population hazard alone does not prove that individual devices become stronger. The hazard grid starts at 0.010.01 because the shape-0.50.5 hazard diverges as t↓0t\downarrow0; the survival grid includes zero.

Cumulative hazard H(t)H(t) is dimensionless and increases at rate h(t)h(t). At a given time, a larger H(t)H(t) corresponds to a smaller survival probability through S(t)=exp⁡(−H(t))S(t)=\exp(-H(t)). The absolute rate of decline of survival is h(t)S(t)h(t)S(t), so a larger hazard alone does not imply a steeper survival curve when the survival levels differ.

PythonIn [6]
CollapseExpand
import numpy as np

cumulative_hazards = []
for scenario in scenarios:
    cumulative_hazards.append(
        dict(name=scenario["name"], H=-np.log(scenario["S"]))
    )
VisualizationOut [7]
CollapseExpand
Three rising cumulative-hazard curves; one rises fastest early, one fastest late.
Cumulative hazard for the three Weibull scenarios. The shape-0.5 curve grows steeply near zero and then flattens, while the shape-2 curve steepens with time. At each time, the scenario with larger cumulative hazard has smaller survival probability because S(t) = exp(-H(t)).

For the censoring plot, imagine devices installed at independent, uniformly distributed calendar dates during a fifteen-unit recruitment period, with follow-up ending at a common administrative cutoff. Each device is followed from installation, so its potential follow-up duration CC is uniform from zero to fifteen and independent of its simulated lifetime TT. This is staggered recruitment without left truncation. Horizontal segments end at Y=min⁡(T,C)Y=\min(T,C); diamonds mark failures and squares mark censorings. A censoring marker locates the end of observation, not an unobserved failure.

PythonIn [8]
CollapseExpand
rng = np.random.default_rng(0)
n_swim = 40
T_swim = rng.exponential(scale=5.0, size=n_swim)
C_swim = rng.uniform(0, 15.0, size=n_swim)
Y_swim = np.minimum(T_swim, C_swim)
delta_swim = (T_swim <= C_swim).astype(int)

order = np.argsort(Y_swim)
Y_swim = Y_swim[order]
delta_swim = delta_swim[order]
PythonIn [9]
CollapseExpand
import matplotlib.pyplot as plt
from book_plot_style import PALETTE, polish_axes, use_book_style

use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.5))
for i, (y, d) in enumerate(zip(Y_swim, delta_swim)):
    color = pal(0) if d == 1 else pal(1)
    ax.plot([0.0, y], [i, i], color=color, linewidth=1.1)
    marker = "D" if d == 1 else "s"
    ax.plot(y, i, marker=marker, color=color, markersize=5)
ax.set_xlabel("Observed time Y (simulated units)")
ax.set_ylabel("Subject index (sorted by Y)")
ax.set_title("Observed failures and censoring")
ax.legend(
    handles=[
        plt.Line2D(
            [],
            [],
            color=pal(0),
            marker="D",
            linestyle="-",
            label="Observed event",
        ),
        plt.Line2D(
            [],
            [],
            color=pal(1),
            marker="s",
            linestyle="-",
            label="Right-censored",
        ),
    ],
    loc="lower right",
)
polish_axes(ax)
plt.tight_layout()
plt.show()
VisualizationOut [9]
CollapseExpand
Forty horizontal segments starting at zero, sorted by length. Some end with diamond markers, others with square markers, indicating events and censoring.
Right censoring in a simulated device study, with each horizontal line representing one device ordered by observed time. Diamonds mark observed failures and squares mark censored records, so for censored records the true failure time lies somewhere to the right of the marker.

This is the visual form of the (Yi,Δi)(Y_i, \Delta_i) representation. Read each line as "device ii was observed from 0 to YiY_i; the marker tells us whether the device failed or was still running." The censored lines are not wrong or missing. They carry important information, namely that the true failure time is greater than the endpoint we see. A correct survival likelihood gives those records weight S(Yi)S(Y_i) for the region to the right of the marker, which is exactly the probability that the event had not yet occurred at the moment we stopped observing.

Example

To see the likelihood in action, let us work through a small hand-computable example. Suppose we observe six subjects, each of whose observed times and event indicators are as follows.

Observed times and event indicators for six subjects.
Subject iiObserved time YiY_iEvent Δi\Delta_i
121
231
350
411
541
660

Four of the six records are observed events, and two are right-censored. We do not know the underlying T3T_3 or T6T_6. All we know is that T3>5T_3 > 5 and T6>6T_6 > 6. Those two censored records are the entire reason this example is worth doing. If we knew all six event times, the exercise would be an ordinary exponential fit. Because we do not, the example shows how the likelihood turns partial information into a usable estimate. Suppose we adopt an exponential model, h(t)=λh(t) = \lambda for all tt, so that S(t)=e−λtS(t) = e^{-\lambda t}, f(t)=λe−λtf(t) = \lambda e^{-\lambda t}, and H(t)=λtH(t) = \lambda t. The log-likelihood becomes

ℓ(λ)=∑i=16[Δilog⁡h(Yi)−H(Yi)]=Dlog⁡λ−λ∑i=16Yi,\ell(\lambda) = \sum_{i=1}^{6} \Big[ \Delta_i \log h(Y_i) - H(Y_i) \Big] = D \log \lambda - \lambda \sum_{i=1}^{6} Y_i,

where D=∑iΔiD = \sum_i \Delta_i is the number of events and the sum inside the second term is over all records, not just the events.

The two sufficient statistics are

D=1+1+0+1+1+0=4,∑i=16Yi=2+3+5+1+4+6=21.D = 1 + 1 + 0 + 1 + 1 + 0 = 4, \qquad \sum_{i=1}^{6} Y_i = 2 + 3 + 5 + 1 + 4 + 6 = 21.

To maximize ℓ(λ)\ell(\lambda), take the derivative with respect to λ\lambda:

dℓdλ=Dλ−∑i=16Yi,\frac{d\ell}{d\lambda} = \frac{D}{\lambda} - \sum_{i=1}^{6} Y_i,

which is the standard one-variable calculus problem from Maximum Likelihood Estimation. Setting it to zero gives λ^=D/∑iYi=4/21≈0.1905\hat{\lambda} = D / \sum_i Y_i = 4 / 21 \approx 0.1905. The second derivative is −D/λ2<0-D/\lambda^2 < 0, so this is a maximum. Notice what the estimator is doing. It divides the number of observed events by the total time that subjects were under observation. Censored records still contribute exposure time to the denominator, which is exactly how they inform the hazard without pretending that their events were observed.

Two alternative estimates reveal what the correct likelihood is protecting us from.

If we treat every observed record as an event, that is, we ignore censoring completely, we would maximize the product of densities over all six records, ∏iλe−λYi\prod_i \lambda e^{-\lambda Y_i}, whose logarithm is ℓnaive(λ)=6log⁡λ−λ∑i=16Yi=6log⁡λ−21λ\ell_{\text{naive}}(\lambda) = 6 \log \lambda - \lambda \sum_{i=1}^{6} Y_i = 6 \log \lambda - 21 \lambda, since ∑i=16Yi=21\sum_{i=1}^{6} Y_i = 21. The maximizer is λ^naive=6/21≈0.2857\hat{\lambda}_{\text{naive}} = 6/21 \approx 0.2857, which is fifty percent higher than the honest estimate. That corresponds to an expected lifetime of 1/0.2857≈3.501 / 0.2857 \approx 3.50 time units, versus 1/0.1905≈5.251 / 0.1905 \approx 5.25 for the correct fit.

If instead we drop the two censored records, the "complete cases" approach, we maximize ∑Δi=1log⁡(λe−λYi)=4log⁡λ−λ∑Δi=1Yi=4log⁡λ−10λ\sum_{\Delta_i = 1} \log(\lambda e^{-\lambda Y_i}) = 4 \log \lambda - \lambda \sum_{\Delta_i = 1} Y_i = 4 \log \lambda - 10 \lambda, where the total observed time ∑Δi=1Yi=2+3+1+4=10\sum_{\Delta_i = 1} Y_i = 2 + 3 + 1 + 4 = 10 only includes the four observed events. The maximizer is λ^drop=4/10=0.4\hat{\lambda}_{\text{drop}} = 4/10 = 0.4, which is 2.12.1 times the censoring-aware estimate, or more than double it. That corresponds to an expected lifetime of 2.52.5 time units.

PythonIn [10]
CollapseExpand
Y_ex = np.array([2.0, 3.0, 5.0, 1.0, 4.0, 6.0])
d_ex = np.array([1, 1, 0, 1, 1, 0])

D_ex = int(d_ex.sum())
sumY_ex = float(Y_ex.sum())

lam_hat = D_ex / sumY_ex
lam_naive = len(Y_ex) / sumY_ex
sumY_event = float(Y_ex[d_ex == 1].sum())
lam_drop = D_ex / sumY_event

# Independent library comparison: an exponential distribution with fixed location.
reference_data = CensoredData.right_censored(Y_ex, d_ex == 0)
reference_loc, reference_scale = expon.fit(reference_data, floc=0.0)
lam_reference = 1.0 / reference_scale
np.testing.assert_allclose(lam_reference, lam_hat, rtol=1e-10, atol=1e-12)

ll_hat = D_ex * np.log(lam_hat) - lam_hat * sumY_ex
ll_naive = len(Y_ex) * np.log(lam_naive) - lam_naive * sumY_ex
ll_drop = D_ex * np.log(lam_drop) - lam_drop * sumY_event
Out[11]:
estimate lambda_hat expected_lifetime
0 correct MLE 0.1905 5.25
1 treat censored as events 0.2857 3.50
2 drop censored 0.4000 2.50

SciPy's CensoredData.right_censored marks the two censored observations, and expon.fit(..., floc=0) estimates the exponential scale while holding the location at zero. Its reciprocal agrees with 4/214/21 to the tolerance asserted in the executed cell. This checks the six-record exponential case against an established censored-distribution implementation; it is not a check of covariate regression or delayed entry.

On these six records, treating censorings as events increases the rate from 4/214/21 to 6/216/21, and dropping them gives 4/104/10. The censored durations contribute eleven units of known event-free exposure. Dropping them removes that exposure without changing the event count. This calculation demonstrates the effect on this dataset; it is not proof of the direction of every regression bias. The three fits use different observation rules, so their maximized objective values would not be valid likelihood comparisons on a common dataset.

To see the maximum visually, plot the log-likelihood as a function of λ\lambda.

PythonIn [12]
CollapseExpand
lam_grid = np.linspace(0.05, 0.45, 200)
ll_grid = D_ex * np.log(lam_grid) - lam_grid * sumY_ex
PythonIn [13]
CollapseExpand
import matplotlib.pyplot as plt
from book_plot_style import PALETTE, polish_axes, use_book_style

use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(lam_grid, ll_grid, color=pal(0), linewidth=1.1, label="log-likelihood")
ax.axvline(
    lam_hat,
    color=pal(1),
    linestyle="--",
    linewidth=1.0,
    label=f"MLE  $\\hat\\lambda = {lam_hat:.4f}$",
)
ax.axvline(
    lam_naive,
    color=pal(2),
    linestyle=":",
    linewidth=1.0,
    label=f"naive $\\hat\\lambda = {lam_naive:.4f}$",
)
ax.scatter([lam_hat], [ll_hat], color=pal(1), zorder=5)
ax.set_xlabel("$\\lambda$")
ax.set_ylabel("log-likelihood $\\ell(\\lambda)$")
ax.set_title("Exponential rate: six-record likelihood")
ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2))
polish_axes(ax)
plt.tight_layout()
plt.show()
VisualizationOut [13]
CollapseExpand
A concave curve peaking at a small positive value of lambda, with a dashed line at its maximum and a dotted vertical line further to the right marking the naive estimate.
Log-likelihood profile for the exponential model on six records, four events and two censored. The concave curve peaks at the censoring-aware MLE of about 0.1905, marked by the dashed line, while the naive estimate that counts censorings as events sits further to the right, showing how the censoring indicator affects the fitted rate.

For this exponential rate objective, D>0D>0 and positive total exposure imply a strictly negative second derivative −D/λ2-D/\lambda^2 and a unique positive maximizer. The claim is about this parameterization and dataset, not a general concavity guarantee for every survival family or optimizer. The dashed line marks the maximum; the dotted naive estimate sits to its right. Next we compare the analytic estimate, a primitive numerical objective, and a library fit that directly accepts censored observations.

Implementation in NumPy and SciPy

For larger datasets, code can accumulate the event count and exposure while retaining the same closed-form exponential estimator. We now build the log-likelihood from primitives, fit it with SciPy, and compare with the analytic estimator. This is a reusable pattern for continuous parametric survival models fitted by full right-censoring likelihood; other estimators use different objectives or risk-set calculations. We encode the hazard, encode the cumulative hazard, sum the log-likelihood contributions, and minimize the negative. The hand calculation gives us a known answer to check against. The implementation gives us a procedure that scales.

Our simulation returns (Y,Δ)(Y,\Delta) with exponential latent lifetimes and independent uniform follow-up windows. As in the swimlane example, varying administrative follow-up can represent uniformly staggered installations with a common calendar cutoff. Latent event times are retained only as a teaching diagnostic; fitting uses the observed pair.

PythonIn [14]
CollapseExpand
def simulate_exponential_censored(n, lam_true, censoring_window, rng):
    """Simulate right-censored exponential event times and a binary event indicator."""
    T_true = rng.exponential(scale=1.0 / lam_true, size=n)
    C_time = rng.uniform(0.0, censoring_window, size=n)
    Y_obs = np.minimum(T_true, C_time)
    delta = (T_true <= C_time).astype(int)
    return Y_obs, delta, T_true


lam_true = 0.15
censoring_window = 12.0
rng = np.random.default_rng(7)

Y_train, d_train, T_train = simulate_exponential_censored(
    500, lam_true, censoring_window, rng
)
Y_test, d_test, _ = simulate_exponential_censored(
    200, lam_true, censoring_window, rng
)

We now derive the log-likelihood from scratch. For the exponential model with rate λ\lambda, h(y)=λh(y) = \lambda and H(y)=λyH(y) = \lambda y, so the negative log-likelihood is

−ℓ(λ)=λ∑iYi−∑iΔilog⁡λ.-\ell(\lambda) = \lambda \sum_i Y_i - \sum_i \Delta_i \log \lambda.

where:

  • λ\lambda: the exponential rate (constant hazard), with λ>0\lambda > 0
  • YiY_i: the observed time for record ii
  • Δi\Delta_i: the event indicator for record ii
  • ∑iYi\sum_i Y_i: total time under observation, including censored records
  • ∑iΔi\sum_i \Delta_i: total number of observed events

We minimize this with SciPy's minimize. A small guard rejects any non-positive λ\lambda, because log⁡λ\log \lambda is undefined there.

PythonIn [15]
CollapseExpand
def negative_log_likelihood_exponential(lam, Y, delta):
    lam = float(np.asarray(lam).reshape(-1)[0])
    if not np.isfinite(lam) or lam <= 0:
        return np.inf
    return -(delta.sum() * np.log(lam) - lam * Y.sum())


fit_result = minimize(
    fun=negative_log_likelihood_exponential,
    x0=np.array([0.10]),
    args=(Y_train, d_train),
    method="Nelder-Mead",
    options=dict(xatol=1e-10, fatol=1e-10),
)

if not fit_result.success:
    raise RuntimeError(f"Exponential optimizer failed: {fit_result.message}")

lam_mle_numeric = float(fit_result.x[0])
lam_mle_analytic = float(d_train.sum() / Y_train.sum())
np.testing.assert_allclose(
    lam_mle_numeric, lam_mle_analytic, rtol=1e-7, atol=1e-10
)
ConsoleOut [16]
CollapseExpand
events in training set: 250 / 500
total observed time:    1743.3
analytic MLE lambda:    0.143406
numeric  MLE lambda:    0.143406
true     lambda:        0.150000

The numerical estimate agrees with D/∑iYiD/\sum_iY_i to the asserted relative tolerance of 10−710^{-7}; this is a numerical check, not a machine-precision guarantee. The optimizer also reports successful termination. Agreement with the analytic solution checks this objective and fitting call. The estimate is near the generating rate 0.150.15 in this sample, but sampling variability prevents exact recovery of a population parameter.

To complete the workflow, we compute the negative log-likelihood on the held-out test set at the fitted λ\lambda. This tells us how the model generalizes to new records, not just how well it fit the training sample.

PythonIn [17]
CollapseExpand
nll_train = float(
    negative_log_likelihood_exponential(lam_mle_analytic, Y_train, d_train)
)
nll_test = float(
    negative_log_likelihood_exponential(lam_mle_analytic, Y_test, d_test)
)
per_record_train = nll_train / len(Y_train)
per_record_test = nll_test / len(Y_test)
Out[18]:
metric train test
0 negative log-likelihood 735.5183 310.7176
1 per-record NLL 1.4710 1.5536

The per-record negative log-likelihood is similar across these training and test samples. This is one held-out check under the same simulated data-generating and censoring processes. It does not prove calibration or absence of overfitting. Likelihood comparisons between event-time models should use the same observed records and observation mechanism. The chapter on evaluating survival predictions adds censoring-aware discrimination and probability-error measures.

We also fit the two naive estimators to the same observed training records. This measures their error in this simulated realization; repeated samples would be needed to estimate their sampling bias empirically.

PythonIn [19]
CollapseExpand
lam_naive_events = len(Y_train) / Y_train.sum()
lam_naive_drop = d_train.sum() / Y_train[d_train == 1].sum()
Out[20]:
estimator lambda_hat expected_lifetime
0 correct MLE 0.1434 6.9732
1 treat censored as events 0.2868 3.4866
2 drop censored 0.3237 3.0889

Both naive rates exceed the generating rate in this run: about 0.290.29 when censorings are counted as events and 0.320.32 after dropping them, versus 0.150.15. Their ordering follows from the retained exposure in this sample; closeness to the generating parameter is not guaranteed by that ordering in every sample. The figure compares the resulting survival curves.

PythonIn [21]
CollapseExpand
t_grid_fit = np.linspace(0.01, 25.0, 400)
survival_true = np.exp(-lam_true * t_grid_fit)
survival_mle = np.exp(-lam_mle_analytic * t_grid_fit)
survival_naive_events = np.exp(-lam_naive_events * t_grid_fit)
survival_naive_drop = np.exp(-lam_naive_drop * t_grid_fit)
PythonIn [22]
CollapseExpand
import matplotlib.pyplot as plt
from book_plot_style import PALETTE, polish_axes, use_book_style

use_book_style()
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(
    t_grid_fit,
    survival_true,
    color=pal(0),
    linewidth=1.1,
    label=f"true  $\\lambda = {lam_true:.3f}$",
)
ax.plot(
    t_grid_fit,
    survival_mle,
    color=pal(1),
    linewidth=1.1,
    linestyle="--",
    label=f"correct MLE  $\\hat\\lambda = {lam_mle_analytic:.3f}$",
)
ax.plot(
    t_grid_fit,
    survival_naive_events,
    color=pal(2),
    linewidth=1.1,
    linestyle=":",
    label=f"treat censored as events  $\\hat\\lambda = {lam_naive_events:.3f}$",
)
ax.plot(
    t_grid_fit,
    survival_naive_drop,
    color=pal(3),
    linewidth=1.1,
    linestyle="-.",
    label=f"drop censored  $\\hat\\lambda = {lam_naive_drop:.3f}$",
)
ax.set_xlabel("Time t")
ax.set_ylabel("Survival probability S(t)")
ax.set_title("Survival fits with and without censoring")
ax.set_ylim(0, 1.02)
ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2))
polish_axes(ax)
plt.tight_layout()
plt.show()
VisualizationOut [22]
CollapseExpand
Four monotonically decreasing curves. The true and MLE curves are close together and lie above the two naive curves.
Fitted exponential survival curves compared with the generating truth. The censoring-aware MLE closely tracks the true curve, while counting censorings as events or dropping them both depress the fitted survival curve at every positive plotted time in this realization.

The two naive curves lie below the generating survival curve on the positive plotted times, while the censoring-aware estimate is close in this realization. The simulation makes the consequence of the event indicator visible. It does not establish exact recovery or validate the exponential assumption for a real equipment dataset.

Key parameters

The exponential has a single parameter, the rate λ>0\lambda > 0, and its interpretation is worth spelling out because the same parameterization recurs throughout this part of the book. Its survival function is S(t)=e−λtS(t) = e^{-\lambda t}, its density is f(t)=λe−λtf(t) = \lambda e^{-\lambda t}, and its hazard is constant at h(t)=λh(t) = \lambda.

  • E[T]=1/λ\mathbb{E}[T] = 1/\lambda is the mean event time.
  • Median(T)=log⁡2/λ\text{Median}(T) = \log 2 / \lambda is the median event time.
  • Var[T]=1/λ2\mathrm{Var}[T] = 1/\lambda^2, and the coefficient of variation (the standard deviation divided by the mean, (1/λ)/(1/λ)(1/\lambda)/(1/\lambda)) is 1.
  • The cumulative hazard is H(t)=λtH(t) = \lambda t, linear in time.

These identities make the exponential a convenient baseline but a restrictive one. A constant hazard is restrictive when risk changes with age. The Weibull family we used in the visualization section has two parameters (shape and scale) and lets the hazard rise or fall. A later chapter will fit those parameters with the same style of likelihood.

Key methods

For independent right censoring without delayed entry, the same likelihood construction supports other absolutely continuous parametric event-time models:

  1. Write the hazard hθ(t)h_\theta(t) and the cumulative hazard Hθ(t)H_\theta(t) in terms of the model's parameters θ\theta.
  2. Form the negative log-likelihood −ℓ(θ)=∑iHθ(Yi)−∑iΔilog⁡hθ(Yi)-\ell(\theta) = \sum_i H_\theta(Y_i) - \sum_i \Delta_i \log h_\theta(Y_i).
  3. Optimize with SciPy, guarding against invalid parameter regions (negative rates, positive-shape violations, etc.).
  4. Validate on a held-out sample with the same per-record likelihood or a suitable survival metric.
  5. Compare against the naive estimators to see how much censoring matters in this particular dataset.

Other families require their own parameter constraints, stable density or survival calculations, diagnostics, and checks that the fitted optimum is meaningful. Delayed entry, other censoring schemes, clustered observations, and competing events change the likelihood or data representation; they are not handled by substituting a new hazard alone.

Practical Implications

Time-to-event methods are most useful when the estimand is a duration and the observation process is explicit. The subsections below cover suitable applications, data preparation, common pitfalls, and computational and deployment considerations.

Appropriate Problems and Domains

Time-to-event methods apply when the target is a duration and the observation process supplies event times or valid censoring bounds. Equipment failure, subscription churn, credit default, and relapse can have that structure, provided the event, origin, population, and follow-up rule are defined. Event times can also be fully observed: censoring is an important reason for using survival methods, not a requirement for them.

When all durations are observed, an ordinary complete-data distributional fit may be sufficient. Light censoring is not, by itself, permission to ignore censored records. For a fixed-horizon question, such as relapse within twelve months, a binary classifier can be appropriate when status at that horizon is known for each subject. Censoring before the horizon leaves that label unknown and needs an explicit treatment.

A third consideration is the audience and the estimand. Hazard ratios describe relative rates of failure among those still at risk, survival probabilities describe the fraction event-free at a given time, and expected or median durations describe typical lifetimes. These answer different questions, and a model that reports one of them does not automatically answer the others without additional computation. Choosing the estimand before fitting is more efficient than fitting first and then searching for a summary that stakeholders can interpret.

Best Practices

Before analysis, record the time origin, event definition, observation-ending rule, and cohort inclusion or entry rule. Retain the observed duration, event indicator, entry time when relevant, and covariate measurement times. These decisions determine the target population and the information each record provides; model fitting cannot resolve an ambiguous event or an incorrectly defined origin.

A Kaplan–Meier survival estimate or Nelson–Aalen cumulative-hazard estimate can help assess a parametric model under their censoring and entry assumptions. A nearly linear cumulative hazard is consistent with an exponential model; marked curvature suggests a changing hazard. Thin risk sets limit interpretation in the tail. These are diagnostics, not proofs of a model or censoring assumption.

For iterative fits, inspect optimizer status, enforce parameter constraints, and compare sensible starting values. Compare information criteria using the same fitted records and observation scheme. Compare held-out likelihood by scoring candidate models on the same held-out records and observation scheme, and describe any differences in training data. Report event counts, follow-up, parameter count, and uncertainty alongside sample size. There is no universal events-per-parameter cutoff guaranteeing stability; the information also depends on effect sizes, covariate distributions, and censoring. Regularization can stabilize estimation but does not add information to the observed-data likelihood.

Data Requirements and Preprocessing

Durations need consistent units and a documented origin; hazards have inverse-time units. Zero durations can reflect rounding, simultaneous entry and event, or data errors, so investigate their meaning before fitting a model that assumes strictly positive times. Replacing them by an arbitrary epsilon changes the data. Ties from rounded measurement may be compatible with a chosen approximation, but genuine interval observation calls for interval-censoring methods rather than pretending the bounds are exact event times.

A baseline prediction model can use only covariates available at its prediction time. A measurement obtained three months later cannot be used as if available at baseline. Time-varying models can instead use information available just before each risk interval, with a suitable data layout and target. Standardizing a continuous covariate changes the unit represented by its coefficient: a hazard ratio per standard deviation differs from a ratio per original unit. With an unpenalized model and an equivalent parameter transformation, predictions and comparisons at the same original covariate values remain unchanged; regularization requires additional care.

A duration-and-indicator row supports basic right-censored models without delayed entry or time-varying covariates. Entry requires an additional LiL_i field, and changing covariates commonly require start–stop risk intervals. When the target distinguishes multiple event types, retain an event-type field. A prespecified composite first-event target can instead use a binary event indicator. Different calendar recruitment dates alone do not imply left truncation when each subject is observed from its own defined origin.

Common Pitfalls

An analysis should explain why censoring is plausibly independent given the variables it uses. Related prognosis and observation-ending mechanisms can invalidate that assumption, but the direction of bias is not universal. Where dependence is plausible, choose a sensitivity analysis or a model whose additional assumptions address the mechanism. Censoring weights help only under their own conditional-independence, positivity, and model requirements.

A hazard is a rate and can exceed one. For an event-free subject at time tt, the probability of an event by t+ut+u is 1−S(t+u)/S(t)1-S(t+u)/S(t), when S(t)>0S(t)>0. For a constant rate hh, this is 1−e−hu1-e^{-hu}, whereas huhu is only a small-interval approximation. Increasing the product huhu does not make that linear approximation a valid probability.

Two further pitfalls concern the timing of the analysis rather than the algebra. When only a handful of subjects remain at risk, a single event can remove a large fraction of the estimated remaining survival probability. Such an unstable tail can look like a dramatic late effect when it is only sampling noise. Plots should display the number at risk beneath the curve, and statements about late survival should be qualified when that number is small. Separately, aligning subjects on different time origins, or allowing a covariate to be defined only for subjects who survived long enough to have it recorded, creates apparent effects that are artifacts of the study design. A fitted summary alone cannot establish that the data construction avoided these problems; inspect how the records were assembled.

Computational Considerations

The exponential rate estimate needs a single pass to accumulate events and exposure, followed by a division. Fits with unknown shape or regression coefficients commonly require iteration. A Weibull shape fit generally uses numerical root finding or optimization; a Cox fit evaluates a partial likelihood over risk sets. Iteration cost depends on the number of parameters and implementation, so an ordinary-regression runtime analogy is not a reliable complexity bound.

Non-parametric estimation has its own cost profile. Computing a Kaplan–Meier curve requires sorting the observed times, which is the dominant expense at O(nlog⁡n)O(n \log n), after which the cumulative product is a single linear pass. Nelson–Aalen is similar. Risk-set-based quantities that are recomputed from scratch for each event time can degrade toward quadratic behavior, which can become expensive as the sample grows. It is usually better to accumulate counts through the sorted event times than to filter the full sample at each step.

Use log-density and log-survival calculations to avoid multiplying probabilities that underflow. For the exponential model here, zero observed events and positive exposure give ℓ(λ)=−λ∑iYi\ell(\lambda)=-\lambda\sum_iY_i, whose supremum is at the boundary λ↓0\lambda\downarrow0; there is no positive interior rate estimate. Report that case explicitly rather than an arbitrary optimizer result. Other survival families need their own existence checks. Constraints and multiple starting points can help numerical fitting, but do not prove a global optimum.

Performance and Deployment Considerations

Mean squared error against censored durations evaluates the wrong event-time target. Censoring-aware concordance measures assess risk ordering among suitably comparable pairs; a chance ordering has expected concordance 0.50.5 under the usual tie conventions. What constitutes useful discrimination depends on the application, so there is no universal 0.70.7 threshold. Censoring-adjusted Brier scores assess probability error across supported horizons and reflect both calibration and discrimination. Held-out event-time likelihood remains useful for comparing parametric specifications on the same observations and observation process.

Deployment adds concerns that do not appear in a single fitted analysis. The survival function is time-dependent, so a deployed model must store its time origin and be queried with an elapsed time, not a calendar date. Mismatches here produce silently wrong predictions. Predictions should be reported with the horizon attached, since a survival probability without a time is meaningless, and predicted curves should be assessed against matured holdout outcomes at supported horizons to check calibration.

Monitor follow-up and censoring patterns as well as covariates and matured outcomes. A changing censoring fraction can reflect a newer cohort with less follow-up rather than a changed event process. Compare like horizons and cohort ages before interpreting drift, and reconsider censoring assumptions if the observation mechanism changes. Refitting and evaluation schedules should follow outcome availability and the application rather than a fixed generic cadence.

Summary

Time-to-event data is the record of a duration combined with a censoring indicator, Yi=min⁡(Ti,Ci)Y_i = \min(T_i, C_i) and Δi=1{Ti≤Ci}\Delta_i = \mathbf{1}\{T_i \le C_i\}. The indicator is not a nuisance. It separates the two kinds of information a survival dataset contains, namely "the event happened at this time" and "the event had not happened by this time." The likelihood that consumes this pair is ∏ifθ(yi)δiSθ(yi)1−δi\prod_i f_\theta(y_i)^{\delta_i} S_\theta(y_i)^{1 - \delta_i}, or equivalently ∏ihθ(yi)δie−Hθ(yi)\prod_i h_\theta(y_i)^{\delta_i} e^{-H_\theta(y_i)} after rewriting in terms of the hazard and the cumulative hazard.

Three functions describe the same distribution. The survival function S(t)=P(T>t)S(t) = P(T > t) is the probability of remaining event-free. The density f(t)=−S′(t)f(t) = -S'(t) is the familiar probability density. The hazard h(t)=f(t)/S(t)h(t) = f(t)/S(t) is the instantaneous rate of the event given survival so far. The bridge between the survival and cumulative-hazard representations is S(t)=exp⁡(−H(t))S(t) = \exp(-H(t)), which is what makes hazard-based models algebraically convenient. You should be comfortable with all three and be able to translate among them at will.

Independent censoring with distinct nuisance parameters permits removal of censoring factors from the event-time likelihood. Unmeasured dependence can invalidate that factorization, and additional models, weighting assumptions, or sensitivity analysis may be needed. Competing-event analyses need a declared target. When that target distinguishes event types, retain an event-type indicator; a prespecified composite first-event target can use a binary indicator. Non-informative delayed entry conditions on survival to LiL_i through S(Li)S(L_i), and changes exposure even for an exponential lifetime.

The exponential rate estimate is λ^=D/∑iYi\hat\lambda=D/\sum_iY_i when there is at least one event and positive exposure. In the six-record example it is 4/214/21, while counting censorings as events gives 6/216/21 and dropping them gives 4/104/10. The primitive optimizer agrees with the analytic estimate, and SciPy's censored exponential fit checks the hand example independently. In the larger seeded sample the censoring-aware estimate is near the generating rate; the naive estimates are substantially larger. The next chapters extend the estimators while retaining explicit attention to what each observed record tells us.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about right censoring, hazard functions, and the survival likelihood.

Time-to-Event Data, Censoring, and Hazards

Question 1 of 70 of 7 completed
A record has observed time Y = 5 and event indicator delta = 0. What does this record tell you about the true event time T?
Join the discussionAsk a question or share what you learned.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

A different angle
All writing
Machine Learning

Evaluating Survival Predictions

Score censored survival models with concordance and IPCW Brier scores. Learn to compare Cox, Weibull AFT, and Kaplan-Meier forecasts honestly.

77 min read
Machine Learning

Cox Proportional Hazards

Learn how the Cox proportional hazards model estimates covariate effects on hazards using partial likelihood, with NumPy implementation and diagnostics.

57 min read
Machine Learning

Kaplan–Meier and Nelson–Aalen Estimation

Learn Kaplan-Meier and Nelson-Aalen estimation for right-censored data, covering risk sets, ties, Greenwood variance, medians, and NumPy implementation.

39 min read

About the author

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2026timeevent, author = {Michael Brenndoerfer}, title = {Time-to-Event Data, Censoring, and Hazard Functions}, year = {2026}, url = {https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-10-28} }
APAAcademic
Michael Brenndoerfer (2026). Time-to-Event Data, Censoring, and Hazard Functions. Retrieved from https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch
MLAAcademic
Michael Brenndoerfer. "Time-to-Event Data, Censoring, and Hazard Functions." 2026. Web. October 28, 2026. <https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch>.
CHICAGOAcademic
Michael Brenndoerfer. "Time-to-Event Data, Censoring, and Hazard Functions." Accessed October 28, 2026. https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch.
HARVARDAcademic
Michael Brenndoerfer (2026) 'Time-to-Event Data, Censoring, and Hazard Functions'. Available at: https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch (Accessed: October 28, 2026).
SimpleBasic
Michael Brenndoerfer (2026). Time-to-Event Data, Censoring, and Hazard Functions. https://mbrenndoerfer.com/writing/survival-data-censoring-hazards-from-scratch
Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy