Modelling Dependence with Copulas in R

A copula is a function which couples a multivariate distribution function to its marginal distribution functions, generally called marginals or simply margins. Copulas are great tools for modelling and simulating correlated random variables.

The main appeal of copulas is that by using them you can model the correlation structure and the marginals (i.e. the distribution of each of your random variables) separately. This can be an advantage because for some combination of marginals there are no built-in functions to generate the desired multivariate distribution. For instance, in R it is easy to generate random samples from a multivariate normal distribution, however it is not so easy to do the same with say a distribution whose margins are Beta, Gamma and Student, respectively.
Furthermore, as you can probably see by googling copulas, there is a wide range of models providing a set of very different and customizable correlation structures that can be easily fitted to observed data and used for simulations. This variety of choice is one of the things I like the most about copulas.

In this post, we are going to show how to use a copula in R using the copula package and then we try to provide a simple example of application.

How copulas work (roughly)

But first, let’s try to get a grasp on how copulas actually work.
We generate n samples from a multivariate normal distribution of 3 random variables given the covariance matrix sigma using the MASS package.

library(MASS)
set.seed(100)

m <- 3
n <- 2000
sigma <- matrix(c(1, 0.4, 0.2,
                  0.4, 1, -0.8,
                  0.2, -0.8, 1), 
                nrow=3)
z <- mvrnorm(n,mu=rep(0, m),Sigma=sigma,empirical=T)

Now we check the samples correlation using cor() and a pairplot.
We are going to set method='spearman' in order to use Spearman’s Rho instead of the 'pearson' method in the cor() function (this is not strictly necessary in our example since we are going to use an elliptical copula, if you use non-elliptical copulas you may want to use either Spearman’s Rho or Kendall’s Tau).

library(psych)
cor(z,method='spearman')
pairs.panels(z)

          [,1]       [,2]       [,3]
[1,] 1.0000000  0.3812244  0.1937548
[2,] 0.3812244  1.0000000 -0.7890814
[3,] 0.1937548 -0.7890814  1.0000000

The output and the pairplot show us that indeed our samples have exactly the expected correlation structure.
Rplot

And now comes the magic trick: recall that if \(X\) is a random variable with distribution \(F\) then \(F(X)\) is uniformly distributed in the interval [0, 1]. In our toy example we already know the distribution \(F\) for each of the three random variables so this part is quick and straightforward.

u <- pnorm(z)
pairs.panels(u)

Here is the pairplot of our new random variables contained in u. Note that each distribution is uniform in the [0,1] interval. Note also that the correlation the same, in fact, the transformation we applied did not change the correlation structure between the random variables. Basically we are left only with what is often referred as the dependence structure.
Rplot01
By using the rgl library we can plot a nice 3D representation of the vector u. This is very nice for presentation purposes, you can easily rotate around the graph in case you would like to check a different perspective: go nuts! Just click on the picture and drag the mouse around.

library(rgl)
plot3d(u[,1],u[,2],u[,3],pch=20,col='navyblue')

Here is the plot:
Immagine 1
Now, as a last step, we only need to select the marginals and apply them to u. I chose the marginals to be Gamma, Beta and Student distributed with the parameters specified below.

x1 <- qgamma(u[,1],shape=2,scale=1)
x2 <- qbeta(u[,2],2,2)
x3 <- qt(u[,3],df=5)
plot3d(x1,x2,x3,pch=20,col='blue')

Below is the 3D plot of our simulated data. Spend a few seconds inspecting it. For sure this is a not so trivial dependence structure that has been generated in a very simple way and very few steps.

Immagine 2
What is worth noticing is that by starting from a multivariate normal sample we have build a sample with the desired and fixed dependence structure and, basically, arbitrary marginals.

df <- cbind(x1,x2,x3)
pairs.panels(df)
cor(df,meth='spearman')

          x1         x2         x3
x1 1.0000000  0.3812244  0.1937548
x2 0.3812244  1.0000000 -0.7890814
x3 0.1937548 -0.7890814  1.0000000

Here is the pairplot of the random variables:

Rplot02
Again, I would like to emphasize that I chose totally arbitrary marginals. I could have chosen any marginal as long as it was a continuous function.

Using the copula package

The whole process performed above can be done more efficiently, concisely and with some additional care entirely by the copula package. Let’s replicate the process above using copulas.

library(copula)
set.seed(100)
myCop <- normalCopula(param=c(0.4,0.2,-0.8), dim = 3, dispstr = "un")
myMvd <- mvdc(copula=myCop, margins=c("gamma", "beta", "t"),
              paramMargins=list(list(shape=2, scale=1),
                                list(shape1=2, shape2=2), 
                                list(df=5)) )

Now that we have specified the dependence structure through the copula (a normal copula) and set the marginals, the mvdc() function generates the desired distribution. Then we can generate random samples using the rmvdc() function.

Z2 <- rmvdc(myMvd, 2000)
colnames(Z2) <- c("x1", "x2", "x3")
pairs.panels(Z2)

The simulated data is of course very close to the one simulated before and is displayed in the pairplot below:

Rplot03

A simple example of application

Now for the real world example. We are going to fit a copula to the returns of two stocks and try to simulate the returns using the copula. I already cleaned the data from two stocks and calculated the returns, you can download the data in .csv format (yahoo, cree). Note that the data may not be exactly the one you can find on Google finance because I made some data cleaning and removed some outliers without much thinking mainly because this is a another toy example of application.
Let’s load the returns in R

cree <- read.csv('cree_r.csv',header=F)$V2
yahoo <- read.csv('yahoo_r.csv',header=F)$V2

Before going straight to the copula fitting process, let’s check the correlation between the two stock returns and plot the regression line:

plot(cree,yahoo,pch='.')
abline(lm(yahoo~cree),col='red',lwd=1)
cor(cree,yahoo,method='spearman')

[1] 0.4023584

We can see a very mild positive correlation, the returns look mainly like a random blob on the plot:

Rplot06

In the first example above I chose a normal copula model without much thinking, however, when applying these models to real data one should carefully think at what could suit the data better. For instance, many copulas are better suited for modelling asymetrical correlation other emphasize tail correlation a lot, and so on. My guess for stock returns is that a t-copula should be fine, however a guess is certainly not enough. Fortunately, the VineCopula package offers a great function which tells us what copula we should use. Essentially the VineCopula library allows us to perform copula selection using BIC and AIC through the function BiCopSelect()

library(VineCopula)
u <- pobs(as.matrix(cbind(cree,yahoo)))[,1]
v <- pobs(as.matrix(cbind(cree,yahoo)))[,2]
selectedCopula <- BiCopSelect(u,v,familyset=NA)
selectedCopula

$family
[1] 2

$par
[1] 0.4356302

$par2
[1] 3.844534

Note that I fed into the selection algorithm the pseudo observations using the pobs() function. Pseudo observations are the observations in the [0,1] interval.
The fitting algorithm indeed selected a t-copula (encoded as 2 in the $family reference) and estimated the parameters for us. By typing ?BiCopSelect() you can actually see the encoding for each copula.
Let’s try to fit the suggested model using the copula package and double check the parameters fitting.

t.cop <- tCopula(dim=2)
set.seed(500)
m <- pobs(as.matrix(cbind(cree,yahoo)))
fit <- fitCopula(t.cop,m,method='ml')
coef(fit)

  rho.1      df 
0.43563 3.84453 

It is nice to see that the parameters of the fitted copula are the same as those suggested by the BiCopSelect() function. Let’s take a look at the density of the copula we have just estimated

rho <- coef(fit)[1]
df <- coef(fit)[2]
persp(tCopula(dim=2,rho,df=df),dCopula)

Rplot18
Now we only need to build the copula and sample from it 3965 random samples.

u <- rCopula(3965,tCopula(dim=2,rho,df=df))
plot(u[,1],u[,2],pch='.',col='blue')
cor(u,method='spearman')

          [,1]      [,2]
[1,] 1.0000000 0.3972454
[2,] 0.3972454 1.0000000

This is the plot of the samples contained in the vector u:
Rplot05

The random samples from the copula look a little bit close to the independence case, but that is fine since the correlation between the returns is low. Note that the generated samples have the same correlation as the data.
The t-copula emphasizes extreme results: it is usually good for modelling phenomena where there is high correlation in the extreme values (the tails of the distribution).
Note also that it is symmetrical, this might be an issue for our application, however we are going to neglect this.

Now we are facing the hard bit: modelling the marginals. We are going to assume normally distributed returns for simplicity even though it is well known to be a far from sound assumption. We therefore estimate the parameters of the marginals

cree_mu <- mean(cree)
cree_sd <- sd(cree)
yahoo_mu <- mean(yahoo)
yahoo_sd <- sd(yahoo)

Let’s plot the fittings against the histogram in order to gather a visual take on what we are doing:

hist(cree,breaks=80,main='Cree returns',freq=F,density=30,col='cyan',ylim=c(0,20),xlim=c(-0.2,0.3))
lines(seq(-0.5,0.5,0.01),dnorm(seq(-0.5,0.5,0.01),cree_mu,cree_sd),col='red',lwd=2)
legend('topright',c('Fitted normal'),col=c('red'),lwd=2)
hist(yahoo,breaks=80,main='Yahoo returns',density=30,col='cyan',freq=F,ylim=c(0,20),xlim=c(-0.2,0.2))
lines(seq(-0.5,0.5,0.01),dnorm(seq(-0.5,0.5,0.01),yahoo_mu,yahoo_sd),col='red',lwd=2)
legend('topright',c('Fitted normal'),col=c('red'),lwd=2)

The two histograms are displayed below

Rplot11
Rplot12
Do not let the nice graphs fool you, under the assumptions for the distributions of the returns we are neglecting some extreme returns located in the tails of the distribution, but in this example we are interested in the joint behaviour, so that is what we are going to analyse soon.

Now we apply the copula in the mvdc() function and then use rmvdc() to get our simulated observations from the generated multivariate distribution. Finally, we compare the simulated results with the original data.

copula_dist <- mvdc(copula=tCopula(rho,dim=2,df=df), margins=c("norm","norm"),
                    paramMargins=list(list(mean=cree_mu, sd=cree_sd),
                                      list(mean=yahoo_mu, sd=yahoo_sd)))
sim <- rmvdc(copula_dist, 3965)

Good, now we have simulated data, let’s make a visual comparison

plot(cree,yahoo,main='Returns')
points(sim[,1],sim[,2],col='red')
legend('bottomright',c('Observed','Simulated'),col=c('black','red'),pch=21)

This is the final scatterplot of the data under the assumption of normal marginals and t-copula for the dependence structure:
Rplot13
As you can see, the t-copula leads to results close to the real observations, although there are fewer extreme returns than in the real data and we missed some of the most extreme result. If we were interested in modelling the risk associated with these stocks then this would be a major red flag to be addressed with further model calibration.

Since the purpose of this example is simply to show how to fit a copula and generate random observations using real data, we are going to stop here.

Just as a curiosity let me show you how the simulated values from the copula would have changed by tweaking the df parameter in the t-copula.
Let’s try df=1 and df=8

set.seed(4258)
copula_dist <- mvdc(copula=tCopula(rho,dim=2,df=1), margins=c("norm","norm"),
                    paramMargins=list(list(mean=cree_mu, sd=cree_sd),
                                      list(mean=yahoo_mu, sd=yahoo_sd)))
sim <- rmvdc(copula_dist, 3965)
plot(cree,yahoo,main='Returns')
points(sim[,1],sim[,2],col='red')
legend('bottomright',c('Observed','Simulated df=1'),col=c('black','red'),pch=21)
cor(sim[,1],sim[,2],method='spearman')

copula_dist <- mvdc(copula=tCopula(rho,dim=2,df=8), margins=c("norm","norm"),
                    paramMargins=list(list(mean=cree_mu, sd=cree_sd),
                                      list(mean=yahoo_mu, sd=yahoo_sd)))
sim <- rmvdc(copula_dist, 3965)
plot(cree,yahoo,main='Returns')
points(sim[,1],sim[,2],col='red')
legend('bottomright',c('Observed','Simulated df=8'),col=c('black','red'),pch=21)
cor(sim[,1],sim[,2],method='spearman')

[1] 0.3929509
[1] 0.3911127

Note how different are the dependence structures that we have obtained although Spearman’s Rho is similar.
Clearly the parameter df is very important in determining the shape of the distribution. As df increases, the t-copula tends to a gaussian copula.

Rplot16
Rplot17

A final word of caution

As you might have guess at this point, copulas are extremely useful tools when it comes to model the joint behaviour of random variables, however it is also extremely easy to mess with them and neglect important aspects of the phenomenon you are trying to model. In this example we are using just two random variables and some lack of fit is noticeable when plotting simulated data against the real one, but as you add some more variables the results are no longer available to be plotted and you are left with correlation coefficients and pairplots that may lead you to think that the simulated values are very close to the real one when in fact you may be missing out a lot of action. Yet the true power of copulas is to generalize joint behaviour modelling with many random variables in an easy way with the divide et impera approach for the marginals and the dependence structure.
Another factor that is often neglected is that the dependence structure may not be fixed, but rather vary with time. This issue, of course, is not included the copula and should be taken into account when running simulations.

A gist with the full code for this post can be found here.

Thank you for reading this post, leave a comment below if you have any question.

26 Comments

  1. SK
    shani khan March 12, 2021

    Hi I want to know about Conditional probability in r using copula. if any one has codding kindly send me.
    thank you in advanced.

    Reply
  2. SG
    Saba Ghotbi October 14, 2019

    Hi Michy,

    Thank you for the explanation.

    I have a question about the time series which has autocorrelation. How can we implement copula on them?
    The other question is about differentiate between short memory and long memory. I have read in a paper that if a decay of autocorrelation is exponential, it has short memory.
    Do you think my random variables attached here has short memory? https://uploads.disquscdn.com/images/b03451a56ed7a9d3c121f15bfdd4c61c96d13e8edf7df404712b16b8381c7600.jpg https://uploads.disquscdn.com/images/9584769416f9ccdc8d9496c3b3b2ce74b0137f008b223d3b0ab03911af418fb0.jpg

    Reply
  3. T
    Tinashe June 8, 2019

    Hi Michy.

    Thank you for the explanation.
    However, using my my data set, I have failed to simulate using the tCopula. I keep getting the following error mesage:

    Error in validObject(.Object) :
    invalid class “tCopula” object: Parameter value(s) out of bound.

    Any ideas on what the problem might be?

    Regards,
    Tinashe

    Reply
  4. SK
    saurav kumar March 12, 2019

    Hi Alice,

    Thank you very much for simplifying the most difficult topic, which could be easy understood by any novice in this field. I have one question and that is you said in the blog-” If we were interested in modelling the risk associated with these stocks
    then this would be a major red flag to be addressed with further model
    calibration
    . ” . It will be great help from you if you elaborate this in brief because I am implementing copula for risk modeling and also facilitates some additional reference on above. Please !!.

    Thanks for giving your precious time,
    Saurav

    Reply
  5. AS
    Ayyub Sheikhi June 30, 2018

    I have the following error:
    error in plot3d(x1, x2, x3, pch = 20, col = “blue”) :

    could not find function “plot3d”

    can one help?!!!

    Reply
  6. FY
    Fahmida Yeasmin August 1, 2017

    Hello, I actually use copula a lot and I found this post awesome! Thanks a lot for writing this. Great job!!!

    Reply
  7. TT
    Taha Tml June 20, 2017

    Nice post, great efforts. Thank you!

    Reply
  8. MV
    Maarten Vanzieleghem April 4, 2017

    Hi! Thanks for the amazing explanation. Do you have any suggestions on how to compute VaR having applied these copula’s?

    Reply
  9. MV
    Maarten Vanzieleghem March 22, 2017

    https://uploads.disquscdn.com/images/bbbf0003832d7207b7e138bd91b3710f4d3f4b084eab6753e23a6344a106ae0d.png While trying to implement this example in R, I came across an error..any idea how to solve this?

    Reply
  10. MV
    Maarten Vanzieleghem March 22, 2017

    Hi! while trying to make this example in R I came across an error..any ideas how to solve this?

    Reply
  11. T
    Tao December 16, 2016

    Hi,how can I change margins to arma-Garth distributions? Thank you very much!

    Reply
  12. T
    Tom October 28, 2016

    Thank you for the great article. Extending on your work, how would you account for and include auto/serial correlation (like in a time series) in individual data samples in your work? Thanks.

    Reply
  13. 诗婕 July 5, 2016

    Hi! Thank you for the post. This is a very simple and clear way to illustrate. I have a question though. Is there a package to fit higher dimensional copulas? The VineCopula only provides for vine copulas and bivariate copulas.

    Reply
    1. M
      Michy July 9, 2016

      Hi, if your problem is just copula fitting, then you can use the fitCopula function inside the copula library. It should work with higher dimensional data as well.

      Reply
  14. P
    piyush June 26, 2016

    I wish to make a bias correction model using copulas. So I have two variables: ‘obs’ and ‘sim’. I would like to model the joint distribution of them (which I understood successfully from your post) and then calculate the mode of the P(obs|sim), and say that as my bias corrected value. So I am saying that the max probable value in the conditional probability distribution is my corrected value. Can you hint on how can I use conditional probability using copula package. Many thanks!

    Reply
  15. SD
    Sumon Kanti Das May 31, 2016

    Many thanks. I am interested to think about the multivariate distribution where the variables are different types like 1. interval/ratio scale, 2. Nominal scale, 3. Count/discrete.
    If you have any idea, please give some hints or link.

    Reply
  16. LD
    Leland Milton Drake October 24, 2015

    How would the simulated data be done without the use of copulas? If it helps, I am asking this question because I am trying to appreciate the utility of using copulas for statistical modelling.

    Reply
    1. M
      Michy October 29, 2015

      Hello! Well, it depends on the data distribution(s)! For instance: if the marginals are all normally distributed and the dependence structure is the one in the first pairplot, then you do not need copulas, you can just use mvrnorm(). Same for the multivariate t-distribution (I am told!).

      The strength of copulas (and their utility) is that you can model the marginals and the joint distributions separately. Once you have chosen the dependence structure and the copula, then you can choose ARBITRARY marginals. This basically allows you to simulate different and more complex relationships between random variables in few steps. Sometimes there is not another easy and fast option.

      Furthermore, the range of copulas available is very wide.

      Reply
      1. LD
        Leland Milton Drake October 29, 2015

        Thanks, Michy! Your reply clarified a lot of things for me.

        Reply
        1. M
          Michy October 29, 2015

          You’re welcome! I’m glad to know it helped!

          Reply
  17. A
    adunaic October 20, 2015

    This is a really nice and simple explanation of couplas.

    Reply
    1. M
      Michy October 20, 2015

      Thanks! The idea was to make it simple since when I studied copulas for the first time, most of the papers where only formulas!

      Reply
  18. E
    E-R October 19, 2015

    You added a few nice 3d charts

    Reply
  19. E
    E-R October 19, 2015

    Nice post. Complementary to this one: http://eranraviv.com/correlation-and-correlation-structure-2-copulas/

    Reply
    1. K
      Klodian October 19, 2015

      Thanks for your comment. Nice header photo on your blog.

      Reply
    2. M
      Michy October 19, 2015

      Thanks! Glad to know you liked it!
      Indeed!

      Reply

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.