Skip to content

Commit ad28c8e

Browse files
committed
icc bootstrap without the piracy
1 parent a8bf904 commit ad28c8e

4 files changed

Lines changed: 42 additions & 41 deletions

File tree

‎src/MixedModelsExtras.jl‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export termnames, gvif, vif
1717
StatsModels.termnames(::RandomEffectsTerm) = String[]
1818

1919
include("icc.jl")
20-
export icc, confint
20+
export icc, confint, IccBootstrap
2121

2222
include("r2.jl")
2323
export r², r2, adjr², adjr2

‎src/icc.jl‎

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
const SymbolCollection = Union{Symbol,Tuple{Symbol,Vararg{Symbol}},AbstractVector{Symbol}}
22

3+
"""
4+
IccBootstrap{T} <: AbstractVector{T}
5+
6+
Thin wrapper around the per-iteration ICC values computed by [`icc`](@ref) from a
7+
`MixedModelBootstrap`. Behaves like the underlying vector for all `AbstractArray`
8+
purposes (indexing, iteration, `sort`, `quantile`, etc.), but is a distinct type so
9+
that [`confint`](@ref) can be specialized for it without pirating `MixedModelBootstrap`.
10+
"""
11+
struct IccBootstrap{T} <: AbstractVector{T}
12+
values::Vector{T}
13+
end
14+
15+
Base.size(x::IccBootstrap) = size(x.values)
16+
Base.getindex(x::IccBootstrap, i::Int) = getindex(x.values, i)
17+
Base.setindex!(x::IccBootstrap, v, i::Int) = setindex!(x.values, v, i)
18+
Base.IndexStyle(::Type{<:IccBootstrap}) = IndexLinear()
19+
Base.similar(x::IccBootstrap, ::Type{S}, dims::Dims) where {S} = similar(x.values, S, dims)
20+
321
function _group_var(vc::VarCorr, group::Symbol)
422
return sum(abs2, vc.σρ[group].σ)
523
end
@@ -105,13 +123,13 @@ function icc(boot::MixedModelBootstrap,
105123
groups::Union{Symbol,SymbolCollection})
106124
all(ismissing, boot.σ) &&
107125
throw(ArgumentError("Bootstrapping GLMM requires specifying the family."))
108-
return _icc(boot.σs, groups, abs2.(boot.σ))
126+
return IccBootstrap(_icc(boot.σs, groups, abs2.(boot.σ)))
109127
end
110128

111129
function icc(boot::MixedModelBootstrap, family,
112130
groups::Union{Symbol,SymbolCollection})
113131
σ²res = _residual_variance(family)
114-
return _icc(boot.σs, groups, σ²res)
132+
return IccBootstrap(_icc(boot.σs, groups, σ²res))
115133
end
116134

117135
function _icc(tbl, groups::Union{Symbol,SymbolCollection}, σ²res)
@@ -121,40 +139,21 @@ function _icc(tbl, groups::Union{Symbol,SymbolCollection}, σ²res)
121139
return σ²_α ./ σ²
122140
end
123141

124-
function _icc_confint(values, level, method)
125-
method in (:shortest, :equaltail) ||
126-
throw(ArgumentError("`method` must be either :shortest or :equaltail."))
127-
method === :shortest && return shortestcovint(values, level)
128-
tails = ((1 - level) / 2, (1 + level) / 2)
129-
return Tuple(quantile(values, tails))
130-
end
131-
132142
"""
133-
confint(boot::MixedModelBootstrap, groups; level::Real=0.95, method=:shortest)
134-
confint(boot::MixedModelBootstrap, family, [groups]; level::Real=0.95, method=:shortest)
135-
136-
Compute a bootstrap confidence interval for the [`icc`](@ref) computed from `boot`.
143+
confint(icc_boot::IccBootstrap; level::Real=0.95, method=:shortest)
137144
138-
The `groups` and `family` arguments are as for [`icc`](@ref).
145+
Compute a bootstrap confidence interval for an [`IccBootstrap`](@ref), i.e. the
146+
result of calling [`icc`](@ref) on a `MixedModelBootstrap`.
139147
140148
The keyword argument `level` is the confidence level (0.95 by default). The keyword
141149
argument `method` determines whether the `:shortest`, i.e. highest density, interval
142150
is used (the default) or the `:equaltail`, i.e. quantile-based, interval is used --
143151
matching the behavior of `confint(::MixedModelBootstrap)` from MixedModels.jl.
144152
"""
145-
function StatsBase.confint(boot::MixedModelBootstrap,
146-
groups::Union{Symbol,SymbolCollection};
147-
level::Real=0.95, method=:shortest)
148-
return _icc_confint(icc(boot, groups), level, method)
149-
end
150-
151-
function StatsBase.confint(boot::MixedModelBootstrap, family;
152-
level::Real=0.95, method=:shortest)
153-
return _icc_confint(icc(boot, family), level, method)
154-
end
155-
156-
function StatsBase.confint(boot::MixedModelBootstrap, family,
157-
groups::Union{Symbol,SymbolCollection};
158-
level::Real=0.95, method=:shortest)
159-
return _icc_confint(icc(boot, family, groups), level, method)
153+
function StatsBase.confint(icc_boot::IccBootstrap; level::Real=0.95, method=:shortest)
154+
method in (:shortest, :equaltail) ||
155+
throw(ArgumentError("`method` must be either :shortest or :equaltail."))
156+
method === :shortest && return shortestcovint(icc_boot, level)
157+
tails = ((1 - level) / 2, (1 + level) / 2)
158+
return Tuple(quantile(icc_boot, tails))
160159
end

‎test/icc.jl‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@
3434
@test iccboot_subj ≈ σ²_subj_ref ./ (abs2.(boot.σ) .+ σ²_all_ref)
3535

3636
@testset "confint" begin
37-
@test confint(boot, :subj) == shortestcovint(iccboot_subj)
38-
@test confint(boot, :subj) == shortestcovint(iccboot_subj, 0.95)
39-
@test confint(boot, :subj; level=0.8) == shortestcovint(iccboot_subj, 0.8)
40-
@test confint(boot, [:subj, :item]) == shortestcovint(icc(boot))
37+
@test iccboot_subj isa MixedModelsExtras.IccBootstrap
38+
@test iccboot_subj isa AbstractVector{Float64}
4139

42-
lo, hi = confint(boot, :subj; method=:equaltail)
40+
@test confint(iccboot_subj) == shortestcovint(iccboot_subj)
41+
@test confint(iccboot_subj) == shortestcovint(iccboot_subj, 0.95)
42+
@test confint(iccboot_subj; level=0.8) == shortestcovint(iccboot_subj, 0.8)
43+
@test confint(icc(boot, [:subj, :item])) == shortestcovint(icc(boot))
44+
45+
lo, hi = confint(iccboot_subj; method=:equaltail)
4346
@test lo ≈ quantile(iccboot_subj, 0.025)
4447
@test hi ≈ quantile(iccboot_subj, 0.975)
4548

46-
@test_throws ArgumentError confint(boot, :subj; method=:bogus)
49+
@test_throws ArgumentError confint(iccboot_subj; method=:bogus)
4750

4851
# base MixedModels.jl `confint(::MixedModelBootstrap)` (no groups) is untouched
4952
@test Tables.istable(confint(boot))
@@ -79,8 +82,8 @@ end
7982
ci = shortestcovint(iccboot)
8083
@test first(ci) < icc(modelbern) < last(ci)
8184
@test iccboot ≈ icc(boot, Bernoulli(), Symbol("urban & dist"))
82-
@test confint(boot, Bernoulli()) == ci
83-
@test confint(boot, Bernoulli(), Symbol("urban & dist")) == ci
85+
@test confint(iccboot) == ci
86+
@test confint(icc(boot, Bernoulli(), Symbol("urban & dist"))) == ci
8487
end
8588
end
8689

‎test/runtests.jl‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ include("set_up_tests.jl")
55
Aqua.test_all(MixedModelsExtras; ambiguities=false,
66
piracies=(;
77
treat_as_own=[LinearMixedModel, MixedModel,
8-
GeneralizedLinearMixedModel, RandomEffectsTerm,
9-
MixedModelBootstrap]))
8+
GeneralizedLinearMixedModel, RandomEffectsTerm]))
109
end
1110

1211
@testset "ICC" begin

0 commit comments

Comments
 (0)