skfolio 0.7.0__py3-none-any.whl → 0.8.0__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.
- skfolio/__init__.py +2 -2
- skfolio/cluster/__init__.py +1 -1
- skfolio/cluster/_hierarchical.py +1 -1
- skfolio/datasets/__init__.py +1 -1
- skfolio/datasets/_base.py +2 -2
- skfolio/datasets/data/__init__.py +1 -0
- skfolio/distance/__init__.py +1 -1
- skfolio/distance/_base.py +2 -2
- skfolio/distance/_distance.py +4 -4
- skfolio/distribution/__init__.py +56 -0
- skfolio/distribution/_base.py +203 -0
- skfolio/distribution/copula/__init__.py +35 -0
- skfolio/distribution/copula/_base.py +456 -0
- skfolio/distribution/copula/_clayton.py +539 -0
- skfolio/distribution/copula/_gaussian.py +407 -0
- skfolio/distribution/copula/_gumbel.py +560 -0
- skfolio/distribution/copula/_independent.py +196 -0
- skfolio/distribution/copula/_joe.py +609 -0
- skfolio/distribution/copula/_selection.py +111 -0
- skfolio/distribution/copula/_student_t.py +486 -0
- skfolio/distribution/copula/_utils.py +509 -0
- skfolio/distribution/multivariate/__init__.py +11 -0
- skfolio/distribution/multivariate/_base.py +241 -0
- skfolio/distribution/multivariate/_utils.py +632 -0
- skfolio/distribution/multivariate/_vine_copula.py +1254 -0
- skfolio/distribution/univariate/__init__.py +19 -0
- skfolio/distribution/univariate/_base.py +308 -0
- skfolio/distribution/univariate/_gaussian.py +136 -0
- skfolio/distribution/univariate/_johnson_su.py +152 -0
- skfolio/distribution/univariate/_normal_inverse_gaussian.py +153 -0
- skfolio/distribution/univariate/_selection.py +85 -0
- skfolio/distribution/univariate/_student_t.py +144 -0
- skfolio/exceptions.py +6 -6
- skfolio/measures/__init__.py +1 -1
- skfolio/measures/_enums.py +7 -7
- skfolio/measures/_measures.py +4 -7
- skfolio/metrics/__init__.py +2 -0
- skfolio/metrics/_scorer.py +4 -4
- skfolio/model_selection/__init__.py +2 -2
- skfolio/model_selection/_combinatorial.py +15 -12
- skfolio/model_selection/_validation.py +2 -2
- skfolio/model_selection/_walk_forward.py +3 -3
- skfolio/moments/covariance/_base.py +1 -1
- skfolio/moments/covariance/_denoise_covariance.py +1 -1
- skfolio/moments/covariance/_detone_covariance.py +1 -1
- skfolio/moments/covariance/_empirical_covariance.py +1 -1
- skfolio/moments/covariance/_ew_covariance.py +1 -1
- skfolio/moments/covariance/_gerber_covariance.py +1 -1
- skfolio/moments/covariance/_graphical_lasso_cv.py +1 -1
- skfolio/moments/covariance/_implied_covariance.py +2 -7
- skfolio/moments/covariance/_ledoit_wolf.py +1 -1
- skfolio/moments/covariance/_oas.py +1 -1
- skfolio/moments/covariance/_shrunk_covariance.py +1 -1
- skfolio/moments/expected_returns/_base.py +1 -1
- skfolio/moments/expected_returns/_empirical_mu.py +1 -1
- skfolio/moments/expected_returns/_equilibrium_mu.py +1 -1
- skfolio/moments/expected_returns/_ew_mu.py +1 -1
- skfolio/moments/expected_returns/_shrunk_mu.py +2 -2
- skfolio/optimization/__init__.py +2 -0
- skfolio/optimization/_base.py +2 -2
- skfolio/optimization/cluster/__init__.py +2 -0
- skfolio/optimization/cluster/_nco.py +7 -7
- skfolio/optimization/cluster/hierarchical/__init__.py +2 -0
- skfolio/optimization/cluster/hierarchical/_base.py +1 -2
- skfolio/optimization/cluster/hierarchical/_herc.py +2 -2
- skfolio/optimization/cluster/hierarchical/_hrp.py +2 -2
- skfolio/optimization/convex/__init__.py +2 -0
- skfolio/optimization/convex/_base.py +8 -8
- skfolio/optimization/convex/_distributionally_robust.py +4 -4
- skfolio/optimization/convex/_maximum_diversification.py +5 -5
- skfolio/optimization/convex/_mean_risk.py +5 -6
- skfolio/optimization/convex/_risk_budgeting.py +3 -3
- skfolio/optimization/ensemble/__init__.py +2 -0
- skfolio/optimization/ensemble/_base.py +2 -2
- skfolio/optimization/ensemble/_stacking.py +1 -1
- skfolio/optimization/naive/__init__.py +2 -0
- skfolio/optimization/naive/_naive.py +1 -1
- skfolio/population/__init__.py +2 -0
- skfolio/population/_population.py +34 -7
- skfolio/portfolio/_base.py +42 -8
- skfolio/portfolio/_multi_period_portfolio.py +3 -2
- skfolio/portfolio/_portfolio.py +4 -4
- skfolio/pre_selection/__init__.py +2 -0
- skfolio/pre_selection/_drop_correlated.py +2 -2
- skfolio/pre_selection/_select_complete.py +25 -26
- skfolio/pre_selection/_select_k_extremes.py +2 -2
- skfolio/pre_selection/_select_non_dominated.py +2 -2
- skfolio/pre_selection/_select_non_expiring.py +2 -2
- skfolio/preprocessing/__init__.py +2 -0
- skfolio/preprocessing/_returns.py +2 -2
- skfolio/prior/__init__.py +4 -0
- skfolio/prior/_base.py +2 -2
- skfolio/prior/_black_litterman.py +5 -3
- skfolio/prior/_empirical.py +3 -1
- skfolio/prior/_factor_model.py +8 -4
- skfolio/prior/_synthetic_data.py +239 -0
- skfolio/synthetic_returns/__init__.py +1 -0
- skfolio/typing.py +1 -1
- skfolio/uncertainty_set/__init__.py +2 -0
- skfolio/uncertainty_set/_base.py +2 -2
- skfolio/uncertainty_set/_bootstrap.py +1 -1
- skfolio/uncertainty_set/_empirical.py +1 -1
- skfolio/utils/__init__.py +1 -0
- skfolio/utils/bootstrap.py +2 -2
- skfolio/utils/equations.py +13 -10
- skfolio/utils/sorting.py +2 -2
- skfolio/utils/stats.py +7 -7
- skfolio/utils/tools.py +76 -12
- {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/METADATA +99 -24
- skfolio-0.8.0.dist-info/RECORD +120 -0
- {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/WHEEL +1 -1
- skfolio-0.7.0.dist-info/RECORD +0 -95
- {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info/licenses}/LICENSE +0 -0
- {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,456 @@
|
|
1
|
+
"""Base Bivariate Copula Estimator."""
|
2
|
+
|
3
|
+
# Copyright (c) 2025
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# Credits: Matteo Manzi, Vincent Maladière, Carlo Nicolini
|
6
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import numpy.typing as npt
|
12
|
+
import plotly.graph_objects as go
|
13
|
+
import sklearn.utils as sku
|
14
|
+
import sklearn.utils.validation as skv
|
15
|
+
|
16
|
+
from skfolio.distribution._base import BaseDistribution
|
17
|
+
from skfolio.distribution.copula._utils import (
|
18
|
+
empirical_tail_concentration,
|
19
|
+
plot_tail_concentration,
|
20
|
+
)
|
21
|
+
|
22
|
+
UNIFORM_MARGINAL_EPSILON = 1e-9
|
23
|
+
_RHO_BOUNDS = (-0.999, 0.999)
|
24
|
+
|
25
|
+
|
26
|
+
class BaseBivariateCopula(BaseDistribution, ABC):
|
27
|
+
"""Base class for Bivariate Copula Estimators.
|
28
|
+
|
29
|
+
This abstract class defines the interface for bivariate copula models, including
|
30
|
+
methods for fitting, sampling, scoring, and computing partial derivatives.
|
31
|
+
|
32
|
+
Parameters
|
33
|
+
----------
|
34
|
+
random_state : int, RandomState instance or None, default=None
|
35
|
+
Seed or random state to ensure reproducibility.
|
36
|
+
"""
|
37
|
+
|
38
|
+
# Used for AIC and BIC
|
39
|
+
_n_params: int
|
40
|
+
|
41
|
+
def __init__(self, random_state: int | None = None):
|
42
|
+
super().__init__(random_state=random_state)
|
43
|
+
|
44
|
+
def _validate_X(self, X: npt.ArrayLike, reset: bool) -> np.ndarray:
|
45
|
+
"""Validate the input data.
|
46
|
+
|
47
|
+
Parameters
|
48
|
+
----------
|
49
|
+
X : array-like of shape (n_observations, 2)
|
50
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
51
|
+
bivariate observation. Both `u` and `v` must be in the interval `[0, 1]`.
|
52
|
+
|
53
|
+
reset : bool, default=True
|
54
|
+
Whether to reset the `n_features_in_` attribute.
|
55
|
+
If False, the input will be checked for consistency with data
|
56
|
+
provided when reset was last True.
|
57
|
+
|
58
|
+
Returns
|
59
|
+
-------
|
60
|
+
validated_X: ndarray of shape (n_observations, 2)
|
61
|
+
The validated data array.
|
62
|
+
|
63
|
+
Raises
|
64
|
+
------
|
65
|
+
ValueError
|
66
|
+
If input data is invalid (e.g., not in `[0, 1]` or incorrect shape).
|
67
|
+
"""
|
68
|
+
X = skv.validate_data(self, X, dtype=np.float64, reset=reset)
|
69
|
+
if X.shape[1] != 2:
|
70
|
+
raise ValueError("X must contains two columns for Bivariate Copula")
|
71
|
+
if not np.all((X >= 0) & (X <= 1)):
|
72
|
+
raise ValueError(
|
73
|
+
"X must be in the interval `[0, 1]`, usually reprinting uniform "
|
74
|
+
"distributions obtained from marginals CDF transformation"
|
75
|
+
)
|
76
|
+
|
77
|
+
# Handle potential numerical issues by ensuring X doesn't contain exact 0 or 1.
|
78
|
+
X = np.clip(X, UNIFORM_MARGINAL_EPSILON, 1 - UNIFORM_MARGINAL_EPSILON)
|
79
|
+
return X
|
80
|
+
|
81
|
+
@property
|
82
|
+
def n_params(self) -> int:
|
83
|
+
"""Number of model parameters."""
|
84
|
+
return self._n_params
|
85
|
+
|
86
|
+
@property
|
87
|
+
@abstractmethod
|
88
|
+
def lower_tail_dependence(self) -> float:
|
89
|
+
"""Theoretical lower tail dependence coefficient."""
|
90
|
+
pass
|
91
|
+
|
92
|
+
@property
|
93
|
+
@abstractmethod
|
94
|
+
def upper_tail_dependence(self) -> float:
|
95
|
+
"""Theoretical upper tail dependence coefficient."""
|
96
|
+
pass
|
97
|
+
|
98
|
+
@property
|
99
|
+
@abstractmethod
|
100
|
+
def fitted_repr(self) -> str:
|
101
|
+
"""String representation of the fitted copula."""
|
102
|
+
pass
|
103
|
+
|
104
|
+
@abstractmethod
|
105
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "BaseBivariateCopula":
|
106
|
+
"""Fit the copula model.
|
107
|
+
|
108
|
+
Parameters
|
109
|
+
----------
|
110
|
+
X : array-like of shape (n_observations, 2)
|
111
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
112
|
+
bivariate observation. Both `u` and `v` must be in the interval [0, 1],
|
113
|
+
having been transformed to uniform marginals.
|
114
|
+
|
115
|
+
y : None
|
116
|
+
Ignored. Provided for compatibility with scikit-learn's API.
|
117
|
+
|
118
|
+
Returns
|
119
|
+
-------
|
120
|
+
self : BaseBivariateCopula
|
121
|
+
Returns the instance itself.
|
122
|
+
"""
|
123
|
+
pass
|
124
|
+
|
125
|
+
@abstractmethod
|
126
|
+
def cdf(self, X: npt.ArrayLike) -> np.ndarray:
|
127
|
+
"""Compute the CDF of the bivariate copula.
|
128
|
+
|
129
|
+
Parameters
|
130
|
+
----------
|
131
|
+
X : array-like of shape (n_observations, 2)
|
132
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
133
|
+
bivariate observation. Both `u` and `v` must be in the interval `[0, 1]`,
|
134
|
+
having been transformed to uniform marginals.
|
135
|
+
|
136
|
+
Returns
|
137
|
+
-------
|
138
|
+
cdf : ndarray of shape (n_observations,)
|
139
|
+
CDF values for each observation in X.
|
140
|
+
"""
|
141
|
+
pass
|
142
|
+
|
143
|
+
@abstractmethod
|
144
|
+
def partial_derivative(
|
145
|
+
self, X: npt.ArrayLike, first_margin: bool = False
|
146
|
+
) -> np.ndarray:
|
147
|
+
r"""Compute the h-function (partial derivative) for the bivariate copula
|
148
|
+
with respect to a specified margin.
|
149
|
+
|
150
|
+
The h-function with respect to the second margin represents the conditional
|
151
|
+
distribution function of :math:`u` given :math:`v`:
|
152
|
+
|
153
|
+
.. math::
|
154
|
+
h(u \mid v) = \frac{\partial C(u,v)}{\partial v}
|
155
|
+
|
156
|
+
Parameters
|
157
|
+
----------
|
158
|
+
X : array-like of shape (n_observations, 2)
|
159
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
160
|
+
bivariate observation. Both `u` and `v` must be in the interval `[0, 1]`,
|
161
|
+
having been transformed to uniform marginals.
|
162
|
+
|
163
|
+
first_margin : bool, default=False
|
164
|
+
If True, compute the partial derivative with respect to the first
|
165
|
+
margin `u`; otherwise, compute the partial derivative with respect to the
|
166
|
+
second margin `v`.
|
167
|
+
|
168
|
+
Returns
|
169
|
+
-------
|
170
|
+
p : ndarray of shape (n_observations,)
|
171
|
+
h-function values :math:`h(u \mid v) \;=\; p` for each observation in X.
|
172
|
+
"""
|
173
|
+
pass
|
174
|
+
|
175
|
+
@abstractmethod
|
176
|
+
def inverse_partial_derivative(
|
177
|
+
self, X: npt.ArrayLike, first_margin: bool = False
|
178
|
+
) -> np.ndarray:
|
179
|
+
r"""Compute the inverse of the bivariate copula's partial derivative, commonly
|
180
|
+
known as the inverse h-function [1]_.
|
181
|
+
|
182
|
+
Let :math:`C(u, v)` be a bivariate copula. The h-function with respect to the
|
183
|
+
second margin is defined by
|
184
|
+
|
185
|
+
.. math::
|
186
|
+
h(u \mid v) \;=\; \frac{\partial\,C(u, v)}{\partial\,v},
|
187
|
+
|
188
|
+
which is the conditional distribution of :math:`U` given :math:`V = v`.
|
189
|
+
The **inverse h-function**, denoted :math:`h^{-1}(p \mid v)`, is the unique
|
190
|
+
value :math:`u \in [0,1]` such that
|
191
|
+
|
192
|
+
.. math::
|
193
|
+
h(u \mid v) \;=\; p,
|
194
|
+
\quad \text{where } p \in [0,1].
|
195
|
+
|
196
|
+
In practical terms, given :math:`(p, v)` in :math:`[0, 1]^2`,
|
197
|
+
:math:`h^{-1}(p \mid v)` solves for the :math:`u` satisfying
|
198
|
+
:math:`p = \partial C(u, v)/\partial v`.
|
199
|
+
|
200
|
+
Parameters
|
201
|
+
----------
|
202
|
+
X : array-like of shape (n_observations, 2)
|
203
|
+
An array of bivariate inputs `(p, v)`, each in the interval `[0, 1]`.
|
204
|
+
- The first column `p` corresponds to the value of the h-function.
|
205
|
+
- The second column `v` is the conditioning variable.
|
206
|
+
|
207
|
+
first_margin : bool, default=False
|
208
|
+
If True, compute the inverse partial derivative with respect to the first
|
209
|
+
margin `u`; otherwise, compute the inverse partial derivative with respect
|
210
|
+
to the second margin `v`.
|
211
|
+
|
212
|
+
Returns
|
213
|
+
-------
|
214
|
+
u : ndarray of shape (n_observations,)
|
215
|
+
A 1D-array of length `n_observations`, where each element is the computed
|
216
|
+
:math:`u = h^{-1}(p \mid v)` for the corresponding pair in `X`.
|
217
|
+
|
218
|
+
References
|
219
|
+
----------
|
220
|
+
.. [1] "Multivariate Models and Dependence Concepts", Joe, H. (1997)
|
221
|
+
.. [2] "An Introduction to Copulas", Nelsen, R. B. (2006)
|
222
|
+
"""
|
223
|
+
pass
|
224
|
+
|
225
|
+
@abstractmethod
|
226
|
+
def score_samples(self, X: npt.ArrayLike) -> np.ndarray:
|
227
|
+
"""Compute the log-likelihood of each sample (log-pdf) under the model.
|
228
|
+
|
229
|
+
Parameters
|
230
|
+
----------
|
231
|
+
X : array-like of shape (n_observations, 2)
|
232
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
233
|
+
bivariate observation. Both `u` and `v` must be in the interval `[0, 1]`,
|
234
|
+
having been transformed to uniform marginals.
|
235
|
+
|
236
|
+
Returns
|
237
|
+
-------
|
238
|
+
density : ndarray of shape (n_observations,)
|
239
|
+
The log-likelihood of each sample under the fitted copula.
|
240
|
+
"""
|
241
|
+
pass
|
242
|
+
|
243
|
+
def sample(self, n_samples: int = 1):
|
244
|
+
"""Generate random samples from the bivariate copula using the inverse
|
245
|
+
Rosenblatt transform.
|
246
|
+
|
247
|
+
Parameters
|
248
|
+
----------
|
249
|
+
n_samples : int, default=1
|
250
|
+
Number of samples to generate.
|
251
|
+
|
252
|
+
Returns
|
253
|
+
-------
|
254
|
+
X : array-like of shape (n_samples, 2)
|
255
|
+
An array of bivariate inputs `(u, v)` where each row represents a
|
256
|
+
bivariate observation. Both `u` and `v` are uniform marginals in the
|
257
|
+
interval `[0, 1]`.
|
258
|
+
"""
|
259
|
+
skv.check_is_fitted(self)
|
260
|
+
rng = sku.check_random_state(self.random_state)
|
261
|
+
|
262
|
+
# Generate independent Uniform(0, 1) samples
|
263
|
+
X = rng.random(size=(n_samples, 2))
|
264
|
+
|
265
|
+
# Apply the inverse Rosenblatt transform on the first variable.
|
266
|
+
X[:, 1] = self.inverse_partial_derivative(X, first_margin=True)
|
267
|
+
return X
|
268
|
+
|
269
|
+
def tail_concentration(self, quantiles: np.ndarray) -> np.ndarray:
|
270
|
+
"""
|
271
|
+
Compute the tail concentration function for a set of quantiles.
|
272
|
+
|
273
|
+
The tail concentration function is defined as follows:
|
274
|
+
- For quantiles q ≤ 0.5:
|
275
|
+
C(q) = P(U ≤ q, V ≤ q) / q
|
276
|
+
|
277
|
+
- For quantiles q > 0.5:
|
278
|
+
C(q) = (1 - 2q + P(U ≤ q, V ≤ q)) / (1 - q)
|
279
|
+
|
280
|
+
where U and V are the pseudo-observations of the first and second variables,
|
281
|
+
respectively. This function returns the concentration values for each q
|
282
|
+
provided.
|
283
|
+
|
284
|
+
Parameters
|
285
|
+
----------
|
286
|
+
quantiles : ndarray of shape (n_quantiles,)
|
287
|
+
A 1D array of quantile levels (values between 0 and 1) at which to compute
|
288
|
+
the tail concentration.
|
289
|
+
|
290
|
+
Returns
|
291
|
+
-------
|
292
|
+
concentration : ndarray of shape (n_quantiles,)
|
293
|
+
The computed tail concentration values corresponding to each quantile.
|
294
|
+
|
295
|
+
References
|
296
|
+
----------
|
297
|
+
.. [1] "Quantitative Risk Management: Concepts, Techniques, and Tools",
|
298
|
+
McNeil, Frey, Embrechts (2005)
|
299
|
+
|
300
|
+
Raises
|
301
|
+
------
|
302
|
+
ValueError
|
303
|
+
If any value in `quantiles` is not in the interval [0, 1].
|
304
|
+
"""
|
305
|
+
quantiles = np.asarray(quantiles)
|
306
|
+
if not np.all((quantiles >= 0) & (quantiles <= 1)):
|
307
|
+
raise ValueError("quantiles must be between 0.0 and 1.0.")
|
308
|
+
X = np.stack((quantiles, quantiles)).T
|
309
|
+
cdf = self.cdf(X)
|
310
|
+
concentration = np.where(
|
311
|
+
quantiles <= 0.5,
|
312
|
+
cdf / quantiles,
|
313
|
+
(1.0 - 2 * quantiles + cdf) / (1.0 - quantiles),
|
314
|
+
)
|
315
|
+
return concentration
|
316
|
+
|
317
|
+
def plot_tail_concentration(
|
318
|
+
self, X: npt.ArrayLike | None = None, title: str | None = None
|
319
|
+
) -> go.Figure:
|
320
|
+
"""
|
321
|
+
Plot the tail concentration function.
|
322
|
+
|
323
|
+
This method computes the tail concentration function at 100 evenly spaced
|
324
|
+
quantile levels between 0.005 and 0.995.
|
325
|
+
The plot displays the concentration values on the y-axis and the quantile levels
|
326
|
+
on the x-axis.
|
327
|
+
|
328
|
+
The tail concentration is defined as:
|
329
|
+
- Lower tail: λ_L(q) = P(U₂ ≤ q | U₁ ≤ q)
|
330
|
+
- Upper tail: λ_U(q) = P(U₂ ≥ q | U₁ ≥ q)
|
331
|
+
|
332
|
+
where U₁ and U₂ are the pseudo-observations of the first and second variables,
|
333
|
+
respectively.
|
334
|
+
|
335
|
+
Parameters
|
336
|
+
----------
|
337
|
+
X : array-like of shape (n_samples, 2), optional
|
338
|
+
If provided, it is used to plot the empirical tail concentration for
|
339
|
+
comparison versus the model tail concentration.
|
340
|
+
|
341
|
+
title : str, optional
|
342
|
+
The title for the plot. If not provided, a default title based on the fitted
|
343
|
+
copula's representation is used.
|
344
|
+
|
345
|
+
Returns
|
346
|
+
-------
|
347
|
+
fig : go.Figure
|
348
|
+
A Plotly figure object containing the tail concentration curve.
|
349
|
+
|
350
|
+
References
|
351
|
+
----------
|
352
|
+
.. [1] "Quantitative Risk Management: Concepts, Techniques, and Tools",
|
353
|
+
McNeil, Frey, Embrechts (2005)
|
354
|
+
"""
|
355
|
+
if title is None:
|
356
|
+
title = f"Tail Concentration of Bivariate {self.__class__.__name__}"
|
357
|
+
if X is not None:
|
358
|
+
title += " vs Empirical"
|
359
|
+
|
360
|
+
quantiles = np.linspace(5e-3, 1.0 - 5e-3, num=100)
|
361
|
+
concentration = self.tail_concentration(quantiles)
|
362
|
+
|
363
|
+
tail_concentration_dict = {self.__class__.__name__: concentration}
|
364
|
+
if X is not None:
|
365
|
+
tail_concentration_dict["Empirical"] = empirical_tail_concentration(
|
366
|
+
X, quantiles=quantiles
|
367
|
+
)
|
368
|
+
|
369
|
+
fig = plot_tail_concentration(
|
370
|
+
tail_concentration_dict=tail_concentration_dict,
|
371
|
+
quantiles=quantiles,
|
372
|
+
title=title,
|
373
|
+
smoothing=1.3,
|
374
|
+
)
|
375
|
+
return fig
|
376
|
+
|
377
|
+
def plot_pdf_2d(self, title: str | None = None) -> go.Figure:
|
378
|
+
"""
|
379
|
+
Plot a 2D contour of the estimated probability density function (PDF).
|
380
|
+
|
381
|
+
This method generates a grid over [0, 1]^2, computes the PDF, and displays a
|
382
|
+
contour plot of the PDF.
|
383
|
+
Contour levels are limited to the 97th quantile to avoid extreme densities.
|
384
|
+
|
385
|
+
Parameters
|
386
|
+
----------
|
387
|
+
title : str, optional
|
388
|
+
The title for the plot. If not provided, a default title based on the fitted
|
389
|
+
copula's representation is used.
|
390
|
+
|
391
|
+
Returns
|
392
|
+
-------
|
393
|
+
fig : go.Figure
|
394
|
+
A Plotly figure object containing the 2D contour plot of the PDF.
|
395
|
+
"""
|
396
|
+
skv.check_is_fitted(self)
|
397
|
+
|
398
|
+
if title is None:
|
399
|
+
title = f"PDF of the Bivariate {self.__class__.__name__}"
|
400
|
+
|
401
|
+
u = np.linspace(0.01, 0.99, 100)
|
402
|
+
U, V = np.meshgrid(u, u)
|
403
|
+
grid_points = np.column_stack((U.ravel(), V.ravel()))
|
404
|
+
pdfs = np.exp(self.score_samples(grid_points)).reshape(U.shape)
|
405
|
+
# After the 97th quantile, the pdf gets too dense, and it dilutes the plot.
|
406
|
+
end = round(np.quantile(pdfs, 0.97), 1)
|
407
|
+
fig = go.Figure(
|
408
|
+
data=go.Contour(
|
409
|
+
x=u,
|
410
|
+
y=u,
|
411
|
+
z=pdfs,
|
412
|
+
colorscale="Magma",
|
413
|
+
contours=dict(start=0, end=end, size=0.2),
|
414
|
+
line=dict(width=0),
|
415
|
+
colorbar=dict(title="PDF"),
|
416
|
+
)
|
417
|
+
)
|
418
|
+
fig.update_layout(
|
419
|
+
title=title,
|
420
|
+
xaxis_title="u",
|
421
|
+
yaxis_title="v",
|
422
|
+
)
|
423
|
+
return fig
|
424
|
+
|
425
|
+
def plot_pdf_3d(self, title: str | None = None) -> go.Figure:
|
426
|
+
"""
|
427
|
+
Plot a 3D surface of the estimated probability density function (PDF).
|
428
|
+
|
429
|
+
This method generates a grid over [0, 1]^2, computes the PDF, and displays a
|
430
|
+
3D surface plot of the PDF using Plotly.
|
431
|
+
|
432
|
+
Parameters
|
433
|
+
----------
|
434
|
+
title : str, optional
|
435
|
+
The title for the plot. If not provided, a default title based on the fitted
|
436
|
+
copula's representation is used.
|
437
|
+
|
438
|
+
Returns
|
439
|
+
-------
|
440
|
+
fig : go.Figure
|
441
|
+
A Plotly figure object containing a 3D surface plot of the PDF.
|
442
|
+
"""
|
443
|
+
skv.check_is_fitted(self)
|
444
|
+
|
445
|
+
if title is None:
|
446
|
+
title = f"PDF of the Bivariate {self.__class__.__name__}"
|
447
|
+
|
448
|
+
u = np.linspace(0.03, 0.97, 100)
|
449
|
+
U, V = np.meshgrid(u, u)
|
450
|
+
grid_points = np.column_stack((U.ravel(), V.ravel()))
|
451
|
+
pdfs = np.exp(self.score_samples(grid_points)).reshape(U.shape)
|
452
|
+
fig = go.Figure(data=[go.Surface(x=U, y=V, z=pdfs, colorscale="Magma")])
|
453
|
+
fig.update_layout(
|
454
|
+
title=title, scene=dict(xaxis_title="u", yaxis_title="v", zaxis_title="PDF")
|
455
|
+
)
|
456
|
+
return fig
|