İlişkili veri simülasyonu için Cholesky ayrışması veya alternatifi nasıl kullanılır


19

Bir korelasyon matrisi verildiğinde ilişkili rasgele değişkenleri simüle etmek için Cholesky ayrışmasını kullanıyorum. Mesele şu ki, sonuç asla korelasyon yapısını verildiği gibi yeniden üretmez. İşte Python'da durumu göstermek için küçük bir örnek.

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))) 

Bu yazdırır:

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

Gördüğünüz gibi, post-hoc tahmini korelasyon matrisi öncekinden önemli ölçüde farklıdır. Kodumda bir hata var mı veya Cholesky ayrışmasını kullanmanın başka bir yolu var mı?

Düzenle

Bu karmaşa için özür dilerim. Kodda ve / veya daha önce çalıştığım malzemenin bazı yanlış anlaşılmaları nedeniyle Cholesky ayrışmasının uygulanma biçiminde bir hata olduğunu düşünmemiştim. Aslında, yöntemin kendisinin kesin olması gerekmediğinden emindim ve bu soruyu bana gönderen duruma kadar iyiydim. Sahip olduğum yanlış anlama işaret ettiğiniz için teşekkür ederiz. @Silverfish tarafından önerilen gerçek durumu daha iyi yansıtmak için başlığı düzenledim.


1
Cholesky gayet iyi çalışıyor ve bu gerçekten bir "benim kodumda hata bulabilirsiniz" tipi bir soru. Sorunun başlığı ve içeriği, başlangıçta yazıldığı gibi, temelde "Cholesky işe yaramıyor, alternatif nedir?" Bu siteyi arayan kullanıcılar için çok kafa karıştırıcı olacaktır. Bu soru bunu yansıtacak şekilde düzenlenmeli mi? (Dezavantajı, javlacalle'ın cevabının daha az alakalı olacağıdır. Bunun tersi, soru metninin, arama yapanların sayfada gerçekten ne bulacağını yansıtacağıdır.)
Silverfish

@Antoni Parellada Evet, sanırım MATLAB kodumu (a) Python numpy'ye doğru bir şekilde yapmanın, np.linalg.cholesky'nin daha düşük üçgen olması ve MATLAB'ın kolunun üst üçgen olması için ayarlanması ile tamamladığınızı düşünüyorum. OP'nin yanlış kodunu zaten MATLAB eşdeğerine çevirmiştim ve yanlış sonuçlarını çoğaltmıştım.
Mark L. Stone

Yanıtlar:


11

Cholesky ayrışmasına dayanan yaklaşım işe yaramalı, burada ve Mark L. Stone'un cevabında bu cevabın hemen hemen aynı zamanda yayınlandığı gösterilmiştir.

Yine de, bazen çok değişkenli Normal dağılımdan çekimler ürettim N-(μ,Σ) :

Y=SX+μ,ileS=Λ1/2Φ,

YXΦΣΛΣΦ .

Örnek R(üzgünüm soruda kullandığınız yazılımı kullanmıyorum):

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

Bu yazı ve bu yazı ile de ilgilenebilirsiniz .


Çoğaltılan korelasyon-matrisini kesin hale getirmek için, rasgele verilerdeki sahte korelasyonları, veri oluşturma prosedürüne uygulamadan önce rasgele jeneratörden çıkarmalıdır. Örneğin, ilk olarak sahte korelasyonları görmek için eps olarak rastgele verilerinizin korelasyonunu kontrol edin.
Gottfried Helms

17

Koddan ziyade kelimeler ve cebirle yaptıklarınızı açıklarsanız insanlar muhtemelen hatalarınızı çok daha hızlı bulurlardı (ya da en azından sahte kod kullanarak yazıyorlardı).

Bunun eşdeğerini yapıyor gibi görünüyorsunuz (muhtemelen aktarılmış olsa da):

  1. nxkZ

  2. σbenμben

  3. Y=LX

L , korelasyon matrisinizin sol Cholesky faktörüdür.

Yapmanız gereken şudur:

  1. nxkZ

  2. X=LZ

  3. σbenμben

Bu algoritmanın yerinde birçok açıklaması var. Örneğin

İlişkili rasgele sayılar nasıl oluşturulur (verilen araçlar, varyanslar ve korelasyon derecesi)?

Cholesky yöntemini verilen ortalama ile ilişkili rasgele değişkenler üretmek için kullanabilir miyim?

Bu, doğrudan istenen kovaryans matrisi açısından tartışır ve ayrıca istenen bir örnek kovaryans elde etmek için bir algoritma verir :

Belirli bir örnek kovaryans matrisi ile veri oluşturma


11

Cholesky çarpanlarına ayırmada yanlış bir şey yok. Kodunuzda bir hata var. Aşağıdaki düzenlemeye bakın.

MATLAB kodu ve sonuçları, önce n_obs = 10000 için, sonra n_obs = 1e8 için. Basitlik açısından, sonuçları etkilemediğinden, araçlarla uğraşmam, yani onları sıfırlar. MATLAB'ın kolunun, M 'matrisinin üst üçgen Cholesky faktörü R'yi ürettiğini ve R' * R = M. numpy.linalg.cholesky'nin daha düşük bir üçgen Cholesky faktörü ürettiğini unutmayın, bu nedenle koduma karşı bir ayarlama yapılması gerekir; ancak kodunuzun bu açıdan iyi olduğuna inanıyorum.

   >> 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

Edit: Hatasını buldum. Standart sapmayı yanlış uyguladınız. Bu yaptığınız şeyin eşdeğeridir, bu yanlıştır.

   >> 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 kodla ilgili değil, ama bunun tüm iyi cevaplara ve özellikle @Mark L. Stone katkısına nasıl bakacağını görmek ilgimi çekti. Sorunun asıl cevabı yazısında verilmiştir (lütfen şüphe durumunda yazısına kredi verin). İleride bu yazının alınmasını kolaylaştırmak için bu eklenmiş bilgileri buraya taşıyorum. Mark'ın cevabından sonra, diğer mükemmel cevapların hiçbirini çalmadan, bu OP'deki yazıyı düzelterek sorunu çözer.

Kaynak

PİTONDA:

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

Diğerlerinin gösterdiği gibi: Cholesky çalışır. Burada pseudocode'a çok kısa ve çok yakın bir kod parçası: MatMate'de bir kod parçası:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.