Skip to content

Plots

rcopula.plots

Diagnostic plots for copulas.

Numbers alone are a poor way to judge a dependence model. A fitted parameter and a goodness-of-fit p-value tell you whether a family fits; these tell you where it does not, which is the part that decides whether the failure matters. A copula that misses the middle of the distribution is usually harmless; one that misses the corner is not.

Each one answers a question the others cannot.

============================= =============================================== :func:contour What the density or CDF actually looks like. :func:surface The same in three dimensions (R's persp). :func:scatter_matrix Every pair at once, on the copula scale. :func:tail_concentration Fitted against empirical, in the corners. :func:kendall_plot A dependence diagnostic needing no fitted model. :func:pickands_plot The Pickands function inside its bounds. :func:vine_trees A vine's trees, edge by edge. :func:nested_tree A nested copula's hierarchy. :func:dependence_heatmap A pairwise matrix, when one number will not do. :func:dependogram_plot Which subsets of variables are dependent. :func:pairs_rosenblatt Where a fitted model fails, pair by pair. ============================= ===============================================

:func:tail_concentration is the one to reach for after fitting. Two families calibrated to the same Kendall's tau agree almost everywhere and separate exactly where the risk is, so a plot that puts the empirical corner behaviour next to the fitted one shows the disagreement that a single summary statistic averages away.

Every function takes an optional ax (or axes for the multi-panel ones) and returns the axes it drew on, so the plots compose into whatever figure you are building. Nothing is shown or saved automatically: call matplotlib.pyplot.show() or ax.figure.savefig(...) yourself. matplotlib is imported only when a plot is drawn.

References

Genest, C. and Boies, J.-C. (2003). Detecting dependence with Kendall plots. The American Statistician 57(4), 275-284. Venter, G. (2002). Tails of copulas. Proceedings of the Casualty Actuarial Society 89, 68-113. The tail concentration function. Fisher, N. I. and Switzer, P. (2001). Graphical assessment of dependence: is a picture worth 100 tests? The American Statistician 55(3), 233-239.

contour

contour(copula: Copula, kind: str = 'pdf', n: int = _GRID, margin: float = 0.01, ax: Any = None, **kwargs: Any) -> Any

Draw a contour map of a two-variable copula, like the height lines on a terrain map.

Shows where the copula puts its probability: tight contours in a corner mean the two variables tend to be extreme together there. Use it to see the shape of a family or of a fitted model at a glance.

Parameters:

Name Type Description Default
copula Copula

Bivariate (dim == 2), with parameters specified.

required
kind ('pdf', 'logpdf', 'cdf')

What to draw: the density, its logarithm, or the distribution function. "logpdf" is usually the readable one for a tail-dependent family, whose density spans many orders of magnitude and whose linear contours therefore all crowd into one corner.

"pdf"
n int

Grid points per axis.

60
margin float

How far to stay off the boundary, where a tail-dependent density diverges. The grid spans [margin, 1 - margin] on each axis.

0.01
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
**kwargs Any

Passed to matplotlib.axes.Axes.contour. levels defaults to 14.

{}

Returns:

Type Description
Axes

The axes drawn on, labelled u1 and u2.

Raises:

Type Description
ValueError

If the copula is not bivariate, kind is not one of the three options, or no grid value is finite (for example an unspecified parameter). Individual non-finite values -- a logpdf of -inf where the density underflows -- are drawn at the lowest finite level.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import ClaytonCopula
>>> from rcopula.plots import contour
>>> ax = contour(ClaytonCopula(3.0), kind="logpdf")
>>> ax.get_xlabel()
'u1'

surface

surface(copula: Copula, kind: str = 'pdf', n: int = _GRID, margin: float = 0.02, ax: Any = None, **kwargs: Any) -> Any

Draw a two-variable copula's density or CDF as a 3-D surface (R's persp).

The same information as :func:contour, shown as a landscape whose height is the density (or CDF) above each point of the unit square.

Parameters:

Name Type Description Default
copula Copula

Bivariate (dim == 2), with parameters specified.

required
kind ('pdf', 'logpdf', 'cdf')

What to draw: the density, its logarithm, or the distribution function.

"pdf"
n int

Grid points per axis.

60
margin float

How far to stay off the boundary, where a tail-dependent density diverges.

0.02
ax Axes3D or None

A 3-D axes to draw on (created with projection="3d"). None creates a new figure with one.

None
**kwargs Any

Passed to Axes3D.plot_surface. cmap defaults to "viridis" and linewidth to 0.

{}

Returns:

Type Description
Axes3D

The 3-D axes drawn on; the z-axis is labelled with kind.

Raises:

Type Description
ValueError

If the copula is not bivariate or kind is not one of the three options.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import FrankCopula
>>> from rcopula.plots import surface
>>> ax = surface(FrankCopula(5.0), kind="cdf")
>>> ax.get_zlabel()
'cdf'

scatter_matrix

scatter_matrix(data: ArrayLike, names: list[str] | None = None, axes: Any = None, **kwargs: Any) -> Any

Plot every pair of variables against each other after converting them to ranks.

A grid of panels: scatter plots below the diagonal, histograms on it, and the pair's Kendall's tau above it. Ranks are taken first, so the panels show dependence and nothing else -- the marginal shapes that dominate a raw scatter plot are removed. Two variables whose raw plot is an uninformative smear can show a clean pattern here.

Parameters:

Name Type Description Default
data array_like of float, shape (n, d)

Observations on any scale; they are converted to pseudo-observations (ranks scaled into (0, 1)) first.

required
names list of str or None

One label per column. None uses "u1", "u2", ....

None
axes array_like of matplotlib.axes.Axes, shape (d, d), or None

Grid of axes to draw into, indexed axes[i][j]. None creates a new figure.

None
**kwargs Any

Passed to matplotlib.axes.Axes.scatter for the lower-triangle panels. s defaults to 4 and alpha to 0.4.

{}

Returns:

Type Description
numpy.ndarray of matplotlib.axes.Axes, shape (d, d)

The grid of axes (or the axes you passed in).

Raises:

Type Description
ValueError

If names does not have one entry per column.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import ClaytonCopula
>>> from rcopula.plots import scatter_matrix
>>> u = ClaytonCopula(3.0, dim=3).rvs(300, random_state=0)
>>> grid = scatter_matrix(u, names=["a", "b", "c"])
>>> grid.shape
(3, 3)

tail_concentration

tail_concentration(data: ArrayLike | None = None, copula: Copula | list[Copula] | None = None, n: int = 99, ax: Any = None, *, copula_kwargs: dict[str, Any] | None = None, **kwargs: Any) -> Any

Compare how often data and fitted copulas are jointly extreme, out to each corner.

Draws the tail concentration function for the data (solid black) and for each copula (dashed). The left half of the plot describes the lower tail (both variables small), the right half the upper tail (both large). This is the plot to look at after fitting. The two branches are

.. math:: L(q) = \frac{C(q, q)}{q}\ \ (q \le \tfrac12), \qquad R(q) = \frac{1 - 2q + C(q, q)}{1 - q}\ \ (q > \tfrac12),

which tend to :math:\lambda_L and :math:\lambda_U at the ends. Plotting them together makes visible the thing a single dependence measure hides: families calibrated to the same Kendall's tau agree in the middle and separate in the corners, and the corners are where the money is.

Parameters:

Name Type Description Default
data array_like of float, shape (n, 2), or None

Observations on any scale (converted to ranks), plotted as the empirical curve.

None
copula Copula, list of Copula, or None

One or more bivariate copulas, plotted as reference curves. At least one of data and copula must be given.

None
n int

Number of evaluation points q, equally spaced in (0, 1).

99
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
copula_kwargs dict or None

Keyword-only. Passed to matplotlib.axes.Axes.plot for every copula curve, overriding their defaults (linestyle="--" and label=<family name>). For example {"linewidth": 2}.

None
**kwargs Any

Passed to matplotlib.axes.Axes.plot for the empirical curve only, overriding its defaults (color="black", label="empirical"). Use copula_kwargs to style the copula curves.

{}

Returns:

Type Description
Axes

The axes drawn on, with a legend.

Raises:

Type Description
ValueError

If neither data nor copula is given, or if the data or any copula is not bivariate.

Examples:

Two families at the same tau, and the picture that distinguishes them:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import ClaytonCopula, GumbelCopula
>>> from rcopula.plots import tail_concentration
>>> u = ClaytonCopula.from_tau(0.5).rvs(2000, random_state=0)
>>> ax = tail_concentration(u, [ClaytonCopula.from_tau(0.5), GumbelCopula.from_tau(0.5)])
>>> ax.get_ylabel()
'concentration'

kendall_plot

kendall_plot(data: ArrayLike, ax: Any = None, **kwargs: Any) -> Any

Draw a Kendall plot (K-plot): a quick visual test of whether two variables are dependent.

Points on the diagonal mean independence; points above it mean positive dependence, below it negative. No model has to be fitted first.

Genest & Boies (2003) plot the ordered statistics :math:W_{i} of the empirical Kendall function against their expected values under independence. Points on the diagonal mean independence; bowing above it means positive dependence, below means negative -- and the shape of the departure carries information a scalar correlation does not.

Its appeal is that it needs neither margins nor a fitted family: it is a function of the ranks alone, so it can be looked at before any modelling decision has been made.

Parameters:

Name Type Description Default
data array_like of float, shape (n, d)

Observations on any scale; only ranks are used.

required
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
**kwargs Any

Passed to matplotlib.axes.Axes.scatter. s defaults to 8.

{}

Returns:

Type Description
Axes

The axes drawn on: expected values under independence on the x-axis, ordered empirical Kendall values W on the y-axis.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import ClaytonCopula
>>> from rcopula.plots import kendall_plot
>>> u = ClaytonCopula(3.0).rvs(300, random_state=0)
>>> ax = kendall_plot(u)
>>> ax.get_xlabel()
'expected under independence'

pickands_plot

pickands_plot(copula: Copula | list[Copula], n: int = 201, ax: Any = None, **kwargs: Any) -> Any

Plot the Pickands function of extreme-value copulas, showing their strength and symmetry.

The Pickands function :math:A(t) is the curve that fully describes an extreme-value copula. Every extreme-value copula is determined by a convex :math:A with :math:\max(t, 1-t) \le A(t) \le 1. The upper edge is independence and the lower one comonotonicity, so the plot places a family between those two extremes and shows at a glance how much dependence it carries and whether it is symmetric -- which is the property Khoudraji's device exists to break.

Parameters:

Name Type Description Default
copula Copula or list of Copula

One or more bivariate extreme-value copulas: an :class:~rcopula.core.extreme_value.ExtremeValueCopula, a Gumbel copula, or an extreme-value :class:~rcopula.structural.KhoudrajiCopula.

required
n int

Number of points t in [0, 1].

201
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
**kwargs Any

Passed to matplotlib.axes.Axes.plot for each copula's curve.

{}

Returns:

Type Description
Axes

The axes drawn on, with the admissible bounds in grey and a legend.

Raises:

Type Description
ValueError

If a copula is not extreme-value.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> from rcopula import GalambosCopula, GumbelCopula
>>> from rcopula.plots import pickands_plot
>>> ax = pickands_plot([GumbelCopula(2.0), GalambosCopula(1.5)])
>>> ax.get_ylabel()
'A(t)'

vine_trees

vine_trees(vine: Any, max_trees: int | None = None, axes: Any = None, **kwargs: Any) -> Any

Draw each tree of a vine copula, labelling every node and edge.

A vine is a sequence of trees, and the thing a reader needs to see is which pairs each tree joins, what it conditions on, and which family was selected there. Tree 1 carries most of the dependence and is where a misspecified family costs most; the higher trees are usually where a fit is spending parameters on noise, which this makes visible at a glance.

Parameters:

Name Type Description Default
vine VineCopula

A fitted or specified vine.

required
max_trees int or None

Draw only the first max_trees trees. None draws all of them.

None
axes sequence of matplotlib.axes.Axes or None

One axes per tree to draw. None creates a new figure with one panel per tree in a row.

None
**kwargs Any

Passed to matplotlib.axes.Axes.plot for every edge line, overriding the defaults (color="0.6", linewidth=1.0, zorder=1).

{}

Returns:

Type Description
numpy.ndarray of matplotlib.axes.Axes, shape (n_trees,)

One axes per drawn tree (or the axes you passed in).

Notes

This is the standard vine picture: the nodes of tree k + 1 are the edges of tree k. Labels use the variables' column indices (the vine's order is applied):

  • tree 1's nodes are the variables, "0", "1", ...;
  • a node of a later tree is labelled by the edge it stands for, as "a,b" (from tree 1) or "a,b|c,d" -- the conditioned pair, then the conditioning set;
  • every edge is labelled with its pair-copula's family name and parameters.

Nodes are placed on a circle.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import rcopula as rc
>>> from rcopula.plots import vine_trees
>>> vine = rc.VineCopula(
...     [[rc.ClaytonCopula(3.0), rc.GumbelCopula(2.5)], [rc.FrankCopula(4.0)]],
...     structure="D",
... )
>>> grid = vine_trees(vine)
>>> len(grid)
2
>>> sorted(t.get_text() for t in grid[1].texts if t.get_text()[0].isdigit())
['0,1', '1,2']

nested_tree

nested_tree(node: Any, ax: Any = None, *, leaf_kwargs: dict[str, Any] | None = None, **kwargs: Any) -> Any

Draw a nested Archimedean copula as a tree diagram, with each node's parameter and tau.

The whole point of nesting is that dependence varies by branch, so the picture worth drawing is the tree with each node's parameter and Kendall's tau on it -- from which the pairwise tau of any two leaves can be read off directly, since two variables meet at exactly one node.

Parameters:

Name Type Description Default
node NestedArchimedean

The root of the tree, with every parameter specified.

required
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
leaf_kwargs dict or None

Keyword-only. Passed to matplotlib.axes.Axes.scatter for the leaf markers (the variables), overriding their defaults (s=220, color="white", edgecolor="0.4", zorder=3).

None
**kwargs Any

Passed to matplotlib.axes.Axes.scatter for the internal-node markers only (the Archimedean generators), overriding their defaults (s=520, color="0.92", edgecolor="0.35", zorder=3). Use leaf_kwargs to style the leaves.

{}

Returns:

Type Description
Axes

The axes drawn on (axis lines hidden). Leaves are the variable indices; each internal node shows theta and its Kendall's tau.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import rcopula as rc
>>> from rcopula.plots import nested_tree
>>> from rcopula.structural import NestedArchimedean
>>> cop = NestedArchimedean(
...     rc.GumbelCopula(1.5),
...     children=[
...         NestedArchimedean(rc.GumbelCopula(4.0), [0, 1, 2]),
...         NestedArchimedean(rc.GumbelCopula(3.0), [3, 4]),
...     ],
... )
>>> ax = nested_tree(cop)
>>> ax.get_title()
'Nested Gumbel copula'

dependence_heatmap

dependence_heatmap(values: ArrayLike, names: list[str] | None = None, label: str = "Kendall's tau", ax: Any = None, **kwargs: Any) -> Any

Draw a colour-coded grid of a pairwise dependence matrix, with the numbers written in.

Useful precisely for the constructions whose whole point is that dependence is not one number: a nested copula's :meth:tau_matrix, or the empirical matrix from a sample.

Parameters:

Name Type Description Default
values array_like of float, shape (d, d)

Square matrix to show, e.g. cop.tau_matrix() or rcopula.cor_kendall(data).

required
names list of str or None

Tick labels, one per row/column. None uses "0", "1", ....

None
label str

Title of the plot.

"Kendall's tau"
ax Axes or None

Axes to draw on. None creates a new figure and axes.

None
**kwargs Any

Passed to matplotlib.axes.Axes.imshow. cmap defaults to "RdBu_r" with vmin=-1 and vmax=1.

{}

Returns:

Type Description
Axes

The axes drawn on.

Raises:

Type Description
ValueError

If values is not square.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import rcopula as rc
>>> from rcopula.plots import dependence_heatmap
>>> from rcopula.structural import NestedArchimedean
>>> cop = NestedArchimedean(
...     rc.GumbelCopula(1.5),
...     children=[
...         NestedArchimedean(rc.GumbelCopula(4.0), [0, 1, 2]),
...         NestedArchimedean(rc.GumbelCopula(3.0), [3, 4]),
...     ],
... )
>>> ax = dependence_heatmap(cop.tau_matrix())
>>> ax.get_title()
"Kendall's tau"

dependogram_plot

dependogram_plot(result: DependogramResult, *, level: float = 0.05, ax: Axes | None = None) -> Axes

Draw a bar chart of which groups of variables show dependence, from a dependogram test.

Plots the result of :func:~rcopula.htest.dependogram. One bar per subset, tallest first, with the subsets that reject independence marked. The point of the picture over the table is that it shows where the dependence lives at a glance: three variables can be pairwise independent and jointly dependent, and the bar for the triple stands alone.

Parameters:

Name Type Description Default
result DependogramResult

From :func:~rcopula.htest.dependogram.

required
level float

Keyword-only. Significance level: subsets with p-value below it are drawn in red.

0.05
ax Axes or None

Keyword-only. Axes to draw on. None creates a new figure and axes.

None

Returns:

Type Description
Axes

The axes drawn on: one bar per subset, height = Cramer-von Mises statistic, annotated with its p-value.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import numpy as np
>>> from rcopula.htest import dependogram
>>> from rcopula.plots import dependogram_plot
>>> rng = np.random.default_rng(0)
>>> x, y = rng.uniform(size=(2, 200))
>>> data = np.column_stack([x, y, (x + y) % 1.0])
>>> ax = dependogram_plot(dependogram(data, n_rep=100, random_state=1))
>>> ax.get_ylabel()
'Cramer-von Mises statistic'

pairs_rosenblatt

pairs_rosenblatt(copula: Copula, u: ArrayLike, *, level: float = 0.05, axes: Any = None) -> Any

Show, pair by pair, where a fitted copula fails to describe the data.

This is R's pairsRosenblatt. A goodness-of-fit test says whether a copula fits. This says where it does not.

Under the fitted copula the Rosenblatt transform produces independent uniforms, so every panel below should look like structureless noise on the unit square. Any panel that does not is a pair the model has failed to describe -- and unlike a single p-value, the picture names it.

Each panel is shaded by the p-value of an independence test on that pair, using the asymptotic normality of Kendall's tau,

.. math:: \sqrt{\tfrac{9n(n-1)}{2(2n+5)}}\;\hat\tau \;\to\; N(0, 1),

which is exact enough at these sample sizes and costs nothing next to a permutation test run :math:\binom{d}{2} times.

Parameters:

Name Type Description Default
copula Copula

The fitted model to be judged, of dimension d.

required
u array_like of float, shape (n, d)

Pseudo-observations (values in (0, 1), e.g. from :func:~rcopula.pseudo_obs).

required
level float

Keyword-only. Panels whose independence p-value is below this are shaded red.

0.05
axes array_like of matplotlib.axes.Axes, shape (d, d), or None

Keyword-only. Grid to draw into. None creates a new figure.

None

Returns:

Type Description
numpy.ndarray of matplotlib.axes.Axes, shape (d, d)

Scatter plots below the diagonal, p-value and tau text above it. The figure also gets a title and tight_layout.

Notes

Reading it: structure in the panel for :math:(i, j) implicates the dependence between coordinates :math:i and :math:j given the coordinates before them, because the Rosenblatt transform conditions successively. A misfit at :math:(1, 2) is a plain bivariate failure; one at :math:(3, 4) is a failure of conditional dependence, which is exactly what a vine's higher trees exist to capture.

Examples:

>>> import matplotlib
>>> matplotlib.use("Agg")
>>> import rcopula as rc
>>> from rcopula.plots import pairs_rosenblatt
>>> truth = rc.ClaytonCopula(3.0, dim=3)
>>> data = truth.rvs(600, random_state=0)
>>> grid = pairs_rosenblatt(truth, data)
>>> grid.shape
(3, 3)

Fitted with the wrong family, panels light up:

>>> wrong = pairs_rosenblatt(rc.GaussianCopula(0.7, dim=3, dispstr="ex"), data)
>>> wrong.shape
(3, 3)