2016-03-10 32 views
3

我试图在vba中移动平均值代码,但以下各处返回相同的值。UDF无处不在返回相同的值

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

那么这里有什么问题? –

+0

我的代码似乎不工作。它为所有单元格返回相同的值。如果我删除application.volatile行,我必须手动按下F2并输入每个单元格才能得到正确的答案。我认为原因是该功能无法识别动态范围有变化,但不知道如何解决。 –

+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¹方法,但允许通过平均不explcitly指定父工作表默认为ActiveSheet property的范围内。

Excel Application object返回SUM function的结果和一些数学计算平均值。在工作表的AVERAGE function的一个命令中可能会返回相同的结果,但空白单元格的处理方式不同。

 trial1 = Application.Average(rng) 

¹挥发性功能重新计算每当在整个工作簿改变任何东西,不只是当东西,影响了他们的成果的变化。

+0

只是想知道为什么你在另一个人的问题上打开奖励,这很奇怪:-D –

+3

我做了同样的一个[这里](http://stackoverflow.com/questions/31472816/expanding-column- cells-for-each-column-cell/31594569#31594569),并且反应足以将另一个标记为“接受的答案”。这需要更多的曝光;它浪费了2天,这个话题是一个很好的答案。 (此外,我有很多代表,并没有太多的其他工作) – Jeeped

+0

我倾向于使用'Application.Volatile False'而不是'Application.Volatile'。我甚至不知道你使用的语法是可行的!同时,关于ActiveCell属性的观点是有效的,并且是对这个特殊问题的任何答案中最有用的部分:我几乎不需要添加。 –

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 

另一种解决方案在这里是使用与控制/移位进入数组公式/输入:

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 

,并使用这个数组功能:

  • 选择所有结果将被写入的范围(应该是数据集的大小)
  • 类型 “= MovAvg(A1:A100,2)”,其中A1:A100在数据的源和2中的子集
  • 按下Ctrl + Shift +的大小输入
+0

[Application.ThisCell](https://msdn.microsoft.com/en-us/library/office/ff834969.aspx)上的优秀着作。没有[Application.Caller](https://msdn.microsoft.com/en-us/library/office/ff193687.aspx)上的typedef,这可能是更好的解决方案。 – Jeeped

3

它是一种给我一个UDF计算移动平均数的奇怪。如果要在工作表中使用此UDF,我相信您会将其放在现有数据的旁边,并且如果要更改平均金额范围的大小,请手动更新它们?

假设你可以命名范围“MovingAverageSize”存储范围的大小来计算平均值,对现有数据的右平均量,考虑如下:

  • 范围C2名为从B3存储MovingAverageSize
  • 数据和向下
  • 移动平均结果被存储在数据的右列1
  • 如果数据小于MovingAverageSize,所述SUM功能相应地调整
  • 任何计算误差与结果在零
  • 每次MovingAverageSize变化值时,它触发一个Sub更新式(代码放置在工作表对象中而不是普通模块)
  • 或者,您可以更改代码以将MovingAverage放置到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 

样本数据和截图
SampleDataSampleData2
SampleData3SampleData4SampleData5 SampleData6

+0

非常透彻的分析;所介绍的方法非常有效。 – Jeeped

2

一个UDF只能访问一系列磨片它作为参数传递。因为(1)您的计算是确定性的且不易变的,(2)每当输入范围内的任何单元发生变化时,Excel将自动重新计算您的UDF,以及(3)由于'volatile' 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的既定目标)。

+0

你提出了一些很好的观点;特别是你如何强调这种UDF应该具有作为参数传递的范围。 – Jeeped

+0

您可能需要重新检查公式,结果对于大于2的子集不正确。您还应该删除输入范围,一个就足够了。 此外,尽管仅使用UDF提供的范围是一种很好的做法,但在某些情况下,例如此处的目的是计算加权移动平均值是必要的。 –

1

我相信

  1. 你trial1()函数中的一个或多个单元格,作为一个公式的一部分,或者本身

  2. 你想重新计算这些细胞每当用户改变工作表上的任何单元格

为此,您需要确定发生更改的单元格。此单元格不是由

A.ActiveCell - 因为这是计算开始时光标所在的单元格;它可以在任何地方,却没有考虑改变细胞

B. Application.ThisCell - 因为它返回该用户定义的函数被称为从细胞,而不是改变

细胞的细胞发生更改的地方传递给Worksheet的Change事件。该事件由Range类型的参数触发 - 范围发生了变化。你可以使用该参数来识别已更改的单元并将其传递给trial1(),可能通过全局变量(是的,我知道)。

我在工作表中试过这个,它的工作原理,让我知道你的结果。

+0

与新的见解和提出的解决方案良好的总结。 – Jeeped