funcsim 0.2.4__tar.gz → 0.2.6__tar.gz

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.
Files changed (37) hide show
  1. {funcsim-0.2.4/funcsim.egg-info → funcsim-0.2.6}/PKG-INFO +2 -2
  2. {funcsim-0.2.4 → funcsim-0.2.6}/README.md +1 -1
  3. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/__init__.py +4 -2
  4. funcsim-0.2.6/funcsim/copcompare.py +116 -0
  5. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/copfit.py +26 -6
  6. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/dependence.py +402 -9
  7. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/nearby.py +2 -2
  8. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/shrinkage.py +26 -11
  9. {funcsim-0.2.4 → funcsim-0.2.6/funcsim.egg-info}/PKG-INFO +2 -2
  10. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim.egg-info/SOURCES.txt +1 -0
  11. {funcsim-0.2.4 → funcsim-0.2.6}/setup.py +1 -1
  12. {funcsim-0.2.4 → funcsim-0.2.6}/LICENSE +0 -0
  13. {funcsim-0.2.4 → funcsim-0.2.6}/MANIFEST.in +0 -0
  14. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/addist.py +0 -0
  15. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/conversions.py +0 -0
  16. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/core.py +0 -0
  17. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/cpt.py +0 -0
  18. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/cvmdist.py +0 -0
  19. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/distfit.py +0 -0
  20. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/ecdfgof.py +0 -0
  21. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/edf.py +0 -0
  22. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/eut.py +0 -0
  23. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/imanconover.py +0 -0
  24. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/kde.py +0 -0
  25. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/ksdist.py +0 -0
  26. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/multicore.py +0 -0
  27. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/plotting.py +0 -0
  28. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/rdarrays.py +0 -0
  29. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/screen.py +0 -0
  30. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/shapiro.py +0 -0
  31. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/testsim.py +0 -0
  32. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/utests.py +0 -0
  33. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim/vect.py +0 -0
  34. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim.egg-info/dependency_links.txt +0 -0
  35. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim.egg-info/requires.txt +0 -0
  36. {funcsim-0.2.4 → funcsim-0.2.6}/funcsim.egg-info/top_level.txt +0 -0
  37. {funcsim-0.2.4 → funcsim-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funcsim
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: functional, simple stochastic simulation
5
5
  Home-page: https://github.com/h-bryant/funcsim
6
6
  Author: Henry Bryant
@@ -54,7 +54,7 @@ Features:
54
54
  - stratified/Latin hypercube sampling
55
55
  - painless multi-core simulations
56
56
  - fitting and comparison of univariate distributions, kernel density estimates, and goodness-of-fit tests
57
- - multivariate normal and KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas (implemented natively, no additional packages required); Iman-Conover
57
+ - multivariate normal (fitted to data or specified by a mean vector and covariance matrix) and multivariate KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas, fitted to pseudo-observations or specified by their parameters (implemented natively, no additional packages required); information-criterion comparison of copula families; Iman-Conover; covariance shrinkage
58
58
  - expected utility and cumulative prospect theory calculations
59
59
  - plotly-based fan charts and diagnostic plots
60
60
  - highly flexible, and as simple as possible (but no more)
@@ -9,7 +9,7 @@ Features:
9
9
  - stratified/Latin hypercube sampling
10
10
  - painless multi-core simulations
11
11
  - fitting and comparison of univariate distributions, kernel density estimates, and goodness-of-fit tests
12
- - multivariate normal and KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas (implemented natively, no additional packages required); Iman-Conover
12
+ - multivariate normal (fitted to data or specified by a mean vector and covariance matrix) and multivariate KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas, fitted to pseudo-observations or specified by their parameters (implemented natively, no additional packages required); information-criterion comparison of copula families; Iman-Conover; covariance shrinkage
13
13
  - expected utility and cumulative prospect theory calculations
14
14
  - plotly-based fan charts and diagnostic plots
15
15
  - highly flexible, and as simple as possible (but no more)
@@ -10,6 +10,8 @@ from .dependence import CopulaClayton
10
10
  from .dependence import CopulaGumbel
11
11
  from .dependence import CopulaFrank
12
12
 
13
+ from .copcompare import copcompare
14
+
13
15
  from .imanconover import imanconover
14
16
 
15
17
  from .shrinkage import shrink
@@ -46,6 +48,6 @@ def version():
46
48
  Returns
47
49
  -------
48
50
  str
49
- The version string of the package (e.g., '0.2.4').
51
+ The version string of the package (e.g., '0.2.6').
50
52
  """
51
- return "0.2.4"
53
+ return "0.2.6"
@@ -0,0 +1,116 @@
1
+ """
2
+ Information-criteria comparison of funcsim's copula families.
3
+ """
4
+
5
+ import math
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from . import conversions
11
+ from . import copfit
12
+ from .dependence import (CopulaGauss, CopulaStudent, CopulaClayton,
13
+ CopulaGumbel, CopulaFrank)
14
+
15
+
16
+ def _fmt(x: float) -> str:
17
+ return f"{x:.3f}"
18
+
19
+
20
+ def _rho_label(rho: np.ndarray) -> str:
21
+ # a scalar for two variables, otherwise just the matrix size
22
+ K = rho.shape[0]
23
+ if K == 2:
24
+ return f"rho={_fmt(rho[0, 1])}"
25
+ return f"rho: {K}x{K} matrix"
26
+
27
+
28
+ def copcompare(udata: conversions.ArrayLike) -> pd.DataFrame:
29
+ """
30
+ Compare fits of funcsim's copula families to pseudo-observations.
31
+
32
+ Each family (Gaussian, Student's t, Clayton, Gumbel, Frank) is fitted
33
+ to `udata` exactly as the corresponding copula class would fit it, its
34
+ copula log-likelihood (the pseudo-likelihood of Genest, Ghoudi, and
35
+ Rivest, 1995) is evaluated at those fitted parameters, and the Akaike
36
+ and Bayesian information criteria are computed from it.
37
+
38
+ Parameters
39
+ ----------
40
+ udata : ArrayLike
41
+ Pseudo-observations (marginal CDF values) with variables in columns
42
+ and observations in rows; every value must lie in (0, 1).
43
+
44
+ Returns
45
+ -------
46
+ pd.DataFrame
47
+ One row per family, indexed by family name and sorted by BIC from
48
+ best (smallest) to worst, with columns ``parameters`` (the fitted
49
+ values as text), ``n_params``, ``loglik``, ``AIC``, and ``BIC``.
50
+
51
+ Notes
52
+ -----
53
+ The parameters are the ones the copula classes use: normal-scores
54
+ correlation for the Gaussian copula, Kendall's-tau inversion plus a
55
+ profile likelihood for the degrees of freedom for the Student's t copula,
56
+ and maximum pseudo-likelihood for the Archimedean families. The
57
+ Gaussian and Student's t log-likelihoods are therefore evaluated at
58
+ moment-type estimates rather than at the exact maximum, so their
59
+ criteria are conservative by a small amount. Standard information
60
+ criteria applied to pseudo-likelihoods are approximate (Grønneberg and
61
+ Hjort, 2014); treat differences of a few points as ties. Warnings
62
+ issued while fitting a family (for example, negative dependence in data
63
+ handed to an Archimedean family that represents only positive
64
+ dependence) are passed through.
65
+
66
+ References
67
+ ----------
68
+ Genest, C., Ghoudi, K., & Rivest, L.-P. (1995). A semiparametric
69
+ estimation procedure of dependence parameters in multivariate families
70
+ of distributions. Biometrika, 82(3), 543-552.
71
+
72
+ Grønneberg, S., & Hjort, N. L. (2014). The copula information criteria.
73
+ Scandinavian Journal of Statistics, 41(2), 436-459.
74
+
75
+ Examples
76
+ --------
77
+ >>> table = fs.copcompare(uh)
78
+ >>> print(table.round(2))
79
+ """
80
+ u = conversions.alToArray(udata)
81
+ if u.ndim != 2 or u.shape[1] < 2:
82
+ raise ValueError("udata must have at least two columns (variables)")
83
+ (M, K) = u.shape
84
+ npair = K * (K - 1) // 2
85
+
86
+ def row(name, parameters, n_params, loglik):
87
+ return {"copula": name,
88
+ "parameters": parameters,
89
+ "n_params": n_params,
90
+ "loglik": loglik,
91
+ "AIC": 2.0 * n_params - 2.0 * loglik,
92
+ "BIC": n_params * math.log(M) - 2.0 * loglik}
93
+
94
+ cg = CopulaGauss(u)
95
+ rho_g = cg.rho.to_numpy()
96
+ ct = CopulaStudent(u)
97
+ rho_t = ct.rho.to_numpy()
98
+ cc = CopulaClayton(u)
99
+ cgu = CopulaGumbel(u)
100
+ cf = CopulaFrank(u)
101
+ uc = copfit._clipu(u)
102
+
103
+ rows = [
104
+ row("Gaussian", _rho_label(rho_g), npair,
105
+ copfit.gauss_loglik(rho_g, u)),
106
+ row("Student", f"{_rho_label(rho_t)}, nu={_fmt(ct.nu)}", npair + 1,
107
+ copfit.student_loglik(rho_t, ct.nu, u)),
108
+ row("Clayton", f"theta={_fmt(cc.theta)}", 1,
109
+ copfit.clayton_loglik(cc.theta, uc)),
110
+ row("Gumbel", f"theta={_fmt(cgu.theta)}", 1,
111
+ copfit.gumbel_loglik(cgu.theta, uc)),
112
+ row("Frank", f"theta={_fmt(cf.theta)}", 1,
113
+ copfit.frank_loglik(cf.theta, uc)),
114
+ ]
115
+ table = pd.DataFrame(rows).set_index("copula")
116
+ return table.sort_values("BIC")
@@ -267,6 +267,31 @@ def fit_frank(u: np.ndarray) -> Tuple[float, float, bool]:
267
267
  return (theta, tb, False)
268
268
 
269
269
 
270
+ def gauss_loglik(R: np.ndarray, u: np.ndarray) -> float:
271
+ # total Gaussian copula log-density over the rows of u for the
272
+ # correlation matrix R:
273
+ # log c(u) = -0.5 log|R| - 0.5 z' (R^-1 - I) z, with z = Phi^-1(u)
274
+ uc = _clipu(u)
275
+ z = stats.norm.ppf(uc)
276
+ (M, d) = z.shape
277
+ (sign, logdet) = np.linalg.slogdet(R)
278
+ if sign <= 0:
279
+ raise ValueError("R must be positive definite")
280
+ quad = np.einsum("ij,jk,ik->i", z, np.linalg.inv(R) - np.eye(d), z)
281
+ return float(-0.5 * M * logdet - 0.5 * np.sum(quad))
282
+
283
+
284
+ def student_loglik(R: np.ndarray, nu: float, u: np.ndarray) -> float:
285
+ # total Student's t copula log-density over the rows of u for the
286
+ # correlation matrix R and degrees of freedom nu:
287
+ # log c(u) = log t_{nu,R}(x) - sum_k log t_nu(x_k), with x = t_nu^-1(u)
288
+ uc = _clipu(u)
289
+ x = stats.t.ppf(uc, df=nu)
290
+ d = R.shape[0]
291
+ joint = stats.multivariate_t(loc=np.zeros(d), shape=R, df=nu).logpdf(x)
292
+ return float(np.sum(joint) - np.sum(stats.t.logpdf(x, df=nu)))
293
+
294
+
270
295
  def fit_student(u: np.ndarray) -> Tuple[np.ndarray, float]:
271
296
  # fit a Student's t copula; returns (correlation matrix, deg. of freedom).
272
297
  # stage 1: pairwise Kendall's tau -> rho_ij = sin(pi tau_ij / 2),
@@ -281,17 +306,12 @@ def fit_student(u: np.ndarray) -> Tuple[np.ndarray, float]:
281
306
  R = R / np.outer(s, s) # renormalize to unit diagonal
282
307
  np.fill_diagonal(R, 1.0)
283
308
 
284
- mvt_dim = R.shape[0]
285
-
286
309
  def negll(lognu):
287
310
  nu = math.exp(lognu)
288
311
  try:
289
312
  with np.errstate(divide="ignore", invalid="ignore",
290
313
  over="ignore"):
291
- x = stats.t.ppf(uc, df=nu)
292
- ll = (stats.multivariate_t(loc=np.zeros(mvt_dim), shape=R,
293
- df=nu).logpdf(x).sum()
294
- - stats.t.logpdf(x, df=nu).sum())
314
+ ll = student_loglik(R, nu, uc)
295
315
  except Exception:
296
316
  return np.inf
297
317
  return -ll if np.isfinite(ll) else np.inf
@@ -2,7 +2,7 @@ import math
2
2
  import numpy as np
3
3
  import pandas as pd
4
4
  from scipy import stats
5
- from typing import Generator, Optional, Tuple
5
+ from typing import Generator, Optional, Sequence, Tuple
6
6
  import warnings
7
7
  from . import conversions
8
8
  from . import copfit
@@ -24,6 +24,75 @@ def _checkcov(cov, name):
24
24
  return cov
25
25
 
26
26
 
27
+ def _cop_names(K: int,
28
+ names: Optional[Sequence[str]],
29
+ source: Optional[pd.Index] = None,
30
+ ) -> pd.Index:
31
+ # variable names for a copula built from parameters rather than data:
32
+ # the explicit `names` if given, else the labels carried by the
33
+ # parameter object (`source`, e.g. a DataFrame's columns), else v0, v1, ...
34
+ if names is not None:
35
+ nms = pd.Index(list(names))
36
+ elif source is not None:
37
+ nms = pd.Index(source)
38
+ else:
39
+ nms = pd.Index([f"v{k}" for k in range(K)])
40
+ if len(nms) != K:
41
+ raise ValueError(f"names must have length {K}; got {len(nms)}")
42
+ return nms
43
+
44
+
45
+ def _corr_from_params(rho: conversions.ArrayLike) -> np.ndarray:
46
+ # validate a user-supplied correlation matrix for an elliptical copula:
47
+ # square, symmetric, unit diagonal. If it is not positive definite the
48
+ # nearest positive definite matrix (Higham, 1988) is substituted and
49
+ # renormalized to a unit diagonal
50
+ if isinstance(rho, (list, tuple)):
51
+ R = np.asarray(rho, dtype=float)
52
+ else:
53
+ R = np.asarray(conversions.alToArray(rho), dtype=float)
54
+ if R.ndim != 2 or R.shape[0] != R.shape[1]:
55
+ raise ValueError(f"rho must be a square matrix; got shape {R.shape}")
56
+ if not np.allclose(R, R.T):
57
+ raise ValueError("rho must be symmetric")
58
+ if not np.allclose(np.diag(R), 1.0):
59
+ raise ValueError("rho must have ones on its diagonal")
60
+ Rpd = nearby.nearestpd(0.5 * (R + R.T))
61
+ s = np.sqrt(np.diag(Rpd))
62
+ Rn = Rpd / np.outer(s, s)
63
+ np.fill_diagonal(Rn, 1.0)
64
+ Rn = 0.5 * (Rn + Rn.T)
65
+ # Higham's projection of an indefinite matrix has a zero eigenvalue at
66
+ # machine precision, and rescaling can push it slightly negative, so the
67
+ # Cholesky factorization used for draws may fail. Shrink toward the
68
+ # identity by the smallest power of ten that restores positive
69
+ # definiteness; the convex combination keeps the unit diagonal exactly
70
+ Id = np.eye(R.shape[0])
71
+ candidates = ((1.0 - eps) * Rn + eps * Id
72
+ for eps in (0.0, *(10.0 ** p for p in range(-14, -3))))
73
+ Rout = next(filter(nearby.is_positive_definite, candidates), None)
74
+ if Rout is None:
75
+ raise ValueError("rho could not be repaired to a positive definite "
76
+ "correlation matrix")
77
+ return Rout
78
+
79
+
80
+ def _param_labels(rho: conversions.ArrayLike) -> Optional[pd.Index]:
81
+ # column labels carried by a parameter matrix, if any
82
+ if isinstance(rho, pd.DataFrame):
83
+ return rho.columns
84
+ return None
85
+
86
+
87
+ def _positive_scalar(x: float, what: str, minimum: float = 0.0) -> float:
88
+ # validate a scalar copula parameter that must exceed `minimum`
89
+ xf = float(x)
90
+ if not (math.isfinite(xf) and xf > minimum):
91
+ raise ValueError(f"{what} must be a finite number greater than "
92
+ f"{minimum}; got {x}")
93
+ return xf
94
+
95
+
27
96
  def _rand_int(u, M):
28
97
  # given a standard uniform draw "u", select a
29
98
  # random integer from a length "M" sequece: 0, 1, ..., M-1
@@ -284,10 +353,12 @@ class MvKde():
284
353
 
285
354
  class MvNorm():
286
355
  """
287
- A multivariate normal distribution object. A vector of means
288
- and a covariance matrix are computed from the input data. If the sample
289
- covariance matrix is not positive definite, the Higham
290
- method is used to calculate the nearest positive definite matrix.
356
+ A multivariate normal distribution object. Constructed from data, a
357
+ vector of means and a covariance matrix are computed from the input.
358
+ Constructed with :meth:`from_params`, the mean vector and covariance
359
+ matrix are supplied directly. Either way, if the covariance matrix is
360
+ not positive definite, the Higham method is used to calculate the
361
+ nearest positive definite matrix.
291
362
 
292
363
  Parameters
293
364
  ----------
@@ -323,10 +394,80 @@ class MvNorm():
323
394
  swp = shapiro.swtest(self._data[:, k])[1]
324
395
  if not swp > 0.05:
325
396
  msg = (f"variable '{self._names[k]}' may not be "
326
- f" normally distributed. (Shapiro-Wilk p-value"
397
+ f"normally distributed. (Shapiro-Wilk p-value"
327
398
  f"={swp:.3f})")
328
399
  warnings.warn(msg, UserWarning)
329
400
 
401
+ @classmethod
402
+ def from_params(cls,
403
+ mu: conversions.VectorLike,
404
+ sigma: conversions.ArrayLike,
405
+ names: Optional[Sequence[str]] = None,
406
+ ) -> "MvNorm":
407
+ """
408
+ Create a multivariate normal distribution object from a mean vector
409
+ and a covariance matrix, rather than from data.
410
+
411
+ Parameters
412
+ ----------
413
+ mu : VectorLike
414
+ Mean vector of length K.
415
+ sigma : ArrayLike
416
+ K-by-K covariance matrix (a nested list is also accepted). It
417
+ must be symmetric. If it is not positive definite, the nearest
418
+ positive definite matrix (Higham, 1988) is substituted with a
419
+ warning.
420
+ names : sequence of str, optional
421
+ Variable names, length K. If omitted, the index of `mu` (if it
422
+ is a pandas Series) or the column labels of `sigma` (if it is a
423
+ pandas DataFrame) are used; otherwise the variables are named
424
+ 'v0', 'v1', ....
425
+
426
+ Returns
427
+ -------
428
+ MvNorm
429
+ A distribution object whose :meth:`draw` method returns joint
430
+ draws with the given means and covariances.
431
+
432
+ Notes
433
+ -----
434
+ No normality check is performed, since there are no data to check.
435
+
436
+ Examples
437
+ --------
438
+ >>> dist = fs.MvNorm.from_params([3.0, 4.0], [[1.0, 0.5], [0.5, 1.0]])
439
+ """
440
+ muA = np.asarray(conversions.vlToArray(mu), dtype=float)
441
+ if isinstance(sigma, (list, tuple)):
442
+ sigmaA = np.asarray(sigma, dtype=float)
443
+ else:
444
+ sigmaA = np.asarray(conversions.alToArray(sigma), dtype=float)
445
+ K = muA.shape[0]
446
+ if sigmaA.shape != (K, K):
447
+ raise ValueError(f"sigma must be {K}-by-{K} to match mu; "
448
+ f"got shape {sigmaA.shape}")
449
+ if not np.allclose(sigmaA, sigmaA.T):
450
+ raise ValueError("sigma must be symmetric")
451
+ if names is not None:
452
+ nms = pd.Index(list(names))
453
+ elif isinstance(mu, pd.Series):
454
+ nms = pd.Index(mu.index)
455
+ elif isinstance(sigma, pd.DataFrame):
456
+ nms = pd.Index(sigma.columns)
457
+ else:
458
+ nms = pd.Index([f"v{k}" for k in range(K)])
459
+ if len(nms) != K:
460
+ raise ValueError(f"names must have length {K}; got {len(nms)}")
461
+
462
+ obj = cls.__new__(cls)
463
+ obj._data = None
464
+ obj._names = nms
465
+ (obj._M, obj._K) = (0, K)
466
+ obj._mu = muA
467
+ obj._sigma = nearby.nearestpd(sigmaA)
468
+ obj._A = np.linalg.cholesky(obj._sigma)
469
+ return obj
470
+
330
471
  def draw(self,
331
472
  ugen: Generator[float, None, None]
332
473
  ) -> pd.Series:
@@ -344,9 +485,9 @@ class MvNorm():
344
485
  A pandas Series representing a joint draw from the multivariate
345
486
  normal distribution. The index values are the variable names,
346
487
  and the values are the random values. If no variable names were
347
- provided in the input data,
348
- the variables will be named 'v0', 'v1', ..., reflecting the
349
- order of the columns in the input data.
488
+ provided (as column labels of the input data, or through the
489
+ `names` argument of :meth:`from_params`), the variables are
490
+ named 'v0', 'v1', ..., in column order.
350
491
  """
351
492
  uvec = [next(ugen) for i in range(self._K)]
352
493
  retA = self._mu + np.dot(self._A, stats.norm.ppf(uvec))
@@ -410,6 +551,57 @@ class CopulaGauss():
410
551
  # cholesky decomposition of the correlation matrix, for draws
411
552
  self._A = np.linalg.cholesky(self._rho)
412
553
 
554
+ @classmethod
555
+ def from_params(cls,
556
+ rho: conversions.ArrayLike,
557
+ names: Optional[Sequence[str]] = None,
558
+ ) -> "CopulaGauss":
559
+ """
560
+ Create a Gaussian copula object from a correlation matrix, rather
561
+ than from pseudo-observations.
562
+
563
+ Parameters
564
+ ----------
565
+ rho : ArrayLike
566
+ K-by-K correlation matrix (a nested list is also accepted). It
567
+ must be symmetric with ones on its diagonal. If it is not
568
+ positive definite, the nearest positive definite matrix
569
+ (Higham, 1988) is substituted and rescaled to a unit diagonal.
570
+ names : sequence of str, optional
571
+ Variable names, length K. If omitted, the column labels of
572
+ `rho` (if it is a pandas DataFrame) are used; otherwise the
573
+ variables are named 'v0', 'v1', ....
574
+
575
+ Returns
576
+ -------
577
+ CopulaGauss
578
+ A copula object whose :meth:`draw` method returns joint standard
579
+ uniform draws with the given Gaussian dependence.
580
+
581
+ Examples
582
+ --------
583
+ >>> cop = fs.CopulaGauss.from_params([[1.0, 0.9], [0.9, 1.0]])
584
+ """
585
+ R = _corr_from_params(rho)
586
+ K = R.shape[0]
587
+ obj = cls.__new__(cls)
588
+ obj._data = None
589
+ obj._z = None
590
+ obj._names = _cop_names(K, names, _param_labels(rho))
591
+ (obj._M, obj._K) = (0, K)
592
+ obj._rho = R
593
+ obj._A = np.linalg.cholesky(obj._rho)
594
+ return obj
595
+
596
+ @property
597
+ def rho(self) -> pd.DataFrame:
598
+ """
599
+ The copula's correlation matrix parameter, as a DataFrame whose
600
+ index and columns are the variable names.
601
+ """
602
+ return pd.DataFrame(self._rho.copy(), index=self._names,
603
+ columns=self._names)
604
+
413
605
  def draw(self,
414
606
  ugen: Generator[float, None, None]
415
607
  ) -> pd.Series:
@@ -480,6 +672,66 @@ class CopulaStudent():
480
672
  # cholesky decomposition of the correlation matrix, for draws
481
673
  self._A = np.linalg.cholesky(self._rho)
482
674
 
675
+ @classmethod
676
+ def from_params(cls,
677
+ rho: conversions.ArrayLike,
678
+ nu: float,
679
+ names: Optional[Sequence[str]] = None,
680
+ ) -> "CopulaStudent":
681
+ """
682
+ Create a Student's t copula object from a correlation matrix and a
683
+ degrees-of-freedom parameter, rather than from pseudo-observations.
684
+
685
+ Parameters
686
+ ----------
687
+ rho : ArrayLike
688
+ K-by-K correlation matrix (a nested list is also accepted). It
689
+ must be symmetric with ones on its diagonal. If it is not
690
+ positive definite, the nearest positive definite matrix
691
+ (Higham, 1988) is substituted and rescaled to a unit diagonal.
692
+ nu : float
693
+ Degrees of freedom, greater than zero. As `nu` grows the copula
694
+ approaches the Gaussian copula with the same `rho`.
695
+ names : sequence of str, optional
696
+ Variable names, length K. If omitted, the column labels of
697
+ `rho` (if it is a pandas DataFrame) are used; otherwise the
698
+ variables are named 'v0', 'v1', ....
699
+
700
+ Returns
701
+ -------
702
+ CopulaStudent
703
+ A copula object whose :meth:`draw` method returns joint standard
704
+ uniform draws with the given Student's t dependence.
705
+
706
+ Examples
707
+ --------
708
+ >>> cop = fs.CopulaStudent.from_params([[1.0, 0.9], [0.9, 1.0]], nu=4)
709
+ """
710
+ R = _corr_from_params(rho)
711
+ K = R.shape[0]
712
+ obj = cls.__new__(cls)
713
+ obj._data = None
714
+ obj._names = _cop_names(K, names, _param_labels(rho))
715
+ (obj._M, obj._K) = (0, K)
716
+ obj._rho = R
717
+ obj._nu = _positive_scalar(nu, "nu")
718
+ obj._A = np.linalg.cholesky(obj._rho)
719
+ return obj
720
+
721
+ @property
722
+ def rho(self) -> pd.DataFrame:
723
+ """
724
+ The copula's correlation matrix parameter, as a DataFrame whose
725
+ index and columns are the variable names.
726
+ """
727
+ return pd.DataFrame(self._rho.copy(), index=self._names,
728
+ columns=self._names)
729
+
730
+ @property
731
+ def nu(self) -> float:
732
+ """The copula's degrees-of-freedom parameter."""
733
+ return float(self._nu)
734
+
483
735
  def draw(self,
484
736
  ugen: Generator[float, None, None]
485
737
  ) -> pd.Series:
@@ -565,6 +817,53 @@ class CopulaClayton():
565
817
  f"representation for your data.",
566
818
  UserWarning)
567
819
 
820
+ @classmethod
821
+ def from_params(cls,
822
+ theta: float,
823
+ K: int = 2,
824
+ names: Optional[Sequence[str]] = None,
825
+ ) -> "CopulaClayton":
826
+ """
827
+ Create a Clayton copula object from its dependence parameter,
828
+ rather than from pseudo-observations.
829
+
830
+ Parameters
831
+ ----------
832
+ theta : float
833
+ Dependence parameter, greater than zero. Kendall's tau is theta / (theta + 2).
834
+ K : int, optional
835
+ Number of variables. Default is 2. Ignored when `names` is
836
+ given, in which case K is the number of names.
837
+ names : sequence of str, optional
838
+ Variable names. If omitted, the variables are named 'v0',
839
+ 'v1', ....
840
+
841
+ Returns
842
+ -------
843
+ CopulaClayton
844
+ A copula object whose :meth:`draw` method returns joint standard
845
+ uniform draws with the given Clayton dependence.
846
+
847
+ Examples
848
+ --------
849
+ >>> cop = fs.CopulaClayton.from_params(theta=2.0)
850
+ """
851
+ th = _positive_scalar(theta, "theta", 0.0)
852
+ k = len(names) if names is not None else int(K)
853
+ if k < 2:
854
+ raise ValueError("a copula needs at least two variables")
855
+ obj = cls.__new__(cls)
856
+ obj._data = None
857
+ obj._names = _cop_names(k, names)
858
+ (obj._M, obj._K) = (0, k)
859
+ obj._theta = th
860
+ return obj
861
+
862
+ @property
863
+ def theta(self) -> float:
864
+ """The copula's dependence parameter."""
865
+ return float(self._theta)
866
+
568
867
  def _Ftilde(self, t):
569
868
  return (1.0 + t)**(-1.0 / self._theta)
570
869
 
@@ -655,6 +954,53 @@ class CopulaGumbel():
655
954
  f"representation for your data.",
656
955
  UserWarning)
657
956
 
957
+ @classmethod
958
+ def from_params(cls,
959
+ theta: float,
960
+ K: int = 2,
961
+ names: Optional[Sequence[str]] = None,
962
+ ) -> "CopulaGumbel":
963
+ """
964
+ Create a Gumbel copula object from its dependence parameter,
965
+ rather than from pseudo-observations.
966
+
967
+ Parameters
968
+ ----------
969
+ theta : float
970
+ Dependence parameter, greater than one (one is independence, which this sampler cannot represent). Kendall's tau is 1 - 1 / theta.
971
+ K : int, optional
972
+ Number of variables. Default is 2. Ignored when `names` is
973
+ given, in which case K is the number of names.
974
+ names : sequence of str, optional
975
+ Variable names. If omitted, the variables are named 'v0',
976
+ 'v1', ....
977
+
978
+ Returns
979
+ -------
980
+ CopulaGumbel
981
+ A copula object whose :meth:`draw` method returns joint standard
982
+ uniform draws with the given Gumbel dependence.
983
+
984
+ Examples
985
+ --------
986
+ >>> cop = fs.CopulaGumbel.from_params(theta=2.0)
987
+ """
988
+ th = _positive_scalar(theta, "theta", 1.0)
989
+ k = len(names) if names is not None else int(K)
990
+ if k < 2:
991
+ raise ValueError("a copula needs at least two variables")
992
+ obj = cls.__new__(cls)
993
+ obj._data = None
994
+ obj._names = _cop_names(k, names)
995
+ (obj._M, obj._K) = (0, k)
996
+ obj._theta = th
997
+ return obj
998
+
999
+ @property
1000
+ def theta(self) -> float:
1001
+ """The copula's dependence parameter."""
1002
+ return float(self._theta)
1003
+
658
1004
  def _Ftilde(self, t):
659
1005
  return math.exp(-(t**(1.0/self._theta)))
660
1006
 
@@ -743,6 +1089,53 @@ class CopulaFrank():
743
1089
  f"representation for your data.",
744
1090
  UserWarning)
745
1091
 
1092
+ @classmethod
1093
+ def from_params(cls,
1094
+ theta: float,
1095
+ K: int = 2,
1096
+ names: Optional[Sequence[str]] = None,
1097
+ ) -> "CopulaFrank":
1098
+ """
1099
+ Create a Frank copula object from its dependence parameter,
1100
+ rather than from pseudo-observations.
1101
+
1102
+ Parameters
1103
+ ----------
1104
+ theta : float
1105
+ Dependence parameter, greater than zero. Kendall's tau is 1 - 4 (1 - D_1(theta)) / theta, with D_1 the first Debye function.
1106
+ K : int, optional
1107
+ Number of variables. Default is 2. Ignored when `names` is
1108
+ given, in which case K is the number of names.
1109
+ names : sequence of str, optional
1110
+ Variable names. If omitted, the variables are named 'v0',
1111
+ 'v1', ....
1112
+
1113
+ Returns
1114
+ -------
1115
+ CopulaFrank
1116
+ A copula object whose :meth:`draw` method returns joint standard
1117
+ uniform draws with the given Frank dependence.
1118
+
1119
+ Examples
1120
+ --------
1121
+ >>> cop = fs.CopulaFrank.from_params(theta=5.0)
1122
+ """
1123
+ th = _positive_scalar(theta, "theta", 0.0)
1124
+ k = len(names) if names is not None else int(K)
1125
+ if k < 2:
1126
+ raise ValueError("a copula needs at least two variables")
1127
+ obj = cls.__new__(cls)
1128
+ obj._data = None
1129
+ obj._names = _cop_names(k, names)
1130
+ (obj._M, obj._K) = (0, k)
1131
+ obj._theta = th
1132
+ return obj
1133
+
1134
+ @property
1135
+ def theta(self) -> float:
1136
+ """The copula's dependence parameter."""
1137
+ return float(self._theta)
1138
+
746
1139
  def draw(self,
747
1140
  ugen: Generator[float, None, None]
748
1141
  ) -> pd.Series:
@@ -72,7 +72,7 @@ def nearestpd(array):
72
72
 
73
73
  if is_positive_definite(A3):
74
74
  msg = ("Higham's (1988) first method was employed to compute "
75
- "the pos. def. matrix nearest to the sample covariance matrix.")
75
+ "the positive definite matrix nearest to the input matrix.")
76
76
  warnings.warn(msg, UserWarning)
77
77
  return repackage(array, A3)
78
78
 
@@ -94,6 +94,6 @@ def nearestpd(array):
94
94
  A3 += Id * (-mineig * k ** 2 + spacing)
95
95
  k += 1
96
96
  msg = ("Higham's (1988) second method was employed to compute "
97
- "the pos. def. matrix nearest to the sample covariance matrix.")
97
+ "the positive definite matrix nearest to the input matrix.")
98
98
  warnings.warn(msg, UserWarning)
99
99
  return repackage(array, A3)
@@ -6,6 +6,7 @@ Genetics and Molecular Biology//, vol 4(2005), issue 1.
6
6
  """
7
7
 
8
8
  import itertools
9
+ from typing import Tuple, Union
9
10
  import numpy as np
10
11
  import pandas as pd
11
12
  import xarray as xr
@@ -207,7 +208,10 @@ def _target_f(x):
207
208
 
208
209
 
209
210
  def shrink(data: conversions.ArrayLike,
210
- target: str ) -> conversions.ArrayLike:
211
+ target: str,
212
+ return_intensity: bool = False
213
+ ) -> Union[conversions.ArrayLike,
214
+ Tuple[conversions.ArrayLike, float]]:
211
215
  """
212
216
  Covariance shrinkage estimator.
213
217
 
@@ -229,11 +233,18 @@ def shrink(data: conversions.ArrayLike,
229
233
  Targets 'B', 'E', and 'F' originate in Ledoit and Wolf (2003,
230
234
  2004). The shrinkage intensity is estimated analytically in each
231
235
  case.
236
+ return_intensity : bool, optional
237
+ If True, also return the estimated shrinkage intensity. Default is
238
+ False.
232
239
 
233
240
  Returns
234
241
  -------
235
- ArrayLike
236
- The estimated covariance matrix, with a type matching the data array
242
+ ArrayLike or tuple
243
+ The estimated covariance matrix, with a type matching the data
244
+ array. If `return_intensity` is True, a tuple ``(sigma, lam)``,
245
+ where ``lam`` is the shrinkage intensity, a float in [0, 1]: 0
246
+ leaves the sample covariance matrix unchanged and 1 replaces it
247
+ with the target.
237
248
 
238
249
  Raises
239
250
  ------
@@ -259,26 +270,30 @@ def shrink(data: conversions.ArrayLike,
259
270
  'E': _target_e,
260
271
  'F': _target_f}
261
272
 
262
- sig = target_map[target](a_np)[0]
273
+ sig, lam = target_map[target](a_np)
263
274
 
264
275
  # return the same type of array that was passed. the result is a
265
276
  # p-by-p covariance matrix, so both axes are labeled with the
266
277
  # variable (column) labels of the input, not the observation index
267
278
  if isinstance(data, pd.DataFrame):
268
- df = pd.DataFrame(data=sig, index=data.columns, columns=data.columns)
269
- return df
279
+ result = pd.DataFrame(data=sig, index=data.columns,
280
+ columns=data.columns)
270
281
  elif isinstance(data, xr.DataArray):
271
282
  coldim = str(data.dims[-1])
272
283
  if coldim in data.coords:
273
284
  varnames = np.asarray(data.coords[coldim].values)
274
285
  else:
275
286
  varnames = np.arange(sig.shape[0])
276
- da = xr.DataArray(sig,
277
- dims=(coldim, coldim + "_"),
278
- coords={coldim: varnames, coldim + "_": varnames})
279
- return da
287
+ result = xr.DataArray(sig,
288
+ dims=(coldim, coldim + "_"),
289
+ coords={coldim: varnames,
290
+ coldim + "_": varnames})
280
291
  else:
281
- return sig
292
+ result = sig
293
+
294
+ if return_intensity:
295
+ return result, float(lam)
296
+ return result
282
297
 
283
298
 
284
299
  if __name__ == '__main__':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funcsim
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: functional, simple stochastic simulation
5
5
  Home-page: https://github.com/h-bryant/funcsim
6
6
  Author: Henry Bryant
@@ -54,7 +54,7 @@ Features:
54
54
  - stratified/Latin hypercube sampling
55
55
  - painless multi-core simulations
56
56
  - fitting and comparison of univariate distributions, kernel density estimates, and goodness-of-fit tests
57
- - multivariate normal and KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas (implemented natively, no additional packages required); Iman-Conover
57
+ - multivariate normal (fitted to data or specified by a mean vector and covariance matrix) and multivariate KDE distributions; Gaussian, Student's t, Clayton, Gumbel, and Frank copulas, fitted to pseudo-observations or specified by their parameters (implemented natively, no additional packages required); information-criterion comparison of copula families; Iman-Conover; covariance shrinkage
58
58
  - expected utility and cumulative prospect theory calculations
59
59
  - plotly-based fan charts and diagnostic plots
60
60
  - highly flexible, and as simple as possible (but no more)
@@ -6,6 +6,7 @@ setup.py
6
6
  funcsim/__init__.py
7
7
  funcsim/addist.py
8
8
  funcsim/conversions.py
9
+ funcsim/copcompare.py
9
10
  funcsim/copfit.py
10
11
  funcsim/core.py
11
12
  funcsim/cpt.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="funcsim",
8
- version="0.2.4",
8
+ version="0.2.6",
9
9
  license="bsd-3-clause",
10
10
  author="Henry Bryant",
11
11
  author_email="henry@tamu.edu",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes