This file has a lot of Latex and GitHub currently cannot render it on Markdown files. You can read all the math clearly as a webpage or access this as a regular github repository.
The source of this example can be found here and the output here.
See the top directory of this repository for instructions to set up the NAG Library for Java.
This notebook looks at computing nearest correlation matrices using the NAG Library for Java.
-
An
$$n$$ by$$n$$ matrix is a correlation matrix if:- it is symmetric
- it has ones on the diagonal
- its eigenvalues are non-negative (positive semidefinite)
$$ \Large Ax = \lambda x, \quad x \neq 0$$
-
The element in the $$i$$th row and $$j$$th column is the correlation between the $$i$$th and $$j$$th variables. This could be stock process, for example.
-
Empirical correlation matrices are often not mathematically true due to inconsistent or missing data.
-
Thus we are required to find a true correlation matrix, where our input,
$$G$$ , is an approximate correlation matrix. -
In particular we seek the nearest correlation matrix, in most cases.
- The vector
$$p_i$$ , the $$i$$th column of a matrix,$$P$$ , holds the$$m$$ observations of the $$i$$th variable, of which there are$$n$$ .$$\bar{p}_i$$ is the sample mean.
-
$$S$$ is a covariance matrix, with$$S_{ij}$$ the covariance between variables$$i$$ and$$j$$ -
$$R$$ is the corresponding correlation matrix, given by:
-
Now, what if we don't have all observations for each variable?
-
We compute each covariance with observations that are available for both the ith and jth variable.
-
For example NAG routine G02BB.
-
We then compute the correlation matrix as before.
- Prices for 8 stocks on the first working day of 10 consecutive months.
Stock A | Stock B | Stock C | Stock D | Stock E | Stock F | Stock G | Stock H | |
---|---|---|---|---|---|---|---|---|
Month 1 | 59.875 | 42.734 | 47.938 | 60.359 | 54.016 | 69.625 | 61.500 | 62.125 |
Month 2 | 53.188 | 49.000 | 39.500 | 34.750 | 83.000 | 44.500 | ||
Month 3 | 55.750 | 50.000 | 38.938 | 30.188 | 70.875 | 29.938 | ||
Month 4 | 65.500 | 51.063 | 45.563 | 69.313 | 48.250 | 62.375 | 85.250 | |
Month 5 | 69.938 | 47.000 | 52.313 | 71.016 | 59.359 | 61.188 | 48.219 | |
Month 6 | 61.500 | 44.188 | 53.438 | 57.000 | 35.313 | 55.813 | 51.500 | 62.188 |
Month 7 | 59.230 | 48.210 | 62.190 | 61.390 | 54.310 | 70.170 | 61.750 | 91.080 |
Month 8 | 61.230 | 48.700 | 60.300 | 68.580 | 61.250 | 70.340 | ||
Month 9 | 52.900 | 52.690 | 54.230 | 68.170 | 70.600 | 57.870 | 88.640 | |
Month 10 | 57.370 | 59.040 | 59.870 | 62.090 | 61.620 | 66.470 | 65.370 | 85.840 |
-
We will use NaNs where there is missing data.
-
So our
$$P = \left[p_1, p_2, \ldots, p_n \right]$$ is:
- And to compute the covariance between the 3rd and 4th variables:
- Let's compute this in Java.
// Define a 2-d array and use Double.NaN to set elements as NaNs
double[][] P = new double[][] {
{ 59.875, 42.734, 47.938, 60.359, 54.016, 69.625, 61.500, 62.125 },
{ 53.188, 49.000, 39.500, Double.NaN, 34.750, Double.NaN, 83.000, 44.500 },
{ 55.750, 50.000, 38.938, Double.NaN, 30.188, Double.NaN, 70.875, 29.938 },
{ 65.500, 51.063, 45.563, 69.313, 48.250, 62.375, 85.250, Double.NaN },
{ 69.938, 47.000, 52.313, 71.016, Double.NaN, 59.359, 61.188, 48.219 },
{ 61.500, 44.188, 53.438, 57.000, 35.313, 55.813, 51.500, 62.188 },
{ 59.230, 48.210, 62.190, 61.390, 54.310, 70.170, 61.750, 91.080 },
{ 61.230, 48.700, 60.300, 68.580, 61.250, 70.340, Double.NaN, Double.NaN },
{ 52.900, 52.690, 54.230, Double.NaN, 68.170, 70.600, 57.870, 88.640 },
{ 57.370, 59.040, 59.870, 62.090, 61.620, 66.470, 65.370, 85.840 } };
public static double[][] cov_bar(double[][] P) {
double[] xi, xj;
boolean[] xib, xjb, notp;
int n = P[0].length;
double[][] S = new double[n][n];
int notpFalseCount;
for (int i = 0; i < n; i++) {
// Take the ith column
xi = getMatrixColumn(P, i);
for (int j = 0; j < i + 1; j++) {
// Take the jth column, where j <= i
xj = getMatrixColumn(P, j);
// Set mask such that all NaNs are true
xib = getNanMask(xi);
xjb = getNanMask(xj);
notp = addBoolArrOr(xib, xjb);
// S[i][j] = (xi - mean(xi)) * (xj - mean(xj))
S[i][j] = matrixMaskedDot(vectorSubScalar(xi, vectorMaskedMean(xi, notp)),
vectorSubScalar(xj, vectorMaskedMean(xj, notp)), notp);
// Take the sum over !notp to normalize
notpFalseCount = 0;
for (boolean b : notp) {
if (!b) {
notpFalseCount++;
}
}
S[i][j] = 1.0 / (notpFalseCount - 1) * S[i][j];
S[j][i] = S[i][j];
}
}
return S;
}
public static double[][] cor_bar(double[][] P) {
double[][] S, D;
S = cov_bar(P);
// D = 1.0 / SQRT(S)
D = getMatrixFromDiag(vectorRightDiv(vectorSqrt(getMatrixDiag(S)), 1.0));
// S_ = S * D
F01CK f01ck = new F01CK();
double[] S_ = new double[S.length * S[0].length];
double[] S1d = convert2DTo1D(S);
double[] D1d = convert2DTo1D(D);
int n = S.length;
int p = n;
int m = n;
double[] z = new double[0];
int iz = 0;
int opt = 1;
int ifail = 0;
f01ck.eval(S_, S1d, D1d, n, p, m, z, iz, opt, ifail);
// D_ = D * S_
double[] D_ = new double[n * n];
f01ck.eval(D_, D1d, S_, n, p, m, z, iz, opt, ifail);
return convert1DTo2D(D_, n);
}
double[][] G = cor_bar(P);
The approximate correlation matrix
1.0000 -0.3250 0.1881 0.5760 0.0064 -0.6111 -0.0724 -0.1589
-0.3250 1.0000 0.2048 0.2436 0.4058 0.2730 0.2869 0.4241
0.1881 0.2048 1.0000 -0.1325 0.7658 0.2765 -0.6172 0.9006
0.5760 0.2436 -0.1325 1.0000 0.3041 0.0126 0.6452 -0.3210
0.0064 0.4058 0.7658 0.3041 1.0000 0.6652 -0.3293 0.9939
-0.6111 0.2730 0.2765 0.0126 0.6652 1.0000 0.0492 0.5964
-0.0724 0.2869 -0.6172 0.6452 -0.3293 0.0492 1.0000 -0.3983
-0.1589 0.4241 0.9006 -0.3210 0.9939 0.5964 -0.3983 1.0000
- We see below that our matrix
$$G$$ is not a mathematically true correlation matrix.
F08NA f08na = new F08NA();
String jobvl = "N";
String jobvr = "N";
int n = G[0].length;
double[] G1d = convert2DTo1D(G);
int lda = G.length;
double[] wr = new double[n];
double[] wi = new double[n];
int ldvl = 1;
double[] vl = new double[ldvl];
int ldvr = 1;
double[] vr = new double[ldvr];
int lwork = 3 * n;
double[] work = new double[lwork];
int info = 0;
f08na.eval(jobvl, jobvr, n, G1d, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info);
Arrays.sort(wr);
Sorted eigenvalues of G: -0.2498 -0.0160 0.0895 0.2192 0.7072 1.7534 1.9611 3.5355
- Our problem now is to solve:
-
In order to find
$$X$$ , a true correlation matrix, where$$G$$ is an approximate correlation matrix. -
An algorithm by Qi and Sun (2006), applies an inexact Newton method to a dual (unconstrained) formulation of this problem.
-
Improvements were suggested by Borsdorf and Higham (2010 MSc).
-
It is globally and quadratically (fast!) convergent.
-
This is implemented in NAG routine G02AA.
// Call NAG routine G02AA and print the result
G02AA g02aa = new G02AA();
G1d = convert2DTo1D(G);
n = G.length;
int ldg = n;
int ldx = n;
double errtol = 0.0;
int maxits = 0;
int maxit = 0;
double[] X1d = new double[ldx * n];
int iter = 0;
int feval = 0;
double nrmgrd = 0.0;
int ifail = 0;
g02aa.eval(G1d, ldg, n, errtol, maxits, maxit, X1d, ldx, iter, feval, nrmgrd, ifail);
double[][] X = convert1DTo2D(X1d, ldx);
iter = g02aa.getITER();
Nearest correlation matrix
1.0000 -0.3112 0.1889 0.5396 0.0268 -0.5925 -0.0621 -0.1921
-0.3112 1.0000 0.2050 0.2265 0.4148 0.2822 0.2915 0.4088
0.1889 0.2050 1.0000 -0.1468 0.7880 0.2727 -0.6085 0.8802
0.5396 0.2265 -0.1468 1.0000 0.2137 0.0015 0.6069 -0.2208
0.0268 0.4148 0.7880 0.2137 1.0000 0.6580 -0.2812 0.8762
-0.5925 0.2822 0.2727 0.0015 0.6580 1.0000 0.0479 0.5932
-0.0621 0.2915 -0.6085 0.6069 -0.2812 0.0479 1.0000 -0.4470
-0.1921 0.4088 0.8802 -0.2208 0.8762 0.5932 -0.4470 1.0000
jobvl = "N";
jobvr = "N";
n = X[0].length;
lda = X.length;
wr = new double[n];
wi = new double[n];
ldvl = 1;
vl = new double[ldvl];
ldvr = 1;
vr = new double[ldvr];
lwork = 3 * n;
work = new double[lwork];
info = 0;
f08na.eval(jobvl, jobvr, n, X1d, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info);
Arrays.sort(wr);
Sorted eigenvalues of X: -0.0000 0.0000 0.0380 0.1731 0.6894 1.7117 1.9217 3.4661
- Now, we note that for Stocks A to C we have a complete set of observations.
-
Perhaps we wish to preserve part of the correlation matrix?
-
We could solve the weighted problem, NAG routine G02AB
-
Here
$$W$$ is a diagonal matrix. -
We can also force the resulting matrix to be positive definite.
// Define an arrray of weights
double[] W = new double[] { 10, 10, 10, 1, 1, 1, 1, 1 };
// Set up and call the NAG routine using weights and a minimum eigenvalue
G02AB g02ab = new G02AB();
G1d = convert2DTo1D(G);
ldg = G.length;
n = G[0].length;
String opt = "B";
double alpha = 0.001;
errtol = 0.0;
maxits = 0;
maxit = 0;
ldx = n;
X1d = new double[ldx * n];
iter = 0;
feval = 0;
nrmgrd = 0;
ifail = 0;
g02ab.eval(G1d, ldg, n, opt, alpha, W, errtol, maxits, maxit, X1d, ldx, iter, feval, nrmgrd, ifail);
X = convert1DTo2D(X1d, ldx);
iter = g02ab.getITER();
Nearest correlation matrix using row and column weighting
1.0000 -0.3250 0.1881 0.5739 0.0067 -0.6097 -0.0722 -0.1598
-0.3250 1.0000 0.2048 0.2426 0.4060 0.2737 0.2870 0.4236
0.1881 0.2048 1.0000 -0.1322 0.7661 0.2759 -0.6171 0.9004
0.5739 0.2426 -0.1322 1.0000 0.2085 -0.0890 0.5954 -0.1805
0.0067 0.4060 0.7661 0.2085 1.0000 0.6556 -0.2780 0.8757
-0.6097 0.2737 0.2759 -0.0890 0.6556 1.0000 0.0490 0.5746
-0.0722 0.2870 -0.6171 0.5954 -0.2780 0.0490 1.0000 -0.4550
-0.1598 0.4236 0.9004 -0.1805 0.8757 0.5746 -0.4550 1.0000
jobvl = "N";
jobvr = "N";
n = X[0].length;
lda = X.length;
wr = new double[n];
wi = new double[n];
ldvl = 1;
vl = new double[ldvl];
ldvr = 1;
vr = new double[ldvr];
lwork = 3 * n;
work = new double[lwork];
info = 0;
f08na.eval(jobvl, jobvr, n, X1d, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info);
Arrays.sort(wr);
Sorted eigenvalues of X: 0.0010 0.0010 0.0305 0.1646 0.6764 1.7716 1.8910 3.4639
-
Would it be better to be able to weight individual elements in our approximate matrix?
-
In our example the top left 3 by 3 block of exact correlations, perhaps.
-
Element-wise weighting means we wish to find the minimum of
-
So individually
$$h_{ij} \times (g_{ij} - x_{ij}).$$ -
However, this is a more “difficult” problem, and more computationally expensive.
-
This is implemented in the NAG routine G02AJ.
// Set up a matrix of weights
n = P[0].length;
double[][] H = new double[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if ((i < 3) && (j < 3)) {
H[i][j] = 100.0;
} else {
H[i][j] = 1;
}
}
}
100.0000 100.0000 100.0000 1.0000 1.0000 1.0000 1.0000 1.0000
100.0000 100.0000 100.0000 1.0000 1.0000 1.0000 1.0000 1.0000
100.0000 100.0000 100.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000 1.0000
// Call the NAG routine specifying a minimum eigenvalue
G02AJ g02aj = new G02AJ();
G1d = convert2DTo1D(G);
ldg = G.length;
n = G[0].length;
alpha = 0.001;
double[] H1d = convert2DTo1D(H);
int ldh = H.length;
errtol = 0;
maxit = 0;
ldx = n;
X1d = new double[ldx * n];
iter = 0;
double norm2 = 0;
ifail = 0;
g02aj.eval(G1d, ldg, n, alpha, H1d, ldh, errtol, maxit, X1d, ldx, iter, norm2, ifail);
X = convert1DTo2D(X1d, ldx);
iter = g02aj.getITER();
Nearest correlation matrix using element-wise weighting
1.0000 -0.3251 0.1881 0.5371 0.0255 -0.5893 -0.0625 -0.1929
-0.3251 1.0000 0.2048 0.2249 0.4144 0.2841 0.2914 0.4081
0.1881 0.2048 1.0000 -0.1462 0.7883 0.2718 -0.6084 0.8804
0.5371 0.2249 -0.1462 1.0000 0.2138 -0.0002 0.6070 -0.2199
0.0255 0.4144 0.7883 0.2138 1.0000 0.6566 -0.2807 0.8756
-0.5893 0.2841 0.2718 -0.0002 0.6566 1.0000 0.0474 0.5930
-0.0625 0.2914 -0.6084 0.6070 -0.2807 0.0474 1.0000 -0.4471
-0.1929 0.4081 0.8804 -0.2199 0.8756 0.5930 -0.4471 1.0000
jobvl = "N";
jobvr = "N";
n = X[0].length;
lda = X.length;
wr = new double[n];
wi = new double[n];
ldvl = 1;
vl = new double[ldvl];
ldvr = 1;
vr = new double[ldvr];
lwork = 3 * n;
work = new double[lwork];
info = 0;
f08na.eval(jobvl, jobvr, n, X1d, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info);
Arrays.sort(wr);
Sorted eigenvalues of X: 0.0010 0.0010 0.0375 0.1734 0.6882 1.7106 1.9224 3.4660
-
We probably really wish to fix our leading block of true correlations, so it does not change at all.
-
We have the NAG routine G02AN.
-
This routine fixes a leading block, which we require to be positive definite.
-
We apply the shrinking algorithm of Higham, Strabić and Šego. The approach is not computationally expensive.
-
What we find is the smallest α, such that X is a true correlation matrix:
-
$$G_{11}$$ is the leading$$k$$ by$$k$$ block of the approximate correlation matrix that we wish to fix. -
$$\alpha$$ is in the interval$$[0,1]$$ .
// Call the NAG routine fixing the top 3-by-3 block
G02AN g02an = new G02AN();
G1d = convert2DTo1D(G);
ldg = G.length;
n = G[0].length;
int k = 3;
errtol = 0;
double eigtol = 0;
ldx = n;
X1d = new double[ldx * n];
alpha = 0.001;
iter = 0;
double eigmin = 0;
norm2 = 0;
ifail = 0;
g02an.eval(G1d, ldg, n, k, errtol, eigtol, X1d, ldx, alpha, iter, eigmin, norm2, ifail);
X = convert1DTo2D(X1d, ldx);
iter = g02an.getITER();
alpha = g02an.getALPHA();
Nearest correlation matrix with fixed leading block
1.0000 -0.3250 0.1881 0.4606 0.0051 -0.4887 -0.0579 -0.1271
-0.3250 1.0000 0.2048 0.1948 0.3245 0.2183 0.2294 0.3391
0.1881 0.2048 1.0000 -0.1060 0.6124 0.2211 -0.4936 0.7202
0.4606 0.1948 -0.1060 1.0000 0.2432 0.0101 0.5160 -0.2567
0.0051 0.3245 0.6124 0.2432 1.0000 0.5320 -0.2634 0.7949
-0.4887 0.2183 0.2211 0.0101 0.5320 1.0000 0.0393 0.4769
-0.0579 0.2294 -0.4936 0.5160 -0.2634 0.0393 1.0000 -0.3185
-0.1271 0.3391 0.7202 -0.2567 0.7949 0.4769 -0.3185 1.0000
jobvl = "N";
jobvr = "N";
n = X[0].length;
lda = X.length;
wr = new double[n];
wi = new double[n];
ldvl = 1;
vl = new double[ldvl];
ldvr = 1;
vr = new double[ldvr];
lwork = 3 * n;
work = new double[lwork];
info = 0;
f08na.eval(jobvl, jobvr, n, X1d, lda, wr, wi, vl, ldvl, vr, ldvr, work, lwork, info);
Arrays.sort(wr);
Sorted eigenvalues of X: 0.0000 0.1375 0.2744 0.3804 0.7768 1.6263 1.7689 3.0356
Value of alpha returned: 0.2003
- The routine G02AP fixes arbitrary elements by finding the smallest α, such that X is a true correlation matrix in:
-
A "1" in H fixes corresponding elements in G.
-
$$0 < h_{ij} < 1$$ weights corresponding element in G. -
$$\alpha$$ is again in the interval$$[0,1]$$ .
-
First method proposed to solve our original problem, however, it is very slow.
-
The idea is we alternate projecting onto two sets, which are:
- the set of smeidefinite matrices (S1), and
- matrices with unit diagonal (s2)
-
We do this until we converge on a matrix with both properties.
-
A new approach by Higham and Strabić uses Anderson Acceleration, and makes the method worthwhile.
-
In particular, we will be able to fix elements whilst finding the nearest true correlation matrix in the Frobenius norm.
-
Our projections are now:
- the set of (semi)definite matrices with some minimum eigenvalue, and
- matrix with elements
$$G_{i,j}$$ for some given indices$$i$$ and$$j$$
-
To appear in a future NAG Library.