Well not really RANDBETWEEN()
. I'm trying to create a UDF to return the index of a number within an array, where the larger the number the more likely it is to be chosen.
I know how to assign probabilities to random numbers in a worksheet (i.e. using MATCH()
on the sum of the probabilities, there's plenty of stuff on SO explaining that), but I want a UDF because I'm passing a special input array into the function - not just a selected range.
My issue is, the weighting is off, numbers later on in the array are more likely to be returned than those earlier in the array and I can't see where in my code I've gone wrong. Here's the UDF so far:
Public Function PROBABLE(ParamArray inputArray() As Variant) As Long
'Takes a set of relative or absolute probabilities and ranks a random number within them
Application.Volatile (True)
Dim outputArray() As Variant
Dim scalar As Single
Dim rankNum As Single
Dim runningTot As Single
'''''
'Here I take inputArray() and convert to outputArray(),
'which is fed into the probability code below
'''''
scalar = 1 / WorksheetFunction.Sum(outputArray)
rankNum = Rnd()
runningTot = 0
For i = 0 To UBound(outputArray)
runningTot = runningTot + outputArray(i)
If runningTot * scalar >= rankNum Then
PROBABLE = i + 1
Exit Function
End If
Next i
End Function
The function should look at the relative sizes of the numbers in outputArray()
and pick randomly but weighted towards the larger numbers.
E.g. outputArray()
of {1,0,0,1}
should assign probabilities respectively of {50%,0%,0%,50%}
However when I tested that outputArray()
, for 1000 samples and 100 iterations, and graphed how frequently item 1 or item 4 in the array was returned, I got this result:
Approximately 20%:80% distribution. Plotting {1,1,1,1}
(all should have equal chance) gave a 10%:20%:30%:40% distribution
I know I'm missing something obvious but I can't tell what, any help?
UPDATE
Some people were asking for the complete code, here it is.
Public Function PROBABLE(ParamArray inputArray() As Variant) As Long
'Takes a set of relative or absolute probabilities and ranks a random number within them
Application.Volatile (True) 'added some dimensions up here
Dim outputArray() As Variant
Dim inElement As Variant
Dim subcell As Variant
Dim scalar As Single
Dim rankNum As Single
Dim runningTot As Single
'convert ranges to values
'creating a new array from the mixture of ranges and values in the input array
''''
'This is where I create outputArray() from inputArray()
''''
ReDim outputArray(0)
For Each inElement In inputArray
'Normal values get copied from the input UDF to an output array, ranges get split up then appended
If TypeName(inElement) = "Range" Or TypeName(inElement) = "Variant()" Then
For Each subcell In inElement
outputArray(UBound(outputArray)) = subcell
ReDim Preserve outputArray(UBound(outputArray) + 1)
Next subcell
'Stick the element on the end of an output array
Else
outputArray(UBound(outputArray)) = inElement
ReDim Preserve outputArray(UBound(outputArray) + 1)
End If
Next inElement
ReDim Preserve outputArray(UBound(outputArray) - 1)
''''
'End of new code, the rest is as before
''''
scalar = 1 / WorksheetFunction.Sum(outputArray)
rankNum = Rnd()
runningTot = 0
For i = 0 To UBound(outputArray)
runningTot = runningTot + outputArray(i)
If runningTot * scalar >= rankNum Then
PROBABLE = i + 1
Exit Function
End If
Next i
End Function
The start inputArray()
outputArray()
section is used to standardise different input methods. I.e. the user can enter a mixture of values, cell references/ranges and arrays, and the function can cope. e.g. {=PROBABLE(A1,5,B1:C15,IF(ISTEXT(D1:D3),LEN(D1:D3),0))}
(you get the picture) should work just as well as =PROBABLE(A1:A3)
. I cycle through the sub-elements of the inputArray() and put them in my outputArray(). I'm fairly certain there's nothing wrong with this portion of code.
Then to get my results, I copied the UDF into A1:A1000
, used a COUNTIF(A1:A1000,1)
or instead of count 1, I did count 2, 3, 4 etc for each of the possible UDF outputs and made a short macro to recalculate the sheet 100 times, each time copying the result of the countif into a table to graph. I can't say precisely how I did that because I left this all at work, but I'll update on Monday.