Cox Proportional Hazards › Visualizing Cox Proportional Hazards
Machine Learning from ScratchAll chapters Cox Proportional Hazards › Visualizing Cox Proportional HazardsSection view · Sections in this chapter7 / 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
The first visualization shows what the fitted Cox model implies about survival. We plot the baseline-implied survival curves for maintained and unmaintained equipment on a common time axis. The cumulative baseline is evaluated as a right-continuous step function, with each event time contributing an increment and the value staying constant between events. The comparison holds the fitted coefficient and baseline fixed while changing only the maintenance covariate. This is a model-implied association in a constructed dataset, not a causal experiment conclusion.
The residual panels ask a separate question: whether the fixed-coefficient model leaves a pattern over event time. Individual residuals are noisy, and a fitted trend is a descriptive aid rather than a formal proportional-hazards test. We compare the small equipment example with a separate seeded generator whose two groups have nonproportional hazards by construction. The absence of a visible trend in a single small sample cannot validate the assumption.
VisualizationOut [28]Expand

Both fitted curves are nonincreasing and start at one before the first event; the first plot cell shows a visible title and uses the shared style. A negative fitted maintenance coefficient places its curve at or above the reference curve. The absolute survival gap changes with cumulative hazard, and it need not reach its maximum or return to zero within the observed follow-up. The model's constant hazard multiplier is therefore a different quantity from a constant survival-probability difference or ratio.
The fitted survival curves imply a survival-probability ratio that changes with time. That ratio is a different quantity from the constant fitted hazard ratio, even though both are derived from the same Cox model.
The accompanying plot contrasts the two quantities directly: the survival-probability ratio rises in steps with accumulated baseline hazard, while the hazard ratio remains flat. This compares a rate ratio with a ratio of survival probabilities; neither plotted ratio is itself a probability.
PythonIn [29]Expand
survival_ratio = surv_maintained / surv_unmaintained
fitted_hazard_ratio = float(np.exp(beta_hat[0]))VisualizationOut [30]Expand

The next visualization is a diagnostic plot. For each event, the Schoenfeld residual is a single number per covariate. Under proportional hazards, the true-parameter conditional residual mean is zero. Estimated residuals remain noisy; a trend is a reason to investigate, not proof of a changing effect.
VisualizationOut [31]Expand

In our equipment data the residuals are sparse (thirteen events) and the trend line is nearly flat, which is consistent with proportional hazards. Thirteen event residuals give only a sparse view of changes over time. A nearly flat fitted line can miss departures that occur outside the observed event times or have little linear association with time. We will discuss how to interpret residuals rigorously, and how to test the proportional-hazards assumption, in the Practical Implications section.
Proportional-hazards diagnostics and limitations
The Schoenfeld residual is the building block of the standard proportional-hazards diagnostic. Formally, for the -th covariate and event time where individual fails, we defined
At the true coefficient under the proportional-hazards model, conditional on a risk set and one failure, the event's covariate has the weighted risk-set mean. Its unscaled Schoenfeld residual therefore has conditional mean zero. Residuals evaluated at an estimated coefficient are noisy, so a visible trend suggests investigation rather than proving a changing effect. Grambsch and Therneau (1994) develop tests based on scaled Schoenfeld residuals and transformed time. This chapter plots descriptive trends; it does not execute that formal test.
This diagnostic has two limitations. First, sensitivity depends on event count and timing, covariate variation in the risk sets, and the departure being tested. Sparse events can leave little information about time-varying effects, while a precisely estimated departure can be statistically detectable even when its practical effect is small. Interpret effect size and uncertainty alongside the p-value. Second, a test based on a chosen transformation of time can have low power against departures with little association with that transformation. Nonmonotone patterns can therefore be missed, which is why residual plots are useful alongside a formal test.
To make the failure mode concrete, we simulate a dataset in which the hazard ratio is explicitly increasing over time. The covariate is binary; the group follows an exponential with constant hazard, and the group follows a Weibull with shape 2, which has an increasing hazard. The hazard ratio between the groups therefore grows over time, violating proportional hazards by construction.
PythonIn [32]Expand
rng = np.random.default_rng(42)
n_sim = 300
x_sim = rng.integers(0, 2, size=n_sim).astype(float)
t_event_sim = np.where(
x_sim == 0, rng.exponential(1.0, n_sim), 0.7 * rng.weibull(2.0, n_sim)
)
censor_sim = rng.uniform(0.0, 2.5, n_sim)
time_sim = np.minimum(t_event_sim, censor_sim)
event_sim = (t_event_sim <= censor_sim).astype(int)
X_sim = x_sim.reshape(-1, 1)
fit_sim = optimize.minimize(
lambda b: -cox_loglike_untied(b, X_sim, time_sim, event_sim),
x0=np.zeros(1),
jac=lambda b: -cox_gradient(b, X_sim, time_sim, event_sim),
method="BFGS",
options={"gtol": 1e-7},
)
assert (
fit_sim.success
and np.max(np.abs(cox_gradient(fit_sim.x, X_sim, time_sim, event_sim)))
< 1e-5
)
time_sim_events, resid_sim = schoenfeld_residuals(
fit_sim.x, X_sim, time_sim, event_sim
)
trend_sim = np.polyfit(time_sim_events, resid_sim[:, 0], 1)
trend_sim_times = np.sort(time_sim_events)
trend_sim_values = np.polyval(trend_sim, trend_sim_times)The following plot displays the individual residuals and their fitted trend for this known nonproportional generator.
VisualizationOut [33]Expand

In this constructed example, the fitted residual trend rises over event time, while individual binary-covariate residuals form two bands. A similar trend in a real dataset is a reason to investigate proportional hazards and model specification. If a changing effect is supported, possible responses include a time-varying coefficient, stratification for a categorical covariate, or a spline interaction between the covariate and time.
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!