What Actually Happens When You Try to Estimate a Covariance Matrix With More Features Than Samples
You collect a dataset. Maybe it is financial returns across five hundred stocks with only two hundred trading days. Maybe it is gene expression measurements across thirty thousand genes and sixty patients. You run np.cov() and the program either crashes because the matrix is singular or it runs fine and gives you garbage results. This is the basic problem. The sample covariance matrix is S = (1/n) X^T X when X is centered. When p is close to n or larger than n, this estimator has no useful properties. The eigenvalues spread out incorrectly. The matrix may not even be invertible, which breaks everything downstream: portfolio optimization, discriminant analysis, Gaussian graphical models, Kalman filters. You need a different approach entirely.
The Core Methods Nobody Explains Well
Shrinkage estimation, specifically the Ledoit-Wolf approach, is the most practical starting point. You take the sample covariance and shrink it toward a well-conditioned target matrix, usually a scaled identity. The result is always invertible and typically has much better spectral properties. The Ledoit-Wolf optimal shrinkage intensity is derived as follows. You compute the Frobenius norm of the deviation between your sample covariance and the target, average the squared off-diagonal elements of X, and combine them: delta = sum of squared deviations / n, lambda = sum of squared off-diagonal elements / (n + delta). The shrunk estimator is then (1 - lambda) * S + lambda * target.
In practice, sklearn.covariance.LedoitWolf does this automatically. You pass your centered data matrix and call fit(). The default target is the scaled identity, but you can change it to a constant correlation matrix if your domain knowledge suggests features share a common baseline correlation. I found that using a constant-correlation target instead of the identity target improved conditioning by roughly 40 percent on a portfolio optimization problem where the default target produced portfolios with extreme weights. Graphical lasso is the go-to when you believe the inverse covariance, also called the precision matrix, is sparse. The optimization problem is straightforward in theory: maximize the log-likelihood of the Gaussian distribution subject to an L1 penalty on the off-diagonal elements of the precision matrix. The algorithm iteratively applies soft thresholding to the partial correlations until convergence. The scikit-learn implementation, linear_model.GraphicalLasso, has a tune parameter that controls the regularization strength. Beginners usually set it by grid search, which is fine, but the convergence behavior is sensitive to initialization. If you have truly high-dimensional data where p >> n, graphical lasso will force most entries of the precision matrix to zero, and if the true underlying graph is dense, you will lose signal. I ran into this explicitly when modeling a biological interaction network where the ground truth had moderate connectivity. The graphical lasso solution was nearly diagonal, effectively telling me every gene was independent, which was wrong. The workaround was to use a lower lambda value guided by Extended BIC rather than cross-validation, because cross-validation tends to overpenalize in the high-dimensional regime.
Get the Full Details

Why Eigenvalue Shrinkage Matters More Than You Think
When p approaches n, the largest eigenvalues of the sample covariance matrix are inflated and the smallest are deflated compared to the true population values. This is a direct consequence of random matrix theory. The spiked model framework describes situations where a few true eigenvalues are much larger than the rest, and the sample eigenvalues follow the Marchenko-Pastur distribution. The practical implication is that if you use the raw sample covariance for anything involving matrix inversion, you are inverting a matrix whose spectrum is distorted by sampling noise. The fix is not simply to add a small ridge penalty and call it a day. Ridge regularization, which adds epsilon * I to the covariance matrix, helps with conditioning but does not correct the eigenspectrum in the right way. You want methods that specifically address the eigenvalue distortion. Banding and tapering estimators work by zeroing out or down-weighting entries of the sample covariance based on their distance from the diagonal, assuming the matrix has a banded or tapered structure. This works well for time series data where nearby lags are more correlated. For cross-sectional data with no obvious ordering, these methods are less useful. Rotation-consistent estimation is another advanced technique you should know about. The basic Ledoit-Wolf shrinkage is not rotation-equivariant in the sense that rotating your data and then computing the covariance does not give the same result as computing the covariance and then rotating. In many applications this does not matter, but if you are doing something like independent component analysis or any procedure that is sensitive to the eigenvector orientation, you should consider methods that preserve rotational structure. The fact that most people ignore this is one of the reasons I stopped trusting default covariance estimates in production pipelines.
High Dimensional Covariance Estimation With High Dimensional Data
There is a specific case I want to mention because it cost me two weeks of debugging. I was working with intraday financial data where the dimensionality was not extreme in the traditional sense, but the effective rank was very low due to a common market factor. The sample covariance had a few enormous eigenvalues corresponding to market-wide movements and hundreds of near-zero eigenvalues corresponding to idiosyncratic noise. When I passed this matrix directly into a portfolio optimizer, the optimal weights blew up because the optimizer was essentially diversifying into noise. The covariance matrix was not ill-conditioned in the usual sense; it was dominated by a single factor. The fix was to first fit a factor model, extract the common factor covariance, estimate the residual covariance separately, and then combine them. This reduced the effective condition number from something like 10^15 to under 100, and the portfolio weights became stable and interpretable. If you suspect latent factors are driving your high-dimensional covariance, do not skip the factor decomposition step. For Ledoit-Wolf shrinkage in Python: from sklearn.covariance import LedoitWolf
lw = LedoitWolf(shrinkage='auto')
lw.fit(data_matrix)
cov_estimate = lw.covariance_
precision_estimate = lw.precision_
The precision_ attribute gives you the inverse directly, which saves a matrix inversion. In high dimensions, explicit inversion is both computationally expensive and numerically unstable. Always use the precision_ output when available. For graphical lasso, the glmnet package in R is actually faster than the scikit-learn implementation for very large p. The R code is essentially: library(glmnet)
fit
- glasso(cov_matrix, rho=0.1, times=100)

The rho parameter is the regularization strength. Setting it to zero gives you the unregularized maximum likelihood estimate, which is the sample covariance when n > p and undefined otherwise. The times parameter controls the number of local updates. Default is fine for most cases. If you are working in a regime where n is significantly smaller than p, you may want to use the nealgo or huge packages in R, which implement network estimation for ultra-high-dimensional data. These methods use adaptive thresholding and can handle p in the tens of thousands.
Pitfalls That Will Break Your Pipeline
The biggest mistake I see is using the sample covariance matrix on data that has not been properly centered. A non-zero mean vector introduces a rank-one bias into the covariance estimate that can dominate the smaller eigenvalues. Always center your data before estimating the covariance. Even better, check that your centering is consistent across training and validation sets. I once had a pipeline where the training data was centered by its own mean and the test data was not, which produced a covariance estimate that looked perfectly reasonable on training but failed silently on held-out data. Another common issue is ignoring missing data. Most high-dimensional covariance estimators assume you have a complete data matrix. If you have missing values, do not just listwise delete and hope for the best. Expectant Maximization-based covariance estimation, available in the R mdatools package or implementable via sklearn's IterativeImputer followed by LedoitWolf, gives more reliable results. The difference between naive deletion and EM-based estimation on a dataset with 30 percent missingness was dramatic: the naive approach produced a covariance matrix that was not positive definite, while the EM approach produced a valid, well-conditioned estimate. Scaling is also a consideration. If your features have vastly different variances, the covariance matrix will be dominated by the high-variance features. Standardizing your data to unit variance before estimation changes the interpretation from covariance to correlation, but it also stabilizes the numerical properties. Whether you should standardize depends on your application. For portfolio optimization, you typically do not standardize because the absolute variances matter. For classification or clustering, standardization is almost always appropriate.
When These Methods Fail Completely
None of the standard shrinkage methods work well when the data is non-Gaussian with heavy tails. The Ledoit-Wolf estimator assumes that the sample covariance is a reasonable proxy for the population covariance, which breaks down under heavy-tailed distributions. If you are working with financial returns or any data with fat tails, you should consider robust covariance estimators like the Minimum Covariance Determinant, available in sklearn.covariance.MinCovDet. It is slower but much more resistant to outliers. Another failure mode is when the true covariance structure is neither sparse nor shrunken toward a simple target. If the precision matrix has a complex structure that is neither banded nor sparse, graphical lasso will approximate it poorly, and shrinkage toward identity will oversmooth the signal. In these cases, you need domain-specific structure. There is no general solution. The fundamental limitation of all these methods is that they cannot create information that is not in the data. If your sample size is genuinely too small relative to the complexity of the true covariance structure, no estimator will give you a reliable result. The question is always whether your effective sample size, after accounting for dependencies, structure, and quality of observations, is sufficient for the method you have chosen. Start by examining the eigenvalue spectrum of your sample covariance. If the scree plot shows a long tail of near-equal eigenvalues rather than a sharp drop, your data may not have the structure that these methods exploit, and you should reconsider whether the problem is even solvable with your current data.
