Come utilizzare la decomposizione di Cholesky o un'alternativa per la simulazione di dati correlati


19

Uso la decomposizione di Cholesky per simulare variabili casuali correlate in base a una matrice di correlazione. Il fatto è che il risultato non riproduce mai la struttura di correlazione così come viene fornita. Ecco un piccolo esempio in Python per illustrare la situazione.

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

Questo stampa:

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

Come puoi vedere, la matrice di correlazione stimata post-hoc differisce drasticamente da quella precedente. C'è un bug nel mio codice o c'è qualche alternativa all'uso della decomposizione di Cholesky?

modificare

Chiedo scusa per questo casino. Non pensavo ci fosse un errore nel codice e / o nel modo in cui la decomposizione di Cholesky veniva applicata a causa di un malinteso sul materiale che avevo studiato prima. In effetti ero sicuro che il metodo in sé non doveva essere preciso ed ero stato d'accordo con quello fino alla situazione che mi ha fatto pubblicare questa domanda. Grazie per aver indicato l'idea sbagliata che ho avuto. Ho modificato il titolo per riflettere meglio la situazione reale proposta da @Silverfish.


1
Cholesky funziona bene, e questa è davvero una domanda tipo "riesci a trovare il bug nel mio codice". Il titolo e il contenuto della domanda, come è stato originariamente scritto, sono fondamentalmente "Cholesky non funziona, qual è un'alternativa"? Ciò sarà molto confuso per gli utenti che effettuano ricerche in questo sito. Questa domanda dovrebbe essere modificata per riflettere questo? (Il rovescio della medaglia è che la risposta di javlacalle sarebbe meno pertinente. Il lato positivo è che il testo della domanda rifletterebbe ciò che i ricercatori avrebbero effettivamente trovato sulla pagina.)
Silverfish

@Antoni Parellada Sì, penso che tu abbia tradotto il mio codice MATLAB per il (a) modo corretto di farlo in Python numpy, completo di regolazione per np.linalg.cholesky che è triangolare inferiore rispetto a col di MATLAB che è triangolare superiore. Avevo già tradotto il codice errato del PO nel suo equivalente MATLAB e duplicato i suoi risultati errati.
Mark L. Stone

Risposte:


11

L'approccio basato sulla decomposizione di Cholesky dovrebbe funzionare, è descritto qui ed è mostrato nella risposta di Mark L. Stone pubblicata quasi contemporaneamente a questa risposta.

N(μ,Σ)

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

YXΦΣΛΣΦ

Esempio in R(mi dispiace non sto usando lo stesso software che hai usato nella domanda):

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

Potresti anche essere interessato a questo post e questo post .


Per rendere precisa la matrice di correlazione riprodotta, è necessario rimuovere le correlazioni spurie nei dati casuali dal generatore casuale prima di applicarlo alla procedura di generazione dei dati. Ad esempio, controlla la correlazione dei tuoi dati casuali in eps per vedere prima quelle correlazioni spurie.
Gottfried Helms,

17

Le persone probabilmente troverebbero il tuo errore molto più velocemente se spiegassi cosa hai fatto con parole e algebra piuttosto che con il codice (o almeno scrivendolo usando lo pseudocodice).

Sembra che tu stia facendo l'equivalente di questo (sebbene possibilmente trasposto):

  1. Genera un n×KZ

  2. moltiplicare le colonne per σioμio

  3. Y=LX

L

Quello che dovresti fare è questo:

  1. n×KZ

  2. calcolare X=LZ

  3. σioμio

Ci sono molte spiegazioni di questo algoritmo sul sito. per esempio

Come generare numeri casuali correlati (date medie, varianze e grado di correlazione)?

Posso usare il metodo Cholesky per generare variabili casuali correlate con una data media?

Questo discute direttamente in termini di matrice di covarianza desiderata e fornisce anche un algoritmo per ottenere una covarianza campione desiderata :

Generazione di dati con una data matrice di covarianza campione


11

Non c'è niente di sbagliato nella fattorizzazione di Cholesky. C'è un errore nel tuo codice. Vedi modifica sotto.

Ecco il codice MATLAB e i risultati, prima per n_obs = 10000 come hai fatto, poi per n_obs = 1e8. Per semplicità, poiché non influisce sui risultati, non mi preoccupo dei mezzi, cioè li faccio zeri. Si noti che il col di MATLAB produce un fattore Cholesky triangolare superiore R della matrice M tale che R '* R = M. numpy.linalg.cholesky produce un fattore Cholesky triangolare inferiore, quindi è necessario un aggiustamento rispetto al mio codice; ma credo che il tuo codice vada bene in questo senso.

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

Modifica: ho trovato il tuo errore. Hai applicato erroneamente la deviazione standard. Questo è l'equivalente di quello che hai fatto, il che è sbagliato.

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

Il CV non riguarda il codice, ma ero incuriosito dal vedere come ciò avrebbe avuto cura di tutte le risposte valide, e in particolare il contributo di @Mark L. Stone. La risposta effettiva alla domanda viene fornita sul suo post (si prega di accreditare il suo post in caso di dubbio). Sto spostando queste informazioni allegate qui per facilitare il recupero di questo post in futuro. Senza minimizzare nessuna delle altre eccellenti risposte, dopo la risposta di Mark, questo risolve il problema correggendo il post nel PO.

fonte

A 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

Come altri hanno già dimostrato: opere cholesky. Ecco un pezzo di codice che è molto breve e molto vicino allo pseudocodice: un codice in 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.