Copula likelihoods for variables with atoms require two probabilities
at every observation: the CDF value F(x) and its left limit
F(x-). For an integer-valued variable,
F(x-) = F(x - 1). Continuous variables satisfy
F(x-) = F(x).
The high-level vine() interface computes these values
from fitted margins. The copula-only bicop() and
vinecop() interfaces accept them explicitly.
All ordinary discrete variables are assumed to have integer support.
Declare a numeric or integer column through
var_types = "d".
n <- 100
latent <- rnorm(n)
x <- data.frame(
value = latent + rnorm(n),
count = rpois(n, exp(0.5 + 0.3 * latent))
)
fit <- vine(
x,
var_types = c("c", "d"),
copula_controls = list(family_set = "onepar")
)
summary(fit)$margins
#> # A data.frame: 2 x 8
#> margin name family type xmin xmax npars loglik
#> 1 value kde1d c -Inf Inf 5.5 -170
#> 2 count kde1d d -Inf Inf 3.9 -157
dvine(x[1:5, ], fit)
#> [1] 0.084671851 0.070090196 0.005292868 0.023579072 0.095144184Explicitly discrete columns are checked for finite integer-valued observations. This catches accidental declarations of rounded-looking continuous data.
Ordered factors are discrete variables whose levels are mapped
internally to the integer support 0, 1, .... Their class
and levels are restored after simulation.
survey <- data.frame(
rating = ordered(
sample(c("low", "middle", "high"), n, replace = TRUE),
levels = c("low", "middle", "high")
),
score = rnorm(n)
)
ordered_fit <- vine(
survey,
copula_controls = list(family_set = "indep")
)
ordered_draws <- rvine(5, ordered_fit)
str(ordered_draws)
#> 'data.frame': 5 obs. of 2 variables:
#> $ rating: Ord.factor w/ 3 levels "low"<"middle"<..: 3 3 1 3 1
#> $ score : num -2.83595 -0.00385 -0.99217 -0.23827 0.62289No var_types entry is needed because
ordered() carries the declaration in the column class.
zero_inflated() marks a continuous variable with an atom
at zero. It behaves like a numeric vector in a data frame and survives
ordinary subsetting.
amount <- zero_inflated(c(rep(0, 25), rexp(75)))
zi_data <- data.frame(amount = amount, score = rnorm(100))
inherits(zi_data$amount, "zero_inflated")
#> [1] TRUE
zi_fit <- vine(
zi_data,
copula_controls = list(family_set = "indep")
)
#> Warning: margin selection for variable 1 reported problems (kde1d has no finite
#> parameter count; AIC and BIC are unavailable).
summary(zi_fit)$margins[, c("name", "type")]
#> # A data.frame: 2 x 2
#> name type
#> amount zi
#> score cThe atom is always located at zero. A custom zero-inflated margin
must return the atom probability from dmargin(0, margin).
rvinecopulib then computes the left limit as
pmargin(0, margin) - dmargin(0, margin)
and uses the ordinary CDF away from zero. Alternatively, declare the
type with var_types = "zi".
Let d be the dimension and k the number of
discrete variables. Copula methods accept two layouts:
2d columns, with all
F(x) columns followed by all F(x-)
columns;d + k columns, with the
F(x) block followed only by left-limit columns for discrete
variables, in variable order.Consider a three-dimensional model with discrete variables 1 and 3.
n <- 80
raw <- data.frame(
first = rpois(n, 2),
middle = rnorm(n),
last = rpois(n, 4)
)
values <- cbind(
ppois(raw$first, 2),
pnorm(raw$middle),
ppois(raw$last, 4)
)
left_discrete <- cbind(
ppois(raw$first - 1, 2),
ppois(raw$last - 1, 4)
)
compact <- cbind(values, left_discrete)
left_all <- cbind(
ppois(raw$first - 1, 2),
pnorm(raw$middle),
ppois(raw$last - 1, 4)
)
expanded <- cbind(values, left_all)
copula_fit <- vinecop(
compact,
var_types = c("d", "c", "d"),
family_set = "indep"
)
stopifnot(isTRUE(all.equal(
dvinecop(compact, copula_fit),
dvinecop(expanded, copula_fit)
)))For a mixed bivariate model, this means three columns: the two CDF values and the left limit of the discrete variable. Fully discrete bivariate models need four columns.
dbicop() and dvinecop() return the copula
density contribution defined as the joint density or mass divided by the
marginal densities or masses. This definition is consistent across
continuous, discrete, and mixed models.
For a bivariate model, write \[
u_j = F_j(x_j), \qquad u_j^- = F_j(x_j^-), \qquad
\Delta_j = u_j - u_j^-.
\] If both variables are discrete, the copula contribution is the
rectangle probability divided by the two marginal masses: \[
c^*(u_1,u_2) =
\frac{C(u_1,u_2)-C(u_1^-,u_2)-C(u_1,u_2^-)+C(u_1^-,u_2^-)}
{\Delta_1\Delta_2}.
\] If only the first variable is discrete, the corresponding
mixed contribution is \[
c^*(u_1,u_2) =
\frac{\partial_2 C(u_1,u_2)-\partial_2 C(u_1^-,u_2)}{\Delta_1}.
\] The continuous density c(u1, u2) is recovered
when both jump widths vanish. Vine likelihoods apply these continuous,
mixed, or rectangle contributions edge by edge to the appropriate
conditional CDF values.
dvine() multiplies that contribution by the fitted
marginal densities or masses and therefore returns the full joint
density or probability mass on the original scale. In general, \[
f_X(x) = c^*(u,u^-)\prod_{j=1}^d f_j(x_j),
\] where each f_j denotes a density or a probability
mass as appropriate.
rvinecop() always returns one uniform-scale value per
variable. It does not invent a marginal distribution for a discrete
copula variable. Use rvine() when simulated values should
be returned on the original scale with their integer, ordered, or
zero-inflated margins.
For an atom, the Rosenblatt transform is an interval rather than a
single value. By default, rosenblatt() randomizes uniformly
within that interval so the transformed variable is uniform under a
correct model. Set randomize_discrete = FALSE to return the
deterministic upper endpoint.
More precisely, if G_j(· | x_<j) is the relevant
conditional CDF and V_j is an independent standard uniform
variable, the randomized component is \[
Z_j = G_j(x_j^- \mid x_{<j})
+ V_j\{G_j(x_j \mid x_{<j})-G_j(x_j^- \mid x_{<j})\}.
\] The non-randomized version returns the upper endpoint
G_j(x_j | x_<j).
set.seed(402)
randomized <- rosenblatt(compact, copula_fit)
upper_endpoint <- rosenblatt(
compact,
copula_fit,
randomize_discrete = FALSE
)
head(randomized)
#> [,1] [,2] [,3]
#> [1,] 0.05289451 0.0958401 0.7055710
#> [2,] 0.33647905 0.7438199 0.7051919
#> [3,] 0.74612047 0.1121317 0.2203118
#> [4,] 0.13426465 0.5942987 0.1240232
#> [5,] 0.73703700 0.6269100 0.8771097
#> [6,] 0.51260896 0.1971273 0.7066150
head(upper_endpoint)
#> [,1] [,2] [,3]
#> [1,] 0.1353353 0.0958401 0.7851304
#> [2,] 0.4060058 0.7438199 0.7851304
#> [3,] 0.8571235 0.1121317 0.2381033
#> [4,] 0.1353353 0.5942987 0.2381033
#> [5,] 0.8571235 0.6269100 0.8893260
#> [6,] 0.6766764 0.1971273 0.7851304The randomization uses R’s random-number state and is reproducible
with set.seed().
Copula fitting discards incomplete observations pair by pair,
retaining the maximum information available for each edge. Margin
implementations decide how to handle missing values during marginal
fitting; built-in KDE margins support the package’s established
missing-data workflow. Custom margin_family() fitters
should either handle missing values or fail with a useful message.