VBA creation of a Matrix and double loop - vba

I have a lot of Excel files in one folder and for each one I have (H:H) which contains a certain hour from 00:00 to 23:59 (appears within H at the format 0000 or 2359 etc ..) .
Then, at a certain hour we associate a certain number of operation (contains by column A) in the same sheet, but the time is not "homogeneous" , for example we can have 0012 at H4 on sheet 1 and 0014 in H4 on sheet number 2, meaning that time is not divided the same way according each sheet.
Sheet 1 Sheet 2
0010 ;0004
0017 ;0014
0018 ;0023
0025, ;0045
0025 ;0057
0031
0035
0055
I am trying to create a matrix with VBA on another sheet which would sum the number of operation only for a time-slot, I wish basically 00:15 to 00:59 , 01:00 to 01:59 ... and fill the matrix with the number of operation present in column A which correspond at the number of operation for the period of time (the hour) for each excel sheet of my folder.
The matrix I am trying to make would have the following form:
0000-0059 (H1) 0100-0159 (H2) 0200-0259 (H3) 0300-0359 (H4)...... 2300:2359(H24)
1
2
3
4
.
.
till, "number of files in my folder"
I had the idea to do something like it, but I realized the problem is not that simple ...
Sub Dailytraffic()
Application.ScreenUpdating = False
Dim wB As Workbook
Dim SumResult As Double
Dim H1, H2, H3, H4, H5, H6, H7, H8, H9, H10, H11, H12, H13, H14, H15, H16, H17, H18, H19, H20, H21, H22, H23, H24 As Double
Dim OutPutRange As Range
Set FileSystemObj = CreateObject("Scripting.FileSystemObject")
Set FolderObj = FileSystemObj.GetFolder("C:\...\")
Set OutPutRange = Workbooks("Libro1").Sheets("Hoja1").Range("D4")
Set H1RAN = Workbooks("Libro1").Sheets("Hoja1").Range("B1")
Set H2RAN = Workbooks("Libro1").Sheets("Hoja1").Range("C1")
Set H3RAN = Workbooks("Libro1").Sheets("Hoja1").Range("D1")
.
.
.
Set H24RAN = Workbooks("Libro1").Sheets("Hoja1").Range("AA1")
Set wB = Workbooks.Open(fileobj.Path)
For Each fileobj In FolderObj.Files
Dim i As Long
For i = 1 To 300
If (0 <= Left(wB.Sheets("Schedule Daily Bank Structure R").Cells(8, 4 + i).Value, 2) < 1) Then
H1 = WorksheetFunction.Sum(wB.Sheets("Schedule Daily Bank Structure R").Cells(1, i))
H1RAN.Value = H1
Set H1RAN = H1RAN.Offset(1, 0)
For j = 1 To 300
If (1 <= Left(wB.Sheets("Schedule Daily Bank Structure R").Cells(8, 4 + j).Value, 2) < 2) Then
H2 = WorksheetFunction.Sum(wB.Sheets("Schedule Daily Bank Structure R").Cells(1, i))
H2RAN.Value = H2
Set H2RAN = H2RAN.Offset(1, 0)
End If
Next i
Next j
wB.Save
wB.Close
Next fileobj
End Sub
My Idea would be to repeat 24-2 times more, but it does not seem to work. I have trouble to mix the loop which manages to sum for each different period of time and the Loop which go through the different files ... I am blocked here.

Related

Multiple charts by groups in column VBA

m completely new to vba programming and I was unable to find similar questions. Please guide me in the right direction if the answer to a similar problem is already to be found in this forum.
I have the following dataset (in practice, the dataset contains more names and dates per name):
Name Date Value1 Value2
AA 01-02-2022 0.5744 10
AA 01-03-2022 0.5542 10
AA 01-04-2022 0.5551 10
AA 01-05-2022 0.5678 10
BB 01-02-2022 0.5518 11
BB 01-03-2022 0.5659 11
BB 01-04-2022 0.5455 11
BB 01-05-2022 0.5404 11
CC 01-02-2022 0.5524 12
CC 01-03-2022 0.5321 12
CC 01-04-2022 0.5554 12
CC 01-05-2022 0.5407 12
I want to create multiple charts using VBA - separate charts for each name in column "Name" i.e. one chart for AA, another chart for BB and a third chart for CC etc.VBA The charts should plot Value1 and Value2 on the Y-axis against Date on the X-axis.
The data is loaded into the spreadsheet using a power query, which extracts data from an Oracle database.
If possible, I would like to place the charts next and/or appended to each other.
As (new) names are regularly removed (added) to the dataset, I am looking for a dynamic solution and hope to solve this using VBA.
Please do not hesitate to comment if I need be more clear in my description or to elaborate.
Kind regards,
Please, try the next code. It uses a dictionary to keep the unique names as key and range first row, respectively, last row as items. Then builds DataSource based on them:
Sub TestInsertCClusteredChart()
Dim sh As Worksheet, lastR As Long, arr, arrIt, i As Long, dict As Object
Dim rngDS As Range, ch_shape As Shape, chLeft As Double, chTop As Double
Set sh = ActiveSheet
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A1:A" & lastR).Value2
Set dict = CreateObject("Scripting.Dictionary")
'dict.RemoveAll
For i = 2 To UBound(arr)
If Not dict.Exists(arr(i, 1)) Then
dict.Add arr(i, 1), Array(i)
Else
arrIt = dict(arr(i, 1))
If UBound(arrIt) = 0 Then
ReDim Preserve arrIt(1)
arrIt(1) = i
Else
arrIt(1) = i
End If
dict(arr(i, 1)) = arrIt
End If
Next i
chLeft = sh.Range("F2").left: chTop = sh.Range("F2").top 'positions of the first chart
'build dataSource range and insert chart:
For i = 0 To dict.count - 1
Set rngDS = Union(sh.Range("A1:D1"), sh.Range(sh.cells(dict.Items()(i)(0), "A"), sh.cells(dict.Items()(i)(1), "D")))
Set ch_shape = sh.Shapes.AddChart2 'insert the chart
With ch_shape.Chart
With .ChartArea
.left = chLeft
.top = chTop
End With
.ChartType = xlColumnClustered
.SetSourceData rngDS
chLeft = chLeft + .ChartArea.width 'calculate the left position for the next chart
End With
Next i
End Sub
Sub deleteCharts(sh As Worksheet)
Dim s As Shape
For Each s In sh.Shapes
If s.HasChart Then s.Delete
Next
End Sub
Please, send some feedback after testing it.
The chart is created using default dimensions (height, width). They can be set, of course.
Adapted the code according to your recent requirements.

VBA for loop error

I have this loop to take groups of 4 cells in one worksheet and average them into another worksheet. When the one line reads z = z+3 it runs, but if I change it to z = z+4 it doesn't (Runtime error 1004 Unable to get the Average property of the WorksheetFunction class). Why is this?
Dim summary As Worksheet
Set summary = ThisWorkbook.Sheets("Sheet3")
Dim cost As Worksheet
Set cost = ThisWorkbook.Sheets("Sheet4")
Dim y As Integer
Dim z As Integer
z = 2
For y = 2 To 17
cost.Cells(y, 3) = Round(Application.WorksheetFunction.Average(Range(summary.Cells(4, z), summary.Cells(4, (z + 3)))), 0)
z = z + 4
Next y
If you run your routine with z=z+3, then the final (where y=17) area averaged is $AX$4:$BA$4
However, if with z=z+4, all those extra cells mean the final area averaged is $BN$4:$BQ$4.
As you've stated in your comments that your data only extends to $AX$4, once y reaches 14 all of the cells being averaged are empty, and the function fails.

Comparing Snapshot Data in Excel (VBA?) [duplicate]

I have this worksheet which gets data from API and its refreshes itself every 200 milliseconds. I want to calculate the change in value which is constantly increasing every 200 ms. For example Cell B2 has a value of 4 after 200 ms its changes to 7 then to 16 then to 26 etc, it just keeps adding value into it. All I want is to subtract the old value from the latest value to get the change for example 7-4=3 or 16-7=9 or 26-16=10.
I have added an image for clarification. This shows how I'm getting a data from software.
And one more image:
First, enable Iterative Calculations in Excel by going to File -> Options -> Formulas and then checking the box next to "Enable iterative calculation".
You need to define the following cells:
cell B1 0 (set to 1 to reset)
cell B2 =IF($B$1 = 1,, $B$2 + 1)
Use the following formula and fill down from B9 for as many changes as you would like to see (This formula assumes you have maximum iterations set to 100):
cell B9 =IF($B$1 = 1,"", IF($B$2 / 100 = $A9, $B$5, B9))
I will try to show an example here. If your cell that automatically updates is B5, then the changes will be tracked in B9 and below as the cell is refreshed. It may not be exactly what you are looking for, but I think it is close.
A B
1 reset 0
2 count 500
3
4
5 price 9
6
7
8 ID price
9 1 11
10 2 12
11 3 13
12 4 12
13 5 9
I suggest VBA solution, based on worksheet change event handling. Open VBA Project and put the below code into the target worksheet in Microsoft Excel Objects section:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' add reference to Microsoft Scripting Runtime via Menu - Tools - References
Const Scope = "C2:C5" ' monitoring area
Const DX = 1 ' horizontal result offset
Const DY = 0 ' vertical result offset
Const Buf = 0 ' FIFO buffer size
Static oData(0 To Buf) As New Dictionary
Static oIndex As New Dictionary
Dim rCells As Range
Dim oCell
Dim i As Long
Set rCells = Application.Intersect(Target, Target.Parent.Range(Scope))
If Not rCells Is Nothing Then
For Each oCell In rCells
With oCell
i = oIndex(.Address)
.Offset(DY, DX).Value = .Value - oData(i)(.Address)
oData(i)(.Address) = .Value
i = i + 1
If i > Buf Then i = 0
oIndex(.Address) = i
End With
Next
End If
End Sub
I added some comments for constants. Set the range which change to be monitored in Scope, the offsets where the resulting delta will be output in DX and DY, and as a bonus that algorithm supports computing delta not only between last and previous numbers, but also between any number of frames for each target cell via buffer organized as array of dictionaries, so set the size of the buffer in Buf, if you do not want to use the buffer then just leave 0 size, e. g. the value of 3 will compute delta between the last value and the one delayed by 800 ms for your case.
UPDATE
There is slightly simplified version of the code as requested in comment, put the below code into the target worksheet:
Private Sub Worksheet_Change(ByVal Target As Range)
Const Scope = "C2:C5" ' monitoring area
Static oData As New Dictionary
Dim rCells As Range
Dim oCell
Dim dDelta
Set rCells = Application.Intersect(Target, Target.Parent.Range(Scope))
If Not rCells Is Nothing Then
For Each oCell In rCells
With oCell
dDelta = .Value - oData(.Address)
If dDelta <> 0 Then
.Offset(0, 1).Value = dDelta
oData(.Address) = .Value
End If
End With
Next
End If
End Sub

Excel VBA - Counting Specific Values in one Column IF they Match Between Dates

if anyone could help me, I'd be most grateful. I'm very new to programming and have search tirelessly on the net to find my answer, to no avail.
Basically I have a spreadsheet that contains data of Companies, Items we receive from these companies and the Date we received them e.g.
A B C
Company1 Pen 30/05/2016
Company1 Pencil 01/06/2016
Company1 Eraser 01/06/2016
Company1 Marker 30/05/2016
Company2 Paper 02/06/2016
Company2 Card 02/06/2016
Company2 Docket 01/06/2016
Company2 Folder 01/06/2016
Company3 Red Ink 26/05/2016
Company3 Blue Ink 26/05/2016
Company3 Black Ink 28/05/2016
Company3 Printer 28/05/2016
Above is an example
My worksheet is called ItemsCompleted
What I need to to count how many times a Company appears between any given two dates.
I have a UserForm with a TextBox (DateFrom), a TextBox (DateTo), a ComboBox (CampanyName), a Button to Execute to the Code and a Label to display the number counted.
Worksheets("ItemsCompleted").Select
lblResultsBusCompany = Application.WorksheetFunction.CountIf(Range("D:D"), cmbCompanyResults.Value)
This is the code I have to count how many times a particular company work, now I just need the date range bit. That's what I'm really struggling with.
I sincerely hope someone not only understands what I am looking for, but too can assist me.
Thank you in advance.
Jane
Try to following code, it works once you populate the UserForm objects with the Company's name in the Combo-box, etc. .
Private Sub CommandButton1_Click()
Dim i As Integer
Dim sht As Worksheet
Dim StartDate, FinishDate As Date
Dim counter As Integer
StartDate = CDate(DateFrom.Text)
FinishDate = CDate(DateTo.Text)
Set sht = Worksheets("ItemsCompleted")
i = 1
counter = 0
While sht.Cells(i, 1) <> ""
If sht.Cells(i, 1) = CompanyName.Value Then ' compare value of cell with ComboBox value
If sht.Cells(i, 3) >= StartDate And sht.Cells(i, 3) <= FinishDate Then ' check if column C date is whithin range of selected dates
counter = counter + 1
End If
End If
i = i + 1
Wend
ResultLabel = counter
End Sub
This is inelegant but does the job:
t = 0
cn = UserForm1.CompanyName.Value
df = UserForm1.DateFrom.Value
dt = UserForm1.DateTo.Value
lr = ActiveCell.SpecialCells(xlLastCell).Row
For c = 1 To lr
If Range("A" & c) = cn And Format(Range("C" & c), "dd/mm/yyyy") >= Format(df, "dd/mm/yyyy") And Format(Range("C" & c), "dd/mm/yyyy") <= Format(dt, "dd/mm/yyyy") Then t = t + 1
Next
UserForm1.lblResultsBusCompany.Caption = Str(t)

fast way to copy formatting in excel

I have two bits of code. First a standard copy paste from cell A to cell B
Sheets(sheet_).Cells(x, 1).Copy Destination:=Sheets("Output").Cells(startrow, 2)
I can do almost the same using
Sheets("Output").Cells(startrow, 2) = Sheets(sheet_).Cells(x, 1)
Now this second method is much faster, avoiding copying to clipboard and pasting again. However it does not copy across the formatting as the first method does. The Second version is almost instant to copy 500 lines, while the first method adds about 5 seconds to the time. And the final version could be upwards of 5000 cells.
So my question can the second line be altered to included the cell formatting (mainly font colour) while still staying fast.
Ideally I would like to be able to copy the cell values to a array/list along with the font formatting so I can do further sorting and operations on them before I "paste" them back on to the worksheet..
So my ideal solution would be some thing like
for x = 0 to 5000
array(x) = Sheets(sheet_).Cells(x, 1) 'including formatting
next
for x = 0 to 5000
Sheets("Output").Cells(x, 1)
next
is it possible to use RTF strings in VBA or is that only possible in vb.net, etc.
Answer*
Just to see how my origianl method and new method compar, here are the results or before and after
New code = 65msec
Sheets("Output").Cells(startrow, 2) = Sheets(sheet_).Cells(x, 1)
Sheets("Output").Range("B" & startrow).Font.ColorIndex = Sheets(sheet_).Range("A" & x).Font.ColorIndex 'copy font colour as well
Old code = 1296msec
'Sheets("Output").Cells(startrow, 2).Value = Sheets(sheet_).Cells(x, 1)
'Sheets(sheet_).Cells(x, 1).Copy
'Sheets("Output").Cells(startrow, 2).PasteSpecial (xlPasteFormats)
'Application.CutCopyMode = False
You could have simply used Range("x1").value(11)
something like below:
Sheets("Output").Range("$A$1:$A$500").value(11) = Sheets(sheet_).Range("$A$1:$A$500").value(11)
range has default property "Value" plus value can have 3 optional orguments 10,11,12.
11 is what you need to tansfer both value and formats. It doesn't use clipboard so it is faster.- Durgesh
For me, you can't. But if that suits your needs, you could have speed and formatting by copying the whole range at once, instead of looping:
range("B2:B5002").Copy Destination:=Sheets("Output").Cells(startrow, 2)
And, by the way, you can build a custom range string, like Range("B2:B4, B6, B11:B18")
edit: if your source is "sparse", can't you just format the destination at once when the copy is finished ?
Remember that when you write:
MyArray = Range("A1:A5000")
you are really writing
MyArray = Range("A1:A5000").Value
You can also use names:
MyArray = Names("MyWSTable").RefersToRange.Value
But Value is not the only property of Range. I have used:
MyArray = Range("A1:A5000").NumberFormat
I doubt
MyArray = Range("A1:A5000").Font
would work but I would expect
MyArray = Range("A1:A5000").Font.Bold
to work.
I do not know what formats you want to copy so you will have to try.
However, I must add that when you copy and paste a large range, it is not as much slower than doing it via an array as we all thought.
Post Edit information
Having posted the above I tried by own advice. My experiments with copying Font.Color and Font.Bold to an array have failed.
Of the following statements, the second would fail with a type mismatch:
ValueArray = .Range("A1:T5000").Value
ColourArray = .Range("A1:T5000").Font.Color
ValueArray must be of type variant. I tried both variant and long for ColourArray without success.
I filled ColourArray with values and tried the following statement:
.Range("A1:T5000").Font.Color = ColourArray
The entire range would be coloured according to the first element of ColourArray and then Excel looped consuming about 45% of the processor time until I terminated it with the Task Manager.
There is a time penalty associated with switching between worksheets but recent questions about macro duration have caused everyone to review our belief that working via arrays was substantially quicker.
I constructed an experiment that broadly reflects your requirement. I filled worksheet Time1 with 5000 rows of 20 cells which were selectively formatted as: bold, italic, underline, subscript, bordered, red, green, blue, brown, yellow and gray-80%.
With version 1, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" using copy.
With version 2, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" by copying the value and the colour via an array.
With version 3, I copied every 7th cells from worksheet "Time1" to worksheet "Time2" by copying the formula and the colour via an array.
Version 1 took an average of 12.43 seconds, version 2 took an average of 1.47 seconds while version 3 took an average of 1.83 seconds. Version 1 copied formulae and all formatting, version 2 copied values and colour while version 3 copied formulae and colour. With versions 1 and 2 you could add bold and italic, say, and still have some time in hand. However, I am not sure it would be worth the bother given that copying 21,300 values only takes 12 seconds.
** Code for Version 1**
I do not think this code includes anything that needs an explanation. Respond with a comment if I am wrong and I will fix.
Sub SelectionCopyAndPaste()
Dim ColDestCrnt As Integer
Dim ColSrcCrnt As Integer
Dim NumSelect As Long
Dim RowDestCrnt As Integer
Dim RowSrcCrnt As Integer
Dim StartTime As Single
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
NumSelect = 1
ColDestCrnt = 1
RowDestCrnt = 1
With Sheets("Time2")
.Range("A1:T715").EntireRow.Delete
End With
StartTime = Timer
Do While True
ColSrcCrnt = (NumSelect Mod 20) + 1
RowSrcCrnt = (NumSelect - ColSrcCrnt) / 20 + 1
If RowSrcCrnt > 5000 Then
Exit Do
End If
Sheets("Time1").Cells(RowSrcCrnt, ColSrcCrnt).Copy _
Destination:=Sheets("Time2").Cells(RowDestCrnt, ColDestCrnt)
If ColDestCrnt = 20 Then
ColDestCrnt = 1
RowDestCrnt = RowDestCrnt + 1
Else
ColDestCrnt = ColDestCrnt + 1
End If
NumSelect = NumSelect + 7
Loop
Debug.Print Timer - StartTime
' Average 12.43 secs
Application.Calculation = xlCalculationAutomatic
End Sub
** Code for Versions 2 and 3**
The User type definition must be placed before any subroutine in the module. The code works through the source worksheet copying values or formulae and colours to the next element of the array. Once selection has been completed, it copies the collected information to the destination worksheet. This avoids switching between worksheets more than is essential.
Type ValueDtl
Value As String
Colour As Long
End Type
Sub SelectionViaArray()
Dim ColDestCrnt As Integer
Dim ColSrcCrnt As Integer
Dim InxVLCrnt As Integer
Dim InxVLCrntMax As Integer
Dim NumSelect As Long
Dim RowDestCrnt As Integer
Dim RowSrcCrnt As Integer
Dim StartTime As Single
Dim ValueList() As ValueDtl
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' I have sized the array to more than I expect to require because ReDim
' Preserve is expensive. However, I will resize if I fill the array.
' For my experiment I know exactly how many elements I need but that
' might not be true for you.
ReDim ValueList(1 To 25000)
NumSelect = 1
ColDestCrnt = 1
RowDestCrnt = 1
InxVLCrntMax = 0 ' Last used element in ValueList.
With Sheets("Time2")
.Range("A1:T715").EntireRow.Delete
End With
StartTime = Timer
With Sheets("Time1")
Do While True
ColSrcCrnt = (NumSelect Mod 20) + 1
RowSrcCrnt = (NumSelect - ColSrcCrnt) / 20 + 1
If RowSrcCrnt > 5000 Then
Exit Do
End If
InxVLCrntMax = InxVLCrntMax + 1
If InxVLCrntMax > UBound(ValueList) Then
' Resize array if it has been filled
ReDim Preserve ValueList(1 To UBound(ValueList) + 1000)
End If
With .Cells(RowSrcCrnt, ColSrcCrnt)
ValueList(InxVLCrntMax).Value = .Value ' Version 2
ValueList(InxVLCrntMax).Value = .Formula ' Version 3
ValueList(InxVLCrntMax).Colour = .Font.Color
End With
NumSelect = NumSelect + 7
Loop
End With
With Sheets("Time2")
For InxVLCrnt = 1 To InxVLCrntMax
With .Cells(RowDestCrnt, ColDestCrnt)
.Value = ValueList(InxVLCrnt).Value ' Version 2
.Formula = ValueList(InxVLCrnt).Value ' Version 3
.Font.Color = ValueList(InxVLCrnt).Colour
End With
If ColDestCrnt = 20 Then
ColDestCrnt = 1
RowDestCrnt = RowDestCrnt + 1
Else
ColDestCrnt = ColDestCrnt + 1
End If
Next
End With
Debug.Print Timer - StartTime
' Version 2 average 1.47 secs
' Version 3 average 1.83 secs
Application.Calculation = xlCalculationAutomatic
End Sub
Just use the NumberFormat property after the Value property:
In this example the Ranges are defined using variables called ColLetter and SheetRow and this comes from a for-next loop using the integer i, but they might be ordinary defined ranges of course.
TransferSheet.Range(ColLetter & SheetRow).Value = Range(ColLetter & i).Value
TransferSheet.Range(ColLetter & SheetRow).NumberFormat = Range(ColLetter & i).NumberFormat
Does:
Set Sheets("Output").Range("$A$1:$A$500") = Sheets(sheet_).Range("$A$1:$A$500")
...work? (I don't have Excel in front of me, so can't test.)