Why does this function work in one sheet, but not another

GeeWhiz7

Board Regular
Joined
Nov 22, 2021
Messages
214
Office Version
  1. 365
Platform
  1. Windows
  2. MacOS
Little bit complicated code I found somewhere here on the forum, but cant seem to re-find.

That said, I want to use the very useful SMA function below (written by Bobby if I recall correctly?) and port it to work in the array calculations in vba, not in the sheet.
My first hiccup is that it works in a new worksheet, see second mini-sheet, but not in another sheet I have (first mini-sheet).

Goal: 1) Make work in any workbook and 2) only in vba array/memory by passing a array to it using a For Loop

Cell Formulas
RangeFormula
A2:A51A2=RANDBETWEEN(2,100)
B2:B51B2=SMA(A2:A51,10)
Press CTRL+SHIFT+ENTER to enter array formulas.


Book3
AB
1Input ArraySMA 5
288
37441
49258
57361.75
66662.6
72465.8
8351.6
91135.4
101323.4
113517.2
127627.6
136439.8
144145.8
156456
16249.4
177449
184445
193944.6
205542.8
218158.6
228260.2
236063.4
241057.6
256459.4
268961
276156.8
282650
295358.6
305857.4
314047.6
324644.6
335650.6
347354.6
359061
364762.4
377468
387070.8
391859.8
408057.8
415860
422049.2
437850.8
446059.2
452548.2
462541.6
479456.4
487656
498961.8
504966.6
512165.8
Sheet1
Cell Formulas
RangeFormula
B2:B51B2=SMA(A2:A51,5)
Press CTRL+SHIFT+ENTER to enter array formulas.


Code
VBA Code:
Function SMA(DataValues As Range, NumPeriods As Long, Optional ReturnElement As Long)
    Dim arrData, _
        arrSMA, _
        arrCumulative, _
        i As Long, _
        j As Long, _
        tempsum As Double
    
    NumPeriods = Abs(NumPeriods)
    
    arrData = Range_to_1D_Array(DataValues)
    arrData = ResizeArrayToAllNumeric(arrData, True)
    
    ReDim arrCumulative(1 To UBound(arrData)) As Double
    ReDim arrSMA(1 To UBound(arrData)) As Double
    
    For i = LBound(arrData) To UBound(arrData)
        tempsum = 0
        If i = 1 Then
            arrCumulative(i) = arrData(i)
        Else
            arrCumulative(i) = arrData(i) + arrCumulative(i - 1)
        End If
        If i < NumPeriods Then
            arrSMA(i) = arrCumulative(i) / i
        Else
            For j = i - NumPeriods + 1 To i
                tempsum = tempsum + arrData(j)
            Next j
            arrSMA(i) = tempsum / NumPeriods
        End If
    Next i
    
    If ReturnElement Then
        If ReturnElement > UBound(arrSMA) Then
            SMA = CVErr(xlErrNA)
        Else
            SMA = arrSMA(ReturnElement)
        End If
    Else
        If Application.Caller.Rows.Count > 1 Then
            SMA = Excel.Application.Transpose(arrSMA)
        Else
            SMA = arrSMA
        End If
    End If
    End Function


Function Range_to_1D_Array(ByVal Rng As Range)

    Dim ReturnArray, _
        arrTemp, _
        i As Long, _
        j As Long, _
        k As Long
    
    If Rng.Cells.Count = 1 Then
        ReDim ReturnArray(1 To 1)
        ReturnArray(1) = Rng
    Else
        If Rng.Columns.Count > 1 And Rng.Rows.Count > 1 Then
            arrTemp = Rng.Value
            ReDim ReturnArray(1 To UBound(arrTemp, 1) * UBound(arrTemp, 2))
            k = 0
            For i = 1 To UBound(arrTemp, 1)
                For j = 1 To UBound(arrTemp, 2)
                    k = k + 1
                    ReturnArray(k) = arrTemp(i, j)
                Next j
            Next i
            Erase arrTemp
        Else
            With Excel.Application
                ReturnArray = .Transpose(Rng)
                If Rng.Rows.Count = 1 Then ReturnArray = .Transpose(ReturnArray)
            End With
        End If
    End If
    
    Range_to_1D_Array = ReturnArray
    Erase ReturnArray
    End Function

Function ResizeArrayToAllNumeric(vArr, Optional ZeroMissingandText As Boolean = False)
    Dim vOut, _
        i As Long, _
        Counter As Long
        
    ReDim vOut(LBound(vArr) To UBound(vArr)) As Double
        For i = LBound(vArr) To UBound(vArr)
            If ZeroMissingandText Then
                Counter = Counter + 1
                If IsNumeric(vArr(i)) Then
                    vOut(i) = vArr(i)
                Else
                    vOut(i) = 0
                End If
            Else
                If IsNumeric(vArr(i)) Then
                    Counter = Counter + 1
                    vOut(Counter) = vArr(i)
                End If
            End If
        Next i
    ReDim Preserve vOut(1 To Counter) As Double
    ResizeArrayToAllNumeric = vOut
    Erase vOut
    End Function
Function ArrayDimensions(InputArray As Variant) As Long
    Dim n As Long

    If Not IsArray(InputArray) Or IsObject(InputArray) Then
        n = 0
        Exit Function
    Else
        n = 1
    End If

    On Error Resume Next
        Do
            n = n + 1
        Loop While (LBound(InputArray, n) <= UBound(InputArray, n))
        ArrayDimensions = n - 1
    Err.Clear
    On Error GoTo 0
    End Function
 

Excel Facts

How to fill five years of quarters?
Type 1Q-2023 in a cell. Grab the fill handle and drag down or right. After 4Q-2023, Excel will jump to 1Q-2024. Dash can be any character.
Using that function for the SMA is a very hard way of getting a SMA, I always use very simple formula where I can alter the length of the SMA just by changing a value in a cell.
If you put the value 5 into C1 of book 3, then put this equation into C6 and copy it down
Excel Formula:
=AVERAGE(OFFSET(A7,-C$1,0,C$1))
Note you get the identical values that you have in column B without using and array formula. If you change the value in C1 to 10 you get a ref error until you get the full 10 values. You can get round this with iferror logic if you need to
 
Upvote 0
Using that function for the SMA is a very hard way of getting a SMA, I always use very simple formula where I can alter the length of the SMA just by changing a value in a cell.
If you put the value 5 into C1 of book 3, then put this equation into C6 and copy it down
Excel Formula:
=AVERAGE(OFFSET(A7,-C$1,0,C$1))
Note you get the identical values that you have in column B without using and array formula. If you change the value in C1 to 10 you get a ref error until you get the full 10 values. You can get round this with iferror logic if you need to
Totally agree with you that it is complicated. I actually used to use this formula in a worksheet to do pretty much what you are talking about,
=AVERAGE(OFFSET(D4,0,0,-(MIN(ROW()-ROW($D$3)+1,$E$1)),1))
But, I cannot figure out how to do the same thing in vba array / memory only before writing out to sheet.

I would like to be able to pass a series of columns one at a time in vba to array, compute the moving average and then write out to sheet but I haven’t been successful yet so was looking for code to do it.
 
Upvote 0
try this code:
VBA Code:
Sub test()
Dim outarr()
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
inarr = Range(Cells(1, 1), Cells(lastrow, 1))
ReDim outarr(1 To lastrow, 1 To 1)
Call smacalc(inarr, outarr, 10)
 Range(Cells(1, 2), Cells(lastrow, 2)) = outarr

End Sub

Sub smacalc(inpur As Variant, outpur As Variant, tm As Variant)
For i = tm To UBound(inpur)
   sumv = 0
   For j = i - tm + 1 To i
    sumv = sumv + inpur(j, 1)
   Next j
   outpur(i, 1) = sumv / tm
Next i

End Sub
You can changethe sub smacalc to a function if that is whatyou need just by changing the name to function and assigning the output to it
 
Upvote 0
Solution
try this code:
VBA Code:
Sub test()
Dim outarr()
lastrow = Cells(Rows.Count, "A").End(xlUp).Row
inarr = Range(Cells(1, 1), Cells(lastrow, 1))
ReDim outarr(1 To lastrow, 1 To 1)
Call smacalc(inarr, outarr, 10)
 Range(Cells(1, 2), Cells(lastrow, 2)) = outarr

End Sub

Sub smacalc(inpur As Variant, outpur As Variant, tm As Variant)
For i = tm To UBound(inpur)
   sumv = 0
   For j = i - tm + 1 To i
    sumv = sumv + inpur(j, 1)
   Next j
   outpur(i, 1) = sumv / tm
Next i

End Sub
You can changethe sub smacalc to a function if that is whatyou need just by changing the name to function and assigning the output to it
you've rescued me a couple times now...not sure my coding ability will every repay, but I do appreciate your help.
This is quite compact and does just what I need. I guess the reason I was fixated on the longer solution was the ability to give an average when the SMA value was less than N by simply using whatever count was there until reaching N. I think I can figure out how to modify what you've given me here to do that and if so will repost it.

Thanks again and I am trying to do as you say and NEVER access the worksheet in a loop. Pretty challenging for me at this point.
 
Upvote 0
you've rescued me a couple times now...not sure my coding ability will every repay, but I do appreciate your help.
This is quite compact and does just what I need. I guess the reason I was fixated on the longer solution was the ability to give an average when the SMA value was less than N by simply using whatever count was there until reaching N. I think I can figure out how to modify what you've given me here to do that and if so will repost it.

Thanks again and I am trying to do as you say and NEVER access the worksheet in a loop. Pretty challenging for me at this point.
Okay, I'm stuck a little bit again. I'm trying to make this work for a variant array with multiple columns where I want to loop through each column and do the average with your code.
I can figure out the SLOW way by doing

VBA Code:
Sub SlowWayAvg()
   
Dim outarr()
LastRow = 25

For i = 1 To 2

inarr = Range(Cells(2, i), Cells(LastRow, i))
ReDim outarr(1 To LastRow, 1 To 1)
Call smacalc(inarr, outarr, 4) 'SMA changed to 4
 Range(Cells(28, i), Cells(51, i)) = outarr 'output in same column, but below input
Next i
End Sub

But I can't quite get a faster way.
Presumably this would be by reading the entire input data into inarr in one go and then sending only one column at a time from inarr to the smacalc procedure.
This is what I'm thinking at the moment...gives me a wonky result as I'm still shaky on looping through the array indicies.

VBA Code:
Sub FasterWay()
    Dim inarr()
    Dim outarr()
    Dim temparr()
    Dim x As Long

LastRow = 25

    inarr = Range(Cells(2, 1), Cells(LastRow, 2))
       
    ReDim outarr(1 To LastRow, 1 To 2)
    ReDim temparr(1 To LastRow, 1 To 1)
   
    For x = 1 To 2
        For i = 1 To 24
        temparr(i, 1) = inarr(i, x)
       
        Call smacalc(temparr, outarr, 4) 'changed SMA to 4 for example
        Range(Cells(28, i), Cells(51, i)) = outarr 'output in same column, but below input
       
        Next i
    Next x
       
End Sub

mini-sheet with two input data columns...trying to do column 1, then 2 in the code above and write out the results below the input columns.

Book3.xlsm
AB
1Input
288
3816
441123
55858
661.7561.75
762.6187.8
865.8131.6
951.6154.8
1035.470.8
1123.423.4
1217.217.2
1327.655.2
1439.879.6
1545.891.6
165656
1749.498.8
184949
1945135
2044.689.2
2142.885.6
2258.6117.2
2360.2180.6
2463.4126.8
2557.6172.8
Sheet1
 
Upvote 0
I am not at a pc at the moment so can’t write any code. However the way I would do multiple columns is to create another smacalc subroutine that cater for 2 dimensions and do the loop through the columns there. This saves the multiple read and writes to the worksheet
 
Upvote 0
I am not at a pc at the moment so can’t write any code. However the way I would do multiple columns is to create another smacalc subroutine that cater for 2 dimensions and do the loop through the columns there. This saves the multiple read and writes to the worksheet
No worries and thank you for the reply. That makes sense, let me see if I can figure it out.
 
Upvote 0
I am not at a pc at the moment so can’t write any code. However the way I would do multiple columns is to create another smacalc subroutine that cater for 2 dimensions and do the loop through the columns there. This saves the multiple read and writes to the worksheet
That works perfectly and fast! Thank you
VBA Code:
Sub test2d()
    
    Dim inarr, outaar As Variant
        
    inarr = Range(Cells(2, 1), Cells(25, 2))
    ReDim outarr(1 To 24, 1 To 2)
    
    Call smacalc2d(inarr, outarr, 2)
    Range(Cells(28, 1), Cells(51, 2)) = outarr
    
End Sub

Sub smacalc2d(inpur As Variant, outpur As Variant, tm As Variant) '#passed inarr, outarr and sma value N# in
    
Dim h, i, j As Long
Dim sumv As Double

    For h = 1 To 2
        For i = tm To UBound(inpur)
            sumv = 0
            
            For j = i - tm + 1 To i '
                sumv = sumv + inpur(j, h)
            Next j
            outpur(i, h) = sumv / tm
        Next i
    Next h
    
End Sub
 
Upvote 0
Well done, My only suggestion is to change the loop
for h =1 to 2
Into
For h=1 to unbound(input,2)
Which will then automatically deal with any number of columns
Sent from IPhone apologies for format
 
Upvote 0

Forum statistics

Threads
1,215,066
Messages
6,122,948
Members
449,095
Latest member
nmaske

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top