copulas 0.12.4.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copulas/__init__.py +91 -0
- copulas/bivariate/__init__.py +175 -0
- copulas/bivariate/base.py +448 -0
- copulas/bivariate/clayton.py +163 -0
- copulas/bivariate/frank.py +170 -0
- copulas/bivariate/gumbel.py +144 -0
- copulas/bivariate/independence.py +81 -0
- copulas/bivariate/utils.py +19 -0
- copulas/datasets.py +214 -0
- copulas/errors.py +5 -0
- copulas/multivariate/__init__.py +8 -0
- copulas/multivariate/base.py +200 -0
- copulas/multivariate/gaussian.py +345 -0
- copulas/multivariate/tree.py +691 -0
- copulas/multivariate/vine.py +359 -0
- copulas/optimize/__init__.py +154 -0
- copulas/univariate/__init__.py +25 -0
- copulas/univariate/base.py +661 -0
- copulas/univariate/beta.py +48 -0
- copulas/univariate/gamma.py +38 -0
- copulas/univariate/gaussian.py +27 -0
- copulas/univariate/gaussian_kde.py +192 -0
- copulas/univariate/log_laplace.py +38 -0
- copulas/univariate/selection.py +36 -0
- copulas/univariate/student_t.py +31 -0
- copulas/univariate/truncated_gaussian.py +66 -0
- copulas/univariate/uniform.py +27 -0
- copulas/utils.py +248 -0
- copulas/visualization.py +345 -0
- copulas-0.12.4.dev3.dist-info/METADATA +215 -0
- copulas-0.12.4.dev3.dist-info/RECORD +34 -0
- copulas-0.12.4.dev3.dist-info/WHEEL +5 -0
- copulas-0.12.4.dev3.dist-info/licenses/LICENSE +106 -0
- copulas-0.12.4.dev3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""VineCopula module."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from copulas.bivariate.base import Bivariate, CopulaTypes
|
|
10
|
+
from copulas.multivariate.base import Multivariate
|
|
11
|
+
from copulas.multivariate.tree import Tree, get_tree
|
|
12
|
+
from copulas.univariate.gaussian_kde import GaussianKDE
|
|
13
|
+
from copulas.utils import (
|
|
14
|
+
EPSILON,
|
|
15
|
+
check_valid_values,
|
|
16
|
+
get_qualified_name,
|
|
17
|
+
random_state,
|
|
18
|
+
store_args,
|
|
19
|
+
validate_random_state,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class VineCopula(Multivariate):
|
|
26
|
+
"""Vine copula model.
|
|
27
|
+
|
|
28
|
+
A :math:`vine` is a graphical representation of one factorization of the n-variate probability
|
|
29
|
+
distribution in terms of :math:`n(n − 1)/2` bivariate copulas by means of the chain rule.
|
|
30
|
+
|
|
31
|
+
It consists of a sequence of levels and as many levels as variables. Each level consists of
|
|
32
|
+
a tree (no isolated nodes and no loops) satisfying that if it has :math:`n` nodes there must
|
|
33
|
+
be :math:`n − 1` edges.
|
|
34
|
+
|
|
35
|
+
Each node in tree :math:`T_1` is a variable and edges are couplings of variables constructed
|
|
36
|
+
with bivariate copulas.
|
|
37
|
+
|
|
38
|
+
Each node in tree :math:`T_{k+1}` is a coupling in :math:`T_{k}`, expressed by the copula
|
|
39
|
+
of the variables; while edges are couplings between two vertices that must have one variable
|
|
40
|
+
in common, becoming a conditioning variable in the bivariate copula. Thus, every level has
|
|
41
|
+
one node less than the former. Once all the trees are drawn, the factorization is the product
|
|
42
|
+
of all the nodes.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
vine_type (str):
|
|
46
|
+
type of the vine copula, could be 'center','direct','regular'
|
|
47
|
+
random_state (int or np.random.RandomState):
|
|
48
|
+
Random seed or RandomState to use.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
model (copulas.univariate.Univariate):
|
|
53
|
+
Distribution to compute univariates.
|
|
54
|
+
u_matrix (numpy.array):
|
|
55
|
+
Univariates.
|
|
56
|
+
n_sample (int):
|
|
57
|
+
Number of samples.
|
|
58
|
+
n_var (int):
|
|
59
|
+
Number of variables.
|
|
60
|
+
columns (pandas.Series):
|
|
61
|
+
Names of the variables.
|
|
62
|
+
tau_mat (numpy.array):
|
|
63
|
+
Kendall correlation parameters for data.
|
|
64
|
+
truncated (int):
|
|
65
|
+
Max level used to build the vine.
|
|
66
|
+
depth (int):
|
|
67
|
+
Vine depth.
|
|
68
|
+
trees (list[Tree]):
|
|
69
|
+
List of trees used by this vine.
|
|
70
|
+
ppfs (list[callable]):
|
|
71
|
+
percent point functions from the univariates used by this vine.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
@store_args
|
|
75
|
+
def __init__(self, vine_type, random_state=None):
|
|
76
|
+
warnings.warn(
|
|
77
|
+
'Vines have not been fully tested on Python >= 3.8 and might produce wrong results.'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
self.random_state = validate_random_state(random_state)
|
|
81
|
+
self.vine_type = vine_type
|
|
82
|
+
self.u_matrix = None
|
|
83
|
+
|
|
84
|
+
self.model = GaussianKDE
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def _deserialize_trees(cls, tree_list):
|
|
88
|
+
previous = Tree.from_dict(tree_list[0])
|
|
89
|
+
trees = [previous]
|
|
90
|
+
|
|
91
|
+
for tree_dict in tree_list[1:]:
|
|
92
|
+
tree = Tree.from_dict(tree_dict, previous)
|
|
93
|
+
trees.append(tree)
|
|
94
|
+
previous = tree
|
|
95
|
+
|
|
96
|
+
return trees
|
|
97
|
+
|
|
98
|
+
def to_dict(self):
|
|
99
|
+
"""Return a `dict` with the parameters to replicate this Vine.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
dict:
|
|
103
|
+
Parameters of this Vine.
|
|
104
|
+
"""
|
|
105
|
+
result = {
|
|
106
|
+
'type': get_qualified_name(self),
|
|
107
|
+
'vine_type': self.vine_type,
|
|
108
|
+
'fitted': self.fitted,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if not self.fitted:
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
result.update({
|
|
115
|
+
'n_sample': self.n_sample,
|
|
116
|
+
'n_var': self.n_var,
|
|
117
|
+
'depth': self.depth,
|
|
118
|
+
'truncated': self.truncated,
|
|
119
|
+
'trees': [tree.to_dict() for tree in self.trees],
|
|
120
|
+
'tau_mat': self.tau_mat.tolist(),
|
|
121
|
+
'u_matrix': self.u_matrix.tolist(),
|
|
122
|
+
'unis': [distribution.to_dict() for distribution in self.unis],
|
|
123
|
+
'columns': self.columns,
|
|
124
|
+
})
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_dict(cls, vine_dict):
|
|
129
|
+
"""Create a new instance from a parameters dictionary.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
params (dict):
|
|
133
|
+
Parameters of the Vine, in the same format as the one
|
|
134
|
+
returned by the ``to_dict`` method.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Vine:
|
|
138
|
+
Instance of the Vine defined on the parameters.
|
|
139
|
+
"""
|
|
140
|
+
instance = cls(vine_dict['vine_type'])
|
|
141
|
+
fitted = vine_dict['fitted']
|
|
142
|
+
if fitted:
|
|
143
|
+
instance.fitted = fitted
|
|
144
|
+
instance.n_sample = vine_dict['n_sample']
|
|
145
|
+
instance.n_var = vine_dict['n_var']
|
|
146
|
+
instance.truncated = vine_dict['truncated']
|
|
147
|
+
instance.depth = vine_dict['depth']
|
|
148
|
+
instance.trees = cls._deserialize_trees(vine_dict['trees'])
|
|
149
|
+
instance.unis = [GaussianKDE.from_dict(uni) for uni in vine_dict['unis']]
|
|
150
|
+
instance.ppfs = [uni.percent_point for uni in instance.unis]
|
|
151
|
+
instance.columns = vine_dict['columns']
|
|
152
|
+
instance.tau_mat = np.array(vine_dict['tau_mat'])
|
|
153
|
+
instance.u_matrix = np.array(vine_dict['u_matrix'])
|
|
154
|
+
|
|
155
|
+
return instance
|
|
156
|
+
|
|
157
|
+
@check_valid_values
|
|
158
|
+
def fit(self, X, truncated=3):
|
|
159
|
+
"""Fit a vine model to the data.
|
|
160
|
+
|
|
161
|
+
1. Transform all the variables by means of their marginals.
|
|
162
|
+
In other words, compute
|
|
163
|
+
|
|
164
|
+
.. math:: u_i = F_i(x_i), i = 1, ..., n
|
|
165
|
+
|
|
166
|
+
and compose the matrix :math:`u = u_1, ..., u_n,` where :math:`u_i` are their columns.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
X (numpy.ndarray):
|
|
170
|
+
Data to be fitted to.
|
|
171
|
+
truncated (int):
|
|
172
|
+
Max level to build the vine.
|
|
173
|
+
"""
|
|
174
|
+
LOGGER.info('Fitting VineCopula("%s")', self.vine_type)
|
|
175
|
+
self.n_sample, self.n_var = X.shape
|
|
176
|
+
self.columns = X.columns
|
|
177
|
+
self.tau_mat = X.corr(method='kendall').to_numpy()
|
|
178
|
+
self.u_matrix = np.empty([self.n_sample, self.n_var])
|
|
179
|
+
|
|
180
|
+
self.truncated = truncated
|
|
181
|
+
self.depth = self.n_var - 1
|
|
182
|
+
self.trees = []
|
|
183
|
+
|
|
184
|
+
self.unis, self.ppfs = [], []
|
|
185
|
+
for i, col in enumerate(X):
|
|
186
|
+
uni = self.model()
|
|
187
|
+
uni.fit(X[col])
|
|
188
|
+
self.u_matrix[:, i] = uni.cumulative_distribution(X[col])
|
|
189
|
+
self.unis.append(uni)
|
|
190
|
+
self.ppfs.append(uni.percent_point)
|
|
191
|
+
|
|
192
|
+
self.train_vine(self.vine_type)
|
|
193
|
+
self.fitted = True
|
|
194
|
+
|
|
195
|
+
def train_vine(self, tree_type):
|
|
196
|
+
r"""Build the vine.
|
|
197
|
+
|
|
198
|
+
1. For the construction of the first tree :math:`T_1`, assign one node to each variable
|
|
199
|
+
and then couple them by maximizing the measure of association considered.
|
|
200
|
+
Different vines impose different constraints on this construction. When those are
|
|
201
|
+
applied different trees are achieved at this level.
|
|
202
|
+
|
|
203
|
+
2. Select the copula that best fits to the pair of variables coupled by each edge in
|
|
204
|
+
:math:`T_1`.
|
|
205
|
+
|
|
206
|
+
3. Let :math:`C_{ij}(u_i , u_j )` be the copula for a given edge :math:`(u_i, u_j)`
|
|
207
|
+
in :math:`T_1`. Then for every edge in :math:`T_1`, compute either
|
|
208
|
+
|
|
209
|
+
.. math:: {v^1}_{j|i} = \\frac{\\partial C_{ij}(u_i, u_j)}{\\partial u_j}
|
|
210
|
+
|
|
211
|
+
or similarly :math:`{v^1}_{i|j}`, which are conditional cdfs. When finished with
|
|
212
|
+
all the edges, construct the new matrix with :math:`v^1` that has one less column u.
|
|
213
|
+
|
|
214
|
+
4. Set k = 2.
|
|
215
|
+
|
|
216
|
+
5. Assign one node of :math:`T_k` to each edge of :math:`T_ {k−1}`. The structure of
|
|
217
|
+
:math:`T_{k−1}` imposes a set of constraints on which edges of :math:`T_k` are
|
|
218
|
+
realizable. Hence the next step is to get a linked list of the accesible nodes for
|
|
219
|
+
every node in :math:`T_k`.
|
|
220
|
+
|
|
221
|
+
6. As in step 1, nodes of :math:`T_k` are coupled maximizing the measure of association
|
|
222
|
+
considered and satisfying the constraints impose by the kind of vine employed plus the
|
|
223
|
+
set of constraints imposed by tree :math:`T_{k−1}`.
|
|
224
|
+
|
|
225
|
+
7. Select the copula that best fit to each edge created in :math:`T_k`.
|
|
226
|
+
|
|
227
|
+
8. Recompute matrix :math:`v_k` as in step 4, but taking :math:`T_k` and :math:`vk−1`
|
|
228
|
+
instead of :math:`T_1` and u.
|
|
229
|
+
|
|
230
|
+
9. Set :math:`k = k + 1` and repeat from (5) until all the trees are constructed.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
tree_type (str or TreeTypes):
|
|
234
|
+
Type of trees to use.
|
|
235
|
+
"""
|
|
236
|
+
LOGGER.debug('start building tree : 0')
|
|
237
|
+
# 1
|
|
238
|
+
tree_1 = get_tree(tree_type)
|
|
239
|
+
tree_1.fit(0, self.n_var, self.tau_mat, self.u_matrix)
|
|
240
|
+
self.trees.append(tree_1)
|
|
241
|
+
LOGGER.debug('finish building tree : 0')
|
|
242
|
+
|
|
243
|
+
for k in range(1, min(self.n_var - 1, self.truncated)):
|
|
244
|
+
# get constraints from previous tree
|
|
245
|
+
self.trees[k - 1]._get_constraints()
|
|
246
|
+
tau = self.trees[k - 1].get_tau_matrix()
|
|
247
|
+
LOGGER.debug(f'start building tree: {k}')
|
|
248
|
+
tree_k = get_tree(tree_type)
|
|
249
|
+
tree_k.fit(k, self.n_var - k, tau, self.trees[k - 1])
|
|
250
|
+
self.trees.append(tree_k)
|
|
251
|
+
LOGGER.debug(f'finish building tree: {k}')
|
|
252
|
+
|
|
253
|
+
def get_likelihood(self, uni_matrix):
|
|
254
|
+
"""Compute likelihood of the vine."""
|
|
255
|
+
num_tree = len(self.trees)
|
|
256
|
+
values = np.empty([1, num_tree])
|
|
257
|
+
|
|
258
|
+
for i in range(num_tree):
|
|
259
|
+
value, new_uni_matrix = self.trees[i].get_likelihood(uni_matrix)
|
|
260
|
+
uni_matrix = new_uni_matrix
|
|
261
|
+
values[0, i] = value
|
|
262
|
+
|
|
263
|
+
return np.sum(values)
|
|
264
|
+
|
|
265
|
+
def _sample_row(self):
|
|
266
|
+
"""Generate a single sampled row from vine model.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
numpy.ndarray
|
|
270
|
+
"""
|
|
271
|
+
unis = np.random.uniform(0, 1, self.n_var)
|
|
272
|
+
# randomly select a node to start with
|
|
273
|
+
first_ind = np.random.randint(0, self.n_var)
|
|
274
|
+
adj = self.trees[0].get_adjacent_matrix()
|
|
275
|
+
visited = []
|
|
276
|
+
explore = [first_ind]
|
|
277
|
+
|
|
278
|
+
sampled = np.zeros(self.n_var)
|
|
279
|
+
itr = 0
|
|
280
|
+
while explore:
|
|
281
|
+
current = explore.pop(0)
|
|
282
|
+
adj_is_one = adj[current, :] == 1
|
|
283
|
+
neighbors = np.where(adj_is_one)[0].tolist()
|
|
284
|
+
if itr == 0:
|
|
285
|
+
new_x = self.ppfs[current](unis[current])
|
|
286
|
+
|
|
287
|
+
else:
|
|
288
|
+
for i in range(itr - 1, -1, -1):
|
|
289
|
+
current_ind = -1
|
|
290
|
+
|
|
291
|
+
if i >= self.truncated:
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
current_tree = self.trees[i].edges
|
|
295
|
+
# get index of edge to retrieve
|
|
296
|
+
for edge in current_tree:
|
|
297
|
+
if i == 0:
|
|
298
|
+
if (edge.L == current and edge.R == visited[0]) or (
|
|
299
|
+
edge.R == current and edge.L == visited[0]
|
|
300
|
+
):
|
|
301
|
+
current_ind = edge.index
|
|
302
|
+
break
|
|
303
|
+
else:
|
|
304
|
+
if edge.L == current or edge.R == current:
|
|
305
|
+
condition = set(edge.D)
|
|
306
|
+
condition.add(edge.L) # noqa: PD005
|
|
307
|
+
condition.add(edge.R) # noqa: PD005
|
|
308
|
+
|
|
309
|
+
visit_set = set(visited)
|
|
310
|
+
visit_set.add(current) # noqa: PD005
|
|
311
|
+
|
|
312
|
+
if condition.issubset(visit_set):
|
|
313
|
+
current_ind = edge.index
|
|
314
|
+
break
|
|
315
|
+
|
|
316
|
+
if current_ind != -1:
|
|
317
|
+
# the node is not indepedent contional on visited node
|
|
318
|
+
copula_type = current_tree[current_ind].name
|
|
319
|
+
copula = Bivariate(copula_type=CopulaTypes(copula_type))
|
|
320
|
+
copula.theta = current_tree[current_ind].theta
|
|
321
|
+
|
|
322
|
+
U = np.array([unis[visited[0]]])
|
|
323
|
+
if i == itr - 1:
|
|
324
|
+
tmp = copula.percent_point(np.array([unis[current]]), U)[0]
|
|
325
|
+
else:
|
|
326
|
+
tmp = copula.percent_point(np.array([tmp]), U)[0]
|
|
327
|
+
|
|
328
|
+
tmp = min(max(tmp, EPSILON), 0.99)
|
|
329
|
+
|
|
330
|
+
new_x = self.ppfs[current](np.array([tmp]))
|
|
331
|
+
|
|
332
|
+
sampled[current] = new_x.item()
|
|
333
|
+
|
|
334
|
+
for s in neighbors:
|
|
335
|
+
if s not in visited:
|
|
336
|
+
explore.insert(0, s)
|
|
337
|
+
|
|
338
|
+
itr += 1
|
|
339
|
+
visited.insert(0, current)
|
|
340
|
+
|
|
341
|
+
return sampled
|
|
342
|
+
|
|
343
|
+
@random_state
|
|
344
|
+
def sample(self, num_rows):
|
|
345
|
+
"""Sample new rows.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
num_rows (int):
|
|
349
|
+
Number of rows to sample
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
pandas.DataFrame:
|
|
353
|
+
sampled rows.
|
|
354
|
+
"""
|
|
355
|
+
sampled_values = []
|
|
356
|
+
for i in range(num_rows):
|
|
357
|
+
sampled_values.append(self._sample_row())
|
|
358
|
+
|
|
359
|
+
return pd.DataFrame(sampled_values, columns=self.columns)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Copulas optimization functions."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def bisect(f, xmin, xmax, tol=1e-8, maxiter=50):
|
|
7
|
+
"""Bisection method for finding roots.
|
|
8
|
+
|
|
9
|
+
This method implements a simple vectorized routine for identifying
|
|
10
|
+
the root (of a monotonically increasing function) given a bracketing
|
|
11
|
+
interval.
|
|
12
|
+
|
|
13
|
+
Arguments:
|
|
14
|
+
f (Callable):
|
|
15
|
+
A function which takes as input a vector x and returns a
|
|
16
|
+
vector with the same number of dimensions.
|
|
17
|
+
xmin (np.ndarray):
|
|
18
|
+
The minimum value for x such that f(x) <= 0.
|
|
19
|
+
xmax (np.ndarray):
|
|
20
|
+
The maximum value for x such that f(x) >= 0.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
numpy.ndarray:
|
|
24
|
+
The value of x such that f(x) is close to 0.
|
|
25
|
+
"""
|
|
26
|
+
assert (f(xmin) <= 0.0).all()
|
|
27
|
+
assert (f(xmax) >= 0.0).all()
|
|
28
|
+
|
|
29
|
+
for _ in range(maxiter):
|
|
30
|
+
guess = (xmin + xmax) / 2.0
|
|
31
|
+
fguess = f(guess)
|
|
32
|
+
xmin[fguess <= 0] = guess[fguess <= 0]
|
|
33
|
+
xmax[fguess >= 0] = guess[fguess >= 0]
|
|
34
|
+
if (xmax - xmin).max() < tol:
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
return (xmin + xmax) / 2.0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def chandrupatla(f, xmin, xmax, eps_m=None, eps_a=None, maxiter=50):
|
|
41
|
+
"""Chandrupatla's algorithm.
|
|
42
|
+
|
|
43
|
+
This is adapted from [1] which implements Chandrupatla's algorithm [2]
|
|
44
|
+
which starts from a bracketing interval and, conditionally, swaps between
|
|
45
|
+
bisection and inverse quadratic interpolation.
|
|
46
|
+
|
|
47
|
+
[1] https://github.com/scipy/scipy/issues/7242#issuecomment-290548427
|
|
48
|
+
[2] https://books.google.com/books?id=cC-8BAAAQBAJ&pg=PA95
|
|
49
|
+
|
|
50
|
+
Arguments:
|
|
51
|
+
f (Callable):
|
|
52
|
+
A function which takes as input a vector x and returns a
|
|
53
|
+
vector with the same number of dimensions.
|
|
54
|
+
xmin (np.ndarray):
|
|
55
|
+
The minimum value for x such that f(x) <= 0.
|
|
56
|
+
xmax (np.ndarray):
|
|
57
|
+
The maximum value for x such that f(x) >= 0.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
numpy.ndarray:
|
|
61
|
+
The value of x such that f(x) is close to 0.
|
|
62
|
+
"""
|
|
63
|
+
# Initialization
|
|
64
|
+
a = xmax
|
|
65
|
+
b = xmin
|
|
66
|
+
fa = f(a)
|
|
67
|
+
fb = f(b)
|
|
68
|
+
|
|
69
|
+
# Make sure we know the size of the result
|
|
70
|
+
shape = np.shape(fa)
|
|
71
|
+
assert shape == np.shape(fb)
|
|
72
|
+
|
|
73
|
+
fc = fa
|
|
74
|
+
c = a
|
|
75
|
+
|
|
76
|
+
# Make sure we are bracketing a root in each case
|
|
77
|
+
assert (np.sign(fa) * np.sign(fb) <= 0).all()
|
|
78
|
+
t = 0.5
|
|
79
|
+
# Initialize an array of False,
|
|
80
|
+
# determines whether we should do inverse quadratic interpolation
|
|
81
|
+
iqi = np.zeros(shape, dtype=bool)
|
|
82
|
+
|
|
83
|
+
# jms: some guesses for default values of the eps_m and eps_a settings
|
|
84
|
+
# based on machine precision... not sure exactly what to do here
|
|
85
|
+
eps = np.finfo(float).eps
|
|
86
|
+
if eps_m is None:
|
|
87
|
+
eps_m = eps
|
|
88
|
+
if eps_a is None:
|
|
89
|
+
eps_a = 2 * eps
|
|
90
|
+
|
|
91
|
+
iterations = 0
|
|
92
|
+
terminate = False
|
|
93
|
+
|
|
94
|
+
while maxiter > 0:
|
|
95
|
+
maxiter -= 1
|
|
96
|
+
# use t to linearly interpolate between a and b,
|
|
97
|
+
# and evaluate this function as our newest estimate xt
|
|
98
|
+
xt = np.clip(a + t * (b - a), xmin, xmax)
|
|
99
|
+
ft = f(xt)
|
|
100
|
+
|
|
101
|
+
# update our history of the last few points so that
|
|
102
|
+
# - a is the newest estimate (we're going to update it from xt)
|
|
103
|
+
# - c and b get the preceding two estimates
|
|
104
|
+
# - a and b maintain opposite signs for f(a) and f(b)
|
|
105
|
+
samesign = np.sign(ft) == np.sign(fa)
|
|
106
|
+
c = np.choose(samesign, [b, a])
|
|
107
|
+
b = np.choose(samesign, [a, b])
|
|
108
|
+
fc = np.choose(samesign, [fb, fa])
|
|
109
|
+
fb = np.choose(samesign, [fa, fb])
|
|
110
|
+
a = xt
|
|
111
|
+
fa = ft
|
|
112
|
+
|
|
113
|
+
# set xm so that f(xm) is the minimum magnitude of f(a) and f(b)
|
|
114
|
+
fa_is_smaller = np.abs(fa) < np.abs(fb)
|
|
115
|
+
xm = np.choose(fa_is_smaller, [b, a])
|
|
116
|
+
fm = np.choose(fa_is_smaller, [fb, fa])
|
|
117
|
+
|
|
118
|
+
tol = 2 * eps_m * np.abs(xm) + eps_a
|
|
119
|
+
tlim = tol / np.abs(b - c)
|
|
120
|
+
terminate = np.logical_or(terminate, np.logical_or(fm == 0, tlim > 0.5))
|
|
121
|
+
|
|
122
|
+
if np.all(terminate):
|
|
123
|
+
break
|
|
124
|
+
iterations += 1 - terminate
|
|
125
|
+
|
|
126
|
+
# Figure out values xi and phi
|
|
127
|
+
# to determine which method we should use next
|
|
128
|
+
xi = (a - b) / (c - b)
|
|
129
|
+
phi = (fa - fb) / (fc - fb)
|
|
130
|
+
iqi = np.logical_and(phi**2 < xi, (1 - phi) ** 2 < 1 - xi)
|
|
131
|
+
|
|
132
|
+
if not shape:
|
|
133
|
+
# scalar case
|
|
134
|
+
if iqi:
|
|
135
|
+
# inverse quadratic interpolation
|
|
136
|
+
eq1 = fa / (fb - fa) * fc / (fb - fc)
|
|
137
|
+
eq2 = (c - a) / (b - a) * fa / (fc - fa) * fb / (fc - fb)
|
|
138
|
+
t = eq1 + eq2
|
|
139
|
+
else:
|
|
140
|
+
# bisection
|
|
141
|
+
t = 0.5
|
|
142
|
+
else:
|
|
143
|
+
# array case
|
|
144
|
+
t = np.full(shape, 0.5)
|
|
145
|
+
a2, b2, c2, fa2, fb2, fc2 = a[iqi], b[iqi], c[iqi], fa[iqi], fb[iqi], fc[iqi]
|
|
146
|
+
t[iqi] = fa2 / (fb2 - fa2) * fc2 / (fb2 - fc2) + (c2 - a2) / (b2 - a2) * fa2 / (
|
|
147
|
+
fc2 - fa2
|
|
148
|
+
) * fb2 / (fc2 - fb2)
|
|
149
|
+
|
|
150
|
+
# limit to the range (tlim, 1-tlim)
|
|
151
|
+
t = np.minimum(1 - tlim, np.maximum(tlim, t))
|
|
152
|
+
|
|
153
|
+
# done!
|
|
154
|
+
return xm
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Univariate copulas module."""
|
|
2
|
+
|
|
3
|
+
from copulas.univariate.base import BoundedType, ParametricType, Univariate
|
|
4
|
+
from copulas.univariate.beta import BetaUnivariate
|
|
5
|
+
from copulas.univariate.gamma import GammaUnivariate
|
|
6
|
+
from copulas.univariate.gaussian import GaussianUnivariate
|
|
7
|
+
from copulas.univariate.gaussian_kde import GaussianKDE
|
|
8
|
+
from copulas.univariate.log_laplace import LogLaplace
|
|
9
|
+
from copulas.univariate.student_t import StudentTUnivariate
|
|
10
|
+
from copulas.univariate.truncated_gaussian import TruncatedGaussian
|
|
11
|
+
from copulas.univariate.uniform import UniformUnivariate
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
'BetaUnivariate',
|
|
15
|
+
'GammaUnivariate',
|
|
16
|
+
'GaussianKDE',
|
|
17
|
+
'GaussianUnivariate',
|
|
18
|
+
'TruncatedGaussian',
|
|
19
|
+
'StudentTUnivariate',
|
|
20
|
+
'Univariate',
|
|
21
|
+
'ParametricType',
|
|
22
|
+
'BoundedType',
|
|
23
|
+
'UniformUnivariate',
|
|
24
|
+
'LogLaplace',
|
|
25
|
+
)
|