“When your work speaks for itself, don’t interrupt.” [Henry J. Kaiser]

Abstract

Falls Sie korrelierte Zufallszahlen erzeugen müssen, ist die Iman Conover Methode besser als die Cholesky Zerlegung.

1982 veröffentlichten Iman und Conover ihren ursprünglichen Artikel (externer Link!) “A distribution-free approach to inducing rank correlation among input variables”.

Rick Wicklin schrieb dazu im Jahr 2021 (externer Link!) “Simulate correlated variables by using the Iman-Conover transformation”. Sein Artikel enthält eine SAS Implementierung der Iman Conover Methode.

2005 veröffentlichte Stephen J. Mildenhall (externer Link!) “Correlation and Aggregate Loss Distributions with an Emphasis on the Iman-Conover Method”.

Ich implementierte das Beispiel aus Mildenhall’s Artikel sowohl mit Excel Tabellenblattfunktionen als auch mit Excel / VBA:

Die Eingabematrix X:

sbRandCorr_01_Screen

Die Zielkorrelation S:

sbRandCorr_02_Screen

Die Cholesky Zerlegung C von S:

(Vergleichen Sie bitte mit Cholesky)

sbRandCorr_03_Screen

Die Zwischenmatrix M (konstante Werte, identisch zu Mildenhall’s Daten):

sbRandCorr_04_Screen

Sie können analoge Daten automatisch mit der Matrixformel in A1:A20 erzeugen:

=STANDNORMINV(SEQUENZ(20;1;1;1)/21)/STABWNA(STANDNORMINV(SEQUENZ(20;1;1;1)/21))

oder mit der Matrixformel

=RandomShuffle($A$1:$A$20)

in den Zellen B1:B20 (kopieren Sie diese entsprechend in die Spalten C und D).

Nun erhalten Sie die Kovarianzmatrix E:

sbRandCorr_05_Screen

Und ihre Cholesky Zerlegung F:

(Vergleichen Sie bitte mit Cholesky)

sbRandCorr_06_Screen

Die Zwischenmatrix T:

sbRandCorr_07_Screen

Sie können die erzeugten Korrelationen prüfen:

sbRandCorr_08_Screen

Berechnen Sie den Rang der Zahlen in den Spalten von T:

sbRandCorr_09_Screen

Schließlich erhalten Sie:

sbRandCorr_10_Screen

Appendix – RandomShuffle Code

Bitte den Haftungsausschluss im Impressum beachten.

Function RandomShuffle(vtemp As Variant) As Variant
Dim j As Long, k As Long, n As Long
Dim temp As Double, u As Double
Application.Volatile
With Application.WorksheetFunction
vtemp = .Transpose(.Transpose(vtemp))
n = UBound(vtemp, 1)
j = n
Do While j > 0
    u = Rnd()
    k = Int(j * u + 1)
    temp = vtemp(j, 1)
    vtemp(j, 1) = vtemp(k, 1)
    vtemp(k, 1) = temp
    j = j - 1
Loop
RandomShuffle = vtemp
End With
End Function

Appendix – ImanConover Code

Hier ist die VBA Implementierung der Iman Conover Methode. Ich habe diesen Code auch in der Anwendung sbGenerateTestData verwendet.

Bitte den Haftungsausschluss im Impressum beachten.

Function ImanConover(rInputMatrix As Range, _
        rTargetCorrelation As Range) As Variant
'Implements the Iman-Conover method to generate random
'number vectors with a given correlation.
'Algorithm as described in:
'Mildenham, November 27, 2005
'Correlation and Aggregate Loss Distributions With An
'Emphasis On The Iman-Conover Method
Dim vX As Variant   'Input matrix
Dim vS As Variant   'Target correlation matrix
Dim vC As Variant   'Cholesky decomposition of vS
Dim vM As Variant   'Intermediate matrix M
Dim vE As Variant   'Covariance matrix E
Dim vF As Variant   'Cholesky decomposition of vE
Dim vT As Variant   'Intermediate matrix T
Dim d As Double, dS As Double
Dim i As Long, j As Long, k As Long
Dim lRow As Long, lCol As Long

With Application.WorksheetFunction
vX = .Transpose(.Transpose(rInputMatrix))
lRow = rInputMatrix.Rows.Count
lCol = rInputMatrix.Columns.Count

'#############################################################################
'#                                Check inputs                               #
'#############################################################################

If lCol <> rTargetCorrelation.Columns.Count _
    And rTargetCorrelation.Rows.Count <> rTargetCorrelation.Columns.Count Then
    'Structure of target correlation matrix needs to fit input matrix
    ImanConover = CVErr(xlErrNum)
    Exit Function
End If
vS = .Transpose(.Transpose(rTargetCorrelation))
For i = 1 To lCol
    If vS(i, i) <> 1# Then
        'Target correlation matrix not 1 on diagonal
        ImanConover = CVErr(xlErrValue)
        Exit Function
    End If
    For j = 1 To i - 1
        If vS(i, j) <> vS(j, i) Then
            'Target correlation matrix not symmetric
            ImanConover = CVErr(xlErrValue)
            Exit Function
        End If
    Next j
Next i

vC = .Transpose(Cholesky2(vS))

'#############################################################################
'#                        Create intermediate matrix M                       #
'#############################################################################

ReDim vMV(1 To lRow) As Double
d = 0#
dS = 0#
For i = 1 To Int(lRow / 2)
    vMV(i) = .NormSInv(i / (lRow + 1))
    vMV(lRow - i + 1) = -vMV(i)
    d = d + 2# * vMV(i) * vMV(i)
Next i
If lRow Mod 2 = 1 Then vMV((lRow + 1) / 2) = 0 'Just for clarity, it's already 0
d = Sqr(d / lRow)
For i = 1 To lRow
    vMV(i) = vMV(i) / d
Next i

vM = vX
For i = 1 To lRow
    vM(i, 1) = vMV(i)
Next i

Dim vMW As Variant
For i = 2 To lCol
    vMW = RandomShuffle2(vMV)
    For j = 1 To lRow
        vM(j, i) = vMW(j)
    Next j
Next i

'#############################################################################
'#                        Calculate covariance matrix E                      #
'#############################################################################

vE = vC
For i = 1 To lCol
    vE(i, i) = .Covar(.Index(.Transpose(vM), i), .Index(.Transpose(vM), i))
    For j = i + 1 To lCol
        vE(i, j) = .Covar(.Index(.Transpose(vM), i), .Index(.Transpose(vM), j))
        vE(j, i) = vE(i, j)
    Next j
Next i
   
vF = .Transpose(Cholesky2(vE))

vT = .MMult(.MMult(vM, .MInverse(vF)), vC)

'#############################################################################
'#                        Compute ranks of matrix T                          #
'#############################################################################

Dim vRT As Variant, vR As Variant
vRT = vX
For j = 1 To lCol
    vR = IndexX(lRow, vT, j)
    For i = 1 To lRow
        vRT(i, j) = vR(i)
    Next i
    vR = IndexX(lRow, vX, j)
    For i = 1 To lRow
        vX(i, j) = vX(vR(i), j)
    Next i
Next j

'#############################################################################
'#                        Calculate result matrix Y                          #
'#############################################################################

Dim vY As Variant
vY = vX
For i = 1 To lRow
    For j = 1 To lCol
        vY(i, j) = vX(vRT(i, j), j)
    Next j
Next i

ImanConover = vY
End With
End Function

Function IndexX(n As Long, arr As Variant, colNo As Long) As Variant
'Indexes an array arr[1..n], i.e., outputs the array indx[1..n] such
'that arr[indx[j]] is in ascending order for j = 1, 2, . . . ,n. The
'input quantities n and arr are not changed. Translated from [31].
Const m As Long = 7
Const NSTACK As Long = 50
Dim i As Long, indxt As Long, ir As Long, itemp As Long, j As Long
Dim k As Long, l As Long
Dim jstack As Long, istack(1 To NSTACK) As Long
Dim a As Double

ir = n
l = 1
ReDim indx(1 To n) As Long
For j = 1 To n
    indx(j) = j
Next j

Do While 1
    If (ir - l < m) Then
        For j = l + 1 To ir
            indxt = indx(j)
            a = arr(indxt, colNo)
            For i = j - 1 To l Step -1
                If (arr(indx(i), colNo) <= a) Then Exit For
                indx(i + 1) = indx(i)
            Next i
            indx(i + 1) = indxt
        Next j
        If (jstack = 0) Then Exit Do
        ir = istack(jstack)
        jstack = jstack - 1
        l = istack(jstack)
        jstack = jstack - 1
    Else
        k = (l + ir) / 2
        itemp = indx(k)
        indx(k) = indx(l + 1)
        indx(l + 1) = itemp
        If (arr(indx(l), colNo) > arr(indx(ir), colNo)) Then
            itemp = indx(l)
            indx(l) = indx(ir)
            indx(ir) = itemp
        End If
        If (arr(indx(l + 1), colNo) > arr(indx(ir), colNo)) Then
            itemp = indx(l + 1)
            indx(l + 1) = indx(ir)
            indx(ir) = itemp
        End If
        If (arr(indx(l), colNo) > arr(indx(l + 1), colNo)) Then
            itemp = indx(l)
            indx(l) = indx(l + 1)
            indx(l + 1) = itemp
        End If
        i = l + 1
        j = ir
        indxt = indx(l + 1)
        a = arr(indxt, colNo)
        Do While 1
            Do
                i = i + 1
            Loop While (arr(indx(i), colNo) < a)
            Do
                j = j - 1
            Loop While (arr(indx(j), colNo) > a)
            If (j < i) Then Exit Do
            itemp = indx(i)
            indx(i) = indx(j)
            indx(j) = itemp
        Loop
        indx(l + 1) = indx(j)
        indx(j) = indxt
        jstack = jstack + 2
        If (jstack > NSTACK) Then
            'STACK too small in indexx
            IndexX = CVErr(xlErrNum)
            Exit Function
        End If
        If (ir - i + 1 >= j - l) Then
            istack(jstack) = ir
            istack(jstack - 1) = i
            ir = j - 1
        Else
            istack(jstack) = j - 1
            istack(jstack - 1) = l
            l = i
        End If
    End If
Loop
IndexX = indx
End Function

Function Cholesky2(vA As Variant) As Variant
'Array version
Dim d As Double
Dim i As Long, j As Long, k As Long, n As Long
n = UBound(vA, 1)
If n <> UBound(vA, 2) Then
    Cholesky2 = CVErr(xlErrRef)
    Exit Function
End If

ReDim vR(1 To n, 1 To n) As Variant
For j = 1 To n
    d = 0#
    For k = 1 To j - 1
        d = d + vR(j, k) * vR(j, k)
    Next k
    vR(j, j) = vA(j, j) - d
    If vR(j, j) <= 0 Then
        Cholesky2 = CVErr(xlErrNum)
        Exit Function
    End If
    vR(j, j) = Sqr(vR(j, j))
    
    For i = j + 1 To n
        d = 0#
        For k = 1 To j - 1
            d = d + vR(i, k) * vR(j, k)
        Next k
        vR(i, j) = (vA(i, j) - d) / vR(j, j)
    Next i
    
    For i = 1 To j - 1
        vR(i, j) = 0
    Next i
Next j
Cholesky2 = vR
End Function

Download

Bitte den Haftungsausschluss im Impressum beachten.

mildenhall_example_on_iman_conover.xlsm [53 KB Excel Datei, ohne jegliche Gewährleistung]