Skip to content

Commit ce775d2

Browse files
Merge branch 'JuliaStats:master' into von-mieses-fisher-fix
2 parents 2fcba19 + 1e6801d commit ce775d2

File tree

20 files changed

+519
-23
lines changed

20 files changed

+519
-23
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Distributions"
22
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
33
authors = ["JuliaStats"]
4-
version = "0.25.113"
4+
version = "0.25.117"
55

66
[deps]
77
AliasTables = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
33
GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
44

55
[compat]
6-
Documenter = "0.26, 0.27"
6+
Documenter = "1"
77
GR = "0.72.1, 0.73"

docs/make.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Documenter, Distributions
22
import Random: AbstractRNG, rand!
33

4-
makedocs(
4+
makedocs(;
55
sitename = "Distributions.jl",
66
modules = [Distributions],
77
format = Documenter.HTML(; prettyurls = get(ENV, "CI", nothing) == "true"),
@@ -17,16 +17,18 @@ makedocs(
1717
"reshape.md",
1818
"cholesky.md",
1919
"mixture.md",
20+
"product.md",
2021
"order_statistics.md",
2122
"convolution.md",
2223
"fit.md",
2324
"extends.md",
2425
"density_interface.md",
25-
]
26+
],
27+
warnonly = true,
2628
)
2729

2830
deploydocs(;
2931
repo = "github.com/JuliaStats/Distributions.jl.git",
3032
versions = ["stable" => "v^", "v#.#", "dev" => "master"],
31-
push_preview=true,
33+
push_preview = true,
3234
)

docs/src/multivariate.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ MvNormalCanon
5858
MvLogitNormal
5959
MvLogNormal
6060
Dirichlet
61-
Product
6261
```
6362

6463
## Addition Methods
@@ -105,15 +104,6 @@ params{D<:Distributions.AbstractMvLogNormal}(::Type{D},m::AbstractVector,S::Abst
105104
Distributions._logpdf(d::MultivariateDistribution, x::AbstractArray)
106105
```
107106

108-
## Product distributions
109-
110-
```@docs
111-
Distributions.product_distribution
112-
```
113-
114-
Using `product_distribution` is advised to construct product distributions.
115-
For some distributions, it constructs a special multivariate type.
116-
117107
## Index
118108

119109
```@index

docs/src/product.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Product Distributions
2+
3+
Product distributions are joint distributions of multiple independent distributions.
4+
It is recommended to use `product_distribution` to construct product distributions.
5+
Depending on the type of the argument, it may construct a different distribution type.
6+
7+
## Multivariate products
8+
9+
```@docs
10+
Distributions.product_distribution(::AbstractArray{<:Distribution{<:ArrayLikeVariate}})
11+
Distributions.product_distribution(::AbstractVector{<:Normal})
12+
Distributions.ProductDistribution
13+
Distributions.Product
14+
```
15+
16+
## NamedTuple-variate products
17+
18+
```@docs
19+
Distributions.product_distribution(::NamedTuple{<:Any,<:Tuple{Distribution,Vararg{Distribution}}})
20+
Distributions.ProductNamedTupleDistribution
21+
```
22+
23+
## Index
24+
25+
```@index
26+
Pages = ["product.md"]
27+
```

src/Distributions.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export
4141
Multivariate,
4242
Matrixvariate,
4343
CholeskyVariate,
44+
NamedTupleVariate,
4445
Discrete,
4546
Continuous,
4647
Sampleable,
@@ -296,6 +297,7 @@ include("univariates.jl")
296297
include("edgeworth.jl")
297298
include("multivariates.jl")
298299
include("matrixvariates.jl")
300+
include("namedtuple/productnamedtuple.jl")
299301
include("cholesky/lkjcholesky.jl")
300302
include("samplers.jl")
301303

src/cholesky/lkjcholesky.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@ function insupport(d::LKJCholesky, R::LinearAlgebra.Cholesky)
109109
return true
110110
end
111111

112-
function StatsBase.mode(d::LKJCholesky)
112+
function mode(d::LKJCholesky; check_args::Bool=true)
113+
@check_args(
114+
LKJCholesky,
115+
@setup= d.η),
116+
(η, η > 1, "mode is defined only when η > 1."),
117+
)
113118
factors = Matrix{eltype(d)}(LinearAlgebra.I, size(d))
114119
return LinearAlgebra.Cholesky(factors, d.uplo, 0)
115120
end

src/common.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ const Univariate = ArrayLikeVariate{0}
1616
const Multivariate = ArrayLikeVariate{1}
1717
const Matrixvariate = ArrayLikeVariate{2}
1818

19+
"""
20+
`F <: NamedTupleVariate{K}` specifies that the variate or a sample is of type
21+
`NamedTuple{K}`.
22+
"""
23+
struct NamedTupleVariate{K} <: VariateForm end
24+
1925
"""
2026
`F <: CholeskyVariate` specifies that the variate or a sample is of type
2127
`LinearAlgebra.Cholesky`.

src/multivariate/product.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ An N dimensional `MultivariateDistribution` constructed from a vector of N indep
1010
```julia
1111
Product(Uniform.(rand(10), 1)) # A 10-dimensional Product from 10 independent `Uniform` distributions.
1212
```
13+
14+
!!! note
15+
`Product` is deprecated and will be removed in the next breaking release.
16+
Use [`product_distribution`](@ref) instead.
1317
"""
1418
struct Product{
1519
S<:ValueSupport,

src/namedtuple/productnamedtuple.jl

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""
2+
ProductNamedTupleDistribution{Tnames,Tdists,S<:ValueSupport,eltypes} <:
3+
Distribution{NamedTupleVariate{Tnames},S}
4+
5+
A distribution of `NamedTuple`s, constructed from a `NamedTuple` of independent named
6+
distributions.
7+
8+
Users should use [`product_distribution`](@ref) to construct a product distribution of
9+
independent distributions instead of constructing a `ProductNamedTupleDistribution`
10+
directly.
11+
12+
# Examples
13+
14+
```jldoctest ProductNamedTuple; setup = :(using Distributions, Random; Random.seed!(832))
15+
julia> d = product_distribution((x=Normal(), y=Dirichlet([2, 4])))
16+
ProductNamedTupleDistribution{(:x, :y)}(
17+
x: Normal{Float64}(μ=0.0, σ=1.0)
18+
y: Dirichlet{Int64, Vector{Int64}, Float64}(alpha=[2, 4])
19+
)
20+
21+
22+
julia> nt = rand(d)
23+
(x = 1.5155385995160346, y = [0.533531876438439, 0.466468123561561])
24+
25+
julia> pdf(d, nt)
26+
0.13702825691074877
27+
28+
julia> pdf(d, reverse(nt)) # order of fields does not matter
29+
0.13702825691074877
30+
31+
julia> mode(d) # mode of marginals
32+
(x = 0.0, y = [0.25, 0.75])
33+
34+
julia> mean(d) # mean of marginals
35+
(x = 0.0, y = [0.3333333333333333, 0.6666666666666666])
36+
37+
julia> var(d) # var of marginals
38+
(x = 1.0, y = [0.031746031746031744, 0.031746031746031744])
39+
```
40+
"""
41+
struct ProductNamedTupleDistribution{Tnames,Tdists,S<:ValueSupport,eltypes} <:
42+
Distribution{NamedTupleVariate{Tnames},S}
43+
dists::NamedTuple{Tnames,Tdists}
44+
end
45+
function ProductNamedTupleDistribution(
46+
dists::NamedTuple{K,V}
47+
) where {K,V<:Tuple{Distribution,Vararg{Distribution}}}
48+
vs = _product_valuesupport(values(dists))
49+
eltypes = _product_namedtuple_eltype(values(dists))
50+
return ProductNamedTupleDistribution{K,V,vs,eltypes}(dists)
51+
end
52+
53+
_gentype(d::UnivariateDistribution) = eltype(d)
54+
_gentype(d::Distribution{<:ArrayLikeVariate{S}}) where {S} = Array{eltype(d),S}
55+
function _gentype(d::Distribution{CholeskyVariate})
56+
T = eltype(d)
57+
return LinearAlgebra.Cholesky{T,Matrix{T}}
58+
end
59+
function _gentype(d::ProductNamedTupleDistribution{K}) where {K}
60+
return NamedTuple{K,Tuple{map(_gentype, values(d.dists))...}}
61+
end
62+
_gentype(::Distribution) = Any
63+
64+
_product_namedtuple_eltype(dists) = typejoin(map(_gentype, dists)...)
65+
66+
function Base.show(io::IO, d::ProductNamedTupleDistribution)
67+
return show_multline(io, d, collect(pairs(d.dists)))
68+
end
69+
70+
function distrname(::ProductNamedTupleDistribution{K}) where {K}
71+
return "ProductNamedTupleDistribution{$K}"
72+
end
73+
74+
"""
75+
product_distribution(dists::NamedTuple{K,Tuple{Vararg{Distribution}}}) where {K}
76+
77+
Create a distribution of `NamedTuple`s as a product distribution of independent named
78+
distributions.
79+
80+
The function falls back to constructing a [`ProductNamedTupleDistribution`](@ref)
81+
distribution but specialized methods can be defined.
82+
"""
83+
function product_distribution(
84+
dists::NamedTuple{<:Any,<:Tuple{Distribution,Vararg{Distribution}}}
85+
)
86+
return ProductNamedTupleDistribution(dists)
87+
end
88+
89+
# Properties
90+
91+
Base.eltype(::Type{<:ProductNamedTupleDistribution{<:Any,<:Any,<:Any,T}}) where {T} = T
92+
93+
Base.minimum(d::ProductNamedTupleDistribution) = map(minimum, d.dists)
94+
95+
Base.maximum(d::ProductNamedTupleDistribution) = map(maximum, d.dists)
96+
97+
function _named_fields_match(x::NamedTuple{K}, y::NamedTuple) where {K}
98+
length(x) == length(y) || return false
99+
try
100+
NamedTuple{K}(y)
101+
return true
102+
catch
103+
return false
104+
end
105+
end
106+
107+
function insupport(dist::ProductNamedTupleDistribution{K}, x::NamedTuple) where {K}
108+
return (
109+
_named_fields_match(dist.dists, x) &&
110+
all(map(insupport, dist.dists, NamedTuple{K}(x)))
111+
)
112+
end
113+
114+
# Evaluation
115+
116+
function pdf(dist::ProductNamedTupleDistribution, x::NamedTuple)
117+
return exp(logpdf(dist, x))
118+
end
119+
120+
function logpdf(dist::ProductNamedTupleDistribution{K}, x::NamedTuple) where {K}
121+
return sum(map(logpdf, dist.dists, NamedTuple{K}(x)))
122+
end
123+
124+
function loglikelihood(dist::ProductNamedTupleDistribution, x::NamedTuple)
125+
return logpdf(dist, x)
126+
end
127+
128+
function loglikelihood(dist::ProductNamedTupleDistribution, xs::AbstractArray{<:NamedTuple})
129+
return sum(Base.Fix1(loglikelihood, dist), xs)
130+
end
131+
132+
# Statistics
133+
134+
mode(d::ProductNamedTupleDistribution) = map(mode, d.dists)
135+
136+
mean(d::ProductNamedTupleDistribution) = map(mean, d.dists)
137+
138+
var(d::ProductNamedTupleDistribution) = map(var, d.dists)
139+
140+
std(d::ProductNamedTupleDistribution) = map(std, d.dists)
141+
142+
entropy(d::ProductNamedTupleDistribution) = sum(entropy, values(d.dists))
143+
144+
function kldivergence(
145+
d1::ProductNamedTupleDistribution{K}, d2::ProductNamedTupleDistribution
146+
) where {K}
147+
_named_fields_match(d1.dists, d2.dists) || throw(
148+
ArgumentError(
149+
"Sets of named tuple fields are not the same: !issetequal($(keys(d1.dists)), $(keys(d2.dists)))",
150+
),
151+
)
152+
return sum(map(kldivergence, d1.dists, NamedTuple{K}(d2.dists)))
153+
end
154+
155+
# Sampling
156+
157+
function sampler(d::ProductNamedTupleDistribution{K,<:Any,S}) where {K,S}
158+
samplers = map(sampler, d.dists)
159+
Tsamplers = typeof(values(samplers))
160+
return ProductNamedTupleSampler{K,Tsamplers,S}(samplers)
161+
end
162+
163+
function Base.rand(rng::AbstractRNG, d::ProductNamedTupleDistribution{K}) where {K}
164+
return NamedTuple{K}(map(Base.Fix1(rand, rng), d.dists))
165+
end
166+
function Base.rand(
167+
rng::AbstractRNG, d::ProductNamedTupleDistribution{K}, dims::Dims
168+
) where {K}
169+
return convert(AbstractArray{<:NamedTuple{K}}, _rand(rng, sampler(d), dims))
170+
end
171+
172+
function _rand!(rng::AbstractRNG, d::ProductNamedTupleDistribution, xs::AbstractArray)
173+
return _rand!(rng, sampler(d), xs)
174+
end

0 commit comments

Comments
 (0)