Skip to content

Commit 0463930

Browse files
Santymax98lrnv
andauthored
[Fitting] Add confidence interval estimates through vcov (#311)
Co-authored-by: Oskar Laverny <[email protected]>
1 parent 9f35da2 commit 0463930

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1211
-741
lines changed

Copulas.jl

Lines changed: 0 additions & 1 deletion
This file was deleted.

Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
99
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
1010
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
1111
HCubature = "19dc6840-f33b-545b-b366-655c7e3ffd49"
12-
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
1312
LambertW = "984bce1d-4616-540c-a9ee-88d1112d94c9"
1413
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1514
LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688"

docs/src/manual/fitting_interface.md

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ plot(Ĉ)
3939
### Full Model (with metadata)
4040

4141
```@example fitting_interface
42-
M = fit(CopulaModel, GumbelCopula, U; method=:default, summaries=true)
42+
M = fit(CopulaModel, GumbelCopula, U; method=:default)
4343
M
4444
```
4545

@@ -48,15 +48,15 @@ Returns a `CopulaModel` with:
4848
* `result` (the fitted copula), `n`, `ll` (log-likelihood),
4949
* `method`, `converged`, `iterations`, `elapsed_sec`,
5050
* `vcov` (if available),
51-
* `method_details` (a named tuple with method metadata and, if `summaries=true`, **pairwise summaries**: means, deviations, minima, and maxima of empirical τ/ρ/β/γ).
51+
* `method_details` (a named tuple with method metadata).
5252

5353
---
5454

5555
## Behavior & conventions (important)
5656

5757
- fit operates on types, not on pre-constructed parameterized instances. Always pass a Copula or SklarDist *type* to `fit`, e.g. `fit(GumbelCopula, U)` or `fit(CopulaModel, SklarDist{ClaytonCopula,Tuple{Normal,LogNormal}}, X)`. If you already have a constructed instance `C0`, re-estimate its parameters by calling `fit(typeof(C0), U)`.
5858

59-
- Default method selection: each family exposes the list of available fitting strategies via `_available_fitting_methods(CT)`. When `method = :default` the first element of that tuple is used. Example: `Copulas._available_fitting_methods(MyCopula)`.
59+
- Default method selection: each family exposes the list of available fitting strategies via `_available_fitting_methods(CT, d)`. When `method = :default` the first element of that tuple is used. Example: `Copulas._available_fitting_methods(MyCopula, d)`.
6060

6161
- `CopulaModel` is the full result object returned by the fits performed via `Distributions.fit(::Type{CopulaModel}, ...)`. The light-weight shortcut `fit(MyCopula, U)` returns only a copula instance; use `fit(CopulaModel, ...)` to get diagnostics and metadata.
6262

@@ -80,26 +80,7 @@ The `CopulaModel{CT} <: StatsBase.StatisticalModel` type stores the result and s
8080
| `hqc(M)` | Hannan–Quinn criterion |
8181

8282

83-
Quick access to the contained copula: `_copula_of(M)` (returns the copula even if `result` is a `SklarDist`).
84-
85-
86-
### Pairwise summaries and `method_details`
87-
88-
When you request `summaries=true` (default) the returned `CopulaModel` contains extra pre-computed pairwise statistics inside `M.method_details`. Typical keys are:
89-
90-
- `:tau_mean`, `:tau_sd`, `:tau_min`, `:tau_max`
91-
- `:rho_mean`, `:rho_sd`, `:rho_min`, `:rho_max`
92-
- `:beta_mean`, `:beta_sd`, `:beta_min`, `:beta_max`
93-
- `:gamma_mean`, `:gamma_sd`, `:gamma_min`, `:gamma_max`
94-
95-
Access example:
96-
97-
```@example fitting_interface
98-
M = fit(CopulaModel, GumbelCopula, U; summaries=true)
99-
M.method_details.tau_mean # average pairwise Kendall's tau
100-
```
101-
102-
If `summaries=false` these keys will be absent and `method_details` will be smaller.
83+
By default, the returned `CopulaModel` contains a lot of extra statistics, that you can see by printing the model in the REPL.
10384

10485
### `vcov` and inference notes
10586

@@ -143,7 +124,7 @@ plot(Ŝ.result)
143124
The names and availiability of fitting methods depends on the model. You can check what is available with the following internal call :
144125

145126
```@example fitting_interface
146-
Copulas._available_fitting_methods(ClaytonCopula)
127+
Copulas._available_fitting_methods(ClaytonCopula, 3)
147128
```
148129

149130
The first method in the list is the one used by default.
@@ -168,7 +149,7 @@ When you add a new copula family, implement the following so the generic `fit` f
168149
1. `_example(CT, d)` — return a representative instance (used to obtain default params and initial values).
169150
2. `_unbound_params(CT, d, params)` — transform the family `NamedTuple` parameters to an unconstrained `Vector{Float64}` used by optimizers.
170151
3. `_rebound_params(CT, d, α)` — invert `_unbound_params`, returning a `NamedTuple` suitable for `CT(d, ...)` construction.
171-
4. `_available_fitting_methods(::Type{<:YourCopula})` — declare supported methods (examples: `:mle, :itau, :irho, :ibeta, ...`).
152+
4. `_available_fitting_methods(::Type{<:YourCopula}, d::Int)` — declare supported methods (examples: `:mle, :itau, :irho, :ibeta, ...`).
172153
5. `_fit(::Type{<:YourCopula}, U, ::Val{:mle})` (and other `Val{}` methods) — implement the method and return `(fitted_copula, meta::NamedTuple)`; include keys such as `:θ̂`, `:optimizer`, `:converged`, `:iterations` and optionally `:vcov`.
173154

174155
Place this checklist and a minimal `_fit` skeleton in `docs/src/manual/developer_fitting.md` where contributors can copy/paste and adapt.

src/ArchimaxCopula.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ function _rebound_params(CT::Type{<:ArchimaxCopula{2, <:Generator, <:Tail}}, d,
155155
NamedTuple{all_names}(all_vals)
156156
end
157157

158-
_available_fitting_methods(::Type{<:ArchimaxCopula}) = (:mle,)
158+
_available_fitting_methods(::Type{<:ArchimaxCopula}, d) = (:mle,)
159159

160160
# Fast conditional distortion binding (bivariate)
161161
DistortionFromCop(C::ArchimaxCopula{2}, js::NTuple{1,Int}, uⱼₛ::NTuple{1,Float64}, ::Int) = BivArchimaxDistortion(C.gen, C.tail, Int8(js[1]), float(uⱼₛ[1]))
@@ -177,7 +177,7 @@ end
177177

178178
# --- log-PDF stable ---
179179
function Distributions._logpdf(C::ArchimaxCopula{2, TG, TT}, u) where {TG, TT}
180-
T = promote_type(Float64, eltype(u))
180+
T = typeof(A(C.tail, one(ϕ(C.gen, one(eltype(u))))/2))
181181
@assert length(u) == 2
182182
u1, u2 = u
183183
(0.0 < u1 1.0 && 0.0 < u2 1.0) || return T(-Inf)

src/ArchimedeanCopula.jl

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ _example(::Type{<:ArchimedeanCopula{d,<:FrailtyGenerator} where {d}}, d) = throw
191191
_unbound_params(CT::Type{<:ArchimedeanCopula}, d, θ) = _unbound_params(generatorof(CT), d, θ)
192192
_rebound_params(CT::Type{<:ArchimedeanCopula}, d, α) = _rebound_params(generatorof(CT), d, α)
193193

194-
_available_fitting_methods(::Type{ArchimedeanCopula}) = (:gnz2011,)
195-
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,GT} where {d,GT<:Generator}}) = (:mle,)
196-
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,GT} where {d,GT<:UnivariateGenerator}}) = (:mle, :itau, :irho, :ibeta)
197-
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,<:WilliamsonGenerator{d2, TX}} where {d,d2, TX}}) = Tuple{}() # No fitting method.
198-
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,<:WilliamsonGenerator{d2, <:Distributions.DiscreteNonParametric}} where {d,d2}}) = (:gnz2011,)
194+
_available_fitting_methods(::Type{ArchimedeanCopula}, d) = (:gnz2011,)
195+
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,GT} where {d,GT<:Generator}}, d) = (:mle,)
196+
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,GT} where {d,GT<:UnivariateGenerator}}, d) = (:mle, :itau, :irho, :ibeta)
197+
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,<:WilliamsonGenerator{d2, TX}} where {d,d2, TX}}, d) = Tuple{}() # No fitting method.
198+
_available_fitting_methods(::Type{<:ArchimedeanCopula{d,<:WilliamsonGenerator{d2, <:Distributions.DiscreteNonParametric}} where {d,d2}}, d) = (:gnz2011,)
199199

200200

201201
function _fit(::Union{Type{ArchimedeanCopula},Type{<:ArchimedeanCopula{d,<:WilliamsonGenerator{d2, <:Distributions.DiscreteNonParametric}} where {d,d2}}}, U, ::Val{:gnz2011})
@@ -215,7 +215,7 @@ function _fit(CT::Type{<:ArchimedeanCopula{d, GT} where {d, GT<:UnivariateGenera
215215
θs = map(v -> invf(GT, clamp(v, -1, 1)), upper_triangle_flat)
216216

217217
θ = clamp(Statistics.mean(θs), _θ_bounds(GT, d)...)
218-
return CT(d, θ), (; θ̂=θ, eps)
218+
return CT(d, θ), (; θ̂==θ,))
219219
end
220220
function _fit(CT::Type{<:ArchimedeanCopula{d, GT} where {d, GT<:UnivariateGenerator}}, U, ::Val{:ibeta})
221221
d = size(U,1); δ = 1e-8; GT = generatorof(CT)
@@ -226,27 +226,26 @@ function _fit(CT::Type{<:ArchimedeanCopula{d, GT} where {d, GT<:UnivariateGenera
226226
βmin, βmax = (a0), (b0)
227227
if βmin > βmax; βmin, βmax = βmax, βmin; end
228228
θ = βobs βmin ? a0 : βobs βmax ? b0 : Roots.find_zero-> (θ)-βobs, (a0,b0), Roots.Brent(); xatol=1e-8, rtol=0)
229-
return CT(d,θ), (; θ̂=θ)
229+
return CT(d,θ), (; θ̂==θ,))
230230
end
231231

232232
function _fit(CT::Type{<:ArchimedeanCopula{d, GT} where {d, GT<:UnivariateGenerator}}, U, ::Val{:mle}; start::Union{Symbol,Real}=:itau, xtol::Real=1e-8)
233233
d = size(U,1)
234234
GT = generatorof(CT)
235235
lo, hi = _θ_bounds(GT, d)
236-
θ₀ = [(lo+hi)/2]
236+
θ₀ = [1.0]
237237
if start isa Real
238238
θ₀[1] = start
239239
elseif start (:itau, :irho)
240-
try
241-
θ₀[1] = only(Distributions.params(_fit(CT, U, Val{start}())[1]))
242-
catch e
243-
end
240+
θ₀[1] = _fit(CT, U, Val{start}())[2].θ̂[1]
241+
end
242+
if θ₀[1] <= lo || θ₀[1] >= hi
243+
θ₀[1] = Distributions.params(_example(CT, d))[1]
244244
end
245-
θ₀[1] = clamp(θ₀[1], lo, hi)
246245
f(θ) = -Distributions.loglikelihood(CT(d, θ[1]), U)
247246
res = Optim.optimize(f, lo, hi, θ₀, Optim.Fminbox(Optim.LBFGS()), autodiff = :forward)
248-
θ̂ = Optim.minimizer(res)[1]
249-
return CT(d, θ̂), (; θ̂=θ̂, optimizer=:GradientDescent,
247+
θ = Optim.minimizer(res)[1]
248+
return CT(d, θ), (; θ̂==θ,), optimizer=Optim.summary(res),
250249
xtol=xtol, converged=Optim.converged(res),
251250
iterations=Optim.iterations(res))
252251
end

src/Copula.jl

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -52,43 +52,15 @@ function β(C::Copula{d}) where {d}
5252
Cbar0 = Distributions.cdf(SurvivalCopula(C, Tuple(1:d)), u)
5353
return (2.0^(d-1) * C0 + Cbar0 - 1) / (2^(d-1) - 1)
5454
end
55-
function γ(C::Copula{d}; nmc::Int=100_000, rng::Random.AbstractRNG=Random.MersenneTwister(123)) where {d}
56-
d 2 || throw(ArgumentError("γ(C) requires d≥2"))
57-
if d == 2
58-
f(t) = Distributions.cdf(C, [t, t]) + Distributions.cdf(C, [t, 1 - t])
59-
I, _ = QuadGK.quadgk(f, 0.0, 1.0; rtol=sqrt(eps()))
60-
return -2 + 4I
61-
end
62-
@inline _A(u) = (minimum(u) + max(sum(u) - d + 1, 0.0)) / 2
63-
@inline _Abar(u) = (1 - maximum(u) + max(1 - sum(u), 0.0)) / 2
64-
@inline invfac(k::Integer) = exp(-SpecialFunctions.logfactorial(k))
65-
s = 0.0
66-
@inbounds for i in 0:d
67-
s += (isodd(i) ? -1.0 : 1.0) * binomial(d, i) * invfac(i + 1)
68-
end
69-
a_d = 1/(d + 1) + 0.5*invfac(d + 1) + 0.5*s
70-
b_d = 2/3 + 4.0^(1 - d) / 3
71-
U = rand(rng, C, nmc)
72-
m = 0.0
73-
@inbounds for j in 1:nmc
74-
u = @view U[:, j]
75-
m += _A(u) + _Abar(u)
76-
end
77-
m /= nmc
78-
return (m - a_d) / (b_d - a_d)
55+
function γ(C::Copula{d}) where {d}
56+
_integrand(u) = (1 + minimum(u) - maximum(u) + max(abs(sum(u) - d/2) - (d - 2)/2, 0.0)) / 2
57+
I = Distributions.expectation(_integrand, C; nsamples=10^4)
58+
a = 1/(d+1) + 1/factorial(d+1) # independence
59+
b = (2 + 4.0^(1-d)) / 3 # comonotonicity
60+
return (I - a) / (b - a)
7961
end
80-
81-
function ι(C::Copula{d}; nmc::Int=100_000, rng::Random.AbstractRNG=Random.MersenneTwister(123)) where {d}
82-
U = rand(rng, C, nmc)
83-
s = 0.0
84-
@inbounds for j in 1:nmc
85-
u = @view U[:, j]
86-
lp = Distributions.logpdf(C, u)
87-
isfinite(lp) || throw(DomainError(lp, "logpdf(C,u) non-finite."))
88-
s -= lp
89-
end
90-
H = s / nmc
91-
return H
62+
function ι(C::Copula{d}) where {d}
63+
return Distributions.expectation(u -> -Distributions.logpdf(C, u), C; nsamples=10^4)
9264
end
9365
function λₗ(C::Copula{d}; ε::Float64 = 1e-10) where {d}
9466
g(e) = Distributions.cdf(C, fill(e, d)) / e
@@ -128,51 +100,24 @@ function ρ(U::AbstractMatrix)
128100
return h * (2.0^d * μ - 1.0)
129101
end
130102
function γ(U::AbstractMatrix)
131-
# Assumes pseudo-data given. Multivariate Gini’s gamma (Behboodian–Dolati–Úbeda, 2007)
132103
d, n = size(U)
133-
if d == 2
134-
# Schechtman–Yitzhaki symmetric Gini over ranks (copular invariant)
135-
r1 = StatsBase.tiedrank(@view U[1, :])
136-
r2 = StatsBase.tiedrank(@view U[2, :])
137-
m = n
138-
h = m + 1
139-
acc = 0.0
140-
@inbounds @simd for k in 1:m
141-
acc += abs(r1[k] + r2[k] - h) - abs(r1[k] - r2[k])
142-
end
143-
return 2*acc / (m*h)
144-
else
145-
@inline _A(u) = (minimum(u) + max(sum(u) - d + 1, 0.0)) / 2
146-
@inline _Abar(u) = (1 - maximum(u) + max(1 - sum(u), 0.0)) / 2
147-
invfac(k::Integer) = exp(-SpecialFunctions.logfactorial(k))
148-
s = 0.0
149-
binomf(d,i) = exp(SpecialFunctions.loggamma(d+1) - SpecialFunctions.loggamma(i+1) - SpecialFunctions.loggamma(d-i+1))
150-
@inbounds for i in 0:d
151-
s += (isodd(i) ? -1.0 : 1.0) * binomf(d,i) * invfac(i + 1)
152-
end
153-
a_d = 1/(d + 1) + 0.5*invfac(d + 1) + 0.5*s
154-
b_d = 2/3 + 4.0^(1 - d) / 3
155-
m = 0.0
156-
@inbounds for j in 1:n
157-
u = @view U[:, j]
158-
m += _A(u) + _Abar(u)
159-
end
160-
m /= n
161-
return (m - a_d) / (b_d - a_d)
104+
I = zero(eltype(U))
105+
for j in 1:n
106+
u = U[:,j]
107+
I += (1 + minimum(u) - maximum(u) + max(abs(sum(u) - d/2) - (d - 2)/2, 0.0)) / 2
162108
end
109+
I /= n
110+
a = 1/(d+1) + 1/factorial(d+1)
111+
b = (2 + 4.0^(1-d)) / 3
112+
return (I - a) / (b - a)
163113
end
164114
function (U::AbstractMatrix; t::Symbol=:upper, p::Union{Nothing,Real}=nothing)
165115
# Assumes pseudo-data given. Multivariate tail’s lambda (Schmidt, R. & Stadtmüller, U. 2006)
166-
d, m = size(U)
167-
m 4 || throw(ArgumentError("At least 4 observations are required"))
168-
p === nothing && (p = 1/sqrt(m))
116+
p === nothing && (p = 1/sqrt(size(U, 2)))
169117
(0 < p < 1) || throw(ArgumentError("p must be in (0,1)"))
170-
V = t === :upper ? (1 .- Float64.(U)) : Float64.(U)
171-
cnt = 0
172-
@inbounds @views for j in 1:m
173-
cnt += all(V[:, j] .<= p) # vista sin copiar gracias a @views
174-
end
175-
return clamp(cnt / (p*m), 0.0, 1.0)
118+
in_tail = t=== :upper ? Base.Fix2(>=, 1-p) : Base.Fix2(<=, p)
119+
prob = Statistics.mean(all(in_tail, U, dims=1))
120+
return clamp(prob/p, 0.0, 1.0)
176121
end
177122
λₗ(U::AbstractMatrix; p::Union{Nothing,Real}=nothing) = (U; t=:lower, p=p)
178123
λᵤ(U::AbstractMatrix; p::Union{Nothing,Real}=nothing) = (U; t=:upper, p=p)

src/Copulas.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ module Copulas
22

33
import Base
44
import Random
5-
import InteractiveUtils
65
import SpecialFunctions
76
import Roots
87
import Distributions

src/EllipticalCopulas/GaussianCopula.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ N(::Type{T}) where T<: GaussianCopula = Distributions.MvNormal
8282
function _cdf(C::CT,u) where {CT<:GaussianCopula}
8383
x = StatsBase.quantile.(Distributions.Normal(), u)
8484
d = length(C)
85-
return MvNormalCDF.mvnormcdf(C.Σ, fill(-Inf, d), x, m=10_0000d)[1]
85+
return MvNormalCDF.mvnormcdf(C.Σ, fill(-Inf, d), x)[1]
8686
end
8787

8888
function rosenblatt(C::GaussianCopula, u::AbstractMatrix{<:Real})
@@ -139,6 +139,6 @@ end
139139
function _fit(CT::Type{<:GaussianCopula}, u, ::Val{:mle})
140140
dd = Distributions.fit(N(CT), StatsBase.quantile.(U(CT),u))
141141
Σ = Matrix(dd.Σ)
142-
return GaussianCopula(Σ), (;)
142+
return GaussianCopula(Σ), (; θ̂ = (; Σ = Σ))
143143
end
144-
_available_fitting_methods(::Type{<:GaussianCopula}) = (:itau, :irho, :ibeta, :mle)
144+
_available_fitting_methods(::Type{<:GaussianCopula}, d) = (:mle, :itau, :irho, :ibeta)

src/EllipticalCopulas/TCopula.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,4 @@ function _rebound_params(::Type{<:TCopula}, d::Int, α::AbstractVector{T}) where
9797
return (; ν = ν, Σ = Σ)
9898
end
9999

100-
_available_fitting_methods(::Type{<:TCopula}) = (:mle,)
100+
_available_fitting_methods(::Type{<:TCopula}, d) = (:mle,)

src/ExtremeValueCopula.jl

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ _example(CT::Type{<:ExtremeValueCopula}, d) = CT(d; _rebound_params(CT, d, fill(
137137
_unbound_params(CT::Type{<:ExtremeValueCopula}, d, θ) = _unbound_params(tailof(CT), d, θ)
138138
_rebound_params(CT::Type{<:ExtremeValueCopula}, d, α) = _rebound_params(tailof(CT), d, α)
139139

140-
_available_fitting_methods(::Type{ExtremeValueCopula}) = (:ols, :cfg, :pickands)
141-
_available_fitting_methods(CT::Type{<:ExtremeValueCopula}) = (:mle,)
142-
_available_fitting_methods(CT::Type{<:ExtremeValueCopula{2,GT} where {GT<:UnivariateTail2}}) = (:mle, :itau, :irho, :ibeta, :iupper)
140+
_available_fitting_methods(::Type{ExtremeValueCopula}, d) = (:ols, :cfg, :pickands)
141+
_available_fitting_methods(CT::Type{<:ExtremeValueCopula}, d) = (:mle,)
142+
_available_fitting_methods(CT::Type{<:ExtremeValueCopula{2,GT} where {GT<:UnivariateTail2}}, d) = (:mle, :itau, :irho, :ibeta, :iupper)
143143

144144
# Fitting empírico (OLS, CFG, Pickands):
145145
function _fit(::Type{ExtremeValueCopula}, U, method::Union{Val{:ols}, Val{:cfg}, Val{:pickands}};
@@ -152,25 +152,28 @@ function _fit(CT::Type{<:ExtremeValueCopula{d, GT} where {d, GT<:UnivariateTail2
152152
m isa Val{:irho} ? ρ⁻¹(CT, StatsBase.corspearman(U')[1,2]) :
153153
β⁻¹(CT, corblomqvist(U')[1,2])
154154
θ = clamp(θ, _θ_bounds(tailof(CT), 2)...)
155-
return CT(2, θ), (; θ̂=θ)
155+
return CT(2, θ), (; θ̂==θ,))
156156
end
157157
function _fit(CT::Type{<:ExtremeValueCopula{d, GT} where {d, GT<:UnivariateTail2}}, U, ::Val{:iupper})
158158
θ = clamp(λᵤ⁻¹(CT, λᵤ(U)), _θ_bounds(tailof(CT), 2)...)
159-
return CT(2, θ), (; θ̂=θ)
159+
return CT(2, θ), (; θ̂==θ,))
160160
end
161161

162162
function _fit(CT::Type{<:ExtremeValueCopula{d, GT} where {d, GT<:UnivariateTail2}}, U, ::Val{:mle}; start::Union{Symbol,Real}=:itau, xtol::Real=1e-8)
163163
d = size(U,1)
164164
TT = tailof(CT)
165165
lo, hi = _θ_bounds(TT, d)
166-
θ0 = start isa Real ? start :
167-
start (:itau, :irho, :ibeta, :iupper) ? _fit(CT, U, Val{start}())[2].θ̂ :
168-
only(Distributions.params(_example(CT, d)))
169-
θ0 = clamp(θ0, lo, hi)
166+
θ0_val = if start isa Real
167+
start
168+
else
169+
initial_params = start (:itau, :irho, :ibeta, :iupper) ? _fit(CT, U, Val{start}())[2].θ̂ : only(Distributions.params(_example(CT, d)))
170+
initial_params.θ
171+
end
172+
θ0_clamped = clamp(θ0_val, lo, hi)
170173
f(θ) = -Distributions.loglikelihood(CT(d, θ[1]), U)
171-
res = Optim.optimize(f, lo, hi, [θ0], Optim.Fminbox(Optim.LBFGS()), autodiff = :forward)
174+
res = Optim.optimize(f, lo, hi, [θ0_clamped], Optim.Fminbox(Optim.LBFGS()), autodiff = :forward)
172175
θ̂ = Optim.minimizer(res)[1]
173-
return CT(d, θ̂), (; θ̂=θ̂, optimizer=:GradientDescent,
176+
return CT(d, θ̂), (; θ̂=(;θ=θ̂), optimizer=:GradientDescent,
174177
xtol=xtol, converged=Optim.converged(res),
175178
iterations=Optim.iterations(res))
176179
end

0 commit comments

Comments
 (0)