Як використовувати розклад Холеського або альтернативу для моделювання корельованих даних


19

Я використовую розклад Холеського для імітації корельованих випадкових змінних із заданою кореляційною матрицею. Вся справа в тому, що результат ніколи не відтворює кореляційну структуру, як це дано. Ось невеликий приклад в 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.        ]]

Як бачимо, оціночна кореляційна матриця після спеціальних дій різко відрізняється від попередньої. Чи є помилка в моєму коді чи є якась альтернатива використанню розкладу Чолеського?

Редагувати

Прошу пробачення за цей безлад. Я не думав, що в коді та / або в способі розкладання Чолеського сталася помилка через деяке нерозуміння матеріалу, який я вивчав раніше. Насправді я був впевнений, що сам метод не повинен був бути точним, і я був з цим добре, поки не виникла ситуація, яка змусила мене поставити це питання. Дякую, що вказали на помилкове уявлення, яке я мав. Я відредагував заголовок, щоб краще відобразити реальну ситуацію, запропоновану @Silverfish.


1
Чолеський працює просто чудово, і це справді питання "Ви можете знайти помилку в моєму коді". Заголовок і зміст питання, як це було написано спочатку, "Чолеський не працює, яка альтернатива"? Це буде дуже заплутаним для користувачів, які шукають цей сайт. Чи слід це питання редагувати, щоб відобразити це? (Мінус полягає в тому, що відповідь javlacalle була б менш актуальною. Наверху текст запитання відображав би те, що шукачі насправді знайдуть на сторінці.)
Silverfish

@Antoni Parellada Так, я думаю, ви перевели мій код MATLAB для (а) правильного способу його виконання в numy Python, доповнений коригуванням для np.linalg.cholesky нижнього трикутника проти чота MATLAB, який верхній трикутний. Я вже переклав неправильний код ОП в його еквівалент MATLAB і дублював його неправильні результати.
Марк Л. Стоун

Відповіді:


11

Підхід, заснований на розкладанні Чолеського, повинен працювати, він описаний тут і показаний у відповіді Марка Л. Стоун, розміщеному майже в той же час, що і ця відповідь.

Тим не менш, я інколи створював малюнки з багатоваріантного нормального розподілу наступним чином:N(μ,Σ)

Y=QX+μ,withQ=Λ1/2Φ,

де - кінцеві малюнки, X - черпаки з одновимірного стандартного нормального розподілу, Φ - матриця, що містить нормовані власні вектори цільової матриці Σ, а Λ - діагональна матриця, що містить власні значення ΣYXΦΣΛΣ в тому ж порядку, що і власні вектори в стовпці .Φ

Приклад в 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. n×kZ

  2. σiμi щоб отримати нестандартні нормали

  3. Y=LX

L - лівий коефіцієнт Холеського вашої кореляційної матриці.

Що вам слід зробити, це:

  1. Створити n×kZ

  2. X=LZ щоб отримати корельовані норми.

  3. σiμi

Існує багато пояснень цього алгоритму на сайті. напр

Як генерувати корельовані випадкові числа (задані засоби, відхилення та ступінь кореляції)?

Чи можу я використовувати метод Холеського для генерації корельованих випадкових змінних із заданим середнім?

Цей напрямок обговорює його безпосередньо з точки зору бажаної матриці коваріації, а також дає алгоритм отримання бажаної вибіркової коваріації:

Генерування даних із заданою коваріаційною матрицею вибірки


11

У факторизації Чолеського немає нічого поганого. У вашому коді помилка. Див. Редагування нижче.

Ось код MATLAB та результати, спочатку для n_obs = 10000, як у вас, потім для n_obs = 1e8. Для простоти, оскільки це не впливає на результати, я не морочуся засобами, тобто роблю їх нулі. Зауважте, що чол MATLAB виробляє верхній трикутний коефіцієнт Холеського R матриці M таким чином, що R '* R = M. numpy.linalg.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

Резюме не стосується коду, але я був заінтригований, щоб побачити, як це виглядатиме після всіх хороших відповідей і, зокрема, внеску @Mark L. Stone. Фактична відповідь на питання надається на його посаді (будь-ласка, кредитуйте його допис у разі сумнівів). Я переміщую цю додану інформацію сюди, щоб полегшити пошук цієї публікації в майбутньому. Не відтворюючи жодної з інших відмінних відповідей, після відповіді Марка це вирішує питання, виправляючи посаду в ОП.

Джерело

В ПІТОНІ:

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

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

Як уже показали інші: холеські твори. Ось фрагмент коду, який дуже короткий і дуже близький до псевдокоду: кодексу в 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.