我正在尝试用vba中的移动平均值进行编码,但以下各处返回相同的值。
Function trial1(a As Integer) As Variant
Application.Volatile
Dim rng As Range
Set rng = Range(Cells(ActiveCell.Row, 2), Cells(ActiveCell.Row - a + 1, 2))
trial1 = (Application.Sum(rng)) * (1 / a)
End Function
答案 0 :(得分:6)
ActiveCell property不属于UDF,因为会更改。有时,它甚至不在同一个工作表上。
如果需要在工作表中引用自定义UDF函数所在的单元格,请使用Application.Caller方法。 Range.Parent property可用于在With ... End With statement中明确标识工作表(并避免进一步混淆)。
Function trial1(a As Integer) As Variant
Application.Volatile
Dim rng As Range
with Application.Caller.Parent
Set rng = .Range(.Cells(Application.Caller.Row, 2), _
.Cells(Application.Caller.Row - a + 1, 2))
trial1 = (Application.Sum(rng)) * (1 / a)
end with
End Function
您已应用Application.Volatile¹方法但允许通过不明确指定父工作表将范围平均为默认值ActiveSheet property。
使用Excel Application object返回SUM function的结果和一些数学计算平均值。使用工作表的AVERAGE function可以在一个命令中返回相同的内容,但空白单元格的处理方式会不同。
trial1 = Application.Average(rng)
¹当整个工作簿中的任何内容发生变化时,挥发性函数会重新计算,而不仅仅是在影响其结果的内容发生变化时。
答案 1 :(得分:3)
我相信Application.ActiveCell不是你应该在这里使用的。 Application.ThisCell更适合假设" a"是子集的大小,数据集是右侧的1列。 而且,我只想使用" WorksheetFunction.Average"而不是" Application.Sum"我会添加" Application.Volatile"因此,只要工作表上发生更新,就会重新计算平均值。
因此,您的问题的一个解决方案是:
Public Function Trial1(a As Integer) As Variant
Application.Volatile
Trial1 = WorksheetFunction.Average(Application.ThisCell(1, 2).Resize(a))
End Function
此处的另一个解决方案是使用通过Control / Shift / Enter输入的数组公式:
Public Function MovAvg(dataset As Range, subsetSize As Integer)
Dim result(), subset As Range, i As Long
ReDim result(1 To dataset.Rows.count, 1 To 1)
Set subset = dataset.Resize(subsetSize)
For i = 1 To dataset.Rows.count
result(i, 1) = WorksheetFunction.Average(subset.offset(i - 1))
Next
MovAvg = result
End Function
并使用此数组函数:
答案 2 :(得分:3)
对于UDF来说,给出一个数字计算移动平均值对我来说有点奇怪。如果要在工作表中使用此UDF,我相信您会将其放在现有数据旁边,如果要更改平均数量范围的大小,可以手动更新它们吗?
假设您可以命名范围“ MovingAverageSize ”来存储范围的大小以计算平均值,以及现有数据右侧的平均数量,请考虑以下内容:
MovingAverageSize
工作表对象中的代码:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count = 1 Then
If Target.Address = ThisWorkbook.Names("MovingAverageSize").RefersToRange.Address Then UpdateMovingAverage Target
End If
End Sub
Private Sub UpdateMovingAverage(ByRef Target As Range)
Dim oRngData As Range, oRng As Range, lSize As Long, lStartRow As Long
Debug.Print "UpdateMovingAverage(" & Target.Address & ")"
If IsNumeric(Target) Then
lSize = CLng(Target.Value)
If lSize <= 0 Then
MsgBox "Moving Average Window Size cannot be zero or less!", vbExclamation + vbOKOnly
Else
' Top Data range is "B3"
Set oRngData = Target.Parent.Cells(3, "B") ' <-- Change to match your top data cell
lStartRow = oRngData.Row
' Set the Range to last row on the same column
Set oRngData = Range(oRngData, Cells(Rows.Count, oRngData.Column).End(xlUp))
Application.EnableEvents = False
For Each oRng In oRngData
If (oRng.Row - lSize) < lStartRow Then
oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & lStartRow - oRng.Row & "]C[-1]:RC[-1])/MovingAverageSize,0)"
Else
oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & 1 - lSize & "]C[-1]:RC[-1])/MovingAverageSize,0)"
End If
Next
Application.EnableEvents = True
Set oRngData = Nothing
End If
End If
End Sub
答案 3 :(得分:2)
UDF只应在作为参数传递时访问范围。 此外,您应该消除Application.Volatile,因为(1)您的计算是确定性的而且不易变,(2)只要输入范围中的任何单元格发生变化,Excel将自动重新计算您的UDF,以及(3)因为&#39;挥发性&#39; UDF中的属性可以使模型非常慢,因此在不必要时应该避免使用。 因此,对于移动平均线,正确的公式是:
Public Function SpecialMovingAverage(Rng as Excel.Range) As Double
Dim denominator as Integer
denominator = Rng.Cells.Count
if Denominator = 0 then SpecialMovingAverage = 0: exit function
' write your special moving average logic below
SpecialMovingAverage = WorksheetFunction.Average(Rng)
End Function
注意:我在两条评论之后改变了答案,因为我最初没有看到问题是在移动平均线之后(可能是我的答案后问题已经改变,或者我最初错过了UDF的陈述目标)。
答案 4 :(得分:1)
我相信
您的trial1()函数位于一个或多个单元格中,作为公式的一部分或单独使用
您希望每当用户更改工作表上的任何单元格时重新计算这些单元格
为此,您需要确定发生更改的单元格。
没有给出这个单元格一个。 ActiveCell - 因为这是计算开始时光标所在的单元格;它可能在任何地方,但不在改变的细胞上
B中。 Application.ThisCell - 因为它返回调用用户定义函数的单元格,而不是更改的单元格
发生更改的单元格将传递给Worksheet的Change事件。该事件由Range类型的参数触发 - 更改的范围。您可以使用该参数来识别已更改的单元格,并将其传递给trial1(),可能通过全局变量(是的,我知道)。
我在工作表中尝试了这个并且它有效,所以让我知道你的结果。