Repository navigation
TemporalHazard 1.2.12
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
alphafixed at 1 is now fitted as PROC HAZARD fits it,
so its reportedtau,gamma,etaandmucan change (#415). At
alpha = 1the g3 form is(t/tau)^(gamma*eta), sotauis confounded
withmu, andgammawitheta. With them free the fit walked a ridge
and reportedconverged = TRUEat an arbitrary point on it, warning only
about standard errors. PROC HAZARD re-expresses the phase before fitting
(SETG3_ignore_tau(),setg3.c:313-315and380-425).tauis held at
1, and the product is carried by one parameter:gamma, unlessgammais
the one you fixed. Underconstraint = "eta_gamma"(FIXGE2) both are
held, atgamma = 2andeta = 1.hazard()now does the same, withmu
rescaled to match. It warns and records it infit$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 reportstau = 1and amusmaller by a factor of 5^3. The hold is
announced only when it changes a value or what is fixed, and a free
alphathat starts at 1 is not held, by SAS or here. What PROC HAZARD does
next is not mirrored: it rewrites agamma * etastart at or below 2
(setg3.c:870-921), and R has never done that for any g3 phase. -
Under
constraint = "eta_gamma"(FIXGE2), a fixedalphaabove 1 is
now an error, and a free one started at 1 or above starts at 2/3, as in
PROC HAZARD (#418). Withgamma * eta = 2the g3 form needs
gamma * eta / alpha > 2, that isalpha < 1. PROC HAZARD stops on a
fixedalphaabove 1 (SETG31040,setg3.c:843-847), andhazard()
now does too; it used to fit. A freealphastarted at or above 1 is
rewritten to 2/3 (setg3.c:848-850), andhazard()now does the same,
with a warning and afit$fit$boundaryrecord (mechanism
"g3_fixge2_alpha_start"). A fixedalphaof exactly 1 takes the hold
above. Whether a constrainedgammais 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). WhenSETG3sets an error, the
procedure exits inshape()beforeresults(), 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 awarning()immediately above the fit,
naming theSETG3code, its cause and thePARMSoperands 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.
SeveralSETG3refusals fire precisely because a shape value is out of
range, and the same value is out of range forhzr_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
sayshzr_phase()accepts the shape only when it does, and otherwise says
the document stops at that check. A test executes everySETG3class'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 theSETG3code and the operand, so the
cause is stated beforehzr_phase()refuses; the render then stops there
withhzr_phase()'s own message.Be aware of where that warning does and does not appear. When a chunk
errors, Quarto writes no output document, andknitrcollects 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.qmditself, where thewarning()namingSETG3910sits
immediately above the failing fit, and the$untranslatedrow on the
translated job.The refusal is raised only where it is PROC HAZARD's. With
FIXGE2or
FIXGAE2and noWEIBULL, SAS reachesSETG3down a path thesetg3.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, awarning()above it naming the
cause, and the row:-
a
PARMSoperand 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=orCONDITION=value that its lexer does not read as a
number, or no value at all:MAXITER '=' NUMBERand
CONDITION '=' NUMBER(hazard_y.y:63-64) have no form without a
number, soMAXITER=,MAXITER =and a bareMAXITERare each a
syntax error and the job does not run; -
a template's
?placeholder inPARMS, 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:
FIXMNU1on an active early phase, which PROC HAZARD fits with
|M*NU| = 1; this translation does not mirror that constraint;DELTAother than 0 on an active early phase;FIXTAUwith noTAUwritten, which PROC HAZARD fixes at 0.75 of the
longest follow-up;FIXGE2orFIXGAE2withoutWEIBULL. 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):THALFfixed at a value
that is not positive (SETG1910);MandNUboth fixed on a case no
model takes (SETG1940,SETG1950,SETG1960, andSETG1920and
SETG1930underFIXMNU1); andDELTAfixed 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 forhazard()as well, so the fit still fails after the
warning; -
an early phase PROC HAZARD may not fit (#424). With
NU=0andMfree,
SETG1selects 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. ForM=0 NU=0 FIXNUit produced no result on
both. ForM=1 NU=0andM=-1 NU=0, with or withoutFIXNU, 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 : NAMEat
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, soEARLY AGE=0.1, AGE*SEX=0.2;emitted a fit on a column
calledAGE*SEXand, underSELECTION, a screen offering it as a
candidate. The operand is now left out of the model, and out oftheta
with it, so the emitted formula has one starting value per term. This
holds with and withoutSELECTION, and for every spacing of the
operand. A name PROC HAZARD accepts, including_X1and 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()orLOG() = 0.2is the variableLOG, 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 fitsAGEalone. The translation does not
reproduce which variables survive, and says so in the warning. -
a value on a
PROC HAZARDoption 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,LCENSORorWEIGHTstatement with other
than one operand (#431). Each takes exactly one name
(hazard_y.y:106-127).EVENT DEAD EXTRAfitted onDEADand dropped
EXTRAwith nothing said; it now warns and fits on the first operand.
With no operand at all,WEIGHT,RCENSORandLCENSORare left out
of the fit, andTIMEorEVENTstops the job, since there is no
variable to fit, unless another statement supplies one (a second
TIME, orICENSORforEVENT). 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
SETG1was checked against the HAZARD binary
(C-Version 4.4.4), with the data staged as PROC HAZARD reads it, on the
package'savcdata and on an independent seeded dataset. Where
SETG1moves a starting value and runs, the translation now starts there
too, with a row and no warning (#421): a freeTHALFthat is not positive
starts at 1, where it was emitted as written and the document stopped at
its logarithm;M=0 NU=0with both free starts atM = NU = 1; and
NU=0with onlyMfixed startsNUat 1.A
PARMSorPROCvalue 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 awarning()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 astop()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 statementPROC HAZARDrefuses at parse
(#340) other than a phase variable that is not a name (#440, above), a
PARMSstatement that builds no phase this translator can use, a job
with noDATA=whose phases name covariates (#311), aSELECTION
job that selects no phase, and aTIMEorEVENTstatement 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), soTHALF = 0.3and
MAXITER = 50are the same jobs as the same operands written without the
spaces. This translator
split them on whitespace: thePARMSpieces were recorded and the phase was
built fromPROC HAZARD's default instead of the written value, and the
PROCline reported its pieces as unknown options. Operands are joined
before parsing, on both. A joined operandPROC HAZARDstill rejects
(THALF = ABC, orFIXNU = 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 asKEY = VALUEby 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 = 50fitted withdataset toMAXITER=50and 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 = 50is read as SAS reads it
---DATAswitches the lexer to its dataset-name state, whereMAXITER
is a name (hazard_l.l:59, :80), so the dataset isMAXITERand the
trailing= 50is a stray=that sendsPROC HAZARDto
hazardopt : error(hazard_y.y:76). -
DATA=andOUTHAZ=with no value are refused (#433).DATA '=' dsfieldandOUTHAZ '=' dsfield(hazard_y.y:61-62), where adsfieldis
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 onMAXITER=andCONDITION=.DATA = Xwith spaces, on aPROC HAZARDline not wrapped in a
%HAZARD(...)call, failed before the joining could happen; that is fixed
under #458 below. -
hazard()refuses a function-valued element ofdata(#420).data
masks the calling frame whilehazard()evaluatestime,status,
time_lower,time_upperandweights, and while it evaluates the
formula'sSurv()response. R's function lookup walks past every binding
that is not a function, so an element such asrep = function(...) ...
was called in place ofbase::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 tonrowand dies
on a function while doing it -- at one row there is nothing to replicate,
and a 1-row frame carrying aroundmadeSurv(round(tt), ss)read the
masked value as the response.
What now errors: anydatacarrying 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 passdatawithout it: for a vector
argument, or the formula'sweights, define the helper in the calling
environment; for a helper used inside theSurv()response, compute the
value into adatacolumn 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 reportsobjective = -1e10, which is finite. A Weibull fit
started attheta = c(1e10, 1e10)sits there, and each replicate
reproduced it: 5 of 5 replicates were counted as successes, everysd
was 0, and nothing warned. A replicate at the sentinel is now a failed
replicate, with its own reason infailure_reasons, son_successcan
be lower than before and the summary is built from fewer replicates; fromtheta = 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:
fromtheta = 50the objective is finite but every replicate stays at
its start, with ansdof about 1e-14 around 50. Parameters the fit
holds fixed (byhzr_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 jobPROC HAZARDrejects: 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 asAGE/EI(options glued together),
AGE/E/I,Y/,/S,AGE/MOVEwith 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 inPROC HAZARD, and
ORDER=with/E,/Ior/Sstops it with "mutually exclusive". The
translator used to record the text and fit anyway, with the variable in
the model (or, forAGE=abc, left out of it). Each now emits astop()
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,/Ior/S, or a value after=that is not a plain
decimal number (an exponent needs a decimal point:1.0E5, not1E5).
None of these forms occurs in the reference corpus. Options separated by
spaces (AGE/E I) are valid SAS and still translate, with/Etaking
precedence. -
A named
distcould skip the check on phase-scoped formula terms
(#405).hazard()accepts a named scalar such as
dist = c(model = "multiphase"), but tested it withidentical(), which a
named value never matches.hazard()does this in its own checks, and so
does code that later readsfit$spec$dist, such as the stepwise refit and
the diagnostics. Sohazard()did not refuse a
term such asconstant(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 fromdistbefore reading it, sofit$spec$distis 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
alphaof 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 tookalpha
to1e-5or 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 testhzr_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 smallalphaby
1e-5. That is far too small a fraction of a largegamma, where rounding
takes over, and far too large a fraction of a smalletaoralpha,
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 finalalphadoes not tell you whether this applies. At
0 < alpha <= 1e-5the gradient inalpha, which the optimizer uses,
was about 50% off atalpha = 1e-5and approached 100% asalphafell.
A search only has to pass through that region for the difference to
steer it. On the package's ownavcdata a free-shape fit started at
gamma = 0.1went belowalpha = 1e-5on its way, finished withalpha
between 0.015 and 0.02 -- far outside the region -- and still stopped at
gamma100.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
analphastopped a full log-likelihood unit below what was attainable
and reportedconverged = TRUE. Such
likelihoods are often multimodal, so a corrected fit is not guaranteed
to end higher from every start. At such analphathe 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 lookweightsup
indata. A name that was only a column ofdatafailed 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-weightwcbeside 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.weightsis now looked
up indatafirst, then the calling frame, as the vector interface (time =,status =) already did.stats::lm()also looks indatafirst, 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 andhazard()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 asbase::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 ind$w.
Since 1.2.2 the vector interface had adviseddata$<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 inget(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, aswith()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 whendatais 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'sweights = 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 withoutage, and~ 0 + age + malwithoutage, with
no warning and no message.~ 0 + age + grp, for a factorgrp, lost
ageand kept a column for every level ofgrp.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 + agefits 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 othercontrasts, is now coded as it would
be with the intercept (for an ordered factor,o.Lando.Qrather
thanomandoh), 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 ofNA) 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 + grpcoded 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 + f2hadf1a f1b f1c f2yand now hasf1b f1c f2y. In a
single-distribution fit that is one coefficient fewer intheta. In a
multiphase fit, every phase that inherits the global design loses that
column, sothetais one entry shorter for each such phase; a phase
with its ownformulais unaffected. To reuse athetasupplied 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 anon-conformable argumentserror, and a multiphase fit with an error naming both
lengths and each phase's count (#408). A numeric term fits as before
(~ 0 + ageis~ age), apart from the new warning, which a
hzr_stepwise()refit does not repeat. The phase-formula counterpart is
#303. -
hzr_stepwise()andhzr_bootstrap()now refuse ascopeunder
direction = "backward"(#343). A backward screen only drops terms the
base model already has, and it never readscope. From~ age + mal,
scope = ~ agestill droppedmal, 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 withforce_in. In
hzr_stepwise(), leavescopeunset or empty (~ 1), since an empty
scope offers nothing to enter and so agrees with a backward screen. In
hzr_bootstrap(), an unsetscopemeans no screen at all, so pass an
empty scope such as~ 1withdirection = "backward"to run a backward
screen on each replicate. Underdirection = "both",scopenames what
may enter; as in SAS, the drop half still considers every term in the
model except those inforce_inand those frozen bymax_movebefore
the iteration began.hzr_bootstrap()refuses the combination before
seeding. -
hzr_bootstrap()now refuses a selection argument passed without
scope(#343). Withoutscopethere is no screen, anddirection,
criterion,slentry,slstay,max_steps,max_move,force_inand
force_outwere ignored:direction = "backward", force_in = "age"
returned a fixed-model bootstrap with every term atpct = 100and no
message. A value other than the argument's default is now an error, so a
wrapper that passes the defaults on still works. Passscopeto screen,
or omit the argument to refit the exact model. A value equal to the
default is accepted whether or not it was passed: withoutscopeit asks
for nothing, and nothing reads it. -
A two-sided
scopeformula is now an error inhzr_stepwise()and
hzr_bootstrap()(#343). Only the right-hand side was read, so
scope = com_iv ~ age + malscreenedageandmaland never tested
com_iv, with no message. The error names the left-hand side. The same
applies to each element of a multiphasescopelist, and a list that
names a phase twice, whose second entry was never read, is refused too. -
hzr_stepwise()andhzr_bootstrap()now check...against what a
candidate refit may take from it (#386). Both pass...on to every
candidate refit, andhazard()stores a name it does not declare without
reading it. So a misspelled argument had no effect:slentyr = 1e-6,
meant asslentry, ran the screen at the defaultslentry = 0.30and
selected three variables where the intended threshold selects one, with
no message. Otherhazard()arguments were no safer: a refit takes the
response anddistfrom the base model, sotime_lowerwas ignored on a
formula fit anddistfailed every candidate, andweightsor
time_windowschanged 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....forwardscontrol, including by an unambiguous abbreviation
such ascontr, and anobjectiveequal to the base fit's. A differing
objective, acontrolthat 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 nameshzr_bootstrap().?hzr_stepwisehad also described...
as unused, because the print method's entry replaced it. -
hazard()now refuses a multiphase phase formula with covariates when
nodatais supplied (#299). Such fits previously ignored the phase
formula. On the vector interface (time =,status =) withoutdata, a
phase's own formula was never evaluated: the phase took the globalx, or
no covariates at all, sohzr_phase(formula = ~ mal)fitted a model
withoutmal, with no warning and no message. The call now stops with an
error naming the phase and its formula, underfit = FALSEas 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~ 1unless the call also has a
globalxwith at least one column. Beside such anx, a~ 1phase
is refused too: withoutdatait silently tookx, and withdatait
has no columns, so the same call gave two different models. Pass
data =, use the formula interface, or dropx. A constant term such
as~ log(2), which builds a column only indata, is refused as well. -
hzr_stepwise()now refuses a fit saved by an earlier version whose
phase formula was ignored this way (#299). Givendata, every refit
built the ignored formula's columns into a model whose base never had
them: a forward screen reportedENTER ageover a final model that also
carried the ignoredmal, with no warning. The error names the phase and
its formula. Refit the base model withdata =and retry. -
hzr_stepwise()andhzr_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
namesdata = ddreads the same whetherddwas a data frame orNULL,
and aNULLthere 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 keptdata$frame(every fit from 1.1.0 on,
unless that element was removed) is unaffected, and so is a call without
time =. A call withtime =is read as the vector interface even when
it also passes a formula, so a wrapper'sformula = fmlbeside 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 factorgwith
levelband a numeric columngbboth produced a columngb. The fit
ran without a word:coef()carried twogbnames, andpredict()on a
one-rownewdatareturned two values. The check covers the global design,
anxmatrix passed to the vector interface, the design after
time_windowsexpansion, and, whenfit = TRUE, each phase formula of a
multiphase fit. The error names the colliding columns. Unnamed columns of
xare allowed, but not undertime_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, soSurv(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, everyhzr_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'sdata
fromnewdata. A term that uses row-level values kept outsidedata
(a vector, matrix, list or environment in the formula's environment, as
in~ zzor~ ext$z) is refused, even whennewdatasupplies 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
intodataas a column and refit. When the term's values simply do not
line up withnewdata's rows, the error says so and gives both causes,
since a length-changing function of adatacolumn, 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()ormodel.matrix()call inside that term, which is read
as the design build's own failure and replaced by the naming error.
Before, a suppliedzzor matrixMwas used, but a missing or
list-held one was silently read from the fitting rows (see Bug fixes).
Formula constants, such ascutoffin
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)withoutthetais now an error for the
single-distribution models. Fordist = "weibull","exponential",
"loglogistic"and"lognormal", the optimizer ran only whenthetawas
supplied, so a call that left it out returned an unfitted object --NULL
coefficients, anNAobjective -- 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-lengththeta, which used to fail insideoptim()with a
message that did not name the cause.
dist = "multiphase"is unaffected: it assembles its own start from
phases.fit = FALSEwithoutthetastill builds an unfitted model.Code that relied on the old behaviour was getting no fit. Two tests in this
package were: they comparedNULLcoefficients withNULLcoefficients,
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/ optionsto one covariate at a
time, soEARLY 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 thatOPMOSwas gone. Each
covariate now keeps its own options:/E(EXCLUDE) leaves it out of
the model, asPROC HAZARDdoes without aSELECTIONstatement;/I
and/Sleave it in; and a per-variableMOVE=orORDER=, or any other
option, is recorded in$untranslatedunder the
variable's name.Two related fixes. A second
EARLY,CONSTANTorLATEstatement 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 inthetafor
one column, shifting every later one.A
SELECTION NOSTEPWISE(orNOSW) job is no longer read as no screen
at all. It was translated to a plain fit with every candidate in the
model, butPROC HAZARDstill screens, forward only, with each candidate
starting out of the model; it now translates as a forward-only screen
(see theSELECTIONentry 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 inPROC HAZARD, whichhazard()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 astop()in place of the fit when a
PARMSstatement builds no phase it could use. Operands the translator
could not read (a template'sMUE=?, orMUE = 0.2written with spaces
around=, whichPROC HAZARDaccepts) are recorded in$untranslated,
but the fit chunk
used to be emitted anyway, ashazard(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 aboutthetathat does not name the real
cause. The emittedstop()names it. This is a limit of the translation,
not aPROC HAZARDrefusal, so it is kept apart from the existing
"selects no phase" stop. -
hzr_translate_sas()no longer writesCONDITION=orQUASIinto
hazard()'scontrol(#384). They were emitted asconditionand
method, whichhazard()never reads, so the translation counted two
options as mapped while they did nothing. No fit changes. Both are now
recorded in$untranslatedwith the reason.CONDITION=stops
PROC HAZARD's optimizer when its Hessian approximation becomes too
ill-conditioned, andhazard()has no such stop; it warns about the final
Hessian instead; aCONDITION=outside 3 to 14, whichPROC HAZARD
itself ignores, is recorded as such.QUASIchoosesPROC HAZARD's
optimizer, andhazard()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 astop()in place of the fit when a
job has noDATA=and a phase has covariates (#311). A phase's
covariates are evaluated only indata, and such a job's fit chunk has
none, sohazard()stopped with advice to passdata =, 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
$untranslatedwith the reason, and the emittedstop()says to add
DATA=and translate again. A job with noDATA=and no phase
covariates still translates to a fit, unless it has aSELECTION
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). ASELECTION
job withholds its candidates from the base model, so a refusal reading only
the base model could not see them. When the job'sSELECTIONcould not be
run anyway (FAST, say), that reason is recorded in$untranslatedbeside
theDATA=one, so addingDATA=does not reveal a second refusal. A
SELECTIONjob with noDATA=is refused even if its phase statements
name no variable, because a screen refits every candidate fromdata. This
widens #311 on purpose. A job with noDATA=is now refused, with or
withoutSELECTION, when its only phase variables are any of these:- excluded with
/E; - on a
LATEstatement whenPARMShas noMUL; - on a
CONSTANTstatement whenPARMShas noMUC; - on an
EARLYstatement whenPARMShas noMUE.
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. - excluded with
-
predict(newdata = )matches covariates by name, sonewdatawith
other names now stops. A fit made through the vector interface with a
namedx, sayx = cbind(age = , mal = ), needsnewdatacolumns
calledageandmal. 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 suchnewdatanow gets an error naming the missing columns:
rename the columns to matchx. A fit made with an unnamedxstill
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
asgrpyoungand a transform aslog(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, sopredict(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:newdatagiving a numeric variable and a column built
from it, such asageandI(age^2)at the design-column means, is now
rebuilt fromage, 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()andsplines::bs()).
A constant
such askinI(age > k), a function of the user's, or evenpi
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,newdatawith columns other than the
design columns andtimeis 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 whennewdatagives some of the formula's
variables beside the fitted design columns, with others missing. Say
a fit of~ age + grp + sex, withnewdataholdinggrp = "old",
grpyoung = 1andsexM = 0but nosex. That used to return a value,
and silently the wrong one: the design columns were used, and the
grpit 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 (hereage,grpandsex), or the design columns alone
(age,grpyoung,sexM). A numeric covariate such asageis both a
variable and a column, so it makes a mix only when another column is
built from it, as in~ age * grporI(age^2): a changedagewould
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 namedtime. Innewdatathe columntimeis the
prediction time for"survival","cumulative_hazard", every multiphase
type and any fit withtime_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
~ timegave 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 namedtime, as in
I(age > time), was replaced the same way, because a formula looks its
symbols up innewdatafirst. 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 namedxor 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 namedtimeitself, 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
timevariable 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 ascfg$timedo not stop them.
In the global formula, neither does a value thatscale()stored at
fit time, inpredict()either. A phase formula is re-evaluated as
written, so atimeconstant there stops even insidescale().
"linear_predictor"and single-distribution"hazard"have no
prediction time, so they read atimecolumn as the covariate, now
also when it is the only one (it used to stop with "Predictors are
required"). They refuse only atimeconstant that atimecolumn in
newdatawould mask.predict()withoutnewdatais 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 = ...)readconstant(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 anagecoefficient, 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 intohzr_phase()to get the one the formula described.
Formulas that call an ordinary function such aslog(), 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 itsdata(#278). Replicates resample
the rows ofdata, 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: onavc, a copy ofagekept outsidedata
gave an interval that excluded its own estimate. The check covers the
response, the covariates of the global and phase formulas, and a
select-modescope, and reads variables from the terms, solog(age)
needs only the columnage. A constant outsidedata, such aspi, a
cutoff or a knots vector, is still allowed. The error names the
variables; add them todataand 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 asx: 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 asSurv(...) ~ .
(#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. Ascopeof~ ., 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 asx(#284). A phase
with no formula of its own uses thatx, and a candidate refit had no
terms to rebuild it from, so every refit dropped its columns from every
such phase: onavc, enteringmalinto one phase tookageout 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 withhzr_phase(formula = ~ ...), with the columns
indata; 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 itsscopenames, 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 = timenow means "no entry" in every family (#253). On a
row with status 0 or 1,time_loweris the counting-process entry time
when0 < time_lower < time.hazard()used to warn when
time_lower >= timeon such a row and fit anyway, and each family then
did something different. Multiphase returned a "log-likelihood" of
+47915.76 withconverged = TRUEon the AVC data. The Weibull read the
rows as entering at time 0, and the other three families ignored
time_loweraltogether.time_lower > timeon a status 0/1 row now stops, as SAS HAZARD
rejects a start time after the exit time (errorSETCOE960).time_lower == timeon 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 carrytime_lower = timeand 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 = 0still means no entry time.- Rows with
time_lower == time > 0beside rows with a genuine entry
time now stop. In counting-process data they are zero-length epochs,
whichhzr_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 ofpredict(decompose = TRUE)starts with atime
column holding the prediction times, then adds one column per phase under
the phase's name. A phase calledtimetherefore overwrote the requested
times with its own cumulative hazard, and the times were lost with no
error.hazard()andhzr_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 atimeof
length 0, or a formula whosedatahas no rows, every distribution
returned ahazardobject anyway, and withfit = TRUEit 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, underfit = FALSEas 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 wassurvival::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 withconverged = TRUE. The error names the rows. Pass
aSurvobject as the response, or asstatus, and it is translated.
A character or factorstatusis 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 logicalstatusis still accepted.
A classed numeric such asbit64::integer64, whichdata.table::fread()
andarrowproduce, is now read as its values intime,status,
time_lower,time_upperandweights, and as a column ofdata,
whereSurv()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 ofhzr_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_failureswith the refusal as its reason in
$criteria$refit_failure_reasons, and the screen goes on, as"score"
already did. Code that caught the error withtryCatch(..., error = )
will no longer see it; read$criteria$refit_failuresinstead. -
hzr_stepwise()'s$steps$variablerecords the model's term label on
every row (#449). An entry row used to carry the name as thescope
wrote it and a drop row theterms()label, so a non-syntactic column
_X1entered as_X1and left as`_X1`, and a literal column
age:malentered under the interaction's spellingage:mal. Every
row now uses the label, whatever form thescopetook. 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.