How can I calculate the nearest positive semi-definite matrix?

前端 未结 4 544
名媛妹妹
名媛妹妹 2020-12-29 07:27

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

相关标签:
4条回答
  • 2020-12-29 07:53

    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.

    0 讨论(0)
  • 2020-12-29 07:53

    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
    
    0 讨论(0)
  • 2020-12-29 08:05

    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)
    
    0 讨论(0)
  • 2020-12-29 08:08

    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.        ]]
    
    0 讨论(0)
提交回复
热议问题