Skip to content

TemporalHazard 1.2.12

Choose a tag to compare

@ehrlinger ehrlinger released this 26 Sep 12:52
· 561 commits to main since this release
b56df0d

TemporalHazard 1.2.12 is a GitHub-only release, not submitted to CRAN. It carries all the work since v1.2.9, including the never-tagged 1.2.10 and 1.2.11. It has passed R CMD check --as-cran with the PDF manual on ubuntu (devel and release), Windows (devel and release) and macOS (check-release run 36238494826).

The full change log is too long for a release page (about 176 kB). Read it in NEWS.md at v1.2.12; it is also attached below as NEWS-1.2.12.md. The breaking changes are reproduced here, because they can change the results of existing code.

Breaking changes

  • A g3 phase with alpha fixed at 1 is now fitted as PROC HAZARD fits it,
    so its reported tau, gamma, eta and mu can change (#415).
    At
    alpha = 1 the g3 form is (t/tau)^(gamma*eta), so tau is confounded
    with mu, and gamma with eta. With them free the fit walked a ridge
    and reported converged = TRUE at an arbitrary point on it, warning only
    about standard errors. PROC HAZARD re-expresses the phase before fitting
    (SETG3_ignore_tau(), setg3.c:313-315 and 380-425). tau is held at
    1, and the product is carried by one parameter: gamma, unless gamma is
    the one you fixed. Under constraint = "eta_gamma" (FIXGE2) both are
    held, at gamma = 2 and eta = 1. hazard() now does the same, with mu
    rescaled to match. It warns and records it in fit$fit$boundary
    (mechanism "g3_alpha_one", warning class "hzr_g3_alpha_one", which
    inherits "hzr_boundary").

    The likelihood is unchanged, and a test checks the held fit against
    survival::survreg()'s Weibull. But the coefficients are reported in the
    held parameterisation. A phase such as
    hzr_phase("g3", tau = 5, gamma = 3, alpha = 1, eta = 1, fixed = "shapes")
    now reports tau = 1 and a mu smaller by a factor of 5^3. The hold is
    announced only when it changes a value or what is fixed, and a free
    alpha that starts at 1 is not held, by SAS or here. What PROC HAZARD does
    next is not mirrored: it rewrites a gamma * eta start at or below 2
    (setg3.c:870-921), and R has never done that for any g3 phase.

  • Under constraint = "eta_gamma" (FIXGE2), a fixed alpha above 1 is
    now an error, and a free one started at 1 or above starts at 2/3, as in
    PROC HAZARD (#418).
    With gamma * eta = 2 the g3 form needs
    gamma * eta / alpha > 2, that is alpha < 1. PROC HAZARD stops on a
    fixed alpha above 1 (SETG31040, setg3.c:843-847), and hazard()
    now does too; it used to fit. A free alpha started at or above 1 is
    rewritten to 2/3 (setg3.c:848-850), and hazard() now does the same,
    with a warning and a fit$fit$boundary record (mechanism
    "g3_fixge2_alpha_start"). A fixed alpha of exactly 1 takes the hold
    above. Whether a constrained gamma is heading for infinity is still not
    detected (see Known limitations).

  • A hzr_translate_sas() job PROC HAZARD refuses now warns loudly, and
    says so in $untranslated
    (#359). When SETG3 sets an error, the
    procedure exits in shape() before results(), so the job produces
    nothing. The translation recorded that as an untranslated row and emitted a
    hazard() chunk with nothing to mark it, and a reader who rendered past the
    callout got a converged fit standing in for a job with no result. The
    emitted document now carries a warning() immediately above the fit,
    naming the SETG3 code, its cause and the PARMS operands that produced
    it, and the row is recorded as before. The fit is still emitted: a rendered
    document completes, and the reader is told what it stands in for.

    Some of these still fail further down, and the warning says which.
    Several SETG3 refusals fire precisely because a shape value is out of
    range, and the same value is out of range for hzr_phase(), which will not
    build the phase. Rather than name a list of codes here, which drifted once
    already, the warning itself is derived by trying to construct the phase: it
    says hzr_phase() accepts the shape only when it does, and otherwise says
    the document stops at that check. A test executes every SETG3 class's
    emitted chunks and requires the message and the outcome to agree, so the
    two cannot diverge again. In every case the warning is emitted in its own
    chunk above the fit, naming the SETG3 code and the operand, so the
    cause is stated before hzr_phase() refuses; the render then stops there
    with hzr_phase()'s own message.

    Be aware of where that warning does and does not appear. When a chunk
    errors, Quarto writes no output document, and knitr collects warnings
    into the document rather than printing them, so the warning does not
    reach the render console either. What a reader has in that case is the
    emitted .qmd itself, where the warning() naming SETG3910 sits
    immediately above the failing fit, and the $untranslated row on the
    translated job.

    The refusal is raised only where it is PROC HAZARD's. With FIXGE2 or
    FIXGAE2 and no WEIBULL, SAS reaches SETG3 down a path the setg3.c
    trace does not model, so the trace's verdict is not used there. Only
    SETG3's entry refusals are raised as refusals on that path (see the next
    entry).

  • More hzr_translate_sas() jobs that PROC HAZARD refuses, or fits
    differently, now warn loudly
    (#358, #403, #421). Each was already recorded
    as an untranslated row, but nothing in the rendered document said so, and a
    reader met a converged fit with no sign that PROC HAZARD would not have
    produced it. Each now emits the fit, a warning() above it naming the
    cause, and the row:

    • a PARMS operand PROC HAZARD rejects with a syntax error: a value its
      lexer does not read as a number (NU=1E-3, NU=2.), a value keyword
      with no = NUMBER, a spaced operand that is invalid even joined, or a
      keyword outside its grammar (FIXG1);

    • a MAXITER= or CONDITION= value that its lexer does not read as a
      number, or no value at all: MAXITER '=' NUMBER and
      CONDITION '=' NUMBER (hazard_y.y:63-64) have no form without a
      number, so MAXITER=, MAXITER = and a bare MAXITER are each a
      syntax error and the job does not run;

    • a template's ? placeholder in PARMS, which PROC HAZARD's lexer also
      rejects. It was filled from SAS's default and fitted; it now asks to be
      filled in;

    • a model this translation cannot emit:

      • FIXMNU1 on an active early phase, which PROC HAZARD fits with
        |M*NU| = 1; this translation does not mirror that constraint;
      • DELTA other than 0 on an active early phase;
      • FIXTAU with no TAU written, which PROC HAZARD fixes at 0.75 of the
        longest follow-up;
      • FIXGE2 or FIXGAE2 without WEIBULL. That path is not modelled
        here, so the warning says the translation cannot tell whether PROC
        HAZARD refuses the job or which model it fits;
    • SETG3's entry refusals, on every path;

    • SETG1's refusals for an early phase (#424): THALF fixed at a value
      that is not positive (SETG1910); M and NU both fixed on a case no
      model takes (SETG1940, SETG1950, SETG1960, and SETG1920 and
      SETG1930 under FIXMNU1); and DELTA fixed outside [-1, 1]
      (SETG1900, SETG1901). They were fitted with no row, or warned for the
      wrong reason, that the model was not mirrored. Most of these values are
      out of range for hazard() as well, so the fit still fails after the
      warning;

    • an early phase PROC HAZARD may not fit (#424). With NU=0 and M free,
      SETG1 selects its limiting case, and these jobs fitted with no row.
      What the binary does next was measured on two datasets, and the warning
      says only what was seen. For M=0 NU=0 FIXNU it produced no result on
      both. For M=1 NU=0 and M=-1 NU=0, with or without FIXNU, it stopped
      on a domain error (DLG1980) on one dataset and fitted on the other, so
      the warning says PROC HAZARD may print no estimates on your data. In
      neither case does it say PROC HAZARD fits another model;

    • a phase variable that is not a name to PROC HAZARD's lexer (#440):
      AGE*SEX, LOG(AGE), B SEX, 1AGE. A phase variable must be a NAME,
      [_A-Z][_A-Z0-9]* (hazard_l.l:39, phasevar : NAME at
      hazard_y.y:213), so PROC HAZARD rejects such a job at parse. The phase
      parser passed the text through as a column name, with no row and no
      warning, so EARLY AGE=0.1, AGE*SEX=0.2; emitted a fit on a column
      called AGE*SEX and, under SELECTION, a screen offering it as a
      candidate. The operand is now left out of the model, and out of theta
      with it, so the emitted formula has one starting value per term. This
      holds with and without SELECTION, and for every spacing of the
      operand. A name PROC HAZARD accepts, including _X1 and a word that is
      a keyword elsewhere (E, EARLY), does not warn, and neither does a
      macro reference, which SAS expands before its lexer runs.

      Parentheses follow the lexer rather than a rule of thumb, and the
      verdicts are checked against the HAZARD binary (C-Version 4.4.4). ) is
      whitespace to it (hazard_l.l:32), and ( returns no token but
      switches it to its PROC-line state (hazard_l.l:56). So a last item
      LOG() or LOG() = 0.2 is the variable LOG, which PROC HAZARD fits
      and the translation now keeps. LOG(X), AGE(1), LOG() /I, and every
      item after a ( in the same statement are rejected. A ( also clears
      PROC HAZARD's syntax-error flag, so a job whose phase statements carry
      one may run despite an earlier error: the binary runs
      EARLY AGE*SEX, LOG(); and fits AGE alone. The translation does not
      reproduce which variables survive, and says so in the warning.

    • a value on a PROC HAZARD option that takes none (#431): NOCOV=1,
      CONSERVE=YES, PRINTIT=1, NOPRINT=0, in any spacing. Eleven options
      are bare tokens (hazard_y.y:65-75), so the = is a syntax error and
      the job does not run. The value was ignored and the job fitted with no
      row;

    • a TIME, EVENT, RCENSOR, LCENSOR or WEIGHT statement with other
      than one operand (#431). Each takes exactly one name
      (hazard_y.y:106-127). EVENT DEAD EXTRA fitted on DEAD and dropped
      EXTRA with nothing said; it now warns and fits on the first operand.
      With no operand at all, WEIGHT, RCENSOR and LCENSOR are left out
      of the fit, and TIME or EVENT stops the job, since there is no
      variable to fit, unless another statement supplies one (a second
      TIME, or ICENSOR for EVENT). An operand that is a macro reference is not counted,
      since it can expand to any number of names. The HAZARD binary is the
      oracle for both shapes, and it runs either job when a later phase
      statement carries a (, as above; the warning says so there.

    Every class above for SETG1 was checked against the HAZARD binary
    (C-Version 4.4.4), with the data staged as PROC HAZARD reads it, on the
    package's avc data and on an independent seeded dataset. Where
    SETG1 moves a starting value and runs, the translation now starts there
    too, with a row and no warning (#421): a free THALF that is not positive
    starts at 1, where it was emitted as written and the document stopped at
    its logarithm; M=0 NU=0 with both free starts at M = NU = 1; and
    NU=0 with only M fixed starts NU at 1.

    A PARMS or PROC value that carries a macro reference (&X, %CALL) is
    not refused, because SAS expands it before PROC HAZARD reads the statement.
    An operand this translation could not read, for that reason or any other,
    warns on a job whose phases it did build: the unread operand may be the one
    that sets a shape, and the emitted phase would then carry SAS's default
    where the job wrote something else.

    A refused job no longer stops the render. The warning is per job: a
    file holding several jobs emits one fit chunk each, and only the refused
    job's fit is preceded by a warning() chunk, so every other job is written
    out and runs unchanged. A document carrying a refusal renders to completion
    and shows the warning in its output, where an earlier draft of this work
    made it a stop() and Quarto then exited 1 and produced no output document
    at all, including for the jobs before the refused one.

    The exception is the refusals above whose shape hzr_phase() will not
    build. They are warned about and emitted like everything else, but
    hzr_phase() then refuses the out-of-range value, so a file containing such
    a job still yields no rendered output until it is corrected or removed. A reader who wants the other jobs'
    results in the meantime can delete that job from the file.

    Which jobs stop and which warn, in one place. A job stops only where it
    did before this release: a phase statement PROC HAZARD refuses at parse
    (#340) other than a phase variable that is not a name (#440, above), a
    PARMS statement that builds no phase this translator can use, a job
    with no DATA= whose phases name covariates (#311), a SELECTION
    job that selects no phase, and a TIME or EVENT statement with no
    operand (#431) that leaves nothing to fit. Everything else newly
    recognised in this release warns and still fits.

    The risk this accepts, deliberately: a rendered document that shows a
    warning and then carries on to a fit can be read as a clean result by
    someone who does not read the warning. That is why the warning is raised
    in its own chunk immediately above the fit rather than folded into it, and
    why every such job also carries a row in $untranslated -- the warning is
    read once at render, the row is what a reader can search for afterwards.

  • An operand written with spaces around = is read, not split apart
    (#421). SAS's lexer skips whitespace (hazard_l.l:32), so THALF = 0.3 and
    MAXITER = 50 are the same jobs as the same operands written without the
    spaces. This translator
    split them on whitespace: the PARMS pieces were recorded and the phase was
    built from PROC HAZARD's default instead of the written value, and the
    PROC line reported its pieces as unknown options. Operands are joined
    before parsing, on both. A joined operand PROC HAZARD still rejects
    (THALF = ABC, or FIXNU = 1, which takes no value) is a syntax error,
    just as it is when written without the spaces.

    Joining now works the way PROC HAZARD's own lexer does. Whitespace only
    separates tokens there (hazard_l.l:32) and = is a token in its own
    right (:55), so every spelling of one statement is the same token
    stream to SAS. The operands are normalised to that token stream first and
    then paired as KEY = VALUE by the grammar, so all spellings of a
    statement give one answer by construction rather than by matching
    particular spellings. Two earlier attempts did match spellings, and each
    left another spelling reading a following option as a value: PROC HAZARD DATA = MAXITER = 50 fitted with data set to MAXITER=50 and the
    iteration limit silently dropped.

    A stray = left over after that pairing is now recorded and warned about
    as the syntax error it is. DATA = MAXITER = 50 is read as SAS reads it
    --- DATA switches the lexer to its dataset-name state, where MAXITER
    is a name (hazard_l.l:59, :80), so the dataset is MAXITER and the
    trailing = 50 is a stray = that sends PROC HAZARD to
    hazardopt : error (hazard_y.y:76).

  • DATA= and OUTHAZ= with no value are refused (#433). DATA '=' dsfield and OUTHAZ '=' dsfield (hazard_y.y:61-62), where a dsfield is
    a name or a libref-qualified name (:80-81), have no form without one, so
    the job does not run. OUTHAZ= was previously dropped with no row at all
    and the job fitted; DATA= surfaced as an internal R error naming neither
    the option nor what was lost. Both now warn and record the construct,
    alongside the existing check on MAXITER= and CONDITION=.

    DATA = X with spaces, on a PROC HAZARD line not wrapped in a
    %HAZARD(...) call, failed before the joining could happen; that is fixed
    under #458 below.

  • hazard() refuses a function-valued element of data (#420). data
    masks the calling frame while hazard() evaluates time, status,
    time_lower, time_upper and weights, and while it evaluates the
    formula's Surv() response. R's function lookup walks past every binding
    that is not a function, so an element such as rep = function(...) ...
    was called in place of base::rep() by an expression like
    weights = rep(1, n). The fit changed and nothing warned; this has
    shipped since 1.2.2 (#151). stats::lm() refuses the same shape.
    Both interfaces were affected. The formula path looked immune only
    because the column-reading step replicates each column to nrow and dies
    on a function while doing it -- at one row there is nothing to replicate,
    and a 1-row frame carrying a round made Surv(round(tt), ss) read the
    masked value as the response.
    What now errors: any data carrying a named element that is a
    function, whether or not an expression calls it. That includes using the
    mask to reach a helper, as in
    hazard(time = f(t), status = s, data = list(t = ..., s = ..., f = myfun)),
    and it includes an S4 generic or a reference-class generator, which are
    functions for this purpose, as is an element whose name is
    NA_character_, which R binds under the symbol `NA` and a call can
    reach. Remove the element and pass data without it: for a vector
    argument, or the formula's weights, define the helper in the calling
    environment; for a helper used inside the Surv() response, compute the
    value into a data column first, since the response is evaluated without
    the formula's environment.
    Unaffected: a numeric element or column of the same name, which was
    never consulted; a data-frame list-column of functions, which is a list;
    and an element with no name, which no expression can look up.

  • hzr_bootstrap() no longer counts replicates that estimated nothing as
    successes (#373).
    The optimizer stands in 1e10 for a negative
    log-likelihood it could not evaluate, so a fit that never had a
    likelihood reports objective = -1e10, which is finite. A Weibull fit
    started at theta = c(1e10, 1e10) sits there, and each replicate
    reproduced it: 5 of 5 replicates were counted as successes, every sd
    was 0, and nothing warned. A replicate at the sentinel is now a failed
    replicate, with its own reason in failure_reasons, so n_success can
    be lower than before and the summary is built from fewer replicates; from theta = 20,
    where the optimizer moves before the clamp stops it, that is four
    replicates of five. And a free parameter that does not move across the
    replicates that estimated it, to within rounding, is named in a warning:
    from theta = 50 the objective is finite but every replicate stays at
    its start, with an sd of about 1e-14 around 50. Parameters the fit
    holds fixed (by hzr_phase(..., fixed =), by a constraint, or by
    Conservation of Events) are identical by design and are not named. A run
    in which only some replicates stay at their start while their objective is
    finite is not caught; that rests on the optimizer's convergence test
    (#351). What a sentinel objective should mean for a single fit is tracked
    separately (#486).

  • hzr_translate_sas() no longer fits a job PROC HAZARD rejects: if
    you hold estimates from such a translation, they have no SAS run behind
    them (#340).
    A phase statement with an option written in a form SAS's
    lexer or grammar rejects, such as AGE/EI (options glued together),
    AGE/E/I, Y/, /S, AGE/MOVE with no value, or a value after =
    that its lexer does not read as a number (AGE=abc, AGE/MOVE=1E5,
    AGE/MOVE=Inf), stops the job with a syntax error in PROC HAZARD, and
    ORDER= with /E, /I or /S stops it with "mutually exclusive". The
    translator used to record the text and fit anyway, with the variable in
    the model (or, for AGE=abc, left out of it). Each now emits a stop()
    naming the source. To check a job: search its phase statements for a run
    of option letters after one / (/EI, /SI), a second /, ORDER=
    beside /E, /I or /S, or a value after = that is not a plain
    decimal number (an exponent needs a decimal point: 1.0E5, not 1E5).
    None of these forms occurs in the reference corpus. Options separated by
    spaces (AGE/E I) are valid SAS and still translate, with /E taking
    precedence.

  • A named dist could skip the check on phase-scoped formula terms
    (#405).
    hazard() accepts a named scalar such as
    dist = c(model = "multiphase"), but tested it with identical(), which a
    named value never matches. hazard() does this in its own checks, and so
    does code that later reads fit$spec$dist, such as the stepwise refit and
    the diagnostics. So hazard() did not refuse a
    term such as constant(age) in the global formula (#275). If a function of
    that name was visible, the term became an ordinary covariate and entered
    every phase, a different model with no warning. hazard() now drops the
    names from dist before reading it, so fit$spec$dist is stored without
    them.

  • Standard errors were too small for a late ("g3") phase with free
    shapes: re-run any you have reported (#332).
    At realistic optima the
    standard errors this package reported for such a fit were 12 to 14
    times too small
    , so confidence intervals were far too narrow and Wald
    p-values far too significant. Anyone who has published or acted on a
    standard error, a confidence interval or a Wald test from a fit with a
    free "g3" shape should re-run it. Near the exponential limit (a fitted
    alpha of 0.0021) they were about four times too small, and where the
    data leave a shape undetermined the fit reported finite standard errors
    for a direction the likelihood does not determine at all.

    The fit itself is unchanged unless its search passed through a small
    alpha.
    For every fit without a "g3" phase, every fit whose "g3"
    shapes are fixed, and every free-shape fit whose search never took alpha
    to 1e-5 or below, the estimates and the log-likelihood are identical.
    What moves is the Hessian and everything read from it -- standard errors,
    Wald statistics, confidence intervals, the condition warnings, and the
    score test hzr_stepwise() uses to enter a variable. Where the search did
    pass through that region the optimizer's own gradient was wrong, and the
    estimates can move too; see below.

    The cause was numerical: the G3 second derivatives stepped every shape by
    a fixed amount, about 1.2e-4, and the score stepped a small alpha by
    1e-5. That is far too small a fraction of a large gamma, where rounding
    takes over, and far too large a fraction of a small eta or alpha,
    where truncation does. Each shape is now stepped in proportion to itself.
    Nothing warned, and the individual Hessian entries were never off by more
    than 0.77% -- it is the inversion of an ill-conditioned matrix that turned
    that into an order of magnitude in the standard errors, which is why the
    entry error alone is not the number to judge this by.

    • If a fit has a free alpha, refit it under this version and compare
      -- its final alpha does not tell you whether this applies.
      At
      0 < alpha <= 1e-5 the gradient in alpha, which the optimizer uses,
      was about 50% off at alpha = 1e-5 and approached 100% as alpha fell.
      A search only has to pass through that region for the difference to
      steer it. On the package's own avc data a free-shape fit started at
      gamma = 0.1 went below alpha = 1e-5 on its way, finished with alpha
      between 0.015 and 0.02 -- far outside the region -- and still stopped at
      gamma 100.9 where it used to stop at 94.4.

      Both versions stopped short of an optimum there, and both said so: the
      fit warns that its estimates fail the relative-gradient test SAS/C
      HAZARD requires (0.121 before this change, 0.482 after, against a limit
      of 6.06e-06). If your fit carries that warning, its estimates were not
      a reliable optimum before this release either
      -- refit with more
      starts or other starting values rather than reading a change in them as
      an improvement. On one weakly identified data set, fits started at such
      an alpha stopped a full log-likelihood unit below what was attainable
      and reported converged = TRUE. Such
      likelihoods are often multimodal, so a corrected fit is not guaranteed
      to end higher from every start. At such an alpha the Hessian is now
      evaluated, and is usually too ill-conditioned to invert: standard
      errors are unavailable, with a warning.

  • A formula fit given weights = <name> could silently use the wrong
    weights: re-run any where that name was also a variable in your session
    (#392).
    On the formula interface, hazard() did not look weights up
    in data. A name that was only a column of data failed with "object not
    found", but a name that was both a column and a variable in the calling
    frame (wc <- d$wc, a leftover from an earlier step) silently used the
    variable, not the column, with no error and no warning. On a 40-row
    example with a unit-weight wc beside the intended column, the
    log-likelihood was -34.23592435 instead of -33.53365357; the size of the
    error depends on how far the two vectors differ. weights is now looked
    up in data first, then the calling frame, as the vector interface (time =, status =) already did. stats::lm() also looks in data first, but
    then in the formula's environment, not the caller's. When a name is both a
    column and a visible variable, the column is used and hazard() now
    warns, naming it, on both interfaces. If you see that warning, the fit may
    differ from one made by an earlier version. A name that is only a column,
    or only a variable, does not warn. Nor does the namespace or function name
    in a qualified call such as base::abs(w), which is never looked up in
    data; that false warning had been raised on the vector interface since
    1.2.2 (#151), and only the call's own arguments are now checked. The
    warning's advice now names the data frame the call passed, as in d$w.
    Since 1.2.2 the vector interface had advised data$<name>, which finds
    utils::data() rather than the data frame, so following it was an error.
    The warning reads the names written in the expression: a name chosen at
    run time, as in get(nm), is resolved the same way, column first, as in
    stats::lm(), but is not checked. That is not new; the vector interface
    has behaved so since 1.2.2. When the name is part of a larger expression,
    such as (function(a) 1)(w) or { w <- 1; w }, the warning no longer
    says the column was used, because the expression may never read the name,
    may rebind it first, or may evaluate it elsewhere, as with() does; it
    says instead that which value it read, if any, is not checked.

  • A masked argument with an empty index, such as time = m[, 1], no
    longer fails when data is given.
    Since 1.2.2, hazard(time = m[, 1], status = s, data = d) stopped with "invalid first argument": the check
    for names that are both a column and a caller variable tried to look up
    the empty index as a name. It now skips it, on the vector interface and on
    the formula interface's weights = m[, 2].

  • A multiphase phase formula without an intercept no longer drops its
    first term (#303).
    hzr_phase(formula = ~ 0 + age) or ~ age - 1
    fitted the phase without age, and ~ 0 + age + mal without age, with
    no warning and no message. ~ 0 + age + grp, for a factor grp, lost
    age and kept a column for every level of grp. predict() repeated
    the same design, so its results agreed with the wrong fit. A phase has no
    free intercept of its own (its scale parameter plays that role), so such
    a formula now builds exactly the design of the same formula with an
    intercept: ~ 0 + age fits as ~ age, and factors are coded as they
    would be with the intercept present, not with a column per level.
    hzr_phase() now warns that the removal is ignored, once, when the phase
    is created. Refit a model whose phase formula had no intercept: its
    estimates will change. A formula that fits as before starts with an
    unordered factor, character or logical column under the default
    treatment contrasts, which already built the design of the formula with
    an intercept. An ordered
    factor, or any factor under other contrasts, is now coded as it would
    be with the intercept (for an ordered factor, o.L and o.Q rather
    than om and oh), so its coefficients change meaning. An interaction
    first, such as ~ 0 + g:age, lost a column and now keeps it.

    The score test in hzr_stepwise() rebuilt the other phases of the model
    the old way, so once the fit was corrected they would have disagreed:
    a candidate's score was computed against a design the model was not
    fitted with, silently when the column counts matched, and otherwise every
    candidate in the other phases could not be scored. It now builds them as
    the fit does, and declines to score (a score of NA) when a phase the
    step does not change rebuilds with different columns than the fit
    stored, as a model saved by an earlier version with such a formula can.

  • A model formula without an intercept now builds the design of the same
    formula with one, with a warning (#337).
    Every distribution carries its
    own intercept, its baseline parameter (the one the covariates add to).
    Without one in the formula,
    Surv(time, dead) ~ 0 + grp coded a column for every level of a factor
    grp, collinear with that parameter. A Weibull fit lost its standard errors
    (Hessian not invertible). A multiphase fit inheriting the design
    stopped 9.5 log-likelihood units below the fit with the intercept, with
    warnings but wrong estimates. The formula now fits as
    Surv(time, dead) ~ grp, and factors are coded as they would be with the
    intercept. The design therefore has one column fewer, from the first
    factor term: without an intercept R codes only the first factor with
    every level, and later factors keep their usual coding, so
    ~ 0 + f1 + f2 had f1a f1b f1c f2y and now has f1b f1c f2y. In a
    single-distribution fit that is one coefficient fewer in theta. In a
    multiphase fit, every phase that inherits the global design loses that
    column, so theta is one entry shorter for each such phase; a phase
    with its own formula is unaffected. To reuse a theta supplied for the
    old design, drop the first factor's first-level coefficient from the
    global design, and from each phase that inherits it. Left unchanged,
    the fit stops: a single-distribution fit with a non-conformable arguments error, and a multiphase fit with an error naming both
    lengths and each phase's count (#408). A numeric term fits as before
    (~ 0 + age is ~ age), apart from the new warning, which a
    hzr_stepwise() refit does not repeat. The phase-formula counterpart is
    #303.

  • hzr_stepwise() and hzr_bootstrap() now refuse a scope under
    direction = "backward" (#343).
    A backward screen only drops terms the
    base model already has, and it never read scope. From ~ age + mal,
    scope = ~ age still dropped mal, the variable left out of the scope,
    and a scope variable the base lacked was never tested. The result was the
    same as with no scope, and nothing said so. Such a call is now an error:
    pass the full model as the base and protect terms with force_in. In
    hzr_stepwise(), leave scope unset or empty (~ 1), since an empty
    scope offers nothing to enter and so agrees with a backward screen. In
    hzr_bootstrap(), an unset scope means no screen at all, so pass an
    empty scope such as ~ 1 with direction = "backward" to run a backward
    screen on each replicate. Under direction = "both", scope names what
    may enter; as in SAS, the drop half still considers every term in the
    model except those in force_in and those frozen by max_move before
    the iteration began. hzr_bootstrap() refuses the combination before
    seeding.

  • hzr_bootstrap() now refuses a selection argument passed without
    scope (#343).
    Without scope there is no screen, and direction,
    criterion, slentry, slstay, max_steps, max_move, force_in and
    force_out were ignored: direction = "backward", force_in = "age"
    returned a fixed-model bootstrap with every term at pct = 100 and no
    message. A value other than the argument's default is now an error, so a
    wrapper that passes the defaults on still works. Pass scope to screen,
    or omit the argument to refit the exact model. A value equal to the
    default is accepted whether or not it was passed: without scope it asks
    for nothing, and nothing reads it.

  • A two-sided scope formula is now an error in hzr_stepwise() and
    hzr_bootstrap() (#343).
    Only the right-hand side was read, so
    scope = com_iv ~ age + mal screened age and mal and never tested
    com_iv, with no message. The error names the left-hand side. The same
    applies to each element of a multiphase scope list, and a list that
    names a phase twice, whose second entry was never read, is refused too.

  • hzr_stepwise() and hzr_bootstrap() now check ... against what a
    candidate refit may take from it (#386).
    Both pass ... on to every
    candidate refit, and hazard() stores a name it does not declare without
    reading it. So a misspelled argument had no effect: slentyr = 1e-6,
    meant as slentry, ran the screen at the default slentry = 0.30 and
    selected three variables where the intended threshold selects one, with
    no message. Other hazard() arguments were no safer: a refit takes the
    response and dist from the base model, so time_lower was ignored on a
    formula fit and dist failed every candidate, and weights or
    time_windows changed the likelihood of the candidates but not of the
    base model they were compared with. Each is now an error naming the
    argument and, for a misspelling, the argument it was probably meant to
    be. ... forwards control, including by an unambiguous abbreviation
    such as contr, and an objective equal to the base fit's. A differing
    objective, a control that is not a list, a repeated argument and an
    ambiguous abbreviation are refused at entry rather than failing every
    candidate. hzr_bootstrap() checks before seeding, so a refusal leaves
    the random number stream untouched and names hzr_bootstrap(). ?hzr_stepwise had also described ...
    as unused, because the print method's entry replaced it.

  • hazard() now refuses a multiphase phase formula with covariates when
    no data is supplied (#299).
    Such fits previously ignored the phase
    formula. On the vector interface (time =, status =) without data, a
    phase's own formula was never evaluated: the phase took the global x, or
    no covariates at all, so hzr_phase(formula = ~ mal) fitted a model
    without mal, with no warning and no message. The call now stops with an
    error naming the phase and its formula, under fit = FALSE as well. Pass
    data = with the phase's variables as columns, or use
    hazard(Surv(...) ~ ..., data = ...). A phase with no formula is
    unaffected, and so is an intercept-only ~ 1 unless the call also has a
    global x with at least one column. Beside such an x, a ~ 1 phase
    is refused too: without data it silently took x, and with data it
    has no columns, so the same call gave two different models. Pass
    data =, use the formula interface, or drop x. A constant term such
    as ~ log(2), which builds a column only in data, is refused as well.

  • hzr_stepwise() now refuses a fit saved by an earlier version whose
    phase formula was ignored this way (#299).
    Given data, every refit
    built the ignored formula's columns into a model whose base never had
    them: a forward screen reported ENTER age over a final model that also
    carried the ignored mal, with no warning. The error names the phase and
    its formula. Refit the base model with data = and retry.

  • hzr_stepwise() and hzr_bootstrap() now also refuse a multiphase fit
    saved before 1.1.0 when they cannot tell whether its phase formula was
    used (#324).
    Such an object stores neither its data frame nor a record
    of which phases used their formulas. On the vector interface, a call that
    names data = dd reads the same whether dd was a data frame or NULL,
    and a NULL there meant the phase formula was ignored. A fit with a phase
    whose stored columns are the ones it would have inherited was let through,
    and a screen then credited the ignored formula's columns to the candidate
    it was testing, with no warning. Such fits are now refused, naming the
    phase and its formula. This also refuses a fit that did use a phase
    formula whose columns are exactly the inherited ones: without the record
    the two cannot be told apart. The check turns on the stored data frame,
    not the version: a fit that kept data$frame (every fit from 1.1.0 on,
    unless that element was removed) is unaffected, and so is a call without
    time =. A call with time = is read as the vector interface even when
    it also passes a formula, so a wrapper's formula = fml beside it is
    judged here too. Refit the base model with the current version, passing
    data =, and retry.

  • hazard() now stops when two design columns share a name (#298). A
    factor's dummy columns are named <factor><level>, so a factor g with
    level b and a numeric column gb both produced a column gb. The fit
    ran without a word: coef() carried two gb names, and predict() on a
    one-row newdata returned two values. The check covers the global design,
    an x matrix passed to the vector interface, the design after
    time_windows expansion, and, when fit = TRUE, each phase formula of a
    multiphase fit. The error names the colliding columns. Unnamed columns of
    x are allowed, but not under time_windows: the expansion names each
    window's column <name>_w<k>, so two unnamed columns both became _w1.
    A fit that used to run now stops: rename the numeric column, or rename the
    factor or change its levels (relevel(), levels<-).

  • An offset() term in a formula is now an error. hazard() used to
    drop it without a word: model.matrix() leaves offsets out of the design
    and nothing read them back, so Surv(time, status) ~ age + offset(z)
    gave the same log-likelihood and the same coefficients as
    Surv(time, status) ~ age. Fits written with an offset ignored it
    (#297).
    The global formula, every hzr_phase(formula = ), and a
    hzr_stepwise() formula or scope now stop and name the offending term.
    Offsets are not supported: how one should enter each phase of the
    additive multiphase hazard is an open modelling question, not yet
    decided. stats::offset(z) is not refused, because R reads it as an
    ordinary covariate, not an offset, and fits a coefficient for it.

  • predict(newdata = ) now takes only the columns of the model's data
    from newdata.
    A term that uses row-level values kept outside data
    (a vector, matrix, list or environment in the formula's environment, as
    in ~ zz or ~ ext$z) is refused, even when newdata supplies the
    object, with an error naming the term (term 'zz' of the model ...)
    (#409). Such a term cannot be rebuilt for new rows. Move the variable
    into data as a column and refit. When the term's values simply do not
    line up with newdata's rows, the error says so and gives both causes,
    since a length-changing function of a data column, such as
    I(unique(age)), reaches the same check. An error raised by your own code
    inside a term reaches you unchanged, with its own class and message, even
    when a different term is row-mismatched -- unless it comes from a
    model.frame() or model.matrix() call inside that term, which is read
    as the design build's own failure and replaced by the naming error.
    Before, a supplied zz or matrix M was used, but a missing or
    list-held one was silently read from the fitting rows (see Bug fixes).
    Formula constants, such as cutoff in
    I(age > cutoff) and spline knots, are unaffected. A fit saved by an
    earlier version without its data still takes such a variable from
    newdata, since it cannot tell it from a column.

  • hazard(fit = TRUE) without theta is now an error for the
    single-distribution models.
    For dist = "weibull", "exponential",
    "loglogistic" and "lognormal", the optimizer ran only when theta was
    supplied, so a call that left it out returned an unfitted object -- NULL
    coefficients, an NA objective -- with no error and no warning. print()
    gave no sign of it. The call now stops and asks for starting values, as it
    does for a zero-length theta, which used to fail inside optim() with a
    message that did not name the cause.
    dist = "multiphase" is unaffected: it assembles its own start from
    phases. fit = FALSE without theta still builds an unfitted model.

    Code that relied on the old behaviour was getting no fit. Two tests in this
    package were: they compared NULL coefficients with NULL coefficients,
    so the counting-process equivalence and epoch-split invariance they claimed
    for the Weibull were never checked. Both now fit, and both pass.

  • hzr_translate_sas() no longer drops phase covariates that follow a
    / option (#342).
    SAS attaches / options to one covariate at a
    time, so EARLY AGE, MAL/I, OPMOS; is three covariates. The translator
    cut the list at the first /, fitted ~AGE + MAL, and recorded only
    that phase options were deferred, not that OPMOS was gone. Each
    covariate now keeps its own options: /E (EXCLUDE) leaves it out of
    the model, as PROC HAZARD does without a SELECTION statement; /I
    and /S leave it in; and a per-variable MOVE= or ORDER=, or any other
    option, is recorded in $untranslated under the
    variable's name.

    Two related fixes. A second EARLY, CONSTANT or LATE statement for
    the same phase now adds to that phase's covariates rather than replacing
    them. A covariate named twice in a phase is one parameter, as in
    PROC HAZARD, whose last mention sets its starting value and options;
    within one statement it used to put two starting values in theta for
    one column, shifting every later one.

    A SELECTION NOSTEPWISE (or NOSW) job is no longer read as no screen
    at all. It was translated to a plain fit with every candidate in the
    model, but PROC HAZARD still screens, forward only, with each candidate
    starting out of the model; it now translates as a forward-only screen
    (see the SELECTION entry under New features).

    A phase variable that is not in the fitted model (an /E
    variable, or a covariate of a phase the job does not select) still
    deletes its missing rows in PROC HAZARD, which hazard() cannot do
    for a variable it never sees, so the translated status chunk now stops
    when such a variable is missing and asks for those rows to be dropped.

  • hzr_translate_sas() now emits a stop() in place of the fit when a
    PARMS statement builds no phase it could use.
    Operands the translator
    could not read (a template's MUE=?, or MUE = 0.2 written with spaces
    around =, which PROC HAZARD accepts) are recorded in $untranslated,
    but the fit chunk
    used to be emitted anyway, as hazard(fit = TRUE, theta = c()) under the
    default Weibull. That chunk rendered an unfitted object, and would now fail
    on the error above with a message about theta that does not name the real
    cause. The emitted stop() names it. This is a limit of the translation,
    not a PROC HAZARD refusal, so it is kept apart from the existing
    "selects no phase" stop.

  • hzr_translate_sas() no longer writes CONDITION= or QUASI into
    hazard()'s control (#384).
    They were emitted as condition and
    method, which hazard() never reads, so the translation counted two
    options as mapped while they did nothing. No fit changes. Both are now
    recorded in $untranslated with the reason. CONDITION= stops
    PROC HAZARD's optimizer when its Hessian approximation becomes too
    ill-conditioned, and hazard() has no such stop; it warns about the final
    Hessian instead; a CONDITION= outside 3 to 14, which PROC HAZARD
    itself ignores, is recorded as such. QUASI chooses PROC HAZARD's
    optimizer, and hazard() has no choice to make: it fits by BFGS, a
    quasi-Newton method, after a Nelder-Mead warm-up in some multiphase fits.

  • hzr_translate_sas() now emits a stop() in place of the fit when a
    job has no DATA= and a phase has covariates (#311).
    A phase's
    covariates are evaluated only in data, and such a job's fit chunk has
    none, so hazard() stopped with advice to pass data =, an argument the
    SAS job never had. Before the refusal above (#299) the same chunk fitted
    the phase without its covariates. The job is now recorded in
    $untranslated with the reason, and the emitted stop() says to add
    DATA= and translate again. A job with no DATA= and no phase
    covariates still translates to a fit, unless it has a SELECTION
    statement (see the next entry).

  • The no-DATA= refusal (#311) counts every variable a phase statement
    names, not only the covariates of the base model (#160).
    A SELECTION
    job withholds its candidates from the base model, so a refusal reading only
    the base model could not see them. When the job's SELECTION could not be
    run anyway (FAST, say), that reason is recorded in $untranslated beside
    the DATA= one, so adding DATA= does not reveal a second refusal. A
    SELECTION job with no DATA= is refused even if its phase statements
    name no variable, because a screen refits every candidate from data. This
    widens #311 on purpose.
    A job with no DATA= is now refused, with or
    without SELECTION, when its only phase variables are any of these:

    • excluded with /E;
    • on a LATE statement when PARMS has no MUL;
    • on a CONSTANT statement when PARMS has no MUC;
    • on an EARLY statement when PARMS has no MUE.

    Such jobs used to translate. Their missing-value guard then read those
    variables from whatever environment rendered the document. The refusal
    names the variables rather than claiming a phase has covariates.

  • predict(newdata = ) matches covariates by name, so newdata with
    other names now stops.
    A fit made through the vector interface with a
    named x, say x = cbind(age = , mal = ), needs newdata columns
    called age and mal. Before, predict() matched any names, or a bare
    matrix, to the coefficients by position. That was right only when the
    order happened to agree, and nothing said when it did not (#267). Code
    that passed such newdata now gets an error naming the missing columns:
    rename the columns to match x. A fit made with an unnamed x still
    matches by position. A formula fit saved by an earlier version stored no
    formula design; predict() rebuilds it (see below), and a fit whose
    design it cannot rebuild is matched on its design-matrix columns: a
    factor must be given
    as grpyoung and a transform as log(age). Refit it to give the
    formula's variables instead.

  • A formula fit saved by an earlier version is matched by name, as a new
    fit is.
    Such a fit stored no formula design, so predict(newdata = )
    rebuilds it from the fit's stored formula and data frame, and uses it
    only if it reproduces the fitted design matrix exactly: the same columns
    with the same values. The fit then takes the formula's variables
    (grp = "old"), ignores unused columns, and lets a variable win over a
    contradicting design column, all as a new fit does (#301). One value
    changes with it: newdata giving a numeric variable and a column built
    from it, such as age and I(age^2) at the design-column means, is now
    rebuilt from age, as for a new fit, where its columns used to be taken
    as given. The design is not rebuilt for a 1.0.3-era fit, which kept no
    data frame; for a formula that no longer reproduces the fit; for one
    computed in the call (as.formula(...)), which would be re-run; and for
    a formula that uses anything but the data's columns and a short list of
    R's own design functions (arithmetic and comparisons, I(), log(),
    exp(), sqrt(), abs(), pmin(), pmax(), c(), factor(),
    relevel(), scale(), poly(), splines::ns() and splines::bs()).
    A constant
    such as k in I(age > k), a function of the user's, or even pi
    could have changed since the fit without changing the fitted rows, so it
    is not trusted; a number written into the formula, as in
    I(age > 50), is. For these, newdata with columns other than the
    design columns and time is refused, with
    This fit was saved by an earlier version of TemporalHazard, without a stored formula design, ...; it also has '...'. Refit the model with the current version, or pass only the design columns. Without a design
    nothing can tell such a column from a formula variable that contradicts
    a design column, which would otherwise be ignored silently. Before, such
    a column made the positional match fail, so this is as loud as it was
    (#272).

  • predict(newdata = ) stops when newdata gives some of the formula's
    variables beside the fitted design columns, with others missing.
    Say
    a fit of ~ age + grp + sex, with newdata holding grp = "old",
    grpyoung = 1 and sexM = 0 but no sex. That used to return a value,
    and silently the wrong one: the design columns were used, and the
    grp it was given was ignored. It now stops with the error
    'newdata' gives the formula variable(s) 'grp' but lacks 'sex', while carrying the fitted design columns. Give all of the formula's variables, so the design can be rebuilt from them. Pass all of the formula's
    variables (here age, grp and sex), or the design columns alone
    (age, grpyoung, sexM). A numeric covariate such as age is both a
    variable and a column, so it makes a mix only when another column is
    built from it, as in ~ age * grp or I(age^2): a changed age would
    leave those columns stale. The
    wrong-answer fix itself is under Bug fixes (#272).

  • predict(newdata = ) stops for the time-based predictions of a model
    with a covariate named time.
    In newdata the column time is the
    prediction time for "survival", "cumulative_hazard", every multiphase
    type and any fit with time_windows, so such a covariate could not be
    given its own value. When it was the only covariate it was dropped, and
    the prediction silently came back at the baseline: a Weibull fit of
    ~ time gave 0.1414 where the covariate made it 0.1420. Beside other
    covariates, the one column served as both, so the covariate was always
    set to the prediction time. A formula constant named time, as in
    I(age > time), was replaced the same way, because a formula looks its
    symbols up in newdata first. None of this gave an error. These calls
    now stop and ask for the variable to be renamed and the model refitted,
    whether it is in the global formula, a named x or a multiphase phase
    formula. For a single-distribution model, hzr_gof() and
    hzr_deciles() pass the fitted design columns, which are used as they
    are, so they stop only for a design column named time itself, which
    follow-up time used to overwrite silently. For a multiphase fit they
    use the fitted per-phase designs and re-evaluate no formula, so a
    time variable does not stop them there. Nor does a phase formula that
    the fit did not use: a vector-interface fit ignores one. log(time), a constant such as
    I(age > time) and a list element such as cfg$time do not stop them.
    In the global formula, neither does a value that scale() stored at
    fit time, in predict() either. A phase formula is re-evaluated as
    written, so a time constant there stops even inside scale().
    "linear_predictor" and single-distribution "hazard" have no
    prediction time, so they read a time column as the covariate, now
    also when it is the only one (it used to stop with "Predictors are
    required"). They refuse only a time constant that a time column in
    newdata would mask. predict() without newdata is unaffected
    (#270).

  • A multiphase formula that names a phase as a function is now an error
    (#275). hazard(Surv(int_dead, dead) ~ constant(age), dist = "multiphase", phases = ...) read constant(age) as a phase-scoped term, then replaced
    the whole right-hand side with ~ 1, and nothing sent the term to its
    phase. The fit converged without an age coefficient, with no error and
    no warning. hazard() now stops, names the phase or phases it found, and points to
    hzr_phase(..., formula = ~ var), which is where a phase's covariates
    belong. Code that relied on the old behaviour was fitting a model without
    those covariates; drop the terms from the formula to keep that model, or
    move them into hzr_phase() to get the one the formula described.
    Formulas that call an ordinary function such as log(), and plain global
    covariates, are unaffected.

  • hzr_bootstrap() now refuses a fit whose formula uses a per-row
    variable that is not a column of its data
    (#278). Replicates resample
    the rows of data, so such a variable was held fixed while the rows moved
    under it. The interval was wrong, and every replicate still reported
    success, with no warning: on avc, a copy of age kept outside data
    gave an interval that excluded its own estimate. The check covers the
    response, the covariates of the global and phase formulas, and a
    select-mode scope, and reads variables from the terms, so log(age)
    needs only the column age. A constant outside data, such as pi, a
    cutoff or a knots vector, is still allowed. The error names the
    variables; add them to data and refit. A scope variable that only the
    scope formula's own frame can see, which the refits could never test, is
    refused the same way. So is a vector-interface fit whose design matrix
    was passed directly as x: it was re-evaluated without resampling in
    every replicate, or in select mode dropped from the candidate refits, so
    all of them succeeded and the interval was wrong.

  • hzr_stepwise() now refuses a base fit written as Surv(...) ~ .
    (#279). It read the base model's terms without the data, which cannot
    expand ., and treated the failure as a model with no terms: the screen
    reported zero steps, which looks the same as finding nothing to drop. It
    now stops before printing anything and asks for the base model's terms to
    be written out. A scope of ~ ., which used to give a screen with no
    candidates, stops the same way, and so does a multiphase base fit whose
    global formula uses . while a phase has no formula of its own and so
    inherits it. A screen whose base model has its terms written out is
    unchanged, as is a multiphase screen in which every phase has its own
    formula.

  • hzr_stepwise() now refuses a vector-interface multiphase fit whose
    phases inherit a design matrix passed directly as x
    (#284). A phase
    with no formula of its own uses that x, and a candidate refit had no
    terms to rebuild it from, so every refit dropped its columns from every
    such phase: on avc, entering mal into one phase took age out of
    both, and the log-likelihood fell from -196.44 to -202.17 while the step
    table reported a plain "enter". The only warning blamed a refit that "did
    not converge". The screen now stops before printing anything. Give each
    phase its covariates with hzr_phase(formula = ~ ...), with the columns
    in data; a fit whose phases all have their own formulas is unaffected.
    It refuses the same way when a phase the screen can step inherits
    time-varying coefficients (time_windows), which rebuilding the phase
    from its terms would fit as one constant effect, or a term that expands
    to more than one column, such as a factor with more than two levels or
    poly(), which a step cannot add or drop as one coefficient. Both used
    to change the phase's design without a word. A forward screen steps only
    the phases its scope names, so an inheriting phase outside the scope
    is left alone; a backward or two-way screen can drop from any phase.

  • An entry time after the exit time is now an error, and
    time_lower = time now means "no entry" in every family
    (#253). On a
    row with status 0 or 1, time_lower is the counting-process entry time
    when 0 < time_lower < time. hazard() used to warn when
    time_lower >= time on such a row and fit anyway, and each family then
    did something different. Multiphase returned a "log-likelihood" of
    +47915.76 with converged = TRUE on the AVC data. The Weibull read the
    rows as entering at time 0, and the other three families ignored
    time_lower altogether.

    • time_lower > time on a status 0/1 row now stops, as SAS HAZARD
      rejects a start time after the exit time (error SETCOE960).
    • time_lower == time on a status 0/1 row is read as no entry time, with
      no warning, in all five families. This is the mixed-interval layout,
      where exact and right-censored rows carry time_lower = time and only
      interval-censored rows (status 2) carry a real lower bound. It was
      already the Weibull rule; multiphase used to degenerate on it.
    • time_lower = 0 still means no entry time.
    • Rows with time_lower == time > 0 beside rows with a genuine entry
      time now stop. In counting-process data they are zero-length epochs,
      which hzr_repeated_events() can emit; read as "no entry", each would
      be charged its full cumulative hazard from time 0.
  • "time" is now a reserved phase name, like "total" (#224). The
    decomposed output of predict(decompose = TRUE) starts with a time
    column holding the prediction times, then adds one column per phase under
    the phase's name. A phase called time therefore overwrote the requested
    times with its own cumulative hazard, and the times were lost with no
    error. hazard() and hzr_theta_names() now stop on a phase named
    time, before any fitting, and ask for a different name. Rename the
    phase; nothing else about the model changes.

  • hazard() now stops on zero observations (#231). Given a time of
    length 0, or a formula whose data has no rows, every distribution
    returned a hazard object anyway, and with fit = TRUE it reported
    converged = TRUE. The warnings were about the Hessian (rcond = 0, not
    invertible), which read as a conditioning problem rather than as no data.
    The call now errors before any fitting, under fit = FALSE as well. The
    same holds when no row contributes to the likelihood: every row has
    weight 0, or is right-censored at time 0, where the cumulative hazard is
    0. Such a fit came back converged at its starting values with an
    objective of 0. Check that the data frame, or the subset passed to
    data, has rows, and that some row with positive weight is an event or
    is followed past time 0.

  • hazard() now stops on a status code other than -1, 0, 1 or 2
    (#231). Every likelihood branches on those four codes, so a row coded
    anything else fell through all of them and contributed nothing, with no
    warning. The likeliest way in was survival::Surv(type = "interval")'s
    own codes passed as a plain vector, where 3 means interval-censored: those
    rows were silently dropped, and data coded only that way returned its
    starting values with converged = TRUE. The error names the rows. Pass
    a Surv object as the response, or as status, and it is translated.
    A character or factor status is refused too: it passed as text, and the
    exponential, Weibull, lognormal and log-logistic fits then returned their
    starting values as a converged fit. A logical status is still accepted.
    A classed numeric such as bit64::integer64, which data.table::fread()
    and arrow produce, is now read as its values in time, status,
    time_lower, time_upper and weights, and as a column of data,
    where Surv() and the model formulas read it. Before, those fits read
    its stored bits and returned their starting values as converged.

  • A forward candidate whose refit adds no design column of its own is now
    a recorded refit failure, not an error out of hzr_stepwise() (#442).

    Under "wald" and "aic", hzr_stepwise() used to stop with an error
    ("added no design-matrix column", or "does not add a column" when the
    refit only changes the parameterisation). It now issues a warning, adds the
    candidate to $criteria$refit_failures with the refusal as its reason in
    $criteria$refit_failure_reasons, and the screen goes on, as "score"
    already did. Code that caught the error with tryCatch(..., error = )
    will no longer see it; read $criteria$refit_failures instead.

  • hzr_stepwise()'s $steps$variable records the model's term label on
    every row (#449).
    An entry row used to carry the name as the scope
    wrote it and a drop row the terms() label, so a non-syntactic column
    _X1 entered as _X1 and left as `_X1`, and a literal column
    age:mal entered under the interaction's spelling age:mal. Every
    row now uses the label, whatever form the scope took. For a syntactic
    name the label is the name, so nothing changes; code that matched an
    entry row of a non-syntactic column by its bare name should match the
    backquoted label instead.