In some cases, we need to construct a correlation matrix with a predefined set of eigenvalues, which is not trivial since arbitrary symmetric matrices with a given set of eigenvalues may not satisfy correlation constraints (e.g., unit diagonal elements).
A practical method to generate such matrices is based on the Method of Alternating Projections (MAP), as introduced by Waller (2018). This approach iteratively adjusts a matrix between two sets until convergence. It goes like this:
- Normalize the eigenvalues:
- Ensure they are sorted in descending order.
- Scale them such that their sum equals
, the matrix size.
- Generate a random orthonormal matrix:
- Construct a random matrix.
- Perform QR decomposition to obtain the orthonormal matrix.
- Iterate until convergence:
- Construct an symmetric positive definite (SPD) matrix using the target eigenvalues and the random orthonormal matrix:
- Clamp values between
and
to maintain correlation constraints.
- Force all elements on the diagonal to be
.
- Clamp values between
- Perform eigen decomposition to extract new eigenvalues and eigenvectors.
- Replace the computed eigenvalues with the target values.
- Compute the distance measure .
- Stop if the distance measure is below a given tolerance.
- Construct an symmetric positive definite (SPD) matrix using the target eigenvalues and the random orthonormal matrix:
The algorithm seems to converge exponentially fast:

Below Python code!
import numpy as np def eig_to_cor(eigen_values, eigen_vectors): """ Construct a correlation matrix from given eigenvalues and eigenvectors. """ cor = eigen_vectors @ np.diag(eigen_values) @ eigen_vectors.T cor = np.clip(cor, -1, 1) # Ensure valid correlation values np.fill_diagonal(cor, 1.0) # Force unit diagonal elements return cor def cor_to_eig(cor): """ Extract eigenvalues and eigenvectors from a correlation matrix, ensuring they are sorted in descending order. """ eigen_values, eigen_vectors = np.linalg.eigh(cor) idx = eigen_values.argsort()[::-1] return eigen_values[idx].real, eigen_vectors[:, idx].real def random_orthonormal_matrix(n): """ Generate a random nxn orthonormal matrix using QR decomposition. """ Q, _ = np.linalg.qr(np.random.randn(n, n)) return Q def normalize_eigen_values(eigen_values): """ Normalize eigenvalues to sum to the matrix dimension (n) and ensure that they are sorted in descending order. """ eigen_values = np.sort(eigen_values)[::-1] # Ensure descending order return len(eigen_values) * eigen_values / np.sum(eigen_values) def gen_cor_with_eigenvalues(eigen_values, tol=1E-7): """ Generate a correlation matrix with the specified eigenvalues using the Method of Alternating Projections. """ target_eigen_values = normalize_eigen_values(eigen_values) eigen_vectors = random_orthonormal_matrix(len(target_eigen_values)) while True: cor = eig_to_cor(target_eigen_values, eigen_vectors) eigen_values, eigen_vectors = cor_to_eig(cor) if np.max(np.abs(target_eigen_values - eigen_values)) < tol: break return cor
# Example usage: cor_matrix = gen_cor_with_eigenvalues([3, 1, 1/3, 1/3, 1/3]) print(cor_matrix) > array([[ 1. , 0.52919521, -0.32145437, -0.45812423, -0.65954354], [ 0.52919521, 1. , -0.61037909, -0.65821403, -0.46442884], [-0.32145437, -0.61037909, 1. , 0.64519865, 0.23287104], [-0.45812423, -0.65821403, 0.64519865, 1. , 0.38261988], [-0.65954354, -0.46442884, 0.23287104, 0.38261988, 1. ]])
Waller, N. G. (2018). Generating Correlation Matrices With Specified Eigenvalues Using the Method of Alternating Projections. The American Statistician, 74(1), 21–28. https://doi.org/10.1080/00031305.2017.1401960