├── .gitignore ├── docs ├── src │ ├── assets │ │ └── logo.png │ ├── index.md │ ├── matrix.md │ ├── truncate.md │ └── starting.md ├── Project.toml └── make.jl ├── test ├── ref │ ├── matrixvariates │ │ ├── get_stan_output.R │ │ ├── jsonfiles │ │ │ ├── LKJ_stan_output.json │ │ │ ├── InverseWishart_stan_output.json │ │ │ └── Wishart_stan_output.json │ │ └── scripts │ │ │ ├── lkj.R │ │ │ ├── wishart.R │ │ │ └── inversewishart.R │ ├── continuous │ │ ├── studentizedrange.R │ │ ├── noncentralt.R │ │ ├── noncentralchisq.R │ │ ├── noncentralf.R │ │ ├── noncentralbeta.R │ │ ├── chisq.R │ │ ├── cauchy.R │ │ ├── exponential.R │ │ ├── rayleigh.R │ │ ├── laplace.R │ │ ├── logistic.R │ │ ├── inversegaussian.R │ │ ├── gumbel.R │ │ ├── normal.R │ │ ├── chi.R │ │ ├── weibull.R │ │ ├── levy.R │ │ ├── tdist.R │ │ ├── gamma.R │ │ ├── lognormal.R │ │ ├── uniform.R │ │ ├── betaprime.R │ │ ├── fdist.R │ │ ├── arcsine.R │ │ ├── beta.R │ │ ├── normalinversegaussian.R │ │ ├── inversegamma.R │ │ ├── pareto.R │ │ ├── truncatednormal.R │ │ ├── vonmises.R │ │ ├── triangulardist.R │ │ ├── cosine.R │ │ ├── frechet.R │ │ ├── generalizedpareto.R │ │ └── generalizedextremevalue.R │ ├── discrete │ │ ├── poisson.R │ │ ├── geometric.R │ │ ├── skellam.R │ │ ├── negativebinomial.R │ │ ├── bernoulli.R │ │ ├── binomial.R │ │ ├── betabinomial.R │ │ ├── hypergeometric.R │ │ ├── discreteuniform.R │ │ └── noncentralhypergeometric.R │ ├── discrete_test.lst │ └── rdistributions.R ├── bernoulli.jl ├── quantile_newton.jl ├── conversion.jl ├── betabinomial.jl ├── functionals.jl ├── qq.jl ├── truncated_uniform.jl ├── noncentralt.jl ├── kolmogorov.jl ├── arcsine.jl ├── negativebinomial.jl ├── edgecases.jl ├── truncated_exponential.jl ├── vonmises.jl ├── semicircle.jl ├── soliton.jl ├── binomial.jl ├── multivariate_stats.jl ├── gradlogpdf.jl ├── edgeworth.jl ├── skewnormal.jl ├── runtests.jl ├── logitnormal.jl ├── categorical.jl ├── truncnormal.jl ├── utils.jl ├── types.jl ├── dirichlet.jl ├── dirichletmultinomial.jl ├── univariate_bounds.jl ├── product.jl ├── poissonbinomial.jl └── mvtdist.jl ├── perf └── Project.toml ├── src ├── conversion.jl ├── truncated │ ├── uniform.jl │ └── exponential.jl ├── samplers │ ├── poissonbinomial.jl │ ├── exponential.jl │ ├── aliastable.jl │ ├── categorical.jl │ ├── discretenonparametric.jl │ ├── vonmises.jl │ └── multinomial.jl ├── estimators.jl ├── univariate │ └── continuous │ │ ├── ksonesided.jl │ │ ├── noncentralbeta.jl │ │ ├── noncentralt.jl │ │ ├── biweight.jl │ │ ├── noncentralf.jl │ │ ├── semicircle.jl │ │ ├── studentizedrange.jl │ │ ├── triweight.jl │ │ ├── chisq.jl │ │ ├── epanechnikov.jl │ │ ├── cosine.jl │ │ ├── erlang.jl │ │ ├── noncentralchisq.jl │ │ ├── tdist.jl │ │ ├── normalcanon.jl │ │ ├── gumbel.jl │ │ ├── vonmises.jl │ │ ├── skewnormal.jl │ │ ├── normalinversegaussian.jl │ │ ├── levy.jl │ │ └── chi.jl ├── qq.jl ├── samplers.jl ├── genericfit.jl ├── functionals.jl ├── deprecates.jl ├── mixtures │ └── unigmm.jl ├── multivariate │ └── product.jl ├── show.jl ├── genericrand.jl ├── matrix │ └── matrixreshaped.jl └── convolution.jl ├── .github └── workflows │ ├── TagBot.yml │ └── CompatHelper.yml ├── .travis.yml ├── appveyor.yml ├── LICENSE.md ├── CITATION.bib └── Project.toml /.gitignore: -------------------------------------------------------------------------------- 1 | docs/build 2 | docs/site 3 | .Rhistory 4 | Manifest.toml 5 | .vscode 6 | -------------------------------------------------------------------------------- /docs/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/Distributions.jl/master/docs/src/assets/logo.png -------------------------------------------------------------------------------- /test/ref/matrixvariates/get_stan_output.R: -------------------------------------------------------------------------------- 1 | source("scripts/wishart.R") 2 | source("scripts/inversewishart.R") 3 | source("scripts/lkj.R") 4 | -------------------------------------------------------------------------------- /test/bernoulli.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test, Random 3 | 4 | @test rand(Bernoulli()) isa Bool 5 | @test rand(Bernoulli(), 10) isa Vector{Bool} 6 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" 3 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 4 | 5 | [compat] 6 | Documenter = "~0.22" 7 | -------------------------------------------------------------------------------- /test/quantile_newton.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | 5 | d = Normal() 6 | @test Distributions.quantile_newton(d, 0.5) == quantile(d, 0.5) 7 | @test Distributions.cquantile_newton(d, 0.5) == cquantile(d, 0.5) 8 | -------------------------------------------------------------------------------- /perf/Project.toml: -------------------------------------------------------------------------------- 1 | authors = ["JuliaStats"] 2 | 3 | [deps] 4 | BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" 5 | Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" 6 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 7 | -------------------------------------------------------------------------------- /test/conversion.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | 3 | using Distributions 4 | 5 | @test convert(Binomial, Bernoulli(0.75)) == Binomial(1, 0.75) 6 | 7 | @test convert(Gamma, Exponential(3.0)) == Gamma(1.0, 3.0) 8 | @test convert(Gamma, Erlang(5, 2.0)) == Gamma(5.0, 2.0) 9 | -------------------------------------------------------------------------------- /src/conversion.jl: -------------------------------------------------------------------------------- 1 | 2 | # Discrete univariate 3 | 4 | convert(::Type{Binomial}, d::Bernoulli) = Binomial(1, d.p) 5 | 6 | # Continuous univariate 7 | 8 | convert(::Type{Gamma}, d::Exponential) = Gamma(1.0, d.θ) 9 | convert(::Type{Gamma}, d::Erlang) = Gamma(d.α, d.θ) 10 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | schedule: 4 | - cron: 0 * * * * 5 | jobs: 6 | TagBot: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: JuliaRegistries/TagBot@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /src/truncated/uniform.jl: -------------------------------------------------------------------------------- 1 | ##### 2 | ##### Shortcut for truncating uniform distributions. 3 | ##### 4 | 5 | truncated(d::Uniform, l::T, u::T) where {T <: Real} = Uniform(promote(max(l, d.a), min(u, d.b))...) 6 | 7 | truncated(d::Uniform, l::Integer, u::Integer) = truncated(d, float(l), float(u)) 8 | -------------------------------------------------------------------------------- /src/samplers/poissonbinomial.jl: -------------------------------------------------------------------------------- 1 | struct PoissBinAliasSampler <: Sampleable{Univariate,Discrete} 2 | table::AliasTable 3 | end 4 | 5 | PoissBinAliasSampler(d::PoissonBinomial) = PoissBinAliasSampler(AliasTable(d.pmf)) 6 | 7 | rand(rng::AbstractRNG, s::PoissBinAliasSampler) = rand(rng, s.table) - 1 8 | -------------------------------------------------------------------------------- /test/betabinomial.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | @testset "Log of Beta-binomial distribution" begin 5 | d = BetaBinomial(50, 0.2,0.6) 6 | 7 | for k in Base.OneTo(50) 8 | p = pdf(d, k) 9 | lp = logpdf(d, k) 10 | @test_approx_eq lp log(p) 11 | @test insupport(d, k) 12 | end 13 | @test !insupport(d, 51) 14 | end 15 | -------------------------------------------------------------------------------- /test/functionals.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using Distributions: Categorical, kldivergence, expectation, Normal 3 | @test kldivergence(Categorical([0.0, 0.1, 0.9]), Categorical([0.1, 0.1, 0.8])) ≥ 0 4 | @test kldivergence(Categorical([0.0, 0.1, 0.9]), Categorical([0.1, 0.1, 0.8])) ≈ 5 | kldivergence([0.0, 0.1, 0.9], [0.1, 0.1, 0.8]) 6 | @test expectation(Normal(0.0, 1.0), identity, 1e-10) ≤ 1e-9 7 | -------------------------------------------------------------------------------- /src/samplers/exponential.jl: -------------------------------------------------------------------------------- 1 | struct ExponentialSampler <: Sampleable{Univariate,Continuous} 2 | scale::Float64 3 | end 4 | 5 | rand(rng::AbstractRNG, s::ExponentialSampler) = s.scale * randexp(rng) 6 | 7 | struct ExponentialLogUSampler <: Sampleable{Univariate,Continuous} 8 | scale::Float64 9 | end 10 | 11 | rand(rng::AbstractRNG, s::ExponentialLogUSampler) = -s.scale * log(rand(rng)) 12 | -------------------------------------------------------------------------------- /test/qq.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | a = qqbuild(collect(1:10), collect(1:10)) 5 | @test a.qx ≈ collect(1.0:10) 6 | @test a.qy ≈ collect(1.0:10) 7 | 8 | a = qqbuild(collect(1:10), Uniform(1,10)) 9 | @test a.qx ≈ collect(2.0:9) 10 | @test a.qy ≈ collect(2.0:9) 11 | 12 | a = qqbuild(Uniform(1,10), collect(1:10)) 13 | @test a.qx ≈ collect(2.0:9) 14 | @test a.qy ≈ collect(2.0:9) 15 | -------------------------------------------------------------------------------- /test/truncated_uniform.jl: -------------------------------------------------------------------------------- 1 | using Distributions, Test 2 | 3 | @testset "truncated uniform" begin 4 | # just test equivalence of truncation results 5 | u = Uniform(1, 2) 6 | @test truncated(u, -Inf, Inf) == u 7 | @test truncated(u, 0, Inf) == u 8 | @test truncated(u, -Inf, 2.1) == u 9 | @test truncated(u, 1.1, Inf) == Uniform(1.1, 2) 10 | @test truncated(u, 1.1, 2.1) == Uniform(1.1, 2) 11 | @test truncated(u, 1.1, 1.9) == Uniform(1.1, 1.9) 12 | end 13 | -------------------------------------------------------------------------------- /test/noncentralt.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | 5 | @testset "non-central T-Distributions" begin 6 | ν = 50 7 | λ = -5 8 | 9 | d = NoncentralT(ν, λ) 10 | @test_throws MethodError NoncentralT(ν, complex(λ)) 11 | @test d == typeof(d)(params(d)...) 12 | @test d == deepcopy(d) 13 | @test mean(d) ≈ -5.0766 atol = 1e-5 14 | 15 | λ = 5 16 | d = NoncentralT(ν, λ) 17 | @test mean(d) ≈ 5.0766 atol=1e-5 18 | 19 | λ = 0 20 | d = NoncentralT(ν, λ) 21 | @test var(d) == var(TDist(ν)) 22 | end 23 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # Distributions Package 2 | 3 | The *Distributions* package provides a large collection of probabilistic distributions and related functions. Particularly, *Distributions* implements: 4 | 5 | * Sampling from distributions 6 | * Moments (*e.g* mean, variance, skewness, and kurtosis), entropy, and other properties 7 | * Probability density/mass functions (pdf) and their logarithm (logpdf) 8 | * Moment-generating functions and characteristic functions 9 | * Maximum likelihood estimation 10 | * Distribution composition (Cartesian product of distributions, truncated distributions) 11 | -------------------------------------------------------------------------------- /src/truncated/exponential.jl: -------------------------------------------------------------------------------- 1 | ##### 2 | ##### Truncated exponential distribition 3 | ##### 4 | 5 | function mean(d::Truncated{<:Exponential,Continuous}) 6 | θ = d.untruncated.θ 7 | l, r = extrema(d) # l is always finite 8 | if isfinite(r) 9 | if abs(l-r) ≤ √eps()*θ 10 | # linear is a good approximation, just take the midpoint 11 | middle(l, r) 12 | else 13 | Δ = r - l 14 | R = -Δ/θ 15 | θ + middle(l, r) + Δ/2*(exp(R)+1)/expm1(R) 16 | end 17 | else 18 | θ + l 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/ref/continuous/studentizedrange.R: -------------------------------------------------------------------------------- 1 | 2 | StudentizedRange <- R6Class("StudentizedRange", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu", "k"), 6 | nu = NA, 7 | k = NA, 8 | initialize = function(nu, k) { 9 | self$nu <- nu 10 | self$k <- k 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | d1 <- self$nu 15 | k <- self$k 16 | }, 17 | cdf = function(x) { ptukey(x, self$k, self$nu) }, 18 | quan = function(v) { qtukey(v, self$k, self$nu) } 19 | ) 20 | ) 21 | -------------------------------------------------------------------------------- /test/ref/continuous/noncentralt.R: -------------------------------------------------------------------------------- 1 | 2 | NoncentralT <- R6Class("NoncentralT", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu", "ncp"), 6 | nu = NA, 7 | ncp = NA, 8 | initialize = function(k, ncp) { 9 | self$nu <- k 10 | self$ncp <- ncp 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { list() }, 14 | pdf = function(x, log=FALSE) { dt(x, self$nu, self$ncp, log=log) }, 15 | cdf = function(x) { pt(x, self$nu, self$ncp) }, 16 | quan = function(v) { qt(v, self$nu, self$ncp) } 17 | ) 18 | ) 19 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, Distributions 2 | import Random: AbstractRNG, rand! 3 | 4 | makedocs( 5 | sitename = "Distributions.jl", 6 | modules = [Distributions], 7 | doctest = false, 8 | pages = [ 9 | "index.md", 10 | "starting.md", 11 | "types.md", 12 | "univariate.md", 13 | "truncate.md", 14 | "multivariate.md", 15 | "matrix.md", 16 | "mixture.md", 17 | "fit.md", 18 | "extends.md", 19 | ] 20 | ) 21 | 22 | deploydocs( 23 | repo = "github.com/JuliaStats/Distributions.jl.git", 24 | versions = ["stable" => "v^", "v#.#", "dev" => "master"] 25 | ) 26 | -------------------------------------------------------------------------------- /src/estimators.jl: -------------------------------------------------------------------------------- 1 | # uniform interface for model estimation 2 | 3 | export Estimator, MLEstimator 4 | export nsamples, estimate 5 | 6 | abstract type Estimator{D<:Distribution} end 7 | 8 | nsamples(e::Estimator{D}, x::Array) where {D<:UnivariateDistribution} = length(x) 9 | nsamples(e::Estimator{D}, x::Matrix) where {D<:MultivariateDistribution} = size(x, 2) 10 | 11 | mutable struct MLEstimator{D<:Distribution} <: Estimator{D} end 12 | MLEstimator(::Type{D}) where {D<:Distribution} = MLEstimator{D}() 13 | 14 | estimate(e::MLEstimator{D}, x) where {D<:Distribution} = fit_mle(D, x) 15 | estimate(e::MLEstimator{D}, x, w) where {D<:Distribution} = fit_mle(D, x, w) 16 | -------------------------------------------------------------------------------- /test/ref/continuous/noncentralchisq.R: -------------------------------------------------------------------------------- 1 | 2 | NoncentralChisq <- R6Class("NoncentralChisq", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu", "ncp"), 6 | nu = NA, 7 | ncp = NA, 8 | initialize = function(k, ncp) { 9 | self$nu <- k 10 | self$ncp <- ncp 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { list() }, 14 | pdf = function(x, log=FALSE) { dchisq(x, self$nu, self$ncp, log=log) }, 15 | cdf = function(x) { pchisq(x, self$nu, self$ncp) }, 16 | quan = function(v) { qchisq(v, self$nu, self$ncp) } 17 | ) 18 | ) 19 | -------------------------------------------------------------------------------- /test/kolmogorov.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | 5 | d = Kolmogorov() 6 | # compare to Smirnov's tabulated values 7 | @test round(cdf(d,0.28), digits=6) == .000_001 8 | @test round(cdf(d,0.50), digits=6) == .036_055 9 | @test round(cdf(d,0.99), digits=6) == .719_126 10 | @test round(cdf(d,1.00), digits=6) == .730_000 11 | @test round(cdf(d,1.01), digits=6) == .740_566 12 | # @test round(cdf(d,1.04),digits=6) == .770_434 # table value appears to be wrong 13 | @test round(cdf(d,1.50), digits=6) == .977_782 14 | @test round(cdf(d,2.00), digits=6) == .999_329 15 | @test round(cdf(d,2.50), digits=7) == .999_992_5 16 | @test round(cdf(d,3.00), digits=8) == .999_999_97 17 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | 3 | on: 4 | schedule: 5 | - cron: '00 * * * *' 6 | 7 | jobs: 8 | CompatHelper: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | julia-version: [1.2.0] 13 | julia-arch: [x86] 14 | os: [ubuntu-latest] 15 | steps: 16 | - uses: julia-actions/setup-julia@latest 17 | with: 18 | version: ${{ matrix.julia-version }} 19 | - name: Pkg.add("CompatHelper") 20 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 21 | - name: CompatHelper.main() 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | run: julia -e 'using CompatHelper; CompatHelper.main()' 25 | -------------------------------------------------------------------------------- /test/ref/continuous/noncentralf.R: -------------------------------------------------------------------------------- 1 | 2 | NoncentralF <- R6Class("NoncentralF", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu1", "nu2", "ncp"), 6 | nu1 = NA, 7 | nu2 = NA, 8 | ncp = NA, 9 | initialize = function(k1, k2, ncp) { 10 | self$nu1 <- k1 11 | self$nu2 <- k2 12 | self$ncp <- ncp 13 | }, 14 | supp = function() { c(0, Inf) }, 15 | properties = function() { list() }, 16 | pdf = function(x, log=FALSE) { df(x, self$nu1, self$nu2, self$ncp, log=log) }, 17 | cdf = function(x) { pf(x, self$nu1, self$nu2, self$ncp) }, 18 | quan = function(v) { qf(v, self$nu1, self$nu2, self$ncp) } 19 | ) 20 | ) 21 | -------------------------------------------------------------------------------- /test/arcsine.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using Distributions 3 | using ForwardDiff 4 | 5 | @testset "Arcsine" begin 6 | @test_throws ArgumentError Arcsine(5, 3) 7 | d = Arcsine(3, 5) 8 | d2 = Arcsine(3.5f0, 5) 9 | @test partype(d) == Float64 10 | @test partype(d2) == Float32 11 | @test d == deepcopy(d) 12 | 13 | @test logpdf(d, 4.0) ≈ log(pdf(d, 4.0)) 14 | # out of support 15 | @test isinf(logpdf(d, 2.0)) 16 | @test isinf(logpdf(d, 6.0)) 17 | # on support limits 18 | @test isinf(logpdf(d, 3.0)) 19 | @test isinf(logpdf(d, 5.0)) 20 | 21 | # derivative 22 | for v in 3.01:0.1:4.99 23 | fgrad = ForwardDiff.derivative(x -> logpdf(d, x), v) 24 | glog = gradlogpdf(d, v) 25 | @test fgrad ≈ glog 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/ref/continuous/noncentralbeta.R: -------------------------------------------------------------------------------- 1 | 2 | NoncentralBeta <- R6Class("NoncentralBeta", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta", "ncp"), 6 | alpha = NA, 7 | beta = NA, 8 | ncp = NA, 9 | initialize = function(a, b, ncp) { 10 | self$alpha <- a 11 | self$beta <- b 12 | self$ncp <- ncp 13 | }, 14 | supp = function() { c(0, 1) }, 15 | properties = function() { list() }, 16 | pdf = function(x, log=FALSE) { dbeta(x, self$alpha, self$beta, self$ncp, log=log) }, 17 | cdf = function(x) { pbeta(x, self$alpha, self$beta, self$ncp) }, 18 | quan = function(v) { qbeta(v, self$alpha, self$beta, self$ncp) } 19 | ) 20 | ) 21 | -------------------------------------------------------------------------------- /src/univariate/continuous/ksonesided.jl: -------------------------------------------------------------------------------- 1 | """ 2 | KSOneSided(n) 3 | 4 | Distribution of the one-sided Kolmogorov-Smirnov test statistic: 5 | 6 | ```math 7 | D^+_n = \\sup_x (\\hat{F}_n(x) -F(x)) 8 | ``` 9 | """ 10 | struct KSOneSided <: ContinuousUnivariateDistribution 11 | n::Int 12 | end 13 | 14 | @distr_support KSOneSided 0.0 1.0 15 | 16 | 17 | #### Evaluation 18 | 19 | # formula of Birnbaum and Tingey (1951) 20 | function ccdf(d::KSOneSided, x::Float64) 21 | if x >= 1 22 | return 0.0 23 | elseif x <= 0 24 | return 1.0 25 | end 26 | n = d.n 27 | s = 0.0 28 | for j = 0:floor(Int,n-n*x) 29 | p = x+j/n 30 | s += pdf(Binomial(n,p),j) / p 31 | end 32 | s*x 33 | end 34 | 35 | cdf(d::KSOneSided, x::Float64) = 1 - ccdf(d,x) 36 | -------------------------------------------------------------------------------- /test/ref/discrete/poisson.R: -------------------------------------------------------------------------------- 1 | 2 | Poisson <- R6Class("Poisson", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("lambda"), 6 | lambda = NA, 7 | initialize = function(lambda=1) { 8 | self$lambda <- lambda 9 | }, 10 | supp = function() { c(0, Inf) }, 11 | properties = function() { 12 | lam <- self$lambda 13 | list(rate=lam, 14 | mean=lam, 15 | var=lam, 16 | skewness=1/sqrt(lam), 17 | kurtosis=1/lam) 18 | }, 19 | pdf = function(x, log=FALSE) { dpois(x, self$lambda, log=log) }, 20 | cdf = function(x){ ppois(x, self$lambda) }, 21 | quan = function(v){ qpois(v, self$lambda) } 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /src/samplers/aliastable.jl: -------------------------------------------------------------------------------- 1 | struct AliasTable <: Sampleable{Univariate,Discrete} 2 | accept::Vector{Float64} 3 | alias::Vector{Int} 4 | end 5 | ncategories(s::AliasTable) = length(s.alias) 6 | 7 | function AliasTable(probs::AbstractVector) 8 | n = length(probs) 9 | n > 0 || throw(ArgumentError("The input probability vector is empty.")) 10 | accp = Vector{Float64}(undef, n) 11 | alias = Vector{Int}(undef, n) 12 | StatsBase.make_alias_table!(probs, 1.0, accp, alias) 13 | AliasTable(accp, alias) 14 | end 15 | 16 | function rand(rng::AbstractRNG, s::AliasTable) 17 | i = rand(rng, 1:length(s.alias)) % Int 18 | u = rand(rng) 19 | @inbounds r = u < s.accept[i] ? i : s.alias[i] 20 | r 21 | end 22 | 23 | show(io::IO, s::AliasTable) = @printf(io, "AliasTable with %d entries", ncategories(s)) 24 | -------------------------------------------------------------------------------- /src/qq.jl: -------------------------------------------------------------------------------- 1 | struct QQPair 2 | qx::Vector{Float64} 3 | qy::Vector{Float64} 4 | end 5 | 6 | function qqbuild(x::Vector, y::Vector) 7 | n = min(length(x), length(y)) 8 | grid = [0.0:(1 / (n - 1)):1.0;] 9 | qx = quantile(x, grid) 10 | qy = quantile(y, grid) 11 | return QQPair(qx, qy) 12 | end 13 | 14 | function qqbuild(x::Vector, d::UnivariateDistribution) 15 | n = length(x) 16 | grid = [(1 / (n - 1)):(1 / (n - 1)):(1.0 - (1 / (n - 1)));] 17 | qx = quantile(x, grid) 18 | qd = quantile.(Ref(d), grid) 19 | return QQPair(qx, qd) 20 | end 21 | 22 | function qqbuild(d::UnivariateDistribution, x::Vector) 23 | n = length(x) 24 | grid = [(1 / (n - 1)):(1 / (n - 1)):(1.0 - (1 / (n - 1)));] 25 | qd = quantile.(Ref(d), grid) 26 | qx = quantile(x, grid) 27 | return QQPair(qd, qx) 28 | end 29 | -------------------------------------------------------------------------------- /test/ref/continuous/chisq.R: -------------------------------------------------------------------------------- 1 | 2 | Chisq <- R6Class("Chisq", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu"), 6 | nu = NA, 7 | initialize = function(nu) { 8 | self$nu <- nu 9 | }, 10 | supp = function() { c(0, Inf) }, 11 | properties = function() { 12 | k <- self$nu 13 | list(dof=k, 14 | mean=k, 15 | var=2 * k, 16 | skewness=sqrt(8 / k), 17 | kurtosis=12 / k, 18 | entropy=k / 2 + log(2) + lgamma(k/2) + (1 - k/2) * digamma(k/2)) 19 | }, 20 | pdf = function(x, log=FALSE) { dchisq(x, self$nu, log=log) }, 21 | cdf = function(x) { pchisq(x, self$nu) }, 22 | quan = function(v) { qchisq(v, self$nu) } 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /src/samplers/categorical.jl: -------------------------------------------------------------------------------- 1 | #### naive sampling 2 | 3 | struct CategoricalDirectSampler{T<:Real,Ts<:AbstractVector{T}} <: Sampleable{Univariate,Discrete} 4 | prob::Ts 5 | 6 | function CategoricalDirectSampler{T,Ts}(p::Ts) where { 7 | T<:Real,Ts<:AbstractVector{T}} 8 | isempty(p) && throw(ArgumentError("p is empty.")) 9 | new{T,Ts}(p) 10 | end 11 | end 12 | 13 | CategoricalDirectSampler(p::Ts) where {T<:Real,Ts<:AbstractVector{T}} = 14 | CategoricalDirectSampler{T,Ts}(p) 15 | 16 | ncategories(s::CategoricalDirectSampler) = length(s.prob) 17 | 18 | function rand(rng::AbstractRNG, s::CategoricalDirectSampler) 19 | p = s.prob 20 | n = length(p) 21 | i = 1 22 | c = p[1] 23 | u = rand(rng) 24 | while c < u && i < n 25 | c += p[i += 1] 26 | end 27 | return i 28 | end 29 | -------------------------------------------------------------------------------- /test/negativebinomial.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test, ForwardDiff 3 | 4 | # Currently, most of the tests for NegativeBinomail are in the "ref" folder. 5 | # Eventually, we might want to consolidate the tests here 6 | 7 | mydiffp(r, p, k) = r/p - k/(1 - p) 8 | 9 | @testset "NegativeBinomial r=$r, p=$p, k=$k" for 10 | p in exp10.(-10:0) .- eps(), # avoid p==1 since it's not differentiable 11 | r in exp10.(range(-10, stop=2, length=25)), 12 | k in (0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024) 13 | 14 | @test ForwardDiff.derivative(_p -> logpdf(NegativeBinomial(r, _p), k), p) ≈ mydiffp(r, p, k) rtol=1e-12 atol=1e-12 15 | end 16 | 17 | @testset "Check the corner case p==1" begin 18 | @test logpdf(NegativeBinomial(0.5, 1.0), 0) === 0.0 19 | @test logpdf(NegativeBinomial(0.5, 1.0), 1) === -Inf 20 | end 21 | -------------------------------------------------------------------------------- /test/ref/continuous/cauchy.R: -------------------------------------------------------------------------------- 1 | 2 | Cauchy <- R6Class("Cauchy", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { 14 | list(location=self$mu, 15 | scale=self$sigma, 16 | median=self$mu, 17 | entropy=log(self$sigma) + log(4 * pi)) 18 | }, 19 | pdf = function(x, log=FALSE) { dcauchy(x, self$mu, self$sigma, log=log) }, 20 | cdf = function(x) { pcauchy(x, self$mu, self$sigma) }, 21 | quan = function(v) { qcauchy(v, self$mu, self$sigma) } 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /test/edgecases.jl: -------------------------------------------------------------------------------- 1 | using Test, Distributions, StatsBase 2 | 3 | @testset "TDist(Inf) is Normal(0,1)" begin 4 | T = TDist(Inf) 5 | N = Normal(0, 1) 6 | 7 | x = rand(N) 8 | z = rand(T, 10000000) 9 | 10 | @test mean(T) == mean(N) 11 | @test median(T) == median(N) 12 | @test mode(T) == mode(N) 13 | @test var(T) == var(N) 14 | @test skewness(T) == skewness(N) 15 | @test kurtosis(T) == kurtosis(N) 16 | @test entropy(T) == entropy(N) 17 | @test pdf(T, x) ≈ pdf(N, x) 18 | @test logpdf(T, x) ≈ logpdf(N, x) 19 | @test gradlogpdf(T, x) ≈ gradlogpdf(N, x) 20 | @test cf(T, x) ≈ cf(N, x) 21 | 22 | fnecdf = ecdf(z) 23 | y = [-1.96, -1.644854, -1.281552, -0.6744898, 0, 0.6744898, 1.281552, 1.644854, 1.96] 24 | @test isapprox(fnecdf(y), [0.025, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.975], atol=1e-3) 25 | end 26 | -------------------------------------------------------------------------------- /test/ref/discrete/geometric.R: -------------------------------------------------------------------------------- 1 | 2 | Geometric <- R6Class("Geometric", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("p"), 6 | p = NA, 7 | initialize = function(p=0.5) { 8 | self$p <- p 9 | }, 10 | supp = function(){ c(0, Inf) }, 11 | properties = function() { 12 | p <- self$p 13 | list(succprob=p, 14 | failprob=1.0-p, 15 | mean=(1-p)/p, 16 | var=(1-p)/p^2, 17 | skewness=(2-p)/sqrt(1-p), 18 | kurtosis=6 + p^2/(1-p), 19 | entropy=-((1-p)*log(1-p) + p * log(p))/p) 20 | }, 21 | pdf = function(x, log=FALSE) { dgeom(x, self$p, log=log) }, 22 | cdf = function(x) { pgeom(x, self$p) }, 23 | quan = function(v){ qgeom(v, self$p) } 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /src/samplers.jl: -------------------------------------------------------------------------------- 1 | # delegation of samplers 2 | 3 | """ 4 | rand!(::AbstractRNG, ::Sampleable, ::AbstractArray) 5 | 6 | Samples in-place from the sampler and stores the result in the provided array. 7 | """ 8 | rand!(::AbstractRNG, ::Sampleable, ::AbstractArray) 9 | 10 | """ 11 | rand(::AbstractRNG, ::Sampleable) 12 | 13 | Samples from the sampler and returns the result. 14 | """ 15 | rand(::AbstractRNG, ::Sampleable) 16 | 17 | for fname in ["aliastable.jl", 18 | "binomial.jl", 19 | "poissonbinomial.jl", 20 | "poisson.jl", 21 | "exponential.jl", 22 | "gamma.jl", 23 | "multinomial.jl", 24 | "vonmises.jl", 25 | "vonmisesfisher.jl", 26 | "discretenonparametric.jl", 27 | "categorical.jl"] 28 | 29 | include(joinpath("samplers", fname)) 30 | end 31 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/jsonfiles/LKJ_stan_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dims": [3, 3], 4 | "params": [ 5 | [3], 6 | [0.5] 7 | ], 8 | "X": [1, -0.709732768016743, 0.185058141141117, -0.709732768016743, 1, -0.21936085645571, 0.185058141141117, -0.21936085645571, 1], 9 | "lpdf": [-2.15514455393051] 10 | }, 11 | { 12 | "dims": [3, 3], 13 | "params": [ 14 | [3], 15 | [1] 16 | ], 17 | "X": [1, -0.709732768016743, 0.185058141141117, -0.709732768016743, 1, -0.21936085645571, 0.185058141141117, -0.21936085645571, 1], 18 | "lpdf": [-1.59631259113885] 19 | }, 20 | { 21 | "dims": [3, 3], 22 | "params": [ 23 | [3], 24 | [3.4] 25 | ], 26 | "X": [1, -0.709732768016743, 0.185058141141117, -0.709732768016743, 1, -0.21936085645571, 0.185058141141117, -0.21936085645571, 1], 27 | "lpdf": [-1.649018779555] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/scripts/lkj.R: -------------------------------------------------------------------------------- 1 | library(rstan) 2 | library(jsonlite) 3 | 4 | lkj <- 5 | ' 6 | functions { 7 | real lkj_cor_lpdf(matrix y, real eta) { 8 | return lkj_corr_lpdf(y | eta); 9 | } 10 | matrix lkj_cor_rng(int K, real eta) { 11 | return lkj_corr_rng(K, eta); 12 | } 13 | } 14 | ' 15 | expose_stan_functions(stanc(model_code = lkj)) 16 | 17 | set.seed(8675309) 18 | d <- 3 19 | X <- lkj_cor_rng(d, 1) 20 | shapes <- c(0.5, 1.0, 3.4) 21 | K <- length(shapes) 22 | lkj_output <- vector(mode = "list", length = K) 23 | for (i in 1:K){ 24 | lpdf <- lkj_cor_lpdf(X, shapes[i]) 25 | lkj_output[[i]] <- list("dims" = c(d, d), 26 | "params" = list(d, shapes[i]), 27 | "X" = c(X), 28 | "lpdf" = lpdf) 29 | } 30 | write_json(lkj_output, "jsonfiles/LKJ_stan_output.json", digits = 20, pretty = TRUE) 31 | -------------------------------------------------------------------------------- /test/ref/discrete/skellam.R: -------------------------------------------------------------------------------- 1 | 2 | Skellam <- R6Class("Skellam", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("mu1", "mu2"), 6 | mu1 = NA, 7 | mu2 = NA, 8 | initialize = function(mu1=1, mu2=mu1) { 9 | self$mu1 <- mu1 10 | self$mu2 <- mu2 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { 14 | u1 <- self$mu1 15 | u2 <- self$mu2 16 | list(mean=u1 - u2, 17 | var=u1 + u2, 18 | skewness=(u1 - u2) / (u1 + u2)^1.5, 19 | kurtosis=1/(u1 + u2)) 20 | }, 21 | pdf = function(x, log=FALSE) { skellam::dskellam(x, self$mu1, self$mu2, log=log) }, 22 | cdf = function(x){ skellam::pskellam(x, self$mu1, self$mu2) }, 23 | quan = function(v){ skellam::qskellam(v, self$mu1, self$mu2) } 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /test/truncated_exponential.jl: -------------------------------------------------------------------------------- 1 | using Distributions, Random, Test 2 | 3 | @testset "truncated exponential" begin 4 | d = Exponential(1.5) 5 | l = 1.2 6 | r = 2.7 7 | @test mean(d) ≈ mean(truncated(d, -3.0, Inf)) # non-binding truncation 8 | @test mean(truncated(d, l, Inf)) ≈ mean(d) + l 9 | # test values below calculated using symbolic integration in Maxima 10 | @test mean(truncated(d, 0, r)) ≈ 0.9653092084094841 11 | @test mean(truncated(d, l, r)) ≈ 1.82703493969601 12 | 13 | # all the fun corner cases and numerical quirks 14 | @test mean(truncated(Exponential(1.0), -Inf, 0)) == 0 # degenerate 15 | @test mean(truncated(Exponential(1.0), -Inf, 0+eps())) ≈ 0 atol = eps() # near-degenerate 16 | @test mean(truncated(Exponential(1.0), 1.0, 1.0+eps())) ≈ 1.0 # near-degenerate 17 | @test mean(truncated(Exponential(1e308), 1.0, 1.0+eps())) ≈ 1.0 # near-degenerate 18 | end 19 | -------------------------------------------------------------------------------- /test/vonmises.jl: -------------------------------------------------------------------------------- 1 | # Tests for Von-Mises distribution 2 | 3 | using Distributions 4 | using Test 5 | 6 | function test_vonmises(μ::Float64, κ::Float64) 7 | d = VonMises(μ, κ) 8 | @test length(d) == 1 9 | @test mean(d) == μ 10 | @test median(d) == μ 11 | @test mode(d) == μ 12 | @test d == typeof(d)(params(d)...,d.I0κx) 13 | @test d == deepcopy(d) 14 | @test partype(d) == Float64 15 | # println(d) 16 | 17 | # conversions 18 | @test typeof(convert(VonMises{Float32}, d)) == VonMises{Float32} 19 | 20 | # Support 21 | @test support(d) == RealInterval(d.μ-π,d.μ+π) 22 | @test pdf(d, d.μ-2π) == 0.0 23 | @test pdf(d, d.μ+2π) == 0.0 24 | 25 | end 26 | 27 | 28 | ## General testing 29 | 30 | for (μ, κ) in [(2.0, 1.0), 31 | (2.0, 5.0), 32 | (3.0, 1.0), 33 | (3.0, 5.0), 34 | (5.0, 2.0)] 35 | 36 | test_vonmises(μ, κ) 37 | end 38 | -------------------------------------------------------------------------------- /test/ref/continuous/exponential.R: -------------------------------------------------------------------------------- 1 | 2 | Exponential <- R6Class("Exponential", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("theta"), 6 | theta = NA, 7 | beta = NA, 8 | initialize = function(s=1) { 9 | self$theta <- s 10 | self$beta <- 1 / s 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | s <- self$theta 15 | list(scale=s, 16 | rate=self$beta, 17 | mean=s, 18 | median=s * log(2), 19 | var=s^2, 20 | skewness=2.0, 21 | kurtosis=6.0, 22 | entropy=1.0 + log(s)) 23 | }, 24 | pdf = function(x, log=FALSE) { dexp(x, self$beta, log=log) }, 25 | cdf = function(x) { pexp(x, self$beta) }, 26 | quan = function(v) { qexp(v, self$beta) } 27 | ) 28 | ) 29 | -------------------------------------------------------------------------------- /test/ref/discrete/negativebinomial.R: -------------------------------------------------------------------------------- 1 | 2 | NegativeBinomial <- R6Class("NegativeBinomial", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("r", "p"), 6 | r = NA, 7 | p = NA, 8 | initialize = function(r=1, p=0.5) { 9 | self$r <- r 10 | self$p <- p 11 | }, 12 | supp = function(){ c(0, Inf) }, 13 | properties = function(){ 14 | r <- self$r 15 | p <- self$p 16 | list(succprob=p, 17 | failprob=1.0-p, 18 | mean=(1-p) * r / p, 19 | var=(1-p) * r / p^2, 20 | skewness=(2-p) / sqrt((1-p) * r), 21 | kurtosis=6 / r + p^2 / ((1-p)*r)) 22 | }, 23 | pdf = function(x, log=FALSE){ dnbinom(x, self$r, self$p, log=log)}, 24 | cdf = function(x) { pnbinom(x, self$r, self$p) }, 25 | quan = function(v) { qnbinom(v, self$r, self$p) } 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /src/samplers/discretenonparametric.jl: -------------------------------------------------------------------------------- 1 | """ 2 | DiscreteNonParametricSampler(xs, ps) 3 | 4 | Data structure for efficiently sampling from an arbitrary probability mass 5 | function defined by support `xs` and probabilities `ps`. 6 | """ 7 | struct DiscreteNonParametricSampler{T<:Real, S<:AbstractVector{T}, A<:AliasTable} <: Sampleable{Univariate,Discrete} 8 | support::S 9 | aliastable::A 10 | 11 | function DiscreteNonParametricSampler{T,S}(support::S, probs::AbstractVector{<:Real} 12 | ) where {T<:Real,S<:AbstractVector{T}} 13 | aliastable = AliasTable(probs) 14 | new{T,S,typeof(aliastable)}(support, aliastable) 15 | end 16 | end 17 | 18 | DiscreteNonParametricSampler(support::S, probs::AbstractVector{<:Real} 19 | ) where {T<:Real,S<:AbstractVector{T}} = 20 | DiscreteNonParametricSampler{T,S}(support, probs) 21 | 22 | rand(rng::AbstractRNG, s::DiscreteNonParametricSampler) = 23 | (@inbounds v = s.support[rand(rng, s.aliastable)]; v) 24 | -------------------------------------------------------------------------------- /test/ref/continuous/rayleigh.R: -------------------------------------------------------------------------------- 1 | 2 | Rayleigh <- R6Class("Rayleigh", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("sigma"), 6 | sigma = NA, 7 | initialize = function(s=1) { 8 | self$sigma <- s 9 | }, 10 | supp = function() { c(0, Inf) }, 11 | properties = function() { 12 | s <- self$sigma 13 | list(scale = s, 14 | mean = s * sqrt(pi / 2), 15 | median = s * sqrt(2 * log(2)), 16 | mode = s, 17 | var = s^2 * (4 - pi) / 2, 18 | skewness = 0.631110657818937, 19 | kurtosis = 0.245089300687638, 20 | entropy = 0.94203424217079 + log(s)) 21 | }, 22 | pdf = function(x, log=FALSE) { drayleigh(x, self$sigma, log=log) }, 23 | cdf = function(x){ prayleigh(x, self$sigma) }, 24 | quan = function(v){ qrayleigh(v, self$sigma) } 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /test/ref/discrete/bernoulli.R: -------------------------------------------------------------------------------- 1 | 2 | Bernoulli <- R6Class("Bernoulli", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("p"), 6 | p = NA, 7 | initialize = function(p=0.5) { 8 | self$p <- p 9 | }, 10 | supp = function() { c(0, 1) }, 11 | properties = function() { 12 | p <- self$p 13 | q <- 1.0 - p 14 | xlogx <- function(x) { ifelse(x > 0, x * log(x), 0) } 15 | list(succprob=p, 16 | failprob=q, 17 | mean=p, 18 | median= if(p<=0.5) {0} else {1}, 19 | var=p * q, 20 | skewness=(1 - 2*p) / sqrt(p*q), 21 | kurtosis=(1 - 6*p*q) / (p*q), 22 | entropy=-(xlogx(p) + xlogx(q))) 23 | }, 24 | pdf = function(x, log=FALSE){ dbinom(x, 1, self$p, log=log) }, 25 | cdf = function(x){ pbinom(x, 1, self$p) }, 26 | quan = function(v){ qbinom(v, 1, self$p) } 27 | ) 28 | ) 29 | -------------------------------------------------------------------------------- /test/semicircle.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | d = Semicircle(2.0) 5 | 6 | @test params(d) == (2.0,) 7 | 8 | @test minimum(d) == -2.0 9 | @test maximum(d) == +2.0 10 | @test extrema(d) == (-2.0, 2.0) 11 | 12 | @test mean(d) == .0 13 | @test var(d) == 1.0 14 | @test skewness(d) == .0 15 | @test median(d) == .0 16 | @test mode(d) == .0 17 | @test entropy(d) == 1.33787706640934544458 18 | 19 | @test pdf(d, -5) == .0 20 | @test pdf(d, -2) == .0 21 | @test pdf(d, 0) == .31830988618379067154 22 | @test pdf(d, +2) == .0 23 | @test pdf(d, +5) == .0 24 | 25 | @test logpdf(d, -5) == -Inf 26 | @test logpdf(d, -2) == -Inf 27 | @test logpdf(d, 0) ≈ log(.31830988618379067154) 28 | @test logpdf(d, +2) == -Inf 29 | @test logpdf(d, +5) == -Inf 30 | 31 | @test cdf(d, -5) == .0 32 | @test cdf(d, -2) == .0 33 | @test cdf(d, 0) == .5 34 | @test cdf(d, +2) == 1.0 35 | @test cdf(d, +5) == 1.0 36 | 37 | @test quantile(d, .0) == -2.0 38 | @test quantile(d, .5) == .0 39 | @test quantile(d, 1.0) == +2.0 40 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/scripts/wishart.R: -------------------------------------------------------------------------------- 1 | library(rstan) 2 | library(jsonlite) 3 | 4 | wishart <- 5 | ' 6 | functions { 7 | real wish_lpdf(matrix W, real nu, matrix Sigma) { 8 | return wishart_lpdf(W | nu, Sigma); 9 | } 10 | matrix wish_rng(real nu, matrix Sigma) { 11 | return wishart_rng(nu, Sigma); 12 | } 13 | } 14 | ' 15 | expose_stan_functions(stanc(model_code = wishart)) 16 | 17 | set.seed(8675309) 18 | S <- matrix(c(1.0, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 1.0), 3, 3) 19 | d <- nrow(S) 20 | X <- wish_rng(4, S) 21 | dfs <- c(4.5, 8.2, 10.0) 22 | K <- length(dfs) 23 | wishart_output <- vector(mode = "list", length = K) 24 | for (i in 1:K){ 25 | lpdf <- wish_lpdf(X, dfs[i], S) 26 | wishart_output[[i]] <- list("dims" = c(d, d), 27 | "params" = list(dfs[i], c(S)), 28 | "X" = c(X), 29 | "lpdf" = lpdf) 30 | } 31 | write_json(wishart_output, "jsonfiles/Wishart_stan_output.json", digits = 20, pretty = TRUE) 32 | -------------------------------------------------------------------------------- /test/ref/continuous/laplace.R: -------------------------------------------------------------------------------- 1 | 2 | Laplace <- R6Class("Laplace", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "beta"), 6 | mu = NA, 7 | beta = NA, 8 | initialize = function(u=0, b=1) { 9 | self$mu <- u 10 | self$beta <- b 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | b <- self$beta 16 | list(location = u, 17 | scale = b, 18 | mean = u, 19 | median = u, 20 | mode = u, 21 | var = 2 * b^2, 22 | skewness = 0, 23 | kurtosis = 3, 24 | entropy = 1 + log(2 * b)) 25 | }, 26 | pdf = function(x, log=FALSE){ dlaplace(x, self$mu, self$beta, log=log) }, 27 | cdf = function(x){ plaplace(x, self$mu, self$beta) }, 28 | quan = function(v){ qlaplace(v, self$mu, self$beta) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/discrete/binomial.R: -------------------------------------------------------------------------------- 1 | 2 | Binomial <- R6Class("Binomial", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("n", "p"), 6 | n = NA, 7 | p = NA, 8 | initialize = function(n=1, p=0.5) { 9 | self$n <- n 10 | self$p <- p 11 | }, 12 | supp = function() { c(0, self$n) }, 13 | properties = function() { 14 | n <- self$n 15 | p <- self$p 16 | q <- 1.0 - p 17 | list(succprob=p, 18 | failprob=q, 19 | ntrials=n, 20 | mean=n * p, 21 | median=round(n * p), 22 | var=n * p * q, 23 | skewness=(q - p) / sqrt(n*p*q), 24 | kurtosis=(1 - 6*p*q) / (n*p*q)) 25 | }, 26 | pdf = function(x, log=FALSE) { dbinom(x, self$n, self$p, log=log) }, 27 | cdf = function(x) { pbinom(x, self$n, self$p) }, 28 | quan = function(v) { qbinom(v, self$n, self$p) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/soliton.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | 3 | @testset "Soliton" begin 4 | K, M, δ, atol = 100, 60, 0.2, 0 5 | Ω = Soliton(K, M, δ, atol) 6 | @test pdf(Ω, M) > pdf(Ω, M-1) 7 | @test pdf(Ω, M) > pdf(Ω, M+1) 8 | @test cumsum(pdf.(Ω, 1:K)) ≈ cdf.(Ω, 1:K) 9 | @test cdf(Ω, 0) ≈ 0 10 | @test cdf(Ω, K) ≈ 1 11 | @test mean(Ω) ≈ 7.448826535558562 12 | @test var(Ω) ≈ 178.91717915136957 13 | @test minimum(Ω) == 1 14 | @test maximum(Ω) == K 15 | @test quantile(Ω, 0) == 1 16 | @test quantile(Ω, 1) == K 17 | @test insupport(Ω, 1) && insupport(Ω, 2) && insupport(Ω, K) 18 | @test !insupport(Ω, 0) && !insupport(Ω, 2.1) && !insupport(Ω, K + 1) 19 | 20 | K, M, δ, atol = 100, 60, 0.2, 1e-3 21 | Ω = Soliton(K, M, δ, atol) 22 | ds = [d for d in 1:K if pdf(Ω, d) > 0] 23 | @test all(pdf.(Ω, ds) .> atol) 24 | @test cdf(Ω, 0) ≈ 0 25 | @test cdf(Ω, K) ≈ 1 26 | @test minimum(Ω) == 1 27 | @test maximum(Ω) == K 28 | @test quantile(Ω, 0) == 1 29 | @test quantile(Ω, 1) == M 30 | end 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | os: 3 | - linux 4 | - osx 5 | julia: 6 | - 1.0 7 | - 1.4 8 | - nightly 9 | 10 | notifications: 11 | email: false 12 | 13 | after_success: 14 | - julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder()); Coveralls.submit(process_folder())' 15 | 16 | branches: 17 | only: 18 | - master 19 | - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ 20 | 21 | jobs: 22 | allow_failures: 23 | - julia: nightly 24 | fast_finish: true 25 | include: 26 | - stage: "Documentation" 27 | julia: 1.0 28 | os: linux 29 | script: 30 | - julia --project=perf/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); 31 | Pkg.instantiate()' 32 | - julia --project=perf/ perf/samplers.jl 33 | - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); 34 | Pkg.instantiate()' 35 | - julia --project=docs/ docs/make.jl 36 | after_success: skip 37 | -------------------------------------------------------------------------------- /test/ref/continuous/logistic.R: -------------------------------------------------------------------------------- 1 | 2 | Logistic <- R6Class("Logistic", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | s <- self$sigma 16 | list(location = u, 17 | scale = s, 18 | mean = u, 19 | median = u, 20 | mode = u, 21 | var = s^2 * pi^2 / 3, 22 | skewness = 0, 23 | kurtosis = 1.2, 24 | entropy = log(s) + 2) 25 | }, 26 | pdf = function(x, log=FALSE){ dlogis(x, self$mu, self$sigma, log=log) }, 27 | cdf = function(x){ plogis(x, self$mu, self$sigma) }, 28 | quan = function(v){ qlogis(v, self$mu, self$sigma) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/continuous/inversegaussian.R: -------------------------------------------------------------------------------- 1 | 2 | InverseGaussian = R6Class("InverseGaussian", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "lambda"), 6 | mu = NA, 7 | lambda = NA, 8 | initialize = function(u=1, lambda=1) { 9 | self$mu <- u 10 | self$lambda <- lambda 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | l <- self$lambda 16 | list(shape = l, 17 | mean = u, 18 | mode = u * ( (1 + 9 * u^2 / (4 * l^2))^0.5 - (3 * u) / (2 * l) ), 19 | var = u^3 / l, 20 | skewness = 3 * sqrt(u / l), 21 | kurtosis = 15 * u / l) 22 | }, 23 | pdf = function(x, log=FALSE){ statmod::dinvgauss(x, self$mu, self$lambda, log=log) }, 24 | cdf = function(x){ statmod::pinvgauss(x, self$mu, self$lambda) }, 25 | quan = function(v){ statmod::qinvgauss(v, self$mu, self$lambda) } 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /test/ref/continuous/gumbel.R: -------------------------------------------------------------------------------- 1 | 2 | Gumbel <- R6Class("Gumbel", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "beta"), 6 | mu = NA, 7 | beta = NA, 8 | initialize = function(u=0, b=1) { 9 | self$mu <- u 10 | self$beta <- b 11 | }, 12 | supp = function(){ c(-Inf, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | b <- self$beta 16 | g <- 0.57721566490153286 17 | list(location = u, 18 | scale = b, 19 | mean = u + b * g, 20 | median = u - b * log(log(2)), 21 | mode = u, 22 | var = pi^2 * b^2 / 6, 23 | skewness = 1.13954709940464866, 24 | kurtosis = 2.4) 25 | }, 26 | pdf = function(x, log=FALSE){ dgumbel(x, self$mu, self$beta, log=log) }, 27 | cdf = function(x){ pgumbel(x, self$mu, self$beta) }, 28 | quan = function(v){ qgumbel(v, self$mu, self$beta) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/continuous/normal.R: -------------------------------------------------------------------------------- 1 | 2 | Normal <- R6Class("Normal", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(-Inf, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | s <- self$sigma 16 | list(mean=u, 17 | var=s^2, 18 | location=u, 19 | scale=s, 20 | skewness=0, 21 | kurtosis=0, 22 | entropy=(log(2 * pi) + 1 + log(s^2))/2) 23 | }, 24 | pdf = function(x, log=FALSE) { dnorm(x, self$mu, self$sigma, log=log) }, 25 | cdf = function(x) { pnorm(x, self$mu, self$sigma) }, 26 | quan = function(v) { qnorm(v, self$mu, self$sigma ) } 27 | ) 28 | ) 29 | 30 | NormalCanon = list( 31 | new = function(c1=0, c2=1) { 32 | Normal$new(c1/c2, 1/sqrt(c2)) 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/scripts/inversewishart.R: -------------------------------------------------------------------------------- 1 | library(rstan) 2 | library(jsonlite) 3 | 4 | invwishart <- 5 | ' 6 | functions { 7 | real inv_wish_lpdf(matrix W, real nu, matrix Sigma) { 8 | return inv_wishart_lpdf(W | nu, Sigma); 9 | } 10 | matrix inv_wish_rng(real nu, matrix Sigma) { 11 | return inv_wishart_rng(nu, Sigma); 12 | } 13 | } 14 | ' 15 | expose_stan_functions(stanc(model_code = invwishart)) 16 | 17 | set.seed(8675309) 18 | S <- matrix(c(1.0, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 1.0), 3, 3) 19 | d <- nrow(S) 20 | X <- inv_wish_rng(4, S) 21 | dfs <- c(4.5, 8.2, 10.0) 22 | K <- length(dfs) 23 | invwishart_output <- vector(mode = "list", length = K) 24 | for (i in 1:K){ 25 | lpdf <- inv_wish_lpdf(X, dfs[i], S) 26 | invwishart_output[[i]] <- list("dims" = c(d, d), 27 | "params" = list(dfs[i], c(S)), 28 | "X" = c(X), 29 | "lpdf" = lpdf) 30 | } 31 | write_json(invwishart_output, "jsonfiles/InverseWishart_stan_output.json", digits = 20, pretty = TRUE) 32 | -------------------------------------------------------------------------------- /test/ref/continuous/chi.R: -------------------------------------------------------------------------------- 1 | 2 | Chi <- R6Class("Chi", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu"), 6 | nu = NA, 7 | initialize = function(nu) { 8 | self$nu <- nu 9 | }, 10 | supp = function() { c(0, Inf) }, 11 | properties = function() { 12 | k <- self$nu 13 | u <- sqrt(2) * gamma((k+1)/2) / gamma(k/2) 14 | v <- k - u^2 15 | m3 <- u / v^1.5 * (1 - 2 * v) 16 | m4 <- (2 / v) * (1 - u * sqrt(v) * m3 - v) 17 | list(dof = k, 18 | mode = ifelse(k >= 1, sqrt(k - 1), NaN), 19 | mean = u, 20 | var = v, 21 | skewness = m3, 22 | kurtosis = m4, 23 | entropy = lgamma(k/2) + (k - log(2) - (k-1)*psigamma(k/2, 0)) / 2 24 | ) 25 | }, 26 | pdf = function(x, log=FALSE) { chi::dchi(x, self$nu, log=log) }, 27 | cdf = function(x) { chi::pchi(x, self$nu) }, 28 | quan = function(v) { chi::qchi(v, self$nu) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/continuous/weibull.R: -------------------------------------------------------------------------------- 1 | 2 | Weibull <- R6Class("Weibull", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "theta"), 6 | alpha = NA, 7 | theta = NA, 8 | initialize = function(a=1, s=1) { 9 | self$alpha <- a 10 | self$theta <- s 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | a <- self$alpha 15 | s <- self$theta 16 | var.val <- s^2 * (gamma(1 + 2 / a) - gamma(1 + 1 / a)^2) 17 | gv <- -digamma(1) 18 | list(shape = a, 19 | scale = s, 20 | mean = s * gamma(1.0 + 1 / a), 21 | median = s * (log(2) ^ (1 / a)), 22 | var = var.val, 23 | entropy = gv * (1 - 1 / a) + log(s / a) + 1) 24 | }, 25 | pdf = function(x, log=FALSE) { dweibull(x, self$alpha, self$theta, log=log) }, 26 | cdf = function(x) { pweibull(x, self$alpha, self$theta) }, 27 | quan = function(v) { qweibull(v, self$alpha, self$theta) } 28 | ) 29 | ) 30 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/jsonfiles/InverseWishart_stan_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dims": [3, 3], 4 | "params": [ 5 | [4.5], 6 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 7 | ], 8 | "X": [1.80115497975516, 1.39357092294611, 0.679396397136899, 1.39357092294611, 1.32682592836847, 0.624106043062951, 0.679396397136899, 0.624106043062951, 0.431228950208813], 9 | "lpdf": [-0.598439090738466] 10 | }, 11 | { 12 | "dims": [3, 3], 13 | "params": [ 14 | [8.2], 15 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 16 | ], 17 | "X": [1.80115497975516, 1.39357092294611, 0.679396397136899, 1.39357092294611, 1.32682592836847, 0.624106043062951, 0.679396397136899, 0.624106043062951, 0.431228950208813], 18 | "lpdf": [-4.62691092488684] 19 | }, 20 | { 21 | "dims": [3, 3], 22 | "params": [ 23 | [10], 24 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 25 | ], 26 | "X": [1.80115497975516, 1.39357092294611, 0.679396397136899, 1.39357092294611, 1.32682592836847, 0.624106043062951, 0.679396397136899, 0.624106043062951, 0.431228950208813], 27 | "lpdf": [-8.00783683273594] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /test/ref/matrixvariates/jsonfiles/Wishart_stan_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dims": [3, 3], 4 | "params": [ 5 | [4.5], 6 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 7 | ], 8 | "X": [1.99999655903368, -0.167167443909772, 0.295135013769298, -0.167167443909772, 1.34195749133266, -0.951505045533196, 0.295135013769298, -0.951505045533196, 2.23638578347992], 9 | "lpdf": [-9.0208647061427] 10 | }, 11 | { 12 | "dims": [3, 3], 13 | "params": [ 14 | [8.2], 15 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 16 | ], 17 | "X": [1.99999655903368, -0.167167443909772, 0.295135013769298, -0.167167443909772, 1.34195749133266, -0.951505045533196, 0.295135013769298, -0.951505045533196, 2.23638578347992], 18 | "lpdf": [-13.0493365402911] 19 | }, 20 | { 21 | "dims": [3, 3], 22 | "params": [ 23 | [10], 24 | [1, 0.5, 0.5, 0.5, 1, 0.5, 0.5, 0.5, 1] 25 | ], 26 | "X": [1.99999655903368, -0.167167443909772, 0.295135013769298, -0.167167443909772, 1.34195749133266, -0.951505045533196, 0.295135013769298, -0.951505045533196, 2.23638578347992], 27 | "lpdf": [-16.4302624481402] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /test/ref/continuous/levy.R: -------------------------------------------------------------------------------- 1 | 2 | Levy <- R6Class("Levy", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(self$mu, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | s <- self$sigma 16 | erfcinv <- function (x) qnorm(x/2, lower = FALSE)/sqrt(2) 17 | list(location = u, 18 | mode = u + s / 3, 19 | mean = Inf, 20 | var = Inf, 21 | skewness = NaN, 22 | kurtosis = NaN, 23 | # 0.47693627620447 = erfc^{-1}(0.5) 24 | median = u + (s/2) / (0.47693627620447)^2) 25 | }, 26 | pdf = function(x, log=FALSE){ VGAM::dlevy(x, self$mu, self$sigma, log.arg=log) }, 27 | cdf = function(x) { VGAM::plevy(x, self$mu, self$sigma) }, 28 | quan = function(v) { VGAM::qlevy(v, self$mu, self$sigma) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/continuous/tdist.R: -------------------------------------------------------------------------------- 1 | 2 | TDist <- R6Class("TDist", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu"), 6 | nu = NA, 7 | initialize = function(nu) { 8 | self$nu <- nu 9 | }, 10 | supp = function() { c(-Inf, Inf) }, 11 | properties = function() { 12 | nu <- self$nu 13 | a <- (nu + 1) / 2 14 | ent.val <- a * (digamma(a) - digamma(nu / 2)) + 15 | log(nu) / 2 + lbeta(nu / 2, 1 / 2) 16 | list(dof=nu, 17 | mean=ifelse(nu > 1, 0, NaN), 18 | median=0, 19 | var=ifelse(nu > 2, nu / (nu - 2), 20 | ifelse(nu > 1, Inf, NaN)), 21 | skewness = ifelse(nu > 3, 0, NaN), 22 | kurtosis = ifelse(nu > 4, 6 / (nu - 4), 23 | ifelse(nu > 2, Inf, NaN)), 24 | entropy = ent.val) 25 | }, 26 | pdf = function(x, log=FALSE) { dt(x, self$nu, log=log) }, 27 | cdf = function(x) { pt(x, self$nu) }, 28 | quan = function(v) { qt(v, self$nu) } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /test/ref/continuous/gamma.R: -------------------------------------------------------------------------------- 1 | 2 | Gamma <- R6Class("Gamma", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "theta"), 6 | alpha = NA, 7 | theta = NA, 8 | beta = NA, 9 | initialize = function(a=1, s=1) { 10 | self$alpha <- a 11 | self$theta <- s 12 | self$beta <- 1 / s 13 | }, 14 | supp = function() { c(0, Inf) }, 15 | properties = function() { 16 | a <- self$alpha 17 | s <- self$theta 18 | list(shape=a, 19 | scale=s, 20 | rate=1/s, 21 | mean=a * s, 22 | var=a * s^2, 23 | skewness=2 / sqrt(a), 24 | kurtosis=6 / a, 25 | entropy=a + log(s) + lgamma(a) + (1 - a) * digamma(a) 26 | ) 27 | }, 28 | pdf = function(x, log=FALSE) { dgamma(x, self$alpha, self$beta, log=log) }, 29 | cdf = function(x) { pgamma(x, self$alpha, self$beta) }, 30 | quan = function(v) { qgamma(v, self$alpha, self$beta) } 31 | ) 32 | ) 33 | 34 | Erlang = list(new = Gamma$new) 35 | -------------------------------------------------------------------------------- /test/binomial.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test, Random 3 | 4 | 5 | # Test the consistency between the recursive and nonrecursive computation of the pdf 6 | # of the Binomial distribution 7 | Random.seed!(1234) 8 | for (p, n) in [(0.6, 10), (0.8, 6), (0.5, 40), (0.04, 20), (1., 100), (0., 10), (0.999999, 1000), (1e-7, 1000)] 9 | local p 10 | 11 | d = Binomial(n, p) 12 | 13 | a = pdf.(d, 0:n) 14 | for t=0:n 15 | @test pdf(d, t) ≈ a[1+t] 16 | end 17 | 18 | li = rand(0:n, 2) 19 | rng = minimum(li):maximum(li) 20 | b = pdf.(d, rng) 21 | for t in rng 22 | @test pdf(d, t) ≈ b[t - first(rng) + 1] 23 | end 24 | 25 | end 26 | 27 | # Test calculation of expectation value for Binomial distribution 28 | @test Distributions.expectation(Binomial(6), identity) ≈ 3.0 29 | @test Distributions.expectation(Binomial(10, 0.2), x->-x) ≈ -2.0 30 | 31 | # Test mode 32 | @test Distributions.mode(Binomial(100, 0.4)) == 40 33 | @test Distributions.mode(Binomial(1, 0.51)) == 1 34 | @test Distributions.mode(Binomial(1, 0.49)) == 0 35 | 36 | @test isplatykurtic(Bernoulli(0.5)) 37 | @test ismesokurtic(Normal(0.0, 1.0)) 38 | @test isleptokurtic(Laplace(0.0, 1.0)) 39 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - julia_version: 1 4 | - julia_version: nightly 5 | 6 | platform: 7 | - x86 8 | - x64 9 | 10 | # # Uncomment the following lines to allow failures on nightly julia 11 | # # (tests will run but not make your overall status red) 12 | matrix: 13 | allow_failures: 14 | - julia_version: nightly 15 | 16 | branches: 17 | only: 18 | - master 19 | - /release-.*/ 20 | 21 | notifications: 22 | - provider: Email 23 | on_build_success: false 24 | on_build_failure: false 25 | on_build_status_changed: false 26 | 27 | install: 28 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) 29 | 30 | build_script: 31 | - echo "%JL_BUILD_SCRIPT%" 32 | - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" 33 | 34 | test_script: 35 | - echo "%JL_TEST_SCRIPT%" 36 | - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" 37 | 38 | # # Uncomment to support code coverage upload. Should only be enabled for packages 39 | # # which would have coverage gaps without running on Windows 40 | # on_success: 41 | # - echo "%JL_CODECOV_SCRIPT%" 42 | # - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" 43 | -------------------------------------------------------------------------------- /test/ref/continuous/lognormal.R: -------------------------------------------------------------------------------- 1 | 2 | LogNormal <- R6Class("LogNormal", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | u <- self$mu 15 | s <- self$sigma 16 | es2 <- exp(s^2) 17 | list(meanlogx = u, 18 | varlogx = s^2, 19 | stdlogx = s, 20 | mean = exp(u + s^2/2), 21 | median = exp(u), 22 | mode = exp(u - s^2), 23 | var = (es2 - 1) * exp(2 * u + s^2), 24 | skewness = (es2 + 2) * sqrt(es2 - 1), 25 | kurtosis = es2^4 + 2 * es2^3 + 3 * es2^2 - 6, 26 | entropy = (u + 1/2) + log(sqrt(2 * pi) * s)) 27 | }, 28 | pdf = function(x, log=FALSE){ dlnorm(x, self$mu, self$sigma, log=log) }, 29 | cdf = function(x){ plnorm(x, self$mu, self$sigma) }, 30 | quan = function(v){ qlnorm(v, self$mu, self$sigma) } 31 | ) 32 | ) 33 | -------------------------------------------------------------------------------- /test/ref/continuous/uniform.R: -------------------------------------------------------------------------------- 1 | 2 | Uniform <- R6Class("Uniform", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("a", "b"), 6 | a = NA, 7 | b = NA, 8 | initialize = function(a1=NA, a2=NA) { 9 | if (is.na(a1)) { 10 | a <- 0; b <- 1 11 | } else if (is.na(a2)) { 12 | a <- 0; b <- a1 13 | } else { 14 | a <- a1; b <- a2 15 | } 16 | self$a <- a 17 | self$b <- b 18 | }, 19 | supp = function() { c(self$a, self$b) }, 20 | properties = function() { 21 | a <- self$a 22 | b <- self$b 23 | list(location = a, 24 | scale = b - a, 25 | mean = (a + b) / 2, 26 | median = (a + b) / 2, 27 | var = (b - a)^2 / 12, 28 | skewness = 0, 29 | kurtosis = -6 / 5, 30 | entropy = log(b - a)) 31 | }, 32 | pdf = function(x, log=FALSE) { dunif(x, self$a, self$b, log=log) }, 33 | cdf = function(x) { punif(x, self$a, self$b) }, 34 | quan = function(v) { qunif(v, self$a, self$b) } 35 | ) 36 | ) 37 | -------------------------------------------------------------------------------- /test/multivariate_stats.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | 5 | const n_samples = 5_000_001 6 | 7 | mu = [1.0, 2.0, 3.0] 8 | C = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.] 9 | h = mu 10 | J = C 11 | 12 | 13 | for d in [ 14 | Dirichlet(3, 2.0), 15 | Dirichlet([2.0, 1.0, 3.0]), 16 | IsoNormal(mu, 2.0), 17 | DiagNormal(mu, [1.5, 2.0, 2.5]), 18 | MvNormal(mu, C), 19 | IsoNormalCanon(h, 2.0), 20 | DiagNormalCanon(h, [1.5, 2.0, 1.2]), 21 | MvNormalCanon(h, J)] 22 | 23 | println(d) 24 | dmean = mean(d) 25 | dcov = cov(d) 26 | dent = entropy(d) 27 | 28 | x = rand(d, n_samples) 29 | xmean = vec(mean(x, 2)) 30 | z = x .- xmean 31 | xcov = (z * z') * (1 / n_samples) 32 | 33 | lp = logpdf(d, x) 34 | xent = -mean(lp) 35 | 36 | println("expected mean = $dmean") 37 | println("empirical mean = $xmean") 38 | println("--> abs.dev = $(maximum(abs(dmean - xmean)))") 39 | 40 | println("expected cov = $dcov") 41 | println("empirical cov = $xcov") 42 | println("--> abs.dev = $(maximum(abs(dcov - xcov)))") 43 | 44 | println("expected entropy = $dent") 45 | println("empirical entropy = $xent") 46 | println("--> abs.dev = $(abs(dent - xent))") 47 | 48 | println() 49 | end 50 | -------------------------------------------------------------------------------- /test/ref/continuous/betaprime.R: -------------------------------------------------------------------------------- 1 | 2 | BetaPrime <- R6Class("BetaPrime", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta"), 6 | alpha = NA, 7 | beta = NA, 8 | initialize = function(a=1, b=a) { 9 | self$alpha <- a 10 | self$beta <- b 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | a <- self$alpha 15 | b <- self$beta 16 | list(mean = if (b > 1) { a / (b - 1) } else { NaN }, 17 | mode = if (a > 1) { 18 | (a - 1) / (b + 1) 19 | } else { 0 }, 20 | var = if (b > 2) { 21 | a * (a + b - 1) / ((b - 2) * (b - 1)^2) 22 | } else { NaN }, 23 | skewness = if (b > 3) { 24 | 2 * (2 * a + b - 1) / (b - 3) * 25 | sqrt((b - 2) / (a * (a + b - 1))) 26 | } else { NaN }) 27 | }, 28 | pdf = function(x, log=FALSE){ dbetapr(x, self$alpha, self$beta, log=log) }, 29 | cdf = function(x) { pbetapr(x, self$alpha, self$beta) }, 30 | quan = function(v) { qbetapr(v, self$alpha, self$beta) } 31 | ) 32 | ) 33 | -------------------------------------------------------------------------------- /test/ref/continuous/fdist.R: -------------------------------------------------------------------------------- 1 | 2 | FDist <- R6Class("FDist", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("nu1", "nu2"), 6 | nu1 = NA, 7 | nu2 = NA, 8 | initialize = function(nu1, nu2) { 9 | self$nu1 <- nu1 10 | self$nu2 <- nu2 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | d1 <- self$nu1 15 | d2 <- self$nu2 16 | list(mean = if (d2 > 2) { d2 / (d2 - 2) } else { NaN }, 17 | mode = if (d1 > 2) { 18 | (d1 - 2) * d2 / (d1 * (d2 + 2)) 19 | } else { 0 }, 20 | var = if (d2 > 4) { 21 | 2 * d2^2 * (d1 + d2 - 2) / 22 | ( d1 * (d2 - 2)^2 * (d2 - 4) ) 23 | } else { NaN }, 24 | skewness = if (d2 > 6) { 25 | (2 * d1 + d2 - 2) * sqrt(8 * (d2 - 4)) / 26 | ( (d2 - 6) * sqrt(d1 * (d1 + d2 - 2)) ) 27 | } else { NaN }) 28 | }, 29 | pdf = function(x, log=FALSE) { df(x, self$nu1, self$nu2, log=log) }, 30 | cdf = function(x) { pf(x, self$nu1, self$nu2) }, 31 | quan = function(v) { qf(v, self$nu1, self$nu2) } 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /test/ref/continuous/arcsine.R: -------------------------------------------------------------------------------- 1 | 2 | Arcsine <- R6Class("Arcsine", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("a", "b"), 6 | a = NA, 7 | b = NA, 8 | rd = NA, # R distr object 9 | initialize = function(a1=NA, a2=NA) { 10 | if (is.na(a1)) { 11 | a <- 0; b <- 1 12 | } else if (is.na(a2)) { 13 | a <- 0; b <- a1 14 | } else { 15 | a <- a1; b <- a2 16 | } 17 | self$a <- a 18 | self$b <- b 19 | self$rd <- distr::Arcsine() * ((b-a)/2) + ((a+b)/2) 20 | }, 21 | supp = function() { c(self$a, self$b) }, 22 | properties = function() { 23 | s <- self$b - self$a 24 | list(location=self$a, 25 | scale=s, 26 | mean=(self$a + self$b) * 0.5, 27 | var=1/8 * s^2, 28 | skewness=0, 29 | kurtosis=-1.5, 30 | median=(self$a + self$b) * 0.5, 31 | entropy=log(pi/4) + log(s)) 32 | }, 33 | pdf = function(x, log=FALSE) { distr::d(self$rd)(x, log=log) }, 34 | cdf = function(x) { distr::p(self$rd)(x) }, 35 | quan = function(v) { distr::q(self$rd)(v) } 36 | ) 37 | ) 38 | -------------------------------------------------------------------------------- /test/ref/discrete/betabinomial.R: -------------------------------------------------------------------------------- 1 | 2 | BetaBinomial <- R6Class("BetaBinomial", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("n", "alpha", "beta"), 6 | n = NA, 7 | alpha = NA, 8 | beta = NA, 9 | probs = NA, 10 | initialize = function(n, a, b) { 11 | self$n <- n 12 | self$alpha <- a 13 | self$beta <- b 14 | self$probs <- dbbinom(0:n, self$n, self$alpha, self$beta) 15 | }, 16 | supp = function() { c(0, self$n) }, 17 | properties = function() { 18 | n <- self$n 19 | a <- self$alpha 20 | b <- self$beta 21 | u <- a + b 22 | v1 <- n * a * b * (a+b+n) 23 | kur.orig <- u^2 * (1+u) / (v1 * (u+2) * (u+3)) * ( 24 | u*(u-1+6*n) + 3*a*b*(n-2) + 6*n^2 - 25 | 3*a*b*n*(6-n)/u - 18*a*b*n^2/u^2) 26 | list(ntrials = n, 27 | mean = n * a / u, 28 | var = v1 / u^2 / (u+1), 29 | skewness = (u+2*n)*(b-a)/(u+2) * sqrt((u+1)/v1), 30 | kurtosis = kur.orig-3) 31 | }, 32 | pdf = function(x, log=FALSE){ dbbinom(x, self$n, self$alpha, self$beta, log=log) }, 33 | cdf = function(x) { pbbinom(x, self$n, self$alpha, self$beta) }, 34 | quan = function(v) { qcat(v, self$probs) - 1 } 35 | ) 36 | ) 37 | -------------------------------------------------------------------------------- /test/ref/continuous/beta.R: -------------------------------------------------------------------------------- 1 | 2 | Beta <- R6Class("Beta", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta"), 6 | alpha = NA, 7 | beta = NA, 8 | initialize = function(a=1, b=a) { 9 | self$alpha <- a 10 | self$beta <- b 11 | }, 12 | supp = function() { c(0.0, 1.0) }, 13 | properties = function() { 14 | a <- self$alpha 15 | b <- self$beta 16 | skew <- 2 * (b - a) * sqrt(a + b + 1) / (a + b + 2) / sqrt(a * b) 17 | kurt.num <- 6 * ((a - b)^2 * (a + b + 1) - a * b * (a + b + 2)) 18 | kurt.den <- a * b * (a + b + 2) * (a + b + 3) 19 | ent <- lbeta(a, b) - (a - 1) * digamma(a) - (b - 1) * digamma(b) + 20 | (a + b - 2) * digamma(a + b) 21 | list(mean=a / (a + b), 22 | meanlogx=digamma(a) - digamma(a + b), 23 | var=(a * b) / (a + b)^2 / (a + b + 1.0), 24 | varlogx=trigamma(a) - trigamma(a + b), 25 | skewness=skew, 26 | kurtosis=kurt.num / kurt.den, 27 | entropy=ent) 28 | }, 29 | pdf = function(x, log=FALSE){ dbeta(x, self$alpha, self$beta, log=log) }, 30 | cdf = function(x) { pbeta(x, self$alpha, self$beta) }, 31 | quan = function(v) { qbeta(v, self$alpha, self$beta) } 32 | ) 33 | ) 34 | -------------------------------------------------------------------------------- /src/genericfit.jl: -------------------------------------------------------------------------------- 1 | # generic functions for distribution fitting 2 | 3 | function suffstats(dt::Type{D}, xs...) where D<:Distribution 4 | argtypes = tuple(D, map(typeof, xs)...) 5 | error("suffstats is not implemented for $argtypes.") 6 | end 7 | 8 | """ 9 | fit_mle(D, x) 10 | 11 | Fit a distribution of type `D` to a given data set `x`. 12 | 13 | - For univariate distribution, x can be an array of arbitrary size. 14 | - For multivariate distribution, x should be a matrix, where each column is a sample. 15 | """ 16 | fit_mle(D, x) 17 | 18 | """ 19 | fit_mle(D, x, w) 20 | 21 | Fit a distribution of type `D` to a weighted data set `x`, with weights given by `w`. 22 | 23 | Here, `w` should be an array with length `n`, where `n` is the number of samples contained in `x`. 24 | """ 25 | fit_mle(D, x, w) 26 | 27 | fit_mle(dt::Type{D}, x::AbstractArray) where {D<:UnivariateDistribution} = fit_mle(D, suffstats(D, x)) 28 | fit_mle(dt::Type{D}, x::AbstractArray, w::AbstractArray) where {D<:UnivariateDistribution} = fit_mle(D, suffstats(D, x, w)) 29 | 30 | fit_mle(dt::Type{D}, x::AbstractMatrix) where {D<:MultivariateDistribution} = fit_mle(D, suffstats(D, x)) 31 | fit_mle(dt::Type{D}, x::AbstractMatrix, w::AbstractArray) where {D<:MultivariateDistribution} = fit_mle(D, suffstats(D, x, w)) 32 | 33 | fit(dt::Type{D}, x) where {D<:Distribution} = fit_mle(D, x) 34 | fit(dt::Type{D}, args...) where {D<:Distribution} = fit_mle(D, args...) 35 | -------------------------------------------------------------------------------- /src/functionals.jl: -------------------------------------------------------------------------------- 1 | function getEndpoints(distr::UnivariateDistribution, epsilon::Real) 2 | (left,right) = map(x -> quantile(distr,x), (0,1)) 3 | leftEnd = left!=-Inf ? left : quantile(distr, epsilon) 4 | rightEnd = right!=-Inf ? right : quantile(distr, 1-epsilon) 5 | (leftEnd, rightEnd) 6 | end 7 | 8 | function expectation(distr::ContinuousUnivariateDistribution, g::Function, epsilon::Real) 9 | f = x->pdf(distr,x) 10 | (leftEnd, rightEnd) = getEndpoints(distr, epsilon) 11 | quadgk(x -> f(x)*g(x), leftEnd, rightEnd)[1] 12 | end 13 | 14 | ## Assuming that discrete distributions only take integer values. 15 | function expectation(distr::DiscreteUnivariateDistribution, g::Function, epsilon::Real) 16 | f = x->pdf(distr,x) 17 | (leftEnd, rightEnd) = getEndpoints(distr, epsilon) 18 | sum(x -> f(x)*g(x), leftEnd:rightEnd) 19 | end 20 | 21 | function expectation(distr::UnivariateDistribution, g::Function) 22 | expectation(distr, g, 1e-10) 23 | end 24 | 25 | ## Leave undefined until we've implemented a numerical integration procedure 26 | # function entropy(distr::UnivariateDistribution) 27 | # pf = typeof(distr)<:ContinuousDistribution ? pdf : pmf 28 | # f = x -> pf(distr, x) 29 | # expectation(distr, x -> -log(f(x))) 30 | # end 31 | 32 | function kldivergence(P::UnivariateDistribution, Q::UnivariateDistribution) 33 | expectation(P, x -> let p = pdf(P,x); (p > 0)*log(p/pdf(Q,x)) end) 34 | end 35 | -------------------------------------------------------------------------------- /test/ref/continuous/normalinversegaussian.R: -------------------------------------------------------------------------------- 1 | NormalInverseGaussian <- R6Class("NormalInverseGaussian", 2 | inherit = ContinuousDistribution, 3 | public = list( 4 | names = c("mu", "alpha", "beta", "delta"), 5 | mu = NA, 6 | alpha = NA, 7 | beta = NA, 8 | delta = NA, 9 | gamma = NA, 10 | initialize = function(u, a, b, d) { 11 | self$mu <- u 12 | self$alpha <- a 13 | self$beta <- b 14 | self$delta <- d 15 | self$gamma <- sqrt(a^2 - b^2) 16 | }, 17 | supp = function() { c(-Inf, Inf) }, 18 | properties = function() { 19 | u <- self$mu 20 | a <- self$alpha 21 | b <- self$beta 22 | d <- self$delta 23 | g <- self$gamma 24 | list(mean = u + d * b / g, 25 | var = d * a^2 / g^3, 26 | skewness = 3 * b / a / sqrt(d * g), 27 | kurtosis = 3 * (1 + 4 * b^2 / a^2) / (d * g)) 28 | }, 29 | pdf = function(x, log=FALSE) { 30 | fBasics::dnig(x, self$alpha, self$beta, self$delta, self$mu, log=log) 31 | }, 32 | cdf = function(x) { 33 | fBasics::pnig(x, self$alpha, self$beta, self$delta, self$mu) 34 | }, 35 | quan = function(v) { 36 | fBasics::qnig(v, self$alpha, self$beta, self$delta, self$mu) 37 | } 38 | ) 39 | ) 40 | -------------------------------------------------------------------------------- /test/ref/continuous/inversegamma.R: -------------------------------------------------------------------------------- 1 | 2 | InverseGamma <- R6Class("InverseGamma", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta"), 6 | alpha = NA, 7 | beta = NA, 8 | initialize = function(a=1, b=1) { 9 | self$alpha <- a 10 | self$beta <- b 11 | }, 12 | supp = function(){ c(0, Inf) }, 13 | properties = function() { 14 | a <- self$alpha 15 | b <- self$beta 16 | list(shape = a, 17 | scale = b, 18 | rate = 1 / b, 19 | mean = if (a > 1) { b / (a - 1) } else { Inf }, 20 | mode = b / (a + 1), 21 | var = if (a > 2) { 22 | b^2 / ((a - 1)^2 * (a - 2)) 23 | } else { Inf }, 24 | skewness = if (a > 3) { 25 | 4 * sqrt(a - 2) / (a - 3) 26 | } else { NaN }, 27 | kurtosis = if (a > 4) { 28 | (30 * a - 66) / ((a - 3) * (a - 4)) 29 | } else { NaN }, 30 | entropy = a + log(b) + lgamma(a) - (1 + a) * digamma(a)) 31 | }, 32 | pdf = function(x, log=FALSE){ dinvgamma(x, self$alpha, self$beta, log=log) }, 33 | cdf = function(x){ pinvgamma(x, self$alpha, self$beta) }, 34 | quan = function(v){ qinvgamma(v, self$alpha, self$beta) } 35 | ) 36 | ) 37 | -------------------------------------------------------------------------------- /src/univariate/continuous/noncentralbeta.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NoncentralBeta(α, β, λ) 3 | """ 4 | struct NoncentralBeta{T<:Real} <: ContinuousUnivariateDistribution 5 | α::T 6 | β::T 7 | λ::T 8 | NoncentralBeta{T}(α::T, β::T, λ::T) where {T} = new{T}(α, β, λ) 9 | end 10 | 11 | function NoncentralBeta(α::T, β::T, λ::T; check_args=true) where {T <: Real} 12 | check_args && @check_args(NoncentralBeta, α > zero(α) && β > zero(β) && λ >= zero(λ)) 13 | return NoncentralBeta{T}(α, β, λ) 14 | end 15 | 16 | NoncentralBeta(α::Real, β::Real, λ::Real) = NoncentralBeta(promote(α, β, λ)...) 17 | NoncentralBeta(α::Integer, β::Integer, λ::Integer) = NoncentralBeta(float(α), float(β), float(λ)) 18 | 19 | @distr_support NoncentralBeta 0.0 1.0 20 | 21 | ### Parameters 22 | 23 | params(d::NoncentralBeta) = (d.α, d.β, d.λ) 24 | partype(::NoncentralBeta{T}) where {T} = T 25 | 26 | 27 | ### Evaluation & Sampling 28 | 29 | # TODO: add mean and var 30 | 31 | @_delegate_statsfuns NoncentralBeta nbeta α β λ 32 | 33 | # TODO: remove RFunctions dependency once NoncentralChisq has its removed 34 | @rand_rdist(NoncentralBeta) 35 | 36 | function rand(d::NoncentralBeta) 37 | β = d.β 38 | a = rand(NoncentralChisq(2d.α, β)) 39 | b = rand(Chisq(2β)) 40 | a / (a + b) 41 | end 42 | 43 | function rand(rng::AbstractRNG, d::NoncentralBeta) 44 | β = d.β 45 | a = rand(rng, NoncentralChisq(2d.α, β)) 46 | b = rand(rng, Chisq(2β)) 47 | a / (a + b) 48 | end 49 | -------------------------------------------------------------------------------- /test/ref/continuous/pareto.R: -------------------------------------------------------------------------------- 1 | 2 | Pareto <- R6Class("Pareto", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta"), 6 | alpha = NA, 7 | beta = NA, 8 | initialize = function(a=1, b=1) { 9 | self$alpha <- a 10 | self$beta <- b 11 | }, 12 | supp = function() { c(self$beta, Inf) }, 13 | properties = function() { 14 | a <- self$alpha 15 | b <- self$beta 16 | list(shape = a, 17 | scale = b, 18 | mean = if (a > 1) { a * b / (a - 1) } else { Inf }, 19 | median = b * 2^(1/a), 20 | mode = b, 21 | var = if (a > 2) { 22 | a * b^2 / ((a - 1)^2 * (a - 2)) 23 | } else { Inf }, 24 | skewness = if (a > 3) { 25 | 2 * (1 + a) / (a - 3) * sqrt((a - 2) / a) 26 | } else { NaN }, 27 | kurtosis = if (a > 4) { 28 | 6 * (a^3 + a^2 - 6*a - 2) / 29 | (a * (a - 3) * (a - 4)) 30 | } else { NaN }, 31 | entropy = (1 + 1 / a) + log(b / a)) 32 | }, 33 | pdf = function(x, log=FALSE){ dpareto(x, self$alpha, self$beta, log=log) }, 34 | cdf = function(x){ ppareto(x, self$alpha, self$beta) }, 35 | quan = function(v){ qpareto(v, self$alpha, self$beta) } 36 | ) 37 | ) 38 | -------------------------------------------------------------------------------- /test/ref/continuous/truncatednormal.R: -------------------------------------------------------------------------------- 1 | 2 | TruncatedNormal <- R6Class("TruncatedNormal", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma", "a", "b"), 6 | mu = NA, 7 | sigma = NA, 8 | a = NA, 9 | b = NA, 10 | initialize = function(u, s, a, b) { 11 | self$mu <- u 12 | self$sigma <- s 13 | self$a <- a 14 | self$b <- b 15 | }, 16 | supp = function() { c(self$a, self$b) }, 17 | properties = function() { 18 | u <- self$mu 19 | s <- self$sigma 20 | a <- self$a 21 | b <- self$b 22 | za <- (a - u) / s 23 | zb <- (b - u) / s 24 | Z <- pnorm(zb) - pnorm(za) 25 | pa <- dnorm(za) 26 | pb <- dnorm(zb) 27 | v1 <- (ifelse(pa == 0, 0, za * pa) - 28 | ifelse(pb == 0, 0, zb * pb)) / Z 29 | list(mode = pmin(pmax(u, a), b), 30 | mean = u + (pa - pb) / Z * s, 31 | var = s^2 * (1 + v1 - ((pa - pb) / Z)^2), 32 | entropy = (log(2*pi) + 1) / 2 + log(s) + log(Z) + v1 / 2) 33 | }, 34 | pdf = function(x, log=FALSE) { dtnorm(x, self$mu, self$sigma, self$a, self$b, log=log) }, 35 | cdf = function(x) { ptnorm(x, self$mu, self$sigma, self$a, self$b) }, 36 | quan = function(v) { qtnorm(v, self$mu, self$sigma, self$a, self$b) } 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /src/samplers/vonmises.jl: -------------------------------------------------------------------------------- 1 | 2 | struct VonMisesSampler <: Sampleable{Univariate,Continuous} 3 | μ::Float64 4 | κ::Float64 5 | r::Float64 6 | 7 | function VonMisesSampler(μ::Float64, κ::Float64) 8 | τ = 1.0 + sqrt(1.0 + 4 * abs2(κ)) 9 | ρ = (τ - sqrt(2.0 * τ)) / (2.0 * κ) 10 | new(μ, κ, (1.0 + abs2(ρ)) / (2.0 * ρ)) 11 | end 12 | end 13 | 14 | # algorithm from 15 | # DJ Best & NI Fisher (1979). Efficient Simulation of the von Mises 16 | # Distribution. Journal of the Royal Statistical Society. Series C 17 | # (Applied Statistics), 28(2), 152-157. 18 | function rand(rng::AbstractRNG, s::VonMisesSampler) 19 | f = 0.0 20 | local x::Float64 21 | if s.κ > 700.0 22 | x = s.μ + randn(rng) / sqrt(s.κ) 23 | else 24 | while true 25 | t, u = 0.0, 0.0 26 | while true 27 | d = abs2(rand(rng) - 0.5) 28 | e = abs2(rand(rng) - 0.5) 29 | if d + e <= 0.25 30 | t = d / e 31 | u = 4 * (d + e) 32 | break 33 | end 34 | end 35 | z = (1.0 - t) / (1.0 + t) 36 | f = (1.0 + s.r * z) / (s.r + z) 37 | c = s.κ * (s.r - f) 38 | if c * (2.0 - c) > u || log(c / u) + 1 >= c 39 | break 40 | end 41 | end 42 | acf = acos(f) 43 | x = s.μ + (rand(rng, Bool) ? acf : -acf) 44 | end 45 | return x 46 | end 47 | -------------------------------------------------------------------------------- /test/ref/continuous/vonmises.R: -------------------------------------------------------------------------------- 1 | 2 | VonMises <- R6Class("VonMises", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "kappa"), 6 | mu = NA, 7 | kappa = NA, 8 | initialize = function(a1=NA, a2=NA) { 9 | if (is.na(a1)) { 10 | u <- 0; k <- 1 11 | } else if (is.na(a2)) { 12 | u <- 0; k <- a1 13 | } else { 14 | u <- a1; k <- a2 15 | } 16 | self$mu <- circular::circular(u, units='radian') 17 | self$kappa <- k 18 | }, 19 | supp = function() { c(self$mu - self$kappa, self$mu + self$kappa) }, 20 | properties = function() { 21 | u <- self$mu 22 | k <- self$kappa 23 | I0k <- besselI(k, 0) 24 | I1k <- besselI(k, 1) 25 | list(mean = u, 26 | median = u, 27 | mode = u, 28 | var = 1 - I1k / I0k, 29 | entropy = log(2*pi*I0k) - k * I1k/I0k) 30 | }, 31 | pdf = function(x, log=FALSE) { 32 | circular::dvonmises(circular::circular(x, units='radian'), 33 | self$mu, self$kappa, log=log) 34 | }, 35 | cdf = function(x) { 36 | circular::pvonmises(circular::circular(x, units='radian'), 37 | self$mu, self$kappa) 38 | }, 39 | quan = function(v) { 40 | circular::qvonmises(v, self$mu, self$kappa) 41 | } 42 | ) 43 | ) 44 | -------------------------------------------------------------------------------- /test/gradlogpdf.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | 5 | # Test for gradlogpdf on univariate distributions 6 | 7 | @test isapprox(gradlogpdf(Beta(1.5, 3.0), 0.7) , -5.9523809523809526 , atol=1.0e-8) 8 | @test isapprox(gradlogpdf(Chi(5.0), 5.5) , -4.7727272727272725 , atol=1.0e-8) 9 | @test isapprox(gradlogpdf(Chisq(7.0), 12.0) , -0.29166666666666663, atol=1.0e-8) 10 | @test isapprox(gradlogpdf(Exponential(2.0), 7.0) , -0.5 , atol=1.0e-8) 11 | @test isapprox(gradlogpdf(Gamma(9.0, 0.5), 11.0) , -1.2727272727272727 , atol=1.0e-8) 12 | @test isapprox(gradlogpdf(Gumbel(3.5, 1.0), 4.0) , -1.6065306597126334 , atol=1.0e-8) 13 | @test isapprox(gradlogpdf(Laplace(7.0), 34.0) , -1.0 , atol=1.0e-8) 14 | @test isapprox(gradlogpdf(Logistic(-6.0), 1.0) , -0.9981778976111987 , atol=1.0e-8) 15 | @test isapprox(gradlogpdf(LogNormal(5.5), 2.0) , 1.9034264097200273 , atol=1.0e-8) 16 | @test isapprox(gradlogpdf(Normal(-4.5, 2.0), 1.6), -1.525 , atol=1.0e-8) 17 | @test isapprox(gradlogpdf(TDist(8.0), 9.1) , -0.9018830525272548 , atol=1.0e-8) 18 | @test isapprox(gradlogpdf(Weibull(2.0), 3.5) , -6.714285714285714 , atol=1.0e-8) 19 | 20 | # Test for gradlogpdf on multivariate distributions 21 | 22 | @test isapprox(gradlogpdf(MvNormal([1., 2.], [1. 0.1; 0.1 1.]), [0.7, 0.9]) , 23 | [0.191919191919192, 1.080808080808081] ,atol=1.0e-8) 24 | @test isapprox(gradlogpdf(MvTDist(5., [1., 2.], [1. 0.1; 0.1 1.]), [0.7, 0.9]), 25 | [0.2150711513583442, 1.2111901681759383] ,atol=1.0e-8) 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The Distributions module is licensed under the MIT License: 2 | 3 | > Copyright (c) 2012-2013: Douglas Bates, John Myles White, 4 | > Jeff Bezanson, Stefan Karpinski, Viral B. Shah, 5 | > and other contributors: 6 | > 7 | > https://github.com/JuliaStats/Distributions.jl/contributors 8 | > 9 | > Permission is hereby granted, free of charge, to any person obtaining 10 | > a copy of this software and associated documentation files (the 11 | > "Software"), to deal in the Software without restriction, including 12 | > without limitation the rights to use, copy, modify, merge, publish, 13 | > distribute, sublicense, and/or sell copies of the Software, and to 14 | > permit persons to whom the Software is furnished to do so, subject to 15 | > the following conditions: 16 | > 17 | > The above copyright notice and this permission notice shall be 18 | > included in all copies or substantial portions of the Software. 19 | > 20 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | The Distributions module uses the RMath library, which has its own license: 29 | 30 | - [RMath](http://www.r-project.org/Licenses/) 31 | -------------------------------------------------------------------------------- /test/edgeworth.jl: -------------------------------------------------------------------------------- 1 | # 2 | 3 | using Distributions 4 | using Test 5 | 6 | 7 | dg = Gamma(1,1) 8 | 9 | d_s = EdgeworthSum(dg,10) 10 | d_m = EdgeworthMean(dg,10) 11 | d_z = EdgeworthZ(dg,10) 12 | 13 | dg_s = Gamma(10,1) 14 | dg_m = Gamma(10,0.1) 15 | dg_za = Gamma(10,1/std(dg_s)) 16 | 17 | for i = 0.01:0.01:0.99 18 | 19 | q = quantile(dg_s,i) 20 | @test isapprox(quantile(d_s, i) , q , atol=0.02) 21 | @test isapprox(cquantile(d_s, 1 - i), q , atol=0.02) 22 | @test isapprox(cdf(d_s, q) , i , atol=0.002) 23 | @test isapprox(ccdf(d_s, q) , 1 - i , atol=0.002) 24 | @test isapprox(pdf(d_s, q) , pdf(dg_s,q), atol=0.005) 25 | 26 | q = quantile(dg_m,i) 27 | @test isapprox(quantile(d_m, i) , q , atol=0.01) 28 | @test isapprox(cquantile(d_m, 1 - i), q , atol=0.01) 29 | @test isapprox(cdf(d_m, q) , i , atol=0.002) 30 | @test isapprox(ccdf(d_m, q) , 1 - i , atol=0.002) 31 | @test isapprox(pdf(d_m, q) , pdf(dg_m,q), atol=0.05) 32 | 33 | q = quantile(dg_za,i) - mean(dg_za) 34 | @test isapprox(quantile(d_z, i) , q , atol=0.01) 35 | @test isapprox(cquantile(d_z, 1 - i), q , atol=0.01) 36 | @test isapprox(cdf(d_z, q) , i , atol=0.002) 37 | @test isapprox(ccdf(d_z, q) , 1 - i , atol=0.002) 38 | @test isapprox(pdf(d_z, q) , pdf(dg_za,q+mean(dg_za)), atol=0.02) 39 | 40 | end 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/src/matrix.md: -------------------------------------------------------------------------------- 1 | # [Matrix-variate Distributions](@id matrix-variates) 2 | 3 | *Matrix-variate distributions* are the distributions whose variate forms are `Matrixvariate` (*i.e* each sample is a matrix). Abstract types for matrix-variate distributions: 4 | 5 | ```julia 6 | const MatrixDistribution{S<:ValueSupport} = Distribution{Matrixvariate,S} 7 | 8 | const DiscreteMatrixDistribution = Distribution{Matrixvariate, Discrete} 9 | const ContinuousMatrixDistribution = Distribution{Matrixvariate, Continuous} 10 | ``` 11 | 12 | More advanced functionalities related to random matrices can be found in the 13 | [RandomMatrices.jl](https://github.com/JuliaMath/RandomMatrices.jl) package. 14 | 15 | ## Common Interface 16 | 17 | All distributions implement the same set of methods: 18 | 19 | ```@docs 20 | size(::MatrixDistribution) 21 | length(::MatrixDistribution) 22 | Distributions.rank(::MatrixDistribution) 23 | mean(::MatrixDistribution) 24 | var(::MatrixDistribution) 25 | cov(::MatrixDistribution) 26 | pdf{T<:Real}(d::MatrixDistribution, x::AbstractMatrix{T}) 27 | logpdf{T<:Real}(d::MatrixDistribution, x::AbstractMatrix{T}) 28 | Distributions._rand!(::AbstractRNG, ::MatrixDistribution, A::AbstractMatrix) 29 | vec(d::MatrixDistribution) 30 | ``` 31 | 32 | ## Distributions 33 | 34 | ```@docs 35 | MatrixNormal 36 | Wishart 37 | InverseWishart 38 | MatrixReshaped 39 | MatrixTDist 40 | MatrixBeta 41 | MatrixFDist 42 | LKJ 43 | ``` 44 | 45 | ## Internal Methods (for creating your own matrix-variate distributions) 46 | 47 | ```@docs 48 | Distributions._logpdf(d::MatrixDistribution, x::AbstractArray) 49 | ``` 50 | -------------------------------------------------------------------------------- /test/ref/continuous/triangulardist.R: -------------------------------------------------------------------------------- 1 | 2 | TriangularDist = R6Class("TriangularDist", 3 | inherit = ContinuousDistribution, 4 | public =list( 5 | names = c("a", "b", "c"), 6 | a = NA, 7 | b = NA, 8 | c = NA, 9 | initialize = function(a, b, c=(a+b)/2) { 10 | self$a <- a 11 | self$b <- b 12 | self$c <- c 13 | }, 14 | supp = function() { c(self$a, self$b) }, 15 | properties = function() { 16 | a <- self$a 17 | b <- self$b 18 | c <- self$c 19 | m <- (a + b) / 2 20 | list(mean = (a + b + c) / 3, 21 | mode = c, 22 | median = if (c >= m) { 23 | a + sqrt((b - a) * (c - a) / 2) 24 | } else { 25 | b - sqrt((b - a) * (b - c) / 2) 26 | }, 27 | var = (a^2 + b^2 + c^2 - a*b - a*c - b*c) / 18, 28 | skewness = (sqrt(2)/5) * (a + b - 2*c) * (2*a - b - c) * (a - 2*b + c) / 29 | (a^2 + b^2 + c^2 - a*b - a*c - b*c)^1.5, 30 | kurtosis = -0.6, 31 | entropy = 0.5 + log((b - a) / 2)) 32 | }, 33 | pdf = function(x, log=FALSE) { dtriang(x, self$a, self$b, self$c, log=log) }, 34 | cdf = function(x) { ptriang(x, self$a, self$b, self$c) }, 35 | quan = function(v) { qtriang(v, self$a, self$b, self$c) } 36 | ) 37 | ) 38 | 39 | SymTriangularDist = list( 40 | new = function(u=0, s=1) { 41 | TriangularDist$new(u-s, u+s, u) 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /test/ref/continuous/cosine.R: -------------------------------------------------------------------------------- 1 | 2 | Cosine <- R6Class("Cosine", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma"), 6 | mu = NA, 7 | sigma = NA, 8 | initialize = function(u=0, s=1) { 9 | self$mu <- u 10 | self$sigma <- s 11 | }, 12 | supp = function() { c(self$mu - self$sigma, self$mu + self$sigma) }, 13 | properties = function() { 14 | u <- self$mu 15 | s <- self$sigma 16 | list(location = u, 17 | scale = s, 18 | mean = u, 19 | median = u, 20 | mode = u, 21 | var = s^2 * (1/3 - 2/pi^2), 22 | skewness = 0, 23 | kurtosis = 1.2 * (90 - pi^4) / (pi^2 - 6)^2) 24 | }, 25 | pdf = function(x, log=FALSE) { 26 | s <- self$sigma 27 | z <- (x - self$mu) / s 28 | p <- (1 + cospi(z)) / (2 * s) 29 | if (log) { base::log(p) } else { p } 30 | }, 31 | cdf = function(x) { 32 | s <- self$sigma 33 | z <- (x - self$mu) / s 34 | (z+1)/2 + sinpi(z) / (2*pi) 35 | }, 36 | quan = function(v) { 37 | invf <- function(u) { 38 | ret <- uniroot( 39 | function(z) { (z+1)/2 + sinpi(z) / (2*pi) - u }, 40 | c(-1, 1), tol=1e-13) 41 | ret$root 42 | } 43 | r <- vapply(v, invf, 0.0) 44 | self$mu + r * self$sigma 45 | } 46 | ) 47 | ) 48 | -------------------------------------------------------------------------------- /test/ref/discrete/hypergeometric.R: -------------------------------------------------------------------------------- 1 | 2 | Hypergeometric <- R6Class("Hypergeometric", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("ns", "nf", "n"), 6 | ns = NA, 7 | nf = NA, 8 | n = NA, 9 | initialize = function(ns, nf, n) { 10 | self$ns <- ns 11 | self$nf <- nf 12 | self$n <- n 13 | }, 14 | supp = function(){ 15 | N <- self$ns + self$nf 16 | K <- self$ns 17 | n <- self$n 18 | c(max(0, n+K-N), min(n, K)) 19 | }, 20 | properties = function() { 21 | N <- self$ns + self$nf 22 | K <- self$ns 23 | n <- self$n 24 | mean.val <- n * (K / N) 25 | var.val <- n * (K / N) * ((N-K) / N) * ((N - n) / (N - 1)) 26 | skew.val <- ((N - 2*K) * sqrt(N - 1) * (N - 2*n)) / 27 | (sqrt(n * K * (N - K) * (N - n)) * (N - 2)) 28 | kurt.num <- (N-1) * N^2 * (N * (N+1)- 6 * K * (N-K) - 6 * n * (N-n)) + 29 | 6 * n * K * (N-K) * (N-n) * (5*N-6) 30 | kurt.den <- n * K * (N - K) * (N - n) * (N - 2) * (N - 3) 31 | kurt.val <- kurt.num / kurt.den 32 | list(mean=mean.val, 33 | var=var.val, 34 | skewness=skew.val, 35 | kurtosis=kurt.val) 36 | }, 37 | pdf = function(x, log=FALSE){ dhyper(x, self$ns, self$nf, self$n, log=log) }, 38 | cdf = function(x){ phyper(x, self$ns, self$nf, self$n) }, 39 | quan = function(v){ qhyper(v, self$ns, self$nf, self$n) } 40 | ) 41 | ) 42 | -------------------------------------------------------------------------------- /src/univariate/continuous/noncentralt.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NoncentralT(ν, λ) 3 | """ 4 | struct NoncentralT{T<:Real} <: ContinuousUnivariateDistribution 5 | ν::T 6 | λ::T 7 | NoncentralT{T}(ν::T, λ::T) where {T} = new{T}(ν, λ) 8 | end 9 | 10 | function NoncentralT(ν::T, λ::T; check_args=true) where {T <: Real} 11 | check_args && @check_args(NoncentralT, ν > zero(ν)) 12 | return NoncentralT{T}(ν, λ) 13 | end 14 | 15 | NoncentralT(ν::Real, λ::Real) = NoncentralT(promote(ν, λ)...) 16 | NoncentralT(ν::Integer, λ::Integer) = NoncentralT(float(ν), float(λ)) 17 | 18 | @distr_support NoncentralT -Inf Inf 19 | 20 | ### Conversions 21 | convert(::Type{NoncentralT{T}}, ν::S, λ::S) where {T <: Real, S <: Real} = NoncentralT(T(ν), T(λ)) 22 | convert(::Type{NoncentralT{T}}, d::NoncentralT{S}) where {T <: Real, S <: Real} = NoncentralT(T(d.ν), T(d.λ), check_args=false) 23 | 24 | ### Parameters 25 | 26 | params(d::NoncentralT) = (d.ν, d.λ) 27 | partype(::NoncentralT{T}) where {T} = T 28 | 29 | 30 | ### Statistics 31 | 32 | function mean(d::NoncentralT{T}) where T<:Real 33 | if d.ν > 1 34 | isinf(d.ν) ? d.λ : 35 | sqrt(d.ν/2) * d.λ * gamma((d.ν - 1)/2) / gamma(d.ν/2) 36 | else 37 | T(NaN) 38 | end 39 | end 40 | 41 | function var(d::NoncentralT{T}) where T<:Real 42 | d.ν > 2 ? d.ν*(1 + d.λ^2) / (d.ν - 2) - mean(d)^2 : T(NaN) 43 | end 44 | 45 | ### Evaluation & Sampling 46 | 47 | @_delegate_statsfuns NoncentralT ntdist ν λ 48 | 49 | ## sampling 50 | function rand(rng::AbstractRNG, d::NoncentralT) 51 | ν = d.ν 52 | z = randn(rng) 53 | v = rand(rng, Chisq(ν)) 54 | (z+d.λ)/sqrt(v/ν) 55 | end 56 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | % reference paper 2 | @article{2019arXiv190708611B, 3 | author = {{Besan{\c{c}}on}, Mathieu and {Anthoff}, David and {Arslan}, Alex and 4 | {Byrne}, Simon and {Lin}, Dahua and {Papamarkou}, Theodore and 5 | {Pearson}, John}, 6 | title = {Distributions.jl: Definition and Modeling of Probability Distributions in the JuliaStats Ecosystem}, 7 | journal = {arXiv e-prints}, 8 | keywords = {Statistics - Computation, Computer Science - Mathematical Software}, 9 | year = 2019, 10 | month = "Jul", 11 | eid = {arXiv:1907.08611}, 12 | pages = {arXiv:1907.08611}, 13 | archivePrefix = {arXiv}, 14 | eprint = {1907.08611}, 15 | primaryClass = {stat.CO}, 16 | } 17 | 18 | % reference for the software itself 19 | @misc{Distributions.jl-2019, 20 | author = {Dahua Lin and 21 | John Myles White and 22 | Simon Byrne and 23 | Douglas Bates and 24 | Andreas Noack and 25 | John Pearson and 26 | Alex Arslan and 27 | Kevin Squire and 28 | David Anthoff and 29 | Theodore Papamarkou and 30 | Mathieu Besançon and 31 | Jan Drugowitsch and 32 | Moritz Schauer and 33 | other contributors}, 34 | title = {{JuliaStats/Distributions.jl: a Julia package for probability distributions and associated functions}}, 35 | month = july, 36 | year = 2019, 37 | doi = {10.5281/zenodo.2647458}, 38 | url = {https://doi.org/10.5281/zenodo.2647458} 39 | } 40 | -------------------------------------------------------------------------------- /src/deprecates.jl: -------------------------------------------------------------------------------- 1 | #### Deprecate on 0.6 (to be removed on 0.7) 2 | 3 | @Base.deprecate expected_logdet meanlogdet 4 | 5 | function probs(d::DiscreteUnivariateDistribution) 6 | Base.depwarn("probs(d::$(typeof(d))) is deprecated. Please use pdf(d) instead.", :probs) 7 | return pdf(d) 8 | end 9 | 10 | function Binomial(n::Real, p::Real) 11 | Base.depwarn("Binomial(n::Real, p) is deprecated. Please use Binomial(n::Integer, p) instead.", :Binomial) 12 | Binomial(Int(n), p) 13 | end 14 | 15 | function Binomial(n::Real) 16 | Base.depwarn("Binomial(n::Real) is deprecated. Please use Binomial(n::Integer) instead.", :Binomial) 17 | Binomial(Int(n)) 18 | end 19 | 20 | function BetaBinomial(n::Real, α::Real, β::Real) 21 | Base.depwarn("BetaBinomial(n::Real, α, β) is deprecated. Please use BetaBinomial(n::Integer, α, β) instead.", :BetaBinomial) 22 | BetaBinomial(Int(n), α, β) 23 | end 24 | 25 | 26 | # vectorized versions 27 | for fun in [:pdf, :logpdf, 28 | :cdf, :logcdf, 29 | :ccdf, :logccdf, 30 | :invlogcdf, :invlogccdf, 31 | :quantile, :cquantile] 32 | 33 | _fun! = Symbol('_', fun, '!') 34 | fun! = Symbol(fun, '!') 35 | 36 | @eval begin 37 | @deprecate ($_fun!)(r::AbstractArray, d::UnivariateDistribution, X::AbstractArray) r .= ($fun).(d, X) false 38 | @deprecate ($fun!)(r::AbstractArray, d::UnivariateDistribution, X::AbstractArray) r .= ($fun).(d, X) false 39 | @deprecate ($fun)(d::UnivariateDistribution, X::AbstractArray) ($fun).(d, X) 40 | end 41 | end 42 | 43 | @deprecate pdf(d::DiscreteUnivariateDistribution) pdf.(Ref(d), support(d)) 44 | -------------------------------------------------------------------------------- /src/mixtures/unigmm.jl: -------------------------------------------------------------------------------- 1 | # Univariate Gaussian Mixture Models 2 | 3 | struct UnivariateGMM{VT1<:AbstractVector{<:Real},VT2<:AbstractVector{<:Real},C<:Categorical} <: UnivariateMixture{Continuous,Normal} 4 | K::Int 5 | means::VT1 6 | stds::VT2 7 | prior::C 8 | 9 | function UnivariateGMM(ms::VT1, ss::VT2, pri::C) where {VT1<:AbstractVector{<:Real},VT2<:AbstractVector{<:Real},C<:Categorical} 10 | K = length(ms) 11 | length(ss) == K || throw(DimensionMismatch()) 12 | ncategories(pri) == K || 13 | error("The number of categories in pri should be equal to the number of components.") 14 | new{VT1,VT2,C}(K, ms, ss, pri) 15 | end 16 | end 17 | 18 | @distr_support UnivariateGMM -Inf Inf 19 | 20 | ncomponents(d::UnivariateGMM) = d.K 21 | 22 | component(d::UnivariateGMM, k::Int) = Normal(d.means[k], d.stds[k]) 23 | 24 | probs(d::UnivariateGMM) = probs(d.prior) 25 | 26 | mean(d::UnivariateGMM) = dot(d.means, probs(d)) 27 | 28 | rand(d::UnivariateGMM) = (k = rand(d.prior); d.means[k] + randn() * d.stds[k]) 29 | 30 | rand(rng::AbstractRNG, d::UnivariateGMM) = 31 | (k = rand(rng, d.prior); d.means[k] + randn(rng) * d.stds[k]) 32 | 33 | params(d::UnivariateGMM) = (d.means, d.stds, d.prior) 34 | 35 | struct UnivariateGMMSampler{VT1<:AbstractVector{<:Real},VT2<:AbstractVector{<:Real}} <: Sampleable{Univariate,Continuous} 36 | means::VT1 37 | stds::VT2 38 | psampler::AliasTable 39 | end 40 | 41 | rand(rng::AbstractRNG, s::UnivariateGMMSampler) = 42 | (k = rand(rng, s.psampler); s.means[k] + randn(rng) * s.stds[k]) 43 | sampler(d::UnivariateGMM) = UnivariateGMMSampler(d.means, d.stds, sampler(d.prior)) 44 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "Distributions" 2 | uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" 3 | authors = ["JuliaStats"] 4 | version = "0.23.12" 5 | 6 | [deps] 7 | FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" 8 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 9 | PDMats = "90014a1f-27ba-587c-ab20-58faa44d9150" 10 | Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" 11 | QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" 12 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 13 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 14 | SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" 15 | StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" 16 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 17 | StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" 18 | StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c" 19 | 20 | [compat] 21 | FillArrays = "0.9" 22 | PDMats = "0.10" 23 | QuadGK = "2" 24 | SpecialFunctions = "0.8, 0.9, 0.10" 25 | StaticArrays = "0.12" 26 | StatsBase = "0.32, 0.33" 27 | StatsFuns = "0.8, 0.9" 28 | julia = "1" 29 | 30 | [extras] 31 | Calculus = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" 32 | Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" 33 | FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" 34 | ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" 35 | HypothesisTests = "09f84164-cd44-5f33-b23f-e6b0d136a0d5" 36 | JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" 37 | StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" 38 | StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" 39 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 40 | 41 | [targets] 42 | test = ["StableRNGs", "Calculus", "Distributed", "FiniteDifferences", "ForwardDiff", "JSON", "StaticArrays", "HypothesisTests", "Test"] 43 | -------------------------------------------------------------------------------- /test/ref/continuous/frechet.R: -------------------------------------------------------------------------------- 1 | 2 | Frechet <- R6Class("Frechet", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("alpha", "beta"), 6 | alpha = NA, 7 | beta = NA, 8 | initialize = function(a=1, b=1) { 9 | self$alpha <- a 10 | self$beta <- b 11 | }, 12 | supp = function() { c(0, Inf) }, 13 | properties = function() { 14 | a <- self$alpha 15 | b <- self$beta 16 | g1 <- ifelse(a > 1, gamma(1 - 1/a), NaN) 17 | g2 <- ifelse(a > 2, gamma(1 - 2/a), NaN) 18 | g3 <- ifelse(a > 3, gamma(1 - 3/a), NaN) 19 | g4 <- ifelse(a > 4, gamma(1 - 4/a), NaN) 20 | gam <- 0.57721566490153286 21 | list(shape = a, 22 | scale = b, 23 | mean = ifelse(a > 1, b * g1, Inf), 24 | median = b / log(2)^(1/a), 25 | mode = b * (a / (1 + a))^(1/a), 26 | var = ifelse(a > 2, b^2 * (g2 - g1^2), Inf), 27 | skewness = if (a > 3) { 28 | (g3 - 3 * g2 * g1 + 2 * g1^3) / (g2 - g1^2)^1.5 29 | } else { Inf }, 30 | kurtosis = if (a > 4) { 31 | (g4 - 4 * g3 * g1 + 3 * g2^2) / (g2 - g1^2)^2 - 6 32 | } else { Inf }, 33 | entropy = 1 + gam / a + gam + log(b / a)) 34 | }, 35 | pdf = function(x, log=FALSE) { VGAM::dfrechet(x, shape=self$alpha, scale=self$beta, log=log) }, 36 | cdf = function(x) { VGAM::pfrechet(x, shape=self$alpha, scale=self$beta) }, 37 | quan = function(v) { VGAM::qfrechet(v, shape=self$alpha, scale=self$beta) } 38 | ) 39 | ) 40 | -------------------------------------------------------------------------------- /test/skewnormal.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using Distributions 3 | import Distributions: normpdf, normcdf, normlogpdf, normlogcdf 4 | 5 | @testset "SkewNormal" begin 6 | @test_throws ArgumentError SkewNormal(0.0, 0.0, 0.0) 7 | d1 = SkewNormal(1, 2, 3) 8 | d2 = SkewNormal(1.0f0, 2, 3) 9 | @test partype(d1) == Float64 10 | @test partype(d2) == Float32 11 | @test params(d1) == (1.0, 2.0, 3.0) 12 | # Azzalini sn: sprintf("%.17f",dsn(3.3, xi=1, omega=2, alpha=3)) 13 | @test pdf(d1, 3.3) ≈ 0.20587854616839998 14 | @test minimum(d1) ≈ -Inf 15 | @test maximum(d1) ≈ Inf 16 | @test logpdf(d1, 3.3) ≈ log(pdf(d1, 3.3)) 17 | ## cdf and quantile: when we get Owen's T 18 | #@test cdf(d, 4.5) ≈ 1.0 #when we get Owen's T 19 | @test mean(d1) ≈ 2.513879513212096 20 | @test std(d1) ≈ 1.306969326142243 21 | # 22 | d0 = SkewNormal(0.0, 1.0, 0.0) 23 | @test SkewNormal() == d0 24 | d3 = SkewNormal(0.5, 2.2, 0.0) 25 | d4 = Normal(0.5, 2.2) 26 | # 27 | @test pdf(d3, 3.3) == Distributions.pdf(d4, 3.3) 28 | @test pdf.(d3, 1:3) == Distributions.pdf.(d4, 1:3) 29 | a = mean(d3), var(d3), std(d3) 30 | b = Distributions.mean(d4), Distributions.var(d4), Distributions.std(d4) 31 | @test a == b 32 | @test skewness(d3) == Distributions.skewness(d4) 33 | @test kurtosis(d3) == Distributions.kurtosis(d4) 34 | @test mgf(d3, 2.25) == Distributions.mgf(d4, 2.25) 35 | @test cf(d3, 2.25) == Distributions.cf(d4, 2.25) 36 | end 37 | 38 | 39 | # R code using Azzalini's package sn 40 | #library("sn") 41 | #sprintf("%.17f", dsn(3.3, xi=1, omega=2, alpha=3)) 42 | #f1 <- makeSECdistr(dp=c(1,2,3), family="SN", name="First-SN") 43 | #sprintf("%.15f", mean(f1)) 44 | #sprintf("%.15f", sd(f1)) 45 | -------------------------------------------------------------------------------- /test/ref/discrete_test.lst: -------------------------------------------------------------------------------- 1 | Bernoulli() 2 | Bernoulli(0.25) 3 | Bernoulli(0.75) 4 | Bernoulli(0.00) 5 | Bernoulli(1.00) 6 | 7 | BetaBinomial(2, 0.2, 0.25) 8 | BetaBinomial(10, 0.2, 0.25) 9 | BetaBinomial(10, 2, 2.5) 10 | BetaBinomial(10, 60, 40) 11 | 12 | Binomial() 13 | Binomial(3) 14 | Binomial(5, 0.4) 15 | Binomial(6, 0.8) 16 | Binomial(100, 0.1) 17 | Binomial(100, 0.9) 18 | Binomial(10, 0.0) 19 | Binomial(10, 1.0) 20 | 21 | DiscreteUniform() 22 | DiscreteUniform(6) 23 | DiscreteUniform(7) 24 | DiscreteUniform(0, 4) 25 | DiscreteUniform(2, 8) 26 | 27 | Geometric() 28 | Geometric(0.02) 29 | Geometric(0.1) 30 | Geometric(0.5) 31 | Geometric(0.9) 32 | 33 | Hypergeometric(2, 2, 2) 34 | Hypergeometric(3, 2, 2) 35 | Hypergeometric(3, 2, 0) 36 | Hypergeometric(3, 2, 5) 37 | Hypergeometric(4, 5, 6) 38 | Hypergeometric(60, 80, 100) 39 | 40 | NegativeBinomial() 41 | NegativeBinomial(6) 42 | NegativeBinomial(1, 0.5) 43 | NegativeBinomial(5, 0.6) 44 | NegativeBinomial(0.5, 0.5) 45 | 46 | Poisson() 47 | Poisson(0.0) 48 | Poisson(0.5) 49 | Poisson(2.0) 50 | Poisson(10.0) 51 | Poisson(80.0) 52 | 53 | Skellam() 54 | Skellam(2.0) 55 | Skellam(2.0, 3.0) 56 | Skellam(3.2, 1.8) 57 | 58 | FisherNoncentralHypergeometric(8, 6, 10, 1) 59 | FisherNoncentralHypergeometric(8, 6, 10, 10) 60 | FisherNoncentralHypergeometric(8, 6, 10, 0.1) 61 | FisherNoncentralHypergeometric(80, 60, 100, 1) 62 | FisherNoncentralHypergeometric(80, 60, 100, 10) 63 | FisherNoncentralHypergeometric(80, 60, 100, 0.1) 64 | 65 | WalleniusNoncentralHypergeometric(8, 6, 10, 1) 66 | WalleniusNoncentralHypergeometric(8, 6, 10, 10) 67 | WalleniusNoncentralHypergeometric(8, 6, 10, 0.1) 68 | WalleniusNoncentralHypergeometric(40, 30, 50, 1) 69 | WalleniusNoncentralHypergeometric(40, 30, 50, 0.5) 70 | WalleniusNoncentralHypergeometric(40, 30, 50, 2) 71 | -------------------------------------------------------------------------------- /test/ref/discrete/discreteuniform.R: -------------------------------------------------------------------------------- 1 | 2 | DiscreteUniform <- R6Class("DiscreteUniform", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | a = NA, 6 | b = NA, 7 | s = NA, 8 | names = c("a", "b"), 9 | initialize = function(a1=NA, a2=NA) { 10 | if (is.na(a1)) { 11 | a <- 0; b <- 1 12 | } else if (is.na(a2)) { 13 | a <- 0; b <- a1 14 | } else { 15 | a <- a1; b <- a2 16 | } 17 | self$a <- a 18 | self$b <- b 19 | self$s <- b - a + 1 20 | }, 21 | supp = function() { c(self$a, self$b) }, 22 | properties = function() { 23 | a <- self$a 24 | b <- self$b 25 | s <- self$s 26 | list(span=s, 27 | probval=1/s, 28 | mean=(a + b)/2, 29 | median=(a + b)/2, 30 | var=(s^2 - 1)/12, 31 | skewness=0, 32 | kurtosis=-(6 * (s^2 + 1))/(5 * (s^2 - 1)), 33 | entropy=log(s)) 34 | }, 35 | pdf = function(x, log=FALSE) { 36 | a <- self$a 37 | b <- self$b 38 | s <- self$s 39 | t <- x >= a & x <= b 40 | if (log) { 41 | ifelse(t, -log(s), -Inf) 42 | } else { 43 | ifelse(t, 1 / s, 0.0) 44 | } 45 | }, 46 | cdf = function(x) { 47 | a <- self$a 48 | b <- self$b 49 | r <- (x - a + 1) / (b - a + 1) 50 | pmin(pmax(r, 0), 1) 51 | }, 52 | quan = function(v) { 53 | a <- self$a 54 | b <- self$b 55 | cv <- pmin(pmax(v, 0), 1) 56 | floor(a + (b - a + 1) * cv) 57 | } 58 | ) 59 | ) 60 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using PDMats # test dependencies 3 | using Test 4 | using Distributed 5 | using Random 6 | using StatsBase 7 | using LinearAlgebra 8 | using HypothesisTests 9 | 10 | import JSON 11 | import ForwardDiff 12 | 13 | const tests = [ 14 | "arcsine", 15 | "truncate", 16 | "truncnormal", 17 | "truncated_exponential", 18 | "normal", 19 | "lognormal", 20 | "mvnormal", 21 | "mvlognormal", 22 | "types", 23 | "utils", 24 | "samplers", 25 | "categorical", 26 | "univariates", 27 | "continuous", 28 | "edgecases", 29 | "fit", 30 | "multinomial", 31 | "binomial", 32 | "poissonbinomial", 33 | "dirichlet", 34 | "dirichletmultinomial", 35 | "logitnormal", 36 | "mvtdist", 37 | "kolmogorov", 38 | "edgeworth", 39 | "matrixreshaped", 40 | "matrixvariates", 41 | "vonmisesfisher", 42 | "conversion", 43 | "convolution", 44 | "mixture", 45 | "gradlogpdf", 46 | "noncentralt", 47 | "locationscale", 48 | "quantile_newton", 49 | "semicircle", 50 | "qq", 51 | "pgeneralizedgaussian", 52 | "product", 53 | "discretenonparametric", 54 | "functionals", 55 | "chernoff", 56 | "univariate_bounds", 57 | "negativebinomial", 58 | "bernoulli", 59 | "soliton", 60 | "skewnormal", 61 | ] 62 | 63 | printstyled("Running tests:\n", color=:blue) 64 | 65 | Random.seed!(345679) 66 | 67 | # to reduce redundancy, we might break this file down into seperate `$t * "_utils.jl"` files 68 | include("testutils.jl") 69 | 70 | for t in tests 71 | @testset "Test $t" begin 72 | Random.seed!(345679) 73 | include("$t.jl") 74 | end 75 | end 76 | 77 | # print method ambiguities 78 | println("Potentially stale exports: ") 79 | display(Test.detect_ambiguities(Distributions)) 80 | println() 81 | -------------------------------------------------------------------------------- /test/ref/continuous/generalizedpareto.R: -------------------------------------------------------------------------------- 1 | 2 | GeneralizedPareto <- R6Class("GeneralizedPareto", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma", "xi"), 6 | mu = NA, 7 | sigma = NA, 8 | xi = NA, 9 | initialize = function(a1=NA, a2=NA, a3=NA) { 10 | if (is.na(a1)) { 11 | u <- 0; s <- 1; k <- 1 12 | } else if (is.na(a3)) { 13 | stopifnot(!is.na(a2)) 14 | u <- 0; s <- a1; k <- a2 15 | } else { 16 | u <- a1; s <- a2; k <- a3 17 | } 18 | self$mu <- u 19 | self$sigma <- s 20 | self$xi <- k 21 | }, 22 | supp = function() { 23 | u <- self$mu 24 | s <- self$sigma 25 | k <- self$xi 26 | if (k >= 0) { c(u, Inf) } else { c(u, u - s/k) } 27 | }, 28 | properties = function() { 29 | u <- self$mu 30 | s <- self$sigma 31 | k <- self$xi 32 | list(location = u, 33 | scale = s, 34 | shape = k, 35 | mean = ifelse(k < 1, u + s / (1 - k), Inf), 36 | median = u + s * (2^k - 1) / k, 37 | var = if (2*k < 1) { 38 | s^2 / ((1 - k)^2 * (1 - 2*k)) 39 | } else { Inf }, 40 | skewness = if (3*k < 1) { 41 | 2 * (1 + k) * sqrt(1 - 2*k) / (1 - 3*k) 42 | } else { Inf }, 43 | kurtosis = if (4*k < 1) { 44 | 3 * (1 - 2*k) * (2*k^2 + k + 3) / (1 - 3*k) / (1 - 4*k) - 3 45 | } else { Inf } 46 | ) 47 | }, 48 | pdf = function(x, log=FALSE) { dgpd(x, self$mu, self$sigma, self$xi, log=log) }, 49 | cdf = function(x) { pgpd(x, self$mu, self$sigma, self$xi) }, 50 | quan = function(v) { qgpd(v, self$mu, self$sigma, self$xi) } 51 | ) 52 | ) 53 | -------------------------------------------------------------------------------- /docs/src/truncate.md: -------------------------------------------------------------------------------- 1 | # Truncated Distributions 2 | 3 | The package provides the `truncated` function which creates the most 4 | appropriate distribution to represent a truncated version of a given 5 | distribution. 6 | 7 | 8 | A truncated distribution can be constructed using the following signature: 9 | 10 | ```@docs 11 | truncated 12 | ``` 13 | 14 | In the general case, this will create a `Truncated{typeof(d)}` 15 | structure, defined as follows: 16 | 17 | ```@docs 18 | Truncated 19 | ``` 20 | 21 | Many functions, including those for the evaluation of pdf and sampling, 22 | are defined for all truncated univariate distributions: 23 | 24 | - [`maximum(::UnivariateDistribution)`](@ref) 25 | - [`minimum(::UnivariateDistribution)`](@ref) 26 | - [`insupport(::UnivariateDistribution, x::Any)`](@ref) 27 | - [`pdf(::UnivariateDistribution, ::Real)`](@ref) 28 | - [`logpdf(::UnivariateDistribution, ::Real)`](@ref) 29 | - [`cdf(::UnivariateDistribution, ::Real)`](@ref) 30 | - [`logcdf(::UnivariateDistribution, ::Real)`](@ref) 31 | - [`logdiffcdf(::UnivariateDistribution, ::T, ::T) where {T <: Real}`](@ref) 32 | - [`ccdf(::UnivariateDistribution, ::Real)`](@ref) 33 | - [`logccdf(::UnivariateDistribution, ::Real)`](@ref) 34 | - [`quantile(::UnivariateDistribution, ::Real)`](@ref) 35 | - [`cquantile(::UnivariateDistribution, ::Real)`](@ref) 36 | - [`invlogcdf(::UnivariateDistribution, ::Real)`](@ref) 37 | - [`invlogccdf(::UnivariateDistribution, ::Real)`](@ref) 38 | - [`rand(::UnivariateDistribution)`](@ref) 39 | - [`rand!(::UnivariateDistribution, ::AbstractArray)`](@ref) 40 | - [`median(::UnivariateDistribution)`](@ref) 41 | 42 | Functions to compute statistics, such as `mean`, `mode`, `var`, `std`, and `entropy`, are not available for generic truncated distributions. 43 | Generally, there are no easy ways to compute such quantities due to the complications incurred by truncation. 44 | However, these methods are supported for truncated normal distributions `Truncated{<:Normal}`. 45 | 46 | ```@docs 47 | TruncatedNormal 48 | ``` 49 | -------------------------------------------------------------------------------- /src/univariate/continuous/biweight.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Biweight(μ, σ) 3 | """ 4 | struct Biweight{T<:Real} <: ContinuousUnivariateDistribution 5 | μ::T 6 | σ::T 7 | Biweight{T}(µ::T, σ::T) where {T <: Real} = new{T}(µ, σ) 8 | end 9 | 10 | function Biweight(μ::T, σ::T; check_args=true) where {T<:Real} 11 | check_args && @check_args(Biweight, σ > zero(σ)) 12 | return Biweight{T}(μ, σ) 13 | end 14 | 15 | Biweight(μ::Real, σ::Real) = Biweight(promote(μ, σ)...) 16 | Biweight(μ::Integer, σ::Integer) = Biweight(float(μ), float(σ)) 17 | Biweight(μ::T) where {T<:Real} = Biweight(μ, one(T)) 18 | Biweight() = Biweight(0.0, 1.0, check_args=false) 19 | 20 | @distr_support Biweight d.μ - d.σ d.μ + d.σ 21 | 22 | ## Parameters 23 | params(d::Biweight) = (d.μ, d.σ) 24 | @inline partype(d::Biweight{T}) where {T<:Real} = T 25 | 26 | ## Properties 27 | mean(d::Biweight) = d.μ 28 | median(d::Biweight) = d.μ 29 | mode(d::Biweight) = d.μ 30 | 31 | var(d::Biweight) = d.σ^2 / 7 32 | skewness(d::Biweight{T}) where {T<:Real} = zero(T) 33 | kurtosis(d::Biweight{T}) where {T<:Real} = T(1)/21 - 3 34 | 35 | ## Functions 36 | function pdf(d::Biweight{T}, x::Real) where T<:Real 37 | u = abs(x - d.μ) / d.σ 38 | u >= 1 ? zero(T) : (15//16) * (1 - u^2)^2 / d.σ 39 | end 40 | 41 | function cdf(d::Biweight{T}, x::Real) where T<:Real 42 | u = (x - d.μ) / d.σ 43 | u ≤ -1 ? zero(T) : 44 | u ≥ 1 ? one(T) : 45 | (u + 1)^3/16 * @horner(u,8,-9,3) 46 | end 47 | 48 | function ccdf(d::Biweight{T}, x::Real) where T<:Real 49 | u = (d.μ - x) / d.σ 50 | u ≤ -1 ? zero(T) : 51 | u ≥ 1 ? one(T) : 52 | (u + 1)^3/16 * @horner(u, 8, -9, 3) 53 | end 54 | 55 | @quantile_newton Biweight 56 | 57 | function mgf(d::Biweight{T}, t::Real) where T<:Real 58 | a = d.σ*t 59 | a2 = a^2 60 | a == 0 ? one(T) : 61 | 15exp(d.μ * t) * (-3cosh(a) + (a + 3/a) * sinh(a)) / (a2^2) 62 | end 63 | 64 | function cf(d::Biweight{T}, t::Real) where T<:Real 65 | a = d.σ * t 66 | a2 = a^2 67 | a == 0 ? one(T)+zero(T)*im : 68 | -15cis(d.μ * t) * (3cos(a) + (a - 3/a) * sin(a)) / (a2^2) 69 | end 70 | -------------------------------------------------------------------------------- /src/univariate/continuous/noncentralf.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NoncentralF(ν1, ν2, λ) 3 | """ 4 | struct NoncentralF{T<:Real} <: ContinuousUnivariateDistribution 5 | ν1::T 6 | ν2::T 7 | λ::T 8 | NoncentralF{T}(ν1::T, ν2::T, λ::T) where {T} = new{T}(ν1, ν2, λ) 9 | end 10 | 11 | function NoncentralF(ν1::T, ν2::T, λ::T; check_args=true) where {T <: Real} 12 | check_args && @check_args(NoncentralF, ν1 > zero(T) && ν2 > zero(T) && λ >= zero(T)) 13 | return NoncentralF{T}(ν1, ν2, λ) 14 | end 15 | 16 | NoncentralF(ν1::Real, ν2::Real, λ::Real) = NoncentralF(promote(ν1, ν2, λ)...) 17 | NoncentralF(ν1::Integer, ν2::Integer, λ::Integer) = NoncentralF(float(ν1), float(ν2), float(λ)) 18 | 19 | @distr_support NoncentralF 0.0 Inf 20 | 21 | #### Conversions 22 | 23 | function convert(::Type{NoncentralF{T}}, ν1::S, ν2::S, λ::S) where {T <: Real, S <: Real} 24 | NoncentralF(T(ν1), T(ν2), T(λ)) 25 | end 26 | function convert(::Type{NoncentralF{T}}, d::NoncentralF{S}) where {T <: Real, S <: Real} 27 | NoncentralF(T(d.ν1), T(d.ν2), T(d.λ), check_args=false) 28 | end 29 | 30 | ### Parameters 31 | 32 | params(d::NoncentralF) = (d.ν1, d.ν2, d.λ) 33 | partype(::NoncentralF{T}) where {T} = T 34 | 35 | ### Statistics 36 | 37 | function mean(d::NoncentralF{T}) where {T<:Real} 38 | d.ν2 > 2 ? d.ν2 / (d.ν2 - 2) * (d.ν1 + d.λ) / d.ν1 : T(NaN) 39 | end 40 | 41 | var(d::NoncentralF{T}) where {T<:Real} = d.ν2 > 4 ? 2d.ν2^2 * 42 | ((d.ν1 + d.λ)^2 + (d.ν2 - 2)*(d.ν1 + 2d.λ)) / 43 | (d.ν1 * (d.ν2 - 2)^2 * (d.ν2 - 4)) : T(NaN) 44 | 45 | 46 | ### Evaluation & Sampling 47 | 48 | @_delegate_statsfuns NoncentralF nfdist ν1 ν2 λ 49 | 50 | function rand(rng::AbstractRNG, d::NoncentralF) 51 | r1 = rand(rng, NoncentralChisq(d.ν1,d.λ)) / d.ν1 52 | r2 = rand(rng, Chisq(d.ν2)) / d.ν2 53 | r1 / r2 54 | end 55 | 56 | # TODO: remove RFunctions dependency once NoncentralChisq has its removed 57 | @rand_rdist(NoncentralF) 58 | function rand(d::NoncentralF) 59 | r1 = rand(NoncentralChisq(d.ν1,d.λ)) / d.ν1 60 | r2 = rand(Chisq(d.ν2)) / d.ν2 61 | r1 / r2 62 | end 63 | -------------------------------------------------------------------------------- /src/univariate/continuous/semicircle.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Semicircle(r) 3 | 4 | The Wigner semicircle distribution with radius parameter `r` has probability 5 | density function 6 | 7 | ```math 8 | f(x; r) = \\frac{2}{\\pi r^2} \\sqrt{r^2 - x^2}, \\quad x \\in [-r, r]. 9 | ``` 10 | 11 | ```julia 12 | Semicircle(r) # Wigner semicircle distribution with radius r 13 | 14 | params(d) # Get the radius parameter, i.e. (r,) 15 | ``` 16 | 17 | External links 18 | 19 | * [Wigner semicircle distribution on Wikipedia](https://en.wikipedia.org/wiki/Wigner_semicircle_distribution) 20 | """ 21 | struct Semicircle{T<:Real} <: ContinuousUnivariateDistribution 22 | r::T 23 | Semicircle{T}(r::T) where {T <: Real} = new{T}(r) 24 | end 25 | 26 | 27 | function Semicircle(r::T; check_args=true) where {T <: Real} 28 | check_args && @check_args(Semicircle, r > 0) 29 | return Semicircle{T}(r) 30 | end 31 | 32 | Semicircle(r::Integer) = Semicircle(float(r)) 33 | 34 | @distr_support Semicircle -d.r +d.r 35 | 36 | params(d::Semicircle) = (d.r,) 37 | 38 | mean(d::Semicircle) = zero(d.r) 39 | var(d::Semicircle) = d.r^2 / 4 40 | skewness(d::Semicircle) = zero(d.r) 41 | median(d::Semicircle) = zero(d.r) 42 | mode(d::Semicircle) = zero(d.r) 43 | entropy(d::Semicircle) = log(π * d.r) - oftype(d.r, 0.5) 44 | 45 | function pdf(d::Semicircle, x::Real) 46 | xx, r = promote(x, float(d.r)) 47 | if insupport(d, xx) 48 | return 2 / (π * r^2) * sqrt(r^2 - xx^2) 49 | else 50 | return oftype(r, 0) 51 | end 52 | end 53 | 54 | function logpdf(d::Semicircle, x::Real) 55 | xx, r = promote(x, float(d.r)) 56 | if insupport(d, xx) 57 | return log(oftype(r, 2) / π) - 2 * log(r) + log(r^2 - xx^2) / 2 58 | else 59 | return oftype(r, -Inf) 60 | end 61 | end 62 | 63 | function cdf(d::Semicircle, x::Real) 64 | xx, r = promote(x, float(d.r)) 65 | if insupport(d, xx) 66 | u = xx / r 67 | return (u * sqrt(1 - u^2) + asin(u)) / π + one(xx) / 2 68 | elseif x < minimum(d) 69 | return zero(r) 70 | else 71 | return one(r) 72 | end 73 | end 74 | 75 | @quantile_newton Semicircle 76 | -------------------------------------------------------------------------------- /test/logitnormal.jl: -------------------------------------------------------------------------------- 1 | # Tests on Univariate LogitNormal distributions 2 | 3 | using Distributions 4 | using Random, Test 5 | using StatsFuns 6 | 7 | ####### Core testing procedure 8 | function test_logitnormal(g::LogitNormal, n_tsamples::Int=10^6, 9 | rng::Union{AbstractRNG, Missing} = missing) 10 | d = length(g) 11 | #mn = mean(g) 12 | md = median(g) 13 | #mo = mode(g) 14 | #s = var(g) 15 | #e = entropy(g) 16 | @test partype(g) == Float64 17 | #@test isa(mn, Float64) 18 | @test isa(md, Float64) 19 | #@test isa(mo, Float64) 20 | #@test isa(s, Float64) 21 | @test md ≈ logistic(g.μ) 22 | #@test entropy(g) ≈ d*(1 + Distributions.log2π)/2 + logdetcov(g.normal)/2 + sum(mean(g.normal)) 23 | @test insupport(g,1e-8) 24 | # corner cases of 0 and 1 handled as in support 25 | @test insupport(g,1.0) 26 | @test pdf(g,0.0) == 0.0 27 | @test insupport(g,0.0) 28 | @test pdf(g,1.0) == 0.0 29 | @test !insupport(g,-1e-8) 30 | @test pdf(g,-1e-8) == 0.0 31 | @test !insupport(g,1+1e-8) 32 | @test pdf(g,1+1e-8) == 0.0 33 | 34 | # sampling 35 | if ismissing(rng) 36 | X = rand(g, n_tsamples) 37 | else 38 | X = rand(rng, g, n_tsamples) 39 | end 40 | @test isa(X, Array{Float64,1}) 41 | 42 | # evaluation of logpdf and pdf 43 | for i = 1:min(100, n_tsamples) 44 | @test logpdf(g, X[i]) ≈ log(pdf(g, X[i])) 45 | end 46 | @test logpdf.(g, X) ≈ log.(pdf.(g, X)) 47 | @test isequal(logpdf(g, 0),-Inf) 48 | @test isequal(logpdf(g, 1),-Inf) 49 | @test isequal(logpdf(g, -eps()),-Inf) 50 | 51 | # test the location and scale functions 52 | @test location(g) == g.μ 53 | @test scale(g) == g.σ 54 | @test params(g) == (g.μ, g.σ) 55 | @test g == deepcopy(g) 56 | end 57 | 58 | ###### General Testing 59 | @testset "Logitnormal tests" begin 60 | test_logitnormal( LogitNormal() ) 61 | test_logitnormal( LogitNormal(2,0.5) ) 62 | d = LogitNormal(Float32(2)) 63 | typeof(rand(d, 5)) # still Float64 64 | @test typeof(convert(LogitNormal{Float64}, d)) == typeof(LogitNormal(2,1)) 65 | end 66 | -------------------------------------------------------------------------------- /test/categorical.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | using StableRNGs 4 | 5 | @testset "Categorical" begin 6 | 7 | for p in Any[ 8 | [0.5, 0.5], 9 | [0.5f0, 0.5f0], 10 | [1//2, 1//2], 11 | [0.1, 0.3, 0.2, 0.4], 12 | [0.15, 0.25, 0.6] ] 13 | 14 | d = Categorical(p) 15 | k = length(p) 16 | println(" testing $d as Categorical") 17 | 18 | @test isa(d, Categorical) 19 | @test probs(d) == p 20 | @test minimum(d) == 1 21 | @test maximum(d) == k 22 | @test extrema(d) == (1, k) 23 | @test ncategories(d) == k 24 | 25 | c = 0.0 26 | for i = 1:k 27 | c += p[i] 28 | @test pdf(d, i) == p[i] 29 | @test @inferred(logpdf(d, i)) === log(p[i]) 30 | @test @inferred(logpdf(d, float(i))) === log(p[i]) 31 | @test cdf(d, i) ≈ c 32 | @test ccdf(d, i) ≈ 1.0 - c 33 | end 34 | 35 | @test pdf(d, 0) == 0 36 | @test pdf(d, k+1) == 0 37 | @test logpdf(d, 0) == -Inf 38 | @test logpdf(d, k+1) == -Inf 39 | @test cdf(d, 0) == 0.0 40 | @test cdf(d, k+1) == 1.0 41 | @test ccdf(d, 0) == 1.0 42 | @test ccdf(d, k+1) == 0.0 43 | 44 | @test pdf.(d, support(d)) == p 45 | @test pdf.(d, 1:k) == p 46 | 47 | # The test utilities are currently only able to handle Float64s 48 | if partype(d) === Float64 49 | test_distr(d, 10^6) 50 | end 51 | end 52 | 53 | d = Categorical(4) 54 | println(" testing $d as Categorical") 55 | @test minimum(d) == 1 56 | @test maximum(d) == 4 57 | @test extrema(d) == (1, 4) 58 | @test probs(d) == [0.25, 0.25, 0.25, 0.25] 59 | 60 | p = ones(10^6) * 1.0e-6 61 | @test Distributions.isprobvec(p) 62 | 63 | @test typeof(convert(Categorical{Float32,Vector{Float32}}, d)) == Categorical{Float32,Vector{Float32}} 64 | @test typeof(convert(Categorical{Float32,Vector{Float32}}, d.p)) == Categorical{Float32,Vector{Float32}} 65 | 66 | @testset "test args... constructor" begin 67 | @test Categorical(0.3, 0.7) == Categorical([0.3, 0.7]) 68 | end 69 | 70 | @testset "reproducibility across julia versions" begin 71 | d= Categorical([0.1, 0.2, 0.7]) 72 | rng = StableRNGs.StableRNG(600) 73 | @test rand(rng, d, 10) == [2, 1, 3, 3, 2, 3, 3, 3, 3, 3] 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /test/truncnormal.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using Distributions, Random 3 | 4 | rng = MersenneTwister(123) 5 | 6 | @testset "Truncated normal, mean and variance" begin 7 | @test mean(truncated(Normal(0,1),100,115)) ≈ 100.00999800099926070518490239457545847490332879043 8 | @test mean(truncated(Normal(-2,3),50,70)) ≈ 50.171943499898757645751683644632860837133138152489 9 | @test mean(truncated(Normal(0,2),-100,0)) ≈ -1.59576912160573071175978423973752747390343452465973 10 | @test mean(truncated(Normal(0,1),-Inf,Inf)) == 0 11 | @test mean(truncated(Normal(0,1),0,+Inf)) ≈ +√(2/π) 12 | @test mean(truncated(Normal(0,1),-Inf,0)) ≈ -√(2/π) 13 | @test var(truncated(Normal(0,1),50,70)) ≈ 0.00039904318680389954790992722653605933053648912703600 14 | @test var(truncated(Normal(-2,3),50,70)) ≈ 0.029373438107168350377591231295634273607812172191712 15 | @test var(truncated(Normal(0,1),-Inf,Inf)) == 1 16 | @test var(truncated(Normal(0,1),0,+Inf)) ≈ 1 - 2/π 17 | @test var(truncated(Normal(0,1),-Inf,0)) ≈ 1 - 2/π 18 | # https://github.com/JuliaStats/Distributions.jl/issues/827 19 | @test mean(truncated(Normal(1000000,1),0,1000)) ≈ 999.999998998998999001005011019018990904720462367106 20 | @test var(truncated(Normal(),999000,1e6)) ≥ 0 21 | @test var(truncated(Normal(1000000,1),0,1000)) ≥ 0 22 | # https://github.com/JuliaStats/Distributions.jl/issues/624 23 | @test rand(truncated(Normal(+Inf, 1), 0, 1)) ≈ 1 24 | @test rand(truncated(Normal(-Inf, 1), 0, 1)) ≈ 0 25 | end 26 | @testset "Truncated normal $trunc" begin 27 | trunc = truncated(Normal(0, 1), -2, 2) 28 | @testset "Truncated normal $trunc with $func" for func in 29 | [(r = rand, r! = rand!), 30 | (r = ((d, n) -> rand(rng, d, n)), r! = ((d, X) -> rand!(rng, d, X)))] 31 | repeats = 1000000 32 | 33 | @test abs(mean(func.r(trunc, repeats))) < 0.01 34 | @test abs(median(func.r(trunc, repeats))) < 0.01 35 | @test abs(var(func.r(trunc, repeats)) - var(trunc)) < 0.01 36 | 37 | X = Matrix{Float64}(undef, 1000, 1000) 38 | func.r!(trunc, X) 39 | @test abs(mean(X)) < 0.01 40 | @test abs(median(X)) < 0.01 41 | @test abs(var(X) - var(trunc)) < 0.01 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /src/univariate/continuous/studentizedrange.jl: -------------------------------------------------------------------------------- 1 | # Modified from code provided by "Doomphoenix Qxz" on discourse.julialang.com 2 | """ 3 | StudentizedRange(ν, k) 4 | 5 | The *studentized range distribution* has probability density function: 6 | 7 | ```math 8 | f(q; k, \\nu) = \\frac{\\sqrt{2\\pi}k(k - 1)\\nu^{\\nu/2}}{\\Gamma{\\left(\\frac{\\nu}{2}\\right)}2^{\\nu/2 - 1}} \\int_{0}^{\\infty} {x^{\\nu}\\phi(\\sqrt{\\nu}x)} \\left[\\int_{-\\infty}^{\\infty} {\\phi(u)\\phi(u - qx)[\\Phi(u) - \\Phi(u - qx)]^{k - 2}}du\\right]dx 9 | ``` 10 | 11 | where 12 | 13 | ```math 14 | \\begin{align*} 15 | \\Phi(x) &= \\frac{1 + erf(\\frac{x}{\\sqrt{2}})}{2} &&(\\text{Normal Distribution CDF})\\\\ 16 | \\phi(x) &= \\Phi'(x) &&(\\text{Normal Distribution PDF}) 17 | \\end{align*} 18 | ``` 19 | 20 | ```julia 21 | StudentizedRange(ν, k) # Studentized Range Distribution with parameters ν and k 22 | 23 | params(d) # Get the parameters, i.e. (ν, k) 24 | ``` 25 | 26 | External links 27 | 28 | * [Studentized range distribution on Wikipedia](http://en.wikipedia.org/wiki/Studentized_range_distribution) 29 | """ 30 | struct StudentizedRange{T<:Real} <: ContinuousUnivariateDistribution 31 | ν::T 32 | k::T 33 | StudentizedRange{T}(ν::T, k::T) where {T <: Real} = new{T}(ν, k) 34 | end 35 | 36 | function StudentizedRange(ν::T, k::T; check_args=true) where {T <: Real} 37 | check_args && @check_args(StudentizedRange, ν > zero(ν) && k > one(k)) 38 | return StudentizedRange{T}(ν, k) 39 | end 40 | 41 | StudentizedRange(ν::Integer, k::Integer) = StudentizedRange(float(ν), float(k)) 42 | StudentizedRange(ν::Real, k::Real) = StudentizedRange(promote(ν, k)...) 43 | 44 | @distr_support StudentizedRange 0.0 Inf 45 | 46 | 47 | ### Conversions 48 | 49 | function convert(::Type{StudentizedRange{T}}, ν::S, k::S) where {T <: Real, S <: Real} 50 | StudentizedRange(T(ν), T(k)) 51 | end 52 | 53 | function convert(::Type{StudentizedRange{T}}, d::StudentizedRange{S}) where {T <: Real, S <: Real} 54 | StudentizedRange(T(d.ν), T(d.k), check_args=false) 55 | end 56 | 57 | ### Parameters 58 | params(d::StudentizedRange) = (d.ν, d.k) 59 | @inline partype(d::StudentizedRange{T}) where {T <: Real} = T 60 | 61 | 62 | ### Evaluation & Sampling 63 | 64 | @_delegate_statsfuns StudentizedRange srdist k ν 65 | -------------------------------------------------------------------------------- /src/multivariate/product.jl: -------------------------------------------------------------------------------- 1 | import Statistics: mean, var, cov 2 | 3 | """ 4 | Product <: MultivariateDistribution 5 | 6 | An N dimensional `MultivariateDistribution` constructed from a vector of N independent 7 | `UnivariateDistribution`s. 8 | 9 | ```julia 10 | Product(Uniform.(rand(10), 1)) # A 10-dimensional Product from 10 independent `Uniform` distributions. 11 | ``` 12 | """ 13 | struct Product{ 14 | S<:ValueSupport, 15 | T<:UnivariateDistribution{S}, 16 | V<:AbstractVector{T}, 17 | } <: MultivariateDistribution{S} 18 | v::V 19 | function Product(v::V) where 20 | V<:AbstractVector{T} where 21 | T<:UnivariateDistribution{S} where 22 | S<:ValueSupport 23 | return new{S, T, V}(v) 24 | end 25 | end 26 | 27 | length(d::Product) = length(d.v) 28 | function Base.eltype(::Type{<:Product{S,T}}) where {S<:ValueSupport, 29 | T<:UnivariateDistribution{S}} 30 | return eltype(T) 31 | end 32 | 33 | _rand!(rng::AbstractRNG, d::Product, x::AbstractVector{<:Real}) = 34 | broadcast!(dn->rand(rng, dn), x, d.v) 35 | _logpdf(d::Product, x::AbstractVector{<:Real}) = 36 | sum(n->logpdf(d.v[n], x[n]), 1:length(d)) 37 | 38 | mean(d::Product) = mean.(d.v) 39 | var(d::Product) = var.(d.v) 40 | cov(d::Product) = Diagonal(var(d)) 41 | entropy(d::Product) = sum(entropy, d.v) 42 | insupport(d::Product, x::AbstractVector) = all(insupport.(d.v, x)) 43 | 44 | """ 45 | product_distribution(dists::AbstractVector{<:UnivariateDistribution}) 46 | 47 | Creates a multivariate product distribution `P` from a vector of univariate distributions. 48 | Fallback is the `Product constructor`, but specialized methods can be defined 49 | for distributions with a special multivariate product. 50 | """ 51 | function product_distribution(dists::AbstractVector{<:UnivariateDistribution}) 52 | return Product(dists) 53 | end 54 | 55 | """ 56 | product_distribution(dists::AbstractVector{<:Normal}) 57 | 58 | Computes the multivariate Normal distribution obtained by stacking the univariate 59 | normal distributions. The result is a multivariate Gaussian with a diagonal 60 | covariance matrix. 61 | """ 62 | function product_distribution(dists::AbstractVector{<:Normal}) 63 | µ = mean.(dists) 64 | σ = std.(dists) 65 | return MvNormal(µ, σ) 66 | end 67 | -------------------------------------------------------------------------------- /src/univariate/continuous/triweight.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Triweight(μ, σ) 3 | """ 4 | struct Triweight{T<:Real} <: ContinuousUnivariateDistribution 5 | μ::T 6 | σ::T 7 | Triweight{T}(µ::T, σ::T) where {T} = new{T}(µ, σ) 8 | end 9 | 10 | function Triweight(μ::T, σ::T; check_args=true) where {T <: Real} 11 | check_args && @check_args(Triweight, σ > zero(σ)) 12 | return Triweight{T}(μ, σ) 13 | end 14 | 15 | Triweight(μ::Real, σ::Real) = Triweight(promote(μ, σ)...) 16 | Triweight(μ::Integer, σ::Integer) = Triweight(float(μ), float(σ)) 17 | Triweight(μ::T) where {T <: Real} = Triweight(μ, one(T)) 18 | Triweight() = Triweight(0.0, 1.0, check_args=false) 19 | 20 | @distr_support Triweight d.μ - d.σ d.μ + d.σ 21 | 22 | ## Conversions 23 | 24 | convert(::Type{Triweight{T}}, μ::Real, σ::Real) where {T<:Real} = Triweight(T(μ), T(σ)) 25 | convert(::Type{Triweight{T}}, d::Triweight{S}) where {T<:Real, S<:Real} = Triweight(T(d.μ), T(d.σ), check_args=false) 26 | 27 | ## Parameters 28 | 29 | location(d::Triweight) = d.μ 30 | scale(d::Triweight) = d.σ 31 | params(d::Triweight) = (d.μ, d.σ) 32 | @inline partype(d::Triweight{T}) where {T<:Real} = T 33 | 34 | 35 | ## Properties 36 | mean(d::Triweight) = d.μ 37 | median(d::Triweight) = d.μ 38 | mode(d::Triweight) = d.μ 39 | 40 | var(d::Triweight) = d.σ^2 / 9 41 | skewness(d::Triweight{T}) where {T<:Real} = zero(T) 42 | kurtosis(d::Triweight{T}) where {T<:Real} = T(1)/33 - 3 43 | 44 | ## Functions 45 | function pdf(d::Triweight{T}, x::Real) where T<:Real 46 | u = abs(x - d.μ)/d.σ 47 | u >= 1 ? zero(T) : 1.09375*(1 - u*u)^3/d.σ 48 | end 49 | 50 | function cdf(d::Triweight{T}, x::Real) where T<:Real 51 | u = (x - d.μ)/d.σ 52 | u ≤ -1 ? zero(T) : u ≥ 1 ? one(T) : 0.03125*(1 + u)^4*@horner(u,16,-29,20,-5) 53 | end 54 | 55 | function ccdf(d::Triweight{T}, x::Real) where T<:Real 56 | u = (d.μ - x)/d.σ 57 | u ≤ -1 ? zero(T) : u ≥ 1 ? one(T) : 0.03125*(1 + u)^4*@horner(u,16,-29,20,-5) 58 | end 59 | 60 | @quantile_newton Triweight 61 | 62 | function mgf(d::Triweight{T}, t::Float64) where T<:Real 63 | a = d.σ*t 64 | a2 = a*a 65 | a == 0 ? one(T) : 105*exp(d.μ*t)*((15/a2+1)*cosh(a)-(15/a2-6)/a*sinh(a))/(a2*a2) 66 | end 67 | 68 | function cf(d::Triweight{T}, t::Float64) where T<:Real 69 | a = d.σ*t 70 | a2 = a*a 71 | a == 0 ? complex(one(T)) : 105*cis(d.μ*t)*((1-15/a2)*cos(a)+(15/a2-6)/a*sin(a))/(a2*a2) 72 | end 73 | -------------------------------------------------------------------------------- /src/show.jl: -------------------------------------------------------------------------------- 1 | 2 | 3 | # the name of a distribution 4 | # 5 | # Generally, this should be just the type name, e.g. Normal. 6 | # Under certain circumstances, one may want to specialize 7 | # this function to provide a name that is easier to read, 8 | # especially when the type is parametric. 9 | # 10 | distrname(d::Distribution) = string(typeof(d)) 11 | 12 | show(io::IO, d::Distribution) = show(io, d, fieldnames(typeof(d))) 13 | 14 | # For some distributions, the fields may contain internal details, 15 | # which we don't want to show, this function allows one to 16 | # specify which fields to show. 17 | # 18 | function show(io::IO, d::Distribution, pnames) 19 | uml, namevals = _use_multline_show(d, pnames) 20 | uml ? show_multline(io, d, namevals) : show_oneline(io, d, namevals) 21 | end 22 | 23 | const _NameVal = Tuple{Symbol,Any} 24 | 25 | function _use_multline_show(d::Distribution, pnames) 26 | # decide whether to use one-line or multi-line format 27 | # 28 | # Criteria: if total number of values is greater than 8, or 29 | # there are matrix-valued params, we use multi-line format 30 | # 31 | namevals = _NameVal[] 32 | multline = false 33 | tlen = 0 34 | for (i, p) in enumerate(pnames) 35 | pv = getfield(d, p) 36 | if !(isa(pv, Number) || isa(pv, NTuple) || isa(pv, AbstractVector)) 37 | multline = true 38 | else 39 | tlen += length(pv) 40 | end 41 | push!(namevals, (p, pv)) 42 | end 43 | if tlen > 8 44 | multline = true 45 | end 46 | return (multline, namevals) 47 | end 48 | 49 | function _use_multline_show(d::Distribution) 50 | _use_multline_show(d, fieldnames(typeof(d))) 51 | end 52 | 53 | function show_oneline(io::IO, d::Distribution, namevals) 54 | print(io, distrname(d)) 55 | np = length(namevals) 56 | print(io, '(') 57 | for (i, nv) in enumerate(namevals) 58 | (p, pv) = nv 59 | print(io, p) 60 | print(io, '=') 61 | show(io, pv) 62 | if i < np 63 | print(io, ", ") 64 | end 65 | end 66 | print(io, ')') 67 | end 68 | 69 | function show_multline(io::IO, d::Distribution, namevals) 70 | print(io, distrname(d)) 71 | println(io, "(") 72 | for (p, pv) in namevals 73 | print(io, p) 74 | print(io, ": ") 75 | println(io, pv) 76 | end 77 | println(io, ")") 78 | end 79 | -------------------------------------------------------------------------------- /test/utils.jl: -------------------------------------------------------------------------------- 1 | using Distributions, PDMats 2 | using Test, LinearAlgebra 3 | import Distributions: ispossemdef 4 | 5 | # RealInterval 6 | r = RealInterval(1.5, 4.0) 7 | @test minimum(r) == 1.5 8 | @test maximum(r) == 4.0 9 | @test extrema(r) == (1.5, 4.0) 10 | 11 | @test partype(Gamma(1, 2)) == Float64 12 | @test partype(Gamma(1.1, 2)) == Float64 13 | @test partype(Normal(1//1, 2//1)) == Rational{Int} 14 | @test partype(MvNormal(rand(Float32, 5), Matrix{Float32}(I, 5, 5))) == Float32 15 | 16 | # special cases 17 | @test partype(Kolmogorov()) == Float64 18 | @test partype(Hypergeometric(2, 2, 2)) == Float64 19 | @test partype(DiscreteUniform(0, 4)) == Float64 20 | 21 | A = rand(1:10, 5, 5) 22 | B = rand(Float32, 4) 23 | C = 1//2 24 | L = rand(Float32, 4, 4) 25 | D = PDMats.PDMat(L * L') 26 | 27 | # Ensure that utilities functions works with abstract arrays 28 | 29 | @test isprobvec(GenericArray([1, 1, 1])) == false 30 | @test isprobvec(GenericArray([1/3, 1/3, 1/3])) 31 | 32 | # Positive definite matrix 33 | M = GenericArray([1.0 0.0; 0.0 1.0]) 34 | # Non-invertible matrix 35 | N = GenericArray([1.0 0.0; 1.0 0.0]) 36 | 37 | @test Distributions.isApproxSymmmetric(N) == false 38 | @test Distributions.isApproxSymmmetric(M) 39 | 40 | 41 | n = 10 42 | areal = randn(n,n)/2 43 | aimg = randn(n,n)/2 44 | @testset "For A containing $eltya" for eltya in (Float32, Float64, ComplexF32, ComplexF64, Int) 45 | ainit = eltya == Int ? rand(1:7, n, n) : convert(Matrix{eltya}, eltya <: Complex ? complex.(areal, aimg) : areal) 46 | @testset "Positive semi-definiteness" begin 47 | notsymmetric = ainit 48 | notsquare = [ainit ainit] 49 | @test !ispossemdef(notsymmetric) 50 | @test !ispossemdef(notsquare) 51 | for truerank in 0:n 52 | X = ainit[:, 1:truerank] 53 | A = truerank == 0 ? zeros(eltya, n, n) : X * X' 54 | @test ispossemdef(A) 55 | for testrank in 0:n 56 | if testrank == truerank 57 | @test ispossemdef(A, testrank) 58 | else 59 | @test !ispossemdef(A, testrank) 60 | end 61 | end 62 | @test !ispossemdef(notsymmetric, truerank) 63 | @test !ispossemdef(notsquare, truerank) 64 | @test_throws ArgumentError ispossemdef(A, -1) 65 | @test_throws ArgumentError ispossemdef(A, n + 1) 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /src/genericrand.jl: -------------------------------------------------------------------------------- 1 | ### Generic rand methods 2 | 3 | """ 4 | rand([rng::AbstractRNG,] s::Sampleable) 5 | 6 | Generate one sample for `s`. 7 | 8 | rand([rng::AbstractRNG,] s::Sampleable, n::Int) 9 | 10 | Generate `n` samples from `s`. The form of the returned object depends on the variate form of `s`: 11 | 12 | - When `s` is univariate, it returns a vector of length `n`. 13 | - When `s` is multivariate, it returns a matrix with `n` columns. 14 | - When `s` is matrix-variate, it returns an array, where each element is a sample matrix. 15 | 16 | rand([rng::AbstractRNG,] s::Sampleable, dim1::Int, dim2::Int...) 17 | rand([rng::AbstractRNG,] s::Sampleable, dims::Dims) 18 | 19 | Generate an array of samples from `s` whose shape is determined by the given 20 | dimensions. 21 | """ 22 | rand(s::Sampleable) = rand(GLOBAL_RNG, s) 23 | rand(s::Sampleable, dims::Dims) = rand(GLOBAL_RNG, s, dims) 24 | rand(s::Sampleable, dim1::Int, moredims::Int...) = 25 | rand(GLOBAL_RNG, s, (dim1, moredims...)) 26 | rand(rng::AbstractRNG, s::Sampleable, dim1::Int, moredims::Int...) = 27 | rand(rng, s, (dim1, moredims...)) 28 | 29 | """ 30 | rand!([rng::AbstractRNG,] s::Sampleable, A::AbstractArray) 31 | 32 | Generate one or multiple samples from `s` to a pre-allocated array `A`. `A` should be in the 33 | form as specified above. The rules are summarized as below: 34 | 35 | - When `s` is univariate, `A` can be an array of arbitrary shape. Each element of `A` will 36 | be overriden by one sample. 37 | - When `s` is multivariate, `A` can be a vector to store one sample, or a matrix with each 38 | column for a sample. 39 | - When `s` is matrix-variate, `A` can be a matrix to store one sample, or an array of 40 | matrices with each element for a sample matrix. 41 | """ 42 | function rand! end 43 | rand!(s::Sampleable, X::AbstractArray{<:AbstractArray}, allocate::Bool) = 44 | rand!(GLOBAL_RNG, s, X, allocate) 45 | rand!(s::Sampleable, X::AbstractArray) = rand!(GLOBAL_RNG, s, X) 46 | rand!(rng::AbstractRNG, s::Sampleable, X::AbstractArray) = _rand!(rng, s, X) 47 | 48 | """ 49 | sampler(d::Distribution) -> Sampleable 50 | sampler(s::Sampleable) -> s 51 | 52 | Samplers can often rely on pre-computed quantities (that are not parameters 53 | themselves) to improve efficiency. If such a sampler exists, it can be provided 54 | with this `sampler` method, which would be used for batch sampling. 55 | The general fallback is `sampler(d::Distribution) = d`. 56 | """ 57 | sampler(s::Sampleable) = s 58 | -------------------------------------------------------------------------------- /test/ref/rdistributions.R: -------------------------------------------------------------------------------- 1 | # R6 classes for representing distributions in R 2 | 3 | library(R6) 4 | library(extraDistr) 5 | 6 | ################################################# 7 | # 8 | # Base classes 9 | # 10 | ################################################# 11 | 12 | DiscreteDistribution <- R6Class("DiscreteDistribution", 13 | public = list(is.discrete=TRUE) 14 | ) 15 | 16 | ContinuousDistribution <- R6Class("ContinuousDistribution", 17 | public = list(is.discrete=FALSE) 18 | ) 19 | 20 | ################################################# 21 | # 22 | # Discrete distributions 23 | # 24 | ################################################# 25 | 26 | source("discrete/bernoulli.R") 27 | source("discrete/binomial.R") 28 | source("discrete/betabinomial.R") 29 | source("discrete/discreteuniform.R") 30 | source("discrete/geometric.R") 31 | source("discrete/hypergeometric.R") 32 | source("discrete/negativebinomial.R") 33 | source("discrete/noncentralhypergeometric.R") 34 | source("discrete/poisson.R") 35 | source("discrete/skellam.R") 36 | 37 | ################################################# 38 | # 39 | # Continuous distributions 40 | # 41 | ################################################# 42 | 43 | source("continuous/arcsine.R") 44 | source("continuous/beta.R") 45 | source("continuous/betaprime.R") 46 | source("continuous/cauchy.R") 47 | source("continuous/chi.R") 48 | source("continuous/chisq.R") 49 | source("continuous/cosine.R") 50 | source("continuous/exponential.R") 51 | source("continuous/fdist.R") 52 | source("continuous/frechet.R") 53 | source("continuous/gamma.R") 54 | source("continuous/generalizedextremevalue.R") 55 | source("continuous/generalizedpareto.R") 56 | source("continuous/gumbel.R") 57 | source("continuous/inversegamma.R") 58 | source("continuous/inversegaussian.R") 59 | source("continuous/laplace.R") 60 | source("continuous/levy.R") 61 | source("continuous/logistic.R") 62 | source("continuous/lognormal.R") 63 | source("continuous/noncentralbeta.R") 64 | source("continuous/noncentralchisq.R") 65 | source("continuous/noncentralf.R") 66 | source("continuous/noncentralt.R") 67 | source("continuous/normal.R") 68 | source("continuous/normalinversegaussian.R") 69 | source("continuous/pareto.R") 70 | source("continuous/rayleigh.R") 71 | source("continuous/studentizedrange.R") 72 | source("continuous/tdist.R") 73 | source("continuous/triangulardist.R") 74 | source("continuous/truncatednormal.R") 75 | source("continuous/uniform.R") 76 | source("continuous/vonmises.R") 77 | source("continuous/weibull.R") 78 | -------------------------------------------------------------------------------- /src/univariate/continuous/chisq.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Chisq(ν) 3 | The *Chi squared distribution* (typically written χ²) with `ν` degrees of freedom has the 4 | probability density function 5 | 6 | ```math 7 | f(x; k) = \\frac{x^{k/2 - 1} e^{-x/2}}{2^{k/2} \\Gamma(k/2)}, \\quad x > 0. 8 | ``` 9 | 10 | If `ν` is an integer, then it is the distribution of the sum of squares of `ν` independent standard [`Normal`](@ref) variates. 11 | 12 | ```julia 13 | Chisq(k) # Chi-squared distribution with k degrees of freedom 14 | 15 | params(d) # Get the parameters, i.e. (k,) 16 | dof(d) # Get the degrees of freedom, i.e. k 17 | ``` 18 | 19 | External links 20 | 21 | * [Chi-squared distribution on Wikipedia](http://en.wikipedia.org/wiki/Chi-squared_distribution) 22 | """ 23 | struct Chisq{T<:Real} <: ContinuousUnivariateDistribution 24 | ν::T 25 | Chisq{T}(ν::T) where {T} = new{T}(ν) 26 | end 27 | 28 | function Chisq(ν::T; check_args=true) where {T <: Real} 29 | check_args && @check_args(Chisq, ν > zero(ν)) 30 | return Chisq{T}(ν) 31 | end 32 | 33 | Chisq(ν::Integer) = Chisq(float(ν)) 34 | 35 | @distr_support Chisq 0.0 Inf 36 | 37 | #### Parameters 38 | 39 | dof(d::Chisq) = d.ν 40 | params(d::Chisq) = (d.ν,) 41 | @inline partype(d::Chisq{T}) where {T<:Real} = T 42 | 43 | ### Conversions 44 | convert(::Type{Chisq{T}}, ν::Real) where {T<:Real} = Chisq(T(ν)) 45 | convert(::Type{Chisq{T}}, d::Chisq{S}) where {T <: Real, S <: Real} = Chisq(T(d.ν)) 46 | 47 | 48 | #### Statistics 49 | 50 | mean(d::Chisq) = d.ν 51 | 52 | var(d::Chisq) = 2d.ν 53 | 54 | skewness(d::Chisq) = sqrt(8 / d.ν) 55 | 56 | kurtosis(d::Chisq) = 12 / d.ν 57 | 58 | mode(d::Chisq{T}) where {T<:Real} = d.ν > 2 ? d.ν - 2 : zero(T) 59 | 60 | function median(d::Chisq; approx::Bool=false) 61 | if approx 62 | return d.ν * (1 - 2 / (9 * d.ν))^3 63 | else 64 | return quantile(d, 1//2) 65 | end 66 | end 67 | 68 | function entropy(d::Chisq) 69 | hν = d.ν/2 70 | hν + logtwo + loggamma(hν) + (1 - hν) * digamma(hν) 71 | end 72 | 73 | 74 | #### Evaluation 75 | 76 | @_delegate_statsfuns Chisq chisq ν 77 | 78 | mgf(d::Chisq, t::Real) = (1 - 2 * t)^(-d.ν/2) 79 | 80 | cf(d::Chisq, t::Real) = (1 - 2 * im * t)^(-d.ν/2) 81 | 82 | gradlogpdf(d::Chisq{T}, x::Real) where {T<:Real} = x > 0 ? (d.ν/2 - 1) / x - 1//2 : zero(T) 83 | 84 | 85 | #### Sampling 86 | 87 | rand(rng::AbstractRNG, d::Chisq) = 88 | (ν = d.ν; rand(rng, Gamma(ν / 2.0, 2.0one(ν)))) 89 | 90 | sampler(d::Chisq) = (ν = d.ν; sampler(Gamma(ν / 2.0, 2.0one(ν)))) 91 | -------------------------------------------------------------------------------- /src/univariate/continuous/epanechnikov.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Epanechnikov(μ, σ) 3 | """ 4 | struct Epanechnikov{T<:Real} <: ContinuousUnivariateDistribution 5 | μ::T 6 | σ::T 7 | Epanechnikov{T}(µ::T, σ::T) where {T} = new{T}(µ, σ) 8 | end 9 | 10 | function Epanechnikov(μ::T, σ::T; check_args=true) where {T<:Real} 11 | check_args && @check_args(Epanechnikov, σ > zero(σ)) 12 | return Epanechnikov{T}(μ, σ) 13 | end 14 | 15 | Epanechnikov(μ::Real, σ::Real) = Epanechnikov(promote(μ, σ)...) 16 | Epanechnikov(μ::Integer, σ::Integer) = Epanechnikov(float(μ), float(σ)) 17 | Epanechnikov(μ::T) where {T <: Real} = Epanechnikov(μ, one(T)) 18 | Epanechnikov() = Epanechnikov(0.0, 1.0, check_args=false) 19 | 20 | @distr_support Epanechnikov d.μ - d.σ d.μ + d.σ 21 | 22 | #### Conversions 23 | function convert(::Type{Epanechnikov{T}}, μ::Real, σ::Real) where T<:Real 24 | Epanechnikov(T(μ), T(σ), check_args=false) 25 | end 26 | function convert(::Type{Epanechnikov{T}}, d::Epanechnikov{S}) where {T <: Real, S <: Real} 27 | Epanechnikov(T(d.μ), T(d.σ), check_args=false) 28 | end 29 | 30 | ## Parameters 31 | 32 | location(d::Epanechnikov) = d.μ 33 | scale(d::Epanechnikov) = d.σ 34 | params(d::Epanechnikov) = (d.μ, d.σ) 35 | @inline partype(d::Epanechnikov{T}) where {T<:Real} = T 36 | 37 | ## Properties 38 | mean(d::Epanechnikov) = d.μ 39 | median(d::Epanechnikov) = d.μ 40 | mode(d::Epanechnikov) = d.μ 41 | 42 | var(d::Epanechnikov) = d.σ^2 / 5 43 | skewness(d::Epanechnikov{T}) where {T<:Real} = zero(T) 44 | kurtosis(d::Epanechnikov{T}) where {T<:Real} = -2.914285714285714*one(T) # 3/35-3 45 | 46 | ## Functions 47 | function pdf(d::Epanechnikov{T}, x::Real) where T<:Real 48 | u = abs(x - d.μ) / d.σ 49 | u >= 1 ? zero(T) : 3 * (1 - u^2) / (4 * d.σ) 50 | end 51 | 52 | function cdf(d::Epanechnikov{T}, x::Real) where T<:Real 53 | u = (x - d.μ) / d.σ 54 | u <= -1 ? zero(T) : 55 | u >= 1 ? one(T) : 56 | 1//2 + u * (3//4 - u^2/4) 57 | end 58 | 59 | function ccdf(d::Epanechnikov{T}, x::Real) where T<:Real 60 | u = (d.μ - x) / d.σ 61 | u <= -1 ? zero(T) : 62 | u >= 1 ? one(T) : 63 | 1//2 + u * (3//4 - u^2/4) 64 | end 65 | 66 | @quantile_newton Epanechnikov 67 | 68 | function mgf(d::Epanechnikov{T}, t::Real) where T<:Real 69 | a = d.σ * t 70 | a == 0 ? one(T) : 71 | 3exp(d.μ * t) * (cosh(a) - sinh(a) / a) / a^2 72 | end 73 | 74 | function cf(d::Epanechnikov{T}, t::Real) where T<:Real 75 | a = d.σ * t 76 | a == 0 ? one(T)+zero(T)*im : 77 | -3exp(im * d.μ * t) * (cos(a) - sin(a) / a) / a^2 78 | end 79 | -------------------------------------------------------------------------------- /src/matrix/matrixreshaped.jl: -------------------------------------------------------------------------------- 1 | """ 2 | MatrixReshaped(D, n, p) 3 | ```julia 4 | D::MultivariateDistribution base distribution 5 | n::Integer number of rows 6 | p::Integer number of columns 7 | ``` 8 | Reshapes a multivariate distribution into a matrix distribution with n rows and 9 | p columns. 10 | 11 | """ 12 | struct MatrixReshaped{S<:ValueSupport,D<:MultivariateDistribution{S}} <: 13 | MatrixDistribution{S} 14 | d::D 15 | num_rows::Int 16 | num_cols::Int 17 | function MatrixReshaped( 18 | d::D, 19 | n::N, 20 | p::N, 21 | ) where { 22 | D<:MultivariateDistribution{ 23 | S, 24 | }, 25 | } where {S<:ValueSupport} where {N<:Integer} 26 | (n > 0 && p > 0) || throw(ArgumentError("n and p should be positive")) 27 | n * p == length(d) || 28 | throw(ArgumentError("Dimensions provided ($n x $p) do not match source distribution of length $(length(d))")) 29 | return new{S,D}(d, n, p) 30 | end 31 | end 32 | 33 | MatrixReshaped(D::MultivariateDistribution, n::Integer) = 34 | MatrixReshaped(D, n, n) 35 | 36 | show(io::IO, d::MatrixReshaped) = 37 | show_multline(io, d, [(:num_rows, d.num_rows), (:num_cols, d.num_cols)]) 38 | 39 | 40 | # ----------------------------------------------------------------------------- 41 | # Properties 42 | # ----------------------------------------------------------------------------- 43 | 44 | size(d::MatrixReshaped) = (d.num_rows, d.num_cols) 45 | 46 | length(d::MatrixReshaped) = length(d.d) 47 | 48 | rank(d::MatrixReshaped) = minimum(size(d)) 49 | 50 | function insupport(d::MatrixReshaped, X::AbstractMatrix) 51 | return isreal(X) && size(d) == size(X) && insupport(d.d, vec(X)) 52 | end 53 | 54 | mean(d::MatrixReshaped) = reshape(mean(d.d), size(d)) 55 | mode(d::MatrixReshaped) = reshape(mode(d.d), size(d)) 56 | cov(d::MatrixReshaped, ::Val{true} = Val(true)) = 57 | reshape(cov(d.d), prod(size(d)), prod(size(d))) 58 | cov(d::MatrixReshaped, ::Val{false}) = 59 | ((n, p) = size(d); reshape(cov(d), n, p, n, p)) 60 | var(d::MatrixReshaped) = reshape(var(d.d), size(d)) 61 | 62 | params(d::MatrixReshaped) = (d.d, d.num_rows, d.num_cols) 63 | 64 | @inline partype( 65 | d::MatrixReshaped{S,<:MultivariateDistribution{S}}, 66 | ) where {S<:Real} = S 67 | 68 | _logpdf(d::MatrixReshaped, X::AbstractMatrix) = logpdf(d.d, vec(X)) 69 | 70 | function _rand!(rng::AbstractRNG, d::MatrixReshaped, Y::AbstractMatrix) 71 | rand!(rng, d.d, view(Y, :)) 72 | return Y 73 | end 74 | 75 | vec(d::MatrixReshaped) = d.d 76 | -------------------------------------------------------------------------------- /src/univariate/continuous/cosine.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Cosine(μ, σ) 3 | 4 | A raised Cosine distribution. 5 | 6 | External link: 7 | 8 | * [Cosine distribution on wikipedia](http://en.wikipedia.org/wiki/Raised_cosine_distribution) 9 | """ 10 | struct Cosine{T<:Real} <: ContinuousUnivariateDistribution 11 | μ::T 12 | σ::T 13 | Cosine{T}(μ::T, σ::T) where {T} = new{T}(µ, σ) 14 | end 15 | 16 | function Cosine(μ::T, σ::T; check_args=true) where {T <: Real} 17 | check_args && @check_args(Cosine, σ > zero(σ)) 18 | return Cosine{T}(μ, σ) 19 | end 20 | 21 | Cosine(μ::Real, σ::Real) = Cosine(promote(μ, σ)...) 22 | Cosine(μ::Integer, σ::Integer) = Cosine(float(μ), float(σ)) 23 | Cosine(μ::T) where {T <: Real} = Cosine(μ, one(µ)) 24 | Cosine() = Cosine(0.0, 1.0, check_args=false) 25 | 26 | @distr_support Cosine d.μ - d.σ d.μ + d.σ 27 | 28 | #### Conversions 29 | function convert(::Type{Cosine{T}}, μ::Real, σ::Real) where T<:Real 30 | Cosine(T(μ), T(σ)) 31 | end 32 | function convert(::Type{Cosine{T}}, d::Cosine{S}) where {T <: Real, S <: Real} 33 | Cosine(T(d.μ), T(d.σ), check_args=false) 34 | end 35 | 36 | #### Parameters 37 | 38 | location(d::Cosine) = d.μ 39 | scale(d::Cosine) = d.σ 40 | 41 | params(d::Cosine) = (d.μ, d.σ) 42 | @inline partype(d::Cosine{T}) where {T<:Real} = T 43 | 44 | 45 | #### Statistics 46 | 47 | mean(d::Cosine) = d.μ 48 | 49 | median(d::Cosine) = d.μ 50 | 51 | mode(d::Cosine) = d.μ 52 | 53 | var(d::Cosine{T}) where {T<:Real} = d.σ^2 * (1//3 - 2/T(π)^2) 54 | 55 | skewness(d::Cosine{T}) where {T<:Real} = zero(T) 56 | 57 | kurtosis(d::Cosine{T}) where {T<:Real} = 6*(90-T(π)^4) / (5*(T(π)^2-6)^2) 58 | 59 | 60 | #### Evaluation 61 | 62 | function pdf(d::Cosine{T}, x::Real) where T<:Real 63 | if insupport(d, x) 64 | z = (x - d.μ) / d.σ 65 | return (1 + cospi(z)) / (2d.σ) 66 | else 67 | return zero(T) 68 | end 69 | end 70 | 71 | function logpdf(d::Cosine{T}, x::Real) where T<:Real 72 | insupport(d, x) ? log(pdf(d, x)) : -T(Inf) 73 | end 74 | 75 | function cdf(d::Cosine{T}, x::Real) where T<:Real 76 | if x < d.μ - d.σ 77 | return zero(T) 78 | end 79 | if x > d.μ + d.σ 80 | return one(T) 81 | end 82 | z = (x - d.μ) / d.σ 83 | (1 + z + sinpi(z) * invπ) / 2 84 | end 85 | 86 | function ccdf(d::Cosine{T}, x::Real) where T<:Real 87 | if x < d.μ - d.σ 88 | return one(T) 89 | end 90 | if x > d.μ + d.σ 91 | return zero(T) 92 | end 93 | nz = (d.μ - x) / d.σ 94 | (1 + nz + sinpi(nz) * invπ) / 2 95 | end 96 | 97 | quantile(d::Cosine, p::Real) = quantile_bisect(d, p) 98 | -------------------------------------------------------------------------------- /test/types.jl: -------------------------------------------------------------------------------- 1 | # Test type relations 2 | 3 | using Distributions 4 | using ForwardDiff: Dual 5 | 6 | @assert UnivariateDistribution <: Distribution 7 | @assert MultivariateDistribution <: Distribution 8 | @assert MatrixDistribution <: Distribution 9 | 10 | @assert DiscreteDistribution <: Distribution 11 | @assert ContinuousDistribution <: Distribution 12 | 13 | @assert DiscreteUnivariateDistribution <: DiscreteDistribution 14 | @assert DiscreteUnivariateDistribution <: UnivariateDistribution 15 | @assert ContinuousUnivariateDistribution <: ContinuousDistribution 16 | @assert ContinuousUnivariateDistribution <: UnivariateDistribution 17 | @assert DiscreteMultivariateDistribution <: DiscreteDistribution 18 | @assert DiscreteMultivariateDistribution <: MultivariateDistribution 19 | @assert ContinuousMultivariateDistribution <: ContinuousDistribution 20 | @assert ContinuousMultivariateDistribution <: MultivariateDistribution 21 | @assert DiscreteMatrixDistribution <: DiscreteDistribution 22 | @assert DiscreteMatrixDistribution <: MatrixDistribution 23 | @assert ContinuousMatrixDistribution <: ContinuousDistribution 24 | @assert ContinuousMatrixDistribution <: MatrixDistribution 25 | 26 | @testset "Test Sample Type" begin 27 | for T in (Float64,Float32,Dual{Nothing,Float64,0}) 28 | @testset "Type $T" begin 29 | for d in (MvNormal,MvLogNormal,MvNormalCanon,Dirichlet) 30 | dist = d(map(T,ones(2))) 31 | @test eltype(typeof(dist)) == T 32 | @test eltype(rand(dist)) == eltype(dist) 33 | end 34 | dist = Distributions.mvtdist(map(T,1.0),map(T,[1.0 0.0; 0.0 1.0])) 35 | @test eltype(typeof(dist)) == T 36 | @test eltype(rand(dist)) == eltype(dist) 37 | end 38 | end 39 | end 40 | 41 | @testset "equality" begin 42 | dist1 = Normal(1, 1) 43 | dist2 = Normal(1.0, 1.0) 44 | 45 | # Check h is used 46 | @test hash(dist1, UInt(1)) != hash(dist1, UInt(2)) 47 | 48 | @test dist1 == deepcopy(dist1) 49 | @test hash(dist1) == hash(deepcopy(dist1)) 50 | @test dist1 == dist2 51 | @test isequal(dist1, dist2) 52 | @test isapprox(dist1, dist2) 53 | @test hash(dist1) == hash(dist2) 54 | 55 | dist3 = Normal(1, 0.8) 56 | @test dist1 != dist3 57 | @test !isequal(dist1, dist3) 58 | @test !isapprox(dist1, dist3) 59 | @test hash(dist1) != hash(dist3) 60 | @test isapprox(dist1, dist3, atol=0.3) 61 | 62 | dist4 = LogNormal(1, 1) 63 | @test dist1 != dist4 64 | @test !isequal(dist1, dist4) 65 | @test !isapprox(dist1, dist4) 66 | @test hash(dist1) != hash(dist4) 67 | end 68 | -------------------------------------------------------------------------------- /src/univariate/continuous/erlang.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Erlang(α,θ) 3 | 4 | The *Erlang distribution* is a special case of a [`Gamma`](@ref) distribution with integer shape parameter. 5 | 6 | ```julia 7 | Erlang() # Erlang distribution with unit shape and unit scale, i.e. Erlang(1, 1) 8 | Erlang(a) # Erlang distribution with shape parameter a and unit scale, i.e. Erlang(a, 1) 9 | Erlang(a, s) # Erlang distribution with shape parameter a and scale s 10 | ``` 11 | 12 | External links 13 | 14 | * [Erlang distribution on Wikipedia](http://en.wikipedia.org/wiki/Erlang_distribution) 15 | 16 | """ 17 | struct Erlang{T<:Real} <: ContinuousUnivariateDistribution 18 | α::Int 19 | θ::T 20 | Erlang{T}(α::Int, θ::T) where {T} = new{T}(α, θ) 21 | end 22 | 23 | function Erlang(α::Real, θ::T; check_args=true) where {T <: Real} 24 | check_args && @check_args(Erlang, isinteger(α) && α >= zero(α)) 25 | return Erlang{T}(α, θ) 26 | end 27 | 28 | function Erlang(α::Integer, θ::T; check_args=true) where {T <: Real} 29 | check_args && @check_args(Erlang, α >= zero(α)) 30 | return Erlang{T}(α, θ) 31 | end 32 | 33 | function Erlang(α::Integer, θ::Integer) 34 | θf = float(θ) 35 | return Erlang{typeof(θf)}(α, θf) 36 | end 37 | 38 | Erlang(α::Integer) = Erlang(α, 1.0) 39 | Erlang() = Erlang(1, 1.0, check_args=false) 40 | 41 | @distr_support Erlang 0.0 Inf 42 | 43 | #### Conversions 44 | function convert(::Type{Erlang{T}}, α::Integer, θ::S) where {T <: Real, S <: Real} 45 | Erlang(α, T(θ), check_args=false) 46 | end 47 | function convert(::Type{Erlang{T}}, d::Erlang{S}) where {T <: Real, S <: Real} 48 | Erlang(d.α, T(d.θ), check_args=false) 49 | end 50 | 51 | #### Parameters 52 | 53 | shape(d::Erlang) = d.α 54 | scale(d::Erlang) = d.θ 55 | rate(d::Erlang) = inv(d.θ) 56 | params(d::Erlang) = (d.α, d.θ) 57 | @inline partype(d::Erlang{T}) where {T<:Real} = T 58 | 59 | #### Statistics 60 | 61 | mean(d::Erlang) = d.α * d.θ 62 | var(d::Erlang) = d.α * d.θ^2 63 | skewness(d::Erlang) = 2 / sqrt(d.α) 64 | kurtosis(d::Erlang) = 6 / d.α 65 | 66 | function mode(d::Erlang) 67 | (α, θ) = params(d) 68 | α >= 1 ? θ * (α - 1) : error("Erlang has no mode when α < 1") 69 | end 70 | 71 | function entropy(d::Erlang) 72 | (α, θ) = params(d) 73 | α + loggamma(α) + (1 - α) * digamma(α) + log(θ) 74 | end 75 | 76 | mgf(d::Erlang, t::Real) = (1 - t * d.θ)^(-d.α) 77 | cf(d::Erlang, t::Real) = (1 - im * t * d.θ)^(-d.α) 78 | 79 | 80 | #### Evaluation & Sampling 81 | 82 | @_delegate_statsfuns Erlang gamma α θ 83 | 84 | rand(rng, ::AbstractRNG, d::Erlang) = rand(rng, Gamma(Float64(d.α), d.θ)) 85 | sampler(d::Erlang) = Gamma(Float64(d.α), d.θ) 86 | -------------------------------------------------------------------------------- /test/dirichlet.jl: -------------------------------------------------------------------------------- 1 | # Tests for Dirichlet distribution 2 | 3 | using Distributions 4 | using Test, Random, LinearAlgebra 5 | 6 | 7 | Random.seed!(34567) 8 | 9 | rng = MersenneTwister(123) 10 | 11 | @testset "Testing Dirichlet with $key" for (key, func) in 12 | Dict("rand(...)" => [rand, rand], 13 | "rand(rng, ...)" => [dist -> rand(rng, dist), (dist, n) -> rand(rng, dist, n)]) 14 | 15 | d = Dirichlet(3, 2.0) 16 | 17 | @test length(d) == 3 18 | @test d.alpha == [2.0, 2.0, 2.0] 19 | @test d.alpha0 == 6.0 20 | 21 | @test mean(d) ≈ fill(1.0/3, 3) 22 | @test cov(d) ≈ [8 -4 -4; -4 8 -4; -4 -4 8] / (36 * 7) 23 | @test var(d) ≈ diag(cov(d)) 24 | 25 | @test pdf(d, [0.2, 0.3, 0.5]) ≈ 3.6 26 | @test pdf(d, [0.4, 0.5, 0.1]) ≈ 2.4 27 | @test logpdf(d, [0.2, 0.3, 0.5]) ≈ log(3.6) 28 | @test logpdf(d, [0.4, 0.5, 0.1]) ≈ log(2.4) 29 | 30 | x = func[2](d, 100) 31 | p = pdf(d, x) 32 | lp = logpdf(d, x) 33 | for i in 1 : size(x, 2) 34 | @test lp[i] ≈ logpdf(d, x[:,i]) 35 | @test p[i] ≈ pdf(d, x[:,i]) 36 | end 37 | 38 | v = [2.0, 1.0, 3.0] 39 | d = Dirichlet(v) 40 | 41 | @test Dirichlet([2, 1, 3]).alpha == d.alpha 42 | 43 | @test length(d) == length(v) 44 | @test d.alpha == v 45 | @test d.alpha0 == sum(v) 46 | @test d == typeof(d)(params(d)...) 47 | @test d == deepcopy(d) 48 | 49 | @test mean(d) ≈ v / sum(v) 50 | @test cov(d) ≈ [8 -2 -6; -2 5 -3; -6 -3 9] / (36 * 7) 51 | @test var(d) ≈ diag(cov(d)) 52 | 53 | @test pdf(d, [0.2, 0.3, 0.5]) ≈ 3.0 54 | @test pdf(d, [0.4, 0.5, 0.1]) ≈ 0.24 55 | @test logpdf(d, [0.2, 0.3, 0.5]) ≈ log(3.0) 56 | @test logpdf(d, [0.4, 0.5, 0.1]) ≈ log(0.24) 57 | 58 | x = func[2](d, 100) 59 | p = pdf(d, x) 60 | lp = logpdf(d, x) 61 | for i in 1 : size(x, 2) 62 | @test p[i] ≈ pdf(d, x[:,i]) 63 | @test lp[i] ≈ logpdf(d, x[:,i]) 64 | end 65 | 66 | # Sampling 67 | 68 | x = func[1](d) 69 | @test isa(x, Vector{Float64}) 70 | @test length(x) == 3 71 | 72 | x = func[2](d, 10) 73 | @test isa(x, Matrix{Float64}) 74 | @test size(x) == (3, 10) 75 | 76 | v = [2.0, 1.0, 3.0] 77 | d = Dirichlet(Float32.(v)) 78 | 79 | x = func[1](d) 80 | @test isa(x, Vector{Float32}) 81 | @test length(x) == 3 82 | 83 | x = func[2](d, 10) 84 | @test isa(x, Matrix{Float32}) 85 | @test size(x) == (3, 10) 86 | 87 | 88 | # Test MLE 89 | 90 | v = [2.0, 1.0, 3.0] 91 | d = Dirichlet(v) 92 | 93 | n = 10000 94 | x = func[2](d, n) 95 | x = x ./ sum(x, dims=1) 96 | 97 | r = fit_mle(Dirichlet, x) 98 | @test isapprox(r.alpha, d.alpha, atol=0.25) 99 | r = fit(Dirichlet{Float32}, x) 100 | @test isapprox(r.alpha, d.alpha, atol=0.25) 101 | 102 | # r = fit_mle(Dirichlet, x, fill(2.0, n)) 103 | # @test isapprox(r.alpha, d.alpha, atol=0.25) 104 | 105 | end 106 | -------------------------------------------------------------------------------- /src/univariate/continuous/noncentralchisq.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NoncentralChisq(ν, λ) 3 | 4 | The *noncentral chi-squared distribution* with `ν` degrees of freedom and noncentrality parameter `λ` has the probability density function 5 | 6 | ```math 7 | f(x; \\nu, \\lambda) = \\frac{1}{2} e^{-(x + \\lambda)/2} \\left( \\frac{x}{\\lambda} \\right)^{\\nu/4-1/2} I_{\\nu/2-1}(\\sqrt{\\lambda x}), \\quad x > 0 8 | ``` 9 | 10 | It is the distribution of the sum of squares of `ν` independent [`Normal`](@ref) variates with individual means ``\\mu_i`` and 11 | 12 | ```math 13 | \\lambda = \\sum_{i=1}^\\nu \\mu_i^2 14 | ``` 15 | 16 | ```julia 17 | NoncentralChisq(ν, λ) # Noncentral chi-squared distribution with ν degrees of freedom and noncentrality parameter λ 18 | 19 | params(d) # Get the parameters, i.e. (ν, λ) 20 | ``` 21 | 22 | External links 23 | 24 | * [Noncentral chi-squared distribution on Wikipedia](https://en.wikipedia.org/wiki/Noncentral_chi-squared_distribution) 25 | """ 26 | struct NoncentralChisq{T<:Real} <: ContinuousUnivariateDistribution 27 | ν::T 28 | λ::T 29 | NoncentralChisq{T}(ν::T, λ::T) where {T <: Real} = new{T}(ν, λ) 30 | end 31 | 32 | function NoncentralChisq(ν::T, λ::T; check_args=true) where {T <: Real} 33 | check_args && @check_args(NoncentralChisq, ν > zero(ν) && λ >= zero(λ)) 34 | return NoncentralChisq{T}(ν, λ) 35 | end 36 | 37 | NoncentralChisq(ν::Real, λ::Real) = NoncentralChisq(promote(ν, λ)...) 38 | NoncentralChisq(ν::Integer, λ::Integer) = NoncentralChisq(float(ν), float(λ)) 39 | 40 | @distr_support NoncentralChisq 0.0 Inf 41 | 42 | #### Conversions 43 | 44 | function convert(::Type{NoncentralChisq{T}}, ν::S, λ::S) where {T <: Real, S <: Real} 45 | NoncentralChisq(T(ν), T(λ)) 46 | end 47 | function convert(::Type{NoncentralChisq{T}}, d::NoncentralChisq{S}) where {T <: Real, S <: Real} 48 | NoncentralChisq(T(d.ν), T(d.λ), check_args=false) 49 | end 50 | 51 | ### Parameters 52 | 53 | params(d::NoncentralChisq) = (d.ν, d.λ) 54 | @inline partype(d::NoncentralChisq{T}) where {T<:Real} = T 55 | 56 | 57 | ### Statistics 58 | 59 | mean(d::NoncentralChisq) = d.ν + d.λ 60 | var(d::NoncentralChisq) = 2(d.ν + 2d.λ) 61 | skewness(d::NoncentralChisq) = 2sqrt2*(d.ν + 3d.λ)/sqrt(d.ν + 2d.λ)^3 62 | kurtosis(d::NoncentralChisq) = 12(d.ν + 4d.λ)/(d.ν + 2d.λ)^2 63 | 64 | function mgf(d::NoncentralChisq, t::Real) 65 | exp(d.λ * t/(1 - 2t))*(1 - 2t)^(-d.ν/2) 66 | end 67 | 68 | function cf(d::NoncentralChisq, t::Real) 69 | cis(d.λ * t/(1 - 2im*t))*(1 - 2im*t)^(-d.ν/2) 70 | end 71 | 72 | 73 | ### Evaluation & Sampling 74 | 75 | @_delegate_statsfuns NoncentralChisq nchisq ν λ 76 | 77 | # TODO: remove RFunctions dependency 78 | @rand_rdist(NoncentralChisq) 79 | rand(d::NoncentralChisq) = StatsFuns.RFunctions.nchisqrand(d.ν, d.λ) 80 | -------------------------------------------------------------------------------- /src/univariate/continuous/tdist.jl: -------------------------------------------------------------------------------- 1 | """ 2 | TDist(ν) 3 | 4 | The *Students T distribution* with `ν` degrees of freedom has probability density function 5 | 6 | ```math 7 | f(x; d) = \\frac{1}{\\sqrt{d} B(1/2, d/2)} 8 | \\left( 1 + \\frac{x^2}{d} \\right)^{-\\frac{d + 1}{2}} 9 | ``` 10 | 11 | ```julia 12 | TDist(d) # t-distribution with d degrees of freedom 13 | 14 | params(d) # Get the parameters, i.e. (d,) 15 | dof(d) # Get the degrees of freedom, i.e. d 16 | ``` 17 | 18 | External links 19 | 20 | [Student's T distribution on Wikipedia](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 21 | 22 | """ 23 | struct TDist{T<:Real} <: ContinuousUnivariateDistribution 24 | ν::T 25 | TDist{T}(ν::T) where {T <: Real} = new{T}(ν) 26 | end 27 | 28 | function TDist(ν::T; check_args=true) where {T <: Real} 29 | check_args && @check_args(TDist, ν > zero(ν)) 30 | return TDist{T}(ν) 31 | end 32 | 33 | TDist(ν::Integer) = TDist(float(ν)) 34 | 35 | @distr_support TDist -Inf Inf 36 | 37 | #### Conversions 38 | convert(::Type{TDist{T}}, ν::Real) where {T<:Real} = TDist(T(ν)) 39 | convert(::Type{TDist{T}}, d::TDist{S}) where {T<:Real, S<:Real} = TDist(T(d.ν), check_args=false) 40 | 41 | #### Parameters 42 | 43 | dof(d::TDist) = d.ν 44 | params(d::TDist) = (d.ν,) 45 | @inline partype(d::TDist{T}) where {T<:Real} = T 46 | 47 | 48 | #### Statistics 49 | 50 | mean(d::TDist{T}) where {T<:Real} = d.ν > 1 ? zero(T) : T(NaN) 51 | median(d::TDist{T}) where {T<:Real} = zero(T) 52 | mode(d::TDist{T}) where {T<:Real} = zero(T) 53 | 54 | function var(d::TDist{T}) where T<:Real 55 | ν = d.ν 56 | isinf(ν) && return one(T) 57 | ν > 2 ? ν / (ν - 2) : 58 | ν > 1 ? T(Inf) : T(NaN) 59 | end 60 | 61 | skewness(d::TDist{T}) where {T<:Real} = d.ν > 3 ? zero(T) : T(NaN) 62 | 63 | function kurtosis(d::TDist{T}) where T<:Real 64 | ν = d.ν 65 | ν > 4 ? 6 / (ν - 4) : 66 | ν > 2 ? T(Inf) : T(NaN) 67 | end 68 | 69 | function entropy(d::TDist{T}) where T <: Real 70 | isinf(d.ν) && return entropy( Normal(zero(T), one(T)) ) 71 | h = d.ν/2 72 | h1 = h + 1//2 73 | h1 * (digamma(h1) - digamma(h)) + log(d.ν)/2 + logbeta(h, 1//2) 74 | end 75 | 76 | 77 | #### Evaluation & Sampling 78 | 79 | @_delegate_statsfuns TDist tdist ν 80 | 81 | rand(rng::AbstractRNG, d::TDist) = randn(rng) / ( isinf(d.ν) ? 1 : sqrt(rand(rng, Chisq(d.ν))/d.ν) ) 82 | 83 | function cf(d::TDist{T}, t::Real) where T <: Real 84 | isinf(d.ν) && return cf(Normal(zero(T), one(T)), t) 85 | t == 0 && return complex(1) 86 | h = d.ν/2 87 | q = d.ν/4 88 | complex(2(q*t^2)^q * besselk(h, sqrt(d.ν) * abs(t)) / gamma(h)) 89 | end 90 | 91 | gradlogpdf(d::TDist{T}, x::Real) where {T<:Real} = isinf(d.ν) ? gradlogpdf(Normal(zero(T), one(T)), x) : -((d.ν + 1) * x) / (x^2 + d.ν) 92 | -------------------------------------------------------------------------------- /test/ref/continuous/generalizedextremevalue.R: -------------------------------------------------------------------------------- 1 | 2 | GeneralizedExtremeValue <- R6Class("GeneralizedExtremeValue", 3 | inherit = ContinuousDistribution, 4 | public = list( 5 | names = c("mu", "sigma", "xi"), 6 | mu = NA, 7 | sigma = NA, 8 | xi = NA, 9 | initialize = function(u, s, k) { 10 | self$mu <- u 11 | self$sigma <- s 12 | self$xi <- k 13 | }, 14 | supp = function() { 15 | k <- self$xi 16 | if (k > 0) { 17 | c(self$mu - self$sigma / k, Inf) 18 | } else if (k == 0) { 19 | c(-Inf, Inf) 20 | } else if (k < 0) { 21 | c(-Inf, self$mu - self$sigma / k) 22 | } 23 | }, 24 | properties = function() { 25 | u <- self$mu 26 | s <- self$sigma 27 | k <- self$xi 28 | gam <- 0.57721566490153286 29 | zeta3 <- 1.202056903159594 30 | g1 <- if (k < 1) gamma(1 - k) 31 | g2 <- if (2*k < 1) gamma(1 - 2*k) 32 | g3 <- if (3*k < 1) gamma(1 - 3*k) 33 | g4 <- if (4*k < 1) gamma(1 - 4*k) 34 | ent <- log(s) + gam * k + (gam + 1) 35 | if (k == 0) { 36 | list(location = u, 37 | scale = s, 38 | shape = k, 39 | mean = u + s * gam, 40 | median = u - s * log(log(2)), 41 | mode = u, 42 | var = (s * pi)^2 / 6, 43 | skewness = 1.1395470994046487, 44 | kurtosis = 12/5, 45 | entropy = ent) 46 | } else { 47 | list(location = u, 48 | scale = s, 49 | shape = k, 50 | mean = if (k < 1) { u + s * (g1 - 1) / k } else { Inf }, 51 | median = u + s * (log(2)^(-k) - 1) / k, 52 | mode = u + s * ((1 + k)^(-k) - 1) / k, 53 | var = if (2*k < 1) { s^2 * (g2 - g1^2) / k^2 } else { Inf }, 54 | skewness = if (3*k < 1) { 55 | sign(k) * (g3 - 3*g1*g2 + 2*g1^3) / (g2-g1^2)^1.5 56 | } else { Inf }, 57 | kurtosis = if (4*k < 1) { 58 | (g4 - 4*g1*g3 + 6*g2*g1^2 - 3*g1^4) / (g2-g1^2)^2 - 3 59 | } else { Inf }, 60 | entropy = ent) 61 | } 62 | }, 63 | pdf = function(x, log=FALSE) { dgev(x, self$mu, self$sigma, self$xi, log=log) }, 64 | cdf = function(x) { pgev(x, self$mu, self$sigma, self$xi) }, 65 | quan = function(v) { qgev(v, self$mu, self$sigma, self$xi) } 66 | ) 67 | ) 68 | -------------------------------------------------------------------------------- /src/univariate/continuous/normalcanon.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NormalCanon(η, λ) 3 | 4 | Canonical Form of Normal distribution 5 | """ 6 | struct NormalCanon{T<:Real} <: ContinuousUnivariateDistribution 7 | η::T # σ^(-2) * μ 8 | λ::T # σ^(-2) 9 | μ::T # μ 10 | 11 | function NormalCanon{T}(η, λ) where T 12 | @check_args(NormalCanon, λ > zero(λ)) 13 | new{T}(η, λ, η / λ) 14 | end 15 | end 16 | 17 | NormalCanon(η::T, λ::T) where {T<:Real} = NormalCanon{typeof(η/λ)}(η, λ) 18 | NormalCanon(η::Real, λ::Real) = NormalCanon(promote(η, λ)...) 19 | NormalCanon(η::Integer, λ::Integer) = NormalCanon(float(η), float(λ)) 20 | NormalCanon() = NormalCanon(0., 1.) 21 | 22 | @distr_support NormalCanon -Inf Inf 23 | 24 | #### Type Conversions 25 | convert(::Type{NormalCanon{T}}, η::S, λ::S) where {T <: Real, S <: Real} = NormalCanon(T(η), T(λ)) 26 | convert(::Type{NormalCanon{T}}, d::NormalCanon{S}) where {T <: Real, S <: Real} = NormalCanon(T(d.η), T(d.λ)) 27 | 28 | ## conversion between Normal and NormalCanon 29 | 30 | convert(::Type{Normal}, d::NormalCanon) = Normal(d.μ, 1 / sqrt(d.λ)) 31 | convert(::Type{NormalCanon}, d::Normal) = (λ = 1 / d.σ^2; NormalCanon(λ * d.μ, λ)) 32 | canonform(d::Normal) = convert(NormalCanon, d) 33 | 34 | 35 | #### Parameters 36 | 37 | params(d::NormalCanon) = (d.η, d.λ) 38 | @inline partype(d::NormalCanon{T}) where {T<:Real} = T 39 | 40 | #### Statistics 41 | 42 | mean(d::NormalCanon) = d.μ 43 | median(d::NormalCanon) = mean(d) 44 | mode(d::NormalCanon) = mean(d) 45 | 46 | skewness(d::NormalCanon{T}) where {T<:Real} = zero(T) 47 | kurtosis(d::NormalCanon{T}) where {T<:Real} = zero(T) 48 | 49 | var(d::NormalCanon) = 1 / d.λ 50 | std(d::NormalCanon) = sqrt(var(d)) 51 | 52 | entropy(d::NormalCanon) = (-log(d.λ) + log2π + 1) / 2 53 | 54 | location(d::NormalCanon) = mean(d) 55 | scale(d::NormalCanon) = std(d) 56 | 57 | #### Evaluation 58 | 59 | pdf(d::NormalCanon, x::Real) = (sqrt(d.λ) / sqrt2π) * exp(-d.λ * abs2(x - d.μ)/2) 60 | logpdf(d::NormalCanon, x::Real) = (log(d.λ) - log2π - d.λ * abs2(x - d.μ))/2 61 | 62 | zval(d::NormalCanon, x::Real) = (x - d.μ) * sqrt(d.λ) 63 | xval(d::NormalCanon, z::Real) = d.μ + z / sqrt(d.λ) 64 | 65 | cdf(d::NormalCanon, x::Real) = normcdf(zval(d,x)) 66 | ccdf(d::NormalCanon, x::Real) = normccdf(zval(d,x)) 67 | logcdf(d::NormalCanon, x::Real) = normlogcdf(zval(d,x)) 68 | logccdf(d::NormalCanon, x::Real) = normlogccdf(zval(d,x)) 69 | 70 | quantile(d::NormalCanon, p::Real) = xval(d, norminvcdf(p)) 71 | cquantile(d::NormalCanon, p::Real) = xval(d, norminvccdf(p)) 72 | invlogcdf(d::NormalCanon, lp::Real) = xval(d, norminvlogcdf(lp)) 73 | invlogccdf(d::NormalCanon, lp::Real) = xval(d, norminvlogccdf(lp)) 74 | 75 | 76 | #### Sampling 77 | 78 | rand(rng::AbstractRNG, cf::NormalCanon) = cf.μ + randn(rng) / sqrt(cf.λ) 79 | -------------------------------------------------------------------------------- /test/dirichletmultinomial.jl: -------------------------------------------------------------------------------- 1 | # Tests for DirichletMultinomial 2 | 3 | 4 | using Distributions 5 | using Test, Random, SpecialFunctions 6 | 7 | import SpecialFunctions: factorial 8 | Random.seed!(123) 9 | 10 | rng = MersenneTwister(123) 11 | 12 | @testset "Testing DirichletMultinomial with $key" for (key, func) in 13 | Dict("rand(...)" => [rand, rand], 14 | "rand(rng, ...)" => [dist -> rand(rng, dist), (dist, n) -> rand(rng, dist, n)]) 15 | 16 | # Test Constructors 17 | d = DirichletMultinomial(10, ones(5)) 18 | d2 = DirichletMultinomial(10, ones(Int, 5)) 19 | @test typeof(d) == typeof(d2) 20 | @test d == deepcopy(d) 21 | 22 | α = func[1](5) 23 | d = DirichletMultinomial(10, α) 24 | 25 | # test parameters 26 | @test length(d) == 5 27 | @test ntrials(d) == 10 28 | @test params(d) == (10, α) 29 | @test d.α == α 30 | @test d.α0 == sum(α) 31 | @test partype(d) == eltype(α) 32 | 33 | # test statistics 34 | @test mean(d) ≈ α * (d.n / d.α0) 35 | p = d.α / d.α0 36 | @test var(d) ≈ d.n * (d.n + d.α0) / (1 + d.α0) .* p .* (1.0 .- p) 37 | x = func[2](d, 10_000) 38 | 39 | # test statistics with mle fit 40 | d = fit(DirichletMultinomial, x) 41 | @test isapprox(mean(d), vec(mean(x, dims=2)), atol=.5) 42 | @test isapprox(var(d) , vec(var(x, dims=2)) , atol=.5) 43 | @test isapprox(cov(d) , cov(x, dims=2) , atol=.5) 44 | 45 | # test Evaluation 46 | d = DirichletMultinomial(10, 5) 47 | @test typeof(d) == DirichletMultinomial{Float64} 48 | @test !insupport(d, func[1](5)) 49 | @test insupport(d, [2, 2, 2, 2, 2]) 50 | @test insupport(d, 2.0 * ones(5)) 51 | @test !insupport(d, 3.0 * ones(5)) 52 | 53 | for x in (2 * ones(5), [1, 2, 3, 4, 0], [3.0, 0.0, 3.0, 0.0, 4.0], [0, 0, 0, 0, 10]) 54 | local x 55 | @test pdf(d, x) ≈ 56 | factorial(d.n) * gamma(d.α0) / gamma(d.n + d.α0) * prod(gamma.(d.α + x) ./ factorial.(x) ./ gamma.(d.α)) 57 | @test logpdf(d, x) ≈ 58 | log(factorial(d.n)) + loggamma(d.α0) - loggamma(d.n + d.α0) + sum(loggamma, d.α + x) - sum(loggamma, d.α) - sum(log.(factorial.(x))) 59 | end 60 | 61 | # test Sampling 62 | x = func[1](d) 63 | @test isa(x, Vector{Int}) 64 | @test sum(x) == d.n 65 | @test length(x) == length(d) 66 | @test insupport(d, x) 67 | 68 | x = func[2](d, 50) 69 | @test all(x -> (x >= 0), x) 70 | @test size(x, 1) == length(d) 71 | @test size(x, 2) == 50 72 | @test all(sum(x, dims=1) .== ntrials(d)) 73 | @test all(insupport(d, x)) 74 | 75 | # test MLE 76 | x = func[2](d, 10_000) 77 | ss = suffstats(DirichletMultinomial, x) 78 | @test size(ss.s, 1) == length(d) 79 | @test size(ss.s, 2) == ntrials(d) 80 | mle = fit(DirichletMultinomial, x) 81 | @test isapprox(mle.α, d.α, atol=.2) 82 | mle = fit(DirichletMultinomial{Float64}, x) 83 | @test isapprox(mle.α, d.α, atol=.2) 84 | 85 | # test MLE with weights 86 | for w in (.1 * ones(10_000), ones(10_000), 10 * ones(10_000)) 87 | mle2 = fit(DirichletMultinomial, x, w) 88 | @test mle.α ≈ mle2.α 89 | end 90 | 91 | end 92 | -------------------------------------------------------------------------------- /test/univariate_bounds.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | # to make sure that subtypes provides the required behavoir without having to add 5 | # a dependency to InteractiveUtils 6 | function _subtypes(m::Module, x::Type, sts=Base.IdSet{Any}(), visited=Base.IdSet{Module}()) 7 | push!(visited, m) 8 | xt = Base.unwrap_unionall(x) 9 | if !isa(xt, DataType) 10 | return sts 11 | end 12 | xt = xt::DataType 13 | for s in names(m, all = true) 14 | if isdefined(m, s) && !Base.isdeprecated(m, s) 15 | t = getfield(m, s) 16 | if isa(t, DataType) 17 | t = t::DataType 18 | if t.name.name === s && supertype(t).name == xt.name 19 | ti = typeintersect(t, x) 20 | ti != Base.Bottom && push!(sts, ti) 21 | end 22 | elseif isa(t, UnionAll) 23 | t = t::UnionAll 24 | tt = Base.unwrap_unionall(t) 25 | isa(tt, DataType) || continue 26 | tt = tt::DataType 27 | if tt.name.name === s && supertype(tt).name == xt.name 28 | ti = typeintersect(t, x) 29 | ti != Base.Bottom && push!(sts, ti) 30 | end 31 | elseif isa(t, Module) 32 | t = t::Module 33 | in(t, visited) || _subtypes(t, x, sts, visited) 34 | end 35 | end 36 | end 37 | return sts 38 | end 39 | 40 | function _subtypes_in(mods::Array, x::Type) 41 | if !isabstracttype(x) 42 | # Fast path 43 | return Type[] 44 | end 45 | sts = Base.IdSet{Any}() 46 | visited = Base.IdSet{Module}() 47 | for m in mods 48 | _subtypes(m, x, sts, visited) 49 | end 50 | return sort!(collect(sts), by=string) 51 | end 52 | 53 | get_subtypes(m::Module, x::Type) = _subtypes_in([m], x) 54 | 55 | get_subtypes(x::Type) = _subtypes_in(Base.loaded_modules_array(), x) 56 | 57 | 58 | dists = get_subtypes(UnivariateDistribution) 59 | filter!(x -> hasmethod(x, ()), dists) 60 | filter!(x -> isbounded(x()), dists) 61 | 62 | @testset "bound checking $dist" for dist in dists 63 | d = dist() 64 | lb,ub = float.(extrema(support(d))) 65 | lb = prevfloat(lb) 66 | ub = nextfloat(ub) 67 | @test iszero(cdf(d, lb)) 68 | @test isone(cdf(d, ub)) 69 | 70 | lb_lcdf = logcdf(d,lb) 71 | @test isinf(lb_lcdf) & (lb_lcdf < 0) 72 | @test iszero(logcdf(d, ub)) 73 | 74 | @test isone(ccdf(d, lb)) 75 | @test iszero(ccdf(d, ub)) 76 | 77 | ub_lccdf = logccdf(d,ub) 78 | @test isinf(ub_lccdf) & (ub_lccdf < 0) 79 | @test iszero(logccdf(d, lb)) 80 | 81 | @test iszero(pdf(d, lb)) 82 | @test iszero(pdf(d, ub)) 83 | 84 | lb_lpdf = logpdf(d, lb) 85 | @test isinf(lb_lpdf) & (lb_lpdf < 0) 86 | ub_lpdf = logpdf(d, ub) 87 | @test isinf(ub_lpdf) & (ub_lpdf < 0) 88 | end 89 | -------------------------------------------------------------------------------- /src/univariate/continuous/gumbel.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Gumbel(μ, θ) 3 | 4 | The *Gumbel distribution* with location `μ` and scale `θ` has probability density function 5 | 6 | ```math 7 | f(x; \\mu, \\theta) = \\frac{1}{\\theta} e^{-(z + e^-z)}, 8 | \\quad \\text{ with } z = \\frac{x - \\mu}{\\theta} 9 | ``` 10 | 11 | ```julia 12 | Gumbel() # Gumbel distribution with zero location and unit scale, i.e. Gumbel(0, 1) 13 | Gumbel(u) # Gumbel distribution with location u and unit scale, i.e. Gumbel(u, 1) 14 | Gumbel(u, b) # Gumbel distribution with location u and scale b 15 | 16 | params(d) # Get the parameters, i.e. (u, b) 17 | location(d) # Get the location parameter, i.e. u 18 | scale(d) # Get the scale parameter, i.e. b 19 | ``` 20 | 21 | External links 22 | 23 | * [Gumbel distribution on Wikipedia](http://en.wikipedia.org/wiki/Gumbel_distribution) 24 | """ 25 | struct Gumbel{T<:Real} <: ContinuousUnivariateDistribution 26 | μ::T # location 27 | θ::T # scale 28 | Gumbel{T}(µ::T, θ::T) where {T} = new{T}(µ, θ) 29 | end 30 | 31 | function Gumbel(μ::T, θ::T; check_args=true) where {T <: Real} 32 | check_args && @check_args(Gumbel, θ > zero(θ)) 33 | return Gumbel{T}(μ, θ) 34 | end 35 | 36 | Gumbel(μ::Real, θ::Real) = Gumbel(promote(μ, θ)...) 37 | Gumbel(μ::Integer, θ::Integer) = Gumbel(float(μ), float(θ)) 38 | Gumbel(μ::T) where {T <: Real} = Gumbel(μ, one(T)) 39 | Gumbel() = Gumbel(0.0, 1.0, check_args=false) 40 | 41 | @distr_support Gumbel -Inf Inf 42 | 43 | const DoubleExponential = Gumbel 44 | 45 | #### Conversions 46 | 47 | convert(::Type{Gumbel{T}}, μ::S, θ::S) where {T <: Real, S <: Real} = Gumbel(T(μ), T(θ)) 48 | convert(::Type{Gumbel{T}}, d::Gumbel{S}) where {T <: Real, S <: Real} = Gumbel(T(d.μ), T(d.θ), check_args=false) 49 | 50 | #### Parameters 51 | 52 | location(d::Gumbel) = d.μ 53 | scale(d::Gumbel) = d.θ 54 | params(d::Gumbel) = (d.μ, d.θ) 55 | partype(::Gumbel{T}) where {T} = T 56 | 57 | 58 | #### Statistics 59 | 60 | mean(d::Gumbel) = d.μ + d.θ * MathConstants.γ 61 | 62 | median(d::Gumbel{T}) where {T<:Real} = d.μ - d.θ * log(T(logtwo)) 63 | 64 | mode(d::Gumbel) = d.μ 65 | 66 | var(d::Gumbel{T}) where {T<:Real} = T(π)^2/6 * d.θ^2 67 | 68 | skewness(d::Gumbel{T}) where {T<:Real} = 12*sqrt(T(6))*zeta(T(3)) / π^3 69 | 70 | kurtosis(d::Gumbel{T}) where {T<:Real} = T(12)/5 71 | 72 | entropy(d::Gumbel) = log(d.θ) + 1 + MathConstants.γ 73 | 74 | 75 | #### Evaluation 76 | 77 | zval(d::Gumbel, x::Real) = (x - d.μ) / d.θ 78 | xval(d::Gumbel, z::Real) = x * d.θ + d.μ 79 | 80 | function pdf(d::Gumbel, x::Real) 81 | z = zval(d, x) 82 | exp(-z - exp(-z)) / d.θ 83 | end 84 | 85 | function logpdf(d::Gumbel, x::Real) 86 | z = zval(d, x) 87 | - (z + exp(-z) + log(d.θ)) 88 | end 89 | 90 | cdf(d::Gumbel, x::Real) = exp(-exp(-zval(d, x))) 91 | logcdf(d::Gumbel, x::Real) = -exp(-zval(d, x)) 92 | 93 | quantile(d::Gumbel, p::Real) = d.μ - d.θ * log(-log(p)) 94 | 95 | gradlogpdf(d::Gumbel, x::Real) = - (1 + exp((d.μ - x) / d.θ)) / d.θ 96 | -------------------------------------------------------------------------------- /test/product.jl: -------------------------------------------------------------------------------- 1 | using Distributions, Test, Random, LinearAlgebra 2 | using Distributions: Product 3 | 4 | @testset "Testing normal product distributions" begin 5 | Random.seed!(123456) 6 | N = 11 7 | # Construct independent distributions and `Product` distribution from these. 8 | μ = randn(N) 9 | ds = Normal.(μ, 1.0) 10 | x = rand.(ds) 11 | d_product = product_distribution(ds) 12 | @test d_product isa MvNormal 13 | # Check that methods for `Product` are consistent. 14 | @test length(d_product) == length(ds) 15 | @test eltype(d_product) === eltype(ds[1]) 16 | @test logpdf(d_product, x) ≈ sum(logpdf.(ds, x)) 17 | @test mean(d_product) == mean.(ds) 18 | @test var(d_product) == var.(ds) 19 | @test cov(d_product) == Diagonal(var.(ds)) 20 | @test entropy(d_product) ≈ sum(entropy.(ds)) 21 | 22 | y = rand(d_product) 23 | @test y isa typeof(x) 24 | @test length(y) == N 25 | end 26 | 27 | @testset "Testing generic product distributions" begin 28 | Random.seed!(123456) 29 | N = 11 30 | # Construct independent distributions and `Product` distribution from these. 31 | ubound = rand(N) 32 | ds = Uniform.(0.0, ubound) 33 | x = rand.(ds) 34 | d_product = product_distribution(ds) 35 | @test d_product isa Product 36 | # Check that methods for `Product` are consistent. 37 | @test length(d_product) == length(ds) 38 | @test eltype(d_product) === eltype(ds[1]) 39 | @test logpdf(d_product, x) ≈ sum(logpdf.(ds, x)) 40 | @test mean(d_product) == mean.(ds) 41 | @test var(d_product) == var.(ds) 42 | @test cov(d_product) == Diagonal(var.(ds)) 43 | @test entropy(d_product) == sum(entropy.(ds)) 44 | @test insupport(d_product, ubound) == true 45 | @test insupport(d_product, ubound .+ 1) == false 46 | 47 | y = rand(d_product) 48 | @test y isa typeof(x) 49 | @test length(y) == N 50 | end 51 | 52 | @testset "Testing discrete non-parametric product distribution" begin 53 | Random.seed!(123456) 54 | N = 11 55 | 56 | for a in ([0, 1], [-0.5, 0.5]) 57 | # Construct independent distributions and `Product` distribution from these. 58 | support = fill(a, N) 59 | ds = DiscreteNonParametric.(support, Ref([0.5, 0.5])) 60 | x = rand.(ds) 61 | d_product = product_distribution(ds) 62 | @test d_product isa Product 63 | # Check that methods for `Product` are consistent. 64 | @test length(d_product) == length(ds) 65 | @test eltype(d_product) === eltype(ds[1]) 66 | @test logpdf(d_product, x) ≈ sum(logpdf.(ds, x)) 67 | @test mean(d_product) == mean.(ds) 68 | @test var(d_product) == var.(ds) 69 | @test cov(d_product) == Diagonal(var.(ds)) 70 | @test entropy(d_product) == sum(entropy.(ds)) 71 | @test insupport(d_product, fill(a[2], N)) == true 72 | @test insupport(d_product, fill(a[2] + 1, N)) == false 73 | 74 | y = rand(d_product) 75 | @test y isa typeof(x) 76 | @test length(y) == N 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /docs/src/starting.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | The Distributions package is available through the Julia package system by running `Pkg.add("Distributions")`. 6 | Throughout, we assume that you have installed the package. 7 | 8 | ## Starting With a Normal Distribution 9 | 10 | We start by drawing 100 observations from a standard-normal random variable. 11 | 12 | The first step is to set up the environment: 13 | 14 | ```julia 15 | julia> using Random, Distributions 16 | 17 | julia> Random.seed!(123) # Setting the seed 18 | ``` 19 | 20 | Then, we create a standard-normal distribution `d` and obtain samples using `rand`: 21 | 22 | ```julia 23 | julia> d = Normal() 24 | Normal(μ=0.0, σ=1.0) 25 | 26 | julia> x = rand(d, 100) 27 | 100-element Array{Float64,1}: 28 | 0.376264 29 | -0.405272 30 | ... 31 | ``` 32 | 33 | You can easily obtain the `pdf`, `cdf`, `quantile`, and many other functions for a distribution. For instance, the median (50th percentile) and the 95th percentile for the standard-normal distribution are given by: 34 | 35 | ```julia 36 | julia> quantile.(Normal(), [0.5, 0.95]) 37 | 2-element Array{Float64,1}: 38 | 0.0 39 | 1.64485 40 | ``` 41 | 42 | The normal distribution is parameterized by its mean and standard deviation. To draw random samples from a normal distribution with mean 1 and standard deviation 2, you write: 43 | 44 | ```julia 45 | julia> rand(Normal(1, 2), 100) 46 | ``` 47 | 48 | ## Using Other Distributions 49 | 50 | The package contains a large number of additional distributions of three main types: 51 | 52 | * `Univariate` 53 | * `Multivariate` 54 | * `Matrixvariate` 55 | 56 | Each type splits further into `Discrete` and `Continuous`. 57 | 58 | For instance, you can define the following distributions (among many others): 59 | 60 | ```julia 61 | julia> Binomial(p) # Discrete univariate 62 | julia> Cauchy(u, b) # Continuous univariate 63 | julia> Multinomial(n, p) # Discrete multivariate 64 | julia> Wishart(nu, S) # Continuous matrix-variate 65 | ``` 66 | 67 | In addition, you can create truncated distributions from univariate distributions: 68 | 69 | ```julia 70 | julia> truncated(Normal(mu, sigma), l, u) 71 | ``` 72 | 73 | To find out which parameters are appropriate for a given distribution `D`, you can use `fieldnames(D)`: 74 | 75 | ```julia 76 | julia> fieldnames(Cauchy) 77 | 2-element Array{Symbol,1}: 78 | :μ 79 | :β 80 | ``` 81 | 82 | This tells you that a Cauchy distribution is initialized with location `μ` and scale `β`. 83 | 84 | ## Estimate the Parameters 85 | 86 | It is often useful to approximate an empirical distribution with a theoretical distribution. As an example, we can use the array `x` we created above and ask which normal distribution best describes it: 87 | 88 | ```julia 89 | julia> fit(Normal, x) 90 | Normal(μ=0.036692077201688635, σ=1.1228280164716382) 91 | ``` 92 | 93 | Since `x` is a random draw from `Normal`, it's easy to check that the fitted values are sensible. Indeed, the estimates [0.04, 1.12] are close to the true values of [0.0, 1.0] that we used to generate `x`. 94 | -------------------------------------------------------------------------------- /test/ref/discrete/noncentralhypergeometric.R: -------------------------------------------------------------------------------- 1 | 2 | FisherNoncentralHypergeometric <- R6Class("FisherNoncentralHypergeometric", 3 | inherit = DiscreteDistribution, 4 | public = list( 5 | names = c("m1", "m2", "n", "odds"), 6 | m1 = NA, 7 | m2 = NA, 8 | n = NA, 9 | odds = NA, 10 | initialize = function(ns, nf, n, o) { 11 | self$m1 <- ns 12 | self$m2 <- nf 13 | self$n <- n 14 | self$odds <- o 15 | }, 16 | supp = function() { 17 | c( pmax(0, self$n - self$m2), pmin(self$m1, self$n) ) 18 | }, 19 | properties = function() { 20 | m1 <- self$m1 21 | m2 <- self$m2 22 | n <- self$n 23 | o <- self$odds 24 | list(mean = BiasedUrn::meanFNCHypergeo(m1, m2, n, o, precision=1e-16), 25 | var = BiasedUrn::varFNCHypergeo(m1, m2, n, o, precision=1e-16), 26 | mode = BiasedUrn::modeFNCHypergeo(m1, m2, n, o)) 27 | }, 28 | pdf = function(x, log=FALSE) { 29 | p <- BiasedUrn::dFNCHypergeo( 30 | x, self$m1, self$m2, self$n, self$odds, precision=1e-16) 31 | if (log) { log(p) } else { p } 32 | }, 33 | cdf = function(x) { 34 | BiasedUrn::pFNCHypergeo( 35 | x, self$m1, self$m2, self$n, self$odds, precision=1e-16) 36 | }, 37 | quan = function(v) { 38 | BiasedUrn::qFNCHypergeo( 39 | v, self$m1, self$m2, self$n, self$odds) 40 | } 41 | ) 42 | ) 43 | 44 | WalleniusNoncentralHypergeometric <- R6Class("WalleniusNoncentralHypergeometric", 45 | inherit = DiscreteDistribution, 46 | public = list( 47 | names = c("m1", "m2", "n", "odds"), 48 | m1 = NA, 49 | m2 = NA, 50 | n = NA, 51 | odds = NA, 52 | initialize = function(ns, nf, n, o) { 53 | self$m1 <- ns 54 | self$m2 <- nf 55 | self$n <- n 56 | self$odds <- o 57 | }, 58 | supp = function() { 59 | c( pmax(0, self$n - self$m2), pmin(self$m1, self$n) ) 60 | }, 61 | properties = function() { 62 | m1 <- self$m1 63 | m2 <- self$m2 64 | n <- self$n 65 | o <- self$odds 66 | list(mean = BiasedUrn::meanWNCHypergeo(m1, m2, n, o, precision=1e-16), 67 | var = BiasedUrn::varWNCHypergeo(m1, m2, n, o, precision=1e-16), 68 | mode = BiasedUrn::modeWNCHypergeo(m1, m2, n, o)) 69 | }, 70 | pdf = function(x, log=FALSE) { 71 | p <- BiasedUrn::dWNCHypergeo( 72 | x, self$m1, self$m2, self$n, self$odds, precision=1e-16) 73 | if (log) { log(p) } else { p } 74 | }, 75 | cdf = function(x) { 76 | BiasedUrn::pWNCHypergeo( 77 | x, self$m1, self$m2, self$n, self$odds, precision=1e-16) 78 | }, 79 | quan = function(v) { 80 | BiasedUrn::qWNCHypergeo( 81 | v, self$m1, self$m2, self$n, self$odds) 82 | } 83 | ) 84 | ) 85 | -------------------------------------------------------------------------------- /src/convolution.jl: -------------------------------------------------------------------------------- 1 | """ 2 | convolve(d1::T, d2::T) where T<:Distribution -> Distribution 3 | 4 | Convolve two distributions of the same type to yield the distribution corresponding to the 5 | sum of independent random variables drawn from the underlying distributions. 6 | 7 | The function is only defined in the cases where the convolution has a closed form as 8 | defined here https://en.wikipedia.org/wiki/List_of_convolutions_of_probability_distributions 9 | 10 | * `Bernoulli` 11 | * `Binomial` 12 | * `NegativeBinomial` 13 | * `Geometric` 14 | * `Poisson` 15 | * `Normal` 16 | * `Cauchy` 17 | * `Chisq` 18 | * `Exponential` 19 | * `Gamma` 20 | * `MultivariateNormal` 21 | """ 22 | function convolve end 23 | 24 | # discrete univariate 25 | function convolve(d1::Bernoulli, d2::Bernoulli) 26 | _check_convolution_args(d1.p, d2.p) 27 | return Binomial(2, d1.p) 28 | end 29 | 30 | function convolve(d1::Binomial, d2::Binomial) 31 | _check_convolution_args(d1.p, d2.p) 32 | return Binomial(d1.n + d2.n, d1.p) 33 | end 34 | 35 | function convolve(d1::NegativeBinomial, d2::NegativeBinomial) 36 | _check_convolution_args(d1.p, d2.p) 37 | return NegativeBinomial(d1.r + d2.r, d1.p) 38 | end 39 | 40 | function convolve(d1::Geometric, d2::Geometric) 41 | _check_convolution_args(d1.p, d2.p) 42 | return NegativeBinomial(2, d1.p) 43 | end 44 | 45 | convolve(d1::Poisson, d2::Poisson) = Poisson(d1.λ + d2.λ) 46 | 47 | 48 | # continuous univariate 49 | convolve(d1::Normal, d2::Normal) = Normal(d1.μ + d2.μ, hypot(d1.σ, d2.σ)) 50 | convolve(d1::Cauchy, d2::Cauchy) = Cauchy(d1.μ + d2.μ, d1.σ + d2.σ) 51 | convolve(d1::Chisq, d2::Chisq) = Chisq(d1.ν + d2.ν) 52 | 53 | function convolve(d1::Exponential, d2::Exponential) 54 | _check_convolution_args(d1.θ, d2.θ) 55 | return Gamma(2, d1.θ) 56 | end 57 | 58 | function convolve(d1::Gamma, d2::Gamma) 59 | _check_convolution_args(d1.θ, d2.θ) 60 | return Gamma(d1.α + d2.α, d1.θ) 61 | end 62 | 63 | # continuous multivariate 64 | # The first two methods exist for performance reasons to avoid unnecessarily converting 65 | # PDMats to a Matrix 66 | function convolve( 67 | d1::Union{IsoNormal, ZeroMeanIsoNormal, DiagNormal, ZeroMeanDiagNormal}, 68 | d2::Union{IsoNormal, ZeroMeanIsoNormal, DiagNormal, ZeroMeanDiagNormal}, 69 | ) 70 | _check_convolution_shape(d1, d2) 71 | return MvNormal(d1.μ .+ d2.μ, d1.Σ + d2.Σ) 72 | end 73 | 74 | function convolve( 75 | d1::Union{FullNormal, ZeroMeanFullNormal}, 76 | d2::Union{FullNormal, ZeroMeanFullNormal}, 77 | ) 78 | _check_convolution_shape(d1, d2) 79 | return MvNormal(d1.μ .+ d2.μ, d1.Σ.mat + d2.Σ.mat) 80 | end 81 | 82 | function convolve(d1::MvNormal, d2::MvNormal) 83 | _check_convolution_shape(d1, d2) 84 | return MvNormal(d1.μ .+ d2.μ, Matrix(d1.Σ) + Matrix(d2.Σ)) 85 | end 86 | 87 | 88 | function _check_convolution_args(p1, p2) 89 | p1 ≈ p2 || throw(ArgumentError( 90 | "$(p1) !≈ $(p2): distribution parameters must be approximately equal", 91 | )) 92 | end 93 | 94 | function _check_convolution_shape(d1, d2) 95 | length(d1) == length(d2) || throw(ArgumentError("$d1 and $d2 are not the same size")) 96 | end 97 | -------------------------------------------------------------------------------- /src/univariate/continuous/vonmises.jl: -------------------------------------------------------------------------------- 1 | """ 2 | VonMises(μ, κ) 3 | 4 | The *von Mises distribution* with mean `μ` and concentration `κ` has probability density function 5 | 6 | ```math 7 | f(x; \\mu, \\kappa) = \\frac{1}{2 \\pi I_0(\\kappa)} \\exp \\left( \\kappa \\cos (x - \\mu) \\right) 8 | ``` 9 | 10 | ```julia 11 | VonMises() # von Mises distribution with zero mean and unit concentration 12 | VonMises(κ) # von Mises distribution with zero mean and concentration κ 13 | VonMises(μ, κ) # von Mises distribution with mean μ and concentration κ 14 | ``` 15 | 16 | External links 17 | 18 | * [von Mises distribution on Wikipedia](http://en.wikipedia.org/wiki/Von_Mises_distribution) 19 | 20 | """ 21 | struct VonMises{T<:Real} <: ContinuousUnivariateDistribution 22 | μ::T # mean 23 | κ::T # concentration 24 | I0κx::T # I0(κ) * exp(-κ), where I0 is the modified Bessel function of order 0 25 | end 26 | 27 | function VonMises(μ::T, κ::T; check_args=true) where {T <: Real} 28 | check_args && @check_args(VonMises, κ > zero(κ)) 29 | return VonMises{T}(μ, κ, besselix(zero(T), κ)) 30 | end 31 | 32 | VonMises(μ::Real, κ::Real) = VonMises(promote(μ, κ)...) 33 | VonMises(μ::Integer, κ::Integer) = VonMises(float(μ), float(κ)) 34 | VonMises(κ::T) where {T <: Real} = VonMises(zero(T), κ) 35 | VonMises() = VonMises(0.0, 1.0, check_args=false) 36 | 37 | show(io::IO, d::VonMises) = show(io, d, (:μ, :κ)) 38 | 39 | @distr_support VonMises d.μ - π d.μ + π 40 | 41 | #### Conversions 42 | 43 | convert(::Type{VonMises{T}}, μ::Real, κ::Real) where {T<:Real} = VonMises(T(μ), T(κ)) 44 | convert(::Type{VonMises{T}}, d::VonMises{S}) where {T<:Real, S<:Real} = VonMises(T(d.μ), T(d.κ), check_args=false) 45 | 46 | #### Parameters 47 | 48 | params(d::VonMises) = (d.μ, d.κ) 49 | partype(::VonMises{T}) where {T<:Real} = T 50 | 51 | 52 | #### Statistics 53 | 54 | mean(d::VonMises) = d.μ 55 | median(d::VonMises) = d.μ 56 | mode(d::VonMises) = d.μ 57 | var(d::VonMises) = 1 - besselix(1, d.κ) / d.I0κx 58 | # deprecated 12 September 2016 59 | @deprecate circvar(d) var(d) 60 | entropy(d::VonMises) = log(twoπ * d.I0κx) + d.κ * (1 - besselix(1, d.κ) / d.I0κx) 61 | 62 | cf(d::VonMises, t::Real) = (besselix(abs(t), d.κ) / d.I0κx) * cis(t * d.μ) 63 | 64 | 65 | #### Evaluations 66 | 67 | #pdf(d::VonMises, x::Real) = exp(d.κ * (cos(x - d.μ) - 1)) / (twoπ * d.I0κx) 68 | pdf(d::VonMises{T}, x::Real) where T<:Real = 69 | minimum(d) ≤ x ≤ maximum(d) ? exp(d.κ * (cos(x - d.μ) - 1)) / (twoπ * d.I0κx) : zero(T) 70 | logpdf(d::VonMises{T}, x::Real) where T<:Real = 71 | minimum(d) ≤ x ≤ maximum(d) ? d.κ * (cos(x - d.μ) - 1) - log(d.I0κx) - log2π : -T(Inf) 72 | 73 | cdf(d::VonMises, x::Real) = _vmcdf(d.κ, d.I0κx, x - d.μ, 1e-15) 74 | 75 | function _vmcdf(κ::Real, I0κx::Real, x::Real, tol::Real) 76 | tol *= exp(-κ) 77 | j = 1 78 | cj = besselix(j, κ) / j 79 | s = cj * sin(j * x) 80 | while abs(cj) > tol 81 | j += 1 82 | cj = besselix(j, κ) / j 83 | s += cj * sin(j * x) 84 | end 85 | return (x + 2s / I0κx) / twoπ + 1//2 86 | end 87 | 88 | 89 | #### Sampling 90 | 91 | rand(rng::AbstractRNG, d::VonMises) = rand(rng, sampler(d)) 92 | sampler(d::VonMises) = VonMisesSampler(d.μ, d.κ) 93 | -------------------------------------------------------------------------------- /src/univariate/continuous/skewnormal.jl: -------------------------------------------------------------------------------- 1 | """ 2 | SkewNormal(ξ, ω, α) 3 | The *skew normal distribution* is a continuous probability distribution 4 | that generalises the normal distribution to allow for non-zero skewness. 5 | External links 6 | * [Skew normal distribution on Wikipedia](https://en.wikipedia.org/wiki/Skew_normal_distribution) 7 | * [Discourse](https://discourse.julialang.org/t/skew-normal-distribution/21549/7) 8 | * [SkewDist.jl](https://github.com/STOR-i/SkewDist.jl) 9 | """ 10 | struct SkewNormal{T<:Real} <: ContinuousUnivariateDistribution 11 | ξ::T 12 | ω::T 13 | α::T 14 | SkewNormal{T}(ξ::T, ω::T, α::T) where {T} = new{T}(ξ, ω, α) 15 | end 16 | 17 | function SkewNormal(ξ::T, ω::T, α::T; check_args=true) where {T <: Real} 18 | check_args && @check_args(SkewNormal, ω > zero(ω)) 19 | return SkewNormal{T}(ξ, ω, α) 20 | end 21 | 22 | SkewNormal(ξ::Real, ω::Real, α::Real) = SkewNormal(promote(ξ, ω, α)...) 23 | SkewNormal(ξ::Integer, ω::Integer, α::Integer) = SkewNormal(float(ξ), float(ω), float(α)) 24 | SkewNormal(α::T) where {T <: Real} = SkewNormal(zero(α), one(α), α) 25 | SkewNormal() = SkewNormal(0.0, 1.0, 0.0) 26 | 27 | @distr_support SkewNormal -Inf Inf 28 | 29 | #### Conversions 30 | convert(::Type{SkewNormal{T}}, ξ::S, ω::S, α::S) where {T <: Real, S <: Real} = SkewNormal(T(ξ), T(ω), T(α)) 31 | convert(::Type{SkewNormal{T}}, d::SkewNormal{S}) where {T <: Real, S <: Real} = SkewNormal(T(d.ξ), T(d.ω), T(d.α), check_args=false) 32 | 33 | #### Parameters 34 | params(d::SkewNormal) = (d.ξ, d.ω, d.α) 35 | @inline partype(d::SkewNormal{T}) where {T<:Real} = T 36 | 37 | #### Statistics 38 | delta(d::SkewNormal) = d.α / √(1 + d.α^2) 39 | mean_z(d::SkewNormal) = √(2/π) * delta(d) 40 | std_z(d::SkewNormal) = 1 - (2/π) * delta(d)^2 41 | 42 | mean(d::SkewNormal) = d.ξ + d.ω * mean_z(d) 43 | var(d::SkewNormal) = abs2(d.ω) * (1 - mean_z(d)^2) 44 | std(d::SkewNormal) = √var(d) 45 | skewness(d::SkewNormal) = ((4 - π)/2) * (mean_z(d)^3/(1 - mean_z(d)^2)^(3/2)) 46 | kurtosis(d::SkewNormal) = 2 * (π-3) * ((delta(d) * sqrt(2/π))^4/(1-2 * (delta(d)^2)/π)^2) 47 | 48 | # no analytic expression for max m_0(d) but accurate numerical approximation 49 | m_0(d::SkewNormal) = mean_z(d) - (skewness(d) * std_z(d))/2 - (sign(d.α)/2) * exp(-2π/abs(d.α)) 50 | mode(d::SkewNormal) = d.ξ + d.ω * m_0(d) 51 | 52 | #### Evalution 53 | pdf(d::SkewNormal, x::Real) = (2/d.ω) * normpdf((x-d.ξ)/d.ω) * normcdf(d.α * (x-d.ξ)/d.ω) 54 | logpdf(d::SkewNormal, x::Real) = log(2) - log(d.ω) + normlogpdf((x-d.ξ) / d.ω) + normlogcdf(d.α * (x-d.ξ) / d.ω) 55 | #cdf requires Owen's T function. 56 | #cdf/quantile etc 57 | 58 | mgf(d::SkewNormal, t::Real) = 2 * exp(d.ξ * t + (d.ω^2 * t^2)/2 ) * normcdf(d.ω * delta(d) * t) 59 | 60 | cf(d::SkewNormal, t::Real) = exp(im * t * d.ξ - (d.ω^2 * t^2)/2) * (1 + im * erfi((d.ω * delta(d) * t)/(sqrt(2))) ) 61 | 62 | #### Sampling 63 | function rand(rng::AbstractRNG, d::SkewNormal) 64 | u0 = randn(rng) 65 | v = randn(rng) 66 | δ = delta(d) 67 | u1 = δ * u0 + √(1-δ^2) * v 68 | return d.ξ + d.ω * sign(u0) * u1 69 | end 70 | 71 | ## Fitting # to be added see: https://github.com/STOR-i/SkewDist.jl/issues/3 72 | 73 | 74 | -------------------------------------------------------------------------------- /test/poissonbinomial.jl: -------------------------------------------------------------------------------- 1 | using Distributions 2 | using Test 3 | 4 | # Test the special base where PoissonBinomial distribution reduces 5 | # to Binomial distribution 6 | for (p, n) in [(0.8, 6), (0.5, 10), (0.04, 20)] 7 | local p 8 | 9 | d = PoissonBinomial(fill(p, n)) 10 | dref = Binomial(n, p) 11 | println(" testing PoissonBinomial p=$p, n=$n") 12 | 13 | @test isa(d, PoissonBinomial) 14 | @test minimum(d) == 0 15 | @test maximum(d) == n 16 | @test extrema(d) == (0, n) 17 | @test ntrials(d) == n 18 | @test entropy(d) ≈ entropy(dref) 19 | @test median(d) ≈ median(dref) 20 | @test mean(d) ≈ mean(dref) 21 | @test var(d) ≈ var(dref) 22 | @test kurtosis(d) ≈ kurtosis(dref) 23 | @test skewness(d) ≈ skewness(dref) 24 | 25 | for t=0:5 26 | @test mgf(d, t) ≈ mgf(dref, t) 27 | @test cf(d, t) ≈ cf(dref, t) 28 | end 29 | for i=0.1:0.1:.9 30 | @test quantile(d, i) ≈ quantile(dref, i) 31 | end 32 | for i=0:n 33 | @test isapprox(cdf(d, i), cdf(dref, i), atol=1e-15) 34 | @test isapprox(pdf(d, i), pdf(dref, i), atol=1e-15) 35 | end 36 | 37 | end 38 | 39 | # Test against a sum of three Binomial distributions 40 | for (n₁, n₂, n₃, p₁, p₂, p₃) in [(10, 10, 10, 0.1, 0.5, 0.9), 41 | (1, 10, 100, 0.99, 0.1, 0.05), 42 | (5, 1, 3, 0.01, 0.99, 0.999), 43 | (10, 7, 10, 0., 0.9, 0.5)] 44 | 45 | n = n₁ + n₂ + n₃ 46 | p = zeros(n) 47 | p[1:n₁] .= p₁ 48 | p[n₁+1: n₁ + n₂] .= p₂ 49 | p[n₁ + n₂ + 1:end] .= p₃ 50 | d = PoissonBinomial(p) 51 | println(" testing PoissonBinomial [$(n₁) × $(p₁), $(n₂) × $(p₂), $(n₃) × $(p₃)]") 52 | b1 = Binomial(n₁, p₁) 53 | b2 = Binomial(n₂, p₂) 54 | b3 = Binomial(n₃, p₃) 55 | 56 | pmf1 = pdf.(b1, support(b1)) 57 | pmf2 = pdf.(b2, support(b2)) 58 | pmf3 = pdf.(b3, support(b3)) 59 | 60 | @test mean(d) ≈ (mean(b1) + mean(b2) + mean(b3)) 61 | @test var(d) ≈ (var(b1) + var(b2) + var(b3)) 62 | for t=0:5 63 | @test mgf(d, t) ≈ (mgf(b1, t) * mgf(b2, t) * mgf(b3, t)) 64 | @test cf(d, t) ≈ (cf(b1, t) * cf(b2, t) * cf(b3, t)) 65 | end 66 | 67 | for k=0:n 68 | m = 0. 69 | for i=0:min(n₁, k) 70 | mc = 0. 71 | for j=i:min(i+n₂, k) 72 | mc += (k - j <= n₃) && pmf2[j-i+1] * pmf3[k-j+1] 73 | end 74 | m += pmf1[i+1] * mc 75 | end 76 | @test isapprox(pdf(d, k), m, atol=1e-15) 77 | end 78 | end 79 | 80 | # Test the _dft helper function 81 | @testset "_dft" begin 82 | x = Distributions._dft(collect(1:8)) 83 | # Comparator computed from FFTW 84 | fftw_fft = [36.0 + 0.0im, 85 | -4.0 + 9.65685424949238im, 86 | -4.0 + 4.0im, 87 | -4.0 + 1.6568542494923806im, 88 | -4.0 + 0.0im, 89 | -4.0 - 1.6568542494923806im, 90 | -4.0 - 4.0im, 91 | -4.0 - 9.65685424949238im] 92 | @test x ≈ fftw_fft 93 | end 94 | 95 | # Test autodiff using ForwardDiff 96 | f = x -> logpdf(PoissonBinomial(x), 0) 97 | at = [0.5, 0.5] 98 | @test isapprox(ForwardDiff.gradient(f, at), fdm(f, at), atol=1e-6) 99 | -------------------------------------------------------------------------------- /test/mvtdist.jl: -------------------------------------------------------------------------------- 1 | using Distributions, Random, StaticArrays, LinearAlgebra 2 | using Test 3 | 4 | import Distributions: GenericMvTDist 5 | import PDMats: PDMat 6 | 7 | # Set location vector mu and scale matrix Sigma as in 8 | # Hofert M. On Sampling from the Multivariate t Distribution. The R Journal 9 | mu = [1., 2] 10 | Sigma = [4. 2; 2 3] 11 | 12 | # LogPDF evaluation for varying degrees of freedom df 13 | # Julia's output is compared to R's corresponding values obtained via R's mvtnorm package 14 | # R code exemplifying how the R values (rvalues) were obtained: 15 | # options(digits=20) 16 | # library("mvtnorm") 17 | # mu <- 1:2 18 | # Sigma <- matrix(c(4, 2, 2, 3), ncol=2) 19 | # dmvt(c(-2., 3.), delta=mu, sigma=Sigma, df=1) 20 | rvalues = [-5.6561739738159975133, 21 | -5.4874952805811396672, 22 | -5.4441948098568158088, 23 | -5.432461875138580254, 24 | -5.4585441614404803801] 25 | df = [1., 2, 3, 5, 10] 26 | for i = 1:length(df) 27 | d = MvTDist(df[i], mu, Sigma) 28 | @test isapprox(logpdf(d, [-2., 3]), rvalues[i], atol=1.0e-8) 29 | dd = typeof(d)(params(d)...) 30 | @test d.df == dd.df 31 | @test Vector(d.μ) == Vector(dd.μ) 32 | @test Matrix(d.Σ) == Matrix(dd.Σ) 33 | end 34 | 35 | # test constructors for mixed inputs: 36 | @test typeof(GenericMvTDist(1, Vector{Float32}(mu), PDMat(Sigma))) == typeof(GenericMvTDist(1., mu, PDMat(Sigma))) 37 | 38 | @test typeof(GenericMvTDist(1, mu, PDMat(Array{Float32}(Sigma)))) == typeof(GenericMvTDist(1., mu, PDMat(Sigma))) 39 | 40 | d = GenericMvTDist(1, Array{Float32}(mu), PDMat(Array{Float32}(Sigma))) 41 | @test typeof(convert(GenericMvTDist{Float64}, d)) == typeof(GenericMvTDist(1, mu, PDMat(Sigma))) 42 | @test typeof(convert(GenericMvTDist{Float64}, d.df, d.dim, d.μ, d.Σ)) == typeof(GenericMvTDist(1, mu, PDMat(Sigma))) 43 | @test partype(d) == Float32 44 | @test d == deepcopy(d) 45 | 46 | @test size(rand(MvTDist(1., mu, Sigma))) == (2,) 47 | @test size(rand(MvTDist(1., mu, Sigma), 10)) == (2,10) 48 | @test size(rand(MersenneTwister(123), MvTDist(1., mu, Sigma))) == (2,) 49 | @test size(rand(MersenneTwister(123), MvTDist(1., mu, Sigma), 10)) == (2,10) 50 | 51 | # static array for mean/variance 52 | mu_static = @SVector [1., 2] 53 | # depends on PDMats#101 (merged but not released) 54 | # Sigma_static = @SMatrix [4. 2; 2 3] 55 | 56 | for i in 1:length(df) 57 | d = GenericMvTDist(df[i], mu_static, PDMat(Sigma)) 58 | @test d.μ isa SVector 59 | @test isapprox(logpdf(d, [-2., 3]), rvalues[i], atol=1.0e-8) 60 | dd = typeof(d)(params(d)...) 61 | @test d.df == dd.df 62 | @test d.μ == dd.μ 63 | @test Matrix(d.Σ) == Matrix(dd.Σ) 64 | end 65 | 66 | @testset "zero-mean" begin 67 | 68 | X_implicit = GenericMvTDist(2.0, PDMat(Sigma)) 69 | X_expicit = GenericMvTDist(2.0, zeros(2), PDMat(Sigma)) 70 | 71 | # Check that the means equal the same thing. 72 | @test mean(X_expicit) == mean(X_implicit) 73 | 74 | # Check that generated random numbers are the same. 75 | @test isapprox( 76 | rand(MersenneTwister(123456), X_expicit), 77 | rand(MersenneTwister(123456), X_implicit), 78 | ) 79 | 80 | # Check that the logpdf computed is the same. 81 | x = rand(X_implicit) 82 | @test logpdf(X_implicit, x) ≈ logpdf(X_expicit, x) 83 | end 84 | -------------------------------------------------------------------------------- /src/univariate/continuous/normalinversegaussian.jl: -------------------------------------------------------------------------------- 1 | """ 2 | NormalInverseGaussian(μ,α,β,δ) 3 | 4 | The *Normal-inverse Gaussian distribution* with location `μ`, tail heaviness `α`, asymmetry parameter `β` and scale `δ` has probability density function 5 | 6 | ```math 7 | f(x; \\mu, \\alpha, \\beta, \\delta) = \\frac{\\alpha\\delta K_1 \\left(\\alpha\\sqrt{\\delta^2 + (x - \\mu)^2}\\right)}{\\pi \\sqrt{\\delta^2 + (x - \\mu)^2}} \\; e^{\\delta \\gamma + \\beta (x - \\mu)} 8 | ``` 9 | where ``K_j`` denotes a modified Bessel function of the third kind. 10 | 11 | 12 | External links 13 | 14 | * [Normal-inverse Gaussian distribution on Wikipedia](http://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) 15 | 16 | """ 17 | struct NormalInverseGaussian{T<:Real} <: ContinuousUnivariateDistribution 18 | μ::T 19 | α::T 20 | β::T 21 | δ::T 22 | γ::T 23 | function NormalInverseGaussian{T}(μ::T, α::T, β::T, δ::T) where T 24 | γ = sqrt(α^2 - β^2) 25 | 26 | new{T}(μ, α, β, δ, γ) 27 | end 28 | end 29 | 30 | NormalInverseGaussian(μ::T, α::T, β::T, δ::T) where {T<:Real} = NormalInverseGaussian{T}(μ, α, β, δ) 31 | NormalInverseGaussian(μ::Real, α::Real, β::Real, δ::Real) = NormalInverseGaussian(promote(μ, α, β, δ)...) 32 | function NormalInverseGaussian(μ::Integer, α::Integer, β::Integer, δ::Integer) 33 | return NormalInverseGaussian(float(μ), float(α), float(β), float(δ)) 34 | end 35 | 36 | @distr_support NormalInverseGaussian -Inf Inf 37 | 38 | #### Conversions 39 | function convert(::Type{NormalInverseGaussian{T}}, μ::Real, α::Real, β::Real, δ::Real) where T<:Real 40 | NormalInverseGaussian(T(μ), T(α), T(β), T(δ)) 41 | end 42 | function convert(::Type{NormalInverseGaussian{T}}, d::NormalInverseGaussian{S}) where {T <: Real, S <: Real} 43 | NormalInverseGaussian(T(d.μ), T(d.α), T(d.β), T(d.δ)) 44 | end 45 | 46 | params(d::NormalInverseGaussian) = (d.μ, d.α, d.β, d.δ) 47 | @inline partype(d::NormalInverseGaussian{T}) where {T<:Real} = T 48 | 49 | mean(d::NormalInverseGaussian) = d.μ + d.δ * d.β / d.γ 50 | var(d::NormalInverseGaussian) = d.δ * d.α^2 / d.γ^3 51 | skewness(d::NormalInverseGaussian) = 3d.β / (d.α * sqrt(d.δ * d.γ)) 52 | kurtosis(d::NormalInverseGaussian) = 3 * (1 + 4*d.β^2/d.α^2) / (d.δ * d.γ) 53 | 54 | function pdf(d::NormalInverseGaussian, x::Real) 55 | μ, α, β, δ = params(d) 56 | α * δ * besselk(1, α*sqrt(δ^2+(x - μ)^2)) / (π*sqrt(δ^2 + (x - μ)^2)) * exp(δ * d.γ + β*(x - μ)) 57 | end 58 | 59 | function logpdf(d::NormalInverseGaussian, x::Real) 60 | μ, α, β, δ = params(d) 61 | log(α*δ) + log(besselk(1, α*sqrt(δ^2+(x-μ)^2))) - log(π*sqrt(δ^2+(x-μ)^2)) + δ*d.γ + β*(x-μ) 62 | end 63 | 64 | 65 | #### Sampling 66 | 67 | # The Normal Inverse Gaussian distribution is a normal variance-mean 68 | # mixture with an inverse Gaussian as mixing distribution. 69 | # 70 | # Ole E. Barndorff-Nielsen (1997) 71 | # Normal Inverse Gaussian Distributions and Stochastic Volatility Modelling 72 | # Scandinavian Journal of Statistics, Vol. 24, pp. 1--13 73 | # DOI: http://dx.doi.org/10.1111/1467-9469.00045 74 | 75 | function rand(rng::Random.AbstractRNG, d::NormalInverseGaussian) 76 | μ, α, β, δ = params(d) 77 | 78 | Z = InverseGaussian(δ/d.γ, δ^2) 79 | z = rand(rng, Z) 80 | X = Normal(μ + β*z, sqrt(z)) 81 | return rand(rng, X) 82 | end 83 | -------------------------------------------------------------------------------- /src/univariate/continuous/levy.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Levy(μ, σ) 3 | 4 | The *Lévy distribution* with location `μ` and scale `σ` has probability density function 5 | 6 | ```math 7 | f(x; \\mu, \\sigma) = \\sqrt{\\frac{\\sigma}{2 \\pi (x - \\mu)^3}} 8 | \\exp \\left( - \\frac{\\sigma}{2 (x - \\mu)} \\right), \\quad x > \\mu 9 | ``` 10 | 11 | ```julia 12 | Levy() # Levy distribution with zero location and unit scale, i.e. Levy(0, 1) 13 | Levy(u) # Levy distribution with location u and unit scale, i.e. Levy(u, 1) 14 | Levy(u, c) # Levy distribution with location u ans scale c 15 | 16 | params(d) # Get the parameters, i.e. (u, c) 17 | location(d) # Get the location parameter, i.e. u 18 | ``` 19 | 20 | External links 21 | 22 | * [Lévy distribution on Wikipedia](http://en.wikipedia.org/wiki/Lévy_distribution) 23 | """ 24 | struct Levy{T<:Real} <: ContinuousUnivariateDistribution 25 | μ::T 26 | σ::T 27 | end 28 | 29 | function Levy(μ::T, σ::T; check_args=true) where {T} 30 | check_args && @check_args(Levy, σ > zero(σ)) 31 | return Levy{T}(μ, σ) 32 | end 33 | 34 | Levy(μ::Real, σ::Real) = Levy(promote(μ, σ)...) 35 | Levy(μ::Integer, σ::Integer) = Levy(float(μ), float(σ)) 36 | Levy(μ::T) where {T <: Real} = Levy(μ, one(T)) 37 | Levy() = Levy(0.0, 1.0, check_args=false) 38 | 39 | @distr_support Levy d.μ Inf 40 | 41 | #### Conversions 42 | 43 | convert(::Type{Levy{T}}, μ::S, σ::S) where {T <: Real, S <: Real} = Levy(T(μ), T(σ)) 44 | convert(::Type{Levy{T}}, d::Levy{S}) where {T <: Real, S <: Real} = Levy(T(d.μ), T(d.σ), check_args=false) 45 | 46 | #### Parameters 47 | 48 | location(d::Levy) = d.μ 49 | params(d::Levy) = (d.μ, d.σ) 50 | partype(::Levy{T}) where {T} = T 51 | 52 | 53 | #### Statistics 54 | 55 | mean(d::Levy{T}) where {T<:Real} = T(Inf) 56 | var(d::Levy{T}) where {T<:Real} = T(Inf) 57 | skewness(d::Levy{T}) where {T<:Real} = T(NaN) 58 | kurtosis(d::Levy{T}) where {T<:Real} = T(NaN) 59 | 60 | mode(d::Levy) = d.σ / 3 + d.μ 61 | 62 | entropy(d::Levy) = (1 - 3digamma(1) + log(16 * d.σ^2 * π)) / 2 63 | 64 | median(d::Levy{T}) where {T<:Real} = d.μ + d.σ / (2 * T(erfcinv(0.5))^2) 65 | 66 | 67 | #### Evaluation 68 | 69 | function pdf(d::Levy{T}, x::Real) where T<:Real 70 | μ, σ = params(d) 71 | if x <= μ 72 | return zero(T) 73 | end 74 | z = x - μ 75 | (sqrt(σ) / sqrt2π) * exp((-σ) / (2z)) / z^(3//2) 76 | end 77 | 78 | function logpdf(d::Levy{T}, x::Real) where T<:Real 79 | μ, σ = params(d) 80 | if x <= μ 81 | return T(-Inf) 82 | end 83 | z = x - μ 84 | (log(σ) - log2π - σ / z - 3log(z))/2 85 | end 86 | 87 | cdf(d::Levy{T}, x::Real) where {T<:Real} = x <= d.μ ? zero(T) : erfc(sqrt(d.σ / (2(x - d.μ)))) 88 | ccdf(d::Levy{T}, x::Real) where {T<:Real} = x <= d.μ ? one(T) : erf(sqrt(d.σ / (2(x - d.μ)))) 89 | 90 | quantile(d::Levy, p::Real) = d.μ + d.σ / (2*erfcinv(p)^2) 91 | cquantile(d::Levy, p::Real) = d.μ + d.σ / (2*erfinv(p)^2) 92 | 93 | mgf(d::Levy{T}, t::Real) where {T<:Real} = t == zero(t) ? one(T) : T(NaN) 94 | 95 | function cf(d::Levy, t::Real) 96 | μ, σ = params(d) 97 | exp(im * μ * t - sqrt(-2im * σ * t)) 98 | end 99 | 100 | 101 | #### Sampling 102 | 103 | rand(rng::AbstractRNG, d::Levy) = d.μ + d.σ / randn(rng)^2 104 | -------------------------------------------------------------------------------- /src/samplers/multinomial.jl: -------------------------------------------------------------------------------- 1 | 2 | function multinom_rand!(n::Int, p::AbstractVector{Float64}, 3 | x::AbstractVector{T}) where T<:Real 4 | k = length(p) 5 | length(x) == k || throw(DimensionMismatch("Invalid argument dimension.")) 6 | 7 | rp = 1.0 # remaining total probability 8 | i = 0 9 | km1 = k - 1 10 | 11 | while i < km1 && n > 0 12 | i += 1 13 | @inbounds pi = p[i] 14 | if pi < rp 15 | xi = rand(Binomial(n, pi / rp)) 16 | @inbounds x[i] = xi 17 | n -= xi 18 | rp -= pi 19 | else 20 | # In this case, we don't even have to sample 21 | # from Binomial. Just assign remaining counts 22 | # to xi. 23 | 24 | @inbounds x[i] = n 25 | n = 0 26 | # rp = 0.0 (no need for this, as rp is no longer needed) 27 | end 28 | end 29 | 30 | if i == km1 31 | @inbounds x[k] = n 32 | else # n must have been zero 33 | z = zero(T) 34 | for j = i+1 : k 35 | @inbounds x[j] = z 36 | end 37 | end 38 | 39 | return x 40 | end 41 | 42 | function multinom_rand!(rng::AbstractRNG, n::Int, p::AbstractVector{Float64}, 43 | x::AbstractVector{T}) where T<:Real 44 | k = length(p) 45 | length(x) == k || throw(DimensionMismatch("Invalid argument dimension.")) 46 | 47 | rp = 1.0 # remaining total probability 48 | i = 0 49 | km1 = k - 1 50 | 51 | while i < km1 && n > 0 52 | i += 1 53 | @inbounds pi = p[i] 54 | if pi < rp 55 | xi = rand(rng, Binomial(n, pi / rp)) 56 | @inbounds x[i] = xi 57 | n -= xi 58 | rp -= pi 59 | else 60 | # In this case, we don't even have to sample 61 | # from Binomial. Just assign remaining counts 62 | # to xi. 63 | 64 | @inbounds x[i] = n 65 | n = 0 66 | # rp = 0.0 (no need for this, as rp is no longer needed) 67 | end 68 | end 69 | 70 | if i == km1 71 | @inbounds x[k] = n 72 | else # n must have been zero 73 | z = zero(T) 74 | for j = i+1 : k 75 | @inbounds x[j] = z 76 | end 77 | end 78 | 79 | return x 80 | end 81 | 82 | struct MultinomialSampler{T<:Real} <: Sampleable{Multivariate,Discrete} 83 | n::Int 84 | prob::Vector{T} 85 | alias::AliasTable 86 | end 87 | 88 | MultinomialSampler(n::Int, prob::Vector{T}) where T<:Real = 89 | MultinomialSampler{T}(n, prob, AliasTable(prob)) 90 | 91 | _rand!(s::MultinomialSampler, x::AbstractVector{T}) where T<:Real = 92 | _rand!(GLOBAL_RNG, s, x) 93 | function _rand!(rng::AbstractRNG, s::MultinomialSampler, 94 | x::AbstractVector{T}) where T<:Real 95 | n = s.n 96 | k = length(s) 97 | if n^2 > k 98 | multinom_rand!(rng, n, s.prob, x) 99 | else 100 | # Use an alias table 101 | fill!(x, zero(T)) 102 | a = s.alias 103 | for i = 1:n 104 | x[rand(rng, a)] += 1 105 | end 106 | end 107 | return x 108 | end 109 | 110 | length(s::MultinomialSampler) = length(s.prob) 111 | -------------------------------------------------------------------------------- /src/univariate/continuous/chi.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Chi(ν) 3 | 4 | The *Chi distribution* `ν` degrees of freedom has probability density function 5 | 6 | ```math 7 | f(x; k) = \\frac{1}{\\Gamma(k/2)} 2^{1 - k/2} x^{k-1} e^{-x^2/2}, \\quad x > 0 8 | ``` 9 | 10 | It is the distribution of the square-root of a [`Chisq`](@ref) variate. 11 | 12 | ```julia 13 | Chi(k) # Chi distribution with k degrees of freedom 14 | 15 | params(d) # Get the parameters, i.e. (k,) 16 | dof(d) # Get the degrees of freedom, i.e. k 17 | ``` 18 | 19 | External links 20 | 21 | * [Chi distribution on Wikipedia](http://en.wikipedia.org/wiki/Chi_distribution) 22 | 23 | """ 24 | struct Chi{T<:Real} <: ContinuousUnivariateDistribution 25 | ν::T 26 | Chi{T}(ν::T) where {T} = new{T}(ν) 27 | end 28 | 29 | function Chi(ν::T; check_args=true) where {T<:Real} 30 | check_args && @check_args(Chi, ν > zero(ν)) 31 | return Chi{T}(ν) 32 | end 33 | 34 | Chi(ν::Integer) = Chi(float(ν)) 35 | 36 | @distr_support Chi 0.0 Inf 37 | 38 | ### Conversions 39 | convert(::Type{Chi{T}}, ν::Real) where {T<:Real} = Chi(T(ν)) 40 | convert(::Type{Chi{T}}, d::Chi{S}) where {T <: Real, S <: Real} = Chi(T(d.ν), check_args=false) 41 | 42 | #### Parameters 43 | 44 | dof(d::Chi) = d.ν 45 | params(d::Chi) = (d.ν,) 46 | @inline partype(d::Chi{T}) where {T<:Real} = T 47 | 48 | 49 | #### Statistics 50 | 51 | mean(d::Chi) = (h = d.ν/2; sqrt2 * gamma(h + 1//2) / gamma(h)) 52 | 53 | var(d::Chi) = d.ν - mean(d)^2 54 | _chi_skewness(μ::Real, σ::Real) = (σ2 = σ^2; σ3 = σ2 * σ; (μ / σ3) * (1 - 2σ2)) 55 | 56 | function skewness(d::Chi) 57 | μ = mean(d) 58 | σ = sqrt(d.ν - μ^2) 59 | _chi_skewness(μ, σ) 60 | end 61 | 62 | function kurtosis(d::Chi) 63 | μ = mean(d) 64 | σ = sqrt(d.ν - μ^2) 65 | γ = _chi_skewness(μ, σ) 66 | (2/σ^2) * (1 - μ * σ * γ - σ^2) 67 | end 68 | 69 | entropy(d::Chi{T}) where {T<:Real} = (ν = d.ν; 70 | loggamma(ν/2) - T(logtwo)/2 - ((ν - 1)/2) * digamma(ν/2) + ν/2) 71 | 72 | function mode(d::Chi) 73 | d.ν >= 1 || error("Chi distribution has no mode when ν < 1") 74 | sqrt(d.ν - 1) 75 | end 76 | 77 | 78 | #### Evaluation 79 | 80 | pdf(d::Chi, x::Real) = exp(logpdf(d, x)) 81 | 82 | logpdf(d::Chi, x::Real) = (ν = d.ν; 83 | (1 - ν/2) * logtwo + (ν - 1) * log(x) - x^2/2 - loggamma(ν/2) 84 | ) 85 | 86 | gradlogpdf(d::Chi{T}, x::Real) where {T<:Real} = x >= 0 ? (d.ν - 1) / x - x : zero(T) 87 | 88 | cdf(d::Chi, x::Real) = chisqcdf(d.ν, x^2) 89 | ccdf(d::Chi, x::Real) = chisqccdf(d.ν, x^2) 90 | logcdf(d::Chi, x::Real) = chisqlogcdf(d.ν, x^2) 91 | logccdf(d::Chi, x::Real) = chisqlogccdf(d.ν, x^2) 92 | 93 | quantile(d::Chi, p::Real) = sqrt(chisqinvcdf(d.ν, p)) 94 | cquantile(d::Chi, p::Real) = sqrt(chisqinvccdf(d.ν, p)) 95 | invlogcdf(d::Chi, p::Real) = sqrt(chisqinvlogcdf(d.ν, p)) 96 | invlogccdf(d::Chi, p::Real) = sqrt(chisqinvlogccdf(d.ν, p)) 97 | 98 | 99 | #### Sampling 100 | 101 | rand(rng::AbstractRNG, d::Chi) = 102 | (ν = d.ν; sqrt(rand(rng, Gamma(ν / 2.0, 2.0one(ν))))) 103 | 104 | struct ChiSampler{S <: Sampleable{Univariate,Continuous}} <: 105 | Sampleable{Univariate,Continuous} 106 | s::S 107 | end 108 | 109 | rand(rng::AbstractRNG, s::ChiSampler) = sqrt(rand(rng, s.s)) 110 | 111 | sampler(d::Chi) = ChiSampler(sampler(Chisq(d.ν))) 112 | --------------------------------------------------------------------------------