如何使用Cholesky分解或其他方法进行关联数据模拟


19

给定相关矩阵,我使用Cholesky分解来模拟相关的随机变量。问题是,结果永远不会像给出的那样重现相关结构。这是Python中的一个小例子来说明这种情况。

import numpy as np    

n_obs = 10000
means = [1, 2, 3]
sds = [1, 2, 3] # standard deviations 

# generating random independent variables 
observations = np.vstack([np.random.normal(loc=mean, scale=sd, size=n_obs)
                   for mean, sd in zip(means, sds)])  # observations, a row per variable

cor_matrix = np.array([[1.0, 0.6, 0.9],
                       [0.6, 1.0, 0.5],
                       [0.9, 0.5, 1.0]])

L = np.linalg.cholesky(cor_matrix)

print(np.corrcoef(L.dot(observations))) 

打印:

[[ 1.          0.34450587  0.57515737]
 [ 0.34450587  1.          0.1488504 ]
 [ 0.57515737  0.1488504   1.        ]]

如您所见,事后估计的相关矩阵与前一个大大不同。我的代码中是否有错误,或者是否有使用Cholesky分解的替代方法?

编辑

不好意思,请原谅。我认为代码和/或Cholesky分解的应用方式没有错误,因为对我以前研究过的材料有些误解。实际上,我确信该方法本身并不意味着精确,并且在情况使我提出此问题之前,我一直认为还可以。谢谢您指出我的误解。我已经编辑了标题,以更好地反映@Silverfish提出的实际情况。


1
Cholesky工作正常,这实际上是一个“您能否在我的代码中找到错误”类型的问题。问题的标题和内容(最初写的)基本上是“ Cholesky不起作用,有什么替代方法”吗?这将使搜索此站点的用户感到非常困惑。是否应该对此问题进行编辑以反映这一点?(不利的一面是javlacalle的答案不太重要。不利的一面是问题文字将反映搜索者在页面上实际可以找到的内容。)
Silverfish,2015年

@Antoni Parellada是的,我想您已经将(a)正确方法的MATLAB代码转换为Python numpy,并完成了np.linalg.cholesky的下三角与MATLAB的chol的上三角调整。我已经将OP的错误代码转换为等效的MATLAB,并重复了他的错误结果。
Mark L. Stone

Answers:


11

此处描述基于Cholesky分解的方法,该方法在此处描述 并且在Mark L. Stone的答案中几乎与该答案同时发布。

尽管如此,有时我还是根据多元正态分布生成 :ñμΣ

ÿ=X+μ=Λ1个/2Φ

其中是最终绘制,是来自单变量标准正态分布的绘制,是包含目标矩阵的归一化特征向量的矩阵,是包含以相同顺序排列的特征值的对角矩阵作为列中的特征向量。ÿXΦΣΛΣΦ

中的示例R(很抱歉,我没有使用与问题中使用的软件相同的软件):

n <- 10000
corM <- rbind(c(1.0, 0.6, 0.9), c(0.6, 1.0, 0.5), c(0.9, 0.5, 1.0))
set.seed(123)
SigmaEV <- eigen(corM)
eps <- rnorm(n * ncol(SigmaEV$vectors))
Meps <- matrix(eps, ncol = n, byrow = TRUE)    
Meps <- SigmaEV$vectors %*% diag(sqrt(SigmaEV$values)) %*% Meps
Meps <- t(Meps)
# target correlation matrix
corM
#      [,1] [,2] [,3]
# [1,]  1.0  0.6  0.9
# [2,]  0.6  1.0  0.5
# [3,]  0.9  0.5  1.0
# correlation matrix for simulated data
cor(Meps)
#           [,1]      [,2]      [,3]
# [1,] 1.0000000 0.6002078 0.8994329
# [2,] 0.6002078 1.0000000 0.5006346
# [3,] 0.8994329 0.5006346 1.0000000

您可能也对此职位职位感兴趣 。


为了使再现的相关矩阵精确,应在将随机数据应用于数据生成过程之前,从随机发生器中删除随机数据中的虚假相关。例如,检查eps中随机数据的相关性,首先查看虚假相关性。
哥德弗里德·赫尔姆斯

17

如果您用单词和代数而不是代码(或至少使用伪代码编写)来解释所做的工作,人们可能会更快地发现您的错误。

您似乎正在执行以下操作(尽管可能会换位):

  1. 生成标准法线的矩阵žñ×ķž

  2. 将列乘以并添加以获取非标准法线μ σ一世μ一世

  3. 计算以获得相关法线。ÿ=大号X

其中是您的相关矩阵的左Cholesky因子。大号

您应该做的是:

  1. 生成标准法线的矩阵žñ×ķž

  2. 计算以获得相关法线。X=大号ž

  3. 将列乘以并添加以获取非标准法线μ σ一世μ一世

现场对此算法有很多解释。例如

如何生成相关的随机数(给定的均值,方差和相关度)?

我可以使用Cholesky方法生成具有给定均值的相关随机变量吗?

本节将直接根据所需协方差矩阵进行讨论,并给出一种用于获取所需样本协方差的算法:

使用给定的样本协方差矩阵生成数据


11

Cholesky因式分解没有任何问题。您的代码中有错误。请参阅下面的编辑。

这是MATLAB代码和结果,首先是n_obs = 10000,然后n_obs = 1e8。为简单起见,由于它不会影响结果,因此我不会理会均值,即将它们设为零。注意,MATLAB的chol产生矩阵M的上三角Cholesky因子R,使得R'* R =M。numpy.linalg.cholesky产生下三角Cholesky因子,因此需要对我的代码进行调整;但我相信您的代码在这方面是可以的。

   >> correlation_matrix = [1.0, 0.6, 0.9; 0.6, 1.0, 0.5;0.9, 0.5, 1.0];
   >> SD = diag([1 2 3]);
   >> covariance_matrix = SD*correlation_matrix*SD
   covariance_matrix =
      1.000000000000000   1.200000000000000   2.700000000000000
      1.200000000000000   4.000000000000000   3.000000000000000
      2.700000000000000   3.000000000000000   9.000000000000000
   >> n_obs = 10000;
   >> Random_sample = randn(n_obs,3)*chol(covariance_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.599105015695768   0.898395949647890
      0.599105015695768   1.000000000000000   0.495147514173305
      0.898395949647890   0.495147514173305   1.000000000000000
   >> n_obs = 1e8;
   >> Random_sample = randn(n_obs,3)*chol(covariance_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.600101477583914   0.899986072541418
      0.600101477583914   1.000000000000000   0.500112824962378
      0.899986072541418   0.500112824962378   1.000000000000000

编辑:我发现了你的错误。您错误地应用了标准偏差。这与您所做的等效,这是错误的。

   >> n_obs = 10000;
   >> Random_sample = randn(n_obs,3)*SD*chol(correlation_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.336292731308138   0.562331469857830
      0.336292731308138   1.000000000000000   0.131270077244625
      0.562331469857830   0.131270077244625   1.000000000000000
   >> n_obs=1e8;
   >> Random_sample = randn(n_obs,3)*SD*chol(correlation_matrix);
   >> disp(corr(Random_sample))
      1.000000000000000   0.351254525742470   0.568291702131030
      0.351254525742470   1.000000000000000   0.140443281045496
      0.568291702131030   0.140443281045496   1.000000000000000

6

CV与代码无关,但我对看到所有好的答案(特别是@Mark L. Stone贡献)后的外观很感兴趣。该问题的实际答案在其职位上提供(如有疑问,请记入其职位)。我将在此处移动此附加信息,以方便将来检索此帖子。在Mark回答之后,无需淡化任何其他出色的答案,这可以通过更正OP中的帖子来解决问题。

资源

在PYTHON:

import numpy as np

no_obs = 1000             # Number of observations per column
means = [1, 2, 3]         # Mean values of each column
no_cols = 3               # Number of columns

sds = [1, 2, 3]           # SD of each column
sd = np.diag(sds)         # SD in a diagonal matrix for later operations

observations = np.random.normal(0, 1, (no_cols, no_obs)) # Rd draws N(0,1) in [3 x 1,000]

cor_matrix = np.array([[1.0, 0.6, 0.9],
                       [0.6, 1.0, 0.5],
                       [0.9, 0.5, 1.0]])          # The correlation matrix [3 x 3]

cov_matrix = np.dot(sd, np.dot(cor_matrix, sd))   # The covariance matrix

Chol = np.linalg.cholesky(cov_matrix)             # Cholesky decomposition

array([[ 1.        ,  0.        ,  0.        ],
       [ 1.2       ,  1.6       ,  0.        ],
       [ 2.7       , -0.15      ,  1.29903811]])

sam_eq_mean = Chol .dot(observations)             # Generating random MVN (0, cov_matrix)

s = sam_eq_mean.transpose() + means               # Adding the means column wise
samples = s.transpose()                           # Transposing back

print(np.corrcoef(samples))                       # Checking correlation consistency.

[[ 1.          0.59167434  0.90182308]
 [ 0.59167434  1.          0.49279316]
 [ 0.90182308  0.49279316  1.        ]]

IN [R]:

no_obs = 1000             # Number of observations per column
means = 1:3               # Mean values of each column
no_cols = 3               # Number of columns

sds = 1:3                 # SD of each column
sd = diag(sds)         # SD in a diagonal matrix for later operations

observations = matrix(rnorm(no_cols * no_obs), nrow = no_cols) # Rd draws N(0,1)

cor_matrix = matrix(c(1.0, 0.6, 0.9,
                      0.6, 1.0, 0.5,
                      0.9, 0.5, 1.0), byrow = T, nrow = 3)     # cor matrix [3 x 3]

cov_matrix = sd %*% cor_matrix %*% sd                          # The covariance matrix

Chol = chol(cov_matrix)                                        # Cholesky decomposition

     [,1] [,2]      [,3]
[1,]    1  1.2  2.700000
[2,]    0  1.6 -0.150000
[3,]    0  0.0  1.299038

sam_eq_mean = t(observations) %*% Chol          # Generating random MVN (0, cov_matrix)

samples = t(sam_eq_mean) + means

cor(t(samples))

          [,1]      [,2]      [,3]
[1,] 1.0000000 0.6071067 0.8857339
[2,] 0.6071067 1.0000000 0.4655579
[3,] 0.8857339 0.4655579 1.0000000

colMeans(t(samples))
[1] 1.035056 2.099352 3.065797
apply(t(samples), 2, sd)
[1] 0.9543873 1.9788250 2.8903964

1

正如其他人已经表明的那样:cholesky有效。这是一段很短且非常接近伪代码的代码:MatMate中的一个代码段:

Co = {{1.0, 0.6, 0.9},  _
      {0.6, 1.0, 0.5},  _
      {0.9, 0.5, 1.0}}           // make correlation matrix


chol = cholesky(co)              // do cholesky-decomposition           
data = chol * unkorrzl(randomn(3,100,0,1))  
                                 // dot-multiply cholesky with random-
                                 // vectors with mean=0, sdev=1  
                                 //(refined by a "decorrelation" 
                                 //to remove spurious/random correlations)   


chk = data *' /100               // check the correlation of the data
list chk

1.0000  0.6000  0.9000
0.6000  1.0000  0.5000
0.9000  0.5000  1.0000
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.