I am trying to calculate the rolling average pairwise correlation between a number of assets in excel.
I have created a custom function, and tried using the correlation matrix, but neither are satisfactory.
The assets are located in columns
Correlation must be over the defined time period/lookback
But if data is missing in part of the look back, that asset is ignored (until it has sufficient data)
So far, the function works but I cannot force it to ignore incomplete ranges (it replaces blank with 0):
Function avgRho(DataRange As Range)
'
Dim nRow As Long, nCol As Long
Dim i As Long, j As Long, j1 As Long, j2 As Long
Dim RtnData() As Double
Dim v1
Dim counts As Double, sum_correl As Double
Dim rtn1() As Double, rtn2() As Double
'
avgRho = 0
'
nRow = DataRange.Rows.Count
nCol = DataRange.Columns.Count
If nRow <= 2 Or nCol <= 1 Then Exit Function
'
ReDim RtnData(1 To nRow, 1 To nCol)
ReDim rtn1(1 To nRow)
ReDim rtn2(1 To nRow)
'
For i = 1 To nRow
For j = 1 To nCol
v1 = DataRange(i, j).Value
RtnData(i, j) = v1
Next j
Next i
'
counts = 0
sum_correl = 0
For j1 = 1 To nCol
'
For i = 1 To nRow
rtn1(i) = RtnData(i, j1)
Next i
'
For j2 = j1 + 1 To nCol
For i = 1 To nRow
rtn2(i) = RtnData(i, j2)
Next i
'
counts = counts + 1
sum_correl = sum_correl + WorksheetFunction.Correl(rtn1, rtn2)
'
Next j2
'
Next j1
'
If sum_correl > 0 Then avgRho = sum_correl / counts
'
End Function
Solved by doing similar to suggestion, thanks Peekay, filtering out blank cells when adding to the data matrix RtnData
Also changed the count process:
Function avgRho(DataRange As Range)
'
Dim nRow As Long, nCol As Long
Dim i As Integer, j As Integer, j1 As Integer, j2 As Integer
Dim RtnData() As Double
Dim v1
Dim counts As Double, sum_correl As Double
Dim rtn1() As Double, rtn2() As Double
Dim MatColCount As Integer
'
avgRho = 0
MatColCount = 0
'
nRow = DataRange.Rows.Count
nCol = DataRange.Columns.Count
If nRow <= 2 Or nCol <= 1 Then Exit Function
'
ReDim RtnData(1 To nRow, 1 To nCol)
ReDim rtn1(1 To nRow)
ReDim rtn2(1 To nRow)
'
For i = 1 To nRow
MatColCount = 0
For j = 1 To nCol
If DataRange(1, j).Value <> "" And DataRange(nRow, j) <> "" Then
v1 = DataRange(i, j).Value
MatColCount = MatColCount + 1
RtnData(i, MatColCount) = v1
End If
Next j
Next i
'
counts = 0
sum_correl = 0
If MatColCount <= 1 Then Exit Function
'
For j1 = 1 To MatColCount
For i = 1 To nRow
rtn1(i) = RtnData(i, j1)
Next i
'
For j2 = j1 + 1 To MatColCount
For i = 1 To nRow
rtn2(i) = RtnData(i, j2)
Next i
'
counts = counts + 1
sum_correl = sum_correl + WorksheetFunction.Correl(rtn1, rtn2)
'
Next j2
'
Next j1
'
If counts > 0 Then avgRho = sum_correl / counts
'
End Function
Related
i can't find where i am doing wrong my code is not working so. I'm a bit of a novice at this, I don't quite understand what the problem is
it gives me warning on this line
matrix = Range("B5").Resize(rows, cols)
Sub TamsayiliRasgeleMatris()
'Deklarasyonlar
Dim rows As Integer, cols As Integer
Dim lowerBound As Integer, upperBound As Integer
Dim sum As Double, average As Double
'Kullanıcıdan girdiler alma
rows = Range("A2").Value
cols = Range("B2").Value
lowerBound = Range("C2").Value
upperBound = Range("D2").Value
'Boş bir matris oluşturma
Dim matrix As Variant
matrix = Range("B5").Resize(rows, cols)
'Matrisi rastgele sayılarla doldurma
For i = 1 To rows
For j = 1 To cols
matrix(i, j) = Int((upperBound - lowerBound + 1) * Rnd + lowerBound)
sum = sum + matrix(i, j)
Next j
Next i
'Matrisi çalışma sayfasına yazma
matrix.Copy Destination:=Range("B5")
'Ortalama değerini hesaplayın ve E2 hücresine yazma
average = sum / (rows * cols)
Range("E2").Value = average
'Matris transpozunu oluşturun ve altına yapıştırın
Dim transposed As Variant
transposed = Application.Transpose(matrix)
transposed.Copy Destination:=Range("B5").Offset(rows + 1, 0)
'Değerleri ortalama değerine göre renklendirin
For i = 1 To rows
For j = 1 To cols
If matrix(i, j) < average Then
matrix(i, j).Interior.Color = vbCyan
ElseIf matrix(i, j) > average Then
matrix(i, j).Interior.Color = vbMagenta
End If
Next j
Next i
End Sub
Understanding Ranges and Arrays
A lot was changed so some of your comments may not apply anymore.
Option Explicit
Sub TamsayiliRasgeleMatris()
Dim ws As Worksheet: Set ws = ActiveSheet ' Improve!
'Kullanicidan girdiler alma
Dim rCount As Long: rCount = ws.Range("A2").Value
Dim cCount As Long: cCount = ws.Range("B2").Value
Dim MinInteger As Long: MinInteger = ws.Range("C2").Value
Dim MaxInteger As Long: MaxInteger = ws.Range("D2").Value
'Boş bir matris oluşturma
Dim Matrix() As Variant: ReDim Matrix(1 To rCount, 1 To cCount)
Dim r As Long, c As Long, Total As Long
'Matrisi rastgele sayilarla doldurma
For r = 1 To rCount
For c = 1 To cCount
Matrix(r, c) = Int((MaxInteger - MinInteger + 1) * Rnd + MinInteger)
Total = Total + Matrix(r, c)
Next c
Next r
ws.Range("E2").Value = Total
Dim rg As Range, fCell As Range
'Matrisi çalişma sayfasina yazma
Set fCell = ws.Range("B5")
With fCell
.Resize(ws.Rows.Count - .Row + 1, ws.Columns.Count - .Column + 1).Clear
End With
Set rg = fCell.Resize(rCount, cCount)
rg.Value = Matrix
'Ortalama degerini hesaplayin ve F2 hücresine yazma
Dim Avg As Double: Avg = Total / (rCount * cCount)
ws.Range("F2").Value = Avg
'Degerleri ortalama degerine göre renklendirin
For r = 1 To rCount
For c = 1 To cCount
Select Case Matrix(r, c)
Case Is < Avg: rg.Cells(r, c).Interior.Color = vbCyan
Case Is > Avg: rg.Cells(r, c).Interior.Color = vbMagenta
Case Else ' !?
End Select
Next c
Next r
'Matris transpozunu oluşturun ve altina yapiştirin
Dim tMatrix() As Long: ReDim tMatrix(1 To cCount, 1 To rCount)
For r = 1 To rCount
For c = 1 To cCount
tMatrix(c, r) = Matrix(r, c)
Next c
Next r
Set fCell = fCell.Offset(rCount + 1)
Set rg = fCell.Resize(cCount, rCount)
rg.Value = tMatrix
'Degerleri ortalama degerine göre renklendirin
For c = 1 To cCount
For r = 1 To rCount
Select Case tMatrix(c, r)
Case Is < Avg: rg.Cells(c, r).Interior.Color = vbCyan
Case Is > Avg: rg.Cells(c, r).Interior.Color = vbMagenta
Case Else ' !?
End Select
Next r
Next c
End Sub
Here follow some suggestion to possibly make your code run
taking in consideration the following code snippet:
Dim matrix As Variant
matrix = Range("B5").Resize(rows, cols)
since:
matrix is declared as of Variant type
Value is the default property for any Range object
then matrix is finally resulting in a Variant array, as if you had coded:
matrix = Range("B5").Resize(rows, cols).Value
further on you are coding:
matrix.Copy Destination:=Range("B5")
which would result in an error since an array doesn't have any Copy method, while this latter is available for many objects, among which the Range object
hence you should sort of "reverse" the matrix assignation code line as follows:
'Matrisi çalisma sayfasina yazma
Range("B5").Resize(rows, cols).Value = matrix
just a little more complicated is the fix of the other wrong Copy statement
Dim transposed As Variant
transposed = Application.Transpose(matrix)
transposed.Copy Destination:=Range("B5").Offset(rows + 1, 0)
which, along the lines of the preceeding fix, is to be coded as follows:
Dim transposed As Variant
transposed = Application.Transpose(matrix)
Range("B5").Offset(rows + 1, 0).Resize(cols, rows).Value = transposed
and where you'll notice I swapped cols and rows in the Resize() property to account for transposition
finally the following snippet:
'Değerleri ortalama değerine göre renklendirin
For i = 1 To rows
For j = 1 To cols
If matrix(i, j) < average Then
matrix(i, j).Interior.Color = vbCyan
ElseIf matrix(i, j) > average Then
matrix(i, j).Interior.Color = vbMagenta
End If
Next j
Next i
is to be twicked as follows:
With Range("B5") 'reference the target range upper-left cell
For i = 1 To rows
For j = 1 To cols
If matrix(i, j) < average Then
.Offset(i - 1, j - 1).Interior.Color = vbCyan 'write in the cell corresponding to the ith row and jth column of matrix
ElseIf matrix(i, j) > average Then
.Offset(i - 1, j - 1).Interior.Color = vbMagenta
End If
Next
Next
End With
I'm completely new to VBA and have decided to try recreate excels built in functions. I'm currently trying to create a function that finds the median. for example, it first identifies whether the array is column vector or row vector. i used bubble sort to sort my array in ascending order and then apply a code to find the median value of the sorted array.
However i seem to get a error during the sort, it exists when it tries to swap two values. i get #VALUE error.
Function mymedian(x As Range) As Double
' order array of values asc
' use bubblesort
Dim nr As Integer
Dim nc As Integer
Dim i As Integer
Dim j As Integer
Dim temp As Double
Dim n As Integer
nr = x.Rows.count
nc = x.Columns.count
' col vector
If nc = 1 Then
For i = 2 To nr
For j = 2 To nr
If x.Cells(j - 1, 1).Value > x.Cells(j, 1).Value Then
temp = x.Cells(j, 1)
x.Cells(j, 1).Value = x.Cells(j - 1, 1).Value ' code exists here
x.Cells(j - 1, 1) = temp
n = n + 1
End If
Next j
Next i
Else
' row vector
If nc > 1 Then
For i = 2 To nc
For j = 2 To nc
If x.Cells(1, j - 1).Value > x.Cells(1, j).Value Then
temp = x.Cells(1, j)
x.Cells(1, j) = x.Cells(1, j - 1).Value
x.Cells(1, j - 1) = temp
n = n + 1
End If
Next j
Next i
End If
End If
As a sub this works fine, does this imply bubble sorts only work as sub routines? i also tried to call the sub within a function, however this wasn't working.
Sub bubblesort()
Dim x As Range
Set x = Selection
Dim nr As Integer
Dim temp As Double
Dim i As Integer
Dim j As Integer
nr = x.Rows.count
For i = 2 To nr
For j = 2 To nr
If x.Cells(j - 1, 1).Value > x.Cells(j, 1).Value Then
temp = x.Cells(j, 1)
x.Cells(j, 1) = x.Cells(j - 1, 1)
x.Cells(j - 1, 1) = temp
End If
Next j
Next i
End Sub
Function middle(x As Range)
Dim n As Integer
Dim mid As Double
Call bubblesort(x)
n = x.Rows.count
mid = x.Cells(n / 2, 1).Value
middle = mid
End Function
Reinventing the Wheel: VBA Median UDF
Reinventing the wheel
Median
MEDIAN
VarType
Cell error values
Function MyMedian(ByVal SourceRange As Range) As Variant
Const ProcName As String = "MyMedian"
On Error GoTo ClearError
' Calculate the source range number of cells ('dnCount').
Dim srCount As Long: srCount = SourceRange.Rows.Count
Dim scCount As Long: scCount = SourceRange.Columns.Count
Dim dnCount As Long: dnCount = srCount * scCount
Dim sData() As Variant
' Write the values from the source range to the source array ('sData'),
' a 2D one-based array.
If dnCount = 1 Then ' one cell
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = SourceRange.Value
Else ' multiple cells
sData = SourceRange.Value
End If
' Define the destination array('dArr'), a 1D one-based array.
Dim dArr() As Double: ReDim dArr(1 To dnCount)
Dim sValue As Variant
Dim sr As Long, sc As Long
Dim sNumber As Double
Dim dn As Long, n As Long, cn As Long
Dim dNumber As Double
' Bubble sort the numbers in the destination array
' while reading from the source array.
For sr = 1 To srCount
For sc = 1 To scCount
sValue = sData(sr, sc)
If VarType(sValue) = vbDouble Then ' the source value is a number
sNumber = CDbl(sValue)
dn = dn + 1
' Locate a greater number in the destination array.
For n = 1 To dn - 1
dNumber = dArr(n)
If dNumber > sNumber Then Exit For
Next n
' Shift the greater destination numbers to the right.
If n < dn Then
For cn = dn To n + 1 Step -1
dArr(cn) = dArr(cn - 1)
Next cn
'Else ' the source number is the greatest number; do nothing
End If
' Write the current source number to the destination array.
dArr(n) = sNumber
'Else ' the source value is not a number; do nothing
End If
Next sc
Next sr
' Mimicking the Excel 'MEDIAN' function to return '#NUM!'
' when there is no number in the source range.
If dn = 0 Then MyMedian = CVErr(xlErrNum): Exit Function
' Return the median using the middle destination array value(s).
If dn Mod 2 = 0 Then ' even
MyMedian = (dArr(dn / 2) + dArr(dn / 2 + 1)) / 2
Else ' odd
MyMedian = dArr(Int(dn / 2) + 1)
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
I'm writing a macro public function for finding max of the last N non empty cells for winter months (November, December, January, February).
Here's what I got:
Public Function SuperMax_Winter(rng2 As Range, rng As Range, N As Long) As Double
Dim RngCnt, RngCnt2 As Long, i As Long, Zum As Double, j As Long
Dim ary() As Double
ReDim ary(0)
j = 0
RngCnt = rng.Count
RngCnt2 = rng2.Count
If RngCnt <> RngCnt2 Then SuperMax_Winter = "#ERROR!"
For i = RngCnt To 1 Step -1
If rng(i).Value <> "" Then
If rng2(i).Month = 11 Or rng2(i).Month = 12 Or rng2(i).Month = 1 Or rng2(i).Month = 2 Then
ary(j) = rng(i).Value
If j = N - 1 Then Exit For
ReDim Preserve ary(j + 1)
j = j + 1
End If
End If
Next i
SuperMax_Winter = Application.WorksheetFunction.Max(ary)
End Function
But I get a #VALUE! error.
I think Month should be first:
If Month(rng2(i).Value)= 11 Or Month(rng2(i).Value)= 12 Or Month(rng2(i).Value)= 1 Or Month(rng2(i).Value)= 2 Then
Hope this help.
HI I have copied some code. It works fine as long there are more than two rows in each column. If there is only only one row it returns the value " "
'and not the first and only value in that column. Have can I get it to work?
Sub ListCombinations()
Dim col As New Collection
Dim c As Range, sht As Worksheet, res
Dim i As Long, arr, numCols As Long
Dim numCol As Integer
Dim Col_Cnt As Integer
Dim Rows_Cnt As Integer
Set sht = Worksheets("Sheet5")
Col_Cnt = sht.UsedRange.Columns.Count 'add
Rows_Cnt = sht.UsedRange.Rows.Count ' add
For Each c In sht.Range("A1:B1").Cells
col.add Application.Transpose(sht.Range(c, c.End(xlDown)))
numCols = numCols + 1
MsgBox "numCols = " & numCols
Next c
res = Combine(col, "~~")
For i = 0 To UBound(res)
arr = Split(res(i), "~~")
sht.Range("H1").Offset(i, 0).Resize(1, numCols) = arr
Next i
End Sub
'create combinations from a collection of string arrays
Function Combine(col As Collection, SEP As String) As String()
Dim rv() As String
Dim pos() As Long, lengths() As Long, lbs() As Long, ubs() As Long
Dim t As Long, i As Long, n As Long, ub As Long
Dim numIn As Long, s As String, r As Long
numIn = col.Count
MsgBox numIn
ReDim pos(1 To numIn)
ReDim lbs(1 To numIn)
ReDim ubs(1 To numIn)
ReDim lengths(1 To numIn)
t = 0
For i = 1 To numIn 'calculate # of combinations, and cache bounds/lengths
lbs(i) = LBound(col(i))
ubs(i) = UBound(col(i))
lengths(i) = (ubs(i) - lbs(i)) + 1
pos(i) = lbs(i)
t = IIf(t = 0, lengths(i), t * lengths(i))
Next i
ReDim rv(0 To t - 1) 'resize destination array
For n = 0 To (t - 1)
s = ""
For i = 1 To numIn
s = s & IIf(Len(s) > 0, SEP, "") & col(i)(pos(i)) 'build the string
Next i
rv(n) = s
For i = numIn To 1 Step -1
If pos(i) <> ubs(i) Then 'Not done all of this array yet...
pos(i) = pos(i) + 1 'Increment array index
For r = i + 1 To numIn 'Reset all the indexes
pos(r) = lbs(r) ' of the later arrays
Next r
Exit For
End If
Next i
Next n
Combine = rv
End Function
I'm pretty new to UDF's and I'm not sure entirely how they function. My function returns correct information so long no new rows are inserted. It's as if headRng gets saved to memory when first used and doesn't get updated even if a new row is inserted. How can I fix this?
Additionally. My function appears to be looping a LOT of times. In my code you'll see a msgbox that appears after 1000 rows. So I know it's looping at least 1000 times. No idea why it's looping though. Forgot I had another workbook open with this same function which was causing the 1000+ loop.
Example of how it might be used: https://i.imgur.com/zRQo0SH.png
Function StraightLineFunc(headRng As Range, dataRng As Range) As Double
Application.Volatile True
Dim arrCntr As Integer
Dim arr() As Variant
Dim rowOffset As Integer
Dim cntr As Integer
Dim stdvTotal As Double
stdvTotal = 0
cntr = 0
arrCntr = 1
For Each cell In headRng
If cell <> "Response" And cell <> "Open-Ended Response" And cell <> "" Then
If cell.Offset(-1, 0) <> "" And cntr > 0 Then
stdvTotal = stdvTotal + StdDev(arr)
End If
If cell.Offset(-1, 0) <> "" Then
cntr = cntr + 1
'new grouping heading
Erase arr
ReDim arr(headRng.Columns.Count)
arrCntr = 1
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
arrCntr = arrCntr + 1
Else
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
arrCntr = arrCntr + 1
End If
End If
Next cell
stdvTotal = stdvTotal + StdDev(arr)
StraightLineFunc = stdvTotal
End Function
Function StdDev(arr)
Dim i As Integer
Dim avg As Single, SumSq As Single
Dim k1 As Long, k2 As Long
Dim n As Long
k1 = LBound(arr)
k2 = UBound(arr)
n = 0
avg = Mean(arr)
For i = k1 To k2
If arr(i) = 0 Or arr(i) = "" Then
'do nothing
Else
n = n + 1
SumSq = SumSq + (arr(i) - avg) ^ 2
End If
Next i
StdDev = Sqr(SumSq / (n - 1))
End Function
Function Mean(arr)
Dim Sum As Single
Dim i As Integer
Dim k1 As Long, k2 As Long
Dim n As Long
k1 = LBound(arr)
k2 = UBound(arr)
Sum = 0
n = 0
For i = k1 To k2
If arr(i) = 0 Or arr(i) = "" Then
'do nothing
Else
n = n + 1
Sum = Sum + arr(i)
End If
Next i
Mean = Sum / n
End Function
as about headrng first address remembrance it must be a matter of how you're checking subranges, relying on the presence of certain non blank cells over headrng itself. so that if you insert one or more rows between headrng row and the one above it, it would have a different behavior
as about the looping 1000 times it must be because you must have copied a formula that uses it down to row 1000, so that excel calculates all of them even if you're changing only one row
moreover from your data example I think you should change code as follows
Option Explicit
Function StraightLineFunc1(headRng As Range, dataRng As Range) As Double
Application.Volatile True
Dim arrCntr As Integer
Dim arr() As Variant
Dim rowOffset As Integer
Dim cntr As Integer
Dim stdvTotal As Double
Dim cell As Range
stdvTotal = 0
cntr = 0
arrCntr = 1
For Each cell In headRng
If cell <> "Response" And cell <> "Open-Ended Response" And cell <> "" Then
If cell.Offset(-1, 0) <> "" And cntr > 0 Then
stdvTotal = stdvTotal + WorksheetFunction.StDev(arr)
End If
If cell.Offset(-1, 0) <> "" Then
cntr = cntr + 1
'new grouping heading
Erase arr
arrCntr = 1
ReDim Preserve arr(1 To arrCntr)
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
Else
arrCntr = arrCntr + 1
ReDim Preserve arr(1 To arrCntr)
arr(arrCntr) = cell(dataRng.Row - 1, 1).Value
End If
End If
Next cell
stdvTotal = stdvTotal + WorksheetFunction.StDev(arr)
StraightLineFunc1 = stdvTotal
End Function
which however could still suffer form the remembrance issue
so I'd also throw in a different "subranges" checking like follows
Function StraightLineFunc2(headRng As Range, dataRng As Range) As Double
'Application.Volatile True
Dim stdvTotal As Double
Dim j1 As Long, j2 As Long
j1 = 1
Do Until InStr("Open-Ended Response", headRng(1, j1)) = 0 And headRng(1, j1) <> ""
j1 = j1 + 1
Loop
Set headRng = headRng.Offset(, j1 - 1).Resize(, headRng.Columns.Count - j1 + 1)
j1 = 1
Do While j1 < headRng.Columns.Count
j2 = j1
Do While headRng(1, j2) <> "Response" And j2 <= headRng.Columns.Count
j2 = j2 + 1
Loop
stdvTotal = stdvTotal + WorksheetFunction.StDev(Range(headRng(1, j1), headRng(1, j2 - 1)).Offset(dataRng.Row - headRng.Row))
j1 = j2 + 1
Loop
StraightLineFunc2 = stdvTotal
End Function