Skip to content

Commit f76d4a5

Browse files
authored
make nesting checks more robust and more informative (#867)
* make nesting checks more robust and more informative * NEWS * JuliaFormatter
1 parent b78e319 commit f76d4a5

File tree

3 files changed

+45
-10
lines changed

3 files changed

+45
-10
lines changed

NEWS.md

Lines changed: 5 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. [#867]
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]
@@ -705,3 +709,4 @@ Package dependencies
705709
[#861]: https://github.com/JuliaStats/MixedModels.jl/issues/861
706710
[#864]: https://github.com/JuliaStats/MixedModels.jl/issues/864
707711
[#865]: https://github.com/JuliaStats/MixedModels.jl/issues/865
712+
[#867]: https://github.com/JuliaStats/MixedModels.jl/issues/867

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 <me@phillipalday.com>", "Douglas Bates <dmbates@gmail.com>"]
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: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,39 @@ 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+
if !_isnested(re1[key], re2[key])
253+
@error "Random effects are not nested."
254+
return false
255+
end
256+
end
238257

239258
return true
240259
end
@@ -244,7 +263,12 @@ function StatsModels.isnested(
244263
m2::MixedModel;
245264
atol::Real=0.0,
246265
)
247-
return _iscomparable(m1, m2) && isnested(m1.model, m2; atol)
266+
try
267+
return _iscomparable(m1, m2) && isnested(m1.model, m2; atol)
268+
catch e
269+
@error e.msg
270+
return false
271+
end
248272
end
249273

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

340370
return true
341371
end

0 commit comments

Comments
 (0)