From 8f927ce48d2853bf0f5e675f9b7d1eeb190df234 Mon Sep 17 00:00:00 2001 From: Jack Li Date: Tue, 19 Sep 2023 15:53:34 -0400 Subject: [PATCH 01/13] add loglogistic distribution --- docs/Project.toml | 1 + docs/src/univariate.md | 11 +- src/Distributions.jl | 3 +- src/univariate/continuous/loglogistic.jl | 142 ++++++++++++++++++++++ src/univariates.jl | 1 + test/runtests.jl | 2 + test/univariate/continuous/loglogistic.jl | 25 ++++ 7 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 src/univariate/continuous/loglogistic.jl create mode 100644 test/univariate/continuous/loglogistic.jl diff --git a/docs/Project.toml b/docs/Project.toml index 540725c6d6..2ef490a4d9 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,5 @@ [deps] +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" diff --git a/docs/src/univariate.md b/docs/src/univariate.md index 0b2c48c6ea..62c53dd644 100644 --- a/docs/src/univariate.md +++ b/docs/src/univariate.md @@ -320,10 +320,17 @@ plotdensity((0, 20), Lindley, (1.5,)) # hide ``` ```@docs -Logistic +logistic ``` ```@example plotdensity -plotdensity((-4, 8), Logistic, (2, 1)) # hide +plotdensity((-4, 8), logistic, (2, 1)) # hide +``` + +```@docs +loglogistic +``` +```@example plotdensity +plotdensity((0, 2), logistic, (1, 1)) # hide ``` ```@docs diff --git a/src/Distributions.jl b/src/Distributions.jl index 1e2d580c55..39b3ec1196 100644 --- a/src/Distributions.jl +++ b/src/Distributions.jl @@ -120,6 +120,7 @@ export LKJCholesky, LocationScale, Logistic, + LogLogistic, LogNormal, LogUniform, LogitNormal, @@ -356,7 +357,7 @@ Supported distributions: InverseWishart, InverseGamma, InverseGaussian, IsoNormal, IsoNormalCanon, JohnsonSU, Kolmogorov, KSDist, KSOneSided, Kumaraswamy, Laplace, Levy, Lindley, LKJ, LKJCholesky, - Logistic, LogNormal, MatrixBeta, MatrixFDist, MatrixNormal, + Logistic, LogLogistic, LogNormal, MatrixBeta, MatrixFDist, MatrixNormal, MatrixTDist, MixtureModel, Multinomial, MultivariateNormal, MvLogNormal, MvNormal, MvNormalCanon, MvNormalKnownCov, MvTDist, NegativeBinomial, NoncentralBeta, NoncentralChisq, diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl new file mode 100644 index 0000000000..806ede8808 --- /dev/null +++ b/src/univariate/continuous/loglogistic.jl @@ -0,0 +1,142 @@ +""" + LogLogistic(θ, ϕ) + +The *log logistic distribution* with scale `θ` and shape `ϕ` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. +If ``X \\sim \\operatorname{Logistic}(\\theta, \\phi)`` then ``exp(X) \\sim \\operatorname{LogLogistic}(\\theta, \\phi)``. The probability density function is + +```math +f(x; \\theta, \\phi) = \\frac{(\\phi / \\theta)x/\\theta()^(\\phi - 1)}{(1 + (x/\\theta)^\\phi)^2}, \\theta > 0, \\phi > 0 +``` + +```julia +LogLogistic() # Log-logistic distribution with unit scale and unit shape +LogLogistic(θ) # Log-logistic distribution with scale θ and unit shape +LogLogistic(θ,ϕ) # Log-logistic distribution with scale θ and shape ϕ + +params(d) # Get the parameters, i.e. (θ, ϕ) +scale(d) # Get the scale parameter, i.e. θ +shape(d) # Get the shape parameter, i.e. ϕ +``` + +External links + +* [Log logistic distribution on Wikipedia](https://en.wikipedia.org/wiki/Log-logistic_distribution) + +""" + + +struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution + θ::T + ϕ::T + LogLogistic{T}(θ::T,ϕ::T) where {T} = new{T}(θ,ϕ) +end + +function LogLogistic(θ::T, ϕ::T; check_args=true) where {T <: Real} + check_args && @check_args(LogLogistic, θ > zero(θ) && ϕ > zero(ϕ)) + return LogLogistic{T}(θ, ϕ) +end + +LogLogistic(θ::Real, ϕ::Real) = LogLogistic(promote(θ,ϕ)...) +LogLogistic(θ::Integer, ϕ::Integer) = LogLogistic(float(θ), float(ϕ)) +LogLogistic(θ::T) where {T<:Real} = LogLogistic(θ, 1.0) +LogLogistic() = LogLogistic(1.0, 1.0, check_args=false) + +@distr_support LogLogistic 0.0 Inf + +#### Coversions +convert(::Type{LogLogistic{T}}, θ::S, ϕ::S) where {T <: Real, S <: Real} = LogLogistic(T(θ), T(ϕ)) +convert(::Type{LogLogistic{T}}, d::LogLogistic{S}) where {T <: Real, S <: Real} = LogLogistic(T(d.θ), T(d.ϕ), check_args=false) + +#### Parameters + +params(d::LogLogistic) = (d.θ, d.ϕ) +partype(::LogLogistic{T}) where {T} = T + +#### Statistics + +median(d::LogLogistic) = d.θ +function mean(d::LogLogistic) + if d.ϕ ≤ 1 + error("mean is defined only when ϕ > 1") + end + return d.θ*π/d.ϕ/sin(π/d.ϕ) +end + +function mode(d::LogLogistic) + if d.ϕ ≤ 1 + error("mode is defined only when ϕ > 1") + end + return d.θ*((d.ϕ-1)/(d.ϕ+1))^(1/d.ϕ) +end + +function var(d::LogLogistic) + if d.ϕ ≤ 2 + erros("var is defined only when ϕ > 2") + end + b = π/d.ϕ + return d.θ^2 * (2*b/sin(2*b)-b^2/(sin(b))^2) +end + + +#### Evaluation +function pdf(d::LogLogistic, x::Real) + if x ≤ zero(0) + z = zero(x) + else + # use built-in impletation to evaluate the density + # of loglogistic at x + # Y = log(X) + # Y ~ logistic(log(θ), 1/ϕ) + z = pdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) / x + end + return z +end + +function logpdf(d::LogLogistic, x::Real) + if x ≤ zero(0) + z = log(zero(x)) + else + z = logpdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + log(x) + end + return z +end + +function cdf(d::LogLogistic, x::Real) + if x <= 0 + return 0.0 + end + z = cdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + return z +end + +function logcdf(d::LogLogistic, x::Real) + if x <= 0 + -Inf + end + z = logcdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + return z +end + +function ccdf(d::LogLogistic, x::Real) + if x <= 0 + return 1 + end + z = ccdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + return z +end + +function logccdf(d::LogLogistic, x::Real) + if x <= 0 + return 0.0 + end + z = logccdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + return z +end + + +#### Sampling +function rand(rng::AbstractRNG, d::LogLogistic) + u = rand(rng) + r = u / (1 - u) + return r^(1/d.ϕ)*d.θ +end diff --git a/src/univariates.jl b/src/univariates.jl index 0e2ae05e32..5a5299c209 100644 --- a/src/univariates.jl +++ b/src/univariates.jl @@ -697,6 +697,7 @@ const continuous_distributions = [ "levy", "lindley", "logistic", + "loglogistic", "noncentralbeta", "noncentralchisq", "noncentralf", diff --git a/test/runtests.jl b/test/runtests.jl index cb724278b4..236438c387 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,7 @@ import JSON import ForwardDiff const tests = [ + "univariate/continuous/loglogistic", "univariate/continuous/loguniform", "univariate/continuous/arcsine", "univariate/discrete/dirac", @@ -77,6 +78,7 @@ const tests = [ "univariate/continuous/gumbel", "univariate/continuous/lindley", "univariate/continuous/logistic", + # "univariate/continuous/loglogistic", "univariate/continuous/johnsonsu", "univariate/continuous/noncentralchisq", "univariate/continuous/weibull", diff --git a/test/univariate/continuous/loglogistic.jl b/test/univariate/continuous/loglogistic.jl new file mode 100644 index 0000000000..1aa89efbb9 --- /dev/null +++ b/test/univariate/continuous/loglogistic.jl @@ -0,0 +1,25 @@ +using Distributions +using Test + +@testset "LogLogistic" begin + + @test round(pdf(LogLogistic(), -1), digits=6) == 0.0 + @test round(pdf(LogLogistic(), 1), digits=6) == 0.25 + @test round(pdf(LogLogistic(2), 2), digits=6) == 0.125 + @test round(pdf(LogLogistic(2,2), 1), digits=6) == 0.32 + + @test round(cdf(LogLogistic(), -1), digits=6) == 0.0 + @test round(cdf(LogLogistic(), 1), digits=6) == 0.5 + @test round(cdf(LogLogistic(2), 3), digits=6) == 0.6 + @test round(cdf(LogLogistic(2,2), 4), digits=6) == 0.8 + + @test round(ccdf(LogLogistic(2), 3), digits=6) == 0.4 + @test round(ccdf(LogLogistic(2,2), 4), digits=6) == 0.2 + + @test round(logpdf(LogLogistic(), -1), digits=6) == -Inf + @test round(logpdf(LogLogistic(), 1), digits=6) == -1.386294 + + @test round(logcdf(LogLogistic(2,2), 4), digits=6) == -0.223144 + @test round(logccdf(LogLogistic(2,2), 4), digits=6) == -1.609438 + +end \ No newline at end of file From bf205c5c93333a4b930da34a025e5008bcfb306e Mon Sep 17 00:00:00 2001 From: Jack Li Date: Tue, 10 Oct 2023 15:11:29 -0700 Subject: [PATCH 02/13] resolve issues --- src/univariate/continuous/loglogistic.jl | 131 +++++++++-------------- 1 file changed, 50 insertions(+), 81 deletions(-) diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 806ede8808..557e3194ab 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -1,136 +1,105 @@ """ - LogLogistic(θ, ϕ) + LogLogistic(α, β) -The *log logistic distribution* with scale `θ` and shape `ϕ` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. -If ``X \\sim \\operatorname{Logistic}(\\theta, \\phi)`` then ``exp(X) \\sim \\operatorname{LogLogistic}(\\theta, \\phi)``. The probability density function is +The *log logistic distribution* with scale `α` and shape `β` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. +If ``X \\sim \\operatorname{LogLogistic}(\\alpha, \\beta)`` then ``log(X) \\sim \\operatorname{Logistic}(log(\\alpha), 1/\\beta)``. The probability density function is ```math -f(x; \\theta, \\phi) = \\frac{(\\phi / \\theta)x/\\theta()^(\\phi - 1)}{(1 + (x/\\theta)^\\phi)^2}, \\theta > 0, \\phi > 0 +f(x; \\alpha, \\beta) = \\frac{(\\alpha / \\beta)x/\\beta()^(\\alpha - 1)}{(1 + (x/\\beta)^\\alpha)^2}, \\beta > 0, \\alpha > 0 ``` ```julia LogLogistic() # Log-logistic distribution with unit scale and unit shape -LogLogistic(θ) # Log-logistic distribution with scale θ and unit shape -LogLogistic(θ,ϕ) # Log-logistic distribution with scale θ and shape ϕ +LogLogistic(α,β) # Log-logistic distribution with scale α and shape β -params(d) # Get the parameters, i.e. (θ, ϕ) -scale(d) # Get the scale parameter, i.e. θ -shape(d) # Get the shape parameter, i.e. ϕ +params(d) # Get the parameters, i.e. (α, β) +scale(d) # Get the scale parameter, i.e. α +shape(d) # Get the shape parameter, i.e. β ``` External links * [Log logistic distribution on Wikipedia](https://en.wikipedia.org/wiki/Log-logistic_distribution) - """ - struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution - θ::T - ϕ::T - LogLogistic{T}(θ::T,ϕ::T) where {T} = new{T}(θ,ϕ) + α::T + β::T + LogLogistic{T}(α::T,β::T) where {T} = new{T}(α,β) end -function LogLogistic(θ::T, ϕ::T; check_args=true) where {T <: Real} - check_args && @check_args(LogLogistic, θ > zero(θ) && ϕ > zero(ϕ)) - return LogLogistic{T}(θ, ϕ) +function LogLogistic(α::T, β::T; check_args=true) where {T <: Real} + check_args && @check_args(LogLogistic, α > zero(α) && β > zero(β)) + return LogLogistic{T}(α, β) end -LogLogistic(θ::Real, ϕ::Real) = LogLogistic(promote(θ,ϕ)...) -LogLogistic(θ::Integer, ϕ::Integer) = LogLogistic(float(θ), float(ϕ)) -LogLogistic(θ::T) where {T<:Real} = LogLogistic(θ, 1.0) +LogLogistic(α::Real, β::Real) = LogLogistic(promote(α,β)...) +LogLogistic(α::Integer, β::Integer) = LogLogistic(float(α), float(β)) LogLogistic() = LogLogistic(1.0, 1.0, check_args=false) @distr_support LogLogistic 0.0 Inf #### Coversions -convert(::Type{LogLogistic{T}}, θ::S, ϕ::S) where {T <: Real, S <: Real} = LogLogistic(T(θ), T(ϕ)) -convert(::Type{LogLogistic{T}}, d::LogLogistic{S}) where {T <: Real, S <: Real} = LogLogistic(T(d.θ), T(d.ϕ), check_args=false) - +convert(::Type{LogLogistic{T}}, d::LogLogistic{T}) where {T<:Real} = d +convert(::Type{LogLogistic{T}}, d::LogLogistic) where {T<:Real} = LogLogistic{T}(T(d.α), T(d.β)) #### Parameters -params(d::LogLogistic) = (d.θ, d.ϕ) +params(d::LogLogistic) = (d.α, d.β) partype(::LogLogistic{T}) where {T} = T #### Statistics -median(d::LogLogistic) = d.θ -function mean(d::LogLogistic) - if d.ϕ ≤ 1 - error("mean is defined only when ϕ > 1") +median(d::LogLogistic) = d.α +function mean(d::LogLogistic{T}) where T<:Real + if d.β ≤ 1 + ArgumentError("mean is defined only when β > 1") end - return d.θ*π/d.ϕ/sin(π/d.ϕ) + return d.α*π/d.β/sin(π/d.β) end -function mode(d::LogLogistic) - if d.ϕ ≤ 1 - error("mode is defined only when ϕ > 1") +function mode(d::LogLogistic{T}) where T<:Real + if d.β ≤ 1 + ArgumentError("mode is defined only when β > 1") end - return d.θ*((d.ϕ-1)/(d.ϕ+1))^(1/d.ϕ) + return d.α*((d.β-1)/(d.β+1))^(1/d.β) end -function var(d::LogLogistic) - if d.ϕ ≤ 2 - erros("var is defined only when ϕ > 2") +function var(d::LogLogistic{T}) where T<:Real + if d.β ≤ 2 + ArgumentError("var is defined only when β > 2") end - b = π/d.ϕ - return d.θ^2 * (2*b/sin(2*b)-b^2/(sin(b))^2) + b = π/d.β + return d.α^2 * (2*b/sin(2*b)-b^2/(sin(b))^2) end #### Evaluation -function pdf(d::LogLogistic, x::Real) - if x ≤ zero(0) - z = zero(x) - else - # use built-in impletation to evaluate the density - # of loglogistic at x - # Y = log(X) - # Y ~ logistic(log(θ), 1/ϕ) - z = pdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) / x - end - return z +function pdf(d::LogLogistic{T}, x::Real) where T<:Real + # use built-in impletation to evaluate the density + # of loglogistic at x + # Y = log(X) + # Y ~ logistic(log(θ), 1/ϕ) + x >= 0 ? pdf(Logistic(log(d.α), 1/d.β), log(x)) / x : zero(T) end -function logpdf(d::LogLogistic, x::Real) - if x ≤ zero(0) - z = log(zero(x)) - else - z = logpdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) + log(x) - end - return z +function logpdf(d::LogLogistic{T}, x::Real) where T<:Real + x >= 0 ? logpdf(Logistic(log(d.α), 1/d.β), log(x)) + log(x) : -T(Inf) end -function cdf(d::LogLogistic, x::Real) - if x <= 0 - return 0.0 - end - z = cdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) - return z +function cdf(d::LogLogistic{T}, x::Real) where T<:Real + x >= 0 ? cdf(Logistic(log(d.α), 1/d.β), log(x)) : zero(T) end -function logcdf(d::LogLogistic, x::Real) - if x <= 0 - -Inf - end - z = logcdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) - return z +function logcdf(d::LogLogistic{T}, x::Real) where T<:Real + x >= 0 ? logcdf(Logistic(log(d.α), 1/d.β), log(x)) : -T(Inf) end -function ccdf(d::LogLogistic, x::Real) - if x <= 0 - return 1 - end - z = ccdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) - return z +function ccdf(d::LogLogistic{T}, x::Real) where T<:Real + x >= 0 ? ccdf(Logistic(log(d.α), 1/d.β), log(x)) : one(T) end -function logccdf(d::LogLogistic, x::Real) - if x <= 0 - return 0.0 - end - z = logccdf(Logistic(log(d.θ), 1/d.ϕ), log(x)) - return z +function logccdf(d::LogLogistic{T}, x::Real) where T<:Real + x >= 0 ? logccdf(Logistic(log(d.α), 1/d.β), log(x)) : zero(T) end @@ -138,5 +107,5 @@ end function rand(rng::AbstractRNG, d::LogLogistic) u = rand(rng) r = u / (1 - u) - return r^(1/d.ϕ)*d.θ + return r^(1/d.β)*d.α end From bcc781993046d08003eaabd29d5dc24ceb501534 Mon Sep 17 00:00:00 2001 From: Jack Li Date: Tue, 10 Oct 2023 15:31:25 -0700 Subject: [PATCH 03/13] using sinc --- src/univariate/continuous/loglogistic.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 557e3194ab..41e982c713 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -54,7 +54,7 @@ function mean(d::LogLogistic{T}) where T<:Real if d.β ≤ 1 ArgumentError("mean is defined only when β > 1") end - return d.α*π/d.β/sin(π/d.β) + return d.α/sinc(1/d.β) end function mode(d::LogLogistic{T}) where T<:Real From 025c8a8a1825a97f60f0abb1fa145bf234c8fd2e Mon Sep 17 00:00:00 2001 From: Jack Li Date: Wed, 18 Oct 2023 21:16:11 -0700 Subject: [PATCH 04/13] remove tests with a single argument --- test/univariate/continuous/loglogistic.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/univariate/continuous/loglogistic.jl b/test/univariate/continuous/loglogistic.jl index 1aa89efbb9..962b0e830e 100644 --- a/test/univariate/continuous/loglogistic.jl +++ b/test/univariate/continuous/loglogistic.jl @@ -5,15 +5,12 @@ using Test @test round(pdf(LogLogistic(), -1), digits=6) == 0.0 @test round(pdf(LogLogistic(), 1), digits=6) == 0.25 - @test round(pdf(LogLogistic(2), 2), digits=6) == 0.125 @test round(pdf(LogLogistic(2,2), 1), digits=6) == 0.32 @test round(cdf(LogLogistic(), -1), digits=6) == 0.0 @test round(cdf(LogLogistic(), 1), digits=6) == 0.5 - @test round(cdf(LogLogistic(2), 3), digits=6) == 0.6 @test round(cdf(LogLogistic(2,2), 4), digits=6) == 0.8 - @test round(ccdf(LogLogistic(2), 3), digits=6) == 0.4 @test round(ccdf(LogLogistic(2,2), 4), digits=6) == 0.2 @test round(logpdf(LogLogistic(), -1), digits=6) == -Inf From c82b8b8e32a2406f25af09f953e7093d3fd289e7 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Fri, 17 Oct 2025 05:30:45 -0600 Subject: [PATCH 05/13] Fix docs --- docs/Project.toml | 3 +++ docs/src/univariate.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index c7a818b0ba..9d866dbf41 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -6,3 +6,6 @@ GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" [compat] Documenter = "1" GR = "0.72.1, 0.73" + +[sources.Distributions] +path = ".." diff --git a/docs/src/univariate.md b/docs/src/univariate.md index 78ed16d4a2..ed6b1c9484 100644 --- a/docs/src/univariate.md +++ b/docs/src/univariate.md @@ -320,17 +320,17 @@ plotdensity((0, 20), Lindley, (1.5,)) # hide ``` ```@docs -logistic +Logistic ``` ```@example plotdensity -plotdensity((-4, 8), logistic, (2, 1)) # hide +plotdensity((-4, 8), Logistic, (2, 1)) # hide ``` ```@docs -loglogistic +LogLogistic ``` ```@example plotdensity -plotdensity((0, 2), logistic, (1, 1)) # hide +plotdensity((0, 2), LogLogistic, (1, 1)) # hide ``` ```@docs From 4afe62616e13db6823ddd392df46e0ce5c223679 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Fri, 17 Oct 2025 05:31:07 -0600 Subject: [PATCH 06/13] Simplify test/runtests.jl diff --- test/runtests.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 72282890bd..39b5e65aac 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,7 +12,6 @@ import ForwardDiff const tests = [ "aqua", - "univariate/continuous/loglogistic", "univariate/continuous/loguniform", "univariate/continuous/arcsine", "univariate/discrete/dirac", @@ -82,7 +81,7 @@ const tests = [ "univariate/continuous/gumbel", "univariate/continuous/lindley", "univariate/continuous/logistic", - # "univariate/continuous/loglogistic", + "univariate/continuous/loglogistic", "univariate/continuous/johnsonsu", "univariate/continuous/noncentralchisq", "univariate/continuous/weibull", From 28d0b37a8534c6506fcbdc80f0543dd246d56043 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Fri, 17 Oct 2025 17:59:01 -0600 Subject: [PATCH 07/13] Fixes --- src/univariate/continuous/loglogistic.jl | 90 +++++++++++++----------- test/ref/continuous/loglogistic.R | 38 ++++++++++ test/ref/readme.md | 2 +- 3 files changed, 89 insertions(+), 41 deletions(-) create mode 100644 test/ref/continuous/loglogistic.R diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 41e982c713..952fc069d7 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -1,7 +1,7 @@ """ LogLogistic(α, β) -The *log logistic distribution* with scale `α` and shape `β` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. +The *log-logistic distribution* with scale `α` and shape `β` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. If ``X \\sim \\operatorname{LogLogistic}(\\alpha, \\beta)`` then ``log(X) \\sim \\operatorname{Logistic}(log(\\alpha), 1/\\beta)``. The probability density function is ```math @@ -21,7 +21,6 @@ External links * [Log logistic distribution on Wikipedia](https://en.wikipedia.org/wiki/Log-logistic_distribution) """ - struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution α::T β::T @@ -42,70 +41,81 @@ LogLogistic() = LogLogistic(1.0, 1.0, check_args=false) #### Coversions convert(::Type{LogLogistic{T}}, d::LogLogistic{T}) where {T<:Real} = d convert(::Type{LogLogistic{T}}, d::LogLogistic) where {T<:Real} = LogLogistic{T}(T(d.α), T(d.β)) -#### Parameters +#### Parameters params(d::LogLogistic) = (d.α, d.β) partype(::LogLogistic{T}) where {T} = T #### Statistics median(d::LogLogistic) = d.α -function mean(d::LogLogistic{T}) where T<:Real - if d.β ≤ 1 - ArgumentError("mean is defined only when β > 1") +function mean(d::LogLogistic) + (; α, β) = d + if !(β > 1) + ArgumentError("the mean of a log-logistic distribution is defined only when its shape β > 1") end - return d.α/sinc(1/d.β) + return α/sinc(inv(β)) end -function mode(d::LogLogistic{T}) where T<:Real - if d.β ≤ 1 - ArgumentError("mode is defined only when β > 1") - end - return d.α*((d.β-1)/(d.β+1))^(1/d.β) +function mode(d::LogLogistic) + (; α, β) = d + return α*(max(β - 1, 0) / (β + 1))^inv(β) end -function var(d::LogLogistic{T}) where T<:Real - if d.β ≤ 2 - ArgumentError("var is defined only when β > 2") +function var(d::LogLogistic) + (; α, β) = d + if !(β > 2) + ArgumentError("the variance of a log-logistic distribution is defined only when its shape β > 2") end - b = π/d.β - return d.α^2 * (2*b/sin(2*b)-b^2/(sin(b))^2) + invβ = inv(β) + return α^2 * (inv(sinc(2 * invβ)) - inv(sinc(invβ))^2) end +entropy(d::LogLogistic) = log(d.α / d.β) + 2 #### Evaluation -function pdf(d::LogLogistic{T}, x::Real) where T<:Real - # use built-in impletation to evaluate the density - # of loglogistic at x - # Y = log(X) - # Y ~ logistic(log(θ), 1/ϕ) - x >= 0 ? pdf(Logistic(log(d.α), 1/d.β), log(x)) / x : zero(T) -end -function logpdf(d::LogLogistic{T}, x::Real) where T<:Real - x >= 0 ? logpdf(Logistic(log(d.α), 1/d.β), log(x)) + log(x) : -T(Inf) +function pdf(d::LogLogistic, x::Real) + (; α, β) = d + insupport = x > 0 + _x = insupport ? x : zero(x) + xoαβ = (_x / α)^β + res = (β / x) / ((1 + xoαβ) * (1 + inv(xoαβ))) + return insupport ? res : zero(res) end - -function cdf(d::LogLogistic{T}, x::Real) where T<:Real - x >= 0 ? cdf(Logistic(log(d.α), 1/d.β), log(x)) : zero(T) +function logpdf(d::LogLogistic, x::Real) + (; α, β) = d + insupport = x > 0 + _x = insupport ? x : zero(x) + βlogxoα = β * log(_x / α) + res = log(β / x) - (log1pexp(βlogxoα) + log1pexp(-βlogxoα)) + return insupport ? res : oftype(res, -Inf) end -function logcdf(d::LogLogistic{T}, x::Real) where T<:Real - x >= 0 ? logcdf(Logistic(log(d.α), 1/d.β), log(x)) : -T(Inf) -end +cdf(d::LogLogistic, x::Real) = inv(1 + (max(x, 0) / d.α)^(-d.β)) +ccdf(d::LogLogistic, x::Real) = inv(1 + (max(x, 0) / d.α)^d.β) -function ccdf(d::LogLogistic{T}, x::Real) where T<:Real - x >= 0 ? ccdf(Logistic(log(d.α), 1/d.β), log(x)) : one(T) -end +logcdf(d::LogLogistic, x::Real) = -log1pexp(-d.β * log(max(x, 0) / d.α)) +logccdf(d::LogLogistic, x::Real) = -log1pexp(d.β * log(max(x, 0) / d.α)) -function logccdf(d::LogLogistic{T}, x::Real) where T<:Real - x >= 0 ? logccdf(Logistic(log(d.α), 1/d.β), log(x)) : zero(T) -end +quantile(d::LogLogistic, p::Real) = d.α * (p / (1 - p))^inv(d.β) +cquantile(d::LogLogistic, p::Real) = d.α * ((1 - p) / p)^inv(d.β) +invlogcdf(d::LogLogistic, lp::Real) = d.α * expm1(-lp)^(-inv(d.β)) +invlogccdf(d::LogLogistic, lp::Real) = d.α * expm1(-lp)^inv(d.β) #### Sampling + function rand(rng::AbstractRNG, d::LogLogistic) u = rand(rng) - r = u / (1 - u) - return r^(1/d.β)*d.α + return d.α * (u / (1 - u))^(inv(d.β)) +end +function rand!(rng::AbstractRNG, d::LogLogistic, A::AbstractArray{<:Real}) + rand!(rng, A) + let α = d.α, invβ = inv(d.β) + map!(A, A) do u + return α * (u / (1 - u))^invβ + end + end + return A end diff --git a/test/ref/continuous/loglogistic.R b/test/ref/continuous/loglogistic.R new file mode 100644 index 0000000000..f405386f02 --- /dev/null +++ b/test/ref/continuous/loglogistic.R @@ -0,0 +1,38 @@ +LogLogistic <- R6Class("LogLogistic", + inherit = ContinuousDistribution, + public = list( + names = c("alpha", "beta"), + alpha = NA, + beta = NA, + initialize = function(a=1, b=1) { + self$alpha <- a + self$beta <- b + }, + supp = function() { c(0, Inf) }, + properties = function() { + a <- self$alpha + b <- self$beta + g1 <- ifelse(a > 1, gamma(1 - 1/a), NaN) + g2 <- ifelse(a > 2, gamma(1 - 2/a), NaN) + g3 <- ifelse(a > 3, gamma(1 - 3/a), NaN) + g4 <- ifelse(a > 4, gamma(1 - 4/a), NaN) + gam <- 0.57721566490153286 + list(shape = a, + scale = b, + mean = ifelse(b > 1, a, NaN), + median = a, + mode = ifelse(b > 1, b * (a / (1 + a))^(1/a), 0.0), + var = ifelse(a > 2, b^2 * (g2 - g1^2), NaN), + skewness = if (a > 3) { + (g3 - 3 * g2 * g1 + 2 * g1^3) / (g2 - g1^2)^1.5 + } else { Inf }, + kurtosis = if (a > 4) { + (g4 - 4 * g3 * g1 + 3 * g2^2) / (g2 - g1^2)^2 - 6 + } else { Inf }, + entropy = 2 + log(a / b)) + }, + pdf = function(x, log=FALSE) { VGAM::dfisk(x, shape=self$alpha, scale=self$beta, log=log) }, + cdf = function(x) { VGAM::pfisk(x, shape=self$alpha, scale=self$beta) }, + quan = function(v) { VGAM::qfisk(v, shape=self$alpha, scale=self$beta) } + ) +) diff --git a/test/ref/readme.md b/test/ref/readme.md index 7ae82d5d2e..3568ef0b7b 100644 --- a/test/ref/readme.md +++ b/test/ref/readme.md @@ -15,7 +15,7 @@ in addition to the R language itself: | stringr | For string parsing | | R6 | OOP for implementing distributions | | extraDistr | A number of distributions | -| VGAM | For ``Frechet`` and ``Levy`` | +| VGAM | For ``LogLogistic``, ``Frechet`` and ``Levy`` | | distr | For ``Arcsine`` | | chi | For ``Chi`` | | circular | For ``VonMises`` | From 864a4981209c22d69859779415b81270ac90cf41 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sun, 26 Oct 2025 23:43:15 +0100 Subject: [PATCH 08/13] More tests --- src/univariate/continuous/loglogistic.jl | 9 +-- test/univariate/continuous/loglogistic.jl | 68 ++++++++++++++++++----- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 952fc069d7..31957a2c32 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -9,8 +9,7 @@ f(x; \\alpha, \\beta) = \\frac{(\\alpha / \\beta)x/\\beta()^(\\alpha - 1)}{(1 + ``` ```julia -LogLogistic() # Log-logistic distribution with unit scale and unit shape -LogLogistic(α,β) # Log-logistic distribution with scale α and shape β +LogLogistic(α, β) # Log-logistic distribution with scale α and shape β params(d) # Get the parameters, i.e. (α, β) scale(d) # Get the scale parameter, i.e. α @@ -32,9 +31,7 @@ function LogLogistic(α::T, β::T; check_args=true) where {T <: Real} return LogLogistic{T}(α, β) end -LogLogistic(α::Real, β::Real) = LogLogistic(promote(α,β)...) -LogLogistic(α::Integer, β::Integer) = LogLogistic(float(α), float(β)) -LogLogistic() = LogLogistic(1.0, 1.0, check_args=false) +LogLogistic(α::Real, β::Real) = LogLogistic(promote(α, β)...) @distr_support LogLogistic 0.0 Inf @@ -88,7 +85,7 @@ function logpdf(d::LogLogistic, x::Real) insupport = x > 0 _x = insupport ? x : zero(x) βlogxoα = β * log(_x / α) - res = log(β / x) - (log1pexp(βlogxoα) + log1pexp(-βlogxoα)) + res = log(β / _x) - (log1pexp(βlogxoα) + log1pexp(-βlogxoα)) return insupport ? res : oftype(res, -Inf) end diff --git a/test/univariate/continuous/loglogistic.jl b/test/univariate/continuous/loglogistic.jl index 962b0e830e..2c1b354151 100644 --- a/test/univariate/continuous/loglogistic.jl +++ b/test/univariate/continuous/loglogistic.jl @@ -1,22 +1,64 @@ using Distributions using Test -@testset "LogLogistic" begin +@testset "LogLogistic" begin + @testset "Conversion" begin + d = LogLogistic(1.0, 2.0) + @test convert(LogLogistic{Float64}, d) === d + @test convert(LogLogistic{Float32}, d) isa LogLogistic{Float32} + @test convert(LogLogistic{Float32}, d) == d + end - @test round(pdf(LogLogistic(), -1), digits=6) == 0.0 - @test round(pdf(LogLogistic(), 1), digits=6) == 0.25 - @test round(pdf(LogLogistic(2,2), 1), digits=6) == 0.32 + # Values computed with WolframAlpha + @testset "Special values" begin + # pdf + @test iszero(pdf(LogLogistic(1, 1), -1)) + @test pdf(LogLogistic(1, 1), 1) ≈ 0.25 + @test pdf(LogLogistic(2, 2), 1) ≈ 0.32 + @test pdf(LogLogistic(2, 2), 4) ≈ 0.08 - @test round(cdf(LogLogistic(), -1), digits=6) == 0.0 - @test round(cdf(LogLogistic(), 1), digits=6) == 0.5 - @test round(cdf(LogLogistic(2,2), 4), digits=6) == 0.8 + # log pdf + @test logpdf(LogLogistic(1, 1), -1) == -Inf + @test logpdf(LogLogistic(1, 1), 1) ≈ -log(4) + @test logpdf(LogLogistic(2, 2), 1) ≈ log(0.32) + @test logpdf(LogLogistic(2, 2), 4) ≈ log(0.08) - @test round(ccdf(LogLogistic(2,2), 4), digits=6) == 0.2 + # cdf + @test iszero(cdf(LogLogistic(1, 1), -1)) + @test cdf(LogLogistic(1, 1), Inf) == 1 + @test cdf(LogLogistic(1, 1), 1) ≈ 0.5 + @test cdf(LogLogistic(2, 2), 1) ≈ 0.2 + @test cdf(LogLogistic(2, 2), 4) ≈ 0.8 - @test round(logpdf(LogLogistic(), -1), digits=6) == -Inf - @test round(logpdf(LogLogistic(), 1), digits=6) == -1.386294 + # log cdf + @test logcdf(LogLogistic(1, 1), -1) == -Inf + @test iszero(logcdf(LogLogistic(1, 1), Inf)) + @test logcdf(LogLogistic(1, 1), 1) ≈ -log(2) + @test logcdf(LogLogistic(2, 2), 1) ≈ log(0.2) + @test logcdf(LogLogistic(2, 2), 4) ≈ log(0.8) - @test round(logcdf(LogLogistic(2,2), 4), digits=6) == -0.223144 - @test round(logccdf(LogLogistic(2,2), 4), digits=6) == -1.609438 + # ccdf + @test ccdf(LogLogistic(1, 1), -1) == 1 + @test iszero(ccdf(LogLogistic(1, 1), Inf)) + @test ccdf(LogLogistic(1, 1), 1) ≈ 0.5 + @test ccdf(LogLogistic(2, 2), 1) ≈ 0.8 + @test ccdf(LogLogistic(2, 2), 4) ≈ 0.2 -end \ No newline at end of file + # log ccdf + @test iszero(logccdf(LogLogistic(1, 1), -1)) + @test logccdf(LogLogistic(1, 1), Inf) == -Inf + @test logccdf(LogLogistic(1, 1), 1) ≈ -log(2) + @test logccdf(LogLogistic(2, 2), 1) ≈ log(0.8) + @test logccdf(LogLogistic(2, 2), 4) ≈ log(0.2) + end + + @testset "Default tests" begin + for d in [ + LogLogistic(1, 1), + LogLogistic(2, 1), + LogLogistic(2, 2), + ] + test_distr(d, 10^6) + end + end +end From 6b2e02c2b333f8f09c2055df197cb5d1027e4b1c Mon Sep 17 00:00:00 2001 From: David Widmann Date: Mon, 27 Oct 2025 14:49:57 +0100 Subject: [PATCH 09/13] More fixes and tests --- Project.toml | 4 +- src/univariate/continuous/loglogistic.jl | 10 ++-- test/testutils.jl | 1 + test/univariate/continuous/loglogistic.jl | 70 +++++++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 643ea0f47e..ed35734f8f 100644 --- a/Project.toml +++ b/Project.toml @@ -43,6 +43,7 @@ FiniteDifferences = "0.12" ForwardDiff = "0.10, 1" JSON = "0.21" LinearAlgebra = "<0.0.1, 1" +Optim = "1.13" OffsetArrays = "1" PDMats = "0.11.35" Printf = "<0.0.1, 1" @@ -69,6 +70,7 @@ Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +Optim = "429524aa-4258-5aef-a3af-852621145aeb" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" @@ -76,4 +78,4 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "StableRNGs", "Calculus", "ChainRulesCore", "ChainRulesTestUtils", "DensityInterface", "Distributed", "FiniteDifferences", "ForwardDiff", "JSON", "SparseArrays", "StaticArrays", "Test", "OffsetArrays"] +test = ["Aqua", "StableRNGs", "Calculus", "ChainRulesCore", "ChainRulesTestUtils", "DensityInterface", "Distributed", "FiniteDifferences", "ForwardDiff", "JSON", "Optim", "SparseArrays", "StaticArrays", "Test", "OffsetArrays"] diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 31957a2c32..8a5ba67b16 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -26,12 +26,12 @@ struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution LogLogistic{T}(α::T,β::T) where {T} = new{T}(α,β) end -function LogLogistic(α::T, β::T; check_args=true) where {T <: Real} +function LogLogistic(α::T, β::T; check_args::Bool=true) where {T <: Real} check_args && @check_args(LogLogistic, α > zero(α) && β > zero(β)) return LogLogistic{T}(α, β) end -LogLogistic(α::Real, β::Real) = LogLogistic(promote(α, β)...) +LogLogistic(α::Real, β::Real; check_args::Bool = true) = LogLogistic(promote(α, β)...; check_args) @distr_support LogLogistic 0.0 Inf @@ -49,7 +49,7 @@ median(d::LogLogistic) = d.α function mean(d::LogLogistic) (; α, β) = d if !(β > 1) - ArgumentError("the mean of a log-logistic distribution is defined only when its shape β > 1") + throw(ArgumentError("the mean of a log-logistic distribution is defined only when its shape β > 1")) end return α/sinc(inv(β)) end @@ -62,7 +62,7 @@ end function var(d::LogLogistic) (; α, β) = d if !(β > 2) - ArgumentError("the variance of a log-logistic distribution is defined only when its shape β > 2") + throw(ArgumentError("the variance of a log-logistic distribution is defined only when its shape β > 2")) end invβ = inv(β) return α^2 * (inv(sinc(2 * invβ)) - inv(sinc(invβ))^2) @@ -77,7 +77,7 @@ function pdf(d::LogLogistic, x::Real) insupport = x > 0 _x = insupport ? x : zero(x) xoαβ = (_x / α)^β - res = (β / x) / ((1 + xoαβ) * (1 + inv(xoαβ))) + res = (β / _x) / ((1 + xoαβ) * (1 + inv(xoαβ))) return insupport ? res : zero(res) end function logpdf(d::LogLogistic, x::Real) diff --git a/test/testutils.jl b/test/testutils.jl index 46737e06d1..8a62c52d3b 100644 --- a/test/testutils.jl +++ b/test/testutils.jl @@ -626,6 +626,7 @@ allow_test_stats(d::UnivariateDistribution) = true allow_test_stats(d::NoncentralBeta) = false allow_test_stats(::StudentizedRange) = false allow_test_stats(::LogitNormal) = false # `mean` is not defined since it has no analytical solution +allow_test_stats(d::LogLogistic) = d.β > 2 # second moment is only defined if shape β > 2 function test_stats(d::ContinuousUnivariateDistribution, xs::AbstractVector{Float64}) # using Monte Carlo methods diff --git a/test/univariate/continuous/loglogistic.jl b/test/univariate/continuous/loglogistic.jl index 2c1b354151..9705a7ec03 100644 --- a/test/univariate/continuous/loglogistic.jl +++ b/test/univariate/continuous/loglogistic.jl @@ -1,7 +1,32 @@ using Distributions using Test +import Optim + @testset "LogLogistic" begin + @testset "Constructors" begin + for T1 in (Int, Float32, Float64), T2 in (Int, Float32, Float64) + d = @inferred(LogLogistic(T1(1), T2(2))) + @test d isa LogLogistic{promote_type(T1, T2)} + @test d.α == 1 + @test d.β == 2 + + @test_throws ArgumentError LogLogistic(T1(-1), T2(2)) + @test_throws ArgumentError LogLogistic(T1(-1), T2(2); check_args=true) + d = @inferred(LogLogistic(T1(-1), T2(2); check_args=false)) + @test d isa LogLogistic{promote_type(T1, T2)} + @test d.α == -1 + @test d.β == 2 + + @test_throws ArgumentError LogLogistic(T1(1), T2(-2)) + @test_throws ArgumentError LogLogistic(T1(1), T2(-2); check_args=true) + d = @inferred(LogLogistic(T1(1), T2(-2); check_args=false)) + @test d isa LogLogistic{promote_type(T1, T2)} + @test d.α == 1 + @test d.β == -2 + end + end + @testset "Conversion" begin d = LogLogistic(1.0, 2.0) @test convert(LogLogistic{Float64}, d) === d @@ -9,6 +34,44 @@ using Test @test convert(LogLogistic{Float32}, d) == d end + @testset "median" begin + for α in (0.5, 1, 2, 3), β in (0.5, 1, 2, 3) + d = LogLogistic(α, β) + @test median(d) ≈ quantile(d, 1//2) + end + end + + @testset "mode" begin + for α in (0.5, 1, 2, 3), β in (0.5, 1, 2, 3) + d = LogLogistic(α, β) + opt = Optim.maximize(Base.Fix1(logpdf, d), 0.0, 10.0) + @test mode(d) ≈ Optim.maximizer(opt) rtol = 1e-8 atol = 1e-12 + end + end + + @testset "mean" begin + for α in (0.5, 1, 2, 3), β in (0.5, 1, 2, 3) + d = LogLogistic(α, β) + if β > 1 + @test mean(d) ≈ Distributions.expectation(identity, d) + else + @test_throws ArgumentError("the mean of a log-logistic distribution is defined only when its shape β > 1") mean(d) + end + end + end + + @testset "variance" begin + for α in (0.5, 1, 2, 3), β in (0.5, 1, 2, 3) + d = LogLogistic(α, β) + if β > 2 + m = mean(d) + @test var(d) ≈ Distributions.expectation(x -> (x - m)^2, d) + else + @test_throws ArgumentError("the variance of a log-logistic distribution is defined only when its shape β > 2") var(d) + end + end + end + # Values computed with WolframAlpha @testset "Special values" begin # pdf @@ -52,6 +115,13 @@ using Test @test logccdf(LogLogistic(2, 2), 4) ≈ log(0.2) end + @testset "entropy" begin + for α in (0.5, 1, 2, 3), β in (0.5, 1, 2, 3) + d = LogLogistic(α, β) + @test entropy(d) ≈ -Distributions.expectation(Base.Fix1(logpdf, d), d) rtol = 1e-6 + end + end + @testset "Default tests" begin for d in [ LogLogistic(1, 1), From 4a5f3d57caa4bb819c4df598c654ee1219f08223 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Mon, 27 Oct 2025 16:10:16 +0100 Subject: [PATCH 10/13] More tests --- test/univariate/continuous/loglogistic.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/univariate/continuous/loglogistic.jl b/test/univariate/continuous/loglogistic.jl index 9705a7ec03..a0da9a5b23 100644 --- a/test/univariate/continuous/loglogistic.jl +++ b/test/univariate/continuous/loglogistic.jl @@ -8,6 +8,7 @@ import Optim for T1 in (Int, Float32, Float64), T2 in (Int, Float32, Float64) d = @inferred(LogLogistic(T1(1), T2(2))) @test d isa LogLogistic{promote_type(T1, T2)} + @test partype(d) === promote_type(T1, T2) @test d.α == 1 @test d.β == 2 @@ -15,6 +16,7 @@ import Optim @test_throws ArgumentError LogLogistic(T1(-1), T2(2); check_args=true) d = @inferred(LogLogistic(T1(-1), T2(2); check_args=false)) @test d isa LogLogistic{promote_type(T1, T2)} + @test partype(d) === promote_type(T1, T2) @test d.α == -1 @test d.β == 2 @@ -22,6 +24,7 @@ import Optim @test_throws ArgumentError LogLogistic(T1(1), T2(-2); check_args=true) d = @inferred(LogLogistic(T1(1), T2(-2); check_args=false)) @test d isa LogLogistic{promote_type(T1, T2)} + @test partype(d) === promote_type(T1, T2) @test d.α == 1 @test d.β == -2 end From f50f5a6ccfc3c7e02044a6994121656c03fe54ef Mon Sep 17 00:00:00 2001 From: David Widmann Date: Mon, 27 Oct 2025 17:08:19 +0100 Subject: [PATCH 11/13] Add tests against R (VGAM) --- test/ref/continuous/loglogistic.R | 31 +---- test/ref/continuous_test.lst | 8 ++ test/ref/continuous_test.ref.json | 182 ++++++++++++++++++++++++++++++ test/ref/rdistributions.R | 1 + 4 files changed, 196 insertions(+), 26 deletions(-) diff --git a/test/ref/continuous/loglogistic.R b/test/ref/continuous/loglogistic.R index f405386f02..6a89ea3adb 100644 --- a/test/ref/continuous/loglogistic.R +++ b/test/ref/continuous/loglogistic.R @@ -4,35 +4,14 @@ LogLogistic <- R6Class("LogLogistic", names = c("alpha", "beta"), alpha = NA, beta = NA, - initialize = function(a=1, b=1) { + initialize = function(a, b) { self$alpha <- a self$beta <- b }, supp = function() { c(0, Inf) }, - properties = function() { - a <- self$alpha - b <- self$beta - g1 <- ifelse(a > 1, gamma(1 - 1/a), NaN) - g2 <- ifelse(a > 2, gamma(1 - 2/a), NaN) - g3 <- ifelse(a > 3, gamma(1 - 3/a), NaN) - g4 <- ifelse(a > 4, gamma(1 - 4/a), NaN) - gam <- 0.57721566490153286 - list(shape = a, - scale = b, - mean = ifelse(b > 1, a, NaN), - median = a, - mode = ifelse(b > 1, b * (a / (1 + a))^(1/a), 0.0), - var = ifelse(a > 2, b^2 * (g2 - g1^2), NaN), - skewness = if (a > 3) { - (g3 - 3 * g2 * g1 + 2 * g1^3) / (g2 - g1^2)^1.5 - } else { Inf }, - kurtosis = if (a > 4) { - (g4 - 4 * g3 * g1 + 3 * g2^2) / (g2 - g1^2)^2 - 6 - } else { Inf }, - entropy = 2 + log(a / b)) - }, - pdf = function(x, log=FALSE) { VGAM::dfisk(x, shape=self$alpha, scale=self$beta, log=log) }, - cdf = function(x) { VGAM::pfisk(x, shape=self$alpha, scale=self$beta) }, - quan = function(v) { VGAM::qfisk(v, shape=self$alpha, scale=self$beta) } + properties = function() { }, + pdf = function(x, log=FALSE) { VGAM::dfisk(x, scale=self$alpha, shape=self$beta, log=log) }, + cdf = function(x) { VGAM::pfisk(x, scale=self$alpha, shape=self$beta) }, + quan = function(v) { VGAM::qfisk(v, scale=self$alpha, shape=self$beta) } ) ) diff --git a/test/ref/continuous_test.lst b/test/ref/continuous_test.lst index d403be3e65..a937ceb719 100644 --- a/test/ref/continuous_test.lst +++ b/test/ref/continuous_test.lst @@ -126,6 +126,14 @@ Logistic(5.0, 1.0) Logistic(2.0, 1.5) Logistic(5.0, 1.5) +LogLogistic(0.5, 0.5) +LogLogistic(0.5, 1.0) +LogLogistic(1.0, 0.5) +LogLogistic(1.0, 1.0) +LogLogistic(1.0, 2.0) +LogLogistic(2.0, 1.0) +LogLogistic(2.0, 2.0) + LogNormal() LogNormal(1.0) LogNormal(0.0, 2.0) diff --git a/test/ref/continuous_test.ref.json b/test/ref/continuous_test.ref.json index ae86f2e8a7..edede917a7 100644 --- a/test/ref/continuous_test.ref.json +++ b/test/ref/continuous_test.ref.json @@ -3457,6 +3457,188 @@ { "q": 0.90, "x": 8.29583686600433 } ] }, +{ + "expr": "LogLogistic(0.5, 0.5)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.00617283950617284, "pdf": 7.29, "logpdf": 1.98650354602057, "cdf": 0.1 }, + { "x": 0.03125, "pdf": 2.56, "logpdf": 0.940007258491471, "cdf": 0.2 }, + { "x": 0.0918367346938776, "pdf": 1.14333333333333, "logpdf": 0.133947972509739, "cdf": 0.3 }, + { "x": 0.222222222222222, "pdf": 0.54, "logpdf": -0.616186139423817, "cdf": 0.4 }, + { "x": 0.5, "pdf": 0.25, "logpdf": -1.38629436111989, "cdf": 0.5 }, + { "x": 1.125, "pdf": 0.106666666666667, "logpdf": -2.23804657185647, "cdf": 0.6 }, + { "x": 2.72222222222222, "pdf": 0.0385714285714285, "logpdf": -3.25524346903908, "cdf": 0.7 }, + { "x": 8, "pdf": 0.01, "logpdf": -4.60517018598809, "cdf": 0.8 }, + { "x": 40.5, "pdf": 0.00111111111111111, "logpdf": -6.80239476332431, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.00617283950617284 }, + { "q": 0.25, "x": 0.0555555555555556 }, + { "q": 0.50, "x": 0.5 }, + { "q": 0.75, "x": 4.5 }, + { "q": 0.90, "x": 40.5 } + ] +}, +{ + "expr": "LogLogistic(0.5, 1.0)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.0555555555555556, "pdf": 1.62, "logpdf": 0.482426149244293, "cdf": 0.1 }, + { "x": 0.125, "pdf": 1.28, "logpdf": 0.246860077931526, "cdf": 0.2 }, + { "x": 0.214285714285714, "pdf": 0.98, "logpdf": -0.0202027073175196, "cdf": 0.3 }, + { "x": 0.333333333333333, "pdf": 0.72, "logpdf": -0.328504066972036, "cdf": 0.4 }, + { "x": 0.5, "pdf": 0.5, "logpdf": -0.693147180559945, "cdf": 0.5 }, + { "x": 0.75, "pdf": 0.32, "logpdf": -1.13943428318836, "cdf": 0.6 }, + { "x": 1.16666666666667, "pdf": 0.18, "logpdf": -1.71479842809193, "cdf": 0.7 }, + { "x": 2, "pdf": 0.08, "logpdf": -2.52572864430826, "cdf": 0.8 }, + { "x": 4.5, "pdf": 0.02, "logpdf": -3.91202300542815, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.0555555555555556 }, + { "q": 0.25, "x": 0.166666666666667 }, + { "q": 0.50, "x": 0.5 }, + { "q": 0.75, "x": 1.5 }, + { "q": 0.90, "x": 4.5 } + ] +}, +{ + "expr": "LogLogistic(1.0, 0.5)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.0123456790123457, "pdf": 3.645, "logpdf": 1.29335636546062, "cdf": 0.1 }, + { "x": 0.0625, "pdf": 1.28, "logpdf": 0.246860077931526, "cdf": 0.2 }, + { "x": 0.183673469387755, "pdf": 0.571666666666667, "logpdf": -0.559199208050207, "cdf": 0.3 }, + { "x": 0.444444444444445, "pdf": 0.27, "logpdf": -1.30933331998376, "cdf": 0.4 }, + { "x": 1, "pdf": 0.125, "logpdf": -2.07944154167984, "cdf": 0.5 }, + { "x": 2.25, "pdf": 0.0533333333333334, "logpdf": -2.93119375241642, "cdf": 0.6 }, + { "x": 5.44444444444445, "pdf": 0.0192857142857143, "logpdf": -3.94839064959902, "cdf": 0.7 }, + { "x": 16, "pdf": 0.005, "logpdf": -5.29831736654804, "cdf": 0.8 }, + { "x": 81, "pdf": 0.000555555555555555, "logpdf": -7.49554194388426, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.0123456790123457 }, + { "q": 0.25, "x": 0.111111111111111 }, + { "q": 0.50, "x": 1 }, + { "q": 0.75, "x": 9 }, + { "q": 0.90, "x": 81 } + ] +}, +{ + "expr": "LogLogistic(1.0, 1.0)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.111111111111111, "pdf": 0.81, "logpdf": -0.210721031315653, "cdf": 0.1 }, + { "x": 0.25, "pdf": 0.64, "logpdf": -0.44628710262842, "cdf": 0.2 }, + { "x": 0.428571428571429, "pdf": 0.49, "logpdf": -0.713349887877465, "cdf": 0.3 }, + { "x": 0.666666666666667, "pdf": 0.36, "logpdf": -1.02165124753198, "cdf": 0.4 }, + { "x": 1, "pdf": 0.25, "logpdf": -1.38629436111989, "cdf": 0.5 }, + { "x": 1.5, "pdf": 0.16, "logpdf": -1.83258146374831, "cdf": 0.6 }, + { "x": 2.33333333333333, "pdf": 0.09, "logpdf": -2.40794560865187, "cdf": 0.7 }, + { "x": 4, "pdf": 0.04, "logpdf": -3.2188758248682, "cdf": 0.8 }, + { "x": 9, "pdf": 0.01, "logpdf": -4.60517018598809, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.111111111111111 }, + { "q": 0.25, "x": 0.333333333333333 }, + { "q": 0.50, "x": 1 }, + { "q": 0.75, "x": 3 }, + { "q": 0.90, "x": 9 } + ] +}, +{ + "expr": "LogLogistic(1.0, 2.0)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.333333333333333, "pdf": 0.54, "logpdf": -0.616186139423817, "cdf": 0.1 }, + { "x": 0.5, "pdf": 0.64, "logpdf": -0.44628710262842, "cdf": 0.2 }, + { "x": 0.654653670707977, "pdf": 0.641560597293818, "logpdf": -0.443851637511121, "cdf": 0.3 }, + { "x": 0.816496580927726, "pdf": 0.587877538267963, "logpdf": -0.531236621026118, "cdf": 0.4 }, + { "x": 1, "pdf": 0.5, "logpdf": -0.693147180559945, "cdf": 0.5 }, + { "x": 1.22474487139159, "pdf": 0.391918358845309, "logpdf": -0.936701729134283, "cdf": 0.6 }, + { "x": 1.52752523165195, "pdf": 0.27495454169735, "logpdf": -1.29114949789833, "cdf": 0.7 }, + { "x": 2, "pdf": 0.16, "logpdf": -1.83258146374831, "cdf": 0.8 }, + { "x": 3, "pdf": 0.06, "logpdf": -2.81341071676004, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.333333333333333 }, + { "q": 0.25, "x": 0.577350269189626 }, + { "q": 0.50, "x": 1 }, + { "q": 0.75, "x": 1.73205080756888 }, + { "q": 0.90, "x": 3 } + ] +}, +{ + "expr": "LogLogistic(2.0, 1.0)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.222222222222222, "pdf": 0.405, "logpdf": -0.903868211875598, "cdf": 0.1 }, + { "x": 0.5, "pdf": 0.32, "logpdf": -1.13943428318836, "cdf": 0.2 }, + { "x": 0.857142857142857, "pdf": 0.245, "logpdf": -1.40649706843741, "cdf": 0.3 }, + { "x": 1.33333333333333, "pdf": 0.18, "logpdf": -1.71479842809193, "cdf": 0.4 }, + { "x": 2, "pdf": 0.125, "logpdf": -2.07944154167984, "cdf": 0.5 }, + { "x": 3, "pdf": 0.08, "logpdf": -2.52572864430826, "cdf": 0.6 }, + { "x": 4.66666666666667, "pdf": 0.045, "logpdf": -3.10109278921182, "cdf": 0.7 }, + { "x": 8, "pdf": 0.02, "logpdf": -3.91202300542815, "cdf": 0.8 }, + { "x": 18, "pdf": 0.005, "logpdf": -5.29831736654804, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.222222222222222 }, + { "q": 0.25, "x": 0.666666666666667 }, + { "q": 0.50, "x": 2 }, + { "q": 0.75, "x": 6 }, + { "q": 0.90, "x": 18 } + ] +}, +{ + "expr": "LogLogistic(2.0, 2.0)", + "dtype": "LogLogistic", + "minimum": 0, + "maximum": "inf", + "properties": { + }, + "points": [ + { "x": 0.666666666666667, "pdf": 0.27, "logpdf": -1.30933331998376, "cdf": 0.1 }, + { "x": 1, "pdf": 0.32, "logpdf": -1.13943428318836, "cdf": 0.2 }, + { "x": 1.30930734141595, "pdf": 0.320780298646909, "logpdf": -1.13699881807107, "cdf": 0.3 }, + { "x": 1.63299316185545, "pdf": 0.293938769133981, "logpdf": -1.22438380158606, "cdf": 0.4 }, + { "x": 2, "pdf": 0.25, "logpdf": -1.38629436111989, "cdf": 0.5 }, + { "x": 2.44948974278318, "pdf": 0.195959179422654, "logpdf": -1.62984890969423, "cdf": 0.6 }, + { "x": 3.05505046330389, "pdf": 0.137477270848675, "logpdf": -1.98429667845827, "cdf": 0.7 }, + { "x": 4, "pdf": 0.08, "logpdf": -2.52572864430826, "cdf": 0.8 }, + { "x": 6, "pdf": 0.03, "logpdf": -3.50655789731998, "cdf": 0.9 } + ], + "quans": [ + { "q": 0.10, "x": 0.666666666666667 }, + { "q": 0.25, "x": 1.15470053837925 }, + { "q": 0.50, "x": 2 }, + { "q": 0.75, "x": 3.46410161513775 }, + { "q": 0.90, "x": 6 } + ] +}, { "expr": "LogNormal()", "dtype": "LogNormal", diff --git a/test/ref/rdistributions.R b/test/ref/rdistributions.R index 53623544ba..2bb8c55c84 100644 --- a/test/ref/rdistributions.R +++ b/test/ref/rdistributions.R @@ -62,6 +62,7 @@ source("continuous/laplace.R") source("continuous/levy.R") source("continuous/lindley.R") source("continuous/logistic.R") +source("continuous/loglogistic.R") source("continuous/lognormal.R") source("continuous/noncentralbeta.R") source("continuous/noncentralchisq.R") From c7e2e4b731a302315930593fde4b080ff59909e1 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Mon, 27 Oct 2025 17:28:00 +0100 Subject: [PATCH 12/13] Fix docstring --- docs/src/univariate.md | 2 +- src/univariate/continuous/loglogistic.jl | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/univariate.md b/docs/src/univariate.md index ed6b1c9484..48a90c7a42 100644 --- a/docs/src/univariate.md +++ b/docs/src/univariate.md @@ -330,7 +330,7 @@ plotdensity((-4, 8), Logistic, (2, 1)) # hide LogLogistic ``` ```@example plotdensity -plotdensity((0, 2), LogLogistic, (1, 1)) # hide +plotdensity((0, 2), LogLogistic, (2, 1)) # hide ``` ```@docs diff --git a/src/univariate/continuous/loglogistic.jl b/src/univariate/continuous/loglogistic.jl index 8a5ba67b16..37b181c0f5 100644 --- a/src/univariate/continuous/loglogistic.jl +++ b/src/univariate/continuous/loglogistic.jl @@ -1,11 +1,13 @@ """ LogLogistic(α, β) -The *log-logistic distribution* with scale `α` and shape `β` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. -If ``X \\sim \\operatorname{LogLogistic}(\\alpha, \\beta)`` then ``log(X) \\sim \\operatorname{Logistic}(log(\\alpha), 1/\\beta)``. The probability density function is +The *log-logistic distribution* with scale ``\\alpha`` and shape ``\\beta`` is the distribution of a random variable whose logarithm has a [`Logistic`](@ref) distribution. + +If ``X \\sim \\operatorname{LogLogistic}(\\alpha, \\beta)`` then ``\\log(X) \\sim \\operatorname{Logistic}(\\log(\\alpha), 1/\\beta)``. +The probability density function is ```math -f(x; \\alpha, \\beta) = \\frac{(\\alpha / \\beta)x/\\beta()^(\\alpha - 1)}{(1 + (x/\\beta)^\\alpha)^2}, \\beta > 0, \\alpha > 0 +f(x; \\alpha, \\beta) = \\frac{(\\beta / \\alpha){(x/\\alpha)}^{\\beta - 1}}{{(1 + {(x/\\alpha)}^\\beta)}^2}, \\qquad \\alpha > 0, \\beta > 0. ``` ```julia @@ -18,7 +20,7 @@ shape(d) # Get the shape parameter, i.e. β External links -* [Log logistic distribution on Wikipedia](https://en.wikipedia.org/wiki/Log-logistic_distribution) +* [Log-logistic distribution on Wikipedia](https://en.wikipedia.org/wiki/Log-logistic_distribution) """ struct LogLogistic{T<:Real} <: ContinuousUnivariateDistribution α::T From 4d24b07553d9c07c679dfffedb34572f403ce3f7 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Mon, 27 Oct 2025 17:30:39 +0100 Subject: [PATCH 13/13] Update version to 0.25.123 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ed35734f8f..b460dc8bf8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Distributions" uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" authors = ["JuliaStats"] -version = "0.25.122" +version = "0.25.123" [deps] AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"