Changelog
Source:NEWS.md
TemporalHazard 1.2.0
New features
-
hzr_bootstrap()gains ascopeargument for embedded stepwise variable selection during each bootstrap replicate – the R equivalent of SAS’s%HAZBOOTprocedure. Each replicate runs a freshhzr_stepwise()selection (starting from a fixed-shape refit of the base model) instead of a plain refit, sosummary$pctreports the variable’s selection frequency across resamples andsummary$mean/sd/ci_*describe the coefficient distribution conditional on selection.scope = NULL(the default) preserves the original fixed-formula bootstrap unchanged.
Bug fixes
-
hzr_bootstrap(scope = ..., trace = ...)no longer errors with “formal argument matched by multiple actual arguments”. Select-mode forwarded...tohzr_stepwise()alongside an explicittrace = FALSE, so any caller-suppliedtrace=collided with it.
TemporalHazard 1.1.0
CRAN release: 2026-06-12
New features
predict.hazard(type = "hazard")now works for multiphase models, returning the instantaneous additive hazardh(t|x) = sum_j mu_j(x) phi_j'(t)(previously only single-distribution models supported"hazard", viaexp(eta)). Like"survival"/"cumulative_hazard"it is time-based (requiresnewdata$time), supports covariatenewdata, andse.fit = TRUE(delta-method limits on the log scale via a numeric Jacobian of the hazard evaluator).decompose = TRUEis not supported for"hazard". This gives the multiphase instantaneous hazard a public route (it was previously reachable only through internal functions).predict.hazard(..., se.fit = TRUE, conf.type = "logit")selects the survival confidence-limit transform. The default"log-log"builds limits onlog(-log S)(thesurvival::survfitstandard);"logit"builds them onlogit(1 - S), reproducing SAS HAZARD’sHAZPREDsurvival limits. With the full-information vcov for CoE fits,conf.type = "logit"matches the SAShp.death.AVCsurvival CLs to ~1e-5. Hazard / cumulative-hazard limits are unaffected (their log scale already matches HAZPRED).predict.hazard(type = "cumulative_hazard", decompose = TRUE, se.fit = TRUE)now returns per-phase and total delta-method confidence limits for multiphase models, as a long data frame (time,component,fit,se.fit,lower,upper). Each phase’s CL uses only that phase’s parameters, so per-phase limits do not sum to the total. Previously this combination raised an error.
Changes
-
hzr_deciles()now matches the SASdeciles.hazardmacro exactly. Previously it excluded subjects censored before the horizon and defined the expected count assum(1 - S(horizon)). It now follows the SAS method: all subjects are ranked into equal-sized risk groups by predicted survival at the horizon, and the expected count per group is the sum of predicted cumulative hazard at each subject’s own follow-up time (so group totals sum to the total observed events under conservation of events). Thetimeargument now only stratifies subjects into risk groups; it no longer restricts or excludes any subject, and the expected/observed totals are horizon-independent. Verified to reproduce thehm.death.AVC.decilesSAS decile table (CASES/EXPECTED/ACTUAL) to print precision. The output columns are unchanged; their definitions are updated in?hzr_deciles.
Bug fixes
Conservation-of-Events fits now report the full-information variance. CoE removes one phase’s
log_mufrom the optimizer search (its score equation is the CoE constraint), but the previous code also dropped it from the uncertainty – the conserved phase got anNAstandard error, and anything depending on it (other SEs,se(H), prediction confidence limits) was understated wherever that phase contributed. At the optimum the CoE solution is the unconstrained MLE, sovcov()is now recomputed from the unconstrained-objective Hessian over the full free set (including the conservedlog_mu), matching an all-mu-free (conserve = FALSE) fit at the same point. Onhz.death.AVCevery parameter SE now matches the SAS HAZARD reference (e.g. the conserved earlylog_mu: 0.133 vs the previous ~0.059). The recomputation usesnumDeriv(Suggests) and an invertible Hessian; if either is unavailable the fit emits a warning and the conservedlog_muretains anNAstandard error (as before).Conservation of Events ignored left-truncation (counting-process entry times). For multiphase fits on
Surv(start, stop, event)data, the CoE reparameterization conservedSum H(stop)while the likelihood scores the intercepts on the entry-time scale,Sum E = Sum [H(stop) - H(start)]. The conserved phase therefore absorbed the spuriousSum H(start), biasing its intercept and lowering the attained log-likelihood (thehz.te123.OMCfit-1 parity offset, gap-list P1 #6)..hzr_conserve_events()and.hzr_select_fixmu_phase()now subtract the per-phase entry-time cumulative hazard, matching the likelihood and C HAZARDsetcoeunderLCENSOR/STARTTME. Plain right-censored fits (nostarttime) are unaffected.vcov()was unusable for multiphase fits and returned an unnamed matrix.vcov.hazard()collapsed the entire matrix to a scalarNAwhenever any cell wasNA. Multiphase fits legitimately haveNAvariance rows – for parameters held fixed (e.g. early shapes) and for the Conservation-of-Events-conserved phaselog_mu– so the finite free-parameter block was discarded for almost every multiphase model. The method now returns the full matrix withNArows preserved and labels rows and columns with the coefficient names (phase-prefixed for multiphase, e.g.early.xvsconstant.x), so a covariate shared across phases resolves to distinct, name-addressable slots. A scalarNAis returned only when no covariance matrix is available.Weibull analytic gradient produced
NaNfor right-censoredtime = 0rows..hzr_gradient_weibull()used an unguardedlog(time)in the shape (nu) score; a legal right-censored row attime = 0made0 * -Inf = NaN, which poisoned the entire summed shape-gradient component (then silently zeroed by the optimizer, harming convergence).log(time)is now guarded withlog(pmax(time, .Machine$double.xmin)), matching the analytic Hessian. The other families were audited: exponential (nolog(time)in the score), log-normal (rejectstime = 0), and multiphase (the decomposition clampstime) are unaffected.Weibull event hazard was inconsistent with its cumulative hazard.
.hzr_logl_weibull()defined the event hazard asmu*nu*t^(nu-1)*exp(eta)while the cumulative hazard was(mu*t)^nu*exp(eta); the former is missing amu^(nu-1)factor (the exact derivative isnu*mu^nu*t^(nu-1)*exp(eta) = (nu/t)*H, Form A as in the C/SAS HAZARD reference). The natural-scale log-likelihood and its analytic gradient (d/dmu,d/dnuevent terms) are corrected to match. Pure event/right-censored fits were already correct (they use the self-consistent internal reparameterization); the visible effect is on mixed event + interval/left-censored Weibull fits, which delegate to this likelihood and previously optimized a slightly mis-specified event term.Weibull gradient attribute ignored observation weights.
.hzr_logl_weibull(..., return_gradient = TRUE)attached an unweighted gradient even whenweightswere supplied (the analytic gradient was off by the weight scale, e.g. halved underweights = 2).weightsis now forwarded to the score computation. The model-fitting path was unaffected (it uses a separate internal weighted gradient); this only changes callers reading thereturn_gradient = TRUEattribute on weighted data.hzr_bootstrap()was non-functional for weighted fits (Phase 7c). The resample loop rewired onlydatain the refit call, leaving the originalweightsargument bound to a symbol in the caller’s frame. The internaleval()could not resolve that symbol, so every replicate of a weighted model errored out (n_success == 0) regardless offraction; even had it resolved, the un-resampled weights would have been misaligned with the bootstrapped rows.weightsis now evaluated once and resampled in lockstep with the data on each replicate (mirroring howdatais handled). Unweighted bootstraps are unaffected. A regression test covers both thefraction < 1and full-size weighted paths intest-diagnostics.R. Follow-up:hzr_bootstrap()now resamples the weights already stored on the fitted object (object$data$weights) rather than re-evaluating the call’sweightsexpression inparent.frame(), which fails when the original symbol is no longer in scope (e.g. the fit was built inside a helper that has returned). Caller-frame evaluation remains a fallback for objects fitted before weights were stored. The same fragility applied to the call’sdataargument:hazard()now stores the evaluateddataargument (the data frame passed tohazard(), not amodel.frame()result) on the fitted object (object$data$frame), andhzr_bootstrap()resamples that stored frame instead of re-evaluatingcl$datainparent.frame(), so bootstrap succeeds even when the originaldatasymbol is out of scope. Caller-frame evaluation remains a fallback for objects fitted before the frame was stored.4-phase CoE fixmu-phase selection (Phase 7d).
.hzr_select_fixmu_phase()usedwhich.max()over raw per-phase cumhaz at the starting theta. G3 late phases with typical shape parameters have unnormalized cumhaz orders of magnitude larger than other phases, causing CoE to pin the G3log_muaway from its true near-zero MLE. Fixed by excluding phases whose cumhaz contribution exceeds 10× the median before selecting (falls back towhich.maxwhen all phases are outliers). On the 4-phase CABGKUL fit the CoE vs no-CoE LL gap closes from 6.9 to < 0.1 units. Six new tests cover the 4-phase code path intest-conservation-of-events.R.time_lowerdual-use bug in Weibull and multiphase likelihoods. Whentime_lowerwas supplied for a mixed interval-censored + right-censored dataset, the Weibull LL interpretedtime_loweras the counting-process entry time for right-censored rows, computing H(stop) − H(start) = 0 and silently zeroing those rows’ likelihood contribution. Fixed inlikelihood-weibull.R(4 sites: LL, gradient, L-BFGS-B internal LL/gradient) andlikelihood-multiphase.R:start_vecis now set fromtime_loweronly for genuine epoch rows (status %in% c(0L, 1L)andtime_lower < time). Two regression tests added totest-interval-censoring-weibull.R.-
hzr_decompos()Case 3 corrected andnu = 0, m >= 0now fails loud (Phase 7d). Two issues in the early-phase (G1) sign dispatch:-
Case 3 (
m > 0, nu < 0, “bounded cumulative”) carried a spurious factor ofm. Itsrhoused a bare(2^m - 1)^nuinstead of the((2^m - 1)/m)^nuform used by Case 1, leaving anmfactor on thebt^(-1/nu)term. The CDF diverged from the C HAZARD G1 evaluator (g1flag = 5) by up to ~0.2 and was discontinuous with itsm -> 0limit (Case 3L). Adding the/mdivisor makes themfactors cancel, reproducing the C evaluator exactly and restoring continuity (verified againstsrc/common/hzd_ln_G1_and_SG1.c). No shipped phase uses Case 3, so fitted models are unaffected; the synthetic 3-phase golden fixture was regenerated because its free-shape optimizer path crosses Case 3 territory. -
nu = 0withm >= 0fell through every dispatch branch, leaving the CDF unassigned and raising the crypticobject 'G' not found. Thenu -> 0limit is defined only form < 0; form >= 0it is degenerate. The function now raises a clear, explanatory error. Newtest-decompos-boundary.Rlocks in continuity of all limiting branches (Case 1 -> 1L, 2 -> 1L, 2 -> 2L, 3 -> 3L), Case 3 <-> Cg1flag=5parity,g = dG/dtinternal consistency, CDF sanity, and stability at extremet_half.
-
Case 3 (
Improvements
Hardened Hessian inversion for standard errors (Phase 7c). Post-fit variance-covariance estimation now symmetrizes the Hessian, checks its reciprocal condition number, inverts via Cholesky with a
solve()fallback for non-positive-definite Hessians, and guards non-positive variances instead of silently emittingNaNstandard errors. Ill-conditioned, non-positive-definite, and non-finite Hessians now raise specific, named warnings, and fits carryrcond/pddiagnostics thatsummary()surfaces as a note when a fit is flagged. This closes the “12+-parameter Hessian stability” hardening item for the inversion layer; analytic Hessians (more accurate standard errors) follow in subsequent releases.Analytic Hessian for exponential standard errors (Phase 7c, Layer 2). The exponential distribution now computes its post-fit Hessian in closed form (
X~' diag(wH) X~over event + right-censored rows) rather than numerically, giving more accurate standard errors. The shared optimizer gained ahessian_fnhook that analytic Hessians for the remaining families will reuse; left/interval-censored exponential fits fall back to the numerical Hessian.Analytic Hessian for Weibull standard errors (Phase 7c, Layer 2). The Weibull distribution now computes its post-fit Hessian in closed form on the internal
(alpha, psi, beta)optimization scale (then mapped to the natural scale by the existing delta method) rather than numerically, giving more accurate standard errors. Covers event + right-censored data (including counting-process start times); left/interval-censored fits fall back to the numerical Hessian.Analytic Hessian for log-logistic standard errors (Phase 7c, Layer 2). The log-logistic distribution now computes its post-fit Hessian in closed form on the internal
(log alpha, log beta, beta_coef)scale rather than numerically, giving more accurate standard errors. Covers event + right-censored data; left/interval-censored fits fall back to the numerical Hessian.Analytic Hessian for log-normal standard errors (Phase 7c, Layer 2). The log-normal distribution now computes its post-fit Hessian in closed form on the internal
(mu, log_sigma, beta_coef)scale rather than numerically, giving more accurate standard errors. Covers event + right-censored data; left/interval-censored fits fall back to the numerical Hessian.Analytic Hessian for multiphase standard errors (Phase 7c, Layer 2 PR-6). Post-fit standard errors for all multiphase fits now come from a closed-form Hessian of the negative log-likelihood rather than a numerical Richardson approximation. The Hessian is assembled from three terms: (A) a phase-block-diagonal curvature of Σᵢ wᵢ H(tᵢ), (B) a dense Fisher information outer product Σₑ (wᵢ/hᵢ²) ∇h ∇hᵀ capturing cross-phase parameter interactions, and (C) a phase-block-diagonal curvature of −Σₑ wᵢ log h(tᵢ). μ/β parameters use fully closed-form expressions; shape parameters (t_half, ν, m, and G3 parameters) use second-order central differences. The Conservation-of-Events full-information vcov path also switches to the analytic Hessian. Left/interval-censored fits fall back to the numerical Hessian. Completes the 6-PR analytic-Hessian rollout across all five families.
Documentation
-
vignette("fitting-hazard-models")gains an Interval and left censoring section covering: status coding reference (-1/0/1/2), a cardiac clinic-visit simulation with right- and interval-censored observations, the directtime_lower/time_upperAPI, and a comparison showing the interval-censored fit recoveringnuclose to 1.0 (true value) while the naive exact-at-upper fit incurs a shape bias of ~+0.45. Includes a callout note on the correct use oftime_lower = 0for right-censored rows. -
vignette("fitting-hazard-models")gains a Convergence troubleshooting section covering: reading the KM cumulative hazard for Weibull starting values (log-log plot), when to fix shape parameters vs. estimate freely, diagnosing overparameterization via near-zero phase scales andNAfromvcov(), andcontroloptions (n_starts,maxit). - Added a package-level overview help page (
?TemporalHazard) giving the additive multiphase model, the phase-type vocabulary, the SAS/C HAZARD bridge, and a map of the main entry points. - Expanded the mathematical content of the core help files in the style of
randomForestSRC: explicit display equations for the generalized temporal decompositionG(t)(?hzr_decompos), the additive cumulative-hazard model on?hzr_phaseand?hazard, and defining formulas plus the Mächler (2012) reference for the numerical primitives (?hzr_log1pexp,?hzr_log1mexp,?hzr_clamp_prob). - Added methodological references to the nonparametric diagnostics (Kaplan-Meier/Greenwood, Nelson-Aalen, Aalen-Johansen) and filled in missing cross-references across the exported help pages.
- Explained the remaining enumerated options in the style of the
hzr_phase()phase-type help.?hazardgains a Baseline distributions section describing eachdistvalue ("weibull","exponential","loglogistic","lognormal","multiphase") by its hazard shape and when to use it;?hzr_stepwisegains a Selection direction and criterion section explaining eachdirection("forward"/"backward"/"both") andcriterion("wald"/"aic"), including how Wald selection differs from C/SAS HAZARD’s score-statistic path.
Testing
Patient-specific HAZPRED prediction parity (Group A fixtures
hp.death.AVC.hm1/hm2). Newtest-sas-parity.Rblocks predict survival and instantaneous hazard – with logit survival CLs and log hazard CLs at the SAS 1-SD level – from the saved multivariable both-phase model (hm.death.AVCfinal fit, “HMDEATH”) for two covariate profiles each (hm1: with/without an associated cardiac anomaly; hm2: complete vs partial canal by date of repair), matching SAS to ~5e-4 (survival) / ~8e-3 (hazard; the looser hazard tolerance reflects the near-singular 9-coefficient fit and the steep early-phase times). Adds a header-driven.hzr_parse_sas_nomogram_mv()(parses the BY-group “digital nomogram” whose rows each carry their own covariate vector) and a shared.hzr_fit_avc_hmdeath()helper.-
Stratified HAZPRED calibration parity (Group A fixture
hs.death.AVC.hm1). Newtest-sas-parity.Rblocks reproduce the population-averaged, stratified-by-COM_IVoutputs from the same HMDEATH model: (1) the observed-vs-expected “predict number of deaths” table – per stratum, EXPECTED = sum of predicted cumulative hazard at each subject’s own follow-up, PEXPECT = sum of predicted death probability, ACTUAL = observed deaths (totals conserve events, 14.76 + 55.24 = 70), to ~5e-3; and- the per-stratum mean survival curve (MSURVIV) at the digital time grid, to ~5e-4. Adds
.hzr_parse_sas_calibration()and.hzr_parse_sas_strata_survival().
- the per-stratum mean survival curve (MSURVIV) at the digital time grid, to ~5e-4. Adds
hm.death.AVCstepwise documented as a non-parity gap (Group A). The phase-aware forwardSELECTION SLE=0.2 SLS=0.1fit’s final selected model is the saved “HMDEATH” fit already verified by thehm.death.AVC.deciles/hp.death.AVC.hm1/hm2parity tests; its selection path cannot be reproduced (SAS uses approximate variances during selection while R’s full Hessian is near-singular here; SAS’s/I/Sflags are phase-level but R’sforce_inis phase-blind; R oscillates at p ~ slstay and lands in a worse basin – the same divergence already documented forhm.deadp.VALVES).test-sas-parity.Rgains a regression-guard test that exercises the multiphase phase-aware stepwise path end-to-end on real data without asserting path parity; seeinst/dev/FIXTURE-GAP-LIST.md.bs.death.AVCbootstrap documented as a non-parity gap (Group A). SAS%HAZBOOTruns a fresh stepwise selection on each bootstrap resample and reports a variable-selection frequency; R’shzr_bootstrap()resamples and refits a fixed model (no embedded-selection mode), and reimplementing the SAS procedure would inherit the documentedhm.death.AVCstepwise divergence.test-sas-parity.Radds.hzr_parse_sas_bootstrap()and asserts the SAS reference selection frequencies in parseable form (so the parity test is half-written for a future bootstrap-with-selection capability), plus a regression guard that R’s fixed-model bootstrap runs on the cohort; seeinst/dev/FIXTURE-GAP-LIST.md.Phase-specific covariate recovery tests (Phase 7d). New
test-phase-specific-covariates.Rconfirms thathzr_phase(formula = ~ ...)is correct, not just runnable: simulation-based recovery tests verify that a covariate entered into one phase recovers its true coefficient, that the same covariate carries independent (here opposite-sign) effects across two phases, and that a covariate confined to one phase does not leak into another. This is the honest substitute for a SAS parity fixture and guards against the “accepts the formal but never applies it” regression that has surfaced before with weights and counting-process times.Added fractional (non-integer) weight coverage to close the roadmap 7a gap. Prior weight tests verified weighting only via integer row duplication, which cannot express fractional (e.g. inverse-probability) weights. The new tests assert the two properties that define a correct per-row weighted log-likelihood: an additive split (a row of weight
a + bequals two identical copies of weightsaandb) and linear scaling (L(theta; c*w) = c * L(theta; w), gradient likewise, MLE invariant), across the Weibull, exponential, and multiphase-with-covariates paths.Made the single-distribution weighted-fit tests exercise a real fit. They previously omitted
thetastart values, sohazard(fit = TRUE)took its unfitted branch and the assertions comparedNULL/NAvacuously; they now supply starts and genuinely compare the weighted MLE to the duplicated-row MLE.Added interval-censoring coverage under the multiphase model (roadmap 7c). The multiphase likelihood’s interval-/left-censored branch had a working code path but no isolated test. New R-only self-consistency invariants in
test-interval-censoring-multiphase.Rverify the interval contribution equalslog(S(lower) - S(upper)), the left-censored term equalslog(1 - exp(-H(u))), right-censoring stays-(H(stop) - H(start))(including left truncation), invalid bounds (lower > upper) yield-Inf, integer weights match row duplication on interval rows, and an interval-censored multiphase fit converges.Added a SAS fractional-weight parity capture scaffold under
inst/extdata/weights-fixtures/(roadmap 7a / FIXTURE-GAP-LIST B5): aPROC HAZARD ... WEIGHT IPWtemplate, a deterministic non-integer weight dataset, a.lstparser, andtest-weights-sas-parity.R. The parity test re-fits the SAS specification in R and compares covariate estimates and log-likelihood; it skips when the capture fixture is absent (as it is by default), so CI and installation are unaffected until a SAS run is dropped in. R-side fractional-weight correctness is already proven by the invariants above; this is the drop-in external SAS confirmation.
TemporalHazard 1.0.3
CRAN release: 2026-05-30
Bug fixes / CRAN compliance
-
hzr_bootstrap()no longer touches.GlobalEnvdirectly. The 1.0.2oldseed/on.exit()/assign(".Random.seed", ...)save-restore wrapper added in 1.0.2 violated CRAN policy on writing to.GlobalEnvand has been removed. Whenseedis supplied the function simply callsset.seed(seed)(the documented R API for seeded reproducibility); the@param seeddocumentation now notes that the caller’s RNG state is not restored on exit. Withseed = NULL(the default) the function does not callset.seed()at entry, so it starts from the caller’s current RNG state; the bootstrap still consumes random numbers and advances that state in the usual way.
TemporalHazard 1.0.2
Bug fixes / CRAN compliance
- The golden-fixture generators (
.hzr_create_*_golden_fixture(), previouslyR/golden_fixtures.R) have been moved out of the package todata-raw/golden_fixtures.R. They are maintainer-only helpers for regenerating the bundledinst/fixtures/*.rdsreference outputs and are not part of the installed package, so they are no longer shipped, checked, or user-reachable. This resolves the home-filespace concern at its root: the earlier fallback resolved tosystem.file("fixtures", ...)— i.e. the installed package directory — whenever the package was installed, so the 1.0.1 “falls back totempdir()” fix did not actually prevent writing to the user library. The bundled.rdsfixtures still ship and the parity tests still read them viasystem.file(). -
.hzr_generate_golden_fixture()(the C-binary reference writer inR/parity-helpers.R, which shares a file with test-time helpers and so was kept in the package) now takes a requiredoutput_dirargument with no default path. - Removed the remaining hardcoded
seed = 42literals from the relocated generators; recorded fixture metadata reflects the actualseedargument passed (NULLby default, so no seed is set inside the function). -
hzr_bootstrap()no longer leaves the caller’s random-number stream altered whenseedis supplied: the global.Random.seedis saved beforeset.seed()and restored viaon.exit(), matching the fixture generators. Bootstrap reproducibility under a givenseedis unchanged.
TemporalHazard 1.0.1
Bug fixes / CRAN compliance
- Added
\valuedocumentation to all exported functions that were missing it:hazard(),coef.hazard(),vcov.hazard(),print.hzr_calibrate(),print.hzr_deciles(),print.hzr_gof(), andprint.hzr_kaplan(). - Internal fixture generators (
R/golden_fixtures.R) no longer set a specific seed unconditionally. Generators now accept an optionalseedargument; when provided, the global RNG state is saved and restored viaon.exit(). - Default
output_dirfor fixture generators falls back totempdir()instead of the package source directory, keeping the home filespace unmodified.
TemporalHazard 0.9.8
New features
-
Delta-method confidence limits on
predict.hazard()— Phase 4g of the development plan lands. Two new arguments:se.fit = FALSEandlevel = 0.95. Whense.fit = TRUE, the return value becomes a data frame with columnsfit,se.fit,lower,upper.-
Weibull and multiphase use closed-form Jacobians (
dH/dtheta,dexp(eta)/dtheta,deta/dtheta); exponential / log-logistic / log-normal fall back tonumDeriv::jacobianon a per-call cumhaz closure. -
Transforms match SAS HAZARD (
hzp_calc_haz_CL.c/hzp_calc_srv_CL.c):hazardandcumulative_hazarduse log-scale CLs;survivaluses log(-log S) CLs (equivalent to log-cumhaz) so 0 <= lower <= upper <= 1;linear_predictoris symmetric on the natural scale. -
Fixed-shape / CoE multiphase fits produce meaningful CLs — the delta-method sandwich is restricted to the free-parameter submatrix of
vcov, treating fixed parameters as known-with-zero-variance. - Backward compatible:
se.fit = FALSE(default) preserves the pre-0.9.8 scalar-vector / decompose-data-frame return shape.
-
Weibull and multiphase use closed-form Jacobians (
TemporalHazard 0.9.7
New features
-
Counting-process / repeating-events likelihood wired up — Phase 4f of the development plan lands.
Surv(start, stop, event)with anystart > 0is now accepted. The Weibull and multiphase log-likelihoods applyH(stop) - H(start)to event and right-censored terms; the trivialstart = 0case degenerates toH(stop)and recovers the plain-Surv fit exactly. Splitting each row into contiguous epochs preserves both the log-likelihood and the MLE to optimizer tolerance (split-invariance). -
Weibull + multiphase analytic gradients handle H(start). The closed-form Weibull score adds a
-d H(start)/d thetaterm per row (guarded atstart = 0). The multiphase analytic gradient computes per-phasePhi_j(start)and its shape derivatives, then adds+w_H_start * mu_j * dPhi_j(start)to each parameter’s score; G3 phase derivatives atstartuse the same finite-difference machinery as atstop. -
0.9.5 narrowing removed. The
hazard()guard that rejected counting-processSurv(start, stop, event)with anystart > 0is gone.
TemporalHazard 0.9.6
New features
-
weightsnow supported for all distributions — Phase 4e of the development plan lands. The exponential, log-logistic, and log-normal likelihoods and their analytic gradients now apply row weights to every censoring term (event, right-censored, left-censored, interval-censored). The 0.9.5 guard inhazard()that rejectedweightsfordist %in% c("exponential", "loglogistic", "lognormal")has been removed. Fits with integer weights reproduce the row-duplicated fit to optimizer tolerance across all five distributions. -
Conservation of Events now honours weights.
.hzr_conserve_events()and.hzr_select_fixmu_phase()take an optionalweightsargument; the multiphase optimizer threads it through so per-phase cumulative hazards are summed on the same scale as the (weighted) observed event count. CoE no longer auto-disables when weights are non-uniform — the dimension reduction stays on and the MLE matches the full-dim path.
Bug fixes
-
Multiphase analytic gradient now applies
weights..hzr_gradient_multiphase()accepted neitherweightsnor its downstream equivalents: the per-row score weightsw_H/inv_hwere set to ±1 and the interval-censored finite-difference correction summed an unweighted LL. Weighted multiphase fits therefore optimised a weighted objective with an unweighted score; BFGS line search still converged near the correct MLE but the final gradient norm did not go to zero. All three paths now honour row weights, and the optimizer’sgradient_fnwrapper (including the all-zero numeric fallback and the CoE wrapper) forwardsweightsconsistently. Regression test covers weighted analytic vs numerical gradient parity. Surfaced by Copilot review on PR #18.
TemporalHazard 0.9.5
New features
-
Stepwise covariate selection —
hzr_stepwise()runs forward, backward, or two-way stepwise selection on an existinghazardfit using Wald p-values or AIC deltas as the entry / retention criterion. Phase-specific entry is supported for multiphase models: a covariate can enter one phase and not another. Defaults match SASPROC HAZARD(SLENTRY = 0.30,SLSTAY = 0.20); AIC mode usesΔAIC < 0uniformly. SAS-styleMOVEoscillation guard freezes variables that enter + exit more thanmax_movetimes. Returns an object of classc("hzr_stepwise", "hazard")with a$stepsselection trace, scope record, and elapsed timer. Implements the core algorithm from C HAZARDstepw.c/backw.c.
Bug fixes
-
Multiphase convergence after weights/repeating-events merge — restored multiphase optimization that regressed in 0.9.4: three interacting defects in the new
weightsthreading (dup-arg collision in the multiphase / Weibull closures, positional-arg corruption in every distribution’s gradient call) made every optimizer iteration error silently insidetryCatch. Diagnosed and fixed via commit 73b4657. -
Weibull analytic gradient now applies
weights— both.hzr_gradient_weibull()and thegrad_internalclosure inside.hzr_optim_weibull()acceptedweightsas a formal but did not apply it to the score vector. The optimizer still converged via line search on the (weighted) log-likelihood, but the gradient direction was wrong and the final gradient norm did not go to zero. Both gradient paths now weight the event indicator and cumulative hazard building blocks. Fits with integer weights reproduce the equivalent row-duplicated fit to optimizer tolerance.
Scope change
-
weightsis now only accepted fordist = "weibull"anddist = "multiphase". The 0.9.4 NEWS claimed weights were threaded through all distribution-specific likelihoods; in fact the exponential, log-logistic, and log-normal single-distribution paths accepted the formal but never applied it, so the fit was silently unweighted.hazard()now raises an explicit error whenweightsis supplied with one of those distributions rather than returning an unweighted fit. Full support for the remaining single-dist paths is tracked ininst/dev/DEVELOPMENT-PLAN.mdPhase 4e. -
Conservation of Events is auto-disabled when weights are not all 1.
.hzr_conserve_events()receives the weighted event count as its target but sums per-phase cumulative hazards across rows without applying weights, so Turner’s adjustment comes out on a mismatched scale. The multiphase optimizer now detects non-unit weights and skips the CoE dimension reduction, falling through to the (correctly weighted) full-dimensional path. Fits are still correct; they just don’t benefit from the one-parameter analytical closed-form solve. Weighted CoE wire-up is tracked alongside the other weights completion work ininst/dev/DEVELOPMENT-PLAN.mdPhase 4e. -
Repeating-events / counting-process notation narrowed.
Surv(start, stop, event)withstart > 0is no longer accepted byhazard(). The 0.9.4 NEWS claimed each epoch contributedH(stop) - H(start)to the likelihood, but downstream likelihoods only readtime_lowerfor interval-censored rows (status == 2); counting-process rows (statusin{0, 1}) were silently scored withH(stop)alone, so any fit with nonzero entry times was silently wrong.hazard()now raises an explicit error. The trivial caseSurv(0, t, d)– equivalent toSurv(t, d)– continues to work. Full wire-up ofH(stop) - H(start)for all distribution paths is tracked ininst/dev/DEVELOPMENT-PLAN.mdPhase 4f.
TemporalHazard 0.9.4
New features
-
Observation weights —
weightsargument inhazard()applies Fisher weighting to the log-likelihood fordist = "weibull"anddist = "multiphase". Each observation’s contribution is multiplied by its weight, enabling severity-weighted event analyses. Implements the SASWEIGHTstatement. The original 0.9.4 entry claimed coverage of all distribution paths; the 0.9.5 patch corrected the claim and fixed a gradient wire-up bug in the Weibull path. -
Repeating events —
Surv(start, stop, event)start-stop notation is parsed. The original 0.9.4 entry claimed each epoch contributedH(stop) - H(start)to the likelihood, but the downstream likelihoods never applied the lower bound for counting-process rows; the 0.9.5 patch narrowed the feature to the trivialstart = 0case and added an explicit error for nonzero starts.
TemporalHazard 0.9.3
New features
-
hzr_deciles()— Decile-of-risk calibration function comparing observed vs. expected event counts across risk groups with chi-square GOF testing. Implements the SASdeciles.hazard.sasmacro workflow. -
hzr_gof()— Goodness-of-fit function comparing parametric predictions against nonparametric (Kaplan-Meier) estimates with observed vs. expected event counting. Implements the SAShazplot.sasmacro workflow. -
hzr_kaplan()— Kaplan-Meier survival estimator with logit-transformed confidence limits that respect the [0, 1] boundary, interval hazard rate, density, and restricted mean survival time (life integral). Implements the SASkaplan.sasmacro output structure. -
hzr_calibrate()— Variable calibration function for assessing functional form before model entry. Groups a continuous covariate into quantile bins and applies logit, Gompertz, or Cox link transforms. Supports stratification via thebyparameter. Implements the SASlogit.sasandlogitgr.sasmacros. -
hzr_nelson()— Wayne Nelson cumulative hazard estimator with lognormal confidence limits. Supports weighted events for severity-adjusted repeated event analyses. Implements the SASnelsonl.sasmacro. -
hzr_bootstrap()— Bootstrap resampling for hazard model coefficients with bagging support (fractional sampling). Returns per-replicate estimates and summary statistics (mean, SD, percentile CI). Implements the SASbootstrap.hazard.sasmacro workflow. -
hzr_competing_risks()— Competing risks cumulative incidence using the Aalen-Johansen estimator with Greenwood variance. Handles any number of competing event types. Implements the SASmarkov.sasmacro. -
Conservation of Events (CoE) — Turner’s theorem is now integrated into the multiphase optimizer. One phase’s log_mu scaling parameter is solved analytically at each iteration, reducing the optimization dimension by 1 and improving numerical stability and convergence. Enabled by default; disable with
control = list(conserve = FALSE). Implements the core algorithm from C HAZARDsetcoe.c/consrv.c. - New vignette: “Complete Clinical Analysis Walkthrough” — end-to-end workflow from Kaplan-Meier baseline through validated multivariable model, mirroring the SAS HAZARD analytical sequence.
Improvements
- Multi-start optimizer now respects user-set RNG seeds for reproducibility (removed
set.seed(NULL)that was actively breaking determinism). - Vignette metadata normalized to YAML
vignette:key across all 8 files. -
fitparameter documentation corrected to state default is FALSE. - README now includes key capabilities table and development plan link.
TemporalHazard 0.9.1
New features
- G3 late-phase decomposition (
hzr_phase("g3", ...)) now fully integrated into the multiphase optimizer, Hessian, and prediction pipeline. -
fixed = "shapes"parameter inhzr_phase()allows fixing shape parameters during estimation (matching C/SAS HAZARD workflow of estimating only log-mu scale parameters).
Bug fixes
-
summary.hazard()now correctly reports standard errors when some parameters are fixed. Previously,anyNA(vcov)rejected the entire variance-covariance matrix when fixed parameters had NA entries. -
print.summary.hazard()coefficient table now shows the correct label for G3 phases (was printing empty parentheses). -
print.summary.hazard()phase listing now uses the phase name in CDF labels (e.g., “cdf (late risk)”) instead of hardcoded “early risk”. - SAS missing value markers (
.) in CSV datasets are now handled viana.strings = c("NA", ".")indata-raw/make_data.R, preventing numeric columns from being read as character.
Documentation
- Seven Quarto vignettes: getting-started, fitting-hazard-models, prediction-visualization, inference-diagnostics, mathematical-foundations, package-architecture, and sas-to-r-migration.
- Roxygen examples now include both single-phase and multiphase models.
- README switched to self-contained CABGKUL examples with G3 late phase.
- Dataset axis labels corrected to “Months” (not “Years”).
Infrastructure
- CI workflows updated to use
roxygen2::load_pkgloadfor lazy data compatibility. - Added lintr CI workflow with
.lintrconfiguration. - pkgdown action bumped to
peaceiris/actions-gh-pages@v4. - Added
use-public-rspm: trueto all CI workflows. - Added
lintrto Suggests.
TemporalHazard 0.9.0
New features
- Multiphase engine: N-phase additive cumulative hazard models via
dist = "multiphase"withhzr_phase()specification. -
hzr_decompos()parametric family implementing the three-parameter temporal decomposition of Blackstone, Naftel, and Turner (1986). - Multi-start optimizer with Hessian-based variance-covariance estimation.
- C binary parity tests against the KUL CABG reference dataset.
- Five clinical reference datasets:
avc,cabgkul,omc,tga,valves.
TemporalHazard 0.1.0
New features
- Single-phase engine: Weibull, exponential, log-logistic, and log-normal distributions with formula interface.
-
hazard()API withpredict(),summary(),coef(),vcov()S3 methods. - Golden fixture regression testing system.
- Numerically stable helper primitives (
hzr_log1pexp,hzr_log1mexp,hzr_clamp_prob).