ModifiedDistributions
Wrappers for Distributions.jl univariate distributions that each change one behaviour — rescaling, likelihood weighting, hazard modification, or a forward transform — while everything else keeps working, plus the generic get_dist unwrap protocol.
Why ModifiedDistributions?
A model routinely needs "the same delay, but weighted, rescaled, or hazard-shifted"; ModifiedDistributions wraps each change as one small, composable piece instead of hand-deriving the change-of-variables maths each time.
Every wrapper stays a complete distribution — sampling, quantiles, moments — so a modified delay drops into a PPL model exactly like the distribution it wraps.
Wrappers nest and unwrap cleanly, so a pipeline built from several small changes stays inspectable and reversible rather than collapsing into one opaque function.
Weighting a likelihood is a one-line wrapper instead of an ad hoc
n * logpdf(...)term scattered through model code.Hazard-scale changes, such as an intervention that shifts risk, have no Distributions.jl counterpart and are given here in closed form.
The same wrappers apply across ComposedDistributions.jl chains and ConvolvedDistributions.jl count series, so a delay modified once carries that change through the rest of the pipeline.
Getting started
See documentation for a full walkthrough.
Modifiers nest, so a real pipeline stacks several changes on one delay: an intervention that halves the hazard of admission, a half-day reporting lag, and a 60% ascertainment fraction tagged for a downstream count series.
using ModifiedDistributions, Distributions
admission = Gamma(2.0, 1.0) # baseline infection-to-admission delay
pipeline = thin(affine(modify(admission, -log(2.0)); shift = 0.5), 0.6)Transformed(Affine(Modified(Distributions.Gamma{Float64}(α=2.0, θ=1.0), -0.6931471805599453; link=LogLink)))pipeline prints as the nested wrapper it is.
pipelineTransformed(Affine(Modified(Distributions.Gamma{Float64}(α=2.0, θ=1.0), -0.6931471805599453; link=LogLink)))Each stage compounds the three-day survival: halving the hazard raises it to its square root, and the reporting lag raises it further (day 3 is now effectively day 2.5 post-admission); thin does not touch the scalar distribution at all, only tagging it for later use.
(baseline_survival = ccdf(admission, 3.0),
after_intervention = ccdf(modify(admission, -log(2.0)), 3.0),
after_reporting_lag = ccdf(pipeline, 3.0))(baseline_survival = 0.19914827347145578, after_intervention = 0.44626032029685964, after_reporting_lag = 0.536001394759049)Unwrapping recovers the baseline delay underneath every layer.
get_dist_recursive(pipeline) == admissiontrueThe getting started guide carries this same pipeline further: what thin does once it meets a real count series, and how modifiers apply across a composed chain.
Relationship to Distributions.jl
Distributions.jl already supports affine arithmetic on some distributions (2.0 * X + 1.0) by returning a new parameterisation where one exists. affine instead wraps any univariate distribution with the exact change-of-variables maths, so it works uniformly and keeps the inner distribution recoverable via get_dist. Likewise weight replaces ad hoc n * logpdf(d, x) terms in model code with a distribution object that carries its weight, and modify gives hazard-scale transforms that have no Distributions.jl counterpart.
Related packages
ComposedDistributions.jl composes distributions into event-tree chains; a package extension here lets the modifier verbs apply across a chain's observed total, and hosts the leaf protocol that lets a modified leaf compose inside a chain.
ConvolvedDistributions.jl sums independent delays and convolves count series; a package extension applies
thin/cumulativeto the convolved counts and lets modified distributions serve as convolution components.LoweredDistributions.jl turns a distribution into a backend-agnostic dynamical-systems representation; its own package extension lowers the modifiers that carry dynamics (an
affinerescale, amodifyhazard change on anExponential) and refuses the observation-only ones (a shift, aweight, a forward transform) rather than approximating them.CensoredDistributions.jl builds primary-event and interval censoring on distributions, including ones already modified by this package.
DistributionsInference.jl is the emerging home for probabilistic-programming integrations (Turing.jl, DynamicPPL, Bijectors) that a modified or weighted distribution plugs into.
Where to learn more
Want to get started running code? See the getting started guide.
Want to understand the API? See the API reference.
Want to see the code? Check out our GitHub repository.
Getting help
For usage questions, ask on the Julia Discourse (the SciML or usage categories) or the epinowcast community forum, our home for epidemiological modelling questions. Please use GitHub issues for bug reports and feature requests only.
Contributing
We welcome contributions and new contributors! This package follows ColPrac and the SciML style.
Supporting and citing
If you would like to support ModifiedDistributions, please star the repository — such metrics help secure future funding.
If you use ModifiedDistributions in your work, please cite it. Citation metadata lives in CITATION.cff, which GitHub renders as a "Cite this repository" button on the repository page. A version-specific DOI is minted with the first Zenodo release.
Code of conduct
Please note that the ModifiedDistributions project is released with a Contributor Code of Conduct. By contributing, you agree to abide by its terms.