Fitting and Selection
VineCopulas.jl uses sequential vine estimation. The package fits one tree at a time, computes conditional pseudo-observations through h-functions, and then uses those transformed observations to fit the next tree.
Sequential vine fitting
fit(CVineCopula, U), fit(DVineCopula, U), and fit(RVineCopula, U) return the fitted vine copula itself. method=:default and method=:sequential are equivalent; joint :mle fitting is not provided.
Quick fit
The simplest automatic R-vine fit is:
using VineCopulas
using Distributions: fit
using Random
truth = DVineCopula(
[1, 2, 3],
[[GaussianCopula(2, 0.55), ClaytonCopula(2, 1.4)],
[FrankCopula(2, 2.0)]],
)
U = rand(MersenneTwister(7), truth, 250)
fitted = fit(RVineCopula, U)
(typeof(fitted), order(fitted), truncation(fitted))(RVineCopula{3, 2, Tuple{Tuple{GaussianCopula{2, Matrix{Float64}}, SurvivalCopula{2, JoeCopula{2, Float64}}}, Tuple{FrankCopula{2, Float64}}}}, (1, 2, 3), 2)The returned object is an ordinary RVineCopula: it can be evaluated, simulated, truncated, and passed to aic or bic.
Statistical fitted models and inference
Use VineModel when the fitting recipe, observations, likelihood summaries, and coefficient vector are needed alongside the fitted distribution:
M = fit(VineModel, RVineCopula, U)
fitted_distribution(M)
fitting_method(M) # :sequential
order(M); structure(M); truncation(M)
coef(M); coefnames(M)
StatsBase.aic(M); StatsBase.bic(M)
edge_table(M)edge_table(M) derives one row per active edge with its tree, edge index, conditioned and conditioning variables, family, rotation, and natural parameters. It does not store a second copy of the fitted structure.
StatsBase.aic(M) and StatsBase.bic(M) use the final vine likelihood and the number of active numerical pair-copula parameters. They are useful comparative scores for sequential fits to the same data, but the parameters are not a joint maximum-likelihood estimate; their usual joint-MLE asymptotic interpretation is therefore not asserted here.
VineCopulas.aicc(M) and VineCopulas.hqc(M) are currently internal convenience criteria computed from that same final likelihood and active numerical parameter count. They do not assert a special sequential-vine theory. A future sparse-vine criterion should consider mBICV instead.
Inference is a fixed-selection bootstrap:
I = infer(M; method=:bootstrap, nresamples=200, rng=MersenneTwister(1))
StatsBase.vcov(I)
StatsBase.stderror(I)
StatsBase.confint(I)Conditional inference
Current vine covariance inference conditions on the selected structure, pair families, and rotations. Every replicate resamples observations and sequentially refits all active pair parameters, propagating pseudo-observations through the trees. It does not include model-selection uncertainty, and it intentionally does not offer a joint-MLE Hessian.
Pair-family selection
Pair-copula selection chooses the best bivariate family for one edge. It can be used directly:
using VineCopulas
using Distributions: fit
using Random
C = ClaytonCopula(2, 1.8)
U2 = rand(MersenneTwister(9), C, 250)
pair = select_paircopula(
U2;
family_set=:default,
selection_criterion=:bic,
allow_rotations=true,
)
typeof(pair)ClaytonCopula{2, Float64} (alias for ArchimedeanCopula{2, Copulas.ClaytonGenerator{Float64}})The main controls are:
| Keyword | Meaning |
|---|---|
family_set | Candidate families, such as :default, :all, or an explicit tuple |
selection_criterion | :loglik, :aic, or :bic |
allow_rotations | Whether to include rotated candidates |
include_independence | Whether independence can be selected |
preselect | Whether dependence sign may prune rotation candidates |
Tip
Use selection_criterion=:bic for a conservative default. Use selection_criterion=:loglik mainly for diagnostics or controlled comparisons where model dimension is fixed.
Structure selection
Automatic R-vine fitting follows a Dissmann-style sequential procedure:
compute dependence weights for candidate edges;
build a maximum spanning tree;
fit pair-copulas on selected edges;
propagate conditional pseudo-observations;
build the next candidate graph under the proximity condition;
repeat until the requested truncation depth.
using VineCopulas
using Distributions: fit
using Random
truth = DVineCopula(
[1, 2, 3, 4],
[[GaussianCopula(2, 0.5), ClaytonCopula(2, 1.3), FrankCopula(2, 2.0)],
[GumbelCopula(2, 1.2), JoeCopula(2, 1.3)]];
trunc=2,
)
U = rand(MersenneTwister(10), truth, 250)
model = fit(
RVineCopula,
U;
trunc=2,
tree_criterion=:tau,
tree_algorithm=:kruskal,
family_set=:default,
)
(order = order(model), truncation = truncation(model), edge_count = sum(length, edges(model)))(order = (1, 3, 2, 4), truncation = 2, edge_count = 5)At tree
where tree_criterion attaches to the candidate edge tree_criterion changes tree_algorithm=:kruskal path is the one used in the external parity tests.
Built-in tree criteria
Each built-in criterion is the absolute value of a documented dependence statistic, listed here with its estimator, its range and its reference (full citations on the References page):
tree_criterion | range | reference | |
|---|---|---|---|
:tau | Kendall (1938); Dißmann et al. (2013) | ||
:rho | Spearman (1904) | ||
:hoeffd | Hoeffding (1948); Hollander, Wolfe & Chicken (2014) | ||
:mcor | Gebelein (1941); Rényi (1959); Breiman & Friedman (1985) | ||
:joe | Joe (1989) | ||
:cxi | Chatterjee (2021) |
Three notes on the less common ones.
:hoeffdis Hoeffding'sstatistic; its population value is , which is 0 under independence and 1 under any monotone functional relation. A non-monotone functional relation scores below 1 (a symmetric parabola gives about). Ties are counted strictly. The estimator needs at least five observations. :mcoris the maximum correlation coefficientof Gebelein and Rényi, estimated by the alternating conditional expectations algorithm of Breiman & Friedman with a running-mean smoother of half-width , at most 10 inner and 100 outer iterations, and stopping tolerances and on the change of the mean squared difference between the two transforms. These are the constants ofvinecopulib'smcor, so the two agree on the same data. For a Gaussian pair , and a functional relation, monotone or not, scores near 1.:joeis the mutual information of a Gaussian copula with correlation, Joe's relative-entropy dependence measure under the Gaussian assumption. It is not bounded, and it is strictly increasing in : a maximum spanning tree depends only on the order of its weights, so :joeselects the same trees ason normal scores, and its scale is visible only through threshold. It isInfon a pair whose normal scores are exactly linearly dependent.:cxiis Chatterjee's, which measures how well one variable is a measurable function of the other and is therefore asymmetric; the criterion takes the larger of the two directions. The population value is in ; the estimate can be slightly negative, and the absolute value is taken.
The rank correlations and :joe do not see a symmetric non-monotone relation (a parabola in :hoeffd, :mcor and :cxi do; the last two rank a functional relation above a noisy monotone one.
A custom tree criterion
tree_criterion also accepts a function. It is called once per candidate edge f(u_a, u_b, a, b, D), where u_a and u_b are the two conditional pseudo-observation vectors and a, b, D are variable labels, and its value is D is the roots chosen so far), D-vine order selection, and the R-vine spanning trees. The built-in :tau is (ua, ub, a, b, D) -> abs(VineCopulas._kendall_tau_b(ua, ub)).
The contract:
the value must be a finite
Real;NaN,±Inf,missing, or anything that is not aRealthrows anArgumentErrornaming the edge and the value;the value is used as is: no absolute value is taken, and the tree maximises it, so the sign is the caller's choice;
the function should be deterministic in its arguments. Each edge is scored once per tree, so a random criterion is not refused, but the fit is then not reproducible without control of its randomness.
# A precomputed weight matrix for the first tree, |tau| above it.
W = my_tree1_weights(U)
model = fit(RVineCopula, U;
tree_criterion=(ua, ub, a, b, D) -> isempty(D) ? W[a, b] : abs(VineCopulas._kendall_tau_b(ua, ub)))
# The Pearson correlation of the normal scores, the monotone image of :joe.
normal_r = (ua, ub, a, b, D) -> abs(cor(quantile.(Normal(), ua), quantile.(Normal(), ub)))
model = fit(RVineCopula, U; tree_criterion=normal_r)threshold follows the criterion's scale
threshold forces an independence pair-copula on every candidate edge with threshold, on the criterion's own scale, and it does not remove the edge from the candidate set. Its admissible range therefore follows the criterion: :tau, :rho, :hoeffd, :mcor and :cxi; :joe; any finite value for a function.
With a custom criterion threshold is a cut on that function's scale and no longer a dependence threshold in the usual sense. For example (ua, ub, a, b, D) -> abs(VineCopulas._kendall_tau_b(ua, ub)) + 2 leaves threshold=2.3 is exactly :tau with threshold=0.3, and threshold=0.3 forces nothing.
Group-constrained first tree
groups is an optional structural constraint for automatic R-vine selection. It takes one integer group id per variable:
model = fit(RVineCopula, U; groups=[1, 1, 1, 2, 2, 2])Let the variables be partitioned into groups groups restricts selection to those for which every tree_criterion, tree 1 solves
This is a clustered spanning-tree connectivity constraint. For the complete candidate graph available at R-vine tree 1, the constrained maximum spanning tree decomposes into maximum spanning trees within the groups and a maximum spanning tree on the contracted graph of groups.
The implementation obtains the same optimum in one deterministic Kruskal pass: within-group candidates are processed before cross-group candidates, while decreasing edge weight and the existing deterministic tie break are preserved inside each class.
Thus grouped tree-1 selection has the same asymptotic complexity as the unconstrained Kruskal selection. Its implementation is tested against brute force enumeration of all admissible spanning trees on small instances.
groups affects tree 1 only. From tree 2 onward, candidate edges are determined by the R-vine proximity condition and selection proceeds exactly as in the ordinary sequential fit.
Two limiting cases reproduce the unconstrained fit exactly:
groups = ones(Int, p) # all variables in one group
groups = collect(1:p) # one group per variablegroups composes with tree_criterion, threshold, trunc, and sampling_tail. It cannot be combined with structure, because a fixed structure already determines tree 1.
The partition should be interpreted as structural information supplied by the user rather than inferred from the data. Its usefulness therefore depends on whether the grouping is meaningful for the application.
The effect is particularly relevant for truncated vines, where the first trees determine which dependencies are represented explicitly. In the special full-depth Gaussian case, different R-vine structures can represent the same Gaussian copula; remaining differences are then due to finite-sample sequential estimation rather than to the group constraint itself.
Fixed-structure fitting
If the R-vine structure is part of the statistical design, pass it explicitly:
using VineCopulas
using Distributions: fit
using Random
ord = [3, 1, 4, 2]
S = ([1, 4, 2], [4, 2], [2])
st = RVineStructure(ord, S; trunc=3)
source = RVineCopula(
st,
[
[GaussianCopula(2, 0.4), ClaytonCopula(2, 1.5), FrankCopula(2, 2.0)],
[GaussianCopula(2, 0.3), ClaytonCopula(2, 1.2)],
[FrankCopula(2, 1.2)],
],
)
U = rand(MersenneTwister(12), source, 250)
refit = fit(RVineCopula, U; structure=st, family_set=:default)
(order(refit), truncation(refit))((3, 1, 4, 2), 3)Fixed structure is not fixed family
Passing structure=st fixes the vine graph/order. It does not freeze the pair-copula families unless the API path you use also supplies explicit edges or a previously constructed vine.
Truncation
trunc=q limits fitting to the first q trees:
fit(RVineCopula, U; trunc=2)For an existing model, use:
smaller = truncate(vine, 2)Truncation returns a new model or structure and does not mutate the original. It cannot restore pair-copulas that are not present in the input.
Level zero
The mathematical case truncate(vine, 0) corresponds to multivariate independence. The current public truncation API starts at level 1 because the existing engines assume at least one active tree. Level-zero truncation is a roadmap item, not a hidden feature.
Model comparison
For explicit or fitted vine objects:
using VineCopulas
using Random
vine = DVineCopula(
[1, 2, 3],
[[GaussianCopula(2, 0.5), ClaytonCopula(2, 1.3)],
[FrankCopula(2, 1.8)]],
)
U = rand(MersenneTwister(14), vine, 200)
(loglikelihood = loglikelihood(vine, U),
npars = npars(vine),
AIC = aic(vine, U),
BIC = bic(vine, U))(loglikelihood = 95.27958396863679, npars = 3, AIC = -184.55916793727357, BIC = -174.66421583762946)AIC and BIC are post-fit scores for the supplied model and data. They do not by themselves prove that the sequential fitting procedure found a global full-vine maximum likelihood optimum.
Metadata and diagnostics
The fitted vine is deliberately a distribution, not a fitted-result container. Its order, truncation, selected edge families, and post-fit scores can be inspected directly. A richer fitted-result API for diagnostics such as selected truncation, mBICV, selection traces, and edge-level convergence is planned separately.