Skip to content

Commit

Permalink
Add automatic tests (#93)
Browse files Browse the repository at this point in the history
Also fixes a few bugs
  • Loading branch information
lrnv authored Dec 4, 2023
1 parent 6a27243 commit 0cf206a
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 55 deletions.
30 changes: 15 additions & 15 deletions src/ArchimedeanCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,27 +93,26 @@ end
# 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
r = rand(rng,R)
sx = sum(x)
for i in 1:d
x[i] = ϕ(C,r * x[i]/sx)
end
end

function Distributions._rand!(rng::Distributions.AbstractRNG, C::CT, x::AbstractVector{T}) where {T<:Real, CT<:ArchimedeanCopula}
# By default, we use the williamson sampling.
Random.randexp!(rng,x)
_archi_rand!(rng,C,williamson_dist(C),x)
r = rand(rng,williamson_dist(C))
sx = sum(x)
for i in 1:length(C)
x[i] = ϕ(C,r * x[i]/sx)
end
return x
end
function Distributions._rand!(rng::Distributions.AbstractRNG, C::CT, A::DenseMatrix{T}) where {T<:Real, CT<:ArchimedeanCopula}
# More efficient version that precomputes the williamson transform on each call to sample in batches:
Random.randexp!(rng,A)
R = williamson_dist(C)
for i in 1:size(A,2)
_archi_rand!(rng,C,R,view(A,:,i))
n = size(A,2)
r = rand(rng,williamson_dist(C),n)
for i in 1:n
sx = sum(A[:,i])
for j in 1:length(C)
A[j,i] = ϕ(C,r[i] * A[j,i]/sx)
end
end
return A
end
Expand All @@ -123,8 +122,9 @@ function Distributions.fit(::Type{CT},u) where {CT <: ArchimedeanCopula}
τ = StatsBase.corkendall(u')
# Then the off-diagonal elements of the matrix should be averaged:
avgτ = (sum(τ) .- d) / (d^2-d)
θ = τ⁻¹(CT,avgτ)
return CT(d,θ)
GT = generatorof(CT)
θ = τ⁻¹(GT,avgτ)
return ArchimedeanCopula(d,GT(θ))
end

τ(C::ArchimedeanCopula{d,TG}) where {d,TG} = τ(C.G)
Expand Down
8 changes: 4 additions & 4 deletions src/Generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ function ϕ⁽ᵏ⁾(G::Generator, k, t)
end
williamson_dist(G::Generator, d) = WilliamsonTransforms.𝒲₋₁(t -> ϕ(G,t),d)

τ(G::Generator) = @error("This generator has no kendall tau implemented.")
ρ(G::Generator) = @error ("This generator has no Spearman rho implemented.")
τ⁻¹(G::Generator, τ_val) = @error("This generator has no inverse kendall tau implemented.")
ρ⁻¹(G::Generator, ρ_val) = @error ("This generator has no inverse Spearman rho implemented.")
# τ(G::Generator) = @error("This generator has no kendall tau implemented.")
# ρ(G::Generator) = @error ("This generator has no Spearman rho implemented.")
# τ⁻¹(G::Generator, τ_val) = @error("This generator has no inverse kendall tau implemented.")
# ρ⁻¹(G::Generator, ρ_val) = @error ("This generator has no inverse Spearman rho implemented.")


abstract type UnivariateGenerator <: Generator end
Expand Down
12 changes: 10 additions & 2 deletions src/Generator/WilliamsonGenerator.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""
WilliamsonGenerator{TX}
i𝒲{TX}
Fields:
* `X::TX` -- a random variable that represents its williamson d-transform
Expand All @@ -8,8 +9,9 @@ Fields:
Constructor
WilliamsonGenerator(X::Distributions.UnivariateDistribution, d)
i𝒲(X::Distributions.UnivariateDistribution,d)
The `WilliamsonGenerator` allows to construct a d-monotonous archimedean generator from a positive random variable `X::Distributions.UnivariateDistribution`. The transformation, wich is called the inverse williamson transformation, is implemented in [WilliamsonTransforms.jl](https://www.github.com/lrnv/WilliamsonTransforms.jl).
The `WilliamsonGenerator` (alias `i𝒲`) allows to construct a d-monotonous archimedean generator from a positive random variable `X::Distributions.UnivariateDistribution`. The transformation, wich is called the inverse williamson transformation, is implemented in [WilliamsonTransforms.jl](https://www.github.com/lrnv/WilliamsonTransforms.jl).
For a univariate non-negative random variable ``X``, with cumulative distribution function ``F`` and an integer ``d\\ge 2``, the Williamson-d-transform of ``X`` is the real function supported on ``[0,\\infty[`` given by:
Expand Down Expand Up @@ -45,5 +47,11 @@ struct WilliamsonGenerator{TX} <: Generator
end
const i𝒲 = WilliamsonGenerator
max_monotony(G::WilliamsonGenerator) = G.d
williamson_dist(G::WilliamsonGenerator) = G.X
function williamson_dist(G::WilliamsonGenerator, d)
if d == G.d
return G.X
end
# what about d < G.d ? Mayeb we can do some frailty stuff ?
return WilliamsonTransforms.𝒲₋₁(t -> ϕ(G,t),d)
end
ϕ(G::WilliamsonGenerator, t) = WilliamsonTransforms.𝒲(G.X,G.d)(t)
6 changes: 3 additions & 3 deletions src/MiscellaneousCopulas/EmpiricalCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ struct EmpiricalCopula{d,MT} <: Copula{d}
u::MT
end
Base.eltype(C::EmpiricalCopula{d,MT}) where {d,MT} = Base.eltype(C.u)
function EmpiricalCopula(u;pseudos=true)
function EmpiricalCopula(u;pseudo_values=true)
d = size(u,1)
if !pseudos
if !pseudo_values
u = pseudos(u)
else
@assert all(0 .<= u .<= 1)
end
return EmpiricalCopula{d,typeof(u)}(u)
end
function _cdf(C::EmpiricalCopula{d,MT},u) where {d,MT}
return mean(all(C.u .<= u,dims=1)) # might not be very efficient implementation.
return StatsBase.mean(all(C.u .<= u,dims=1)) # might not be very efficient implementation.
end
function Distributions._rand!(rng::Distributions.AbstractRNG, C::EmpiricalCopula{d,MT}, x::AbstractVector{T}) where {d,MT,T<:Real}
x .= C.u[:,Distributions.rand(rng,axes(C.u,2),1)[1]]
Expand Down
10 changes: 9 additions & 1 deletion src/MiscellaneousCopulas/RafteryCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ struct RafteryCopula{d, P} <: Copula{d}
end
Base.eltype(R::RafteryCopula) = eltype(R.θ)

function _cdf(R::RafteryCopula{d,P}, u::Vector{T}) where {d,P,T}
function _cdf(R::RafteryCopula{d,P}, u) where {d,P}

if any(iszero,u)
return zero(u[1])
end
if all(isone,u)
return one(u[1])
end

# Order the vector u
u_ordered = sort(u)

Expand Down
11 changes: 10 additions & 1 deletion src/MiscellaneousCopulas/SurvivalCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,16 @@ function reverse(u,idx)
reverse!(v,idx)
return v
end
_cdf(C::SurvivalCopula{d,CT,VI},u) where {d,CT,VI} = _cdf(C.C,reverse(u,C.indices))
function _cdf(C::SurvivalCopula{d,CT,VI},u) where {d,CT,VI}
i = C.indices[end]
newC = SurvivalCopula(C.C,C.indices[1:end-1])
v = deepcopy(u)
v[i] = 1 - v[i]
r2 = _cdf(newC,v)
v[i] = 1
r1 = _cdf(newC,v)
return r1 - r2
end
Distributions._logpdf(C::SurvivalCopula{d,CT,VI},u) where {d,CT,VI} = Distributions._logpdf(C.C,reverse(u,C.indices))
function Distributions._rand!(rng::Distributions.AbstractRNG, C::SurvivalCopula{d,CT,VI}, x::AbstractVector{T}) where {d,CT,VI,T}
Distributions._rand!(rng,C.C,x)
Expand Down
14 changes: 7 additions & 7 deletions test/archimedean_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
for d in 2:10
for d in 2:5
for θ [-1.0,-rand(rng),0.0,rand(rng)]
C = AMHCopula(d,θ)
data = rand(rng,C,100)
Expand All @@ -181,7 +181,7 @@ end
@test all(pdf(C0,data0) .>= 0)
@test all(0 .<= cdf(C0,data0) .<= 1)
fit(ClaytonCopula,data0)
for d in 2:10
for d in 2:5
for θ [-1/(d-1) * rand(rng),0.0,-log(rand(rng)), Inf]
C = ClaytonCopula(d,θ)
data = rand(rng,C,100)
Expand Down Expand Up @@ -212,7 +212,7 @@ end
fit(FrankCopula,data1)


for d in 2:10
for d in 2:5
for θ [1.0,1-log(rand(rng)), Inf]
C = FrankCopula(d,θ)
data = rand(rng,C,10000)
Expand All @@ -227,7 +227,7 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
for d in 2:10
for d in 2:5
for θ [1.0,1-log(rand(rng)), Inf]
C = GumbelCopula(d,θ)
data = rand(rng,C,100)
Expand All @@ -242,7 +242,7 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
for d in 2:10
for d in 2:5
for θ [1.0,1-log(rand(rng)), Inf]
C = JoeCopula(d,θ)
data = rand(rng,C,100)
Expand All @@ -258,7 +258,7 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
for d in 2:10
for d in 2:5
for θ [0.0,rand(rng),1.0]
C = GumbelBarnettCopula(d,θ)
data = rand(rng,C,100)
Expand All @@ -274,7 +274,7 @@ end
using StableRNGs
using Distributions
rng = StableRNG(123)
for d in 2:10
for d in 2:5
for θ [rand(rng),1.0, -log(rand(rng))]
C = InvGaussianCopula(d,θ)
data = rand(rng,C,100)
Expand Down
Loading

0 comments on commit 0cf206a

Please sign in to comment.