Copulas¶
copula
¶
Copulas: the dependence between variables, separated from their margins.
Sklar's theorem says any joint distribution splits into its marginal distributions and a copula holding the dependence, and that the split is unique for continuous margins. That is what makes the copula the right object here: the question of whether two heavy-tailed series crash together is a question about their copula, and it survives any monotone rescaling of either series.
Correlation does not answer it. The four copulas below can be given the same rank correlation and still disagree completely about joint extremes:
=================== ============== ============== ============================
Copula Lower tail Upper tail
=================== ============== ============== ============================
:class:Gaussian 0 0 No joint extremes at any
correlation below one
:class:StudentT positive positive Equal, and positive even at
zero correlation
:class:Gumbel 0 2 - 2**(1/theta) Upper only
:class:Galambos 0 2 ** (-1/theta) Upper only
=================== ============== ============== ============================
The Gaussian row is the one that has caused trouble in practice. Two variables joined by a Gaussian copula with correlation 0.95 are still asymptotically independent in the tail: condition on one being extreme and the probability the other is too goes to zero. A t copula with the same correlation does not behave that way, and neither do markets.
What is not here: vine copulas, which need pair-copula construction and
structure selection and are a subsystem rather than a class (#306 keeps them).
The distribution functions of the elliptical copulas are also absent in closed
form, for the reason given in :mod:heavytails.multivariate -- there is a
Monte Carlo estimate with a standard error instead.
GalambosCopula
dataclass
¶
Bases: Copula
Galambos copula: upper tail dependence, none in the lower tail.
Also an extreme-value copula, and included because it is not Archimedean -- so it makes a different shape of upper dependence available at the same coefficient, and shows that the coefficient does not pin the copula down.
theta -> 0 is independence; larger is stronger dependence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theta
|
float
|
Dependence parameter, positive. |
required |
Raises:
| Type | Description |
|---|---|
ParameterError
|
If |
Examples:
>>> copula = GalambosCopula(theta=1.0)
>>> round(copula.cdf([0.5, 0.5]), 6)
0.353553
>>> {k: round(v, 6) for k, v in copula.tail_dependence().items()}
{'lower': 0.0, 'upper': 0.5}
cdf
¶
u v exp([(-ln u)^-theta + (-ln v)^-theta]^(-1/theta)).
pdf
¶
Copula density, checked against a finite difference of the cdf.
Source code in heavytails/copula.py
rvs
¶
Draw n points, by inverting the conditional distribution.
The Galambos copula is not Archimedean, so there is no mixing-variable construction to sample from. Conditional inversion is numerical and correspondingly slower, which is the price of the extra flexibility.
Source code in heavytails/copula.py
GaussianCopula
dataclass
¶
Bases: Copula
The copula of a multivariate normal.
Present largely as the cautionary case. Its tail dependence is zero at every correlation short of one: condition on one component being extreme and the probability that another is too tends to zero. Fitting a Gaussian copula to data whose extremes do arrive together will reproduce the correlation faithfully and understate the joint risk without any sign that it has done so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
correlation
|
list[list[float]]
|
Correlation matrix, symmetric positive definite with unit diagonal. |
required |
Raises:
| Type | Description |
|---|---|
ParameterError
|
If the matrix is not a valid correlation matrix. |
Examples:
>>> copula = GaussianCopula([[1.0, 0.7], [0.7, 1.0]])
>>> copula.tail_dependence()
{'lower': 0.0, 'upper': 0.0}
>>> round(copula.pdf([0.5, 0.5]), 6)
1.40028
logpdf
¶
Log copula density.
The ratio of the joint normal density to the product of its margins, which is what remains once the marginal shape is divided out.
Source code in heavytails/copula.py
pdf
¶
rvs
¶
Draw n points, by transforming a normal sample to uniforms.
Source code in heavytails/copula.py
GumbelCopula
dataclass
¶
Bases: Copula
Gumbel-Hougaard copula: upper tail dependence, none in the lower tail.
An extreme-value copula, and the one that arises as the limit of maxima. Its asymmetry is the point: joint crashes without joint booms, which is what equity returns tend to look like once the sign convention is fixed.
theta = 1 is independence; larger is stronger dependence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theta
|
float
|
Dependence parameter, at least one. |
required |
Raises:
| Type | Description |
|---|---|
ParameterError
|
If |
Examples:
>>> copula = GumbelCopula(theta=2.0)
>>> round(copula.cdf([0.5, 0.5]), 6)
0.375214
>>> {k: round(v, 6) for k, v in copula.tail_dependence().items()}
{'lower': 0.0, 'upper': 0.585786}
cdf
¶
pdf
¶
Copula density, checked against a finite difference of the cdf.
Source code in heavytails/copula.py
rvs
¶
Draw n points, by the Marshall-Olkin construction.
A Gumbel copula is Archimedean with a positive stable mixing variable, so a draw is two exponentials divided by one stable variate. That is exact, unlike inverting the conditional distribution numerically, and it extends to any dimension.
Source code in heavytails/copula.py
tail_dependence
¶
StudentTCopula
dataclass
¶
Bases: Copula
The copula of a multivariate Student-t.
The workhorse for joint heavy tails, because its extremes arrive together and its dependence is symmetric between the tails. Unlike the Gaussian, the coefficient is positive even at zero correlation -- uncorrelated t components share the mixing variable, which is the market-wide shock in the usual reading.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nu
|
float
|
Degrees of freedom, positive. Smaller means heavier joint tails. |
required |
correlation
|
list[list[float]]
|
Correlation matrix. |
required |
Raises:
| Type | Description |
|---|---|
ParameterError
|
If |
Examples:
>>> copula = StudentTCopula(nu=4.0, correlation=[[1.0, 0.0], [0.0, 1.0]])
>>> round(copula.tail_dependence()["upper"], 6)
0.075587
Zero correlation, and the extremes still arrive together.
logpdf
¶
Log copula density.
Source code in heavytails/copula.py
pdf
¶
rvs
¶
Draw n points, by transforming a t sample to uniforms.
Source code in heavytails/copula.py
tail_dependence
¶
Equal in both tails, and positive at any correlation above -1.
Uses the pairwise correlation of the first two components, since the coefficient is a property of a pair rather than of a matrix.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the copula has only one component. |
Source code in heavytails/copula.py
empirical_tail_dependence
¶
Estimate the tail dependence coefficients from a paired sample.
Ranks both series, then counts how often both exceed the level
quantile. Working on ranks is what makes this a statement about the copula:
it is unchanged by any monotone rescaling of either series, so it does not
care what the margins are.
The estimate is biased upwards, badly, and no level available in practice removes it. The coefficient is a limit as the level goes to one; at any finite level the conditional probability sits above it. How far above is easy to underestimate, so here it is measured on 400,000 draws from a Gaussian copula with correlation 0.7, whose true coefficient is exactly zero:
======== ========== ============ level estimate exceedances ======== ========== ============ 0.90 0.4655 40000 0.95 0.3871 20000 0.99 0.2580 4000 0.995 0.2195 2000 0.999 0.1650 400 0.9995 0.1450 200 ======== ========== ============
It falls, slowly, and never reaches zero at any level the data supports. This estimator cannot tell asymptotic independence from moderate tail dependence, and reading 0.26 off a sample as evidence of joint extremes would be a mistake at any sample size. It is useful for comparing two datasets at the same level, and for rejecting strong dependence; it is not useful for establishing that dependence exists.
The exceedance counts are returned for that reason: they are what decides whether raising the level buys anything.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Sequence[float]
|
First series. |
required |
y
|
Sequence[float]
|
Second series, the same length. |
required |
level
|
float
|
Quantile defining "extreme", in (0, 1). |
0.95
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
|
dict[str, Any]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the series differ in length, are too short for the level, or the level is outside (0, 1). |
Examples:
>>> copula = StudentTCopula(nu=3.0, correlation=[[1.0, 0.5], [0.5, 1.0]])
>>> draws = copula.rvs(20000, seed=1)
>>> result = empirical_tail_dependence(
... [d[0] for d in draws], [d[1] for d in draws], level=0.98
... )
>>> 0.2 < result["upper"] < 0.6
True
Source code in heavytails/copula.py
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 | |