
Image by Author
Survival analysis helps us understand not just if an event will occur, but when and how the timing unfolds. Whether you’re studying customer churn, equipment failure, or patient outcomes, survival analysis techniques give you a framework for extracting insights from time-to-event data. These methods handle both complete observations (where the event happened) and censored data (where subjects remain under observation without experiencing the event).
Traditional survival models like Cox proportional hazards regression rest on a key assumption: predictor effects stay constant over time. If being older increases your risk by 50% at week 1, it should increase your risk by 50% at week 10, week 20, and beyond. But real-world data rarely cooperates. A medication’s protective effect might fade after the first few months. A risk factor’s influence might grow stronger or weaker as time passes. When these temporal dynamics exist and you ignore them, your model’s conclusions can be misleading.
This is where time-varying effects models come in. They let you capture how predictor relationships evolve across your observation period, turning what would otherwise be a model violation into insight about dynamic processes.
We’ll work through a complete example using Python’s lifelines package and the Rossi recidivism dataset. This dataset tracked 432 individuals released from Maryland state prisons between 1972 and 1978, recording whether they were rearrested during a 52-week follow-up. Along with arrest outcomes, it captured characteristics like age, race, marital status, number of prior convictions, work experience, and whether they received financial aid upon release. If you’re new to survival analysis or want foundational context on this dataset, check out this practical Python tutorial covering basics like Kaplan–Meier curves and standard Cox models.
Understanding the Proportional Hazards Assumption
Before diving into time-varying effects, let’s clarify what we mean by “proportional hazards.” When you fit a Cox model, you’re estimating how covariates affect the hazard (the instantaneous risk of the event occurring at a given time). The proportional hazards assumption says that the ratio of hazards between two individuals (say, someone with work experience versus someone without) remains constant over time.
Here’s why that matters: if the hazard ratio is 0.5 at week 1 (meaning work experience cuts risk in half), proportional hazards assumes it stays 0.5 at week 10, week 30, and week 52. The baseline hazard can change (maybe everyone’s risk increases over time), but the relative risk between groups should stay the same.
When this assumption holds, interpretation is straightforward. When it doesn’t, you’re averaging over changing relationships, which can hide critical patterns. Maybe work experience is highly protective early on but loses its effect later. Or perhaps age matters little initially but becomes increasingly important. These dynamics tell different stories about the underlying process and call for different interventions.
Think about these scenarios:
- A drug trial where the treatment effect is strongest in the first three months but diminishes afterward
- Employee turnover where job tenure’s protective effect grows stronger the longer someone stays
- Disease progression where age has minimal impact early but becomes a major factor in later stages
In each case, assuming constant effects would miss the temporal story.
Setting Up the Analysis
Let’s load our data and see what we’re working with.
from lifelines.datasets import load_rossi import pandas as pd rossi = load_rossi()
The dataset includes these key variables:
- week: Time until arrest or censoring (1-52 weeks)
- arrest: Event indicator (1 = arrested, 0 = censored)
- fin: Financial aid upon release (1 = yes, 0 = no)
- age: Age at release (in years)
- race: Race (1 = Black, 0 = other)
- wexp: Work experience before incarceration (1 = yes, 0 = no)
- mar: Marital status (1 = married, 0 = not married)
- paro: Released on parole (1 = yes, 0 = no)
- prio: Number of prior convictions
We’re interested in how these factors affect recidivism risk and whether their effects change over the 52-week observation period.
Testing the Proportional Hazards Assumption
Before modeling time-varying effects, we need to diagnose whether the proportional hazards assumption actually fails. Lifelines provides a built-in test that checks each covariate for violations.
from lifelines import CoxPHFitter # Fit a standard Cox model cph = CoxPHFitter() cph.fit(rossi, duration_col='week', event_col='arrest') # Test proportional hazards assumption test_results = cph.check_assumptions(rossi, p_value_threshold=0.05)
This test examines whether the relationship between each covariate and the hazard changes over time. It uses two methods: a test based on Kaplan-Meier curves (labeled “km”) and a rank-based test. Low p-values indicate violations (the covariate’s effect isn’t staying constant).

Looking at the results table, we see clear violations for two variables:
- Age: Test statistic = 11.03, p < 0.005. The age effect changes significantly over time.
- Work experience (wexp): Test statistic = 7.48, p = 0.01. Work experience also shows time-varying behavior.
The other variables (financial aid, race, marital status, parole status, and prior convictions) all have p-values well above 0.05, meaning their effects appear stable over time. This diagnostic step tells us exactly which variables need time-varying treatment and which can stay in the model with fixed effects.
Modeling Time-Varying Effects Through Interactions
The most straightforward way to capture time-varying effects is to create interaction terms between covariates and time. When you multiply a predictor by the time variable, you’re letting its coefficient change linearly across the observation period.
Here’s how it works mathematically. In a standard Cox model, the log-hazard for individual i at time t is:
log(h(t)) = β₁X₁ + β₂X₂ + … + baseline(t)
With a time interaction, it becomes:
log(h(t)) = β₁X₁ + β₂X₂ + β₃(X₁ × t) + … + baseline(t)
Now the effect of X₁ is β₁ + β₃t, which changes linearly with time. The coefficient β₁ represents the initial effect at time 0, while β₃ tells you how much that effect increases or decreases per time unit.
Let’s add time interactions for age and work experience (the two variables that violated the proportional hazards assumption).
# Create time interaction terms rossi['age_t'] = rossi['age'] * rossi['week'] rossi['wexp_t'] = rossi['wexp'] * rossi['week'] # Fit model with time interactions cph_interactions = CoxPHFitter() cph_interactions.fit(rossi, duration_col='week', event_col='arrest') print(cph_interactions.summary[['coef', 'exp(coef)', 'p']])
Output:
coef exp(coef) p covariate fin -0.072864 0.929727 7.356059e-01 age 0.459828 1.583801 9.547919e-30 race 0.268084 1.307457 4.306380e-01 wexp -0.985933 0.373091 8.473994e-02 mar 0.890982 2.437523 2.336609e-02 paro -0.565179 0.568258 9.247528e-03 prio 0.050599 1.051901 9.734331e-02 wexp_t 0.036653 1.037333 1.708233e-02 age_t -0.022517 0.977734 1.693594e-45
Both interaction terms are statistically significant (p < 0.05), confirming that age and work experience have time-varying effects. But what do these numbers mean?
Interpreting Time-Varying Coefficients
Time interactions require careful interpretation because you’re looking at two components: the main effect (the relationship at time 0) and the interaction term (how that relationship changes per time unit).
Age Effect
The age coefficient is 0.460 with an interaction term of -0.023. Here’s what’s happening:
- At week 0: Each additional year of age multiplies the hazard by exp(0.460) = 1.58, meaning older individuals face about 58% higher recidivism risk initially.
- Change per week: The interaction term of -0.023 means this effect decreases by about 2.3% per week.
- At week 20: The age effect becomes exp(0.460 + (-0.023 × 20)) = exp(0.460 – 0.46) ≈ exp(0) = 1.0, meaning age has no effect.
- Beyond week 20: The effect continues declining, and older age becomes protective against recidivism.
This pattern suggests that older individuals struggle more with immediate post-release adjustment but adapt better over time compared to younger individuals.
Work Experience Effect
Work experience shows the opposite pattern. The coefficient is -0.986 with an interaction term of +0.037:
- At week 0: Having work experience multiplies the hazard by exp(-0.986) = 0.37, cutting recidivism risk by roughly 63% (a strong protective effect).
- Change per week: The positive interaction (+0.037) means this protective effect weakens by about 3.7% each week.
- At week 27: The effect reaches neutral: exp(-0.986 + (0.037 × 27)) ≈ 1.0.
- Beyond week 27: Work experience becomes a risk factor rather than protective.
This reversal is striking. It suggests that while work experience provides an initial advantage (perhaps through better job prospects or more structured routines), this advantage erodes over time. By the end of the observation period, those with prior work experience face higher recidivism risk than those without. This could reflect frustration with limited opportunities, unmet expectations, or other long-term challenges specific to this group.
Visualizing How Effects Change Over Time
Visualizing these time-varying effects makes the patterns clearer. Let’s plot the hazard ratios for age and work experience across the 52-week observation period.
import numpy as np
import matplotlib.pyplot as plt
# Generate time points
time_points = np.linspace(0, 52, 100)
# Calculate age effect over time
age_main = 0.460
age_interaction = -0.023
age_hr = np.exp(age_main + age_interaction * time_points)
# Calculate work experience effect over time
wexp_main = -0.986
wexp_interaction = 0.037
wexp_hr = np.exp(wexp_main + wexp_interaction * time_points)
# Create plot
plt.figure(figsize=(10, 6))
plt.plot(time_points, age_hr, label='Age Effect', linewidth=2)
plt.plot(time_points, wexp_hr, label='Work Experience Effect', linewidth=2)
plt.axhline(y=1, color='r', linestyle='--', alpha=0.3, label='No Effect')
plt.xlabel('Time (weeks)')
plt.ylabel('Hazard Ratio')
plt.title('Time-Varying Effects on Recidivism Risk')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
The plot reveals dramatically different temporal trajectories for these two predictors.
Age Effect (blue line): Starts at a hazard ratio around 1.6, indicating older age is a risk factor initially. The line steadily declines, crossing the neutral line (hazard ratio = 1.0) around week 20. By week 52, it drops to about 0.47, meaning older age has become strongly protective. This downward trajectory captures how older individuals’ initial vulnerability gives way to better long-term stability.
Work Experience Effect (orange line): Begins at a hazard ratio of approximately 0.37, showing strong initial protection. But the line climbs steadily upward, crossing neutral around week 27, and reaching 2.6 by week 52. This dramatic upward swing shows how work experience flips from being highly protective to being a substantial risk factor as time passes.
The crossing point around week 27 is telling. Before this point, age is a risk factor and work experience is protective. After this point, the relationships reverse. These opposing patterns suggest fundamentally different reintegration dynamics at play (perhaps related to adaptation speed, social support networks, employment stability, or other factors that operate on different timescales for different demographic groups).
What This Means for Practice
Modeling time-varying effects has real implications for intervention timing and resource allocation.
In the recidivism context, these results suggest:
- Early-stage support for older individuals: The elevated risk in the first 20 weeks points to a critical window where older releases need targeted assistance (housing support, healthcare access, or social reintegration programs).
- Long-term monitoring for those with work experience: The initial advantage of work experience gives false security. By the second half of the year, this group needs increased attention. Maybe they face unique barriers like credential obsolescence, employer bias, or frustration with limited advancement opportunities.
- Timing matters for resource allocation: A one-size-fits-all approach misses these temporal dynamics. Early interventions should focus differently than mid-term or late-term support.
These insights extend beyond criminal justice. In healthcare, time-varying effects might reveal when treatment adherence drops or when side effects become problematic. In customer retention, you might discover that churn risk factors operate differently during onboarding versus long-term engagement. In equipment maintenance, component age might interact with usage patterns in ways that change over a machine’s lifecycle.
Conclusion
Time-varying effects modeling extends standard survival analysis by recognizing that predictor relationships aren’t frozen in time. Through interaction terms, you can capture how effects strengthen, weaken, or even reverse across the observation period.
The approach is straightforward: multiply predictors by time and include these products as additional covariates. In our recidivism example, we discovered that age transitions from a risk factor to a protective factor, while work experience follows the opposite trajectory. These patterns wouldn’t emerge from a standard Cox model.
When you encounter proportional hazards violations, don’t treat them as nuisances to be ignored. They’re telling you something interesting about your data. Time-varying effects models let you listen to that message and translate it into insights about timing, intervention windows, and the evolving nature of risk.
