Как использовать разложение Холецкого или альтернативу для моделирования коррелированных данных


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
Cholesky работает просто отлично, и это действительно вопрос типа «можешь ли ты найти ошибку в моем коде». Название и содержание вопроса, как оно изначально написано, в основном «Холецкий не работает, что является альтернативой»? Это будет очень запутанно для пользователей, которые ищут этот сайт. Следует ли редактировать этот вопрос, чтобы отразить это? (Недостатком является то, что ответ javlacalle будет менее актуальным. Достоинством является то, что текст вопроса будет отражать то, что на самом деле найдут искатели на странице.)
Silverfish

@Antoni Parellada Да, я думаю, что вы перевели мой код MATLAB для (a) правильного способа сделать это в Python Numpy, в комплекте с настройкой для np.linalg.cholesky, чтобы быть нижней треугольной по сравнению с chol MATLAB, верхней верхней треугольной. Я уже перевел неправильный код OP в его эквивалент MATLAB и продублировал его неверные результаты.
Марк Л. Стоун

Ответы:


11

Подход, основанный на разложении Холецкого, должен работать, он описан здесь и показан в ответе Марка Л. Стоуна, опубликованном почти одновременно с этим ответом.

N(μ,Σ)

Yзнак равноQИкс+μ,сQзнак равноΛ1/2Φ,

YИксΦΣΛΣΦ

Пример в 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×КZ

  2. σяμя

  3. высчитывает Yзнак равноLИкс

L

Что вы должны сделать, это:

  1. N×КZ

  2. Иксзнак равноLZ

  3. σяμя

Есть много объяснений этого алгоритма на сайте. например

Как генерировать коррелированные случайные числа (с учетом средних, дисперсий и степени корреляции)?

Могу ли я использовать метод Холецкого для генерации коррелированных случайных величин с заданным средним значением?

Этот обсуждает это непосредственно в терминах желаемой ковариационной матрицы, а также дает алгоритм для получения желаемой выборки ковариации:

Генерация данных с заданной выборочной ковариационной матрицей


11

Нет ничего плохого в факторизации Холецкого. В вашем коде есть ошибка. Смотрите редактирование ниже.

Вот код и результаты MATLAB, сначала для n_obs = 10000, как у вас, а затем для n_obs = 1e8. Для простоты, поскольку это не влияет на результаты, я не беспокоюсь о средствах, т. Е. Делаю их нулями. Обратите внимание, что chol 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.        ]]

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

Как уже показали другие: холеский работает. Вот фрагмент кода, который очень короткий и очень близок к псевдокоду: кодовый фрагмент в 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.