Skip to content

Commit f1c6658

Browse files
committed
adds initial support for mixture of two copula
1 parent a900763 commit f1c6658

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

starvine/bvcopula/copula/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
'clayton_copula',
66
'gumbel_copula',
77
'marshall_olkin_copula',
8+
'mixture_copula',
89
]
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
##
2+
# \brief Mixture of two copula
3+
from __future__ import print_function, absolute_import, division
4+
import numpy as np
5+
from scipy import stats
6+
# STARVINE IMPORTS
7+
from starvine.bvcopula.copula.copula_base import CopulaBase
8+
from starvine.bvcopula.copula.mvtdstpack import mvtdstpack as mvt
9+
10+
11+
class MixtureCopula(CopulaBase):
12+
"""!
13+
@brief Gaussian copula model
14+
single parameter
15+
\f$\theta[0] \in (-1, 1)\f$
16+
"""
17+
def __init__(self, copula_a, wt_a, copula_b, wt_b):
18+
wt_a = wt_a / (wt_a + wt_b)
19+
wt_b = wt_b / (wt_a + wt_b)
20+
self._name = copula_a.name + '-' + copula_b.name
21+
self._thetaBounds = tuple(list(copula_a.thetaBounds) + list(copula_b.thetaBounds) + [(0.,1.), (0.,1.)])
22+
self._theta0 = tuple(list(copula_a.theta0) + list(copula_b.theta0) + [wt_a, wt_b])
23+
self.fittedParams = list(copula_a.theta0) + list(copula_b.theta0) + [wt_a, wt_b]
24+
self._copula_a = copula_a
25+
self._copula_b = copula_b
26+
self._n_params_a = len(copula_a.thetaBounds)
27+
self._n_params_b = len(copula_b.thetaBounds)
28+
29+
@property
30+
def wgts(self):
31+
return self._fittedParams[-2:]
32+
33+
@property
34+
def thetaBounds(self):
35+
return self._thetaBounds
36+
37+
@property
38+
def theta0(self):
39+
return self._theta0
40+
41+
@property
42+
def name(self):
43+
return self._name
44+
45+
@property
46+
def rotation(self):
47+
# mixture copula has no defined rotation
48+
return 0
49+
50+
@CopulaBase._rotPDF
51+
def _pdf(self, u, v, rotation=0, *theta):
52+
theta_a = theta[0:self._n_params_a]
53+
theta_b = theta[self._n_params_a:-2]
54+
wgt_a = theta[-2]
55+
wgt_b = theta[-1]
56+
return wgt_a * self._copula_a.pdf(u, v, self._copula_a.rotation, *theta_a) + \
57+
wgt_b * self._copula_b.pdf(u, v, self._copula_b.rotation, *theta_b)
58+
59+
@CopulaBase._rotCDF
60+
def _cdf(self, u, v, rotation=0, *theta):
61+
theta_a = theta[0:self._n_params_a]
62+
theta_b = theta[self._n_params_a:-2]
63+
wgt_a = theta[-2]
64+
wgt_b = theta[-1]
65+
return wgt_a * self._copula_a.cdf(u, v, self._copula_a.rotation, *theta_a) + \
66+
wgt_b * self._copula_b.cdf(u, v, self._copula_b.rotation, *theta_b)
67+
68+
@CopulaBase._rotH
69+
def _h(self, v, u, rotation=0, *theta):
70+
theta_a = theta[0:self._n_params_a]
71+
theta_b = theta[self._n_params_a:-2]
72+
wgt_a = theta[-2]
73+
wgt_b = theta[-1]
74+
return wgt_a * self._copula_a.h(u, v, self._copula_a.rotation, *theta_a) + \
75+
wgt_b * self._copula_b.h(u, v, self._copula_b.rotation, *theta_b)
76+
77+
@CopulaBase._rotHinv
78+
def _hinv(self, v, u, rotation=0, *theta):
79+
theta_a = theta[0:self._n_params_a]
80+
theta_b = theta[self._n_params_a:-2]
81+
wgt_a = theta[-2]
82+
wgt_b = theta[-1]
83+
return wgt_a * self._copula_a.hinv(u, v, self._copula_a.rotation, *theta_a) + \
84+
wgt_b * self._copula_b.hinv(u, v, self._copula_b.rotation, *theta_b)
85+
86+
@CopulaBase._rotGen
87+
def _gen(self, t, *theta):
88+
"""!
89+
@brief Copula generating function
90+
"""
91+
raise NotImplementedError

starvine/bvcopula/copula_factory.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@ def validateRotation(rotation):
1010
raise RuntimeError("Invalid rotation.")
1111

1212

13+
def validateMixtureParams(mix_list):
14+
for tuple_copula_settings in mix_list:
15+
# ensure ("string", int, float) tuples in mix list
16+
assert len(tuple_copula_settings) == 3
17+
assert isinstance(tuple_copula_settings[0], str) # type
18+
assert isinstance(tuple_copula_settings[1], int) # rotation
19+
assert isinstance(tuple_copula_settings[2], float) # wgt
20+
21+
1322
def Copula(copulatype, rotation=0):
1423
"""!
1524
@brief Factory method. Returns a bivariate copula instance.
1625
@param copulatype <b>string</b> Copula type.
1726
@param rotation <b>int</b> Copula rotation 1==90 deg, 2==180 deg, 3==270 deg
1827
"""
28+
if isinstance(copulatype, list):
29+
if len(copulatype) == 1:
30+
copulatype, rotation = copulatype[0], copulatype[1]
31+
elif len(copulatype) == 2:
32+
validateMixtureParams(copulatype)
33+
return MixCopula(*copulatype[0], *copulatype[1])
34+
else:
35+
raise RuntimeError("Mixture copula of more than 2 distributions is not implemented")
1936
validateRotation(rotation)
2037
if re.match("t", copulatype):
2138
return t_copula.StudentTCopula(0)
@@ -34,3 +51,19 @@ def Copula(copulatype, rotation=0):
3451
else:
3552
# default
3653
raise RuntimeError("Copula type %s is not available" % str(copulatype))
54+
55+
56+
def MixCopula(copulatype_a, rotation_a=0, wgt_a=0.5, copulatype_b=None, rotation_b=0, wgt_b=0.5):
57+
"""!
58+
@brief Helper method to generate a mixture distribution of two copula
59+
@param copulatype_a <b>string</b> Copula A type.
60+
@param rotation_a <b>int</b> Copula rotation 1==90 deg, 2==180 deg, 3==270 deg
61+
@param copulatype_b <b>string</b> Copula B type.
62+
@param rotation_b <b>int</b> Copula rotation 1==90 deg, 2==180 deg, 3==270 deg
63+
@param wgt_a <b>float</b> Copula A weight in mixture
64+
@param wgt_b <b>float</b> Copula B weight in mixture
65+
"""
66+
wgt_a = wgt_a / (wgt_a + wgt_b)
67+
wgt_b = wgt_b / (wgt_a + wgt_b)
68+
return mixture_copula.MixtureCopula(Copula(copulatype_a, rotation_a), wgt_a,
69+
Copula(copulatype_b, rotation_b), wgt_b)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
##
2+
# \brief Tests ability to fit and sample a mixture of 2 copula
3+
from __future__ import print_function, division
4+
import unittest
5+
from scipy.stats.mstats import rankdata
6+
# COPULA IMPORTS
7+
from starvine.bvcopula.copula.mixture_copula import MixtureCopula as mc
8+
import numpy as np
9+
import matplotlib.pyplot as plt
10+
import os
11+
pwd_ = os.path.dirname(os.path.abspath(__file__))
12+
from starvine.bvcopula.copula.gumbel_copula import GumbelCopula
13+
dataDir = pwd_ + "/data/"
14+
np.random.seed(123)
15+
tol = 4e-2
16+
17+
18+
class TestMixtureCopula(unittest.TestCase):
19+
def setUp(self):
20+
self._mix_copula = mc(GumbelCopula(1), 0.5,
21+
GumbelCopula(2), 0.5)
22+
23+
def testMixCoplulaPdf(self):
24+
u = np.linspace(1.0e-8, 1.0-1e-8, 50)
25+
v = np.linspace(1.0e-8, 1.0-1e-8, 50)
26+
c_pdf = self._mix_copula.pdf(u, v)
27+
self.assertTrue(np.all(c_pdf >= 0))
28+
29+
def testMixCoplulaCdf(self):
30+
u = np.linspace(1.0e-8, 1.0-1e-8, 50)
31+
v = np.linspace(1.0e-8, 1.0-1e-8, 50)
32+
c_pdf = self._mix_copula.cdf(u, v)
33+
self.assertTrue(np.all(c_pdf >= 0))
34+
self.assertTrue(np.all(c_pdf <= 1))
35+
36+
def testMixCoplulaSample(self):
37+
pass

0 commit comments

Comments
 (0)