A correlation coefficient answers
one question: how much do two variables move together on average. It is
silent on a different question that often matters more: do they move
together in the extremes. Two variables can share a modest correlation
and still spike together in their upper tails, or be nearly independent
in the body and coupled only when both are large. A copula separates
that dependence structure from the marginal shapes and lets it be read
directly. This case study fits a bivariate copula to a real pair, the
daily maximum temperature and ozone concentration at a New York
monitoring station (the airquality data), and asks not only
how strong the dependence is but where in the joint distribution it
lives. The answer overturns what the raw scatter appears to show, which
is the lesson.
library(gpumetropolis)
aq <- airquality[stats::complete.cases(airquality[, c("Temp", "Ozone")]), ]
x <- aq$Temp
y <- aq$Ozone
plot(x, y, pch = 16, col = grDevices::adjustcolor("black", 0.5),
xlab = "temperature (F)", ylab = "ozone (ppb)",
main = "Ozone against temperature, New York, 1973")The scatter suggests both a positive trend and a fan: the hottest days carry not just higher ozone but a wider and higher spread. The eye reads that fan as a hint of upper-tail dependence, extremes travelling together at the top. That reading is a hypothesis, and the copula is what tests it; the answer below is not the one the scatter suggests, which is the point of the exercise.
The copula lives on the unit square, and the data enter it as
pseudo-observations: each value is replaced by its rank divided by
n + 1, which maps the marginal to a uniform and leaves only
the dependence. gpum_copula() does this internally; here is
the picture it works from.
u <- rank(x) / (length(x) + 1)
v <- rank(y) / (length(y) + 1)
plot(u, v, pch = 16, col = grDevices::adjustcolor("black", 0.5),
xlab = "u = rank(temperature)", ylab = "v = rank(ozone)",
main = "Pseudo-observations on the unit square")The points look denser toward the upper-right corner than the lower-left, which would be upper-tail dependence. Whether that apparent asymmetry is real dependence or an artefact of the eye is what the formal comparison settles.
Rather than assume a family, family = "auto" fits all
four candidates and selects the one preferred by predictive comparison
(WAIC on the copula density, which is a proper density on the unit
square). The choice of family is itself a hypothesis about tail
behaviour, so letting the data choose is part of the answer.
fit <- gpum_copula(data.frame(temp = x, ozone = y), family = "auto",
n_iter = 12000, n_chains = 8, seed = 1, backend = "cpu")
fit
#> <gpum_copula>
#> selected family : frank (by WAIC)
#> theta : 7.038 [5.507, 8.637]
#> Kendall's tau : 0.561 [0.487, 0.625] (empirical 0.586)
#> tail dependence : lower 0.000, upper 0.000
#> diagnostic : R-hat 1.000 (converged); empirical tau inside the 95% band (adequate fit)
#> family comparison:
#> family criterion value delta converged
#> frank WAIC -95.79 0.000 TRUE
#> gaussian WAIC -88.78 7.002 TRUE
#> gumbel WAIC -87.66 8.121 TRUE
#> clayton WAIC -60.69 35.094 TRUEThe printed object is the technical diagnostic: the selected family, the dependence parameter and Kendall’s tau with their credible intervals, the tail-dependence coefficients, and a verdict line that reports convergence (R-hat) and goodness of fit (whether the empirical Kendall tau falls inside the model’s posterior band). The comparison table ranks the four families by WAIC.
The data select Frank, not Gumbel. Frank is a symmetric, tail-independent copula: strong overall dependence (Kendall’s tau near 0.56) with no concentration in either tail. The upper-tail hint the scatter suggested did not survive the comparison; the Gumbel family, which would impose upper-tail dependence, ranks below Frank on WAIC. The fan in the raw scatter was heteroscedasticity of the ozone margin, higher variance at high temperature, not a coupling of the extremes. This is the discipline the copula enforces: the dependence structure is read after the marginal shapes are removed by the ranks, and the eye’s reading of the raw scatter, which mixes the two, is overruled.
Kendall’s tau is the family-free summary, comparable across copulas; the tail-dependence coefficients are the extreme-co-movement probabilities.
kendall_tau(fit)
#> mean q2.5 median q97.5
#> 0.5614716 0.4871316 0.5633997 0.6249870
tail_dependence(fit)
#> $lower
#> mean q2.5 median q97.5
#> 0 0 0 0
#>
#> $upper
#> mean q2.5 median q97.5
#> 0 0 0 0Both tail-dependence coefficients are zero, the signature of the Frank family: temperature and ozone are strongly associated through the body of the distribution, but their extremes are asymptotically independent. Knowing that today is among the hottest on record does not, in the tail limit, make a record ozone reading more likely than the overall association already implies. That is a materially different risk statement from the upper-tail dependence the scatter suggested, and it is the copula, not the eye, that is entitled to make it.
The plot() method is the visual companion of the printed
diagnostic: the fitted density contours over the pseudo-observations,
the posteriors of the parameter and of Kendall’s tau against the
empirical value, and the observed-against-generated check on the unit
square.
The Frank contours are symmetric about the anti-diagonal, no corner favoured, the posterior of tau sits on the empirical mark, and the generated blue cloud overlays the observed black one. That overlay is the confirmation that a symmetric, tail-independent copula reproduces the observed dependence; had the upper tail been genuinely coupled, the generated cloud would fall short of the observed points in the upper-right corner.
The copula package fits the same families by maximum
pseudo-likelihood. The Bernstein-von Mises theorem says the Bayesian
posterior mean and the maximum-likelihood estimate must agree for a
regular model at this sample size; the check confirms the fit is not an
artefact of the sampler.
if (requireNamespace("copula", quietly = TRUE)) {
cop <- switch(fit$family,
gumbel = copula::gumbelCopula(), clayton = copula::claytonCopula(),
frank = copula::frankCopula(), gaussian = copula::normalCopula())
ml <- copula::fitCopula(cop, cbind(u, v), method = "mpl")
cat(sprintf("family %s: posterior mean %.3f vs pseudo-MLE %.3f\n",
fit$family, fit$param["mean"], as.numeric(coef(ml))))
cat(sprintf("Kendall tau: posterior %.3f vs empirical %.3f\n",
fit$tau["mean"], fit$tau_empirical))
}
#> family frank: posterior mean 7.038 vs pseudo-MLE 7.131
#> Kendall tau: posterior 0.561 vs empirical 0.586The scatter argued for upper-tail dependence, which is the Gumbel family. Fitting Gumbel by force shows what imposing that hypothesis would claim, and the comparison of step 2 already recorded that it fits worse. Read the two side by side: Gumbel would assert a positive upper tail-dependence coefficient the data do not support, at a worse WAIC, while agreeing with Frank on the overall strength.
fit_gumbel <- gpum_copula(data.frame(temp = x, ozone = y),
family = "gumbel", n_iter = 8000, n_chains = 8,
seed = 1, backend = "cpu")
rbind(
selected_frank = c(tau = fit$tau["mean"],
tail_upper = fit$tail_upper["mean"]),
forced_gumbel = c(tau = fit_gumbel$tau["mean"],
tail_upper = fit_gumbel$tail_upper["mean"])
)
#> tau.mean tail_upper.mean
#> selected_frank 0.5614716 0.0000000
#> forced_gumbel 0.5295618 0.6140167The two families agree on the overall strength of dependence and disagree on where it lives: Frank places none in the tails, Gumbel would place a substantial share in the upper tail. Choosing the family is choosing a hypothesis about the extremes, and the copula workflow makes that choice an inference the data adjudicate rather than an assumption the analyst imports.
The workflow here is bivariate. Three or more variables need the vine decomposition of a later release, which factors a high-dimensional dependence into a cascade of bivariate copulas, each of the kind fit here. The marginal distributions were handled non-parametrically through the ranks; the companion release on marginal auto-selection fits an explicit distribution to each column so the two halves of Sklar’s theorem, margins and copula, are both modelled. And a caveat of method: heavy ties in either variable blur the pseudo-observations and the copula’s identifiability (Genest and Neslehova 2007); the ozone and temperature readings here are near-continuous, so the ranks are clean.