vine() separates marginal modeling from copula modeling.
Each fitted margin provides a density or probability mass function, a
distribution function, and a quantile function. rvinecopulib uses those
functions to move between the data scale and the copula scale.
This vignette covers the default nonparametric margins, optional parametric selection, custom families, discrete and zero-inflated variables, and the protocol that other packages can implement.
Default nonparametric margins
The default is a kde1d fit for every variable.
x <- data.frame(
first = rnorm(60),
second = rgamma(60, shape = 2)
)
fit_kde <- vine(
x,
copula_controls = list(family_set = "indep")
)
summary(fit_kde)$margins
#> # A data.frame: 2 x 8
#> margin name family type xmin xmax npars loglik
#> 1 first kde1d c -Inf Inf 3.5 -73
#> 2 second kde1d c -Inf Inf 5.0 -88KDE controls belong to the family specification. This keeps
vine() free of method-specific options:
fit_bounded <- vine(
transform(x, second = pmin(second, 10)),
margins_controls = list(
family_set = list(
kde1d_family(mult = 1.5),
kde1d_family(xmin = 0, xmax = 10, deg = 1)
)
),
copula_controls = list(family_set = "indep")
)Observation weights
The weights argument to vine() is used for
both the margins and copula. kde1d supports weights
directly. Every margin-family fitter receives x,
weights, and type; it must use the weights or
reject them explicitly:
weighted_normal <- margin_family(
fit = function(x, weights, type) {
location <- weighted.mean(x, weights)
scale <- sqrt(weighted.mean((x - location)^2, weights))
margin_dist(
d = function(y) dnorm(y, location, scale),
p = function(y) pnorm(y, location, scale),
q = function(p) qnorm(p, location, scale),
family = "weighted-normal",
type = type,
npars = 2,
loglik = sum(weights * dnorm(x, location, scale, log = TRUE))
)
},
family_name = "weighted-normal"
)
fit_weighted <- vine(x, margins_controls = list(family_set = weighted_normal),
weights = runif(nrow(x)))When no weights are supplied, weights is
numeric(). The built-in univariateML family rejects
non-empty weights. A failed candidate does not stop selection if another
candidate succeeds, but the failure is reported.
On non-Windows systems, margins are fitted in forked processes when
several cores are requested. Stochastic custom fitters may then depend
on the number of processes; set
margins_controls = list(cores = 1) when results must remain
invariant to the core count. Margin fitting is always serial on
Windows.
Parametric selection with univariateML
The suggested univariateML package supplies named
parametric families. A character vector is a common candidate set for
every variable; rvinecopulib fits every compatible candidate and
performs the selection itself.
fit_parametric <- vine(
x,
margins_controls = list(
family_set = c("norm", "cauchy", "gamma"),
selcrit = "bic"
),
copula_controls = list(family_set = "indep")
)
#> Loading required namespace: intervals
#> Warning: margin selection for variable 1 reported problems (gamma failed: x not
#> in the support of the data).
summary(fit_parametric)$margins
#> # A data.frame: 2 x 8
#> margin name family type xmin xmax npars loglik
#> 1 first norm c -Inf Inf 2 -74
#> 2 second gamma c 0 Inf 2 -89Available criteria are "loglik", "aic", and
"bic". The aliases "parametric" and
"par" expand to all univariateML families, while
"all" also includes "kde1d". These names
require univariateML; custom families and the default KDE fit do
not.
Use a list to specify candidates separately by variable:
fit_mixed <- vine(
data.frame(amount = rexp(100), count = rpois(100, 3)),
var_types = c("c", "d"),
margins_controls = list(
family_set = list(
amount = c("exp", "gamma", "weibull"),
count = c("pois", "nbinom")
)
)
)A named list must contain every variable name exactly once. An unnamed list is matched by position.
Custom fitted families
margin_family() wraps a fitting function with the
canonical x, weights, and type
interface. It returns a fitted object implementing the fitted-margin
protocol. margin_dist() is the simplest way to construct
such an object.
normal_family <- margin_family(
fit = function(x, weights, type) {
location <- mean(x)
scale <- sqrt(mean((x - location)^2))
margin_dist(
d = function(y) dnorm(y, location, scale),
p = function(y) pnorm(y, location, scale),
q = function(p) qnorm(p, location, scale),
family = "custom_normal",
type = type,
npars = 2,
loglik = sum(dnorm(x, location, scale, log = TRUE))
)
},
family_name = "custom_normal",
types = "c"
)
fit_custom <- vine(
x,
margins_controls = list(family_set = normal_family),
copula_controls = list(family_set = "indep")
)
summary(fit_custom)$margins
#> # A data.frame: 2 x 8
#> margin name family type xmin xmax npars loglik
#> 1 first custom_normal c -Inf Inf 2 -74
#> 2 second custom_normal c -Inf Inf 2 -99The fitting callback can capture additional settings in its
environment or receive them through the fit_args argument
to margin_family(). When several candidates compete, each
fitted object’s margin_info() result needs a finite
loglik entry; AIC and BIC additionally require a finite
npars entry. If one candidate fails, selection continues
with the remaining fits; if all fail, vine() reports the
collected errors. A sole candidate without a finite parameter count is
retained with a warning, but model AIC and BIC are then unavailable.
To combine named and custom candidates separately by variable, use nested lists:
The fitted-margin protocol
Packages can integrate their own fitted classes by implementing four
S3 methods. The dmargin(), pmargin(), and
qmargin() generics dispatch on their second argument,
margin; margin_info() provides model
metadata.
dmargin.my_margin <- function(x, margin) { ... }
pmargin.my_margin <- function(x, margin) { ... }
qmargin.my_margin <- function(p, margin) { ... }
margin_info.my_margin <- function(object) {
list(
family_name = "my-family",
type = "c",
support = c(-Inf, Inf),
npars = object$npars,
loglik = object$loglik
)
}The metadata methods are part of the protocol rather than attributes
that the core code interprets. See ?margin_protocol for the
complete return-value and left-limit contract.
A package can likewise implement the margin-family protocol directly
with fit_margin() and margin_info() methods.
The core selection path treats such families like any other
implementation.
Integer-valued discrete variables
All discrete margins use integer support. Declare an ordinary numeric
column with var_types = "d", or use an ordered
column. Ordered factors are fitted internally using the integer codes
0, 1, ...; simulations restore the original ordered
levels.
ordered_data <- data.frame(
rating = ordered(
sample(c("low", "middle", "high"), 80, replace = TRUE),
levels = c("low", "middle", "high")
),
value = rnorm(80)
)
fit_ordered <- vine(
ordered_data,
copula_controls = list(family_set = "indep")
)
str(rvine(4, fit_ordered))
#> 'data.frame': 4 obs. of 2 variables:
#> $ rating: Ord.factor w/ 3 levels "low"<"middle"<..: 3 3 1 3
#> $ value : num 1.298 -0.3304 -0.0999 0.3728For integer-supported margins, rvinecopulib computes the left-limit
CDF as F(x - 1).
Continuous variables with an atom at zero
zero_inflated() marks a numeric vector as continuous
away from zero with an atom at zero. The marker survives data-frame
storage and subsetting. The same type can be declared explicitly with
var_types = "zi".
zero_data <- data.frame(
claim = zero_inflated(c(rep(0, 20), rexp(60))),
score = rnorm(80)
)
inherits(zero_data$claim, "zero_inflated")
#> [1] TRUEA custom zero-inflated family declares types = "zi"; its
fitted margin returns type = "zi" from
margin_info(). At zero, dmargin() must return
the atom probability. rvinecopulib then computes the left limit as
F(0) - f(0); away from zero the left limit equals the
ordinary CDF.
Fixed margins and persistence
stats_margin() adapts fixed stats
distributions, including norm, lnorm,
gamma, and weibull, to the fitted-margin
protocol. Legacy list(distr = ...) specifications remain
accepted by vine_dist(). These margins retain their
distribution parameter counts because the supplied parameters may have
been estimated before constructing the vine distribution.
fixed_model <- vine_dist(
margins = list(
stats_margin("norm", mean = 0, sd = 1),
stats_margin("lnorm", meanlog = 0, sdlog = 0.5)
),
pair_copulas = list(list(bicop_dist())),
structure = dvine_structure(1:2)
)
rvine(3, fixed_model)
#> [,1] [,2]
#> [1,] -0.4671167 1.8678497
#> [2,] 1.5498242 1.4452552
#> [3,] 0.3289217 0.5503876Fitted margins, including callback-based margin_dist()
objects, are stored in the vine model. Standard R serialization
therefore preserves the complete model:
Related documentation
-
Getting started introduces the
complete
vine()modeling workflow. - Discrete, mixed, and zero-inflated data derives the likelihood contributions and documents copula-scale layouts.
- The
vine()reference, margin-family constructor, fitted-margin protocol, and margin-family protocol give complete API contracts.