generate_model_set() gains
null.cov.cutoff, defaulting to 0.8, and returns
null.term.correlations. Behaviour change:
a predictor correlated with a variable named in null.terms
above that cutoff is now dropped from the model set, and a warning names
it, what it correlates with, and how strongly (FSSgam_package#23).
null.terms forces a term into every candidate, and those
terms were outside the cov.cutoff screen, which covers
pred.vars.cont, pred.vars.fact and
linear.vars only. So a candidate could be arbitrarily
strongly correlated with a forced term and appear in every model in the
set. That inflates the variance of the forced term’s estimate, which is
frequently the term the analysis exists to estimate, and nothing in the
output indicated it.
A separate cutoff, with a much looser default than
cov.cutoff’s 0.28, because the two screens answer different
questions. cov.cutoff decides which predictors may appear
together; this decides which predictor is so nearly a restatement of a
forced term that fitting both is not informative. Set it to 1 to admit
every predictor whatever its correlation with a forced term.
A variable inside a bs='re' smooth is excluded
from the screen. A random-effect grouping factor is correlated
with the predictors measured within it by construction: in the companion
repository’s case study 2, null.terms is
s(Location,Site,bs='re') and Status is nested
in Location, so their correlation is 1. That is the design
of the study rather than collinearity to screen out, and dropping
Status there would be wrong. The screen is for a forced
term that competes with a candidate for the same variation.
Correlations among the null.terms variables themselves
are neither computed nor screened. Those terms are forced in by the
user’s decision, and dropping one is what must not happen.
null.term.correlations is returned whether or not
anything is dropped, so the correlations can be inspected where no
warning is raised; it is NULL where there is nothing to
report. A supplied cor.matrix is used for any forced term
it names in either dimension, overriding the computed estimate for that
term; the rest of the block is computed from use.dat, which
is the ordinary case, a supplied matrix being indexed by predictor and a
forced term not being one. Requiring the matrix to name the term would
disable this screen for every caller who supplies one. Where the
computation fails – which is what a predictor of a class
check_correlations() cannot classify causes – the screen is
skipped with a warning rather than the call stopping, so a caller
relying on FSSgam_package#13 still gets their model set and is told
their forced terms were not screened.
A pair a supplied cor.matrix gives no value for
is treated as not supplied, and computed from
use.dat. That is NA in both
directions, or NA in the one direction a matrix naming the
forced term in a single dimension has. Read as a correlation of zero,
such a pair admitted the predictor to every candidate alongside the
forced term – the outcome this screen exists to prevent – decided by a
cell the user left empty rather than by a correlation, while the same
NA between two predictors stops the call
(FSSgam_package#27). Where one direction gives a value, that value is
used, unchanged. A forced term can be part supplied and part computed,
the two being merged cell by cell rather than row by row
(FSSgam_package#41).
Where the correlation estimate is asymmetric, as
check_non_linear_correlations() returns it, both directions
are read and the larger is used – what the cov.cutoff
screen already does. Reading one direction admitted a candidate that is
a deterministic function of a forced term: measured on a squared forced
term in the committed test, [forced, candidate] is 0.280 –
below the default cutoff – while [candidate, forced] is
0.991. The smaller figure depends on the random draw and the test
asserts only that it is below the cutoff, which is the property that
matters. Each cell of null.term.correlations is the value
screened on rather than one direction of it; on the symmetric default
path the two are equal.
null.cov.cutoff is validated: it must be a single
non-negative number. Unvalidated, an NA gave an internal
“missing value where TRUE/FALSE needed” and a length-2 value was
accepted silently.
Where dropping predictors leaves fewer than
max.predictors, the error says so and names them, rather
than reporting only that max.predictors exceeds the number
of predictors.
Behaviour change: a test.fit
produced by mgcv::gamm() directly is rejected, with a
message naming the remedy. Such a fit records no call, so
stats::update() has nothing to re-evaluate and every
candidate refit fails. The error it produced instead – “need an object
with call component” – named neither the argument nor the remedy, and
the message that followed advised the user to stop using
uGamm, which is what they would have had to use to succeed
(FSSgam_package#34).
The documentation is corrected with it.
generate_model_set()’s null.terms text
directed the user to gamm for a correlation structure, its
@details contrasts gam and gamm
at length, and fit_model_set()’s r2.type names
gamm among the classes it reports for. All three now say
that a gamm fit reaches this package only through
MuMIn::uGamm, which supplies the call that a bare
mgcv::gamm() fit lacks.
Behaviour change:
generate_model_set() rejects a predictor named twice within
pred.vars.cont, pred.vars.fact or
linear.vars, naming it. Such a name produced a candidate
holding it twice, ZONE+ZONE, together with two “no
non-missing arguments to max; returning -Inf” warnings naming nothing:
enumerate_candidate_models() deduplicates a candidate’s
terms before indexing the correlation matrix, so a candidate holding one
term twice gives a 1x1 sub-matrix whose triangles are empty, and
max() of an empty vector returns -Inf, which
is below any cov.cutoff (FSSgam_package#28).
The warnings had a second cause, which rejecting the repeated name
does not address: naming a predictor in both pred.vars.cont
and linear.vars, the documented idiom below, puts the name
in the candidate pool twice and reaches the same empty triangle at any
max.predictors of 2 or more. Both screens now test the
sub-matrix through a helper that returns FALSE for an empty
triangle rather than calling max() on it, so no candidate
set emits the warning. Which combinations survive is unchanged,
-Inf having been below every cutoff.
A name appearing in both pred.vars.fact and
pred.vars.cont or linear.vars is rejected too,
as is a name repeated within the character form of
factor.factor.interactions or
smooth.smooth.interactions. That last is a third route to
the same defect and the one the others miss:
factor.factor.interactions = c("fa", "fa", "fb") built the
interaction column fa.I.fb twice, so use.dat
gained two columns of that name and the candidate set gained
fa.I.fb+fa.I.fb.
A name appearing in both pred.vars.cont and
linear.vars is not rejected: that is the
documented way to fit a continuous predictor linearly,
setdiff(pred.vars.cont, linear.vars) deciding which
predictors get a smooth.
Behaviour change: a variable named in the
character form of factor.factor.interactions or
smooth.smooth.interactions must be a predictor of the model
set. Each argument was checked against the wrong set – the first against
colnames(use.dat), so any column was accepted, and the
second against rownames(cor.matrix), so what was accepted
depended on whether a cor.matrix was supplied and what
names it carried. A non-predictor named in either was screened against
cov.cutoff and contributed interaction terms, while
appearing in no other candidate, in included.vars, or in
the variable-inclusion columns fit_model_set() returns
(FSSgam_package#37).
Behaviour change: a factor predictor with fewer than two observed levels is rejected, naming it and its level count. Such a factor explains nothing, cannot be screened for collinearity, and produces an interaction column that is a copy of the other factor, so the model set holds the same model under two names. Levels declared but taken by no row are counted as absent (FSSgam_package#33).
Bug fix: check_correlations() and
check_non_linear_correlations() now wrap their
lm() call in try(), as every other fit in both
already was. It was the one unguarded fit in each, so a single-level
factor stopped the whole call with “contrasts can be applied only to
factors with 2 or more levels” instead of leaving that cell
NA. Both are exported and callable directly, so the guard
matters beyond the generate_model_set() path the check
above covers (FSSgam_package#33).
Behaviour change: a hard coded factor
interaction column whose generated name is already a column of
use.dat is not created, and a warning names it.
cbind() accepted the duplicate, and every later stage
selects predictors by name: use.dat[, "a.I.b"] returned the
first of the two, which is the user’s own column, while the model
formulas and the correlation matrix were built for the generated one.
Nothing reported the collision. Declining rather than overwriting,
because overwriting silently changes a predictor the user named
(FSSgam_package#22).
This is reached without contrivance: passing the
used.data of one call as the use.dat of the
next presents every generated name as an existing column.
Two generated names can also collide with each other, which is the
same defect reached from inside rather than outside:
(a, b.I.c) and (a.I.b, c) both paste to
a.I.b.I.c, and the two columns are different variables.
That is declined and warned about as well, though it is no longer
reachable through the public API, the predictor name it requires being
rejected by the check below.
Behaviour change: a predictor whose name
contains .by., .te., .t.,
.I., + or * is rejected, as is
one named null. Those strings separate the parts of a
generated term name and are found with grep(fixed = TRUE)
over every term, so a predictor named catch.by.effort – an
ordinary variable name – was parsed as an interaction. The candidate
became ~s(catch.by.effort) + s(catch, by = effort), which
cannot be fitted, so the predictor vanished from the model set. The only
indication was the -Inf warnings, which this release
removes for unrelated reasons (FSSgam_package#39).
+ joins the terms of a candidate name and *
writes a linear interaction, so either is parsed as more than one term
in the same way. null is the name given to the null model’s
own candidate, so a predictor of that name produced two candidates
called null: mod.formula[["null"]] returned
the first, and the variable importance table matched the wrong row.
factor.smooth.interactions in its character form is
validated alongside the other two interaction arguments, which it was
not.
This rejects the failure rather than fixing the parse. Making a
term’s structure explicit instead of recovering it from its name is a
larger change, and the issue records what it would involve. One
consequence is worth knowing: passing the used.data of one
call as the use.dat of the next presents generated names,
which contain .I. by construction, so those columns must be
dropped or renamed before they can be named as predictors.
Bug fix: a user-supplied cor.matrix with exactly one
cell was silently discarded and recomputed from use.dat. A
model set with a single predictor is the only case in which a supplied
matrix is 1x1, and length(cor.matrix) == 1 was the test for
“nothing supplied”, which a 1x1 matrix also satisfies. The supplied
value governed no screen and did not appear in
predictor.correlations, which is the opposite of the
documented contract that a supplied matrix replaces the automatic
estimate (FSSgam_package#26).
The test is now on the default value rather than on length, in both
places it was made – build_predictor_correlation_matrix()
and the factor_correlations() helper inside
resolve_factor_interactions(), which had its own copy. It
tests dimensionality rather than a list of accepted classes, so the S4
matrix Matrix::Matrix() returns is accepted, as it was
before this argument was validated at all. Note that
Matrix::nearPD() is not such a case: it returns a list
whose mat component is the matrix, and only that component
was ever accepted.
Behaviour change: cor.matrix is now
validated, and a value that is neither a matrix, a data.frame, nor
NA is rejected naming its class. What happened to such a
value before depended on its length, and neither outcome was right. A
length-one value of any class – a string, or a list holding a matrix –
satisfied the sentinel and was silently treated as nothing supplied, so
the model set was built from a computed matrix as though the argument
had not been given. A value of any other length fell through to the
supplied branch and failed against the missing-predictor check,
reporting predictors rather than the argument. Calls of the first kind
used to run and now error.
Behaviour change: an NA between two
terms of a supplied cor.matrix is now reported by
generate_model_set(), naming the pairs, where it previously
stopped the call with missing value where TRUE/FALSE needed
— naming neither the matrix, the argument, nor the pair
(FSSgam_package#27).
The report is made where each sub-matrix is formed, immediately
before the screen takes max(abs(.)) of it, rather than at a
single earlier point. Two earlier placements were tried and both were
wrong, in opposite directions. Checking only in
build_predictor_correlation_matrix() missed every pair
screened before it, resolve_factor_interactions() running
first. Checking a union of every name that might be screened then
reported pairs that are not: a name given in the character form of
factor.factor.interactions is screened against the others
in that argument and never against pred.vars.cont.
Reporting at the screening site covers exactly the pairs that are
screened.
A consequence, and a departure from what the issue asked
for. The report covers exactly the pairs some screen compares,
so it depends on max.predictors and on the interaction
arguments. The issue asked for the report not to depend on
max.predictors; that would mean rejecting a matrix over a
cell nothing reads, which is a false failure. An NA is
reported whenever it is read and not otherwise.
Note that “read” is not the same as “in a candidate together”. A
.by. term is one term of a candidate but splits into two
for the screen, so at max.predictors = 1 a continuous
predictor and a factor are still compared; two continuous predictors are
not.
Bug fix: the correlation matrix
resolve_factor_interactions() computes when no
cor.matrix is supplied is now zero-filled, as the one
build_predictor_correlation_matrix() computes already was.
Without it a computed matrix reached the factor-factor screen with
NA in it – check_correlations() returns
NA for a pair involving a single-level factor
(FSSgam_package#33) – and the call stopped with
missing value where TRUE/FALSE needed
(FSSgam_package#27).
Behaviour change, and a poor outcome that is better than the
error it replaces. An NA becoming zero means
“uncorrelated”, so a factor-factor pair that previously stopped the call
is now admitted. Where the NA came from a single-level
factor the interaction column is a copy of the other factor:
fa.I.const is paste(fa, "a"), so
fa and fa.I.const are the same model under two
names, and both enter the AICc table and the variable importance scores.
That is not a good model set, and the real defect is that a single-level
factor is accepted as a predictor at all, which is
FSSgam_package#33.
Measured over a grid of 432 scenarios – six factor sets, three
continuous sets, both forms of factor.factor.interactions,
max.predictors 1 to 3, both settings of
non.linear.correlations, two forms of
factor.smooth.interactions – comparing this version against
the previous one:
| outcome | cells |
|---|---|
| identical model set | 104 |
| identical error | 208 |
| error before, model set now | 40 |
| error before, a different error now | 80 |
| model set before, a different model set now | 0 |
| model set before, an error now | 0 |
No call that produced a model set before produces a different one, or fails, now.
Two things bound the change. Every one of the 40 cells that now build
has pred.vars.cont = NA: name a continuous predictor and
the call still stops, in check_correlations(), on the same
single-level factor (FSSgam_package#33). And all 80 differing errors are
single-level-factor scenarios reaching a later failure than before; the
scenarios whose predictors contain NA are 144 of the 208
that error identically, rejected before any of this code runs.
Two earlier counts of this grid were wrong and are recorded so the
figures are not taken from them. The first reported 392 identical, of
which 288 cells passed an invalid null.terms and were
identical only in the error string validate_null_terms()
returns, so no model set was compared in them at all. The second
reported 144 identical, which double-counted the 40: 144 is the number
of cells that build under this version, not the number that are
unchanged.
combine_uncorrelated() and
enumerate_candidate_models() are otherwise unchanged:
stopping on an NA is the right behaviour at both, the
alternative being to drop the combination silently, and this only makes
the message name the pair.
DESCRIPTION now declares
Depends: R (>= 4.4.0), raised from
R (>= 3.5). The old floor was not reachable:
MuMIn and mgcv, both hard
Imports, each declare Depends: R (>= 4.4.0)
at the versions current when this was measured (MuMIn 1.48.19, mgcv
1.9-4), so the package could not be installed on anything lower whatever
this field said. Nothing about what the package can do changes; the
declaration now states what was already true. A user pinned to an older
MuMIn and mgcv may find the new floor excludes
a configuration that did work, which is the reason the change is noted
here rather than treated as a correction (FSSgam_package#31).
The floor is now checked rather than asserted.
tools/check-r-floor.R compares the declared floor against
the Depends of every hard dependency, transitively, and
fails if the declared one is lower; it runs in CI as a new
r-floor-consistency job. The closure matters:
Matrix declares R (>= 4.4) and is not a
direct dependency of this package, arriving through mgcv
and MuMIn, so a check of the direct Imports
alone would miss it. The script also stops rather than passing wherever
it cannot count a constraint: a dependency that is not installed, an R
constraint written in a form it does not read, and a
DESCRIPTION declaring no floor at all.
.github/workflows/R-CMD-check.yaml also gains an
ubuntu-latest, R 4.4.0 matrix entry, so the
package is built, installed and tested on an R at the declared floor for
the first time.
The two check different things, and only the first detects what was
wrong here. Installing a package rejects a floor set above the running R
and accepts any floor below it, so no matrix of R versions can see a
floor that is too low: reverting DESCRIPTION to
R (>= 3.5) leaves every matrix job green.
Consequently the test suite uses deparse1() directly and
the local deparse_one() in
tests/testthat/helper-fixtures.R, which existed only
because deparse1() arrived in R 4.0.0, is removed.
fit_model_set()’s r2.vals.unique column
is now documented. It was returned in mod.data.out and
described nowhere. The documentation states that the column is the model
R2 minus the null model’s R2, so it is the variance explained beyond
whatever was supplied in null.terms; that it is a per-model
quantity rather than a variance partition among terms, and so is a
per-predictor contribution only at max.predictors = 1; that
it is on whatever scale r2.type produced, and so can be
negative; and that values are comparable only within a model set sharing
the same null.terms and r2.type
(FSSgam_package#24).
check_non_linear_correlations() now guards the
intercept-only multinom() fit against try()
failure, as check_correlations() already did. Where only
the fitted model was tested, a failed null was passed to
round() as the character vector try() returns,
raising “non-numeric argument to mathematical function”, which
propagated out of the function rather than leaving the cell
NA, which is how a failed fit is already handled. The guard
is defensive: both models are fitted on the same rows, so no case
reaching it is known, and no test accompanies it (FSSgam_package#19).
This does not make the function degrade to NA in every
failure. The lm() branch of the same function, and the
equivalent one in check_correlations(), are not guarded at
all, and a single-level factor predictor still aborts both:
FSSgam_package#33.
The two github.io URLs in DESCRIPTION,
and the one of them also written in README.md, are
rewritten with a trailing slash. Without it both redirect with a 301 and
R CMD check --as-cran reports them under checking CRAN
incoming feasibility, which devtools::check() does not run
(FSSgam_package#20).
gamm4 moves from Imports to
Suggests. A fresh install of this package no longer
installs gamm4 or lme4. If you fit through
MuMIn::uGamm(lme4 = TRUE), as the companion repository’s
case study 2 does, install gamm4 yourself. Nothing
in this package calls it: the only uses are
inherits(x, "gamm4") class tests, which need no package,
and an @importFrom gamm4 gamm4 directive for a function
never called. That directive is what made every parallel worker load
gamm4 and lme4, through the
FSSgam namespace, for every model set regardless of the
family fitted.
fit_model_set(parallel = TRUE) no longer names
gamm4 in the packages it attaches on the
doSNOW workers. Together with the change above this is the
only direction recorded on FSSgam_package#14 that measured as reducing
the dispatch stall, in which the call hangs indefinitely with no output
and no error. Running fit_model_set(parallel = TRUE) on
case_study1 from installed copies of each version,
alternating in blocks of ten so that both saw the same machine load, one
cluster per fresh R process, gave 44 stalls in 140 runs before and 25 in
140 after (chi-squared p = 0.013, this host, 2026-09-01).
The narrower comparison agrees. A foreach() whose body
is i * 2, so that no package code runs at all, was run 50
times with gamm4 in the packages the workers attach and 50
times without, alternating run by run: 34 stalls against 19 (chi-squared
p = 0.005, same host and date).
Three qualifications. That is a reduction and not a removal, and
FSSgam_package#14 stays open. Both figures are one session’s measurement
of a rate on one host, and an earlier run of the narrower comparison
with its two arms taken one after the other rather than alternated gave
a result an independent replication could not reproduce; only
alternating designs are quoted here. And neither extends to a
uGamm(lme4 = TRUE) test.fit: unserialising such an object
loads gamm4 and lme4 on the worker by itself,
before the packages are attached, so for those users the worker side is
substantially unchanged.
check_correlations(parallel = TRUE) benefits as well,
without any change of its own: its workers load the FSSgam
namespace and so used to load gamm4 through it.
Bug fix: check_correlations() estimated the
correlation between two factor predictors from deviances computed on
different sets of rows whenever either factor contained missing values.
The fitted model was fitted on the pair’s complete cases and the
intercept-only model on the whole column, so the ratio the estimate is
built from was not comparable and the reported correlation was wrong by
an amount that depended on how much of the column was missing. Both
models are now fitted on the pair’s complete cases. Values are unchanged
for data with no missing predictor values, which is every call made by
generate_model_set(), since it rejects predictors
containing NA (FSSgam_package#16).
Bug fix: generate_model_set() screened the wrong
pairs for collinearity when a supplied cor.matrix carried
the same names in a different order in its rows and its columns. The
correlation sub-matrix for a candidate was built from two positional
indices, one in rowname order and one in colname order, so where the two
orders differed the diagonal of that sub-matrix held cross correlations
and the triangles held the ones from the original diagonal, and the
candidate was dropped whatever its predictors’ correlation. Both sites
now index by name in one order. Measured on master, with a
supplied matrix over depth, complexity and
ZONE, pred.vars.fact = "ZONE" and everything
else at its default: the matrix with its columns permuted returned 5
candidates where the same matrix unpermuted returned 9 at
max.predictors = 2 and 13 at
max.predictors = 3. On this branch the two orders agree, at
9 and 13. The same change means a factor named twice in
pred.vars.fact no longer yields an interaction of that
factor with itself (FSSgam_package#15).
generate_model_set() no longer requires a
user-supplied cor.matrix to name the hard coded factor
interaction columns that factor.factor.interactions causes
to be created. Which of those columns exist depends on the supplied
matrix itself, through the collinearity screen that decides which factor
combinations survive, so a user could not know in advance which names to
supply; the call stopped partway through model set construction
demanding them. Rows and columns for any interaction column the matrix
does not carry are now computed from use.dat and appended,
and every value the user supplied is kept as supplied. A predictor the
user named and did not supply is still an error, so a misspelled name is
still reported rather than quietly computed
(FSSgam_package#15).
fit_model_set() and full_subsets_gam()
gain logLik.fn, a function of one fitted model returning a
single log-likelihood value, defaulting to NULL. One
restriction applies under parallel = TRUE with
save.model.fits = FALSE, the only combination that
evaluates it on a worker process: a function written at the top level of
a script has its environment replaced before it is sent, so anything it
refers to and does not define is not found there. Build it with a
constructor, or write it to refer to nothing outside itself. This is the
restriction beckyfisher/FSSgam#10 reports for the
family argument, and it has the same cause.
AICc and BIC are ordinarily read from
MuMIn::AICc() and stats::BIC(), which both
resolve to the fitted family’s own aic slot. Where
supplied, logLik.fn replaces the log-likelihood alone: the
criterion is built at the degrees of freedom and sample size the default
route uses, so every model in the set is scored the same way and the
delta values, the weights and variable importance follow.
extract_mod_dat() takes the same argument.
Behaviour change: a model set whose
test.fit was fitted with one of mgcv’s
censored families, cnorm() or clog(), is now
ranked on a censored log-likelihood computed by this package, and a
message says so. The ranking, the weights and variable importance
therefore differ from those of 1.1.0 and earlier for those two families
and no other (FSSgam_package#42).
Both families’ aic slots return a value that is not a
censored log-likelihood in mgcv 1.9-4, and
AICc and BIC were read from them. The fits
themselves are unaffected: dev.resids and ls
contain none of the defects, so the smoothing parameters, the
coefficients and the scale are as before, and only the reported
criterion changes.
Measured on Debian WSL2, R 4.6.1, mgcv 1.9-4 and
MuMIn 1.48.19, on the simulated set the issue reports:
n = 300 with the lowest 20 per cent left censored, four
candidate predictors of which two are noise, and eleven candidates: the
previous criterion selected x, and the censored
log-likelihood selects x+z, which is the generating model.
The spread of delta.AICc across the set was 35.8 against
310.8, so a nominal delta.AICc <= 5 admitted four models
against two. Summed variable importance put the noise predictor
v at 0.253 above z at 0.182, where
z is in the generating model; it now reports 0.035 and
0.838.
The log-likelihood is computed from the fitted mean, the scale and
the response’s censoring coding rather than from the family’s
aic slot, so it is independent of the defects and stays
correct if they are repaired. Left, right and interval censoring, either
column order, and non-unit prior weights are all covered, and every
quantity is computed in logs so that an observation far into a tail does
not underflow. An infinite bound written in the first column is refused
instead: mgcv reads a censored case from the second column
alone, so under clog() such a row matches none of its
censored cases and is dropped from the fit without a message, and
scoring it would count observations the fit ignores.
cnorm() does not fit that coding at all. It is checked in
the test suite against a second computation built from
mgcv’s own saturated log-likelihood and deviance residuals,
which shares no code with it.
Behaviour change: fit_model_set()
stops, before any candidate is fitted, on a test.fit whose
criterion is not defined, naming the family. A quasi-likelihood such as
quasipoisson() or quasibinomial() has no
log-likelihood and so no AIC, and MuMIn::AICc() returns
NA for every candidate of such a set. The whole of
mod.data.out was then unusable – delta.AICc,
delta.BIC and both weight columns NA, and
variable importance with nothing to weight – and nothing named the
cause. The fits succeeded, and the only sign was four
no non-missing arguments to min warnings, two raised by
compute_model_weights() taking the minimum of the
all-NA AICc and BIC columns and
two by wi() doing the same. Measured on the eight-model
case_study1 set at 023cd1a
(FSSgam_package#44).
Two checks, because neither covers the other. The value
MuMIn::AICc() returns for the test.fit is the
general one: it does not depend on a list of family names, so any family
whose criterion is undefined reaches it. The fitted family’s name is
checked as well, because a quasi-likelihood supplied through
MuMIn::uGamm() or mgcv::gamm() passes the
first. AICc for such a fit is read from the internal
lme fit of the PQL working model and is a number, and a PQL
log-likelihood is a function of the working response, which itself
changes with the fixed effects, so those differences rank nothing.
That second check is a behaviour change beyond the
NA case. Measured on case_study1,
uGamm(Herbivore.abundance ~ s(depth, k = 3, bs = 'cr'), random = list(site = ~1), family = quasipoisson())
with three candidates: 1.1.0 returned a complete table,
AICc 170.261, 176.030 and 138.582 and a weight of 1.000 on
the best; this release stops instead, naming the family.
Supplying logLik.fn is what allows either set to be
fitted and ranked; the message says so, and names nb(),
tw() and betar() as families that have a
likelihood. generate_model_set() fits the null model, so on
the full_subsets_gam() route that one fit precedes the
refusal.
fit_model_set() also stops where every candidate that
fitted was given no criterion, and warns naming the candidates where
only some were, which a supplied logLik.fn that gives the
test.fit a value and the candidates none would otherwise
produce silently.
Bug fix: summed variable importance came back NA for
every predictor whenever any candidate in the table had an
NA model weight, including predictors appearing in no such
candidate. The weights are now summed with na.rm = TRUE,
which is what corrects VI.mods = "all". Under the default
VI.mods = "min.n" the correction is elsewhere: the count of
models per predictor is taken over the candidates that have a weight
rather than over every row, so the two fitting paths give the same
scores for the same model set.
The route to it on 1.1.0 is a candidate that failed to fit with
save.model.fits = FALSE, which keeps a row for it; the
save.model.fits = TRUE path drops the row and was
unaffected, so which of the two a user saw depended on a memory setting,
and max.models changes that setting without being asked. A
supplied logLik.fn that gives some candidates no value
reaches it as well.
Bug fix: fit_model_set(save.model.fits = FALSE)
recorded a candidate that fitted and was given no criterion as a model
that failed to fit, and reported it in failed.models. A
failed model is now identified by whether fit_mod_l()
returned a fitted object, which is the test the
save.model.fits = TRUE path has always used, so the two
paths agree on which candidates succeeded. max.models
forces save.model.fits = FALSE above 200 candidates, so
which path ran was not always the user’s choice.
The declared testthat floor is raised from 3.1.5 to
3.2.0, for local_mocked_bindings(), which arrived as
experimental in 3.1.7 and was declared stable in 3.2.0. It is a
Suggests, so nothing a user installs changes.
Package metadata prepared for a CRAN submission. The
Title drops “in R”, which CRAN reads as redundant.
Authors@R declares the Australian Institute of Marine
Science as copyright holder (cph); every file in
R/ states that copyright and the holder was previously
undeclared.
No Language field is declared, which was measured rather
than assumed. The documentation uses Australian spelling, but
DESCRIPTION names “Generalized Additive Models”, the
standard term. Measured with hunspell 3.0.5 on this tree:
en_GB flags six words in the Title and
Description against en_US’s four, adding
“Generalized” and “GAMs”, and 57 against 56 across man/.
CRAN reports the DESCRIPTION words, so declaring
en-GB would report more of them, not fewer.
The reference to Fisher et al. (2018) is written as
\doi{10.1002/ece3.4134} in all four places it appears,
rather than as a link to the publisher’s page. The publisher refuses
automated requests, so the URL check reported that link as possibly
invalid; a DOI is resolved instead.
README.md states what the package does, how to
install it, and a worked two-step call of
generate_model_set() and fit_model_set(). The
CRAN status badge is removed until the package is accepted, its link
having been a 404 that the URL check reported. Corrected “FFSgam” to
“FSSgam” and “calaculation” to “calculation” in
check_correlations()’s parallel
argument.
Completed the snake_case rename across the public API.
full.subsets.gam() is now a deprecated wrapper around the
new full_subsets_gam() (same treatment as
generate.model.set() and fit.model.set(),
since it’s used directly in the companion docs repository’s published
case studies). check.correlations(),
check.non.linear.correlations(),
build.inclusion.mat(), extract.mod.dat(), and
fit.mod.l() are renamed outright to
check_correlations(),
check_non_linear_correlations(),
build_inclusion_mat(), extract_mod_dat(), and
fit_mod_l() with no backward-compatible alias – a breaking
change for any code calling these five directly, but none of them appear
anywhere in the companion repo’s published vignettes/FAQ.
Bug fix: fit_model_set() (and
full.subsets.gam()/full_subsets_gam(), which
call it) failed to fit most candidate models when test.fit
used an mgcv extended family that estimates an extra parameter, such as
nb() (negative binomial). Each candidate refit reused the
already-fitted family object from test.fit, which carries
that estimated parameter in its own mutable environment; re-using it
warm-started every refit from an unrelated formula’s estimate and
destabilised the fit for most (but not all) candidates. Candidate models
now get a fresh, independent family object for every fit, the same way
the null model already did – and, unlike an earlier attempt at this same
fix, this now holds regardless of how family was originally
specified on test.fit (a literal call such as
family = nb(), or a variable/list element such as
family = my.families[[2]]), and no longer crashes every
candidate under parallel = TRUE when family was supplied
via a variable.
Bug fix: generate_model_set() (and
full_subsets_gam(), which calls it) did not validate the
null.terms argument, so passing anything other than a
single character string (e.g. NA, NULL, a
number, a logical, a character vector of length > 1, or a factor)
produced a confusing, type-dependent low-level error – or, for a bare
numeric/logical value, was silently accepted and spliced into a nonsense
model formula instead of failing. null.terms is now
validated up front with a clear error message
(beckyfisher/FSSgam#7).
Bug fix: generate_model_set() (and
full_subsets_gam(), which calls it) failed on model sets
with exactly one predictor. use.dat[, all.predictors]
dropped to a bare vector before being passed to
check_correlations() /
check_non_linear_correlations(), which report on the
columns of a data.frame, so the call failed with an unrelated “argument
is of length zero” (or, for non.linear.correlations = TRUE,
“invalid ‘row.names’ length”) error instead of building a 1x1
correlation matrix. check_non_linear_correlations() now
also returns the 1x1 unit matrix for a single-column input, matching
check_correlations().
Bug fix: generate_model_set() silently dropped the
interaction term from every linear.vars predictor after the
first. The pattern used to identify .t. interaction terms
was built as paste(linear.vars, ".t."), a vector whenever
more than one linear predictor was supplied, and grep()
used only its first element (with a warning). Affected candidates were
still named as interactions but fitted as the corresponding factor
main-effect model, so the model table reported duplicate fits under
distinct names.
Bug fix: supplying factor predictors with
pred.vars.cont = NA – the documented way to run without
smooth predictors – made generate_model_set() construct a
phantom NA.by.<factor> interaction term out of the
NA itself. With max.predictors = 1 the term
was discarded again before the model set was returned and the only
symptom was two spurious “no non-missing arguments to max; returning
-Inf” warnings, but from max.predictors = 2 it survived
into the candidate set as a model whose formula smoothed the literal
NA.
Bug fix: case_study1 was documented as having 28
variables; it has 27.
Bug fix: generate_model_set()’s @return
documented a generated.models element that it has never
returned, and omitted n.mods, mod.formula,
test.fit and included.vars, which it does.
fit_model_set()’s @return documented a
used.data element that it has never returned –
full_subsets_gam() is what carries that.
Bug fix: a candidate model named as an interaction between a
factor and a linear predictor was fitted as the factor main effect
alone, whenever the .t. term came from the list form of
factor.smooth.interactions naming a linear predictor absent
from linear.vars. The model table then reported two
identical fits under different names.
Substantially expanded the testthat suite, from 105
passing expectations at 1.0.0 to 568 at this release, and from 71.33% to
94.4% line coverage (FSSgam_package#5 covered the first 447 of those;
the rest accompany the fixes below). New coverage includes: every
non-default argument of generate_model_set(),
fit_model_set() and full_subsets_gam(); the
save.model.fits = FALSE fitting path; gamm4,
uGamm and gamm test.fits; the
cyclic.vars, linear.vars and
bs.arg formula construction; the bundled datasets; every
documented error and warning message; and numerical snapshot tests
pinning fitted values for five end-to-end scenarios.
Added a test-coverage GitHub Actions workflow
(.github/workflows/test-coverage.yaml, using
covr + Codecov) and a coverage badge to the README
(FSSgam_package#3).
Bug fix: fit_model_set() (and
full_subsets_gam(), which calls it) failed on a model set
with exactly one predictor, with
'x' must be an array of at least two dimensions. The
variable importance calculation indexed the model table by column name
without drop = FALSE, so a single predictor collapsed it to
a vector before colSums().
generate_model_set() was fixed for the single-predictor
case earlier in this development cycle; fit_model_set() was
not.
Bug fix: with VI.mods = 'min.n', a predictor present
in no successfully fitted model was given the weight of the single best
model containing any predictor, rather than none. The number of models
to sum over is zero in that case, and 1:0 counts
backwards.
Bug fix: with save.model.fits = FALSE and
parallel = TRUE, a single candidate that could not be
fitted aborted the whole run instead of being recorded in
failed.models. The foreach() on that path had
its .errorhandling disabled; the
save.model.fits = TRUE path was unaffected.
Bug fix: an unrecognised r2.type was accepted
silently and produced a column of NA r2 values.
fit_model_set() and full_subsets_gam() now
reject anything other than "r2.lm.est", "r2"
or "dev". extract_mod_dat() is unchanged,
since it documents r2.type. as passed through.
Bug fix: an unrecognised VI.mods failed with
object 'aic.var.weights' not found.
fit_model_set() and full_subsets_gam() now
reject anything other than "min.n" or
"all".
Bug fix: full_subsets_gam()’s deprecated
factor.interactions, smooth.interactions and
size arguments raised an error on values they are
documented to accept. A character vector of length greater than one gave
the condition has length > 1, and NA gave
missing value where TRUE/FALSE needed. All three are now
detected with missing(), which is correct for every type,
length and NA. As a result
smooth.interactions = NA now warns about the deprecation,
which it did not do reliably before.
Behaviour change: full_subsets_gam()’s
max.models default changes from 500 to 200, matching
fit_model_set(). The two disagreed and neither documented
its default, so a candidate set of between 201 and 500 models saved its
model fits through full_subsets_gam() but not through
generate_model_set() plus fit_model_set().
Anyone relying on the old wrapper default for a set in that range will
now receive the “model fits will not be saved” warning and an empty
success.models; pass max.models = 500
explicitly to keep the previous behaviour. The default is now stated in
the documentation of both functions.
full_subsets_gam() gains VI.mods, which
it previously did not forward to fit_model_set(), so
VI.mods = 'all' was unreachable through the wrapper
(FSSgam_package#7).
fit_model_set() and full_subsets_gam()
gain progress, defaulting to interactive().
The progress bar was previously written to stdout unconditionally, so
suppressing it in a script or report required wrapping the call in
capture.output() (FSSgam_package#9).
check_correlations() and
check_non_linear_correlations() no longer emit spurious
“NaNs produced” warnings when a factor-factor pair is perfectly
separated. The deviance is read directly off the
nnet::multinom() fit rather than from its
summary(), which computes standard errors that were
discarded (FSSgam_package#10). Reported correlation values are
unchanged.
check_correlations()’s factor-factor diagonal is now
exactly 1. It was previously a fitted multinomial pseudo-R2 of about
0.999999, because each factor was regressed on itself
(FSSgam_package#12). Off-diagonal values are unchanged.
Bug fix: max.predictors = 1 with
factor.factor.interactions built interaction columns it
should not have. The enumeration was written as
for (i in 2:n), and at n = 1 that sequence
counts backwards to c(2, 1), so a size-1 “combination” was
enumerated as well. Its pasted name is just the original variable name,
so used.data gained a duplicate column per factor, and four
“no non-missing arguments to max; returning -Inf” warnings were emitted.
No interaction terms are now generated at
max.predictors = 1, with a warning that says so; on a
two-factor set used.data loses three spurious
columns.
Bug fix: factor.factor.interactions given as a
character vector failed with
arguments imply differing number of rows when every named
combination exceeded cov.cutoff. It now warns and
continues, as the TRUE form always did. The warning text
for both forms now names cov.cutoff and its value,
replacing a message that referred to a non-existent
cor.cuttoff.
Behaviour change: the collinearity screen that decides which
interaction terms are built now tests both halves of the correlation
matrix in every case. Three of the four places that perform it tested
only the upper triangle, so a pair could be admitted whose correlation
exceeded cov.cutoff in the other direction. The matrices
are not symmetric: check_correlations() estimates a
factor-factor value by fitting multinom() separately in
each direction, and check_non_linear_correlations() is
asymmetric by construction. In practice this affects only a pair whose
two estimates straddle cov.cutoff, which for factor pairs
means a difference of under 0.001; a model set containing such a pair
will now exclude the interaction it previously included.
factor.factor.interactions given as a character
vector no longer skips silently when every named pair exceeds
cov.cutoff. An internal guard counted cells of the
correlation matrix rather than pairs, so no interaction was built and
nothing was reported; it now warns, as the TRUE form does.
The model set produced is unchanged.
Bug fix: a name in cyclic.vars was matched against
the assembled model terms with an unanchored grep(), so it
captured every predictor whose name contained it, and the name was used
as a regular expression. Declaring depth cyclic also fitted
depthx with bs = "cc", and a predictor name
containing a full stop matched any character in that position. The
affected models were fitted with the wrong smoothing basis, without
error, under a candidate name that looked correct. The basis is now
chosen from the variable names as each term is built.
Bug fix: a te() term over three or more predictors
was given only two bs values, because the code that
assigned them took the first two variables. mgcv warns “bs wrong length
and ignored” and substitutes its own default, so both
bs.arg and any cyclic.vars were silently
discarded for that term. Each marginal now carries its own basis.
Reachable through smooth.smooth.interactions given as a
character vector with max.predictors >= 3.
Behaviour change: candidate model names are now sorted in byte
order, via sort(method = "radix"), and no longer depend on
the session’s collation locale (FSSgam_package#8). A model named
complexity+ZONE in an en_US.UTF-8 session was
named ZONE+complexity in a C-locale one, so a saved
analysis was not reproducible across machines with different locales.
Names are now the C-locale form everywhere. The fitted results are
identical, but modname values and the row order of
mod.data.out change for anyone working in a non-C locale; a
saved model table matched on modname needs
regenerating.
Behaviour change: a user-supplied cor.matrix now
governs every stage that screens on correlation, not only the exclusion
of assembled candidate models (FSSgam_package#13). There are three such
stages: which factor-factor interaction columns are built, which
te() smooth-smooth interaction terms are built, and which
candidates survive. The first two previously recomputed correlations
from use.dat and ignored the supplied matrix, so a user who
supplied a matrix saying two predictors were uncorrelated still found
their te() term absent. Users who do not supply a
cor.matrix see no change: the continuous-continuous block
does not depend on which other predictors are present, verified against
both check_correlations() and
check_non_linear_correlations(). A supplied matrix must now
include any hard coded factor interaction it causes to be created, which
?generate_model_set already required; missing predictors
are reported by name.
Bug fix: a user-supplied cor.matrix did not replace
the automatic estimate, it only overrode it.
check_correlations() (or
check_non_linear_correlations()) was called over every
predictor and its result discarded whenever a matrix was supplied, so
supplying one neither avoided the multinom() and
gam() fits nor allowed a predictor of a class those
functions reject – a Date column, say – to be used at all.
The estimate is now computed only when no matrix is supplied
(FSSgam_package#13).
Bug fix: with save.model.fits = FALSE, a run in
which no candidate fitted returned a model table of NA with
delta.AICc and wi.AICc filled with
NaN, rather than the “None of your models fitted
successfully” error raised on the save.model.fits = TRUE
path. Which of the two happened depended only on a memory setting. Both
paths now raise the error.
fit_model_set() and full_subsets_gam()
reject a progress value that is not a single
TRUE or FALSE, and
full_subsets_gam() now validates VI.mods at
entry as it already did r2.type, so an unrecognised value
is reported before the candidate set is built rather than
after.
smooth.smooth.interactions naming a column of
use.dat that is not among the predictors is now reported as
such. It was previously screened against an empty sub-matrix of the
correlation matrix, so combine_uncorrelated() took
max() of nothing, warned “no non-missing arguments to max”,
and built the te() term regardless of its correlations. The
error message names the variable and the requirement (it previously said
the variable was not supplied in use.dat, which was not
what was being checked).
fit_mod_l() is no longer exported. It is an internal
helper documented as not called directly, and its arguments changed as
recently as the family resolution fix. It remains available as
FSSgam:::fit_mod_l(). wi(),
extract_mod_dat() and build_inclusion_mat()
stay exported, being more plausible to call directly.
Documentation corrections. Twenty-six spelling errors across the
reference pages, of which three affected meaning: the correlation values
are the square root of an R squared, not the “square-route”;
full_subsets_gam()’s description of what it sums referred
to “the ?i values” where a character had been lost from “the wi values”;
and check_non_linear_correlations()’s note told users to
increase cor.cutoff, which is not an argument –
cov.cutoff is. A non-breaking hyphen in the
case_study2 documentation is replaced with an ASCII
one.
The sixteen argument descriptions shared between
generate_model_set() and full_subsets_gam()
were duplicated verbatim and had already drifted; they are now
inherited, so they cannot drift again. full_subsets_gam()’s
null.terms entry gains the sentence about fitting a
correlation structure that only generate_model_set()
carried.
Modernisation release ahead of CRAN submission.
DESCRIPTION updated to use Authors@R,
explicit Imports (doSNOW,
foreach, gamm4, mgcv,
MuMIn, nnet, parallel,
stats, utils), and added the package
repository to URL.R.rsp vignette builder declaration;
this package does not ship vignettes (see the companion repository at https://github.com/beckyfisher/FSSgam for worked
examples).@examples to all exported
functions.data-raw/, build_package_FFSgam.R,
and CLAUDE.md to .Rbuildignore.generate_model_set() and fit_model_set().
generate.model.set() and fit.model.set()
remain exported but are now deprecated wrappers (see
R/deprecated.R) that warn via .Deprecated()
and forward to the new names; full.subsets.gam() calls the
new names internally so it does not itself emit deprecation
warnings.require()/bare calls throughout
R/ with explicit package::function() calls,
and class(x) == "y" checks with
inherits(x, "y"). Note: smooth-term constructors
(s(), te(), t2()) are kept
unqualified inside model formulas, because mgcv resolves them by literal
symbol name during formula parsing rather than through normal function
dispatch – mgcv::s(...) inside a formula does not
work.full.subsets.gam()’s deprecated
size argument (superseded by max.predictors)
was a no-op – it warned correctly but never actually fed its value into
max.predictors. Calling
full.subsets.gam(size = n) now actually constrains the
model set to n predictors, as documented.full.subsets.gam()’s used.data
and predictor.correlations output fields were always
NULL (a field-name mismatch referenced
model.set$use.dat/$cor.matrix, which don’t
exist – the actual fields are
$used.data/$predictor.correlations). Both are
now populated as documented.