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

Archimedeans #73

Merged
merged 10 commits into from
Nov 20, 2023
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
Cubature = "667455a9-e2ce-5579-9412-b964f529a492"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
GSL = "92c85e6c-cbff-5e0c-80f7-495c94daaecd"
LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
MvNormalCDF = "37188c8d-bc69-4638-b057-733e744175ec"
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Expand All @@ -24,12 +24,12 @@ Combinatorics = "1"
Cubature = "1.5"
Distributions = "0.25"
ForwardDiff = "0.10"
GSL = "1"
HypothesisTests = "v0.11"
InteractiveUtils = "1.6"
LinearAlgebra = "1.6"
LogExpFunctions = "0.3"
MvNormalCDF = "0.2, 0.3"
QuadGK = "2"
Random = "1.6"
Roots = "1, 2"
SpecialFunctions = "2"
Expand Down
1 change: 0 additions & 1 deletion src/ArchimedeanCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ end

williamson_dist(C::ArchimedeanCopula{d}) where d = WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d)
function τ(C::ArchimedeanCopula)
@show C
return 4*Distributions.expectation(r -> ϕ(C,r), williamson_dist(C)) - 1
end

Expand Down
38 changes: 27 additions & 11 deletions src/ArchimedeanCopulas/AMHCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,39 @@

τ(C::AMHCopula) = _amh_tau_f(C.θ) # no closed form inverse...

_amh_tau_f(θ) = θ == 1 ? 1/3 : 1 - 2(θ+(1-θ)^2*log1p(-θ))/(3θ^2)
function _amh_tau_f(θ)

# if θ = -1, we obtain (5 -8*log(2))/3

function τ⁻¹(::Type{AMHCopula},τ)
if τ == zero(τ)
return τ
# unstable around zero, we instead cut its taylor expansion:
if abs(θ) < 0.01
return 2/9 * θ
+ 1/18 * θ^2
+ 1/45 * θ^3
+ 1/90 * θ^4
+ 2/315 * θ^5
+ 1/252 * θ^6
+ 1/378 * θ^7
+ 1/540 * θ^8
+ 2/1485 * θ^9
+ 1/990 * θ^10

Check warning on line 50 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L42-L50

Added lines #L42 - L50 were not covered by tests
end
if iszero(θ)
return zero(θ)

Check warning on line 53 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L53

Added line #L53 was not covered by tests
end
if τ > 1/3
u = isone(θ) ? θ : θ + (1-θ)^2 * log1p(-θ)
return 1 - (2/3)*u/θ^2
end
function τ⁻¹(::Type{AMHCopula},tau)
if tau == zero(tau)
return tau

Check warning on line 60 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L60

Added line #L60 was not covered by tests
elseif tau > 1/3
@warn "AMHCopula cannot handle kendall tau's greater than 1/3. We capped it to 1/3."
return one(τ)
end
if τ < (5 - 8*log(2))/3
elseif tau < (5 - 8*log(2))/3
@warn "AMHCopula cannot handle kendall tau's smaller than (5- 8ln(2))/3 (approx -0.1817). We capped it to this value."
return -one(τ)
return -one(tau)
end
return Roots.find_zero(θ -> _amh_tau_f(θ) - τ, (-one(τ), one(τ)))
search_range = tau > 0 ? (0,1) : (-1,0)
return Roots.find_zero(θ -> tau - _amh_tau_f(θ), search_range)
end
williamson_dist(C::AMHCopula{d,T}) where {d,T} = C.θ >= 0 ? WilliamsonFromFrailty(1 + Distributions.Geometric(1-C.θ),d) : WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d)

Expand Down
31 changes: 19 additions & 12 deletions src/ArchimedeanCopulas/FrankCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
θ::T
function FrankCopula(d,θ)
if d > 2 && θ < 0
throw(ArgumentError("Negatively dependent Frank copulas cannot exists in dimensions > 2"))
throw(ArgumentError("Negatively dependent Frank copulas cannot exists in dimensions > 2. You passed θ = $θ"))

Check warning on line 26 in src/ArchimedeanCopulas/FrankCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/FrankCopula.jl#L26

Added line #L26 was not covered by tests
end
if θ == -Inf
return WCopula(d)
Expand All @@ -44,25 +44,32 @@
# Avoid type piracy by defiing it myself:
ϕ( C::FrankCopula, t::TaylorSeries.Taylor1) = C.θ > 0 ? -log(-expm1(LogExpFunctions.log1mexp(-C.θ)-t))/C.θ : -log1p(exp(-t) * expm1(-C.θ))/C.θ

D₁ = GSL.sf_debye_1 # sadly, this is C code.
# D₁ = GSL.sf_debye_1 # sadly, this is C code.
# could be replaced by :
# using QuadGK
# D₁(x) = quadgk(t -> t/(exp(t)-1), 0, x)[1]/x
D₁(x) = QuadGK.quadgk(t -> t/expm1(t), 0, x)[1]/x
# to make it more general. but once gain, it requires changing the integrator at each evlauation,
# which is problematic.
# Better option is to try to include this function into SpecialFunctions.jl.


τ(C::FrankCopula) = 1+4(D₁(C.θ)-1)/C.θ
function τ⁻¹(::Type{FrankCopula},τ)
if τ == zero(τ)
return τ
function _frank_tau_f(θ)
if abs(θ) < sqrt(eps(θ))
# return the taylor approx.
return θ/9 * (1 - (θ/10)^2)
else
return 1+4(D₁(θ)-1)/θ
end
if abs(τ==1)
return Inf * τ
end
τ(C::FrankCopula) = _frank_tau_f(C.θ)
function τ⁻¹(::Type{FrankCopula},τ)
s,v = sign(τ),abs(τ)
if v == 0
return v

Check warning on line 67 in src/ArchimedeanCopulas/FrankCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/FrankCopula.jl#L67

Added line #L67 was not covered by tests
elseif v == 1
return s * Inf
else
return s*Roots.fzero(x -> _frank_tau_f(x)-v, 0, Inf)
end
x₀ = (1-τ)/4
return Roots.fzero(x -> (1-D₁(x))/x - x₀, 1e-4, Inf)
end

williamson_dist(C::FrankCopula{d,T}) where {d,T} = C.θ > 0 ? WilliamsonFromFrailty(Logarithmic(-C.θ), d) : WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d)
Expand Down
20 changes: 11 additions & 9 deletions src/ArchimedeanCopulas/GumbelBarnettCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,27 @@
ϕ( C::GumbelBarnettCopula, t) = exp((1-exp(t))/C.θ)
ϕ⁻¹(C::GumbelBarnettCopula, t) = log(1-C.θ*log(t))
function τ(C::GumbelBarnettCopula)
# Define the function to integrate
f(x) = -x * (1 - C.θ * log(x)) * log(1 - C.θ * log(x)) / C.θ
result = Distributions.expectation(f,Distributions.Uniform(0,1))
# Calculate the integral using GSL
# result =
# result, _ = GSL.integration_qags(f, 0.0, 1.0, [C.θ], 1e-7, 1000)
# Use a numerical integration method to obtain tau
result, _ = QuadGK.quadgk(x -> -((x-C.θ*x*log(x))*log(1-C.θ*log(x))/C.θ), 0, 1)

return 1+4*result
end
function τ⁻¹(::Type{GumbelBarnettCopula}, tau)
if tau == zero(tau)
return tau
if tau == 0
return zero(tau)

Check warning on line 47 in src/ArchimedeanCopulas/GumbelBarnettCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/GumbelBarnettCopula.jl#L47

Added line #L47 was not covered by tests
elseif tau > 0
@warn "GumbelBarnettCopula cannot handle positive kendall tau's, returning independence.."
return zero(tau)
elseif tau < τ(GumbelBarnettCopula(2,1))
@warn "GumbelBarnettCopula cannot handle negative kendall tau's smaller than ≈ -0.3613, so we capped to that value."
return one(tau)
end

# Define an anonymous function that takes a value x and computes τ
#for a GumbelBarnettCopula with θ = x
τ_func(x) = τ(GumbelBarnettCopula(2,x))

# Use the bisection method to find the root
x = Roots.find_zero(x -> τ_func(x) - tau, (0.0, 1.0))
x = Roots.find_zero(x -> τ_func(x) - tau, (0.0, 1.0))
return x
end
1 change: 0 additions & 1 deletion src/ArchimedeanCopulas/GumbelCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ struct GumbelCopula{d,T} <: ArchimedeanCopula{d}
θ::T
function GumbelCopula(d,θ)
if θ < 1
@show θ
throw(ArgumentError("Theta must be greater than or equal to 1"))
elseif θ == 1
return IndependentCopula(d)
Expand Down
54 changes: 31 additions & 23 deletions src/ArchimedeanCopulas/InvGaussianCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,45 @@
throw(ArgumentError("Theta must be non-negative."))
elseif θ == 0
return IndependentCopula(d)
elseif isinf(θ)
throw(ArgumentError("Theta cannot be infinite"))
# elseif isinf(θ)
# throw(ArgumentError("Theta cannot be infinite"))
else
return new{d,typeof(θ)}(θ)
end
end
end

ϕ( C::InvGaussianCopula, t) = exp((1-sqrt(1+2*((C.θ)^(2))*t))/C.θ)
ϕ⁻¹(C::InvGaussianCopula, t) = ((1-C.θ*log(t))^(2)-1)/(2*(C.θ)^(2))
function τ(C::InvGaussianCopula)
# Define the function to integrate
f(x) = -x * (((1 - C.θ * log(x))^2 - 1) / (2 * C.θ * (1 - C.θ * log(x))))

# Calculate the integral using an appropriate numerical integration method
result, _ = gsl_integration_qags(f, 0.0, 1.0, [C.θ], 1e-7, 1000)

return 1+4*result
ϕ( C::InvGaussianCopula, t) = isinf(C.θ) ? exp(-sqrt(2*t)) : exp((1-sqrt(1+2*((C.θ)^(2))*t))/C.θ)
ϕ⁻¹(C::InvGaussianCopula, t) = isinf(C.θ) ? ln(t)^2/2 : ((1-C.θ*log(t))^(2)-1)/(2*(C.θ)^(2))
function _invg_tau_f(θ)
if θ == 0
return zero(θ)

Check warning on line 43 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L43

Added line #L43 was not covered by tests
elseif θ > 1e153 # should be Inf, but integrand has issues...
return 1/2
elseif θ < sqrt(eps(θ))
return zero(θ)

Check warning on line 47 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L47

Added line #L47 was not covered by tests
end
function _integrand(x,θ)
y = 1-θ*log(x)
ret = - x*(y^2-1)/(2θ*y)
return ret
end
rez, err = QuadGK.quadgk(x -> _integrand(x,θ),zero(θ),one(θ))
rez = 1+4*rez
return rez
end
function τ⁻¹(::Type{InvGaussianCopula}, τ)
if τ == zero(τ)
return τ
τ(C::InvGaussianCopula) = _invg_tau_f(C.θ)
function τ⁻¹(::Type{InvGaussianCopula}, tau)
if tau == zero(tau)
return tau

Check warning on line 61 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L61

Added line #L61 was not covered by tests
elseif tau < 0
@warn "InvGaussianCopula cannot handle negative dependencies, returning independence..."
return zero(tau)

Check warning on line 64 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L63-L64

Added lines #L63 - L64 were not covered by tests
elseif tau > 0.5
@warn "InvGaussianCopula cannot handle kendall tau greater than 0.5, using 0.5.."
return tau * Inf

Check warning on line 67 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L66-L67

Added lines #L66 - L67 were not covered by tests
end

# Define an anonymous function that takes a value x and computes τ for an InvGaussianCopula copula with θ = x
τ_func(x) = τ(InvGaussianCopula{2, Float64}(x))

# Set an initial value for x₀ (adjustable)
x₀ = (1-τ)/4

return Roots.find_zero(x -> τ_func(x) - τ, x₀)
return Roots.find_zero(x -> _invg_tau_f(x) - tau, (sqrt(eps(tau)), Inf))
end

williamson_dist(C::InvGaussianCopula{d,T}) where {d,T} = WilliamsonFromFrailty(Distributions.InverseGaussian(C.θ,1),d)
2 changes: 1 addition & 1 deletion src/Copulas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module Copulas
import Base
import Random
import SpecialFunctions
import GSL
import Roots
import Distributions
import StatsBase
Expand All @@ -14,6 +13,7 @@ module Copulas
import WilliamsonTransforms
import Combinatorics
import LogExpFunctions
import QuadGK

# Standard copulas and stuff.
include("utils.jl")
Expand Down
70 changes: 33 additions & 37 deletions test/archimedean_tests.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@

@testitem "Test of τ ∘ τ_inv bijection" begin
using Random
taus = [0.0, 0.1, 0.5, 0.9, 1.0]
using StableRNGs
rng = StableRNG(123)

for T in (
# AMHCopula,
ClaytonCopula,
# FrankCopula,
GumbelCopula,
# IndependentCopula,
# JoeCopula,
# GumbelBarnettCopula,
# InvGaussianCopula
)
for τ in taus
@test Copulas.τ(T(2,Copulas.τ⁻¹(T,τ))) ≈ τ
end
end
inv_works(T,tau) = Copulas.τ(T(2,Copulas.τ⁻¹(T,tau))) ≈ tau
check_rnd(T,min,max,N) = all(inv_works(T,x) for x in min .+ (max-min) .* rand(rng,N))

@test check_rnd(ClaytonCopula, -1, 1, 10)
@test check_rnd(GumbelCopula, 0, 1, 10)
@test check_rnd(JoeCopula, 0, 1, 10)
@test check_rnd(GumbelBarnettCopula, -0.35, 0, 10)
@test check_rnd(AMHCopula, -0.18, 1/3, 10)
@test check_rnd(FrankCopula, -1, 1, 10)
@test check_rnd(InvGaussianCopula,0,1/2,10)

end

# For each archimedean, we test:
Expand All @@ -43,11 +41,11 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
C = ClaytonCopula(2,-1)
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
fit(ClaytonCopula,data)
C0 = ClaytonCopula(2,-1)
data0 = rand(rng,C0,100)
@test all(pdf(C0,data0) .>= 0)
@test all(0 .<= cdf(C0,data0) .<= 1)
fit(ClaytonCopula,data0)
for d in 2:10
for θ ∈ [-1/(d-1) * rand(rng),0.0,-log(rand(rng)), Inf]
C = ClaytonCopula(d,θ)
Expand All @@ -66,26 +64,26 @@ end
rand(rng,FrankCopula(2,-Inf),10)
rand(rng,FrankCopula(2,log(rand(rng))),10)

C = FrankCopula(2,-Inf)
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(FrankCopula,data)
C0 = FrankCopula(2,-Inf)
data0 = rand(rng,C0,100)
@test all(pdf(C0,data0) .>= 0)
@test all(0 .<= cdf(C0,data0) .<= 1)
fit(FrankCopula,data0)

C = FrankCopula(2,log(rand(rng)))
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(FrankCopula,data)
C1 = FrankCopula(2,log(rand(rng)))
data1 = rand(rng,C1,100)
@test all(pdf(C1,data1) .>= 0)
@test all(0 .<= cdf(C1,data1) .<= 1)
fit(FrankCopula,data1)


for d in 2:10
for θ ∈ [0.0,rand(rng),1.0,-log(rand(rng)), Inf]
for θ ∈ [1.0,1-log(rand(rng)), Inf]
C = FrankCopula(d,θ)
data = rand(rng,C,100)
data = rand(rng,C,10000)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
# fit(FrankCopula,data)
fit(FrankCopula,data)
end
end
@test true
Expand All @@ -96,7 +94,6 @@ end
rng = StableRNG(123)
for d in 2:10
for θ ∈ [1.0,1-log(rand(rng)), Inf]
@show d,θ
C = GumbelCopula(d,θ)
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
Expand Down Expand Up @@ -132,7 +129,7 @@ end
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(GumbelBarnettCopula,data)
fit(GumbelBarnettCopula,data)
end
end
@test true
Expand All @@ -144,12 +141,11 @@ end
rng = StableRNG(123)
for d in 2:10
for θ ∈ [rand(rng),1.0, -log(rand(rng))]
@show d,θ
C = InvGaussianCopula(d,θ)
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(InvGaussianCopula,data)
fit(InvGaussianCopula,data)
end
end
@test true
Expand Down
Loading
Loading