It appears that I'm not the only one to struggle with this, but I can't find an good answer, so I try my chance here!
I would like to find a 3rd degree polynomial line of best fit, given two sets of data stored in arrays. Basically, it looks like that
Dim X(0 to 9) as Integer
Dim Y(0 to 9) as Integer
for k = 0 to 9
X(k) = 'something
Y(k) = 'something else
Next
So far I've worked out how to solve my issue with a 1st degree polynomial Y = aX + b :
a = Application.WorksheetFunction.LinEst(Y, X, True, False)(1)
b = Application.WorksheetFunction.LinEst(Y, X, True, False)(2)
I also found that if my values X and Y are written into a Sheet, I can find a higher polynomial with this :
'x-axis values are entered in X column, y-values in Y column
Z = Application.Evaluate("=linest(Y1:Y10,X1:X10^{1,2,3})")
'The answer is in Z such that Y = Z(1)*Y^3+Z(2)*Y^2+Z(3)*Y+Z(4)
Assuming my arrays are already sorted, how can I use linest with arrays and not values entered into cells?
If you want the OLS best fit coefficients (i.e. linear regression) and not interpolation, then you can do it like this for a second order polynomial:
Sub test()
Dim X(0 To 9, 0 To 1) As Integer
Dim Y(0 To 9, 0 To 0) As Integer
i = 0
For n = 0 To 9
X(n, 0) = i
X(n, 1) = i * i
Y(n, 0) = i * i + 3 * i - 7
i = i + 1
Next
B = WorksheetFunction.LinEst(Y, X)
B2 = B(1)
B1 = B(2)
B0 = B(3)
End Sub
This correctly returns -7 for B0, 3 for B1 and 1 for B2. This is pretty much the same as this answer: https://stackoverflow.com/a/27137957/1011724
You can make it more general, say a k-order polynomial like this:
Function f(X) As Integer ' This function is replaced by your data
f = -2 * WorksheetFunction.Power(X, 3) + 3 * X - 7
End Function
Sub test2()
Order = 3
ReDim X(0 To 9, 0 To Order - 1)
Dim Y(0 To 9, 0 To 0) As Integer
'Note i is only to generate dummy data
i = 1
For n = 0 To 9 'Replace 9 with the length of your data
X(n, 0) = i 'This line is replaced by your actual data
' Create the higher order features:
For k = 1 To Order
X(n, k-1) = Application.WorksheetFunction.Power(i, k)
Next
Y(n, 0) = f(i) 'This line is replaced by your actual data
i = i + 1
Next
B = WorksheetFunction.LinEst(Y, X)
End Sub
I've found another way to work with polynomial regression with arrays. The code is in this link. https://rosettacode.org/wiki/Polynomial_regression. Because the code in the website was not working due to incorrect array, I made a minor correction. It works now.
Function polynomial_regression(y As Variant, x As Variant, degree As Integer) As Variant
Dim a() As Double
ReDim a(1 To UBound(x), 1 To degree)
For i = 1 To UBound(x)
For j = 1 To degree
a(i, j) = x(i) ^ j
Next j
Next i
polynomial_regression = WorksheetFunction.LinEst(WorksheetFunction.Transpose(y), a, True, True)
End Function
I added a function to the above to use range variables as inputs to work as an equivalent to the Excel function linest()
Public Function RangeToArray(ByRef myRange As Variant) As Variant
Dim individualCell As Range
Dim i As Integer
ReDim myArray(1 To myRange.Count)
i = 1
For Each individualCell In myRange
myArray(i) = individualCell.Value
i = i + 1
Next
RangeToArray = myArray
End Function
Function PolyFit(yRange As Variant, xRange As Variant, degree As Integer) As Variant
Dim xAry() As Variant
Dim yAry() As Variant
Dim a() As Double
xAry() = RangeToArray(xRange)
yAry() = RangeToArray(yRange)
ReDim a(1 To (UBound(xAry)), 1 To (degree))
For i = 1 To (UBound(xAry))
For j = 1 To (degree)
a(i, j) = xAry(i) ^ (j)
Next j
Next i
PolyFit = WorksheetFunction.LinEst(WorksheetFunction.Transpose(yAry), a, True, False)
End Function
I have a 2-dimensional range (i, j) like this:
1 2 3 4 5
6 7 8 9 0
I want to copy&paste it to another sheet like this:
1 6 2 7 3 8 4 9 5 0
I need to recalculate the 2-dim range many times and store the results on another sheet, where each row stores one iteration.
Right now I store all calculations in a array (N, i*j) using two for-loops and then paste all itearations on another sheet.
Is there a faster way to do that?
Current code:
Dim a(1 To 100, 1 To 10) As Double
For iter = 1 To 100
Calculate
For i = 1 To 2
For j = 1 To 5
a(iter, i + j * (i - 1)) = Cells(i, j)
Next j
Next i
Next iter
With Sheets("results")
Range(.Cells(1, 1), .Cells(100, 2 * 5)) = a
End With
UPD:
After each "calculate" the values of the initial range change. The example just illustrates how the values from 2-d range should be stored in one row.
UPD2:
Corrected my current code
Something like this should work for you:
Sub tgr()
Dim rData As Range
Dim iter As Long
Dim lNumIterations As Long
Dim i As Long, j As Long, k As Long
Dim a() As Double
Dim aAfterCalc As Variant
Set rData = Sheets("Data").Range("A1:E2")
lNumIterations = 100
ReDim a(1 To lNumIterations, 1 To rData.Rows.Count * rData.Columns.Count)
For iter = 1 To lNumIterations
k = 0
Calculate
aAfterCalc = rData.Value
For j = 1 To rData.Columns.Count
For i = 1 To rData.Rows.Count
k = k + 1
a(iter, k) = aAfterCalc(i, j)
Next i
Next j
Next iter
Sheets("results").Range("A1").Resize(lNumIterations, UBound(a, 2)).Value = a
End Sub
Try this. It gives your desired output and only uses two loops (instead of three)
' For loop
Dim i As Long, j As Long
' Initalise array
Dim tmp(1 To 100, 1 To 10) As Variant
'Loop through all rows in already initalised array
For i = LBound(tmp, 1) To UBound(tmp, 1)
'Calculate to get updated row contents
Calculate
'Loop through each column in row
'The Round and divided by two is to calculate the number of columns concerned instead of the number in the array
For j = LBound(tmp, 2) To Round((UBound(tmp, 2) + 0.1) / 2)
'First row
tmp(i, (j + j - 1)) = Cells(1, j).Value2
'Second row
' If incase the array is initalised to an odd number otherwise this would be out of range
If j * 2 <= UBound(tmp, 2) Then
tmp(i, j * 2) = Cells(2, j).Value2
End If
Next j
Next i
' Write back to sheet
With Sheets("results").Cells(1, 1)
Range(.Offset(0, 0), .Offset(UBound(tmp, 1) - 1, UBound(tmp, 2) - 1)) = tmp
End With
Not sure I get you, but something like this
Sub test()
Dim a() As Variant
Dim b() As Variant
a = Range("a1:e1").Value
b = Range("a2:e2").Value
For x = 1 To 5
Range("H1").Offset(0, x).Value = a(1, x)
Range("H1").Offset(0, 5 + x).Value = b(1, x)
Next x
End Sub
Private Sub this()
Dim this As Variant, counter As Long, that As Integer, arr() As Variant
counter = 0
this = ThisWorkbook.Sheets("Sheet3").UsedRange
For i = LBound(this, 2) To UBound(this, 2)
counter = counter + 2
ReDim Preserve arr(1 To 1, 1 To counter)
arr(1, counter - 1) = this(1, i)
arr(1, counter) = this(2, i)
Next i
ThisWorkbook.Sheets("Sheet4").Range(ThisWorkbook.Sheets("Sheet4").Cells(1, 1), ThisWorkbook.Sheets("Sheet4").Cells(1, counter)).Value2 = arr
End Sub
The function works as an "asset class allocation" engine (with constraints in the Param range) and simulates a portfolio model on each row of the array. I attempt to publish the array onto the worksheet with four methods, each of them fails.
Those Params are configured in M3:O6 as {Min 5, Max 100, Step 5} for each asset weight in assets A,B,C,D.
The function fails to paste the 2D array into Excel. There are 970 permutations, so array is 970x5. Is this a size issue? Code works fine up to PrintArray AllocArray, ActiveWorkbook.Worksheets("Test").[A1]
Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End Sub
Function ConfigureArrayFolly()
Dim Param() As Variant
Param = Range("M3:O6")
Dim AMin, AMax, AStep, BMin, BMax, BStep, CMin, CMax, CStep, DMin, DMax, DStep As Double
AMin = Param(1, 1): AMax = Param(1, 2): AStep = Param(1, 3)
BMin = Param(2, 1): BMax = Param(2, 2): BStep = Param(2, 3)
CMin = Param(3, 1): CMax = Param(3, 2): CStep = Param(3, 3)
DMin = Param(4, 1): DMax = Param(4, 2): DStep = Param(4, 3)
Dim nSim As Double: nSim = (1 + (AMax - AMin) / AStep) * (1 + (BMax - BMin) / BStep) * (1 + (CMax - CMin) / CStep) * (1 + (DMax - DMin) / DStep)
Dim nAsset As Double: nAsset = 4 ' Count {A, B, ... , F}
'Debug.Print nSim
Dim AllocArray() As Variant
ReDim AllocArray(1 To 970, 0 To nAsset)
Dim Sim As Integer: Sim = 1
Dim A As Double
Dim B As Integer
Dim C As Integer
Dim D As Integer
For A = AMin To AMax Step AStep
For B = BMin To BMax Step BStep
'If (A + B) > 100 Then GoTo endB
For C = CMin To CMax Step CStep
'If (A + B + C) > 100 Then GoTo endC
For D = DMin To DMax Step DStep
' nAsset is the count of set {a1, a2 ... an}
' AllocArray(1, 2, 3) = (Sim, a1, a2)
'Constraints
If (A + B + C + D) <> 100 Then GoTo endD
Debug.Print Sim; A; B; C; D
AllocArray(Sim, 0) = Sim
AllocArray(Sim, 1) = A
AllocArray(Sim, 2) = B
AllocArray(Sim, 3) = C
AllocArray(Sim, 4) = D
Sim = Sim + 1
' Debug.Print "Sim "; Sim; AllocArray(1, 1)
endD:
Next D
endC:
Next C
endB:
Next B
Next A
' Print to sheet - Method One (fails)
Dim NumRows As Long: Dim NumCols As Long
NumRows = UBound(AllocArray, 1) - LBound(AllocArray, 1) + 1
NumCols = UBound(AllocArray, 2) - LBound(AllocArray, 2) + 1
Set Destination = Range("D20").Resize(NumRows, NumCols).Value = AllocArray
' Print to sheet - Method Two (fails)
'Sheets("Test").Range("D20").Value = AllocArray(1, 1)
'Print to sheet - Method Three (fails)
PrintArray AllocArray, ActiveWorkbook.Worksheets("Test").[D20]
'Print to sheet - Method Four (fails)
Range("D20:H989").Value = AllocArray
Sheets("Test").Range("D20").Resize(Sim, NumCols).Value = AllocArray
'Range(D20:G6002) = AllocArray
ConfigureArrayFolly = nSim
End Function
Your array has different lower bounds for each dimension.
You need to adjust for that by adding 1 to UBound(Data,2) :
Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 1), UBound(Data, 2) + 1) = Data
End Sub
A few pointers.
You are correct in using Range("D20").Resize(NumRows, NumCols).Value = AllocArray syntax.
AllocArray needs to be sized with ReDim AllocArray(1 to NumRows, 1 to NumCols)
It is correctly defined as Dim AllocArray() as Variant
The contents need to be either a Double or a String. You are mixing integers and doubles and I think Excel has a hard time with this (I may be wrong). Set A, B, C and D as Double
Dim nAsset As Integer: nAsset = 5 ' Count {A, B, ... , F} this is an integer, why was it defined as a Double?
Use 32-bit integers for counting. Dim Sim As Long: Sim = 1. The max. value for an Integer is 32767 so you may be overflowing. This may apply to other Integer types in your code too.
You already know how big the array is, you don't need the UBound() and LBound() calls.
I am working on a simulation with time to event data that requires some "memory" of when an event started in order to apply a Weibull function at that time. This requires a large matrix of values and I am interested a VBA solution to avoid large conditional and array calculations on the worksheet. The current solution is to create a matrix containing the data and to multiply that by a matrix containing the diagonal of interest and then sum the product to obtain the diagonal alone. The matrix is 1800x1800 on the sheet.
The code below appears to fault during the matrix multiplication and gives a "run time error '9' subscript of of range" message. I have found that 'array_2' -despite being specified as array_2(1,1) -shows as having a lower bound of 1 and upper bound of 2 (see block of code near the bottom for proof).
Any help as to why this is occurring would be much appreciated.
Todd
Sub sum_diag_float()
Dim a, b, q, i, j, x, y, lb, ub, sum As Double
Dim array_1(), array_2(), upright_arr(), array_product() As Variant
ReDim array_1(0 To 9, 0 To 9) As Variant
'create data
For a = 0 To 9
For b = 0 To 9
array_1(a, b) = WorksheetFunction.RandBetween(0, 10)
Next b
Next a
Range("A1:J10").Value = array_1
For q = 1 To 1
ReDim array_2(q, q) As Variant
array_2 = Range(Cells(1, 1), Cells(q + 1, q + 1)).Value
Range(Cells(1, 12), Cells(2, 13)).Value = array_2 'check array specification
'build binary matrix (0/1) from lower left to upper right
ReDim upright_arr(q, q) As Variant
For i = LBound(upright_arr, 1) To UBound(upright_arr, 1)
For j = LBound(upright_arr, 2) To UBound(upright_arr, 2)
If UBound(upright_arr, 2) = i + j Then
upright_arr(i, j) = 1
Else
upright_arr(i, j) = 0
End If
Next j
Next i
Range(Cells(4, 12), Cells(5, 13)).Value = upright_arr 'check matrix specification
'multiply data by the matrix
ReDim array_product(q, q) As Variant
For x = LBound(upright_arr, 1) To UBound(upright_arr, 1)
For y = LBound(upright_arr, 2) To UBound(upright_arr, 2)
array_product(x, y) = upright_arr(x, y) * array_2(x, y)
Next y
Next x
Range(Cells(7, 12), Cells(8, 13)).Value = array_product 'matrix multiplication result
sum = WorksheetFunction.sum(array_product)
Range("N9").Value = sum 'sum of matrix
lb = LBound(array_2, 1) 'proof of array dimension misspecification
Range("L11").Value = lb
ub = UBound(array_2, 1)
Range("L12").Value = ub
lb = LBound(array_2, 2)
Range("L14").Value = lb
ub = UBound(array_2, 2)
Range("L15").Value = ub
Next q
End Sub
The edited code following the first suggestions retains a runtime error '13' :
Sub sum_diag_float()
Dim a As Byte, b As Byte
Dim q As Double, i As Double, j As Double, x As Double, y As Double
Dim lb As Double, ub As Double, sum As Double
Dim array_1() As Double, array_2() As Double, upright_arr() As Double, array_product() As Double
ReDim array_1(0 To 9, 0 To 9) As Double
'create data
For a = 0 To 9
For b = 0 To 9
array_1(a, b) = WorksheetFunction.RandBetween(0, 10)
Next b
Next a
Range("A1:J10").Value = array_1
For q = 1 To 2
ReDim array_2(q, q) As Double
array_2 = Range(Cells(1, 1), Cells(q + 1, q + 1)).Value 'put cells in array
Range(Cells(1, 12), Cells(1 + q, 12 + q)).Value = array_2 'check array
'build binary matrix (0/1) from lower left to upper right
ReDim upright_arr(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) _
To UBound(array_2, 2)) As Double
For i = LBound(array_2, 1) To UBound(array_2, 1)
For j = LBound(array_2, 2) To UBound(array_2, 2)
If UBound(upright_arr, 2) = i + j - 1 Then
upright_arr(i, j) = 1
Else
upright_arr(i, j) = 0
End If
Next j
Next i
Range(Cells(5, 12), Cells(5 + q, 12 + q)).Value = upright_arr
'multiply data by the matrix
ReDim array_product(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) _
To UBound(array_2, 2)) As Double
For x = LBound(array_2, 1) To UBound(array_2, 1)
For y = LBound(array_2, 2) To UBound(array_2, 2)
array_product(x, y) = upright_arr(x, y) * array_2(x, y)
Next y
Next x
Range(Cells(9, 12), Cells(9 + q, 12 + q)).Value = array_product
sum = WorksheetFunction.sum(array_product)
Range("O12").Value = sum 'sum of matrix
Next q
End Sub
After correcting the above issues, the array multiplication step bugs to a run-time error '13' when used in the following code. I believe some variable is not being erased when called twice via the first sub. In reading various documentation, I do not beleive that I should have to provide any of the values to the call to 'sum_diag_float,' but this may be the problem. Ultimately, the sub 'call_txp_surv' will be called within other loops to create the desired simulation data, thus the need for the sub to work without bugs despite the number of times it is called. I am noting that a call_txp_surv with q = 1000 (s = 1000) does not generate an error, but at q = 1800 and s= 1800 the run-time '13' occurs. The code now contains the implemented cell counts which are different from previous code (much larger and thus no simulated data). Variables/counters have been renamed to avoid conflict with others. Any thoughts as to why this macro won't scale-up would be appreciated.
Sub call_txp_surv()
Dim lvad_clvad As Byte
For lvad_clvad = 1 To 2
If lvad_clvad = 1 Then
Worksheets("LVAD>TXP>death").Activate
Else
Worksheets("cLVAD>TXP>death").Activate
End If
Call sum_diag_float
Next lvad_clvad
End Sub
Sub sum_diag_float()
Application.Calculation = xlManual
Application.ScreenUpdating = False
Dim a As Byte, b As Byte
Dim m As Long, n As Long, q As Long, x As Long, y As Long
Dim array_1() As Double, array_2() As Variant, upright_arr() As Variant, array_product() As Variant
'cycle living calculation
For q = 1 To 1800
'put cells into array
array_2 = Range(Cells(3, 20), Cells(3 + q, 20 + q)).Value
'build binary matrix (0/1) from lower left to upper right
ReDim upright_arr(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) To UBound(array_2, 2)) As Variant
For m = LBound(array_2, 1) To UBound(array_2, 1)
For n = LBound(array_2, 2) To UBound(array_2, 2)
If UBound(upright_arr, 2) = m + n - 1 Then
upright_arr(m, n) = 1
Else
upright_arr(m, n) = 0
End If
Next n
Next m
'multiply data by the matrix
ReDim array_product(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) To UBound(array_2, 2)) As Variant
For x = LBound(array_2, 1) To UBound(array_2, 1)
For y = LBound(array_2, 2) To UBound(array_2, 2)
array_product(x, y) = upright_arr(x, y) * array_2(x, y)
Next y
Next x
'Range(Cells(9, 12), Cells(9 + q, 12 + q)).Value = array_product 'matrix multiplication result
sum = WorksheetFunction.sum(array_product)
Cells(4 + q, 1821).Value = sum 'sum of matrix
Next q
'cycle deaths
Dim c As Long, d As Long, s As Long, t As Long, u As Double, diff As Double
s = 1801
'subtract
For c = 1 To s
For d = 1 To (s - c)
diff = Cells(2 + d, 19 + c).Value - Cells(3 + d, 19 + c).Value
Cells(4 + d + (c - 1), 1823 + c).Value = diff
Next d
Next c
'sum
For t = 1 To (s - 1)
u = WorksheetFunction.sum(Range(Cells(4 + t, t + 1823), Cells(4 + t, t + 1824)))
Cells(4 + t, 1822).Value = u
Next t
Application.Calculation = xlAutomatic
Application.ScreenUpdating = True
End Sub
Your code line:
ReDim array_2(q, q) As Variant
dimensions array_2 as (0 to 1, 0 to 1)
But then your next line:
array_2 = Range(Cells(1, 1), Cells(q + 1, q + 1)).Value
actually redimensions array_2 to (1 to 2, 1 to 2) (two rows and two columns) (Dimension 1 for the rows; Dimension 2 for the columns).
This is what happens when you set an array equal to a range of cells.
If you want to retain the 0 to 1 dimensions, you would have to loop through the cells and assign them specifically.
See Arrays and Ranges in VBA by Chip Pearson
As an aside, your line:
Dim a, b, q, i, j, x, y, lb, ub, sum As Double
declares all of the variables, except for sum , as Variants. Given your code, I wonder if they should be declared as Long.
EDIT: With regard to your edited code, I don't understand why you did what you did.
In the link I posted above to Chip Pearson's website, it clearly shows that, when you are setting an array equal to a range, the array should be declared as type Variant; you have declared it as Double (twice!), therefore the Type Mismatch error. Again, if you MUST have the array as type Double, then you will have to loop through and assign the values one by one.
For reasons I wrote above, the ReDim array_2 line adds overhead without accomplishing anything and should be removed.
Why do you declare your counters as being of type Double instead of type Long?
The working and tested code for my question is below. Thanks to Ron for assisting.
Sub sum_diag_float()
Dim a As Byte, b As Byte
Dim q As Long, i As Long, j As Long, x As Long, y As Long
Dim array_1() As Double, array_2() As Variant, upright_arr() As Double, array_product() As Double
ReDim array_1(0 To 9, 0 To 9) As Double
'create data
For a = 0 To 9
For b = 0 To 9
array_1(a, b) = WorksheetFunction.RandBetween(0, 10)
Next b
Next a
Range("A1:J10").Value = array_1
For q = 1 To 2
array_2 = Range(Cells(1, 1), Cells(q + 1, q + 1)).Value 'put cells in array
Range(Cells(1, 12), Cells(1 + q, 12 + q)).Value = array_2 'check array specification
'build binary matrix (0/1) from lower left to upper right
ReDim upright_arr(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) To UBound(array_2, 2)) As Double
For i = LBound(array_2, 1) To UBound(array_2, 1)
For j = LBound(array_2, 2) To UBound(array_2, 2)
If UBound(upright_arr, 2) = i + j - 1 Then
upright_arr(i, j) = 1
Else
upright_arr(i, j) = 0
End If
Next j
Next i
Range(Cells(5, 12), Cells(5 + q, 12 + q)).Value = upright_arr 'check matrix specification
'multiply data by the matrix
ReDim array_product(LBound(array_2, 1) To UBound(array_2, 1), LBound(array_2, 2) To UBound(array_2, 2)) As Double
For x = LBound(array_2, 1) To UBound(array_2, 1)
For y = LBound(array_2, 2) To UBound(array_2, 2)
array_product(x, y) = upright_arr(x, y) * array_2(x, y)
Next y
Next x
Range(Cells(9, 12), Cells(9 + q, 12 + q)).Value = array_product 'matrix multiplication result
sum = WorksheetFunction.sum(array_product)
Range("O12").Value = sum 'sum of matrix
Next q
End Sub