From 5aa54b51066f1e16ed2852ff9613c6d6c4d0ac16 Mon Sep 17 00:00:00 2001 From: Oskar Laverny Date: Sat, 21 Sep 2024 19:45:55 +0200 Subject: [PATCH] Fix Extreme Values Copulas (#226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix A function in AsymGalambosCopula to improve readability. * refactor: Update AsymLogCopula struct with individual asymmetry parameters + Fix the A function * refactor: Simplify calculation in `A` function in AsymMixedCopula. * Fully rewrite BC2Copula parameters and methods Changed the BC2Copula parameters to 'a' and 'b'. Updated the functions to reflect the new parameters. Added functions τ and ρ for calculations. * refactor: Rename function `ρₛ` to `ρ` in CuadrasAugeCopula. * refactor(GalambosCopula.jl): Improve handling of large parameter values * refactor: Simplify calculation functions in HuslerReissCopula * refactor: Update specific A function of LogCopula * refactor: Rename parameters in MOCopula and update related functions * refactor: Improve spacing in MixedCopula calculation formula * refactor: Remove unused _pdf function in tEVCopula * refactor: Simplify _pdf function and add τ and ρ functions to EVCs --- src/ExtremeValueCopula.jl | 35 ++------- src/ExtremeValueCopulas/AsymGalambosCopula.jl | 13 +--- src/ExtremeValueCopulas/AsymLogCopula.jl | 14 ++-- src/ExtremeValueCopulas/AsymMixedCopula.jl | 6 +- src/ExtremeValueCopulas/BC2Copula.jl | 72 ++++++++----------- src/ExtremeValueCopulas/CuadrasAugeCopula.jl | 2 +- src/ExtremeValueCopulas/GalambosCopula.jl | 4 +- src/ExtremeValueCopulas/HuslerReissCopula.jl | 10 +-- src/ExtremeValueCopulas/LogCopula.jl | 7 +- src/ExtremeValueCopulas/MOCopula.jl | 57 ++++----------- src/ExtremeValueCopulas/MixedCopula.jl | 2 +- src/ExtremeValueCopulas/tEVCopula.jl | 10 --- 12 files changed, 67 insertions(+), 165 deletions(-) diff --git a/src/ExtremeValueCopula.jl b/src/ExtremeValueCopula.jl index 176fe2cd..83c46c14 100644 --- a/src/ExtremeValueCopula.jl +++ b/src/ExtremeValueCopula.jl @@ -90,14 +90,6 @@ function D_B_ℓ(C::ExtremeValueCopula, t::Vector{Float64}, B::Vector{Int}) end # Función PDF para ExtremeValueCopula usando ℓ -function _pdf(C::ExtremeValueCopula, u::AbstractArray{<:Real}) - t = -log.(u) - c = exp(-ℓ(C, t)) - D1 = D_B_ℓ(C, t, [1]) - D2 = D_B_ℓ(C, t, [2]) - D12 = D_B_ℓ(C, t, [1, 2]) - return c * (-D12 + D1 * D2) / (u[1] * u[2]) -end function Distributions._logpdf(C::ExtremeValueCopula, u::AbstractArray{<:Real}) t = -log.(u) c = exp(-ℓ(C, t)) @@ -106,26 +98,12 @@ function Distributions._logpdf(C::ExtremeValueCopula, u::AbstractArray{<:Real}) D12 = D_B_ℓ(C, t, [1, 2]) return log(c) + log(-D12 + D1 * D2) - log(u[1] * u[2]) end -# Definir la función para calcular τ -function τ(C::ExtremeValueCopula) - integrand(x) = begin - a = A(C, x) - da = dA(C, x) - return (x * (1 - x) / a) * da - end - - integrate, _ = QuadGK.quadgk(integrand, 0.0, 1.0) - return integrate -end +# Definir la función para calcular τ and ρ +# Warning: the τ function can be veeeery unstable... it is actually very hard to compute correctly. +# In some case, it simply fails by a lot. +τ(C::ExtremeValueCopula) = QuadGK.quadgk(x -> d²A(C, x) * x * (1 - x) / A(C, x), 0.0, 1.0)[1] +ρ(C::ExtremeValueCopula) = 12 * QuadGK.quadgk(x -> 1 / (1 + A(C, x))^2, 0, 1)[1] - 3 -function ρₛ(C::ExtremeValueCopula) - integrand(x) = 1 / (1 + A(C, x))^2 - - integral, _ = QuadGK.quadgk(integrand, 0, 1) - - ρs = 12 * integral - 3 - return ρs -end # Función para calcular el coeficiente de dependencia en el límite superior function λᵤ(C::ExtremeValueCopula) return 2(1 - A(C, 0.5)) @@ -150,9 +128,6 @@ function Distributions._rand!(rng::Distributions.AbstractRNG, C::ExtremeValueCop u1, u2 = rand(rng, Distributions.Uniform(0,1), 2) z = rand(rng, ExtremeDist(C)) p = probability_z(C, z) - if p < -eps() || p > eps() - p = 0 - end c = rand(rng, Distributions.Bernoulli(p)) w = 0 if c == 1 diff --git a/src/ExtremeValueCopulas/AsymGalambosCopula.jl b/src/ExtremeValueCopulas/AsymGalambosCopula.jl index 986825d0..342e6707 100644 --- a/src/ExtremeValueCopulas/AsymGalambosCopula.jl +++ b/src/ExtremeValueCopulas/AsymGalambosCopula.jl @@ -46,14 +46,7 @@ struct AsymGalambosCopula{P} <: ExtremeValueCopula{P} end function A(C::AsymGalambosCopula, t::Real) - α = C.α - θ = C.θ - - term1 = (θ[1] * t)^(-α) - term2 = (θ[2] * (1 - t))^(-α) - - inner_term = term1 + term2 - - result = 1 - inner_term^(-1 / α) - return result + x₁ = - C.α * log(C.θ[1] * t) + x₂ = - C.α * log(C.θ[2] * (1-t)) + return -expm1(-LogExpFunctions.logaddexp(x₁,x₂) / C.α) end \ No newline at end of file diff --git a/src/ExtremeValueCopulas/AsymLogCopula.jl b/src/ExtremeValueCopulas/AsymLogCopula.jl index 329f75fc..756c3a31 100644 --- a/src/ExtremeValueCopulas/AsymLogCopula.jl +++ b/src/ExtremeValueCopulas/AsymLogCopula.jl @@ -21,7 +21,8 @@ References: """ struct AsymLogCopula{P} <: ExtremeValueCopula{P} α::P # Dependence Parameter - θ::Vector{P} # Asymmetry parameters (size 2) + θ₁::P + θ₂::P function AsymLogCopula(α::P, θ::Vector{P}) where {P} if length(θ) != 2 throw(ArgumentError("The vector θ must have 2 elements for the bivariate case")) @@ -30,15 +31,8 @@ struct AsymLogCopula{P} <: ExtremeValueCopula{P} elseif !(0 <= θ[1] <= 1) || !(0 <= θ[2] <= 1) throw(ArgumentError("All parameters θ must be in the interval [0, 1]")) else - return new{P}(α, θ) + return new{P}(α, θ[1],θ[2]) end end end - -function A(C::AsymLogCopula, t::Real) - α = C.α - θ = C.θ - - A = ((θ[1]^α)*(1-t)^α + (θ[2]^α)*(t^α))^(1/α)+(θ[1]- θ[2])*t + 1 -θ[1] - return A -end \ No newline at end of file +A(C::AsymLogCopula, t::Real) = ((C.θ₁^C.α)*(1-t)^C.α + (C.θ₂^C.α)*(t^C.α))^(1/C.α)+(C.θ₁- C.θ₂)*t + 1 - C.θ₁ \ No newline at end of file diff --git a/src/ExtremeValueCopulas/AsymMixedCopula.jl b/src/ExtremeValueCopulas/AsymMixedCopula.jl index e3b524e2..1dccf385 100644 --- a/src/ExtremeValueCopulas/AsymMixedCopula.jl +++ b/src/ExtremeValueCopulas/AsymMixedCopula.jl @@ -52,8 +52,6 @@ struct AsymMixedCopula{P} <: ExtremeValueCopula{P} end function A(C::AsymMixedCopula, t::Real) - θ = C.θ - - a = θ[2]*t^3 + θ[1]*t^2-(θ[1]+θ[2])*t+1 - return a + θ₁, θ₂ = C.θ[1], C.θ[2] + return θ₂*t^3 + θ₁*t^2 - (θ₁+θ₂)*t + 1 end \ No newline at end of file diff --git a/src/ExtremeValueCopulas/BC2Copula.jl b/src/ExtremeValueCopulas/BC2Copula.jl index 9ffdb7b1..c2eb695e 100644 --- a/src/ExtremeValueCopulas/BC2Copula.jl +++ b/src/ExtremeValueCopulas/BC2Copula.jl @@ -3,75 +3,63 @@ Fields: - - θ1::Real - parameter - - θ1::Real - parameter + - a::Real - parameter + - a::Real - parameter Constructor - BC2Copula(θ1, θ2) + BC2Copula(a, b) -The bivariate BC₂ copula is parameterized by two parameters ``\\theta_{i} \\in [0,1], i=1,2``. It is an Extreme value copula with Pickands dependence function: +The bivariate BC2 copula is parameterized by two parameters ``a,b \\in [0,1]``. It is an Extreme value copula with Pickands dependence function: ```math -A(t) = \\max\\{\\theta_1 t, \\theta_2(1-t) \\} + \\max\\{(1-\\theta_1)t, (1-\\theta_2)(1-t)\\} +A(t) = \\max\\{a t, b (1-t) \\} + \\max\\{(1-a)t, (1-b)(1-t)\\} ``` References: * [mai2011bivariate](@cite) Mai, J. F., & Scherer, M. (2011). Bivariate extreme-value copulas with discrete Pickands dependence measure. Extremes, 14, 311-324. Springer, 2011. """ struct BC2Copula{P} <: ExtremeValueCopula{P} - θ1::P - θ2::P - - function BC2Copula(θ::Vararg{Real}) - if length(θ) !== 2 - throw(ArgumentError("BC2Copula requires only 2 arguments.")) - end - T = promote_type(typeof(θ[1]), typeof(θ[2])) - θ1, θ2 = T(θ[1]), T(θ[2]) - - if !(0 <= θ1 <= 1) || !(0 <= θ2 <= 1) - throw(ArgumentError("All θ parameters must be in [0,1]")) + a::P + b::P + function BC2Copula(a,b) + T = promote_type(typeof(a), typeof(b)) + if !(0 <= a <= 1) || !(0 <= b <= 1) + throw(ArgumentError("Both parameters a and b must be in [0,1]")) end - return new{T}(θ1, θ2) + return new{T}(T(a), T(b)) end end function ℓ(C::BC2Copula, t::Vector) - θ1, θ2 = C.θ1, C.θ2 + a, b = C.a, C.b t₁, t₂ = t - return max(θ1*t₁, θ2*t₂) + max((1-θ1)*t₁, (1-θ2)*t₂) + return max(a*t₁, b*t₂) + max((1-a)*t₁, (1-b)*t₂) end function A(C::BC2Copula, t::Real) - θ1, θ2 = C.θ1, C.θ2 - return max(θ1*t, θ2*(1-t)) + max((1-θ1)*t, (1-θ2)*(1-t)) + a, b = C.a, C.b + return max(a*t, b*(1-t)) + max((1-a)*t, (1-b)*(1-t)) end function dA(C::BC2Copula, t::Float64) - θ1, θ2 = C.θ1, C.θ2 - - # Conditions for the derivative of the first part - if θ1*t >= θ2*(1-t) - f1_derivative = θ1 - else - f1_derivative = -θ2 - end - - # Conditions for the derivative of the second part - if (1-θ1)*t >= (1-θ2)*(1-t) - f2_derivative = 1-θ1 - else - f2_derivative = -(1-θ2) - end - - return f1_derivative + f2_derivative + a, b = C.a, C.b + der1 = a*t >= b*(1-t) ? a : -b + der2 = (1-a)*t >= (1-b)*(1-t) ? -a : -(1-b) + return der1 + der2 end function Distributions._rand!(rng::Distributions.AbstractRNG, C::BC2Copula, u::AbstractVector{T}) where {T<:Real} - θ1, θ2 = C.θ1, C.θ2 + a, b = C.a, C.b v1, v2 = rand(rng, Distributions.Uniform(0,1), 2) - u[1] = max(v1^(1/θ1),v2^(1/(1-θ1))) - u[2] = max(v1^(1/θ2),v2^(1/(1-θ2))) + u[1] = max(v1^(1/a), v2^(1/(1-a))) + u[2] = max(v1^(1/b), v2^(1/(1-b))) return u +end + +τ(C::BC2Copula) = 1 - abs(C.a - C.b) + +function ρ(C::BC2Copula) + a,b = C.a, C.b + return 2 * (a + b + a*b + max(a,b) - 2a^2 - 2b^2) / (3 - a - b - min(a,b)) / (a + b + max(a,b)) end \ No newline at end of file diff --git a/src/ExtremeValueCopulas/CuadrasAugeCopula.jl b/src/ExtremeValueCopulas/CuadrasAugeCopula.jl index 7e8f1419..a6a16617 100644 --- a/src/ExtremeValueCopulas/CuadrasAugeCopula.jl +++ b/src/ExtremeValueCopulas/CuadrasAugeCopula.jl @@ -39,7 +39,7 @@ dA(C::CuadrasAugeCopula, t::Real) = t <= 0.5 ? -C.θ : C.θ τ(C::CuadrasAugeCopula) = C.θ/(2-C.θ) -ρₛ(C::CuadrasAugeCopula) = (3*C.θ)/(4-C.θ) +ρ(C::CuadrasAugeCopula) = (3*C.θ)/(4-C.θ) # specific ℓ específica of Cuadras-Augé Copula function ℓ(C::CuadrasAugeCopula, t::Vector) diff --git a/src/ExtremeValueCopulas/GalambosCopula.jl b/src/ExtremeValueCopulas/GalambosCopula.jl index 4e2232c1..b9169914 100644 --- a/src/ExtremeValueCopulas/GalambosCopula.jl +++ b/src/ExtremeValueCopulas/GalambosCopula.jl @@ -27,7 +27,7 @@ struct GalambosCopula{P} <: ExtremeValueCopula{P} θ::P # Copula parameter function GalambosCopula(θ) if θ > 19.5 - @warn "The parameter θ = $(θ) is large, which may lead to numerical instability in any functions. Consider regularizing the input." + @info "GalambosCopula(θ=$(θ)): θ is large, which may lead to numerical issues." end if θ < 0 throw(ArgumentError("Theta must be >= 0")) @@ -41,7 +41,7 @@ struct GalambosCopula{P} <: ExtremeValueCopula{P} end end -A(C::GalambosCopula, t::Real) = 1 - (t^(-C.θ) + (1 - t)^(-C.θ))^(-1/C.θ) +A(C::GalambosCopula, t::Real) = -expm1(-LogExpFunctions.logaddexp(-C.θ*log(t),-C.θ*log(1-t))/C.θ) # This auxiliary function helps determine if we need binary search or not in the generation of random samples function needs_binary_search(C::GalambosCopula) return C.θ > 19.5 diff --git a/src/ExtremeValueCopulas/HuslerReissCopula.jl b/src/ExtremeValueCopulas/HuslerReissCopula.jl index 30f5a5cb..9e1d3f30 100644 --- a/src/ExtremeValueCopulas/HuslerReissCopula.jl +++ b/src/ExtremeValueCopulas/HuslerReissCopula.jl @@ -50,11 +50,7 @@ function A(H::HuslerReissCopula, t::Real) θ = H.θ term1 = t * Distributions.cdf(Distributions.Normal(), θ^(-1) + 0.5 * θ * log(t / (1 - t))) term2 = (1 - t) * Distributions.cdf(Distributions.Normal(), θ^(-1) + 0.5 * θ * log((1 - t) / t)) - - a = term1 + term2 - - - return a + return term1 + term2 end function dA(H::HuslerReissCopula, t::Real) @@ -66,7 +62,5 @@ function dA(H::HuslerReissCopula, t::Real) dA_term2 = -Distributions.cdf(Distributions.Normal(), θ^(-1) + 0.5 * θ * log((1 - t) / t)) + (1 - t) * Distributions.pdf(Distributions.Normal(), θ^(-1) + 0.5 * θ * log((1 - t) / t)) * (0.5 * θ * (-1 / t - 1 / (1 - t))) - da = dA_term1 + dA_term2 - - return da + return dA_term1 + dA_term2 end \ No newline at end of file diff --git a/src/ExtremeValueCopulas/LogCopula.jl b/src/ExtremeValueCopulas/LogCopula.jl index f9483792..d2731b55 100644 --- a/src/ExtremeValueCopulas/LogCopula.jl +++ b/src/ExtremeValueCopulas/LogCopula.jl @@ -42,8 +42,5 @@ function ℓ(G::LogCopula, t::Vector) t₁, t₂ = t return (t₁^θ + t₂^θ)^(1/θ) end -# # specific A funcion of HuslerReissCopula -function A(C::LogCopula, t::Real) - θ = C.θ - return (t^θ + (1 - t)^θ)^(1/θ) -end \ No newline at end of file +# # specific A funcion of LogCopula +A(C::LogCopula, t::Real) = exp(LogExpFunctions.logaddexp(C.θ*log(t),C.θ*log(1-t))/C.θ) diff --git a/src/ExtremeValueCopulas/MOCopula.jl b/src/ExtremeValueCopulas/MOCopula.jl index 0b74ac19..3b7a8363 100644 --- a/src/ExtremeValueCopulas/MOCopula.jl +++ b/src/ExtremeValueCopulas/MOCopula.jl @@ -3,9 +3,9 @@ Fields: - - λ1::Real - parameter - - λ2::Real - parameter - - λ12::Real - parameter + - λ₁::Real - parameter + - λ₂::Real - parameter + - λ₁₂::Real - parameter Constructor @@ -21,49 +21,22 @@ References: * [mai2012simulating](@cite) Mai, J. F., & Scherer, M. (2012). Simulating copulas: stochastic models, sampling algorithms, and applications (Vol. 4). World Scientific. """ struct MOCopula{P} <: ExtremeValueCopula{P} - λ1::P - λ2::P - λ12::P - - function MOCopula(λ::Vararg{Real}) - if length(λ) !== 3 - throw(ArgumentError("MOCopula requires only 3 arguments.")) - end - - T = promote_type(typeof(λ[1]), typeof(λ[2]), typeof(λ[3])) - λ1, λ2, λ12 = T(λ[1]), T(λ[2]), T(λ[3]) - - if λ1 < 0 || λ2 < 0 || λ12 < 0 + a::P + b::P + function MOCopula(λ₁,λ₂,λ₁₂) + if λ₁ < 0 || λ₂ < 0 || λ₁₂ < 0 throw(ArgumentError("All λ parameters must be >= 0")) end - - return new{T}(λ1, λ2, λ12) + a, b = λ₁ / (λ₁ + λ₁₂), λ₂ / (λ₂ + λ₁₂) + return new{typeof(a)}(a,b) end end - -function A(C::MOCopula, t::Real) - λ1, λ2, λ12 = C.λ1, C.λ2, C.λ12 - return ((λ1 * (1 - t))/(λ1 + λ12)) + ((λ2 * t)/(λ2 + λ12)) + λ12 * max((1-t)/(λ1 + λ12), t/(λ2 + λ12)) -end - -function ℓ(C::MOCopula, t::Vector) - λ1, λ2, λ12 = C.λ1, C.λ2, C.λ12 - t₁, t₂ = t - return ((λ1 * t₂)/(λ1 + λ12)) + ((λ2 * t₁)/(λ2 + λ12)) + λ12 * max(t₂/(λ1 + λ12), t₁/(λ2 + λ12)) -end - -function _cdf(C::MOCopula, u::AbstractArray{<:Real}) - λ1, λ2, λ12 = C.λ1, C.λ2, C.λ12 - u₁, u₂ = u - return min(u₂*u₁^(1 - λ12/(λ1 + λ12)), u₁ * u₂^(1 - λ12/(λ2 + λ12))) -end - +A(C::MOCopula, t::Real) = max(t + (1-t)*C.b, (1-t)+C.a*t) +_cdf(C::MOCopula, u::AbstractArray{<:Real}) = min(u[1]^C.a * u[2], u[1] * u[2]^C.b) function Distributions._rand!(rng::Distributions.AbstractRNG, C::MOCopula, u::AbstractVector{T}) where {T<:Real} - λ1, λ2, λ12 = C.λ1, C.λ2, C.λ12 - r, s, t = rand(rng, Distributions.Uniform(0,1),3) - x = min(-log(r)/λ1, -log(t)/λ12) - y = min(-log(s)/λ2, -log(t)/λ12) - u[1] = exp(-(λ1+λ12)*x) - u[2] = exp(-(λ2+λ12)*y) + r, s, t = -log.(rand(rng,3)) # Exponentials(1) + u[1] = exp(-min(r/(1-C.a), t/C.a)) + u[2] = exp(-min(s/(1-C.b), t/C.b)) return u end +τ(C::MOCopula) = C.a*C.b/(C.a+C.b-C.a*C.b) \ No newline at end of file diff --git a/src/ExtremeValueCopulas/MixedCopula.jl b/src/ExtremeValueCopulas/MixedCopula.jl index 56dd3a44..940a25a1 100644 --- a/src/ExtremeValueCopulas/MixedCopula.jl +++ b/src/ExtremeValueCopulas/MixedCopula.jl @@ -34,4 +34,4 @@ struct MixedCopula{P} <: ExtremeValueCopula{P} end end -A(C::MixedCopula, t::Real) = C.θ*t^2 - C.θ*t + 1 \ No newline at end of file +A(C::MixedCopula, t::Real) = C.θ * t^2 - C.θ * t + 1 \ No newline at end of file diff --git a/src/ExtremeValueCopulas/tEVCopula.jl b/src/ExtremeValueCopulas/tEVCopula.jl index 84b3f56e..b1283636 100644 --- a/src/ExtremeValueCopulas/tEVCopula.jl +++ b/src/ExtremeValueCopulas/tEVCopula.jl @@ -93,16 +93,6 @@ function d²A(C::tEVCopula, t::Real) d2A = (dA_plus - dA_minus) / (2 * h) return d2A end - -# PDF function for ExtremeValueCopula using ℓ -function _pdf(C::tEVCopula, u::AbstractArray{<:Real}) - t = -log.(u) - c = exp(-ℓ(C, t)) - D1 = D_B_ℓ(C, t, [1]) - D2 = D_B_ℓ(C, t, [2]) - D12 = D_B_ℓ(C, t, [1, 2]) - return c * (-D12 + D1 * D2) / (u[1] * u[2]) -end function Distributions._logpdf(C::tEVCopula, u::AbstractArray{<:Real}) t = -log.(u) c = exp(-ℓ(C, t))