我试图在Excel中创建自己的VBA函数,我希望它能够接受任何类型的输入并将输入视为向量,但我还没弄明白如何对连续(例如(A1:A10)或(A1:R1))和不连续(例如(A1; B5; G12))范围都这样做。我可以使这些函数同时工作,但不能同时适用于这两种类型。
我希望这样做的原因是我希望制作我自己的AVERAGE和STDEV.S版本,它可以处理单元格中的#N / A值。我知道我可以使用AVERAGEIF(范围;"<>#N / A")来平均值,但是AVERAGEIF不允许我使用不连续范围,就我而言知道,STDEV.S没有这样的选择。
我的数据来自我用各种化学方法测量的几个样品。我每天准备一个样品,然后在剩下的时间里测量它的东西。每个样本都被认为是#34;一个实验"每个实验都作为单独的工作表存储,其中我存储来自所有不同分析方法的数据并进行任何数据处理以使数据具有可比性(例如,根据摩尔浓度计算摩尔浓度,对温差进行调整等);我还存储了很多半无关的信息(例如最终结果不需要的注释,但仍需要保留)。简而言之,将所有运行存储在一个工作表中的数据太多了,因为它会使得它看起来太杂乱而且太杂乱而无法处理单个实验,特别是每当我添加一个新实验时数据;我当前的方法允许我简单地复制现有的工作表并将新数据粘贴到旧的方程式中。然后将处理过的数据链接到"概述"工作表,我列出了最有趣的数据结构,以便我可以轻松地比较不同测量值。链接是使用INDIRECT完成的,这样我就可以轻松地从新实验中添加新信息。由于数据来自实验,因此必然会缺少数据,我使用#N / A来覆盖这些漏洞,因为从一个工作表到另一个工作表的链接会产生一个" 0"如果数据丢失。我知道我可以用一个简单的短划线( - )或类似的替换#N / A,这将使内置的AVERAGE和STDEV.S工作,但我想使用相同的数据数组进行绘图,它显示为如果只有#N / A将从绘图中删除数据点,因为excel中的图形将短划线视为零值。
我的"概述"上的数据工作表安排为
Date pH Na+ conc K+ conc ...lots of other variables
Date 1 7.4 140 3 ...
Date 2 7.1 #N/A 4 ...
.... ... ... ... ...
Date N 7.3 143 3.5 ...
到目前为止,我设法支持连续范围的是以下代码示例,它计算包含#N / A值的单元格的标准偏差。当我选择整列(或列的连续部分)时,此代码可以正常工作,但如果我选择不连续的单元格范围则不行。
Function StdevNaN_S(xRange)
'Sample Standard deviation which excludes NaN values
xR = xRange 'I can, for some strange reason, not use UBound unless I re-store the data in xR...
NoE1 = UBound(xR, 1) 'Number of Elements along dimension 1 (columns)
NoE2 = UBound(xR, 2) 'Number of Elements along dimension 2 (rows)
NoE = NoE1 * NoE2 'Total Number of Elements (this way makes it work regardless of row or column range)
'Need to first calculate the NaN excluded average value - could use the AVERAGEIF to simplify, but that will break if the range is discontinuous
xSum = 0
xAmount = 0
For N = 1 To NoE
If IsNumeric(xRange(N)) Then
xSum = xSum + xRange(N)
xAmount = xAmount + 1 'counting how many cells that are used in the sum, used as the divisor in the average and the variance expression. Couldn't use the "CountIf" expression as it counted cells which contained text
Else
End If
Next N
xAvg = xSum / xAmount
'Uses the average in the variance calculation
xSum = 0
For N = 1 To NoE
If IsNumeric(xRange(N)) Then
xSum = xSum + (xRange(N) - xAvg) ^ 2 'Summing up (x - x_avg) ^ 2, which is the dividend of the variance expression
Else
End If
Next N
StdevNaN_S = (xSum / (xAmount - 1)) ^ 0.5 'the sample standard deviation is the square root of the corrected variance
End Function
我的问题是我希望对部分数据进行平均值和标准差计算。例如,日期1,5,19和34生产的样品是用特定的化学品库存生产的,而日期2:4,6:11和25:33来自第二批库存,其余来自第三批库存,所以我想知道具体股票是否有任何影响。
我在cpaerson.com上找到了一个例子,它展示了如何允许函数采用不连续范围并将其视为向量。他们的例子是
Function SumOf(ParamArray Nums() As Variant) As Variant
''''''''''''''''''''''''''''''''''
' Add up the numbers in Nums
''''''''''''''''''''''''''''''''''
Dim N As Long
Dim D As Double
For N = LBound(Nums) To UBound(Nums)
If IsNumeric(Nums(N)) = True Then
D = D + Nums(N)
Else
SumOf = CVErr(xlErrNum)
Exit Function
End If
Next N
SumOf = D
End Function
然而,此功能仅用于 用于不连续选择 - 如果我选择例如(A1; A5; A19; A34)或(A1; A2; A3; .. 。; A34)但如果我选择(A1:A34),它会给我一个错误。
我应该如何对我的功能进行编码,以便我可以选择我想要的任何单元格,然后将其内容用于计算?
答案 0 :(得分:1)
我终于设法弄清楚如何对数据进行排序,以便该函数可以处理连续和不连续范围,这要归功于对SJR和Ralph以及answer on this question问题的评论。
允许不连续范围的方法是使用ParamArray,然后检查输入的所有参数并检查它们包含的内容(这是我最初失败的地方,因为我不知道如何让Excel检查我输入的每个参数的内容到功能)。棘手的部分是,如果它当前检查的参数只包含一个单元格,那么它与需要处理的方式相比,如果它包含连续范围则应该如何处理。
例如,如果在仅包含一个单元格的参数上使用UBound,则检查ParamArray中的所有参数将失败。另外,为了在参数中连续范围内正确地寻址每个单元,然后需要循环通过InputParameters(i).Cells(j),而如果参数只是一个单元,那么就足以解决它作为InputParameters(i)。
我现在制作的代码按我的意愿工作;我可以选择任何范围的单元格并计算标准偏差和平均值,同时排除NaN值。我把它与内置的STDEV.S,STDEV.P和AVERAGE进行了比较,它产生了完全相同的结果*。我不知道为什么内置函数不会将NaN值排除为默认值,但我为任何想要使用它的人都包含了下面函数的代码。
Function NaNStdev_S(ParamArray xRange() As Variant) As Double
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'A function to calculate the sample standard deviation of any ranges of cells
'while excluding text, logicals, empty cells and cells containing #N/A.
'Can handle both continuous and discontinuous ranges.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim CellsUsed As Integer
Dim NumArg As Integer
Dim NumCell As Integer
Dim xAvg As Double
Dim xSum As Double
Dim xTemp As Variant
Dim xVect() As String
NumArg = UBound(xRange) 'Counts the number of input arguments (i.e., number of discontinuous regions)
For i = 0 To NumArg 'Goes through each discontinuous region
xTemp = xRange(i) 'Stores the current region in a temporary variable as several of the later operations cannot be performed on the full input array
If IsArray(xTemp) Then 'Checks if the current region is an array; if yes, then that array will be continuous
NumCell = UBound(xTemp, 1) * UBound(xTemp, 2) 'Checks how many cells are in the array
For j = 1 To NumCell 'Goes through all cells in the current region
If IsEmpty(xRange(i).Cells(j)) Then 'do nothing
ElseIf Application.IsLogical(xRange(i).Cells(j)) Then 'do nothing
ElseIf IsNumeric(xRange(i).Cells(j)) Then 'If the content of the cell is numeric, then use it
xSum = xSum + xRange(i).Cells(j) 'Add the current cell value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Counts how many of the cell values that are actually used
ReDim Preserve xVect(CellsUsed) 'Adjusts the size of xVect
xVect(CellsUsed) = xRange(i).Cells(j) 'Reformats all usable values into one single vector for later use
Else
End If
Next j
Else 'If the current region is not an array, then it's just a single value
If IsEmpty(xRange(i)) Then 'do nothing
ElseIf IsNumeric(xRange(i)) Then 'If the content of the current region is numeric, then use it
xSum = xSum + xRange(i) 'Add the current cell (region) value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Increase the counter of used values
ReDim Preserve xVect(CellsUsed) 'Adjusts the size of xVect
xVect(CellsUsed) = xRange(i) 'Adds the current value into the reformatted vector for later use
Else
End If
End If
Next i
xAvg = xSum / CellsUsed 'Average of all cells which contains numbers
xSum = 0 'resets the sum as it's no longer needed
For i = 1 To CellsUsed 'Goes through the reformatted vector and calculates the sum of (x - x_avg) ^ 2
xSum = xSum + (xVect(i) - xAvg) ^ 2 'This is the dividend of the variance equation
Next i
NaNStdev_S = (xSum / (CellsUsed - 1)) ^ 0.5 'the sample standard deviation is the square root of the corrected variance
End Function
Function NaNStdev_P(ParamArray xRange() As Variant) As Double
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'A function to calculate the population standard deviation of any ranges of cells
'while excluding text, logicals, empty cells and cells containing #N/A.
'Can handle both continuous and discontinuous ranges.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim CellsUsed As Integer
Dim NumArg As Integer
Dim NumCell As Integer
Dim xAvg As Double
Dim xSum As Double
Dim xTemp As Variant
Dim xVect() As String
NumArg = UBound(xRange) 'Counts the number of input arguments (i.e., number of discontinuous regions)
For i = 0 To NumArg 'Goes through each discontinuous region
xTemp = xRange(i) 'Stores the current region in a temporary variable as several of the later operations cannot be performed on the full input array
If IsArray(xTemp) Then 'Checks if the current region is an array; if yes, then that array will be continuous
NumCell = UBound(xTemp, 1) * UBound(xTemp, 2) 'Checks how many cells are in the array
For j = 1 To NumCell 'Goes through all cells in the current region
If IsEmpty(xRange(i).Cells(j)) Then 'do nothing
ElseIf Application.IsLogical(xRange(i).Cells(j)) Then 'do nothing
ElseIf IsNumeric(xRange(i).Cells(j)) Then 'If the content of the cell is numeric, then use it
xSum = xSum + xRange(i).Cells(j) 'Add the current cell value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Counts how many of the cell values that are actually used
ReDim Preserve xVect(CellsUsed) 'Adjusts the size of xVect
xVect(CellsUsed) = xRange(i).Cells(j) 'Reformats all usable values into one single vector for later use
Else
End If
Next j
Else 'If the current region is not an array, then it's just a single value
If IsEmpty(xRange(i)) Then 'do nothing
ElseIf IsNumeric(xRange(i)) Then 'If the content of the current region is numeric, then use it
xSum = xSum + xRange(i) 'Add the current cell (region) value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Increase the counter of used values
ReDim Preserve xVect(CellsUsed) 'Adjusts the size of xVect
xVect(CellsUsed) = xRange(i) 'Adds the current value into the reformatted vector for later use
Else
End If
End If
Next i
xAvg = xSum / CellsUsed 'Average of all cells which contains numbers
xSum = 0 'resets the sum as it's no longer needed
For i = 1 To CellsUsed 'Goes through the reformatted vector and calculates the sum of (x - x_avg) ^ 2
xSum = xSum + (xVect(i) - xAvg) ^ 2 'This is the dividend of the variance equation
Next i
NaNStdev_P = (xSum / CellsUsed) ^ 0.5 'the population standard deviation is the square root of the variance
End Function
Function NaNAverage(ParamArray xRange() As Variant) As Double
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'A function to calculate the average of any ranges of cells
'while excluding text, logicals, empty cells and cells containing #N/A.
'Can handle both continuous and discontinuous ranges.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim CellsUsed As Integer
Dim NumArg As Integer
Dim NumCell As Integer
Dim xSum As Double
Dim xTemp As Variant
NumArg = UBound(xRange) 'Counts the number of input arguments (i.e., number of discontinuous regions)
For i = 0 To NumArg 'Goes through each discontinuous region
xTemp = xRange(i) 'Stores the current region in a temporary variable as several of the later operations cannot be performed on the full input array
If IsArray(xTemp) Then 'Checks if the current region is an array; if yes, then that array will be continuous
NumCell = UBound(xTemp, 1) * UBound(xTemp, 2) 'Checks how many cells are in the array
For j = 1 To NumCell 'Goes through all cells in the current region
If IsEmpty(xRange(i).Cells(j)) Then 'do nothing
ElseIf Application.IsLogical(xRange(i).Cells(j)) Then 'do nothing
ElseIf IsNumeric(xRange(i).Cells(j)) Then 'If the content of the cell is numeric, then use it
xSum = xSum + xRange(i).Cells(j) 'Add the current cell value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Counts how many of the cell values that are actually used
Else
End If
Next j
Else 'If the current region is not an array, then it's just a single value
If IsEmpty(xRange(i)) Then 'do nothing
ElseIf IsNumeric(xRange(i)) Then 'If the content of the current region is numeric, then use it
xSum = xSum + xRange(i) 'Add the current cell (region) value to the sum of all cell values
CellsUsed = CellsUsed + 1 'Increase the counter of used values
Else
End If
End If
Next i
NaNAverage = xSum / CellsUsed 'Average of all cells which contains numbers
End Function
我提到代码产生与内置函数完全相同的值 - 但是,我确实注意到有一次它没有。 我将以下随机选择的值作为随机大小和位置范围放在Excel工作表中:
(00:01:00, -10, -33, 10, 33, 20, 66, 30, 40, 300, TRUE, {empty cell} , #N/A)
如果它们是随机分布的(即,我将它们放在下面的单元格中(P22:Q23; R22:R23; S22:T22; S21:V21; Q28)),那么它们与STDEV.S产生的值不同(我已经从STDEV.S函数中手动排除了#N / A的单元格),但它们仅在第13个十进制上有所不同(我的函数给出了93.5950714912684,而STDEV.S给出了93.5950714912683),这应该是一个足够小的错误。无关紧要。有趣的是,如果我将所有值都放在一行(即,我将所有值放在例如(M34:Y34)),那么我的函数和内置函数都给出完全相同的结果(即93.5950714912683) 。错误似乎源于包含1分钟的细胞;如果我将00:01:00更改为任何其他时间值(例如00:01:01或01:01:00),则无论值是放在行还是随机分布,两个函数都会产生完全相同的结果工作表上的区域。
我无法解释这种奇怪的行为,但到目前为止它似乎只产生了一个无关紧要的错误,所以我假设我的代码按预期工作。