1. home
  2. posts
  3. about
  4. github
  5. rss

Copula models in Python using sympy

Table of Contents


frank-pdf-3.png

Figure 1: Frank copula probability density function visualisation

Introduction

A copula is a cumulative distribution function with uniform marginals 1. Copulas are a popular statistical tool in risk management because they provide a method for capturing and characterising co-dependency between arbitrary random variables. Copula allow marginal distributions to be estimated independently, with dependency defined on-top of them.

SymPy is a Python library for symbolic mathematics 2. In this article, I use it to simplify differentiation i.e. I use SymPy just for its autodiff capabilities.

In this post, I share some Archimedean copula I have written in Python. The CDF and PDF are computed and visualised for bivariate models.


Tail dependence

The tail dependence of two random variables is a measure of their co-movements in the tails (extremes) of the distributions. Two random variables can be uncorrelated but still exhibit tail dependence. This is especially true in stock returns during moments of panic.

It is a stylized fact of stock returns that they commonly exhibit tail dependence. 3

Upper tail dependence

Upper tail dependence refers to co-movements in the upper tails of two random variables \(X_1\) and \(X_2\).

\begin{equation} \lambda_u = \lim{q \to 1} P(X_2 > F_2^{-1}(q) | X_1 > F_1^{-1}(q)) \end{equation}

Lower tail dependence

Lower tail dependence refers to co-movements in the lower tails of two random variables \(X_1\) and \(X_2\). Share prices tend to fall together at the same time rather than rise together. This is due to the aforementioned panic selling phenomenon.

\begin{equation} \lambda_l = \lim{q \to 0} P(X_2 \le F_2^{-1}(q) | X_1 \le F_1^{-1}(q)) \end{equation}

Base copula

An Archimedean copula is a type of copula function that is constructed using a so-called generator function. The generator function is a mathematical function that satisfies a set of conditions, including being continuous, strictly decreasing, and convex. Some examples of Archimedean generator functions include the Gumbel, Clayton, and Frank functions.

The following class implements a base Archimedean copula. It must be instantiated with a theta value \(\theta\) and a copula formula:

class Archimedean:
  def __init__(self, theta, copula):
    self._theta = theta
    self._c = copula

  def cdf(self, input_1, input_2):
    u1, u2, theta = sp.symbols("u1 u2 theta")

    phi1 = self._c.generator(u1, theta)
    phi2 = self._c.generator(u2, theta)
    expr = self._c.inverse(phi1 + phi2, theta)

    return expr.subs({"u1": input_1,
                        "u2": input_2,
                        "theta": self._theta})

  def pdf(self, input_1, input_2):
    u1, u2 = sp.symbols("u1 u2")
    expr = sp.diff(self.cdf(u1, u2), u1, u2)
    return expr.subs({"u1": input_1, "u2": input_2})

  def conditional_probability(self, input_1, input_2, wrt):
    u1, u2 = sp.symbols("u1 u2")

    if wrt == 1:
        expr = sp.diff(self.cdf(u1, u2), u1)
    elif wrt == 2:
        expr = sp.diff(self.cdf(u1, u2), u2)
    else:
        raise Exception("with respect to must be 1 or 2")

    return expr.subs({"u1": input_1, "u2": input_2})

  def reflected(self, input_1, input_2):
    u1, u2 = sp.symbols("u1 u2")
    expr = u1 + u2 - 1 + self.cdf(1 - u1, 1 - u2)
    return expr.subs({"u1": input_1, "u2": input_2})

Clayton

The Clayton copula is an Archimedean copula that emphasises lower tail dependency. The density function appears to be more spiky for larger values of theta 4.

\begin{gather} \phi(t; \theta) = \frac{t^{-\theta t} - 1} {\theta}, \theta \in [-1, +\inf] \setminus \{0\} \end{gather}
class Clayton:
  def generator(self, t, theta):
    return ((t ** -theta) - 1) / theta

  def inverse(self, t, theta):
    return ((theta * t) + 1) ** -(1 / theta)

clayton-pdf-1.png

Figure 2: Clayton PDF (1)

clayton-pdf-2.png

Figure 3: Clayton PDF (2)


Frank

The Frank copula is an Archimedean copula that emphasises both upper and lower tail dependency. The function is very smooth when theta is small e.g. when \(\theta \le 1\), and appears to stiffen as theta grows towards infinity 5.

\begin{gather} \phi(t; \theta) = -\log{\frac{\exp(-\theta t) - 1} {\exp(-\theta) - 1}}, \theta \in [-\inf, \inf) \setminus \{0\} \end{gather}
class Frank:
  def generator(self, t, theta):
    return -sp.log(((e ** -(theta * t)) - 1) / ((e ** -theta) - 1))

  def inverse(self, t, theta):
    return -(1 / theta) * sp.log((((e ** -theta) - 1) / (e ** t)) + 1)

frank-cdf-1.png

Figure 4: Frank CDF (1)

frank-cdf-2.png

Figure 5: Frank CDF (2)

frank-pdf-1.png

Figure 6: Frank PDF (1)

frank-pdf-2.png

Figure 7: Frank PDF (2)

frank-pdf-3.png

Figure 8: Frank PDF (3)


N14

The N14 copula is an Archimedean copula that puts emphasis on both lower and upper tail dependency; the emphasis is greater for upper dependency. Similar to Clayton: the density function appears to get spiky for larger theta 6

\begin{equation} \phi(t; \theta) = (t^{-1/\theta} - 1)^\theta, \theta \in [1, +\inf] \end{equation}
class N14:
  def generator(self, t, theta):
    return ((t ** (-1 / theta)) - 1) ** theta

  def inverse(self, t, theta):
    return (t ** (1 / theta) + 1) ** -theta

n14-pdf-1.png

Figure 9: N14 PDF


Footnotes:

Last updated: Tuesday 23 December, 2025