Skip to content

Commit 89cbe76

Browse files
committed
make nesting checks more robust and more informative
1 parent b78e319 commit 89cbe76

File tree

3 files changed

+45
-10
lines changed

3 files changed

+45
-10
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
MixedModels v5.1.0 Release Notes
2+
==============================
3+
- Nesting checks for the likelihoodratio test have been slightly tweaked to be more robust, at the cost of being slightly slower. In particular, the comparison of models with pre-centered variables with those with variables centered via StandardizedPredictors.jl was previously incorrectly rejected as non-nested, but should be correctly accepted as nested now. Additionally, some further logging messages are emitted when a nesting check fails.
4+
15
MixedModels v5.0.4 Release Notes
26
==============================
37
- Small update in some code related to displaying dispersion parameters in cases where inference has failed. [#865]

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MixedModels"
22
uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316"
33
author = ["Phillip Alday <[email protected]>", "Douglas Bates <[email protected]>"]
4-
version = "5.0.4"
4+
version = "5.1.0"
55

66
[deps]
77
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"

src/likelihoodratiotest.jl

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,40 @@ function StatsModels.isnested(m1::MixedModel, m2::MixedModel; atol::Real=0.0)
221221
end || return false
222222

223223
# check that the nested fixef are a subset of the outer
224-
all(in.(coefnames(m1), Ref(coefnames(m2)))) || return false
224+
# XXX: the simple coefficient name check is nice and fast, but fails
225+
# if model uses a pre-computed centered value and another uses
226+
# StandardizedPredictors (an example of which we have in EmbraceUncertainty,
227+
# where we use StandardizedPredictors in the simpler model, but swap
228+
# to pre-computation for a more complicated model with a quadratic term)
229+
# all(in.(coefnames(m1), Ref(coefnames(m2)))) || return false
230+
if !_isnested(modelmatrix(m1), modelmatrix(m2))
231+
@error "Fixed effects are not nested."
232+
return false
233+
end
225234

226235
# check that the same grouping vars occur in the outer model
227236
grpng1 = fname.(m1.reterms)
228237
grpng2 = fname.(m2.reterms)
229238

230-
all(in.(grpng1, Ref(grpng2))) || return false
231-
239+
if !isempty(setdiff(grpng1, grpng2))
240+
@error "Inner models have grouping variables not present in outer models."
241+
return false
242+
end
232243
# check that every intercept/slope for a grouping var occurs in the
233244
# same grouping
234-
re1 = Dict(fname(re) => re.cnames for re in m1.reterms)
235-
re2 = Dict(fname(re) => re.cnames for re in m2.reterms)
236-
237-
all(all(in.(val, Ref(re2[key]))) for (key, val) in re1) || return false
245+
# note that re.z is actually the transpose of what we want
246+
re1 = Dict(fname(re) => re.z' for re in m1.reterms)
247+
re2 = Dict(fname(re) => re.z' for re in m2.reterms)
248+
249+
# note that this will fail if amalgamation was not performed,
250+
# whether through explicit disabling or by a grouping variable renaming
251+
for key in keys(re1)
252+
# we only need to grab the first column because
253+
if !_isnested(re1[key], re2[key])
254+
@error "Random effects are not nested."
255+
return false
256+
end
257+
end
238258

239259
return true
240260
end
@@ -244,7 +264,12 @@ function StatsModels.isnested(
244264
m2::MixedModel;
245265
atol::Real=0.0,
246266
)
247-
return _iscomparable(m1, m2) && isnested(m1.model, m2; atol)
267+
try
268+
return _iscomparable(m1, m2) && isnested(m1.model, m2; atol)
269+
catch e
270+
@error e.msg
271+
return false
272+
end
248273
end
249274

250275
function StatsModels.isnested(m1::LinearModel, m2::LinearMixedModel; atol::Real=0.0)
@@ -335,7 +360,13 @@ function _iscomparable(
335360
m1::TableRegressionModel{<:Union{LinearModel,GeneralizedLinearModel}}, m2::MixedModel
336361
)
337362
# check that the nested fixef are a subset of the outer
338-
all(in.(coefnames(m1), Ref(coefnames(m2)))) || return false
363+
# XXX: the simple coefficient name check is nice and fast, but fails
364+
# if model uses a pre-computed centered value and another uses
365+
# StandardizedPredictors (an example of which we have in EmbraceUncertainty,
366+
# where we use StandardizedPredictors in the simpler model, but swap
367+
# to pre-computation for a more complicated model with a quadratic term)
368+
# all(in.(coefnames(m1), Ref(coefnames(m2)))) || return false
369+
_isnested(modelmatrix(m1), modelmatrix(m2)) || return false
339370

340371
return true
341372
end

0 commit comments

Comments
 (0)