Skip to content

Commit 7bfeaaa

Browse files
authored
LikelihoodRatioTest struct and pretty-printing via coeftable (#265)
* LikelihoodRatioTest struct and pretty-printing via coeftable * add missing file * tests * catch REML in LR test
1 parent cdf1046 commit 7bfeaaa

File tree

4 files changed

+130
-22
lines changed

4 files changed

+130
-22
lines changed

src/MixedModels.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ include("randomeffectsterm.jl")
125125
include("linearmixedmodel.jl")
126126
include("gausshermite.jl")
127127
include("generalizedlinearmixedmodel.jl")
128+
include("likelihoodratiotest.jl")
128129
include("linalg/statschol.jl")
129130
include("linalg/cholUnblocked.jl")
130131
include("linalg/rankUpdate.jl")

src/likelihoodratiotest.jl

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
LikelihoodRatioTest
3+
4+
Results of MixedModels.likelihoodratiotest
5+
6+
## Fields
7+
* `formulas`: Vector of model formulae
8+
* `models`: NamedTuple of the `dof` and `deviance` of the models
9+
* `tests`: NamedTuple of the sequential `dofdiff`, `deviancediff`, and resulting `pvalues`
10+
11+
## Properties
12+
* `deviance`
13+
* `pvalues`
14+
15+
"""
16+
struct LikelihoodRatioTest
17+
formulas::AbstractVector{String}
18+
models::NamedTuple{(:dof,:deviance)}
19+
tests::NamedTuple{(:dofdiff,:deviancediff,:pvalues)}
20+
end
21+
22+
Base.propertynames(lrt::LikelihoodRatioTest, private = false) = (
23+
:deviance,
24+
:formulas,
25+
:models,
26+
:pvalues,
27+
:tests,
28+
)
29+
30+
function Base.getproperty(lrt::LikelihoodRatioTest, s::Symbol)
31+
if s == :dof
32+
lrt.models.dof
33+
elseif s == :deviance
34+
lrt.models.deviance
35+
elseif s == :pvalues
36+
lrt.tests.pvalues
37+
elseif s == :formulae
38+
lrt.formulas
39+
else
40+
getfield(lrt, s)
41+
end
42+
end
43+
44+
# backward syntactic but not type compatibility
45+
Base.getindex(lrt::LikelihoodRatioTest, s::Symbol) = getfield(lrt,s)
46+
47+
"""
48+
likelihoodratiotest(m::LinearMixedModel...)
49+
50+
Likeihood ratio test applied to a set of nested models.
51+
52+
Note that nesting of the models is not checked. It is incumbent on the user to check this.
53+
"""
54+
function likelihoodratiotest(m::LinearMixedModel...)
55+
if any(getproperty.(getproperty.(m,:optsum),:REML))
56+
reduce(==,coefnames.(m)) ||
57+
throw(ArgumentError("Likelihood-ratio tests for REML-fitted models are only valid when the fixed-effects specifications are identical"))
58+
end
59+
m = collect(m) # change the tuple to an array
60+
dofs = dof.(m)
61+
formulas = String.(Symbol.(getproperty.(m,:formula)))
62+
ord = sortperm(dofs)
63+
dofs = dofs[ord]
64+
formulas = formulas[ord]
65+
devs = objective.(m)[ord]
66+
dofdiffs = diff(dofs)
67+
devdiffs = .-(diff(devs))
68+
pvals = ccdf.(Chisq.(dofdiffs), devdiffs)
69+
70+
LikelihoodRatioTest(
71+
formulas,
72+
(dof = dofs, deviance = devs),
73+
(dofdiff = dofdiffs, deviancediff = devdiffs, pvalues = pvals)
74+
)
75+
end
76+
77+
function _array_union_nothing(arr::Array{T}) where T
78+
Array{Union{T,Nothing}}(arr)
79+
end
80+
81+
function _prepend_0(arr::Array{T}) where T
82+
pushfirst!(copy(arr), -zero(T))
83+
end
84+
85+
function Base.show(io::IO, lrt::LikelihoodRatioTest; digits=2)
86+
println(io, "Model Formulae")
87+
88+
for (i, f) in enumerate(lrt.formulas)
89+
println("$i: $f")
90+
end
91+
cols = hcat(lrt.models.dof, lrt.models.deviance,
92+
_prepend_0(lrt.tests.deviancediff),
93+
_prepend_0(lrt.tests.dofdiff),
94+
_prepend_0(lrt.tests.pvalues))
95+
96+
ct = CoefTable(
97+
cols, # cols
98+
["model-dof", "deviance", "χ²", "χ²-dof", "P(>χ²)"], # colnms
99+
string.(1:length(lrt.formulas)), # rownms
100+
5, # pvalcol
101+
3 # teststatcol
102+
)
103+
show(io, ct)
104+
105+
nothing
106+
end

src/linearmixedmodel.jl

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -487,28 +487,6 @@ end
487487

488488
lowerbd(m::LinearMixedModel) = m.optsum.lowerbd
489489

490-
"""
491-
likelihoodratiotest(m::LinearMixedModel...)
492-
493-
Likeihood ratio test applied to a set of nested models.
494-
495-
Note that nesting of the models is not checked. It is incumbent on the user to check this.
496-
"""
497-
function likelihoodratiotest(m::LinearMixedModel...)
498-
m = collect(m) # change the tuple to an array
499-
dofs = dof.(m)
500-
ord = sortperm(dofs)
501-
dofs = dofs[ord]
502-
devs = deviance.(m)[ord]
503-
dofdiffs = diff(dofs)
504-
devdiffs = .-(diff(devs))
505-
pvals = ccdf.(Chisq.(dofdiffs), devdiffs)
506-
(
507-
models = (dof = dofs, deviance = devs),
508-
tests = (dofdiff = dofdiffs, deviancediff = devdiffs, p_values = pvals),
509-
)
510-
end
511-
512490
function StatsBase.modelmatrix(m::LinearMixedModel)
513491
fetrm = first(m.feterms)
514492
if fetrm.rank == size(fetrm, 2)

test/likelihoodratiotest.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using MixedModels, Test
2+
3+
@testset "likelihoodratio test" begin
4+
slp = MixedModels.dataset(:sleepstudy);
5+
fm0 = fit(MixedModel,@formula(reaction ~ 1 + (1+days|subj)),slp);
6+
fm1 = fit(MixedModel,@formula(reaction ~ 1 + days + (1+days|subj)),slp);
7+
lrt = MixedModels.likelihoodratiotest(fm0,fm1);
8+
9+
@test [deviance(fm0), deviance(fm1)] == lrt.deviance
10+
@test deviance(fm0) - deviance(fm1) == first(lrt.tests.deviancediff)
11+
@test first(lrt.tests.dofdiff) == 1
12+
@test sum(map(length,lrt.tests)) == 3
13+
@test sum(map(length,lrt.pvalues)) == 1
14+
@test sum(map(length,lrt.models)) == 4
15+
@test length(lrt.formulae) == 2
16+
17+
fm0 = fit(MixedModel,@formula(reaction ~ 1 + (1+days|subj)),slp, REML=true);
18+
fm1 = fit(MixedModel,@formula(reaction ~ 1 + days + (1+days|subj)),slp, REML=true);
19+
fm10 = fit(MixedModel,@formula(reaction ~ 1 + days + (1|subj)),slp, REML=true);
20+
21+
@test_throws ArgumentError MixedModels.likelihoodratiotest(fm0,fm1);
22+
lrt = MixedModels.likelihoodratiotest(fm1,fm10);
23+
end

0 commit comments

Comments
 (0)