Fitting a model is not enough — we must validate it. For standard regression, we check residuals. For point processes, the analog is the time-rescaling theorem, which transforms any fitted point process into i.i.d. uniform (or exponential) random variables. Testing uniformity then tells us whether the model is adequate.
Theorem (Brown et al., 2002): Let N be a point process with conditional intensity λ*(t). Define the rescaled times:
τᵢ = Λ*(tᵢ) = ∫₀^{tᵢ} λ*(s) ds
If the model is correctly specified (i.e., λ*(t) is the true conditional intensity), then:
τᵢ are the event times of a unit-rate Poisson process (HPP with rate 1)Δτᵢ = τᵢ − τᵢ₋₁ are i.i.d. Exponential(1)uᵢ = 1 − exp(−Δτᵢ) are i.i.d. Uniform(0, 1)Intuition: The time change t → Λ*(t) stretches time so that events arrive at a constant rate of 1. If your model is correct, this stretch perfectly regularizes the process.
For the Hawkes process with exponential kernel, Λ*(tᵢ) is:
def compute_rescaled_times(events, mu, alpha, beta):
T = events[-1]
tau = np.zeros(len(events))
R = 0.0
t_prev = 0.0
for i, t in enumerate(events):
# Integral of λ*(s) from t_prev to t:
# = mu*(t - t_prev) + alpha/beta * R * (1 - exp(-beta*(t-t_prev)))
# where R = Σ_{j<i} exp(-beta*(t_prev - t_j))
dt = t - t_prev
tau[i] = (tau[i-1] if i > 0 else 0) + \
mu * dt + (alpha / beta) * R * (1 - np.exp(-beta * dt))
R = np.exp(-beta * dt) * R + 1.0 # update R for new event
t_prev = t
return tau
For an NHPP, Λ*(tᵢ) = ∫₀^{tᵢ} λ(s) ds computed numerically (or analytically if Λ is tractable).
The Kolmogorov-Smirnov (KS) test checks whether the uniform residuals u₁, ..., uₙ are Uniform(0, 1):
from scipy import stats
inter_tau = np.diff(np.concatenate([[0], tau]))
u = 1 - np.exp(-inter_tau) # uniform residuals
ks_stat, p_value = stats.kstest(u, 'uniform')
print(f"KS statistic: {ks_stat:.4f}")
print(f"p-value: {p_value:.4f}")
# p > 0.05 → fail to reject the model
The KS statistic D_n = sup_x |F_n(x) − x| where F_n is the empirical CDF of u. If p < 0.05, the model is rejected.
A complementary visual diagnostic is the QQ plot: plot the sorted uᵢ against the expected quantiles i/(n+1) for i = 1, ..., n. A well-fitted model gives points on the diagonal.
import matplotlib.pyplot as plt
u_sorted = np.sort(u)
expected = np.arange(1, len(u)+1) / (len(u) + 1)
plt.figure(figsize=(5, 5))
plt.plot(expected, u_sorted, 'o', ms=3)
plt.plot([0, 1], [0, 1], 'r--')
plt.xlabel("Expected Uniform Quantile")
plt.ylabel("Observed Rescaled Residual")
plt.title("Time-Rescaling QQ Plot")
Deviations from the diagonal indicate systematic misfit:
If the model is correct, the inter-rescaled-times Δτᵢ are i.i.d. — in particular, they should have zero autocorrelation. Plotting the ACF reveals residual dependence:
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(inter_tau, lags=30)
What to look for:
n*)The Ljung-Box test provides a formal test of joint significance of the first k autocorrelations:
from statsmodels.stats.diagnostic import acorr_ljungbox
result = acorr_ljungbox(inter_tau, lags=10, return_df=True)
A complete goodness-of-fit pipeline for any point process model:
θ̂τᵢ = Λ*(tᵢ; θ̂) for all observed eventsΔτᵢ = τᵢ − τᵢ₋₁ and uᵢ = 1 − exp(−Δτᵢ)kstest(u, 'uniform')u vs. expected quantilesΔτSee code/12_goodness_of_fit.py for a full diagnostic pipeline applied to HPP, NHPP, and Hawkes models.
Δτᵢ are i.i.d. Exp(1), and uᵢ = 1 − exp(−Δτᵢ) are i.i.d. Uniform(0, 1).Λ*(tᵢ).| ← Chapter 11 | Table of Contents | Chapter 13: Advanced Simulation → |