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
Related
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
I have existing script that does a major chunk of what I need. The script (from here: https://www.extendoffice.com/documents/excel/4054-excel-duplicate-rows-based-on-cell-value.html) basically inserts and then copies rows of data X number of times, where X is one of the fields in the table. It works well and the referenced page shows examples of the start and end points.
But when I run the script in Excel I go from ~2,000 lines in my table to ~40,000 lines. I need to modify all the duplicated rows (incremental dates) and so I am now attemting to also include new data into the table while the script runs that will allow me to change data in the duplicated rows... for example I can use the duplicate number 1, 2, 3, 4 and some simple formulas to change dates relative to a start point.
I expect that I will need some additional code inserted into the routine that will add data into a nominated column and do the auto incrementing from 1.
Having zero actual VBA skillz, ive no idea how to tackle the second part of my problem with the code I already have. Any help would be totally awesome !!
Sub CopyData()
'Updateby Extendoffice 20160922
Dim xRow As Long
Dim VInSertNum As Variant
xRow = 1
Application.ScreenUpdating = False
Do While (Cells(xRow, "A") <> "")
VInSertNum = Cells(xRow, "D")
If ((VInSertNum > 1) And IsNumeric(VInSertNum)) Then
Range(Cells(xRow, "A"), Cells(xRow, "D")).Copy
Range(Cells(xRow + 1, "A"), Cells(xRow + VInSertNum - 1, "D")).Select
Selection.Insert Shift:=xlDown
xRow = xRow + VInSertNum - 1
End If
xRow = xRow + 1
Loop
Application.ScreenUpdating = False
End Sub
Try this code below, I used the same sample data on the link you provided. However on this code I created 2 worksheets, one for the raw data to be processed and one for the duplicate output including the increment of dates and duplicate number.
Sub duplicateData()
Dim rSH As Worksheet
Set rSH = ThisWorkbook.Sheets("RAW") 'Your raw data
Dim oSH As Worksheet
Set oSH = ThisWorkbook.Sheets("OUTPUT") 'Output data on another sheet
x = 2
For a = 2 To rSH.Range("A" & Rows.Count).End(xlUp).Row
For b = 1 To rSH.Cells(a, 4).Value '4 is the column of duplicate times
If b = 1 Then
For c = 1 To 4 'Number of your column
oSH.Cells(x, c).Value = rSH.Cells(a, c).Value
Next c
oSH.Cells(x, 5) = 1 'First instance, 5 is the column number of duplicate counter
Else
For c = 1 To 4 'Number of your column
oSH.Cells(x, c).Value = rSH.Cells(a, c).Value
Next c
oSH.Cells(x, 3).Value = CDate(oSH.Cells(x - 1, 3).Value) + 1 '3 is the column number of date to increment
oSH.Cells(x, 5).Value = CInt(oSH.Cells(x - 1, 5).Value) + 1 '5 is the column number of duplicate counter
End If
x = x + 1 'Increment Output row number
Next b
Next a
End Sub
Below code hide a certain number of rows(depending on the number of the week we are in) and shows only the cell corresponding to the current week number and cells corresponding to the future week numbers.
Dim test As String
test = Format(Now, "yyyy", vbMonday) & KW(Now)
For k = 3 To lastColumn
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).ColumnWidth = cWidth
If ThisWorkbook.Worksheets(PlanningTableNameUG).Cells(1, k).Value = test Then
today = True
On Error Resume Next
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k - 1).Ungroup
On Error GoTo 0
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k - 1).Group
End If
If Not today Then
On Error Resume Next
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Ungroup
On Error GoTo 0
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Group
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Hidden = True
If Hidden = True Then
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Group.Copy
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Group.Insert Shift:=xlToRight
End If
Else
ThisWorkbook.Worksheets(PlanningTableNameUG).Columns(k).Hidden = False
End If
Next k
' calculate the week number
Function KW(d As Date) As Integer
Dim Tag As Long
Tag = DateSerial(Year(d + (8 - Weekday(d)) Mod 7 - 3), 1, 1)
KW = (d - Tag - 3 + (Weekday(Tag) + 1) Mod 7) \ 7 + 1
End Function
Now, I need to count how many columns were hidden and add the exact numbers of columns, example:
columns:
1,2,3,4,5,6,7,8,9,10 (column 4 is corresponding to today week number)
I hide: 1,2,3 because there are in the past weeks, and want to add 11,12, 13, together with the week number corresponding for them, but not more than 1 year from current date.
Count of the weeks starts at the begging of the current year.
When hiding the columns you could add an integer variable that increases by one then use this variable to add that many new columns.
Then to add the week number, use the last column with the week number on it and add one to it for each of the new columns...
To count hidden columns in used range of ThisWorkbook.Worksheets(PlanningTableNameUG):
Dim col As Range
Dim cnt As Long
For Each col In ThisWorkbook.Worksheets(PlanningTableNameUG).UsedRange
Debug.Print col.EntireColumn.Hidden
cnt = cnt - col.EntireColumn.Hidden
Next col
EntireColumn.Hidden returns True if is hidden. True is -1 in VBA, that is why i deduct it from the counter to get positive value.
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
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