diff --git a/src/ArchimedeanCopula.jl b/src/ArchimedeanCopula.jl index 33e141a5..dfe6a886 100644 --- a/src/ArchimedeanCopula.jl +++ b/src/ArchimedeanCopula.jl @@ -88,8 +88,10 @@ end ϕ⁻¹(C::ArchimedeanCopula{d},x) where d = Roots.find_zero(t -> ϕ(C,t) - x, (0.0, Inf)) williamson_dist(C::ArchimedeanCopula{d}) where d = WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d) -τ(C::ArchimedeanCopula) = 4*Distributions.expectation(r -> ϕ(C,r), williamson_dist(C)) - 1 - +function τ(C::ArchimedeanCopula) + @show C + return 4*Distributions.expectation(r -> ϕ(C,r), williamson_dist(C)) - 1 +end function _archi_rand!(rng,C::ArchimedeanCopula{d},R,x) where d # x is assumed to already be random exponentials produced by Random.randexp diff --git a/src/ArchimedeanCopulas/AMHCopula.jl b/src/ArchimedeanCopulas/AMHCopula.jl index 0aabc3ac..c00d881f 100644 --- a/src/ArchimedeanCopulas/AMHCopula.jl +++ b/src/ArchimedeanCopulas/AMHCopula.jl @@ -32,16 +32,25 @@ end ϕ( C::AMHCopula,t) = (1-C.θ)/(exp(t)-C.θ) ϕ⁻¹( C::AMHCopula,t) = log(C.θ + (1-C.θ)/t) -τ(C::AMHCopula) = 1 - 2(C.θ+(1-C.θ)^2*log(1-C.θ))/(3C.θ^2) # no closed form inverse... +τ(C::AMHCopula) = _amh_tau_f(C.θ) # no closed form inverse... + +_amh_tau_f(θ) = θ == 1 ? 1/3 : 1 - 2(θ+(1-θ)^2*log1p(-θ))/(3θ^2) + +# if θ = -1, we obtain (5 -8*log(2))/3 + function τ⁻¹(::Type{AMHCopula},τ) if τ == zero(τ) return τ end if τ > 1/3 @warn "AMHCopula cannot handle kendall tau's greater than 1/3. We capped it to 1/3." - return 1 + return one(τ) + end + if τ < (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(τ) end - return Roots.find_zero(θ -> 1 - 2(θ+(1-θ)^2*log(1-θ))/(3θ^2) - τ, (-1.0, 1.0)) + return Roots.find_zero(θ -> _amh_tau_f(θ) - τ, (-one(τ), one(τ))) end williamson_dist(C::AMHCopula{d,T}) where {d,T} = C.θ >= 0 ? WilliamsonFromFrailty(1 + Distributions.Geometric(1-C.θ),d) : WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d) diff --git a/src/ArchimedeanCopulas/FrankCopula.jl b/src/ArchimedeanCopulas/FrankCopula.jl index 01354f73..6a015961 100644 --- a/src/ArchimedeanCopulas/FrankCopula.jl +++ b/src/ArchimedeanCopulas/FrankCopula.jl @@ -58,6 +58,9 @@ function τ⁻¹(::Type{FrankCopula},τ) if τ == zero(τ) return τ end + if abs(τ==1) + return Inf * τ + end x₀ = (1-τ)/4 return Roots.fzero(x -> (1-D₁(x))/x - x₀, 1e-4, Inf) end diff --git a/src/ArchimedeanCopulas/GumbelBarnettCopula.jl b/src/ArchimedeanCopulas/GumbelBarnettCopula.jl index 92d04473..aec315a8 100644 --- a/src/ArchimedeanCopulas/GumbelBarnettCopula.jl +++ b/src/ArchimedeanCopulas/GumbelBarnettCopula.jl @@ -39,22 +39,23 @@ end 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, _ = gsl_integration_qags(f, 0.0, 1.0, [C.θ], 1e-7, 1000) + # result = + # result, _ = GSL.integration_qags(f, 0.0, 1.0, [C.θ], 1e-7, 1000) return 1+4*result end -function τ⁻¹(::Type{GumbelBarnettCopula}, τ) - if τ == zero(τ) - return τ +function τ⁻¹(::Type{GumbelBarnettCopula}, tau) + if tau == zero(tau) + return tau end # Define an anonymous function that takes a value x and computes τ #for a GumbelBarnettCopula with θ = x - τ_func(x) = τ(GumbelBarnettCopula{d, Float64}(x)) + τ_func(x) = τ(GumbelBarnettCopula(2,x)) # Use the bisection method to find the root - x = Roots.find_zero(x -> τ_func(x) - τ, (0.0, 1.0)) + x = Roots.find_zero(x -> τ_func(x) - tau, (0.0, 1.0)) return x end \ No newline at end of file diff --git a/src/ArchimedeanCopulas/GumbelCopula.jl b/src/ArchimedeanCopulas/GumbelCopula.jl index 73c775b9..43e2cbb4 100644 --- a/src/ArchimedeanCopulas/GumbelCopula.jl +++ b/src/ArchimedeanCopulas/GumbelCopula.jl @@ -22,7 +22,8 @@ struct GumbelCopula{d,T} <: ArchimedeanCopula{d} θ::T function GumbelCopula(d,θ) if θ < 1 - throw(ArgumentError("Theta must be greater than 1")) + @show θ + throw(ArgumentError("Theta must be greater than or equal to 1")) elseif θ == 1 return IndependentCopula(d) elseif θ == Inf @@ -35,7 +36,18 @@ end ϕ( C::GumbelCopula, t) = exp(-t^(1/C.θ)) ϕ⁻¹(C::GumbelCopula, t) = (-log(t))^C.θ τ(C::GumbelCopula) = ifelse(isfinite(C.θ), (C.θ-1)/C.θ, 1) -τ⁻¹(::Type{GumbelCopula},τ) =ifelse(τ == 1, Inf, 1/(1-τ)) +function τ⁻¹(::Type{GumbelCopula},τ) + if τ == 1 + return Inf + else + θ = 1/(1-τ) + if θ < 1 + @warn "GumbelCopula cannot handle negative kendall tau's, returning independence.." + return 1 + end + return θ + end +end williamson_dist(C::GumbelCopula{d,T}) where {d,T} = WilliamsonFromFrailty(AlphaStable(α = 1/C.θ, β = 1,scale = cos(π/(2C.θ))^C.θ, location = (C.θ == 1 ? 1 : 0)), d) diff --git a/src/ArchimedeanCopulas/InvGaussianCopula.jl b/src/ArchimedeanCopulas/InvGaussianCopula.jl index 69a2513c..4da22a7e 100644 --- a/src/ArchimedeanCopulas/InvGaussianCopula.jl +++ b/src/ArchimedeanCopulas/InvGaussianCopula.jl @@ -28,6 +28,8 @@ struct InvGaussianCopula{d,T} <: ArchimedeanCopula{d} throw(ArgumentError("Theta must be non-negative.")) elseif θ == 0 return IndependentCopula(d) + elseif isinf(θ) + throw(ArgumentError("Theta cannot be infinite")) else return new{d,typeof(θ)}(θ) end @@ -51,7 +53,7 @@ function τ⁻¹(::Type{InvGaussianCopula}, τ) end # Define an anonymous function that takes a value x and computes τ for an InvGaussianCopula copula with θ = x - τ_func(x) = τ(InvGaussianCopula{d, Float64}(x)) + τ_func(x) = τ(InvGaussianCopula{2, Float64}(x)) # Set an initial value for x₀ (adjustable) x₀ = (1-τ)/4 diff --git a/src/ArchimedeanCopulas/JoeCopula.jl b/src/ArchimedeanCopulas/JoeCopula.jl index c8e08ec1..bffa7f70 100644 --- a/src/ArchimedeanCopulas/JoeCopula.jl +++ b/src/ArchimedeanCopulas/JoeCopula.jl @@ -34,7 +34,20 @@ struct JoeCopula{d,T} <: ArchimedeanCopula{d} end ϕ( C::JoeCopula, t) = 1-(-expm1(-t))^(1/C.θ) ϕ⁻¹(C::JoeCopula, t) = -log1p(-(1-t)^C.θ) -τ(C::JoeCopula) = 1 - 4sum(1/(k*(2+k*C.θ)*(C.θ*(k-1)+2)) for k in 1:1000) # 446 in R copula. +_joe_tau_f(θ) = 1 - 4sum(1/(k*(2+k*θ)*(θ*(k-1)+2)) for k in 1:1000) # 446 in R copula. +τ(C::JoeCopula) = _joe_tau_f(C.θ) +function τ⁻¹(::Type{JoeCopula},τ) + if τ == 1 + return Inf + elseif τ == 0 + return 1 + elseif τ < 0 + @warn "JoeCoula cannot handle negative kendall taus, we return the independence..." + return one(τ) + else + return Roots.find_zero(θ -> _joe_tau_f(θ) - τ, (one(τ),τ*Inf)) + end +end williamson_dist(C::JoeCopula{d,T}) where {d,T} = WilliamsonFromFrailty(Sibuya(1/C.θ), d) diff --git a/src/Copula.jl b/src/Copula.jl index b54b6974..e4edc54c 100644 --- a/src/Copula.jl +++ b/src/Copula.jl @@ -9,10 +9,13 @@ Base.length(::Copula{d}) where d = d # Base.eltype # τ, τ⁻¹ # Base.eltype -function Distributions.cdf(C::Copula{d},u) where d +function Distributions.cdf(C::Copula{d},u::AbstractVector) where d length(u) != length(C) && throw(ArgumentError("Dimension mismatch between copula and input vector")) return _cdf(C,u) end +function Distributions.cdf(C::Copula{d},A::AbstractMatrix) where d + return [Distributions.cdf(C,u) for u in eachcol(A)] +end function _cdf(C::CT,u) where {CT<:Copula} f(x) = Distributions.pdf(C,x) z = zeros(eltype(u),length(C)) diff --git a/src/MiscellaneousCopulas/MCopula.jl b/src/MiscellaneousCopulas/MCopula.jl index 0f53a9c5..463fb3ce 100644 --- a/src/MiscellaneousCopulas/MCopula.jl +++ b/src/MiscellaneousCopulas/MCopula.jl @@ -18,6 +18,9 @@ The two Frechet-Hoeffding bounds are also Archimedean copulas, although this lin struct MCopula{d} <: Copula{d} end MCopula(d) = MCopula{d}() _cdf(::MCopula{d},u) where {d} = minimum(u) +function Distributions._logpdf(C::MCopula, u) + return all(u == u[1]) ? zero(eltype(u)) : eltype(u)(-Inf) +end function Distributions._rand!(rng::Distributions.AbstractRNG, ::MCopula{d}, x::AbstractVector{T}) where {d,T<:Real} x .= rand(rng) end diff --git a/src/MiscellaneousCopulas/WCopula.jl b/src/MiscellaneousCopulas/WCopula.jl index 2e0f2e62..a529de83 100644 --- a/src/MiscellaneousCopulas/WCopula.jl +++ b/src/MiscellaneousCopulas/WCopula.jl @@ -16,6 +16,10 @@ The two Frechet-Hoeffding bounds are also Archimedean copulas, although this lin struct WCopula{d} <: Copula{d} end WCopula(d) = WCopula{d}() _cdf(::WCopula{d},u) where {d} = max(1 + sum(u)-d,0) +function Distributions._logpdf(C::WCopula{d}, u) where {d} + @assert d == 2 + return u[1] + u[2] == 1 ? zero(eltype(u)) : eltype(u)(-Inf) +end function Distributions._rand!(rng::Distributions.AbstractRNG, ::WCopula{d}, x::AbstractVector{T}) where {d,T<:Real} @assert d==2 x[1] = rand(rng) diff --git a/test/archimedean_tests.jl b/test/archimedean_tests.jl index f142d7f6..3f51f2d7 100644 --- a/test/archimedean_tests.jl +++ b/test/archimedean_tests.jl @@ -1,8 +1,6 @@ @testitem "Test of τ ∘ τ_inv bijection" begin using Random - using StableRNGs - rng = StableRNG(123) taus = [0.0, 0.1, 0.5, 0.9, 1.0] for T in ( @@ -21,79 +19,138 @@ end end +# For each archimedean, we test: +# - The sampling of a dataset +# - evaluation of pdf and cdf +# - fitting on the dataset. -@testitem "AMHCopula - Test sampling all cases" begin +@testitem "AMHCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) for d in 2:10 for θ ∈ [-1.0,-rand(rng),0.0,rand(rng)] - rand(rng,AMHCopula(d,θ),10) + C = AMHCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + fit(AMHCopula,data) end end @test true end -@testitem "ClaytonCopula - Test sampling all cases" begin +@testitem "ClaytonCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) - rand(rng,ClaytonCopula(2,-1),10) + 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) for d in 2:10 for θ ∈ [-1/(d-1) * rand(rng),0.0,-log(rand(rng)), Inf] - rand(rng,ClaytonCopula(d,θ),10) + C = ClaytonCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + fit(ClaytonCopula,data) end end @test true end -@testitem "FrankCopula - Test sampling all cases" begin +@testitem "FrankCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) 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) + + 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) + + for d in 2:10 for θ ∈ [0.0,rand(rng),1.0,-log(rand(rng)), Inf] - rand(rng,FrankCopula(d,θ),10) + C = FrankCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + @test_broken fit(FrankCopula,data) end end @test true end -@testitem "GumbelCopula - Test sampling all cases" begin +@testitem "GumbelCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) for d in 2:10 for θ ∈ [1.0,1-log(rand(rng)), Inf] - rand(rng,GumbelCopula(d,θ),10) + @show d,θ + C = GumbelCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + fit(GumbelCopula,data) end end @test true end -@testitem "JoeCopula - Test sampling all cases" begin +@testitem "JoeCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) for d in 2:10 for θ ∈ [1.0,1-log(rand(rng)), Inf] - rand(rng,JoeCopula(d,θ),10) + C = JoeCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + fit(JoeCopula,data) end end @test true end -@testitem "GumbelBarnettCopula - Test sampling all cases" begin +@testitem "GumbelBarnettCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) for d in 2:10 for θ ∈ [0.0,rand(rng),1.0] - rand(rng,GumbelBarnettCopula(d,θ),10) + C = GumbelBarnettCopula(d,θ) + data = rand(rng,C,100) + @test all(pdf(C,data) .>= 0) + @test all(0 .<= cdf(C,data) .<= 1) + @test_broken fit(GumbelBarnettCopula,data) end end @test true end -@testitem "InvGaussianCopula - Test sampling all cases" begin +@testitem "InvGaussianCopula - sampling,pdf,cdf,fit" begin using StableRNGs + using Distributions rng = StableRNG(123) for d in 2:10 - for θ ∈ [rand(rng),1.0, Inf] - rand(rng,InvGaussianCopula(d,θ),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) end end @test true -end \ No newline at end of file +end