I'm coming to Python from R and trying to reproduce a number of things that I'm used to doing in R using Python. The Matrix library for R has a very nifty function called nearPD()
which finds the closest positive semi-definite (PSD) matrix to a given matrix. While I could code something up, being new to Python/Numpy I don't feel too excited about reinventing the wheel if something is already out there. Any tips on an existing implementation in Python?
-
Did you look into the question at: http://stackoverflow.com/questions/619335/a-simple-algorithm-for-generating-positive-semidefinite-matrices The answer shows PSD math for numpy. – jakebird451 Jun 07 '12 at 20:26
-
ack... I was searching on the python tag which that didn't have... I'll look and if redundant I'll pull my question and add Python tag to that one – JD Long Jun 07 '12 at 20:28
-
1I looked at that question and they are just forcing the matrix into PSD with no consideration to "nearest". For contrast, here's the Higham paper on which nearPD() was based: http://www.maths.manchester.ac.uk/~nareports/narep369.pdf – JD Long Jun 07 '12 at 20:38
-
3doesn't scipy function [`cov_nearest`](http://www.statsmodels.org/dev/generated/statsmodels.stats.correlation_tools.cov_nearest.html) do neccessary convertion? – diralik Dec 16 '17 at 15:02
-
1@diraria that is an excellent answer to the question and you should add that as an answer, not a comment. The reason nobody initially answered with `cov_nearest` or `corr_nearest` is those functions were not added until August of 2012 and I asked the question in June of 2012. – JD Long Dec 18 '17 at 16:06
5 Answers
I don't think there is a library which returns the matrix you want, but here is a "just for fun" coding of neareast positive semi-definite matrix algorithm from Higham (2000)
import numpy as np,numpy.linalg
def _getAplus(A):
eigval, eigvec = np.linalg.eig(A)
Q = np.matrix(eigvec)
xdiag = np.matrix(np.diag(np.maximum(eigval, 0)))
return Q*xdiag*Q.T
def _getPs(A, W=None):
W05 = np.matrix(W**.5)
return W05.I * _getAplus(W05 * A * W05) * W05.I
def _getPu(A, W=None):
Aret = np.array(A.copy())
Aret[W > 0] = np.array(W)[W > 0]
return np.matrix(Aret)
def nearPD(A, nit=10):
n = A.shape[0]
W = np.identity(n)
# W is the matrix used for the norm (assumed to be Identity matrix here)
# the algorithm should work for any diagonal W
deltaS = 0
Yk = A.copy()
for k in range(nit):
Rk = Yk - deltaS
Xk = _getPs(Rk, W=W)
deltaS = Xk - Rk
Yk = _getPu(Xk, W=W)
return Yk
When tested on the example from the paper, it returns the correct answer
print nearPD(np.matrix([[2,-1,0,0],[-1,2,-1,0],[0,-1,2,-1],[0,0,-1,2]]),nit=10)
[[ 1. -0.80842467 0.19157533 0.10677227]
[-0.80842467 1. -0.65626745 0.19157533]
[ 0.19157533 -0.65626745 1. -0.80842467]
[ 0.10677227 0.19157533 -0.80842467 1. ]]

- 8,328
- 1
- 29
- 38
-
1
-
3Man I hope more people view this sort of stuff as "just for fun". But seriously, this is cool stuff. Awesome. Perhaps consider submitting it to scipy or something? – lightalchemist Apr 27 '13 at 03:14
-
1Your input matrix (`[[2,-1,0,0]...]`) is already positive-definite. And your output matrix has a Frobenius norm of 2.35 from that input. I’m not really sure what’s going on but I ported a Matlab implementation of Higham’s 1988 paper to Python and I’ll just leave the reference here: http://stackoverflow.com/a/43244194/500207 – Ahmed Fasih Apr 06 '17 at 01:37
-
1It seems that the code in the answer above is based on Higham 2000 https://www.maths.manchester.ac.uk/~higham/narep/narep369.pdf which has the goal of computing the nearest _correlation_ matrix (i.e., enforcing unit diagonal). That is much more restrictive than the nearest symmetric positive semidefinite matrix, which is subject of Higham 1988., which the code from @AhmedFasih is based on. https://www.sciencedirect.com/science/article/pii/0024379588902236 – David Bau Oct 28 '20 at 16:05
I would submit a non-iterative approach. This is slightly modified from Rebonato and Jackel (1999) (page 7-9). Iterative approaches can take a long time to process on matrices of more than a few hundred variables.
import numpy as np
def nearPSD(A,epsilon=0):
n = A.shape[0]
eigval, eigvec = np.linalg.eig(A)
val = np.matrix(np.maximum(eigval,epsilon))
vec = np.matrix(eigvec)
T = 1/(np.multiply(vec,vec) * val.T)
T = np.matrix(np.sqrt(np.diag(np.array(T).reshape((n)) )))
B = T * vec * np.diag(np.array(np.sqrt(val)).reshape((n)))
out = B*B.T
return(out)
Code is modified from a discussion of this topic here around nonPD/PSD matrices in R.

- 12,415
- 17
- 23
-
This might be a stupid question but can you convert back, i.e. from PSD to the original matrix? – IssamLaradji Mar 05 '14 at 11:21
-
No, you have fundamentally changed the matrix. If you need the original, just make sure you keep a copy (memory permitting, of course). – DomPazz Mar 05 '14 at 15:20
-
This code appears to be wrong. Just testing with a simple positive definite diagonal matrix A = np.diag([1,2,3]) outputs a different one, the identity. – pancho Jul 10 '20 at 08:55
-
1@pancho The linked paper appears to be primarily concerned with correlation matrices, such as those that may be expressed as I + XX' - diag(XX'). Hence, the diagonal terms are limited to 1. – Lorry Mar 05 '23 at 21:33
-
correct, calculate the standard deviations, calculate and correct the correlation matrix, and then use the stdev values to make that a covariance. – DomPazz Jun 26 '23 at 23:09
This is perhaps a silly extension to DomPazz answer to consider both correlation and covariance matrices. It also has an early termination if you are dealing with a large number of matrices.
def near_psd(x, epsilon=0):
'''
Calculates the nearest postive semi-definite matrix for a correlation/covariance matrix
Parameters
----------
x : array_like
Covariance/correlation matrix
epsilon : float
Eigenvalue limit (usually set to zero to ensure positive definiteness)
Returns
-------
near_cov : array_like
closest positive definite covariance/correlation matrix
Notes
-----
Document source
http://www.quarchome.org/correlationmatrix.pdf
'''
if min(np.linalg.eigvals(x)) > epsilon:
return x
# Removing scaling factor of covariance matrix
n = x.shape[0]
var_list = np.array([np.sqrt(x[i,i]) for i in xrange(n)])
y = np.array([[x[i, j]/(var_list[i]*var_list[j]) for i in xrange(n)] for j in xrange(n)])
# getting the nearest correlation matrix
eigval, eigvec = np.linalg.eig(y)
val = np.matrix(np.maximum(eigval, epsilon))
vec = np.matrix(eigvec)
T = 1/(np.multiply(vec, vec) * val.T)
T = np.matrix(np.sqrt(np.diag(np.array(T).reshape((n)) )))
B = T * vec * np.diag(np.array(np.sqrt(val)).reshape((n)))
near_corr = B*B.T
# returning the scaling factors
near_cov = np.array([[near_corr[i, j]*(var_list[i]*var_list[j]) for i in xrange(n)] for j in xrange(n)])
return near_cov

- 134
- 6
For those still ending up here, you can now use statsmodels.stats.correlation_tools.cov_nearest

- 121
- 5
-
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31530917) – Kent Kostelac Apr 20 '22 at 08:59
-
when I asked the question 10 years ago, there was no simple canned answer. A few months after I asked the question statsmodels added cov_nearest() and corr_nearest(). I have selected this as the accepted answer because for the last 9+ years this has been the best approach. – JD Long May 12 '22 at 20:54
I know this thread is old, but the solutions provided here were not satisfactory for my covariance matrices: the transformed matrices always looked quite different from the original ones (for the cases I tested at least). So, I'm leaving here a very straightforward answer, based on the solution provided in this answer:
import numpy as np
def get_near_psd(A):
C = (A + A.T)/2
eigval, eigvec = np.linalg.eig(C)
eigval[eigval < 0] = 0
return eigvec.dot(np.diag(eigval)).dot(eigvec.T)
The idea is simple: I compute the symmetric matrix, then do an eigen decomposition to get the eigenvalues and eigenvectors. I zero out all negative eigenvalues and construct back the matrix, which will now be positive semi-definite.
For the sake of completness, I leave a simple code to check whether a matrix is positive semi-definite using numpy (basically checking whether all eigenvalues are non-negative):
def is_pos_semidef(x):
return np.all(np.linalg.eigvals(x) >= 0)

- 448
- 2
- 10
- 23