How to round a list of decimals in Excel, so that the sum of the whole numbers equal a defined total? - excel

Having difficulty developing an excel function that will round a list of decimals, so that the sum of the whole numbers equals the original or a defined total.
Edit
I guess one way to do it is writing a function that first searches for the largest numbers and rounds them to the nearest whole number. That whole number is then counted and the function moves on to the next, until the total count equals the target total.
The problem that I am running into is that if there are too many numbers that are closer to 0, then the function will never equal the target total. So what the function then needs to do is identify the largest decimals, round them up, count, and then move on to the next until the sum of the count is equal to the target total. The left over data can then round to 0.
Sorry, I hope this is clearer....
I am dealing with larger data sets where the totals of the rounded whole numbers have much larger deviations to the original total.
It would preferable if this could be accomplished with an excel function, otherwise I am also open to doing in VBA.
Thanks!
Edit 3: Here is an example data set:

Please study my project VBA.Round.
Browse to paragraph Rounding a series of numbers to a sum
Code is way too much to post here, but an example workbook is included for download.
Example:
This function will read the range of distribution values, round the sum, and fill the two ranges with rounded values of 2 and zero decimals, totalling to the requested total (confirmed, as seen, by the formula):
' Practical example for using Excel ranges for RoundSum
'
' Source URL:
' https://stackoverflow.com/questions/63715043/how-to-round-a-list-of-decimals-in-excel-so-that-the-sum-of-the-whole-numbers-e
'
' 2020-09-14. Gustav Brock, Cactus Data ApS, CPH.
'
Public Sub RoundDistribution()
' Named ranges. These should pairwise match in row size.
Const VolumeName As String = "Volume"
Const PercentValuesName As String = "Percent_Distribution"
Const ValuesName As String = "Distribution"
Const RoundedValuesName As String = "Rounded_Distribution"
Dim Range As Excel.Range
Dim Values() As Currency
Dim Results() As Currency
Dim Total As Integer
Dim Index As Integer
' Read percent distribution values from the named range.
Set Range = ThisWorkbook.Names(PercentValuesName).RefersToRange
' Read original volume value.
Total = ThisWorkbook.Names(VolumeName).RefersToRange(1, 1)
' Dim input and output arrays.
ReDim Values(1 To Range.Rows.Count)
ReDim Results(1 To Range.Rows.Count)
' Fill input array.
For Index = LBound(Values) To UBound(Values)
Values(Index) = Range(Index, 1)
Next
' Round total and retrieve array with distribution values.
Results = RoundSum(Values, RoundMid(Total), 2)
' Fill named range with distribution values.
For Index = LBound(Results) To UBound(Results)
ThisWorkbook.Names(ValuesName).RefersToRange(Index, 1) = Results(Index)
Next
' Round total and retrieve array with rounded distribution values.
Results = RoundSum(Values, RoundMid(Total))
' Fill named range with rounded distribution values.
For Index = LBound(Results) To UBound(Results)
ThisWorkbook.Names(RoundedValuesName).RefersToRange(Index, 1) = Results(Index)
Next
End Sub
Output:
Note please, that the function is capable of rounding to any number of decimals, and to select one instance only of the values 0.34 to obtain a match.
The full demo (Excel workbook) and code is still for download on GitHub.

I'd agree with #pghcpa this is rather an arithmetic problem.
One idea for solution:
order the numbers descending on their fraction part
take the floor of each (i.e ignoring the fractions)
take the sum of those floors
compare that sum to the desired sum, take the difference
This way you'd probably have a positive difference, so you can start from the top and add 1 to each number downwards, all until the difference is gone.

Related

VBA function for Upside/Downside Capture

apologies for my ignorance, I'm brand new to VBA - I'm sure this is a simple problem...
I'm trying to write a fn. for up/down side capture in VBA. This is the problem:
There are two columns. One has fund performance in % (I've labelled 'returns'). The other has index performance in % (labelled 'index'). Both are same length / same number of rows. I need both to be variables to enter to the fn.
For UpsideCapture fn., for all nos. in the index column >0, I want to find the corresponding number in the returns column (which will be on the same row). Once I have those numbers I can compound them.
I've tried using Offset, assuming the returns column is 15 columns to the left of the index column but it doesn't return anything, and I don't really want to rely on it always being 15 columns apart (it arbitrary).
Many thanks!
One of my rubbish attempts is below. Any help is much appreciated. Its really just a case of finding the correct corresponding row based on the value in the index column...
Function UpsideCapture(returns As Range, index As Range) As Variant
Dim n As Integer
Dim m As Integer
Dim i As Integer
n = returns.Rows.Count
m = index.Rows.Count
For i = 1 To m
If index(i) > 0 Then
Upsidecap = ((1 + Upsidecap) * (1 + Offset(returns(i), -15))) - 1
End If
Next
UpsideCapture = Upsidecap
End Function
example

How can I lookup data from one column, when the value I'm referencing changes columns?

I want to do an INDEX-MATCH-like lookup between two documents, except my MATCH's index array doesn't stay in one column.
In Vague-English: I want a value from a known column that matches another value that may be found in any column.
Refer to the image below. Let's call everything to the left of the bold vertical line on column H doc1, and the right side will be doc2.
Doc2 has a column "Find This", which will be the INDEX's array. It is compared with "ID1" from doc1 (Note that the values in "Find This" will not be in the same order as column ID1, but it's easier to undertsand this way).
The "[Result]" column in doc2 will be the value from doc1's "Want This" column from the row that matches "FIND THIS" ...However, sometimes the value from "FIND THIS" is not in the "ID1" column, and is instead in "ID2","ID3", etc.
So, I'm trying to generate Col K from Col J. This would be like pressing Ctrl+F and searching for a value in Col J, then taking the value from Col D in that row and copying it to Col K.
I made identical values from a column the same color in the other doc to make it easier to visualize where they are coming from.
Note also that in column F of doc1, the same value from doc2's "Find This" can be found after some other text.
Also note that the column headers are only there as examples, the ID columns are not actually numbered.
I would simply hard-code the correct column to search from, but I'm not in control of doc1, and I'm worried that future versions may have new "ID" columns, with other's being removed.
I'd prefer this to be a solution in the form of a formula, but VB will do.
To generate column K based on given values of column J then you could use the following:
=INDEX(doc1!$D$2:$D$14,SUMPRODUCT((doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14))-1)
Copy that formula down as far as you need to go.
It basically only returns the row of the where a matching column J is found. we then find that row in the index of your D range to get your value in K.
Proof of concept:
UPDATE:
If you are working with non unique entities n column J. That is the value on its own can be found in multiple rows and columns. Consider using the following to return the Last row where there J value is found:
=INDEX(doc1!$D$2:$D$14,AGGREGATE(14,6,(doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14),1)-1)
UPDATE 2:
And to return the first row where what you are looking in column J is found use:
=INDEX($D$2:$D$14,AGGREGATE(15,6,1/($B$2:$H$14=J2)*ROW($B$2:$H$14)-1,1))
Thanks to Scott Craner for the hint on the minimum formula.
To determine if you have UNIQUE data from column J in your range B2:H14 you can enter this array formula. In order to enter an array formula you need to press CTRL+SHFT+ENTER at the same time and not just ENTER. You will know you have done it right when you see {} around your formula in the formula bar. You cannot at the {} manually.
=IF(MAX(COUNTIF($B$2:$H$14,J2:J14))>1,"DUPLICATES","UNIQUE")
UPDATE 3
AGGREGATE - A relatively new function to me but goes back to Excel 2010. Aggregate is 19 functions rolled into 1. It would be nice if they all worked the same way but they do not. I think it is functions numbered 14 and up that will perform the same way an array formula or a CSE formula if you prefer. The nice thing is you do not need to use CSE when entering or editing them. SUMPRODUCT is another example of a regular formula that performs array formula calculations.
The meat of this explanation I believe is what is happening inside of the AGGREGATE brackets. If you click on the link you will get an idea of what the first two arguments are. The first defines which function you are using, and the second tell AGGREGATE how to deal with Errors, hidden rows, and some other nested functions. That is the relatively easy part. What I believe you want to know is what is happening with this:
(doc1!$B$2:$H$14=J2)*ROW(doc1!$B$2:$H$14)
For illustrative purpose lets reduce this formula to something a little smaller in scale that does the same thing. I'll avoid starting in A1 as that can make life a little easier when counting since it the 1st row and first column. So by placing the example range outside of it you can see some more special considerations potentially.
What I want to know is what row each of the items list in Column C occurs in column B
| B | C
3 | DOG | PLATYPUS
4 | CAT | DOG
5 | PLATYPUS |
The full formula for our mini example would be:
{=($B$3:$B$5=C2)*ROW($B$3:$B$5)}
And we are going to look at the following as an array
=INDEX($B$3:$B$5,AGGREGATE(14,6,($B$3:$B$5=C2)*ROW($B$3:$B$5),1)-2)
So the first brackets is going to be a Boolean array as you noted. Every cell that is TRUE will TRUE until its forced into a math calculation. When that happens, True becomes 1 and False becomes 0.I that formula was entered as a CSE formula and place in D2, it would break down as follows:
FALSE X 3
FALSE X 4
TRUE X 5
The 3, 4 and 5 come from ROW() returning the value of the row number that it is dealing with at the time of the array math operation. Little trick, we could have had ROW(1:3). Just need to make sure the size of the array matches! This is not matrix math is just straight across multiplication. And since the Boolean is now experiencing a math operation we are now looking at:
0 X 3 = 0
0 X 4 = 0
1 X 5 = 5
So the array of {0,0,5} gets handed back to the aggregate for more processing. The important thing to note here is that it contains ONLY 0 and the individual row numbers where we had a match. So with the first aggregate formula, formula 14 was chosen which is the LARGE function. And we also told it to ignore errors, which in this particular case does not matter. So after providing the array to the aggregate function, there was a ,1) to finish off the aggregate function. The 1 tells the aggregate function that we want the 1st larges number when the array is sorted from smallest to largest. If that number was 2 it would be the 2nd largest number and so on. So the last row or the only row that something is found on is returned. So in our small example it would be 5.
But wait that 5 was buried inside another function called Index. and in our small example that INDEX formula would be:
=INDEX($B$3:$B$5,AGGREGATE(...)-2)
Well we know that the range is only 3 rows long, so asking for the 5th row, would have excel smacking you up side the head with an error because your index number is out of range. So in comes the header row correction of -1 in the original formula or -2 for the small example and what we really see for the small example is:
=INDEX($B$3:$B$5,5-2)
=INDEX($B$3:$B$5,3)
and here is a weird bit of info, That last one does not become PLATYPUS...it becomes the cell reference to =B5 which pulls PLATYPUS. But that little nuance is a story for another time.
Now in the comments Scott essentially told me to invert for the error to get the first row. And this is important step for the aggregate and it had me running in circles for awhile. So the full equation for the first row option in our mini example is
=INDEX($B$3:$B$5,AGGREGATE(15,6,1/($B$3:$B$5=C2)*ROW($B$3:$B$5),1)-2)
And what Scott Craner was actually suggesting which Skips one math step is:
=INDEX($B$3:$B$5,AGGREGATE(15,6,ROW($B$3:$B$5)/($B$3:$B$5=C2),1)-2)
However since I only realized this after writing this all up the explanation will continue with the first of these two equations
So the important thing to note here is the change from function 14 to function 15 which is SMALL. Think of it a finding the minimum. And this time that 6 plays a huge factor along with the 1/. So our array in the middle this time equates to:
1/FALSE X 3
1/FALSE X 4
1/TRUE X 5
Which then becomes:
1/0 X 3
1/0 X 4
1/1 X 5
Which then has excel slapping you up side the head again because you are trying to divide by 0:
#div/0 X 3
#div/0 X 4
1/1 X 5
But you were smart and you protected yourself from that slap upside the head when you told AGGREGATE to ignore error when you used 6 as the second argument/reference! Therefore what is above becomes:
{5}
Since we are performing a SMALL, and we passed ,1) as the closing part of the AGGREGATE, we have essentially said give me the minimum row number or the 1st smallest number of the resulting array when sorted in ascending order.
The rest plays out the same as it did for the LARGE AGGREGATE method. The pitfall I fell into originally is I did not use the 1/ to force an error. As a result, every time I tried getting the SMALL of the array I was getting 0 from all the false results.
SUMPRODUCT works in a very similar fashion, but only works when your result array in the middle only returns 1 non zero answer. The reason being is the last step of the SUMPRODUCT function is to all the individual elements of the resulting array. So if you only have 1 non zero, you get that non zero number. If you had two rows that matched for instance 12 and 31, then the SUMPRODUCT method would return 43 which is not any of the row numbers you wanted, where as aggregate large would have told you 31 and aggregate small would have told you 12.
Something like this maybe, starting in K2 and copied down:
=IFERROR(INDEX(D:D,MAX(IFERROR(MATCH(J2,B:B,0),-1),IFERROR(MATCH(J2,E:E,0),-1),IFERROR(MATCH(J2,G:G,0),-1),IFERROR(MATCH(J2,H:H,0),-1))),"")
If you want to keep the positions of the columns for the Match variable, consider creating generic range names for each column you want to check, like "Col1", "Col2", "Col3". Create a few more range names than you think you will need and reference them to =$B:$B, =$E:$E etc. Plug all range names into Match functions inside the Max() statement as above.
When columns are added or removed from the table, adjust the range name definitions to the columns you want to check.
For example, if you set up the formula with five Matches inside the Max(), and the table changes so you only want to check three columns, point three of the range names to the same column. The Max() will only return one result and one lookup, even if the same column is matched several times.
I came up with a vba solution if I understood correctly:
Sub DisplayActiveRange()
Dim sheetToSearch As Worksheet
Set sheetToSearch = Sheet2
Dim sheetToOutput As Worksheet
Set sheetToOutput = Sheet1
Dim search As Range
Dim output As Range
Dim searchCol As String
searchCol = "J"
Dim outputCol As String
outputCol = "K"
Dim valueCol As String
valueCol = "D"
Dim r As Range
Dim currentRow As Integer
currentRow = 1
Dim maxRow As Integer
maxRow = sheetToOutput.UsedRange.Rows.Count
For currentRow = 1 To maxRow
Set search = Range("J" & currentRow)
For Each r In sheetToSearch.UsedRange
If r.Value <> "" Then
If r.Value = search.Value Then
Set output = sheetToOutput.Range(outputCol & currentRow)
output.Value = sheetToSearch.Range(valueCol & currentRow).Value
currentRow = currentRow + 1
Set search = sheetToOutput.Range(searchCol & currentRow)
End If
End If
Next
Next currentRow
End Sub
There might be better ways of doing it, but this will give you what you want. We assume headers in both "source" and "destination" sheets. You will need to adapt the "Const" declarations according to how your sheets are named. Press Control & G in Excel to bring up the VBA window and copy and paste this code into "This Workbook" under the "VBA Project" group, then select "Run" from the menu:
Option Explicit
Private Const sourceSheet = "Source"
Private Const destSheet = "Destination"
Public Sub FindColumns()
Dim rowCount As Long
Dim foundValue As String
Sheets(destSheet).Select
rowCount = 1 'Assume a header row
Do While Range("J" & rowCount + 1).value <> ""
rowCount = rowCount + 1
foundValue = FncFindText(Range("J" & rowCount).value)
Sheets(destSheet).Select
Range("K" & rowCount).value = foundValue
Loop
End Sub
Private Function FncFindText(value As String) As String
Dim rowLoop As Long
Dim colLoop As Integer
Dim found As Boolean
Dim pos As Long
Sheets(sourceSheet).Select
rowLoop = 1
colLoop = 0
Do While Range(alphaCon(colLoop + 1) & rowLoop + 1).value <> "" And found = False
rowLoop = rowLoop + 1
Do While Range(alphaCon(colLoop + 1) & rowLoop).value <> "" And found = False
colLoop = colLoop + 1
pos = InStr(Range(alphaCon(colLoop) & rowLoop).value, value)
If pos > 0 Then
FncFindText = Mid(Range(alphaCon(colLoop) & rowLoop).value, pos, Len(value))
found = True
End If
Loop
colLoop = 0
Loop
End Function
Private Function alphaCon(aNumber As Integer) As String
Dim letterArray As String
Dim iterations As Integer
letterArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
If aNumber <= 26 Then
alphaCon = (Mid$(letterArray, aNumber, 1))
Else
If aNumber Mod 26 = 0 Then
iterations = Int(aNumber / 26)
alphaCon = (Mid$(letterArray, iterations - 1, 1)) & (Mid$(letterArray, 26, 1))
Else
'we deliberately round down using 'Int' as anything with decimal places is not a full iteration.
iterations = Int(aNumber / 26)
alphaCon = (Mid$(letterArray, iterations, 1)) & (Mid$(letterArray, (aNumber - (26 * iterations)), 1))
End If
End If
End Function

Excel VBA / math: count how many groups of n are in a range of numbers

I'm having a bit of a brain freeze, I thought this would be simple but it's not coming to me. I need an equation to calculate the number of groups of 10 in a variable range of variable data. Here's a small example, the real thing has hundreds of numbers with a variable min and max:
Number Rounded number to nearest 10
303.9 300
285 290 - min
443 440
446.8 450
461.1 460 - max
428.83 430
428.545 430
445.835 450
427.215 430
429.97 430
Unique groups of 10
300
290
440
450
460
430
Answer = 6 groups
I don't want to use pivot tables or group functions, I'd rather keep it solely in VBA. Also I don't want to work out the rounded values first and then count the distinct ones because I'd like to use the number to dimension an array, which I'll then populate in a loop. Is there a clever bit of math that can give answer?
I'm not aware of any clever math to do this. You could do this with worksheet formulas:
=SUM(--(FREQUENCY(ROUND(r,dec_places),ROUND(r,dec_places))>0))
where r is the data range and dec_places is the amount of rounding (since you're rounding to the nearest 10s, dec_places = -1).
You could implement this in VBA with something like:
Function CountUniqueRounded(r As Range, dec_places As Double) As Long
' Implement this worksheet formula
' =SUM(--(FREQUENCY(ROUND(r,dec_places),ROUND(r,dec_places))>0))
Dim rAddr As String
Dim rFormula As String
Dim c As Long
' Build up the formula string
rAddr = r.Parent.Name & "!" & r.Address
rFormula = "ROUND(" & rAddr & "," & CStr(dec_places) & ")"
rFormula = "=SUM(--(FREQUENCY(" & rFormula & "," & rFormula & ")>0))"
' Debug.Print rFormula
c = Application.Evaluate(rFormula)
CountUniqueRounded = c
End Function
Hope this helps
I have an Array Formula solution which does not require VBA. Array Formulas take single function which would normally apply only to a single cell, and perform that function over a range of cells, returning an Array of different results (1 for each cell). That Array of results is then collapsed into a single number to give your answer.
Assuming your data is in column A, and goes for less than 1000 rows, this will work:
=SUM(IFERROR(MATCH(10*(ROW(A1:A1000)),ROUND(A1:A1000,-1),0)*0+1,0))
Note that to confirm an Array Formula, you need to press CTRL + SHIFT + ENTER, isntead of just ENTER.
What it does is multiplies 10 by each row from A1 to A1000, and then checks to see if that result can be found when rounding each cell present in A1 to A1000. That is, it checks 10, 20, etc. up to 1000, and tries to find it in a range of your rounded values. Whenever it finds a value, it multiplies the row it was found on by 0, and adds 1. Whenever it doesn't find a value (ie: returns an error), it shows 0. The sum of all matches gives you the number of times each rounded number from 10 - 1000 is found.
Caveat - this will not work if you have numbers > 1000; if such numbers would occur you can easily change all instances of 1,000 to 10,000. Likewise if your data sometimes extends further than row 1000, you will need to increase all instances of 1000 to an appropriate number. If data would vary wildly and you can't estimate size / max limit, then there are ways to automate this, but as the formula is already somewhat complex, I left that as a manual process.

Multiply two 100-Digit Numbers inside Excel Using Matrix

I want to multiply two 100-Digit Numbers In Excel using matrix. The issue in Excel is that after 15-digit, it shows only 0. So, the output also need to be in a Matrix.
1st Number: "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
2nd Number: "2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
Output: "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222217777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777778"
This may be what OP was after. I thought I would try a naive multiplication method to see how long it would take to run. The answer is less than a second for two 100-digit numbers. You have to select the output range (i.e. A3:GR3 for a 200-digit result) and enter the formula containing the input ranges as an array formula using CtrlShiftEnter e.g.
=Multiply(A1:CV1,A2:CV2)
for two 100-digit numbers.
The method is basically just a simulation of school maths long multiplication, except that the intermediate rows are not stored but immediately added to the answer thus saving a lot of space.
The utility of it is obviously not that it is a replacement for the Karatsuba method, but it is a simple verifiable method which could be used for one-off calculations.
Currently limited to multiplication of rows containing more than one cell (so if you wanted to multiply by a single digit number, would have to enter it as e.g. 09).
Start of numbers
Middle of numbers
End of numbers
Function Multiply(rng1 As Variant, rng2 As Variant)
Dim arr() As Integer
Dim arrLength, r1Length, r2Length, carry, product, digit As Integer
Dim tot, totDigit, totCarry As Integer
Dim v1, v2 As Variant
v1 = rng1
v2 = rng2
r1Length = UBound(v1, 2)
r2Length = UBound(v2, 2)
arrLength = r1Length + r2Length
' Declare 1D array with enough space
ReDim arr(1 To arrLength)
' Loop over digits in first number starting from right
For i = r1Length To 1 Step -1
carry = 0
totCarry = 0
' Loop over digits in second number starting from right
For j = r2Length To 1 Step -1
' Calculate next digit in intermediate values (i.e. one row of long multiplication)
product = v1(1, i) * v2(1, j) + carry
digit = product Mod 10
carry = Int(product / 10)
' Calculate next digit in final values (i.e. totals line of long multiplication)
tot = arr(i + j) + digit + totCarry
arr(i + j) = tot Mod 10
totCarry = Int(tot / 10)
Next j
' Process final carry
arr(i) = carry + totCarry
Next i
' Return as an array
Multiply = arr
End Function
OK it might work like this:-
(1) You need to concatenate your numbers into a string because that's what you need as the input to your function. Native Excel won't do concatenation on arrays so you need a UDF like this one. So B2 contains
=concat(D1:G1)
(2) The output from the function is a string so you need to split it back into separate cells. You could use another UDF or a formula like this one copied across:-
=IF(COLUMNS($C3:C3)>LEN($B$3),"",VALUE(MID($B3,COLUMNS($C3:C3),1)))
So for the simple example it would look like this:-
But I might have got the wrong end of the stick completely.
Place your arrays into A1:A100 and B1:B100, then use three formulas:
1) In C2:C200 enter this as an array formula:
=MMULT(IFERROR(INDEX(A1:A100*TRANSPOSE(B1:B100),(ROW(INDIRECT("1:"&ROWS(A1:A100)*2-1))>0)+TRANSPOSE(ROW(INDIRECT("1:"&ROWS(A1:A100))))-1,MOD(ROW(INDIRECT("1:"&ROWS(A1:A100)*2-1))-TRANSPOSE(ROW(INDIRECT("1:"&ROWS(A1:A100)))),ROWS(A1:A100)*2-1)+1),0),SIGN(A1:A100+1))
2) In D1 enter =C1+INT(D2/10) and fill down to D200.
3) In E1 enter =MOD(D1,10) and fill down to D200.
E1:E200 will contain the answer.

VBA: Generating Data that mimics specific parameters (Avg, StdDev..etc)

I have modified a VBA array function given to me here: Excel Generate Normalized Data
That question will explain what I am after.
Download the excel I am using to completely understand:
http://www.mediafire.com/?smq5tl9poitdacc
I am using the following data (The left side are values I enter for the data to be based upon, the right side is the results of the generated data):
As you can see, the % Diff is very good for Avg Click, but Click/Time is off when there is a high Day StdDev (Day +/-). The difference when there is a low Day Stddev is close to 0.
I think this is because the var NoClickDaysPerClick_Running_Avg becomes inaccurate because the NoClickDays_Total (Which is used indirectly to determine the running avg) is "guessed" at the start, and needs to be reassessed each click because the high StdDev adds randomness and the original "guess" becomes more and more inaccurate.
I am not sure if this is the problem, or if it is how I can even solve it.
I am just looking for advice on the best way to do what it is I want. I am not sure why the stdDev are so far off either, but thats not a big deal. I'd rather have a more accurate Click/Time than anything else- reguardless what the Day StdDev is.
here is the function in my VBA:
Function ClickSpacer(Total_Days As Long, ClicksPerDay_Desired_Avg As Double, Clicks_Desired_Deviation As Double, Clicks_Min As Integer, Clicks_Max As Integer, TotalClicksOverTotalDays_Desired_Avg As Double, NoClickDays_Desired_Deviation As Double, NoClickDays_Min As Integer, NoClickDays_Max As Integer)
Dim Day_Array() As Integer
ReDim Day_Array(1 To Total_Days, 1 To 1)
Dim NumDaysToGetClicks As Double
Dim ClickOffset As Long
Dim Clicks_Total As Long
Dim Clicks_SoFar As Long
Dim Clicks_Remaining As Long
Dim NoClickDaysPerClick_Desired_Avg As Double
' Number of clicks that are needed to Achieved desired Avg of clicks over time
Clicks_Total = Round(Total_Days * TotalClicksOverTotalDays_Desired_Avg, 0)
' Number of days in which the user has to click atleast once to achieve desired Avg. clicks per day
NumDaysToGetClicks = Round(Clicks_Total / ClicksPerDay_Desired_Avg, 0)
' The number of non-click days in order fill out the total days
NoClickDays_Total = Round(Total_Days - NumDaysToGetClicks, 0)
' The guessimated average of non-click days per click to fill out total non-click days
' This is never used, just used for comparsion of the running Avg
NoClickDaysPerClick_Desired_Avg = NoClickDays_Total / NumDaysToGetClicks
'This variable is here to achieved closer results to the desired StdDev.
'a higher multiplyer will not limit the deviation but just give an average deviation
'For example, if the Average was 3 with a +/- 2, then with a StdDevMulti of 1
'ALL numbers will be 1 (3-2) through 5 (3+2) with an avg of 3 and stddev of 2, the numbers will NEVER exceed the StdDev.
'With a StdDevMulti of 2, the numbers will be 0 through 6, but should still have an
'Avg deviation of 2.
StdDevMulti = 1
NoClickDays_Desired_Deviation = NoClickDays_Desired_Deviation * StdDevMulti
Clicks_Desired_Deviation = Clicks_Desired_Deviation * StdDevMulti
'Set the obvious defaults
ClickedDaysSoFar = 0
Clicks_SoFar = 0
NoClickDays_SoFar = 0
'Give the ClickOffset a starting value
ClickOffset = NoClickDaysPerClick_Desired_Avg
Do
'used to find the "running" average of days not clicked
NoClickDays_Remaining = NoClickDays_Total - NoClickDays_SoFar
'used to find the "running" average of clicks per day
Clicks_Remaining = (Clicks_Total - Clicks_SoFar)
'used in both "running" averages mentioned above and also will
'mark the end of the while loop.
RemainingClickedDays = (NumDaysToGetClicks - ClickedDaysSoFar)
' Find what the average num. click should be based on the remaining
' and then apply the deviation. Only accept a click below its max
' above its min.
Do
' Generate a random number between -1 and 1
SignChanger = Rnd() - Rnd()
' Apply the randomized StdDev
Clicks_Deviation = Clicks_Desired_Deviation * SignChanger
'Figure out the "running" average
ClicksPerDay_Running_Avg = Clicks_Remaining / RemainingClickedDays
'Figure out a click value and round to the nearest whole number
Generated_Clicks = Round(ClicksPerDay_Running_Avg + Clicks_Deviation, 0)
' Make sure it meets the requirements, if not, try again
Loop While Generated_Clicks < Clicks_Min Or Generated_Clicks > Clicks_Max
' Set the click value to the spaced-out array index
Day_Array(ClickOffset, 1) = Generated_Clicks
'Find a random space based upon the "running" avg. and desired deviation
'Make sure it between the min and max required.
Do
' Generate a random number between -1 and 1
SignChanger = Rnd() - Rnd()
' Apply the randomized StdDev
NoClickDays_Deviation = NoClickDays_Desired_Deviation * SignChanger
'Figure out the "running" average
NoClickDaysPerClick_Running_Avg = NoClickDays_Remaining / RemainingClickedDays
'Figure out a space value and round to the nearest whole number
Generated_NoClickDays = Round(NoClickDaysPerClick_Running_Avg + NoClickDays_Deviation, 0)
' Make sure it meets the requirements, if not, try again
Loop While Generated_NoClickDays < NoClickDays_Min Or Generated_NoClickDays >= NoClickDays_Max
'Define the array index based upon the spacing previously generated.
' Make sure to "add" upon the already known index. Add 1 because you
'have to account for the index the click occupies
ClickOffset = ClickOffset + Generated_NoClickDays + 1
'These should be self-explaintory
ClickedDaysSoFar = ClickedDaysSoFar + 1
Clicks_SoFar = Clicks_SoFar + Generated_Clicks
NoClickDays_SoFar = NoClickDays_SoFar + Generated_NoClickDays
Loop While ClickOffset < Total_Days And RemainingClickedDays > 0
'Set the array equal to the clicks so that it returns the array as
'we want. Ideally this will be just replace Total_Days fields under
'the base, so not to require a array-function. Neither of these work:
'ClickSpacer = Range("P1:P" & UBound(Day_Array) + 1).Value
'Range("P1:P" & UBound(Day_Array) + 1) = Application.Transpose(Day_Array)
ClickSpacer = Day_Array
End Function
I think your assumption is correct. The "problem" with the code you have above is that it uses StdDev as the basis for generating random numbers, so the standard deviation will tend to be accurate and the mean will be less accurate.
If you want more accuracy with the mean and less with the standard deviation, then you'll have to "flip" how numbers are generated: they'll need to center around your desired mean and use the desired standard deviation as a guide, rather than the other way around.
I have an idea about how this can be done, but it will take more concentration than I can apply at work, so I'll have to come back and edit this later. I'll see what I can do.

Resources