Skip to main content

Computes several covariance matrix estimators that ensure positive semi-definiteness (PSD).

Project description

A Package for Posterior Mean Covariance Estimation (psd_covariance)

Introduction

We address the issue of accurate and well-conditioned covariance estimation by developing a regularization scheme based on the principle of positive semi-definiteness (PSD). The method is thus designed to address two settings. First, when an estimator poorly estimates the true eigenvalues, or returns non-positive eigenvalues, it refines them to ensure positive semi-definiteness and enhance both conditioning and predictive performance. Second, when the true covariance matrix itself is ill-conditioned, it produces a better-conditioned estimate while preserving, and often even improving, predictive performance, making it useful for out-of-sample applications such as mean-variance portfolio optimization.

Authors

This package is based on the paper 'Well-Conditioned Covariance Estimation via Bayesian Eigenvalue Regularization', by Kris Boudt, Jesper Cremers, Kirill Dragun & Steven Vanduffel. The psd_covariance package is developed and maintained by Jesper Cremers.

Contents

The package includes the following:

  • The posterior mean (PM) and fixed-trace (FT) estimators from Boudt et al. (2025);
  • Fast likelihood based cross-validation for regularization parameter tuning for PM and FT;
  • Ad hoc thresholding or correcting of non-positive eigenvalues (i.e., Rousseeuw and Molenberghs, 1993);
  • Linear Shrinkage and QIS Shrinkage of Ledoit & Wolf (2004, 2022). Contains code adapted from Michael Wolfs website: https://github.com/pald22/covShrinkage.

Installation

pip install psd-covariance

Imports

import pandas as pd
import numpy as np
from numpy.linalg import norm, cond
import matplotlib.pyplot as plt

Quick Start

Example 1: Transforming non-PSD matrices

We construct a non-PSD estimated covariance matrix with d=10, such that the smallest two eigenvalues are negative.

d = 10
A = np.random.randn(d, d)
Q, _ = np.linalg.qr(A)
eigvals = np.random.uniform(0.5, 2.0, size = d)
eigvals[:2] *= -0.5
Sigma_tilde = Q @ np.diag(eigvals) @ Q.T
eigvals = np.sort(eigvals)
print(eigvals)
# [-0.67406598 -0.38745988  0.62513365  0.71727164  1.03341911  1.23208442, 1.62299543  1.64798788  1.85557961  1.91064792]

To transform the non-PSD matrix to a PSD matrix to obtain improved estimates, we compute the available estimators.

cleaned_thresh, _ = EigenvalueCleaning.threshold_negative(Sigma_tilde)
eigvals_thresh = np.linalg.eigvalsh(cleaned_thresh)

# consider PD matrix
cleaned_replace, _ = EigenvalueCleaning.replace_negative(Sigma_tilde, 
                                                            epsilon=1e-1, PD=True)
eigvals_replace = np.linalg.eigvalsh(cleaned_replace)

# consider PD matrix
cleaned_abs, _ = EigenvalueCleaning.absolute_negative(Sigma_tilde, PD=True)
eigvals_abs = np.linalg.eigvalsh(cleaned_abs)

pm = PosteriorMeanEstimator(fixed_trace=False)
pm.fit(Sigma_tilde, sigma=0.5) # arbitrary choice
eigvals_pm = np.linalg.eigvalsh(pm.Sigma_)

ft = PosteriorMeanEstimator(fixed_trace=True)
ft.fit(Sigma_tilde, sigma=0.5) # arbitrary choice
eigvals_ft = np.linalg.eigvalsh(ft.Sigma_)
print("\nEigenvalues of cleaned matrices:")
print("Threshold Negative      :", np.round(eigvals_thresh, decimals=12))
print("Replace Negative        :", eigvals_replace)
print("Absolute Value          :", eigvals_abs)
print("PM Estimator            :", eigvals_pme)
print("PM Estimator (Fixed Tr.):", eigvals_pme_ft)
# Eigenvalues of cleaned matrices:
# Threshold Negative      : [0.          0.          0.58816645  0.60256691  0.82920843  0.84350457, 0.86596268  1.08551965  1.25500039  1.62205361]
# Replace Negative        : [0.1        0.1        0.58816645 0.60256691 0.82920843 0.84350457, 0.86596268 1.08551965 1.25500039 1.62205361]
# Absolute Value          : [0.55457948 0.58816645 0.60256691 0.74002933 0.82920843 0.84350457, 0.86596268 1.08551965 1.25500039 1.62205361]
# PM Estimator            : [0.22083797 0.25204088 0.7016114  0.71148757 0.88221069 0.89388246, 0.91241481 1.10470297 1.26359962 1.62308834]
# PM Estimator (Fixed Tr.): [0.16493152 0.18823523 0.52399428 0.53137025 0.65887379 0.66759078, 0.68143155 0.82504082 0.94371183 1.21219384]

Example 2: PM and FT Estimation using Cross-Validation

We generate a covariance matrix with a Toeplitz structure with d=10 and we draw n=20 observations from a Normal distribution with mean 0.

# Generate data
np.random.seed(0)
d = 10
n = 20
rho = 0.8
cov_matrix = np.fromfunction(lambda i, j: rho ** np.abs(i - j), (d, d))
X = np.random.multivariate_normal(np.zeros(d), cov_matrix, size=n)
> S = sample_cov(X)

> X = X.to_numpy()
> sigma_range = np.linspace(0.01, 2.0, 150)

# PM cross validation
> pm = PosteriorMeanEstimator(fixed_trace=False)
> sigma_pm = pm.cross_validate_sigma(X, sigma_range)
> print(sigma_pm)
# 0.2504026845637584
> Sigma_pm, Sigma_pm_inv = pm.fit(S, sigma_pm)

# FT cross validation
> ft = PosteriorMeanEstimator(fixed_trace=True)
> sigma_ft = ft.cross_validate_sigma(X, sigma_range)
> print(sigma_ft)
# 0.2771140939597316
> Sigma_ft, Sigma_ft_inv = ft.fit(S, sigma_ft)

References

  • Boudt, K., J. Cremers, K. Dragun, and S. Vanduffel (2025). Well-conditioned covariance esti- mation via bayesian eigenvalue regularization. Working paper.
  • Ledoit, O. and M. Wolf (2004). Honey, i shrunk the sample covariance matrix. The Journal of Portfolio Management 30 (4), 110–119.
  • Ledoit, O. and M. Wolf (2022). Quadratic shrinkage for large covariance matrices. Bernoulli 28 (3), 1519–1547.
  • Rousseeuw, P. J. and G. Molenberghs (1993). Transformation of non positive semidefinite correlation matrices. Communications in Statistics–Theory and Methods 22 (4), 965–984.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

psd_covariance-0.1.1.tar.gz (7.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

psd_covariance-0.1.1-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file psd_covariance-0.1.1.tar.gz.

File metadata

  • Download URL: psd_covariance-0.1.1.tar.gz
  • Upload date:
  • Size: 7.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.1

File hashes

Hashes for psd_covariance-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7f658d3f26eeebfc99e4b421fe7a8e524a0060c14eab71bb2fd37dd9afa045fa
MD5 78e274c98ccc59a534fc61c47c9279f2
BLAKE2b-256 7757ccf78c97eefcdc3146916fc26227d2491a99d33b56211b2966645a18c7eb

See more details on using hashes here.

File details

Details for the file psd_covariance-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: psd_covariance-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.1

File hashes

Hashes for psd_covariance-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 58433156e4427e0a02a962f96f44aa2f3eda607b5da654662f39fffc3f34468a
MD5 b2756c41184dd825ddc8e66676a3a175
BLAKE2b-256 cc5e64e6b94bd5c2859911fd6e41216080a01216bbf07d81b331dc4a711be7f6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page