Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile the objective #639

Merged
merged 103 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
7eb5f7a
Revising profiling of fixed-effects parameters.
dmbates Sep 13, 2022
223836b
Create a single Table from profileβ
dmbates Sep 14, 2022
c6dbfd2
Incorporate some of Phillip's suggestions.
dmbates Sep 15, 2022
d701e67
use a copy of remat.λ when profiling, change initial
dmbates Sep 15, 2022
432f352
shallow copy ReMats
palday Sep 16, 2022
a82277d
contravariance
palday Sep 16, 2022
d3d9c72
Add tests, names in MixedModelProfile, export
dmbates Sep 16, 2022
f6a970a
using BSplineKit, add confint for model and prof
dmbates Sep 19, 2022
799999e
Add tests for confint, be more careful of types
dmbates Sep 19, 2022
9bdac8e
Shuffle fields in profile object and adjust test
dmbates Sep 20, 2022
74c4706
Document functions and structs
dmbates Sep 20, 2022
7e19d32
Initial profilelogσ with fixed stepsz
dmbates Sep 21, 2022
fc082a6
Add profilelogσ and tests
dmbates Sep 21, 2022
39c3a3b
reformat
dmbates Sep 21, 2022
78b0f36
Suppress progress display on refits
dmbates Sep 21, 2022
48920d5
Avoid destructuring to allow for LTS test
dmbates Sep 21, 2022
c9406a2
use Compat.jl for destructuring syntax
palday Sep 22, 2022
49a39b8
Add BSplineKit and TypedTables to Project.toml
dmbates Oct 2, 2022
9c29952
New version of BSplineKit (w periodic splines)
dmbates Oct 4, 2022
beb7264
Merge branch 'main' into profile
dmbates Oct 11, 2022
030d5b7
Merge branch 'main' into profile
dmbates Oct 18, 2022
44fa4a7
Allow BSplineKit@0.11 for testing on LTS julia
dmbates Oct 18, 2022
428fa73
Use profile generic, return a table.
dmbates Oct 25, 2022
8d43e07
Back out a comment, fix tests.
dmbates Oct 25, 2022
927a805
Merge branch 'main' of github.com:JuliaStats/MixedModels.jl into profile
palday Oct 26, 2022
a156265
Be more careful with initial values in profileσ
dmbates Nov 2, 2022
1469cd9
Copy the vector, not just the reference to it
dmbates Nov 2, 2022
1027373
profile function with sigma, beta and theta
dmbates Nov 15, 2022
75cfed1
Fix confint method and comparison values in tests.
dmbates Nov 15, 2022
608d4a4
Add estimate to confint table, smaller δj, tests
dmbates Nov 16, 2022
3cc7b53
Store m and the vc's values. Use Tuples.
dmbates Nov 20, 2022
16dc0b3
Per discussion of #639
dmbates Nov 23, 2022
0c7c65b
Bump version of BSplineKit to allow extrapolation
dmbates Nov 24, 2022
4221eeb
Add and export objective! methods.
dmbates Nov 25, 2022
f81f44c
Split and update src/profile.jl
dmbates Jan 7, 2023
bb5dc6b
Minor correction in a test.
dmbates Jan 7, 2023
d7d314f
Merge branch 'main' into profile
dmbates Jan 7, 2023
f7ac63e
Remove unused definition.
dmbates Jan 27, 2023
7ab2697
Export Table, add .tbl property for bootstrap.
dmbates Jan 27, 2023
5f7d60c
Interim version of profiling, σs have problems.
dmbates Jan 27, 2023
8bf48e9
Fix σ profiles by moving lowerbd on θ
dmbates Jan 28, 2023
d192ba0
Merge current main
dmbates Jan 29, 2023
3f05c8a
Add documentation.
dmbates Jan 31, 2023
d063859
Require julia version 1.8 (b/c BSplineKit 0.14)
dmbates Feb 3, 2023
cd30dd4
Add confint for bootstrap, clean up for profile.
dmbates Feb 3, 2023
c1e78aa
Modify test to match current code
dmbates Feb 3, 2023
deae52c
Bump julia version for documenter to 1,8
dmbates Feb 3, 2023
a628555
Bump version of Aqua
dmbates Feb 7, 2023
f26a825
Check monotone splines. Comment out unused method
dmbates Feb 7, 2023
b9ffc30
Add more tests of profiling. Clean up method use.
dmbates Feb 7, 2023
29be05e
Merge branch 'main' of github.com:JuliaStats/MixedModels.jl into profile
palday Feb 8, 2023
1ac38be
Merge branch 'profile' of https://github.com/JuliaStats/MixedModels.j…
dmbates Feb 8, 2023
d0be208
'tbl' field of MixedModelProfile as a Table
dmbates Feb 10, 2023
3a7e616
JuliaFormatter
palday Feb 16, 2023
2bee46d
JuliaFormatter.format("src")
dmbates Feb 21, 2023
fd80166
Merge branch 'main' into profile
dmbates Feb 22, 2023
a9d7068
Clean up warning message.
dmbates Feb 22, 2023
f32cda2
Merge branch 'main' into profile
dmbates Feb 28, 2023
dc5de78
Return raneftables as a NamedTuple of DictTables
dmbates Feb 28, 2023
688833b
Update src/mixedmodel.jl
dmbates Feb 28, 2023
6aace76
Return a NamedTuple of Tables - easier to sort.
dmbates Mar 1, 2023
48e04d6
Merge branch 'main' into profile
dmbates Mar 10, 2023
17008ff
drop Compat
palday Mar 17, 2023
951adfc
Update src/profile/fixefpr.jl
dmbates Mar 20, 2023
15bd235
Update src/MixedModels.jl
dmbates Mar 20, 2023
8a4ab2c
Update src/linearmixedmodel.jl
dmbates Mar 20, 2023
9918f92
Update src/profile/fixefpr.jl
dmbates Mar 20, 2023
0df4893
Update src/profile/utilities.jl
dmbates Mar 20, 2023
06498ad
Replace Tuple type with stronger NTuple{N,Symbol}
dmbates Mar 20, 2023
1cfb445
Update src/profile/sigmapr.jl
dmbates Mar 20, 2023
fa241f4
add CI method comparison
palday Mar 21, 2023
bba580e
Merge branch 'profile' of github.com:JuliaStats/MixedModels.jl into p…
palday Mar 21, 2023
c16e674
Update src/profile/vcpr.jl
dmbates Mar 21, 2023
182d24f
Fix a syntax error
dmbates Mar 22, 2023
45fd0b7
Remove unnecessary qualifiers
dmbates Mar 22, 2023
1d664c5
Merge branch 'main' of github.com:JuliaStats/MixedModels.jl into profile
palday Apr 12, 2023
33b20b5
Merge branch 'main' into profile
dmbates Apr 12, 2023
a535e1f
Bump compat for BSplineKit
dmbates Apr 13, 2023
5ee3f72
JuliaFormatter change
dmbates Apr 13, 2023
2f54b68
Merge branch 'main' into profile
dmbates Apr 15, 2023
c89480c
Changes suggested in palday's review.
dmbates Apr 17, 2023
9f2ec89
Merge branch 'main' into profile
dmbates Apr 26, 2023
9d85ca4
Merge branch 'main' of github.com:JuliaStats/MixedModels.jl into profile
palday May 3, 2023
4f55a4f
Add news
palday May 3, 2023
afa4827
version bump
palday May 3, 2023
622c9ed
Add docstring and drop type parameter
dmbates May 4, 2023
69e6286
Document MixedModelProfile struct
dmbates May 4, 2023
a938c5a
Document a method as internal and potentially volatile
dmbates May 4, 2023
0c3904b
Document a method src/profile/sigmapr.jl as volatile
dmbates May 4, 2023
c65c61d
Document a method as internal and potentially volatile
dmbates May 4, 2023
16447b7
Document confint method
dmbates May 4, 2023
c064f74
Document confint method
dmbates May 4, 2023
a7926e2
Document an internal method
dmbates May 4, 2023
3a6baeb
Document confint method
dmbates May 4, 2023
c381204
Document profilevcl
dmbates May 4, 2023
40006af
Document the internal method profileσs!
dmbates May 4, 2023
1821e42
Refine target values in test/pls.jl
dmbates May 4, 2023
b35fdef
correct a typo of dot as separator
dmbates May 4, 2023
f031a03
Generalize a type comparison in test/pls.jl
dmbates May 4, 2023
48dee3e
Generalize a type comparison in test/pls.jl [ci skip]
dmbates May 4, 2023
664b033
Suppress progress updates in profile in tests [ci skip]
dmbates May 4, 2023
2c1be4c
Gene
dmbates May 4, 2023
f8f4559
fix indents
palday May 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ version = "4.7.1"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a"
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
GLM = "38e38edf-8417-5370-95a0-9cbb8c7f171a"
Expand All @@ -24,9 +26,11 @@ StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"

palday marked this conversation as resolved.
Show resolved Hide resolved
[compat]
Arrow = "1, 2"
Compat = "3.43, 4"
DataAPI = "1"
Distributions = "0.21, 0.22, 0.23, 0.24, 0.25"
GLM = "1.5.1"
Expand Down
9 changes: 9 additions & 0 deletions src/MixedModels.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module MixedModels

using Arrow
using BSplineKit
using Compat
using DataAPI
using Distributions
using GLM
Expand All @@ -19,6 +21,7 @@ using StatsBase
using StatsModels
using StructTypes
using Tables
using TypedTables

using LinearAlgebra: BlasFloat, BlasReal, HermOrSym, PosDefException, copytri!
using Base: Ryu, require_one_based_indexing
Expand Down Expand Up @@ -54,6 +57,8 @@ export @formula,
LogLink,
MixedModel,
MixedModelBootstrap,
MixedModelProfile,
Not,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why export Not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm "Not" sure why I did that. I'll revert it.

Normal,
OptSummary,
Poisson,
Expand All @@ -76,6 +81,7 @@ export @formula,
cond,
condVar,
condVartables,
confint,
deviance,
dispersion,
dispersion_parameter,
Expand Down Expand Up @@ -107,6 +113,8 @@ export @formula,
parametricbootstrap,
pirls!,
predict,
profileβ,
profilelogσ,
pwrss,
ranef,
raneftables,
Expand Down Expand Up @@ -182,6 +190,7 @@ include("blockdescription.jl")
include("grouping.jl")
include("mimeshow.jl")
include("serialization.jl")
include("profile.jl")

include("precompile_MixedModels.jl")
_precompile_()
Expand Down
16 changes: 13 additions & 3 deletions src/linearmixedmodel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Linear mixed-effects model representation
"""
struct LinearMixedModel{T<:AbstractFloat} <: MixedModel{T}
formula::FormulaTerm
reterms::Vector{AbstractReMat{T}}
reterms::Vector{<:AbstractReMat{T}}
Xymat::FeMat{T}
feterm::FeTerm{T}
sqrtwts::Vector{T}
Expand Down Expand Up @@ -350,12 +350,22 @@ function condVartables(m::MixedModel{T}) where {T}
return NamedTuple{fnames(m)}((map(_cvtbl, condVar(m), m.reterms)...,))
end

function StatsBase.confint(m::MixedModel{T}; level=0.95) where {T}
dmbates marked this conversation as resolved.
Show resolved Hide resolved
cutoff = sqrt.(quantile(Chisq(1), level))
β, std = m.β, m.stderror
return DictTable(;
coef=coefnames(m),
lower=β .- cutoff .* std,
upper=β .+ cutoff .* std
Comment on lines +375 to +379
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens in rank deficiency?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. Part of me just wants to tell users to ensure that their model matrices have full rank but that might be an unpopular suggestion. I'll have to look up what happens with the stderror property in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not adverse to adding a !!! warning entry to the docstring about rank deficiency and we can follow up on this when we have time / energy / necessity.

)
end

function _pushALblock!(A, L, blk)
push!(L, blk)
return push!(A, deepcopy(isa(blk, BlockedSparse) ? blk.cscmat : blk))
end

function createAL(reterms::Vector{AbstractReMat{T}}, Xy::FeMat{T}) where {T}
function createAL(reterms::Vector{<:AbstractReMat{T}}, Xy::FeMat{T}) where {T}
k = length(reterms)
vlen = kchoose2(k + 1)
A = sizehint!(AbstractMatrix{T}[], vlen)
Expand Down Expand Up @@ -749,7 +759,7 @@ end

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

function mkparmap(reterms::Vector{AbstractReMat{T}}) where {T}
function mkparmap(reterms::Vector{<:AbstractReMat{T}}) where {T}
parmap = NTuple{3,Int}[]
for (k, trm) in enumerate(reterms)
n = LinearAlgebra.checksquare(trm.λ)
Expand Down
160 changes: 160 additions & 0 deletions src/profile.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
struct FeProfile{T<:AbstractFloat} # derived model with the j'th fixed-effects coefficient held constant
m::LinearMixedModel{T} # copy of original model after removing the j'th column from X
y₀::Vector{T} # original response vector
xⱼ::Vector{T} # the column that was removed from X
j::Integer
end

"""
Base.copy(ReMat{T,S})

Return a shallow copy of ReMat.

A shallow copy shares as much internal storage as possible with the original ReMat.
Only the vector `λ` and the `scratch` matrix are copied.
"""
function Base.copy(ret::ReMat{T,S}) where {T,S}
return ReMat{T,S}(ret.trm,
ret.refs,
ret.levels,
ret.cnames,
ret.z,
ret.wtz,
copy(ret.λ),
ret.inds,
ret.adjA,
copy(ret.scratch))
end

function FeProfile(m::LinearMixedModel, j::Integer)
Xy = m.Xymat.xy
xcols = collect(axes(Xy, 2))
ycol = pop!(xcols)
notj = deleteat!(xcols, j) # indirectly check that j ∈ xcols
y₀ = Xy[:, ycol]
xⱼ = Xy[:, j]
feterm = FeTerm(Xy[:, notj], m.feterm.cnames[notj])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to use a view here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was copying those vectors and matrices out of an abundance of caution, because I didn't want to accidentally modify values in the original model structure.

Would it be okay to "stick a pin" in this and remind ourselves to follow up later on whether views can be used instead?

That reminds me that we may want to consider setting some of the fields in the larger structures as const (I think the change allowing that was in Julia v1.8.0 so we may need to put that inside a check on version numbers.)

reterms = [copy(ret) for ret in m.reterms]
m = fit!(LinearMixedModel(y₀ - xⱼ * m.β[j], feterm, reterms, m.formula); progress=false)
@. m.optsum.initial = max(m.optsum.initial, m.lowerbd + 0.05)
return FeProfile(m, y₀, xⱼ, j)
end

function refit!(pr::FeProfile{T}, βⱼ) where {T}
return refit!(pr.m, mul!(copyto!(pr.m.y, pr.y₀), pr.xⱼ, βⱼ, -1, 1); progress=false)
end

struct MixedModelProfile{T}
prtbl::Table # Table containing ζ, σ, β, and θ from each conditional fit
δ::AbstractVector{T} # values of fixed coefficient are `β[j] .+ δ .* stderror[j]`
fecnames::Vector{String} # Fixed-effects coefficient names
facnames::Vector{Symbol} # Names of grouping factors
recnames::Vector{Vector{String}} # Vector of vectors of column names for random effects
parmap::Vector{NTuple{3,Int}} # parmap from the model (used to construct λ from θ)
fwd::Vector # Interpolation splines for ζ as a function of β
rev::Vector # Interpolation splines for β as a function of ζ
end

"""
profileβ(m::LinearMixedModel, δ=(-8:8) / 2)

Return a `MixedModelProfile` for the objective of `m` with respect to the fixed-effects coefficients.

`δ` is a vector of standardized steps at which values of each coefficient are fixed.
When β[i] is being profiled the values are fixed at `m.β[i] .+ δ .* m.stderror[i]`.
"""
function profileβ(m::LinearMixedModel{T}, δ=(-8:8) / 2) where {T}
@compat (; β, θ, σ, stderror, objective) = m
betamat = (stderror * δ' .+ β)'
zsz = length(betamat)
zeta = sizehint!(T[], zsz)
sigma = sizehint!(T[], zsz)
beta = sizehint!(SVector{length(β),T}[], zsz)
theta = sizehint!(SVector{length(θ),T}[], zsz)
@inbounds for (j, c) in enumerate(eachcol(betamat))
prj = FeProfile(m, j)
prm = prj.m
j2jm1 = j:(j - 1)
for βj in c
dev = βj - β[j]
if dev ≈ 0
push!(zeta, zero(T))
push!(sigma, σ)
push!(beta, β)
push!(theta, θ)
else
refit!(prj, βj)
βcopy = prm.β
splice!(βcopy, j2jm1, βj)
push!(beta, βcopy)
push!(zeta, sign(dev) * sqrt(prm.objective - objective))
push!(sigma, prm.σ)
push!(theta, prm.θ)
end
end
end
updateL!(setθ!(m, θ))
zetamat = reshape(zeta, length(δ), :)
interp(x, y) = interpolate(x, y, BSplineOrder(4), Natural())
fwdspl = [interp(b, z) for (b, z) in zip(eachcol(betamat), eachcol(zetamat))]
revspl = [interp(z, b) for (b, z) in zip(eachcol(betamat), eachcol(zetamat))]
return MixedModelProfile(
Table(; ζ=zeta, σ=sigma, β=beta, θ=theta),
δ,
copy(coefnames(m)),
[fname(t) for t in m.reterms],
[t.cnames for t in m.reterms],
copy(m.parmap),
fwdspl,
revspl,
)
end

function StatsBase.confint(pr::MixedModelProfile; level=0.95)
cutoff = sqrt.(quantile(Chisq(1), level))
@compat (; fecnames, rev) = pr
lower = [s(-cutoff) for s in rev]
upper = [s(cutoff) for s in rev]
return DictTable(; coef=fecnames, lower=lower, upper=upper)
end

function refitlogσ!(m::LinearMixedModel{T}, stepsz, obj, logσ, zeta, beta, theta) where {T}
push!(logσ, last(logσ) + stepsz)
m.optsum.sigma = exp(last(logσ))
refit!(m; progress=false)
push!(zeta, sign(stepsz[]) * sqrt(m.objective - obj))
push!(beta, m.β)
push!(theta, m.θ)
return m.objective
end

function _logσstepsz(m::LinearMixedModel, σ, objective)
i64 = inv(64)
m.optsum.sigma = exp(log(σ) + i64)
return i64 / sqrt(refit!(m; progress=false).objective - objective)
end

function profilelogσ(m::LinearMixedModel{T}) where {T}
isnothing(m.optsum.sigma) ||
throw(ArgumentError("Can't profile σ, which is fixed at $(m.optsum.sigma)"))
@compat (; β, σ, θ, objective) = m
logσ = [log(σ)]
beta = [SVector{length(β)}(β)]
theta = [SVector{length(θ)}(θ)]
zeta = [zero(T)]
stepsz = -_logσstepsz(m, σ, objective)
while abs(last(zeta)) < 4
refitlogσ!(m, stepsz, objective, logσ, zeta, beta, theta)
end
reverse!(logσ)
reverse!(zeta)
reverse!(beta)
reverse!(theta)
stepsz = -stepsz
while abs(last(zeta)) < 4
refitlogσ!(m, stepsz, objective, logσ, zeta, beta, theta)
end
m.optsum.sigma = nothing
refit!(m)
return Table(; logσ=logσ, ζ=zeta, β=beta, θ=theta)
end
2 changes: 1 addition & 1 deletion src/remat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ end

Combine multiple ReMat with the same grouping variable into a single object.
"""
amalgamate(reterms::Vector{AbstractReMat{T}}) where {T} = _amalgamate(reterms, T)
amalgamate(reterms::Vector{<:AbstractReMat{T}}) where {T} = _amalgamate(reterms, T)

function _amalgamate(reterms::Vector, T::Type)
factordict = Dict{Symbol,Vector{Int}}()
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"

[compat]
StableRNGs = "0.1, 1"
29 changes: 29 additions & 0 deletions test/pls.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ using Statistics
using StatsModels
using Tables
using Test
using TypedTables

using MixedModels: likelihoodratiotest

Expand Down Expand Up @@ -501,6 +502,34 @@ end
@test bic(fm) ≈ bic(m)
@test coef(fm) ≈ coef(m)
end
@testset "profileβ" begin
pr = profileβ(last(models(:sleepstudy)), -4:4)
tbl = pr.prtbl
@test length(tbl) == 18
@test length(pr.fecnames) == 2
@test isone(length(pr.facnames))
recnms = pr.recnames
@test isone(length(recnms))
@test length(only(recnms)) == 2
@test pr.fecnames == only(recnms)
ci = confint(pr)
@test isa(ci, TypedTables.DictTable)
dmbates marked this conversation as resolved.
Show resolved Hide resolved
@test propertynames(ci) == (:coef, :lower, :upper)
@test isapprox(ci.lower.values, [237.68380717307167, 7.359357960468046]; atol=1.e-3)
end
@testset "profilelogσ" begin
m = last(models(:sleepstudy))
pr = profilelogσ(m)
@test isa(pr, TypedTables.Table)
@test propertynames(pr) == (:logσ, :ζ, :β, :θ)
@test length(pr) == 10
@test only(filter(r -> iszero(r.ζ), pr)).logσ == log(m.σ)
end
@testset "confint" begin
ci = confint(last(models(:sleepstudy)))
@test isa(ci, TypedTables.DictTable)
dmbates marked this conversation as resolved.
Show resolved Hide resolved
@test isapprox(ci.lower.values, [238.4061184564825, 7.52295850741417]; atol=1.e-3)
end
end

@testset "d3" begin
Expand Down