Code
using BSplineKit
using CairoMakie
using Distributions
using MixedModels # need MixedModels#profile at present
using TypedTables
include(pkgdir(MixedModels, "test", "modelcache.jl"))
activate!(; type="svg") CairoMakie.
Attach the packages to be used, set up the graphics output and access to models.
using BSplineKit
using CairoMakie
using Distributions
using MixedModels # need MixedModels#profile at present
using TypedTables
include(pkgdir(MixedModels, "test", "modelcache.jl"))
activate!(; type="svg") CairoMakie.
The models
function from test/modelcache.jl
defines several models that are using in testing of MixedModels
. The models are fit the first time they are accessed, then the models are cached so that subsequent accesses are fast.
The profile
function applied to a LinearMixedModel
profiles the parameters, returning a MixedModelProfile
object with fields m
, containing the original model, tbl
, a Table
of results from all the profiles and fwd
and rev
, which are Dict
s of interpolation splines of the profile \(\zeta\) function, both the forward (parameter \(\rightarrow\zeta\)) and reverse (\(\zeta\rightarrow\) parameter) directions.
= profile(models(:sleepstudy)[3])
slpr03 # the original model slpr03.m
Est. | SE | z | p | σ_subj | |
---|---|---|---|---|---|
(Intercept) | 251.4051 | 6.7077 | 37.48 | <1e-99 | 24.1714 |
days | 10.4673 | 1.5193 | 6.89 | <1e-11 | 5.7994 |
Residual | 25.5561 |
Table(slpr03.tbl) # table of profiled parameters and traces
Table with 9 columns and 103 rows:
p ζ β1 β2 σ σ1 σ2 θ1 ⋯
┌────────────────────────────────────────────────────────────────────────
1 │ σ -4.35251 251.405 10.4673 20.1989 25.46 5.96166 1.26047 ⋯
2 │ σ -3.76931 251.405 10.4673 20.8016 25.3193 5.9425 1.21718 ⋯
3 │ σ -3.198 251.405 10.4673 21.4224 25.1728 5.9229 1.17507 ⋯
4 │ σ -2.63822 251.405 10.4673 22.0617 25.0205 5.9029 1.13411 ⋯
5 │ σ -2.08961 251.405 10.4673 22.7201 24.8623 5.88257 1.09429 ⋯
6 │ σ -1.55181 251.405 10.4673 23.3982 24.6983 5.86197 1.05556 ⋯
7 │ σ -1.0245 251.405 10.4673 24.0964 24.5284 5.84119 1.01792 ⋯
8 │ σ -0.507334 251.405 10.4673 24.8156 24.3527 5.82029 0.981349 ⋯
9 │ σ 0.0 251.405 10.4673 25.5561 24.1714 5.79938 0.945818 ⋯
10 │ σ 0.497807 251.405 10.4673 26.3188 23.9841 5.77862 0.911292 ⋯
11 │ σ 0.986399 251.405 10.4673 27.1042 23.7912 5.75804 0.877766 ⋯
12 │ σ 1.46606 251.405 10.4673 27.9131 23.593 5.73775 0.845229 ⋯
13 │ σ 1.93709 251.405 10.4673 28.7461 23.3883 5.71801 0.813617 ⋯
14 │ σ 2.39975 251.405 10.4673 29.604 23.1794 5.69865 0.782984 ⋯
15 │ σ 2.8543 251.405 10.4673 30.4875 22.9643 5.68006 0.753239 ⋯
16 │ σ 3.30102 251.405 10.4673 31.3973 22.7438 5.66221 0.724387 ⋯
17 │ σ 3.74014 251.405 10.4673 32.3343 22.5172 5.64529 0.696387 ⋯
18 │ σ 4.17191 251.405 10.4673 33.2993 22.2847 5.62936 0.669225 ⋯
19 │ θ1 -4.21139 251.405 10.4673 28.4644 5.71506 6.77512 0.20078 ⋯
20 │ θ1 -3.78047 251.405 10.4673 28.0777 8.00161 6.57229 0.284981 ⋯
21 │ θ1 -3.30923 251.405 10.4673 27.6792 10.0846 6.38552 0.364338 ⋯
22 │ θ1 -2.81265 251.405 10.4673 27.2861 12.0919 6.22427 0.443152 ⋯
23 │ θ1 -2.29792 251.405 10.4673 26.907 14.107 6.09106 0.524289 ⋯
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋱
confint(slpr03)
DictTable with 3 columns and 5 rows:
coef estimate lower upper
─────┬─────────────────────────────
β1 │ 251.405 237.572 265.238
β2 │ 10.4673 7.33407 13.6005
θ1 │ 0.945818 0.578677 1.50272
θ2 │ 0.226927 0.150938 0.348503
σ │ 25.5561 22.8806 28.7876
The forward interpolation splines allow transformation from the original parameter scale to the \(\zeta\) scale. The reverse interpolation splines help to determine the range of interest for a parameter. For example, the confint
method for MixedModelProfile
is based solely on the rev
dictionary.
Because the models are fit by optimizing the profiled log-likelihood, which is a function of \(\boldsymbol{\theta}\) only, we first consider the \(\boldsymbol{\theta}\) parameters.
The fwdspline
function supplements a forward spline with information on the slope and intercept of the linear approximation to the spline at the estimated parameter value.
function fwdspline(pr::MixedModelProfile, par::Symbol)
= pr.fwd[par]
spl = spl.x
xv = spl.(xv)
yv = findmin(abs, yv)
minv = xv[last(minv)]
est = (Derivative(1) * spl)(est)
slope return (; spl, xv, yv, slope, intercept = first(minv) - slope * est)
end
fwdspline (generic function with 1 method)
Plotting the spline over the range and at the points at which the profile was evaluated is made easier by a Makie convention of plotting a function over an interval, which is generated by the ..
operator. The result of the expression identity ∘ spl
, where ∘
is the function composition operator, is recognized as a function for the purposes of dispatch. (In other words, forgetting the identity ∘
part, as I frequently do, fails in dispatch with an obscure error message.)
let
= Figure(; resolution=(700,600))
f = Axis(f[1,1]; xlabel = "θ₁", ylabel="ζ")
ax = fwdspline(slpr03, :θ1)
(; spl, xv, yv, slope, intercept) lines!(ax, first(xv) .. last(xv), identity ∘ spl;)
scatter!(ax, xv, yv)
ablines!(ax, intercept, slope)
fend
let
= Figure(; resolution=(700,600))
f = Axis(f[1,1]; xlabel = "θ₂", ylabel="ζ")
ax = fwdspline(slpr03, :θ2)
(; spl, xv, yv, slope, intercept) lines!(ax, first(xv) .. last(xv), identity ∘ spl;)
scatter!(ax, xv, yv)
ablines!(ax, intercept, slope)
fend
We see that in both cases the profile \(\zeta\) function is concave down over the region of interest. The ideal situation is for the profile \(\zeta\) to be close to a straight line suggesting a transformation like the square root.
let
= Figure(; resolution=(700,600))
f = Axis(f[1,1]; xlabel = "√θ₁", ylabel="ζ")
ax = fwdspline(slpr03, :θ1)
(; spl, xv, yv) lines!(ax, sqrt(first(xv)) .. sqrt(last(xv)), spl ∘ abs2;)
scatter!(ax, sqrt.(xv), yv)
fend
let
= Figure(; resolution=(700,600))
f = Axis(f[1,1]; xlabel = "√θ₂", ylabel="ζ")
ax = fwdspline(slpr03, :θ2)
(; spl, xv, yv) lines!(ax, sqrt(first(xv) + sqrt(eps())) .. sqrt(last(xv)), spl ∘ abs2;)
scatter!(ax, sqrt.(xv), yv)
fend
Although it is tempting to use a logarithmic transformation of the \(\theta\) parameter instead of the square root, we must allow the \(\theta\) parameters to take on the value of zero, which is not possible when using a logarithmic transformation.
If the forward splines are monotone increasing over the observed range, as they are here, they can be used as a transformation from \(\theta_1\) and \(\theta_2\) to the \(\zeta\) scale.
As shown in Figure 5 the \(\boldsymbol{\theta}\) parameters are nearly orthogonal over the range of interest, allowing for rapid and stable convergence to the optimum.
function extractzeta(pr::MixedModelProfile)
= pr
(; tbl, fwd) = Table(tbl)
tbl rounddownabs(x) = (abs(x) < 1.0e-5 ? 0. : x)
= sort!(collect(keys(fwd)))
pnms = NamedTuple{(pnms...,)}(ntuple(length(pnms)) do i
ptbl = pnms[i]
k rounddownabs.(fwd[k].(getproperty(tbl, k)))
end
)
Table(merge((; p=tbl.p), ptbl))
end
extractzeta (generic function with 1 method)
= extractzeta(slpr03) slpr03zeta
Table with 6 columns and 103 rows:
p β1 β2 θ1 θ2 σ
┌───────────────────────────────────────────────
1 │ σ 0.0 0.0 1.22659 1.22659 -4.35251
2 │ σ 0.0 0.0 1.07836 1.07836 -3.76931
3 │ σ 0.0 0.0 0.928444 0.928448 -3.198
4 │ σ 0.0 0.0 0.776959 0.776952 -2.63822
5 │ σ 0.0 0.0 0.623988 0.623977 -2.08961
6 │ σ 0.0 0.0 0.469644 0.469643 -1.55181
7 │ σ 0.0 0.0 0.314059 0.314104 -1.0245
8 │ σ 0.0 0.0 0.157441 0.157502 -0.507334
9 │ σ 0.0 0.0 0.0 0.0 0.0
10 │ σ 0.0 0.0 -0.158167 -0.158142 0.497807
11 │ σ 0.0 0.0 -0.316826 -0.316783 0.986399
12 │ σ 0.0 0.0 -0.47569 -0.475698 1.46606
13 │ σ 0.0 0.0 -0.634716 -0.63453 1.93709
14 │ σ 0.0 0.0 -0.793285 -0.793273 2.39975
15 │ σ 0.0 0.0 -0.951465 -0.951434 2.8543
16 │ σ 0.0 0.0 -1.10884 -1.10887 3.30102
17 │ σ 0.0 0.0 -1.26527 -1.26527 3.74014
18 │ σ 0.0 0.0 -1.42044 -1.42042 4.17191
19 │ θ1 0.0 0.0 -4.21139 0.227502 1.78026
20 │ θ1 0.0 0.0 -3.78047 0.14801 1.56093
21 │ θ1 0.0 0.0 -3.30923 0.0787207 1.32972
22 │ θ1 0.0 0.0 -2.81265 0.0248807 1.09626
23 │ θ1 0.0 0.0 -2.29792 -0.0116634 0.865901
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
let
= Figure(; resolution=(700, 700))
f = Axis(f[1,1]; aspect = 1, xlabel="ζ(θ₁)", ylabel="ζ(θ₂)")
ax = filter(r -> r.p == :θ1, slpr03zeta)
tbl scatterlines!(ax, tbl.θ1, tbl.θ2)
= filter(r -> r.p == :θ2, slpr03zeta)
tbl scatterlines!(ax, tbl.θ1, tbl.θ2)
fend
let
= Figure(; resolution=(700, 700))
f = Axis(f[1,1]; aspect=1.0, xlabel="ζ(β₁)", ylabel="ζ(β₂)")
ax = filter(r -> r.p == :β1, slpr03zeta)
tbl scatterlines!(ax, tbl.β1, tbl.β2)
= filter(r -> r.p == :β2, slpr03zeta)
tbl scatterlines!(ax, tbl.β1, tbl.β2)
fend
Because the \(\zeta\) scale is directly related to the objective, a contour of the objective on the \(\zeta\) scale, such as in Figure 5 and Figure 6, is bounded by a square centered at the origin. Furthermore, the intersection of the profile traces with the boundary of the square are exactly the points where the contour touches the square.
To determine these points, we create a natural, cubic interpolation spline for the profile traces and use it to determine the intersection points.
Suppose, for example, that we wish to interpolate contours of the objective as a function of \(\boldsymbol{\theta}=\left(\theta_1, \theta_2\right)\) at levels corresponding to 1, 2, and 3 on the \(\zeta\) scale.
The profile trace of \(\zeta(\theta_2)\) on \(\zeta(\theta_1)\) (blue line and points in Figure 5) intersects \(\zeta(\theta_1)\) at
"""
_signedavgdelta(x, y)
Return a NamedTuple of `x` and `y` plus `m = norm((x,y), Inf)`, and `a` and `d`, the signed average and half-difference of `acos(x/m) / π` and `acos(y/m) / π`
The transformation in the other direction is `x = m * cospi(a + d)`, `y = m * cospi(a - d)`.
Because the cosine is an even function, if the signs of both `a` and `d` are flipped the resulting `x` and `y` are unchanged.
This relationship is used to ensure that `d` is non-negative.
"""
function _signedavgdelta(x, y)
= max(abs(x), abs(y)) # Infinity norm of (x, y)
m = acos.((x, y) ./ m) ./ π
acpix, acpiy = (acpix - acpiy) / 2
d = sign(d) * (acpix + acpiy) / 2
a return (; x, y, m, a, d = abs(d))
end;
function pspline(adtbl::Table, lev)
= BSplineOrder(4), Periodic(2)
ord, per = filter(r -> r.m == lev, adtbl)
tbl = interpolate(tbl.a, tbl.d, ord, per))
(; lev, tbl, perspl end
pspline (generic function with 1 method)
function adpspline(zetatbl::Table, xsym::Symbol, ysym::Symbol, levs::AbstractVector)
= BSplineOrder(4), Natural()
ord, nat = filter(r -> r.p == xsym, zetatbl)
tbl = interpolate(getproperty(tbl, xsym), getproperty(tbl, ysym), ord, nat)
spl1 = filter(r -> r.p == ysym, zetatbl)
tbl = interpolate(getproperty(tbl, ysym), getproperty(tbl, xsym) , ord, nat)
spl2 = sort!(
tbl Table(_signedavgdelta.(vcat(levs, spl2.(levs)), vcat(spl1.(levs), levs)));
= getproperty(:a)
by
)return [pspline(tbl, l) for l in sort!(unique(tbl.m))]
end
adpspline (generic function with 1 method)
= adpspline(slpr03zeta, :θ1, :θ2, filter(!iszero, -4:4)); adspl
Periodic interpolation splines, provided by BSplineKit.jl, are used to provide d
as a function of a
. Note that the period is 2 because we are using the cospi
function for transformation back to the original scale, not cos
.
let
= Figure()
f = Axis(f[1,1]; xlabel="a", ylabel="d")
ax = -1.0:inv(64):1.0
avals for (; tbl, perspl) in adspl
scatter!(ax, tbl.a, tbl.d)
lines!(avals, perspl.(avals))
end
fend
These values are transformed to the scale of \(\zeta_1\) and \(\zeta_2\) as
let
= Figure(; resolution=(700,700))
f = Axis(f[1,1]; aspect=1.0, xlabel="ζ₁", ylabel="ζ₂")
ax = -1.0:inv(64):1.0
avals for (; lev, tbl, perspl) in adspl
scatter!(ax, tbl.x, tbl.y)
= perspl.(avals)
dvals lines!(
.* cospi.(avals .+ dvals),
lev .* cospi.(avals .- dvals),
lev
)end
fend
and on the original scale of \(\theta_1\) and \(\theta_2\) as
let
= Figure(; resolution=(700,700))
f = Axis(f[1,1]; aspect=1.0, xlabel="θ₁", ylabel="θ₂")
ax = -1.0:inv(64):1.0
avals = slpr03.rev
rev for (; lev, tbl, perspl) in adspl
scatter!(ax, rev[:θ1].(tbl.x), rev[:θ2].(tbl.y))
= perspl.(avals)
dvals lines!(
:θ1].(lev .* cospi.(avals .+ dvals)),
rev[:θ2].(lev .* cospi.(avals .- dvals)),
rev[
)end
fend
let
= Figure(; resolution=(700,700))
f = Axis(f[1,1]; aspect=1.0, xlabel="β₁", ylabel="β₂")
ax = -1.0:inv(64):1.0
avals = getindex.(Ref(slpr03.rev), (:β1, :β2))
r1, r2 = sqrt.(quantile.(Chisq(2), [0.5, 0.8, 0.9, 0.95, 0.99]))
levs for (; lev, tbl, perspl) in adpspline(slpr03zeta, :β1, :β2, vcat(-levs, levs))
scatter!(ax, r1.(tbl.x), r2.(tbl.y))
= perspl.(avals)
dvals lines!(
r1.(lev .* cospi.(avals .+ dvals)),
r2.(lev .* cospi.(avals .- dvals)),
)end
fend
When a model is close to singularity, the forward spline for one of the \(\theta\) values can flatten out as that \(\theta\) approaches zero from the right.
For example, the second model for the pastes data incorporates random effects for batch
and for cask
within batch
, but the batch
random effect is not significant.
= profile(first(models(:pastes)))
pspr01 pspr01.m
Est. | SE | z | p | σ_batch & cask | |
---|---|---|---|---|---|
(Intercept) | 60.0533 | 0.5765 | 104.16 | <1e-99 | 3.1037 |
Residual | 0.8234 |
= profile(last(models(:pastes)))
pspr02 pspr02.m
Est. | SE | z | p | σ_batch & cask | σ_batch | |
---|---|---|---|---|---|---|
(Intercept) | 60.0533 | 0.6421 | 93.52 | <1e-99 | 2.9041 | 1.0951 |
Residual | 0.8234 |
likelihoodratiotest(pspr01.m, pspr02.m) MixedModels.
model-dof | deviance | χ² | χ²-dof | P(>χ²) | |
---|---|---|---|---|---|
strength ~ 1 + (1 | batch & cask) | 3 | 248 | |||
strength ~ 1 + (1 | batch) + (1 | batch & cask) | 4 | 248 | 0 | 1 | 0.5234 |
The forward profile spline for \(\theta_2\) in the second model is well-defined
let
= Figure(; resolution=(700,600))
f = Axis(f[1,1]; xlabel = "θ₁", ylabel="ζ")
ax = fwdspline(pspr02, :θ2)
(; spl, xv, yv, slope, intercept) lines!(ax, first(xv) .. last(xv), identity ∘ spl;)
scatter!(ax, xv, yv)
ablines!(ax, intercept, slope)
fend
and the slope at the boundary is nearly zero
Derivative(1) * pspr02.fwd[:θ2])(0.0) (
0.0041995888771644025
resulting in an ill-defined reverse interpolation spline
= Table(filter(x -> x.p == :θ2, pspr02.tbl)) tbl
Table with 8 columns and 15 rows:
p ζ β1 σ σ1 σ2 θ1 θ2
┌──────────────────────────────────────────────────────────────────────────
1 │ θ2 -0.638149 60.0533 0.823408 3.10368 0.0 3.76931 0.0
2 │ θ2 -0.638043 60.0533 0.823409 3.10363 0.0136914 3.76925 0.0166276
3 │ θ2 -0.442487 60.0533 0.825761 3.0276 0.591832 3.66644 0.716711
4 │ θ2 -0.0268488 60.0533 0.823817 2.90981 1.06987 3.5321 1.29867
5 │ θ2 0.0 60.0533 0.823409 2.90407 1.09507 3.52689 1.32992
6 │ θ2 0.530837 60.0533 0.810786 2.82691 1.54908 3.48663 1.91059
7 │ θ2 1.02237 60.0533 0.795307 2.79849 1.95449 3.51875 2.45752
8 │ θ2 1.47204 60.0533 0.780864 2.79346 2.35343 3.5774 3.01388
9 │ θ2 1.90642 60.0533 0.767823 2.79997 2.78912 3.64663 3.63251
10 │ θ2 2.33148 60.0533 0.756418 2.81268 3.28633 3.71842 4.34459
11 │ θ2 2.74957 60.0533 0.746698 2.82835 3.86955 3.78781 5.18221
12 │ θ2 3.16174 60.0533 0.738617 2.84477 4.56757 3.85148 6.18394
13 │ θ2 3.56855 60.0533 0.732059 2.86043 5.41661 3.90738 7.39914
14 │ θ2 3.97036 60.0533 0.72687 2.87438 6.46382 3.95446 8.89268
15 │ θ2 4.36742 60.0533 0.72287 2.88612 7.77173 3.99258 10.7512
let
= pspr02.rev[:θ2]
r = lines(first(r.x) .. last(r.x), identity ∘ r)
f, ax, g scatter!(ax, tbl.ζ, tbl.θ2)
fend
A couple of approaches that could help to alleviate this problem is to add more profile evaluations between 0 and the estimate when creating the profile. It may also be the case that the evaluation close to the estimate, which is used to determine an initial step size, may be causing instability in the derivative evaluation.
= profile(only(models(:penicillin)))
plpr01 plpr01.tbl
94-element Vector{NamedTuple{(:p, :ζ, :β1, :σ, :σ1, :σ2, :θ1, :θ2), Tuple{Symbol, Float64, Float64, Float64, Float64, Float64, Float64, Float64}}}:
(p = :σ, ζ = -4.409436764461413, β1 = 22.97222222222335, σ = 0.42186662684447723, σ1 = 0.8577590355512449, σ2 = 1.77237459062339, θ1 = 2.0332469576159697, θ2 = 4.201267599384634)
(p = :σ, ζ = -3.8122667004468247, β1 = 22.972222222215574, σ = 0.43608083984877916, σ1 = 0.8565660638912731, σ2 = 1.7721624758578842, θ1 = 1.9642368699076682, θ2 = 4.063839347934712)
(p = :σ, ζ = -3.2291223728137366, β1 = 22.972222222226875, σ = 0.45077398111731215, σ1 = 0.8552976198944537, σ2 = 1.7719864746007423, θ1 = 1.8973979327166755, θ2 = 3.9309865893515044)
(p = :σ, ζ = -2.6595410938429853, β1 = 22.972222222227444, σ = 0.4659621874760975, σ1 = 0.853933939906699, σ2 = 1.7717817024898954, θ1 = 1.8326249701334472, θ2 = 3.8024151961488992)
(p = :σ, ζ = -2.1030761545422414, β1 = 22.972222222225916, σ = 0.4816621394592095, σ1 = 0.8524857639202464, σ2 = 1.7715987463817133, θ1 = 1.7698832731121081, θ2 = 3.6780942516486594)
(p = :σ, ζ = -1.5592962931478127, β1 = 22.97222222222242, σ = 0.4978910796282666, σ1 = 0.8509296935665445, σ2 = 1.7713831955495627, θ1 = 1.7090679636234136, θ2 = 3.5577725089433327)
(p = :σ, ζ = -1.027785171196582, β1 = 22.972222222221575, σ = 0.5146668315091734, σ1 = 0.8492644637926592, σ2 = 1.7711503294812851, θ1 = 1.6501247249649544, θ2 = 3.441353164896379)
(p = :σ, ζ = -0.5081408734634761, β1 = 22.972222222225255, σ = 0.5320078191669088, σ1 = 0.847481381243915, σ2 = 1.7709084778434445, θ1 = 1.5929867019079125, θ2 = 3.3287264097294234)
(p = :σ, ζ = 0.0, β1 = 22.97222222222414, σ = 0.5499330874398607, σ1 = 0.8455646003012482, σ2 = 1.7706476279724732, θ1 = 1.5375772427835905, θ2 = 3.21975103592236)
(p = :σ, ζ = 0.497085679207144, β1 = 22.972222222224566, σ = 0.568462322855928, σ1 = 0.8435266850567367, σ2 = 1.7703676419470067, θ1 = 1.4838743943818444, θ2 = 3.114309551867506)
(p = :σ, ζ = 0.9834039592503204, β1 = 22.972222222225025, σ = 0.5876158752533619, σ1 = 0.841337233952612, σ2 = 1.770066878565751, θ1 = 1.4317809803723447, θ2 = 3.0122856667249716)
(p = :σ, ζ = 1.4593283388855995, β1 = 22.97222222222312, σ = 0.6074147801300914, σ1 = 0.8389886183327319, σ2 = 1.7697493951233336, θ1 = 1.3812449841161978, θ2 = 2.913576443998123)
(p = :σ, ζ = 1.925195613372533, β1 = 22.97222222222166, σ = 0.6278807817460791, σ1 = 0.8364736543647948, σ2 = 1.769404271127693, θ1 = 1.3322173232291614, θ2 = 2.8180576991178823)
⋮
(p = :β1, ζ = 2.6122187252401927, β1 = 25.578308134675282, σ = 0.5499136133694412, σ1 = 0.8479076128874624, σ2 = 3.111765609025609, θ1 = 1.5418923850459831, θ2 = 5.658644436821884)
(p = :β1, ζ = 2.823314293901043, β1 = 25.950606122168303, σ = 0.5499143170415058, σ1 = 0.8478050746545447, σ2 = 3.4266980058370633, θ1 = 1.541703949836525, θ2 = 6.231330772169052)
(p = :β1, ζ = 3.008796015543271, β1 = 26.322904109661323, σ = 0.5499137469122637, σ1 = 0.8476856650806067, σ2 = 3.753038890477586, θ1 = 1.5414884058460376, θ2 = 6.824777361087441)
(p = :β1, ζ = 3.1731836953678325, β1 = 26.695202097154343, σ = 0.5499154531957359, σ1 = 0.8475836791495158, σ2 = 4.087191947236251, θ1 = 1.5412981654251285, θ2 = 7.432400605373538)
(p = :β1, ζ = 3.3200904635453474, β1 = 27.067500084647364, σ = 0.5499173421259741, σ1 = 0.8474879071704999, σ2 = 4.427678059467045, θ1 = 1.541118714121874, θ2 = 8.051533785695305)
(p = :β1, ζ = 3.45239121336855, β1 = 27.439798072140384, σ = 0.5499174639161658, σ1 = 0.8474005910023276, σ2 = 4.77339411595523, θ1 = 1.540959592313498, θ2 = 8.680200992276411)
(p = :β1, ζ = 3.5723792199782087, β1 = 27.812096059633404, σ = 0.5499183120777058, σ1 = 0.8473220316246163, σ2 = 5.122910642863003, θ1 = 1.540814359178652, θ2 = 9.315766597238015)
(p = :β1, ζ = 3.6818950555228147, β1 = 28.184394047126425, σ = 0.5499188018750272, σ1 = 0.8472575693572051, σ2 = 5.475498417107875, θ1 = 1.540695765390015, θ2 = 9.956921637227854)
(p = :β1, ζ = 3.7824276364636193, β1 = 28.556692034619445, σ = 0.5499191383421091, σ1 = 0.8472015678517534, σ2 = 5.830632914876754, θ1 = 1.540592986826915, θ2 = 10.602709577366028)
(p = :β1, ζ = 3.8751917849431017, β1 = 28.928990022112465, σ = 0.5499198776533112, σ1 = 0.8471482753889462, σ2 = 6.187868460127933, θ1 = 1.5404940061523258, θ2 = 11.252309130074732)
(p = :β1, ζ = 3.9611873534355353, β1 = 29.301288009605486, σ = 0.5499198553797101, σ1 = 0.8471077755814899, σ2 = 6.546820649595604, θ1 = 1.540420421802331, θ2 = 11.905045045291443)
(p = :β1, ζ = 4.041244307435995, β1 = 29.673585997098506, σ = 0.5499202318025559, σ1 = 0.8470700506028241, σ2 = 6.907194360336087, θ1 = 1.5403507665580074, θ2 = 12.560356867932175)
let
= Figure(; resolution=(700,600))
fig = Axis(fig[1,1]; xlabel="θ₁", ylabel="ζ")
ax = fwdspline(plpr01, :θ1)
(; spl, xv, yv, slope, intercept) lines!(ax, first(xv) .. last(xv), identity ∘ spl)
scatterlines!(ax, xv, yv)
ablines!(ax, intercept, slope)
figend
let
= Figure(; resolution=(700,600))
fig = Axis(fig[1,1]; xlabel="θ₂", ylabel="ζ")
ax = fwdspline(plpr01, :θ2)
(; spl, xv, yv, slope, intercept) lines!(ax, first(xv) .. last(xv), identity ∘ spl)
scatterlines!(ax, xv, yv)
ablines!(ax, intercept, slope)
figend