How to get average of a Column Based on dates - excel

I'm still a newbie at this but I am trying to build a VB Macro to provide me with the Average Bet Value per session. Basically I have a list of Dates in Cell 'AD' and also a list of Bet Values in Cell 'AE'.
I am finding a difficulty in coding VB to calculate :
From Col 'AD' which contains the dates I need to loop through the dates and if there is a 10 minute difference between a date and an other, then both dates will be considered as a start and end date.
Following Step number 1. I need to get the Bet values which fall between the Start and End date and get the average of that session in Col 'AF'
Last but not least I need to do this process for all the dates in Col 'AD' - for every session identified we get the average bet value in col AF'
I have pasted the code on how I generated Col AD and Col AE and a snip of the data I am using.
I hope i explained myself well and would like to thank you all
Excel Sample Data
Public Sub GetDebitValue()
RowCounter = 1
Profile.Range("AE1").Value = "BET VALUE"
Profile.Range("AD1").Value = "BET TIME AND DATE"
Profile.Range("AF1").Value = "SESSION BET AVERAGE"
'Range Col 'H2' Contained the action types called Debit which are considered as bets - if value is debit take the bet amount from Col 'C'
If Profile.Range("H2").Value = "debit" Then
Profile.Range("C2" + CStr(RowCounter + 1)).Value = Profile.Range("C2" + CStr(RowCounter)).Value
'Set Date
Profile.Range("AD2").Value = Profile.Range("I2").Value
'Set Value
Profile.Range("AE2").Value = Profile.Range("C2").Value
End If
'Always start with debit
Do
Select Case Profile.Range("H" + CStr(RowCounter)).Value
Case "debit"
Profile.Range("AE" + CStr(RowCounter + 1)).Value = Profile.Range("C" + CStr(RowCounter)).Value
'Copy Date
Profile.Range("AD" + CStr(RowCounter + 1)).Value = Profile.Range("I" + CStr(RowCounter)).Value
Case Else
'Do Nothing
End Select
If Profile.Range("A" + CStr(RowCounter + 1)).Value = CStr("") Then
Exit Do
End If
'Call GamblingLength
RowCounter = RowCounter + 1
Loop Until RowCounter = 99999
End Sub

I knocked this up quickly, I didn't have time to recreate your exact conditions and figure out some bits of the code but I hope you can use this generic pattern to achieve the steps:
The data starts on row FOUR here (row 3 is the headers)
And the code to achieve this was:
Sub avgs()
Dim i As Long ' row counter
Dim c As Long ' average divisor counter
Dim cTot As Double ' cumulative value total
With Sheet2
c = 1 ' initiate count as 1
cTot = .Range("B4").Value2 ' initiate cumulative total as first row of data val
For i = 5 To .Cells(.Rows.Count, 1).End(xlUp).Row ' cycle through to end row
If .Range("A" & i).Value2 - .Range("A" & i - 1).Value2 < 0.00694 Then
' if less than ten minute gap on this row then add to both avg div and tot counter
c = c + 1
cTot = cTot + .Range("B" & i).Value2
Else
' else set the value in column c and reset the avg div and tot counter
.Range("C" & i - 1).Value2 = cTot / c
c = 1
cTot = .Range("B" & i).Value2
End If
Next i
End With
End Sub

Related

Adding the Last Few Features to my Scheduling Template Generator

Thanks to some wonderful people here on StackOverflow I have almost completed the code for my scheduling template generator! I just have three more things I would like to add that I am having some trouble with.
Current Breakdown: I have two sheets - "2 - Staff Listing" and "X - Template"
-The Values in in the staff listing sheet are a column of staff names beginning in Cell D9
-Then the Staff values are transferred to the template sheets starting in Cell B6
-There is a drop-down menu in the template sheet in Cell C2 where you can select which template you want to make just as an example it could read 5 Week or 5 Week with AM/PM
-Based on this Key I have it repeating each staff member's names X amount of times (5 in this case) and double that if AM/PM is selected so 10 times in this case
What I am hoping to still achieve:
-In the template sheet I would like column C to be the week number and column D to show AM/PM.
-So, for a 5 week AM/PM Template you would have Column C staring in cell C6 list Week 1, Week 1, Week 2, Week 2, up to Week 5. So, once for each week in the Key if it is AM/PM it should appear twice once for AM and once for PM.
-Then I would like it to list AM starting in cell D6 and then go PM and just keep repeating for the length of the names in column B. Additionally, if it is not an AM/PM template then I would like to hide column D.
Below is the code I currently have.
Sub populate_Template()
Worksheets("X - Template").Range("B6:K1000").ClearContents
Dim SourceData As Range
Set SourceData = Sheets("2 - Staff Listing").Range("D9")
Dim RepititionCell As Range
Set RepititionCell = Sheets("X - Template").Range("C2")
Dim Destination As Range
Set Destination = Sheets("X - Template").Range("B6")
Dim lr As Long
Dim arr As Variant
With SourceData.Parent
lr = .Cells(.Rows.count, SourceData.Column).End(xlUp).Row
arr = .Range(SourceData, .Cells(lr, SourceData.Column)).Value
End With
Dim repetition As Long
If Right(RepititionCell, 5) = "AM/PM" Then repetition = Split(RepititionCell.Value, " ")(0) * 2 Else repetition = Split(RepititionCell.Value, " ")(0)
Dim newarr() As String
ReDim newarr(1 To (lr - SourceData.Row + 1) * repetition, 1 To 1)
Dim count As Long
count = 0
Dim i As Long
For i = LBound(arr) To UBound(arr)
Dim y As Long
For y = 1 To repetition
count = count + 1
newarr(count, 1) = arr(i, 1)
Next y
Next i
Destination.Resize(UBound(newarr) - LBound(newarr) + 1).Value = newarr
End Sub
First you need to dim newarr to have three columns instead of one:
ReDim newarr(1 To (lr - SourceData.Row + 1) * repetition, 1 To 3)
Then your For y Next loop should fill in those other two columns. You can use the Mod operator to determine if you're on an odd or even row.
For y = 1 To repetition
Count = Count + 1
newarr(Count, 1) = arr(i, 1)
'if you're doubling up. You may want to put this in a Boolean variable
'to reduce code duplication
If Right(RepititionCell, 5) = "AM/PM" Then
'if it's an odd number
If y Mod 2 = 1 Then
newarr(Count, 2) = "Week " & (y + 1) / 2
newarr(Count, 3) = "AM"
Else 'even number
newarr(Count, 2) = "Week " & y / 2
newarr(Count, 3) = "PM"
End If
Else
'not doubling up, so y is the week number and nothing in column D
newarr(Count, 2) = "Week " & y
End If
Next y
Finally, you have to change your write line to account for the new columns
Destination.Resize(UBound(newarr, 1), UBound(newarr, 2)).Value = newarr
I got rid of the LBound part of the Resize because you're starting at 1 anyway. It's less robust if you happen to start your array at some other number. But between the likelihood that would happen and the improved readability, I think it's better.

Excel-VBA: fist and last row calculation wrong

this is my first VBA project and I'm stuck on one problem:
I need to calculate the monthly skewness using daily returns for multiple assets. First, I detected the months and the cells where each month ends and put them in a new worksheet. Based on that, I calculate the monthly skewness for each asset to get a table with the month in the rows and the assets in the columns.
The problem is that the skewness for the first and last row (so the first and last month) are incorrect, although the month is correctly identified by the first part of the code. In the second part of the code where I calculate the skewness I get this error: "Unable to get the Skew property of the WorksheetFunction class" for the line where I want to store the skew in an array but the skewness is calculated nonetheless. How can I solve the error message and get the correct skewness for the first and last month?
Any help would be very appreciated!
This is the code im working with:
Sub step1()
Dim v()
Dim idm()
Dim MonthCount As Single
'find the last row
lastrow = Worksheets("Data").Range("A2").End(xlDown).Row
'save the dates to array
v = Worksheets("Data").Range("A2:A" & lastrow).Value
'count the number of months
M = 1 'initiate month counter
For Row = 1 To lastrow - 2
'extract months from dates
month1 = month(v(Row, 1))
month2 = month(v(Row + 1, 1))
'increase the counter when the month changes
If month1 <> month2 Then
M = M + 1
End If
Next
MonthCount = M
'resize the month arrays
ReDim idm(1 To MonthCount, 1 To 2)
'detect change in month and save cumulative days as row indexes
M = 1 'initiate month counter
For Row = 1 To lastrow - 2
month1 = month(v(Row, 1))
month2 = month(v(Row + 1, 1))
If month1 <> month2 Then
'save the month
idm(M, 1) = month1 & "/" & Year(v(Row, 1))
'save the cumulative days
idm(M, 2) = Row + 1
M = M + 1
End If
On Error Resume Next
Next
'save the last month, cannot detect month change with empty row below
idm(M, 1) = month1 & "-" & Year(v(Row, 1))
idm(M, 2) = Row
'End With
'write the month array to excel
Worksheets("Months").Range("A2:B" & MonthCount + 1).Value = idm
End Sub
Sub step2()
Dim idMonth()
'save last month index to array
lastmonth = Worksheets("Months").Range("B2").End(xlDown).Row
idMonth() = Worksheets("Months").Range("B2:B" & lastmonth).Value
Dim z As Variant
Dim r1, r2 As Double
nc = Worksheets("Data").Range("A1").CurrentRegion.Columns.Count
'initiate counters
r1 = 2
r2 = 2
For c = 1 To nc
For M = 1 To UBound(idMonth) 'm is month index
'end of the month
r2 = idMonth(M, 1)
'set range for skewness
Set month_range = Worksheets("Data").Range(Cells(r1, c + 1).Address, Cells(r2, c + 1).Address)
'store skew in array
'error in the following line
z = Application.WorksheetFunction.skew(month_range)
'write skew to cells
Worksheets("Months").Cells(M + 1, c + 2).Value = z
'start of next month
r1 = r2 + 1
On Error Resume Next
Next
Next
End Sub

Excel macro to loop through range until value found, populate range below found cell with formula

I have a large table, sometimes with hundreds of rows.
This table is generated by another application that exports to excel.
One column has the heading "Adjusted Price".
I want all the rows in this column to contain a formula (presently they're all 0's).
I want to automate this process because the table gets regenerated all the time.
This column may not always be in the same place. So I need a macro that can find this column ("Adjusted Price") and then fill all the cells in that column with a formula (with the exception of the first row of course).
Can this be done?
Thanks in advance :)
Your homework is to figure out how to plug it in!
Option Explicit
Sub setAdjustedPrice()
Dim column As Integer
Dim adjustedPriceColumn As String
Dim found As Boolean
Dim rowCount As Long
column = 1
rowCount = 1
Do While Range(FncAlphaCon(column) & rowCount).Value <> "" And found = False
If (Range(FncAlphaCon(column) & rowCount).Value = "Adjusted Price") Then
found = True
adjustedPriceColumn = FncAlphaCon(column)
Else
column = column + 1
End If
Loop
If found = True Then
Do While rowCount < ActiveSheet.UsedRange.Rows.count
rowCount = rowCount + 1
Range(adjustedPriceColumn & rowCount) = "YOUR FORMULA"
Loop
Else
MsgBox ("'Adjusted Price' column not found, cannot continue.")
End If
End Sub
Private Function FncAlphaCon(aNumber As Integer) As String
' Fixed version 27/10/2011
Dim letterArray As String
Dim iterations As Integer
letterArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
If aNumber <= 26 Then
FncAlphaCon = (Mid$(letterArray, aNumber, 1))
Else
If aNumber Mod 26 = 0 Then
iterations = Int(aNumber / 26)
FncAlphaCon = (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)
FncAlphaCon = (Mid$(letterArray, iterations, 1)) & (Mid$(letterArray, (aNumber - (26 * iterations)), 1))
End If
End If
End Function

MS Excel: macro inquiry for array

lets say in column A:Row 2, I have a score of 45 and in column B, I have the amount of people that got that score. what i then want to do is on column D, output that score X amount of times. x=repitition.
in the exmaple 5 people got a score of 45 so in column D i want to insert 5 scores of 45. then I see in column A:Row2 3 people got a score of 46 then after the last 45, in column D I want to append 46 3 times.. and so on..
Could someone show me how to do this?
Here you go:
Sub test_scores_repitition()
'run with test scores sheet active
r = 1
dest_r = 1
Do While Not IsEmpty(Range("a" & r))
If IsEmpty(Range("b" & r)) Then Range("b" & r).Value = 0 'if there's no quantity listed for a score, it assumes zero
For i = 1 To Range("b" & r).Value
Range("d" & dest_r).Value = Range("a" & r).Value
dest_r = dest_r + 1
Next i
r = r + 1
Loop
End Sub
Macro answer:
Sub WriteIt()
Dim lrow As Long
Dim WriteRow As Long
Dim EachCount As Long
Dim ReadRow As Long
' find last in list of numbers
lrow = Range("A1").End(xlDown).Row
'start at 2 because of headers
WriteRow = 2
ReadRow = 2
While ReadRow <= lrow
For EachCount = 1 To Cells(ReadRow, 2)
'repeat the number of times in column B
Cells(WriteRow, 4) = Cells(ReadRow, 1)
'the number in column A
WriteRow = WriteRow + 1
Next
ReadRow = ReadRow + 1
'and move to the next row
Wend
'finish when we've written them all
End Sub
it is possible with a formula, just not really recommended as it looks auful, and would be difficult to explain. It uses a Microsoft formula to count the number of unique items in the data above, and once it counts the number it is supposed to write of the number above, it moves to the next number. The formula does not know where to stop, and will put 0 when it runs out of data.
in D2, put =A2
In D3, and copied down, put
=IF(COUNTIF($D$2:D2,OFFSET($A$1,SUM(IF(FREQUENCY($D$2:D2,$D$2:D2)>0,1)),0))<OFFSET($B$1,SUM(IF(FREQUENCY($D$2:D2,$D$2:D2)>0,1)),0),OFFSET($A$1,SUM(IF(FREQUENCY($D$2:D2,$D$2:D2)>0,1)),0),OFFSET($A$1,SUM(IF(FREQUENCY($D$2:D2,$D$2:D2)>0,1))+1,0))

Reduce for loop execution time that returns a count

What I am trying to achieve
I have two sheets: 'dashboard' and 'temp calc'.
Dashboard has all employee details and range "N1" "N2" contain dates.
Now a macro populates employee data and generates a daywise calendar as shown in the following image
'temp calc' has their project details with start date end date.(the date that do not fall between n1 and n2 dates from dashboard sheet are deleted here).
So now referencing their empid from dashboard sheet, and using the first day populated in dashboard sheet i loop through the emp id in temp calc sheet and return a count for the number of projects a employee is currently working on for the particular day. as shown in the following image.
how I achieve this:
the code.....
Option Explicit
Sub Count()
' x= no of columns(dashboard calender)
' y= no of rows(dashboard emp id)
' z= no of rows(temp calc sheet emp id)
Application.ScreenUpdating = False
'Clear calender data
Range("Q4").Select
Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
Selection.ClearContents
Dim i, j, k, l, d, x, y, z, Empid As Long
Dim currentdate, startdate, enddate As Date
x = (Range("n2") - Range("n1")) + 1
y = Application.WorksheetFunction.counta(Range("A:A")) - 1
z = Application.WorksheetFunction.counta(Worksheets("Temp Calc").Range("A:A")) - 1
For i = 1 To y Step 1 'To loop through the emp_id in dashboard.
For j = 1 To x Step 1 'To loop through the calender in dashboard daywise.
d = 0
For k = 1 To z Step 1 'To loop through the emp_id i temp calc sheet.
Empid = ActiveSheet.Cells(i + 3, 1).Value
currentdate = Cells(3, 16 + j).Value
startdate = Worksheets("Temp calc").Cells(k + 1, 3).Value
enddate = Worksheets("Temp calc").Cells(k + 1, 4).Value
If (Worksheets("Temp calc").Cells(k + 1, 1).Value) = Empid Then
If (currentdate >= startdate) And (currentdate <= enddate) Then 'To check whether the first column date falls within the project start and end date
d = d + 1
End If
End If
Next
Worksheets("Dashboard").Cells(i + 3, j + 16) = d
Next
Next
Range("q4").Select
Application.ScreenUpdating = True
End Sub
My problem: The code does the job,but I have two problems.
It is too slow
Sometimes the workbook will say not responding and won't do the work.I've checked it does not work in the background. I left the program running overnight and it went into not responding.
Possible solutions:
using two arrays: one array to store empid in dashboard,second array to store calendar generated in dashboard. and then compare it with data from temp calc sheet and return a count into array number 2 and write it back
the problem is I've just started reading about arrays and I am still learning
I am open to possible alternatives:
cheers,
mathew
There are several built in functions that will do this quite efficiently. There are just a couple I will list here:
Use Autofilter to select only a particular set of data (e.g. autofilter on an employee, or autofilter on date range etc) - then you can step through just the elements belonging to that employee
sort on employee - then you only step through valid employee IDs, and when you get to the next employee you start the next loop
use a pivot table to do the entire thing for you: create the table
with employee ID down the left side, date on top, and use "count" as the function being evaluated. You can use the Filter option in the pivot table to get this down to the date range you want - or you can autofilter the data in the employee table to the range you want before computing the pivot table
Any of these should make your code plenty fast - my personal preference is option 3... And if you don't like the layout of option 3, and you can't make it "just so", then create the pivot table in a hidden sheet and copy the data from there to the sheet you want.
As an aside - doing things like COUNTA("A:A" is likely quite slow since this means looking at all 1.5 million cells in the column. If the rows are contiguous you should be able to do something like:
COUNTA(RANGE("A1", [A1].End(xlDown)))
or (if not contiguous)
numRows = ActiveSheet.Cells.SpecialCells(xlLastCell).Row
COUNTA(RANGE("A1", [A1].OFFSET(numRows,0)))
This works for me..... Hope it will be helpful for someone else with the same problem..
A big thank you to everyone who helped me with this and also for everybodys suggestions and answers.... :)
Sub assginment_count()
Dim a, i As Long, ii As Long, dic As Object, w, e, s
Dim StartDate As Date, EndDate As Date
Set dic = CreateObject("Scripting.Dictionary")
' use dic as a "mother dictionary" object to store unique "Employee" info.
dic.CompareMode = 1
' set compare mode to case-insensitive.
a = Sheets("temp calc").Cells(1).CurrentRegion.Value
' store whole data in "Temp Calc" to variable "a" to speed up the process.
For i = 2 To UBound(a, 1)
' commence loop from row 2.
If Not dic.exists(a(i, 1)) Then
Set dic(a(i, 1)) = CreateObject("Scripting.Dictionary")
' set child dictionary to each unique "Emp Id"
End If
If Not dic(a(i, 1)).exists(a(i, 3)) Then
Set dic(a(i, 1))(a(i, 3)) = _
CreateObject("Scripting.Dictionary")
' set child child dictionary to each unique "Startdt" to unique "Emp Id"
End If
dic(a(i, 1))(a(i, 3))(a(i, 4)) = dic(a(i, 1))(a(i, 3))(a(i, 4)) + 1
' add 1(count) to a unique set of "Emp Id", "Startdt" and "Finishdt", so that it enables to count as
' different match even if multiple same unique set of "Emp Id", "Startdt" and "Finishdt" appears.
Next
With Sheets("dashboard")
StartDate = .[N1].Value: EndDate = .[N2].Value
With .Range("a3").CurrentRegion.Resize(, .Rows(3).Find("*", , , , xlByRows, xlPrevious).Column)
' finding the data range, cos you have blank column within the data range.
.Columns("q").Resize(.Rows.count - 3, .Columns.count - 16).Offset(3).Value = 0
' initialize the values in result range set to "0".
a = .Value
' store whole data range to an array "a"
For i = 4 To UBound(a, 1)
' commence loop from row 4.
If dic.exists(a(i, 1)) Then
' when mother dictionary finds "Employee"
For Each e In dic(a(i, 1))
' loop each "Startdt"
For Each s In dic(a(i, 1))(e)
' loop corresponding "Finishdt"
If (e <= EndDate) * (s >= StartDate) Then
' when "Startdt" <= EndDate and "Finishdt" >= StartDate
For ii = 17 To UBound(a, 2)
' commence loop from col.Q
If (a(3, ii) >= e) * (s >= a(3, ii)) Then
' when date in the list matches to date between "Startdt" and "Finishdt"
a(i, ii) = a(i, ii) + dic(a(i, 1))(e)(s)
' add its count to corresponding place in array "a"
End If
Next
End If
Next
Next
End If
Next
.Value = a
' dump whole data to a range.
End With
End With
End Sub

Resources