---
title: "Marginal models"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Marginal models}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(rvinecopulib)
set.seed(11)
```

`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.

```{r default}
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
```

KDE controls belong to the family specification. This keeps `vine()` free of
method-specific options:

```{r configured-kde}
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:

```{r weighted-custom, eval=FALSE}
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.

```{r parametric, eval=requireNamespace("univariateML", quietly = TRUE)}
fit_parametric <- vine(
  x,
  margins_controls = list(
    family_set = c("norm", "cauchy", "gamma"),
    selcrit = "bic"
  ),
  copula_controls = list(family_set = "indep")
)
summary(fit_parametric)$margins
```

Available 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:

```{r per-variable, eval=FALSE}
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.

```{r custom-family}
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
```

The 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:

```{r nested, eval=FALSE}
margins_controls <- list(
  family_set = list(
    list("norm", normal_family),
    list("gamma", "weibull")
  ),
  selcrit = "aic"
)
```

## 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.

```{r protocol, eval=FALSE}
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.

```{r ordered-default}
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))
```

For 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"`.

```{r zero-inflated-data}
zero_data <- data.frame(
  claim = zero_inflated(c(rep(0, 20), rexp(60))),
  score = rnorm(80)
)
inherits(zero_data$claim, "zero_inflated")
```

A 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.

```{r fixed}
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)
```

Fitted margins, including callback-based `margin_dist()` objects, are stored in
the vine model. Standard R serialization therefore preserves the complete
model:

```{r persistence}
path <- tempfile(fileext = ".rds")
saveRDS(fit_custom, path)
restored <- readRDS(path)
unlink(path)
all.equal(dvine(x, restored), dvine(x, fit_custom))
```

## Related documentation

- [Getting started](getting-started.html) introduces the complete `vine()`
  modeling workflow.
- [Discrete, mixed, and zero-inflated data](discrete-data.html) derives the
  likelihood contributions and documents copula-scale layouts.
- The [`vine()` reference](../reference/vine.html), [margin-family
  constructor](../reference/margin_family.html), [fitted-margin
  protocol](../reference/margin_protocol.html), and [margin-family
  protocol](../reference/margin_family_protocol.html) give complete API
  contracts.
