Skip to content

Frequently asked questions

Short answers to the questions this package tends to raise. For a full walkthrough see Composing distributions, and for the verb map see the Concepts page.

Where did censoring go?

ComposedDistributions is the generic composition layer, split out from CensoredDistributions.jl. It composes any Distributions.jl UnivariateDistribution and adds no censoring of its own. If you need primary-event censoring, interval censoring, or truncation of the observation process, use CensoredDistributions.jl, which builds its censored leaves on top of this composition algebra. Right-truncation of a plain leaf is still available through truncated() from Distributions.jl, since any leaf is an ordinary distribution.

Why does rand of a Resolve or Compete return a single number?

A one_of node is a univariate marginal: its rand is the time to resolution, whichever outcome occurs, and its logpdf scores that time. To also learn which outcome occurred, use rand_outcome, which returns an(outcome, time) pair.

julia
using ComposedDistributions, Distributions, Random
import ComposedDistributions: rand_outcome

node = resolve(:death => (Gamma(1.5, 1.0), 0.3), :recover => Gamma(2.0, 1.5))
rand(Xoshiro(1), node), rand_outcome(Xoshiro(1), node)
(1.8400307468613506, (:death, 1.8400307468613506))

The two share the same drawn time; rand_outcome just also names the outcome.

How do I get the total-delay distribution of a chain?

A Sequential chain models each step separately, and its rand returns the per-step record. To collapse the chain to the single distribution of its origin-to-final gap, use observed_distribution, which convolves the steps into one delay.

julia
chain = sequential(:onset_admit => Gamma(2.0, 1.0),
    :admit_death => Gamma(3.0, 1.0))
total = observed_distribution(chain)
mean(total)
5.0

For two standalone delays that are not part of a tree, convolve_distributions gives their sum X + Y directly.

How do I fix a parameter across branches?

Use shared or tie to make several leaves one free parameter. shared tags a leaf where it is built; tie walks an assembled tree to named leaves and ties them. Either way params_table then inventories the tied occurrences once under the shared tag rather than as separate parameters.

julia
d = compose((incubation = Gamma(2.0, 1.0),
    onset_report = Gamma(2.0, 1.0)))
tied = tie(d, :incubation, :onset_report; name = :delay)
unique(params_table(tied).edge)
1-element Vector{Symbol}:
 :delay

Why is my logpdf -Inf?

A composed logpdf is -Inf when a value falls outside a leaf's support. The commonest cause is a negative or zero value scored against a positive-support delay, such as a Gamma or LogNormal. When scoring a whole record, check that every value sits in its own leaf's support and that the record is laid out in the tree's event order (see event_names); a value in the wrong slot can land outside the leaf that scores it.

How do I fit a composed distribution to data?

A composed object is a Distribution, so its logpdf is the likelihood term you need, and scoring is automatic-differentiation friendly. Drop it into a Turing.jl model, or any optimiser, exactly as you would any distribution; there is no package-specific fitting API. params_table gives the free-parameter inventory and build_priors derives support-respecting priors to start from.

Is a composed distribution really a Distribution?

Yes. Every composer subtypes Distributions.Distribution, so logpdf, rand, mean, var, cdf (where defined) and the rest of the interface work unchanged, and a composed object drops into any code that expects a distribution.