VBA 2D Array for each item find average value - arrays

I am trying to find a way to average an array column value with a condition on items from another column in that array - I am aware that a class or dictionary might be the best solution but I would like to stick to an array as in my real scenario I have to use an array.
In this case the data is as follows
Risk ID Data set 1 Data set 2
23359720 1154 587
23359720 1254 658
23359720 854 756
23293773 965 1456
20053692 1458 458
I would like to find the average of Data sets 1 and 2 per Risk ID, here is what I've tried but does not work - I have seen that this it's not possible to use for each and point it to a specific column, but not sure what else to do in the case of an array?
Edit: expected result data:
ArrayResultAverage()
Risk ID Avg Data set 1 Avg Data set 2
23359720 1087.33 667
23293773 965 1456
20053692 1458 458
Sub Test_Arr_Avg()
'
Dim TESTWB As Workbook
Dim TESTWS As Worksheet
Set TESTWB = ThisWorkbook
Set TESTWS = TESTWB.Worksheets("TEST")
'Array set up
Dim RngTest As Range
Dim ArrTestAvg As Variant
NbRowsTest = TESTWS.Range("A1").End(xlDown).Row
Set RngTest = TESTWS.Range(TESTWS.Cells(1, 1), TESTWS.Cells(NbRowsTest, 3))
ArrTestAvg = RangeToArray2D(RngTest)
'Find the average of Data Range 1 for each item in Risk ID
For k = 1 To UBound(ArrTestAvg, 1)
Dim Sum As Variant
Sum = 0
For Each Item In ArrTestAvg(k, 1)
Sum = Sum + ArrTestAvg(k, 2)
Dim AverageDataSet1 As Variant
AverageDataSet1 = Sum / UBound(ArrTestAvg(Item)) + 1
Debug.Print AverageDataSet1
Next Item
Next k
End Sub
Public Function RangeToArray2D(inputRange As Range) As Variant()
Dim size As Integer
Dim inputValue As Variant, outputArray() As Variant
inputValue = inputRange
On Error Resume Next
size = UBound(inputValue)
If Err.Number = 0 Then
RangeToArray2D = inputValue
Else
On Error GoTo 0
ReDim outputArray(1 To 1, 1 To 1)
outputArray(1, 1) = inputValue
RangeToArray2D = outputArray
End If
On Error GoTo 0
End Function

Get Averages of Unique Data
Adjust the values in the constants section, especially the destination worksheet name (it's the same as the source worksheet name) and its first cell address.
The dictionary's keys hold the unique risk ids, while its items (values) hold the associated destination rows.
The result is written to the same array (which is too big) but with dr the destination row size is tracked and only three columns will be copied.
Before the calculation of the averages, column 1 holds the unique risk ids (the same order as in the dictionary), columns 2 and 3 hold the sums while columns 4 and 5 hold the counts of the first and second data set respectively.
Option Explicit
Sub Test_Arr_Avg()
' Source
Const sName As String = "Sheet1"
' Destination
Const dName As String = "Sheet1"
Const dFirstCellAddress As String = "E1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Read from source.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "A").End(xlUp).Row
Dim srg As Range: Set srg = sws.Range("A1", sws.Cells(slRow, "C"))
Dim srCount As Long: srCount = srg.Rows.Count
' Write source range values to array.
Dim Data As Variant: Data = GetRange(srg)
' Add two helper columns for the count.
ReDim Preserve Data(1 To srCount, 1 To 5)
' Sum up and count uniques.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim dr As Long: dr = 1 ' first row are headers
Dim sr As Long
Dim cr As Long
Dim c As Long
For sr = 2 To srCount
' Sum up.
If dict.Exists(Data(sr, 1)) Then
cr = dict(Data(sr, 1))
For c = 2 To 3
Data(cr, c) = Data(cr, c) + Data(sr, c)
Next c
Else
dr = dr + 1
cr = dr
dict(Data(sr, 1)) = cr
For c = 1 To 3
Data(cr, c) = Data(sr, c)
Next c
End If
' Count.
For c = 4 To 5
Data(cr, c) = Data(cr, c) + 1
Next c
Next sr
' Calculate averages.
For cr = 2 To dr
For c = 2 To 3
Data(cr, c) = Data(cr, c) / Data(cr, c + 2)
' You might want to round the results instead:
'Data(cr, c) = Round(Data(cr, c) / Data(cr, c + 2), 2)
Next c
Next cr
Application.ScreenUpdating = False
' Write to destination.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dFirstCellAddress).Resize(, 3)
.Resize(dr).Value = Data
' Clear below.
.Resize(dws.Rows.Count - .Row - dr + 1).Offset(dr).Clear
' Apply various formatting.
.Font.Bold = True ' headers
.Resize(dr - 1, 2).Offset(1, 1).NumberFormat = "#0.00" ' averages
.EntireColumn.AutoFit ' columns
End With
'wb.Save
' Inform.
Application.ScreenUpdating = True
MsgBox "Risk ids averaged.", vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

It would be complicated to use a single Dictionary. Here I add a Sub-Dictionary for each Risk ID to the main Dictionary. The Sub-Dictionary is used to hold all the values for each ID. The final step is to create an array of averages for all the main Dictionary items.
Sub Test_Arr_Avg()
Dim Data As Variant
With TestWS.Range("A1").CurrentRegion
Data = .Offset(1).Resize(.Rows.Count - 1, 3)
End With
Dim Results As Variant
Results = KeyedAverages(Data, 1, 2)
Stop
End Sub
Function KeyedAverages(Data As Variant, IDColumn As Long, ValueColumn As Long)
Dim Map As Object
Set Map = CreateObject("Scripting.Dictionary")
Dim Key As Variant
Dim r As Long
For r = 1 To UBound(Data)
Key = CStr(Data(r, IDColumn))
If Len(Key) > 0 Then
If Not Map.Exists(Key) Then Map.Add Key, CreateObject("Scripting.Dictionary")
With Map(Key)
.Add CStr(.Count), Data(r, ValueColumn)
End With
End If
Next
Dim Results As Variant
Dim Values As Variant
ReDim Results(1 To Map.Count, 1 To 2)
Dim n As Long
For Each Key In Map.Keys
n = n + 1
Values = Map(Key).Items
Results(n, 1) = Key
Results(n, 2) = WorksheetFunction.Average(Values)
Next
KeyedAverages = Results
End Function
Public Function TestWB() As Workbook
Set TestWB = ThisWorkbook
End Function
Public Function TestWS() As Worksheet
Set TestWS = TestWB.Worksheets("Test")
End Function
Note: Code updated to exclude empty items.

Related

Using Find with data split into multiple 2D arrays

I have a report to update from data in another report. Both reports are large, over 50,000 rows. I read them into arrays so the process runs faster.
I need to split the Source array into separate arrays based on certain conditions in the HR array. I get an object required error when I try to assign a value to the ID variable.
Option Explicit
Sub SearchArrays()
Dim wb As Workbook, wsSource As Worksheet, wsHR As Worksheet
Dim arrSource() As Variant, arrHR() As Variant, arrNotFound() As Variant, arrRemoved() As Variant, arrUpdated() As Variant
'Dim ID As String
Dim ID As Variant
Dim x As Long, y As Long, nCounter As Long, CounterN As Long, rCounter As Long, CounterR As Long, uCounter As Long, CounterU As Long
Set wb = ThisWorkbook
Set wsSource = wb.Worksheets("Source")
Set wsHR = wb.Worksheets("HR")
wsSource.Activate
arrSource = Range("A2", Range("A2").End(xlDown).End(xlToRight)) 'Read Source data into array
wsHR.Activate
arrHR = Range("A2", Range("A2").End(xlDown).End(xlToRight)) 'Read HR data into array
'Use Find to find the values in source array in the hr array
For x = LBound(arrSource, 1) To UBound(arrSource, 1)
For y = LBound(arrHR, 1) To UBound(arrHR, 1)
'ID is in column 2 of Source data and column 3 of HR data
Set ID = arrSource(x, 2).Find(what:=arrHR(y, 3).Value, LookIn:=xlValues, lookat:=xlWhole)
If ID Is Nothing Then
'Copy data to Not Found array
nCounter = nCounter + 1
ReDim Preserve arrNotFound(1 To 5, 1 To nCounter) 'Redimension the Not Found array with each instance
For CounterN = 1 To 5 'The arrNotFound equals the current row
arrNotFound(CounterN, nCounter) = arrSource(x, CounterN)
Next CounterN
ElseIf Not ID Is Nothing And ID.Offset(, 3).Value <> arrHR(y, 3).Offset(, 2) Then
'Copy to removed array
rCounter = rCounter + 1
ReDim Preserve arrRemoved(1 To 5, 1 To rCounter) 'Redimension the Removed array with each instance
For CounterR = 1 To 5 'The arrRemoved equals the current row
arrRemoved(CounterR, rCounter) = arrSource(x, CounterR)
Next CounterR
ElseIf Not ID Is Nothing And ID.Offset(, 3).Value = arrHR(y, 3).Offset(, 2) Then
'Copy to Updated array
uCounter = uCounter + 1
ReDim Preserve arrUpdated(1 To 5, 1 To uCounter) 'Redimension the Updated array with each instance
For CounterU = 1 To 5 'The arrUpdated equals the current row
arrUpdated(CounterU, uCounter) = arrSource(x, CounterU)
Next CounterU
End If
Next y
Next x
'Write arrNotFound to a new worksheet
'Write arrRemoved to a new worksheet
'Write arrUpdated to a new worksheet
End Sub
Sample Data:
Split Data Into Arrays
In a Nutshell
It writes the lookup data to a dictionary (lDict).
It writes the source data to a 2D one-based array (sData).
It writes the source data rows (srData) to three collections in an array (dcData).
It writes the data to up to three 2D one-based arrays in another array (dData). This jagged array holds the 'three' required arrays.
It writes the data to up to three new worksheets.
The Code
Option Explicit
Sub SplitDataIntoArrays()
' Define constants.
' Lookup
Const lName As String = "HR"
Const lCol1 As Long = 3
Const lCol2 As Long = 6
' Source
Const sName As String = "Source"
Const sCol1 As Long = 2
Const sCol2 As Long = 5
' Destination
Dim dNames() As Variant
dNames = VBA.Array("Updated", "Removed", "Not Found")
Const dfCellAddress As String = "A1"
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the values from the lookup worksheet columns
' to two 2D one-based one-column arrays ('lData1', 'lData2').
Dim lws As Worksheet: Set lws = wb.Worksheets(lName)
Dim lrg As Range: Set lrg = lws.Range("A1").CurrentRegion
Dim lrCount As Long: lrCount = lrg.Rows.Count
If lrCount = 1 Then
MsgBox "No data in the lookup worksheet.", vbCritical
Exit Sub
End If
Dim lData1() As Variant: lData1 = lws.Columns(lCol1).Value
Dim lData2() As Variant: lData2 = lws.Columns(lCol2).Value
' Write the unique values from the lookup arrays
' to the lookup dictionary ('lDict') whose keys will hold
' the value from the first array while its items will hold
' the corresponding values from the second array.
Dim lDict As Object: Set lDict = CreateObject("Scripting.Dictionary")
lDict.CompareMode = vbTextCompare
Dim r As Long
Dim lString As String
For r = 2 To lrCount
lString = CStr(lData1(r, 1))
If Len(lString) > 0 Then ' exclude blanks
If Not lDict.Exists(lString) Then
lDict(lString) = CStr(lData2(r, 1))
'Else ' already exists; there shouldn't be duplicates!
End If
End If
Next r
If lDict.Count = 0 Then
MsgBox "No valid data in the lookup column range.", vbCritical
Exit Sub
End If
' Free memory since the lookup data is in the lookup dictionary.
Erase lData1
Erase lData2
' Write the data from the source worksheet
' to a 2D one-based array ('sData').
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range("A1").CurrentRegion
Dim srCount As Long: srCount = srg.Rows.Count
If srCount = 1 Then
MsgBox "No data in the source worksheet.", vbCritical
Exit Sub
End If
Dim scCount As Long: scCount = srg.Columns.Count
Dim sData() As Variant: sData = srg.Value
' Using the information in the lookup dictionary, write the values
' from the source array to the (jagged) destination collection array
' ('dcData') whose each element will hold a collection
' of the appropriate 1D source row arrays ('srData').
Dim srData() As String: ReDim srData(1 To scCount)
Dim dcData() As Variant: ReDim dcData(1 To 3)
Dim dc As Long
' Add a new collection to each element of the destination collection array.
For dc = 1 To 3
Set dcData(dc) = New Collection
Next dc
Dim sString1 As String
Dim sString2 As String
Dim sCase As Long
Dim sc As Long
' Add the row arrays to the collections.
For r = 2 To srCount
sString1 = CStr(sData(r, sCol1))
If lDict.Exists(sString1) Then
sString2 = CStr(sData(r, sCol2))
If StrComp(sString2, lDict(sString1), vbTextCompare) = 0 Then
sCase = 1 ' updated
Else
sCase = 2 ' removed
End If
Else
sCase = 3 ' not found
End If
For sc = 1 To scCount
srData(sc) = sData(r, sc)
Next sc
dcData(sCase).Add srData
Next r
' Write the data from the destination collection array
' to the destination (jagged) array ('dData') which will hold up to three
' 2D one-based arrays (ready to be easily written to the worksheets).
Dim dData() As Variant: ReDim dData(1 To 3)
Dim cData() As Variant ' each 2D one-based array in the destination array
Dim drCount As Long
Dim dItem As Variant
For dc = 1 To 3
drCount = dcData(dc).Count ' number of source row ranges...
' ... or the number of current destination array data rows
If drCount > 0 Then
drCount = drCount + 1 ' include headers
ReDim cData(1 To drCount, 1 To scCount)
' Write headers
For sc = 1 To scCount
cData(1, sc) = sData(1, sc)
Next sc
' Write data.
r = 1 ' headers are written
For Each dItem In dcData(dc)
r = r + 1
For sc = 1 To scCount
cData(r, sc) = dItem(sc)
Next sc
Next dItem
dData(dc) = cData ' assign current array to the destination array
End If
Next dc
' Free memory since the data is in the destination array.
Set lDict = Nothing
Erase sData
Erase dcData
Erase cData
' Write the data from the destination array to the destination worksheets.
Application.ScreenUpdating = False
Dim dws As Worksheet ' Current Destination Worksheet
Dim drg As Range ' Current Destination Range
For dc = 1 To 3
' Delete the worksheet if it exists.
On Error Resume Next
Set dws = wb.Worksheets(dNames(dc - 1))
On Error GoTo 0
If Not dws Is Nothing Then ' the worksheet exists; delete it
Application.DisplayAlerts = False
dws.Delete
Application.DisplayAlerts = True
'Else ' the worksheet doesn't exist; do nothing
End If
If Not IsEmpty(dData(dc)) Then ' appropriate array is not empty; write
' Add a new worksheet after all sheets.
Set dws = wb.Worksheets.Add(After:=wb.Sheets(wb.Sheets.Count))
' Rename the newly added worksheet.
dws.Name = dNames(dc - 1)
' Reference the destination range.
Set drg = dws.Range(dfCellAddress) _
.Resize(UBound(dData(dc), 1), scCount)
' Write the values from the destination array
' to the destination range.
drg.Value = dData(dc)
' Apply some formatting.
drg.Rows(1).Font.Bold = True
drg.EntireColumn.AutoFit
' Reset the variable to be ready for the next check.
Set dws = Nothing
'Else ' appropriate array is empty; do nothing
End If
Next dc
' Save the workbook.
'wb.Save
Application.ScreenUpdating = True
MsgBox "Data split.", vbInformation
End Sub

VBA Copy row from one 2D array and add it to another 2D array

I have a large database (~7000 rows x 25 columns) that i have assigned to an array. Based on user inputs, I am trying to search the database to find items that match the input and copy the entire row to a new array to create a new database to filter with the next question. The arrays are DBRange and DBT and are defined as public variants. I've tried copying the data from DBRange to a new sheet, but that is incredibly slow and I'm trying to speed things up with keeping things within arrays if possible.
DBRange = wsd.Range("A1").CurrentRegion 'Sets DBRange to the entirety of the Database
Cervical = 0
If CervicalStartOB.Value = True Then
Cervical = 1
SpineSection.Hide
For i = LBound(DBRange, 1) To UBound(DBRange, 1) 'starts for loop starting with the 1st row in the array to the last row
If DBRange(i, 13) = "X" Then 'determines if the value in row i column 13 has an X
ReDim Preserve DBT(count, UBound(DBRange, 2))
DBT(count, UBound(DBRange, 2)) = Application.WorksheetFunction.Index(DBRange, i, 0)
count = count + 1
End If
Next i
Get Range Criteria Rows
Option Explicit
Sub GetRangeCriteriaRowsTESTKeepHeaders()
Const sCriteriaString As String = "X"
Const sCol As Long = 13
Const sfRow As Long = 2 ' First Data Row
Dim wsd As Worksheet: Set wsd = ActiveSheet
' Reference the source range
Dim srg As Range: Set srg = wsd.Range("A1").CurrentRegion
' Write the criteria rows to an array.
' If you want to keep headers, use the first row ('sfrow')
' as the parameter of the 'FirstRow' argument of the function.
Dim DBT As Variant
DBT = GetRangeCriteriaRows(srg, sCriteriaString, sCol, sfRow)
If IsEmpty(DBT) Then Exit Sub
End Sub
Sub GetRangeCriteriaRowsTESTOnlyData()
Const sCriteriaString As String = "X"
Const sCol As Long = 13
Const sfRow As Long = 2 ' First Data Row
Dim wsd As Worksheet: Set wsd = ActiveSheet
' Reference the source range.
Dim srg As Range: Set srg = wsd.Range("A1").CurrentRegion
' Reference the data range (no headers).
Dim shrCount As Long: shrCount = sfRow - 1
Dim sdrg As Range
Set sdrg = srg.Resize(srg.Rows.Count - shrCount).Offset(shrCount)
' Write the criteria rows to an array.
' If the range has no headers, don't use the 'FirstRow' argument
' of the function.
Dim DBT As Variant: DBT = GetRangeCriteriaRows(sdrg, sCriteriaString, sCol)
If IsEmpty(DBT) Then Exit Sub
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of the rows of a range ('SourceRange'),
' that meet a string criterion ('CriteriaString') in a column
' ('CriteriaColumnIndex'), in a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRangeCriteriaRows( _
ByVal SourceRange As Range, _
ByVal CriteriaString As String, _
Optional ByVal CriteriaColumnIndex As Long = 1, _
Optional ByVal FirstRow As Long = 1) _
As Variant
Const ProcName As String = "GetRangeCriteriaRows"
On Error GoTo ClearError
' Count the source rows and the source/destination columns.
Dim srCount As Long: srCount = SourceRange.Rows.Count
Dim cCount As Long: cCount = SourceRange.Columns.Count
' Count the source header rows.
Dim shrCount As Long: shrCount = FirstRow - 1
' Define the source data range according to the first row.
Dim sdrg As Range
Set sdrg = SourceRange.Resize(srCount - shrCount).Offset(shrCount)
' Write the source range values to the source array.
Dim sData As Variant: sData = SourceRange.Value
' Count the criteria rows in the source data criteria column range.
Dim sdcrg As Range: Set sdcrg = sdrg.Columns(CriteriaColumnIndex)
Dim drCount As Long
drCount = Application.CountIf(sdcrg, CriteriaString) + shrCount
' Define the destination array.
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
Dim sr As Long ' Current Source Row
Dim c As Long ' Current Source/Destination Column
Dim dr As Long ' Current Destination Row
' Write the header rows from the source array to the destination array.
If FirstRow > 1 Then
For sr = 1 To shrCount
dr = dr + 1
For c = 1 To cCount
dData(dr, c) = sData(sr, c)
Next c
Next sr
End If
' Write the criteria rows from the source array to the destination array.
For sr = FirstRow To srCount
If StrComp(CStr(sData(sr, CriteriaColumnIndex)), CriteriaString, _
vbTextCompare) = 0 Then
dr = dr + 1
For c = 1 To cCount
dData(dr, c) = sData(sr, c)
Next c
End If
Next sr
GetRangeCriteriaRows = dData
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

VBA Jagged Array Duplicates

I'm new to coding with VBA, and a beginner programmer in general. I have the following simple table (the data keeps getting inputted on daily basis, so it changes):
Item #
Description
Date
Location
Plate
Load
Type
Rate
Cost
0001
des1
30/1/21
Site
ABC123
5
One
typ1
100
0002
des2
30/1/21
Office
ACB465
4
One
typ1
100
0003
des3
30/1/21
Office
ABC789
3
One
typ1
100
0004
des4
30/1/21
Site
ABS741
5
One
typ1
100
0005
des4
31/1/21
Office
ABC852
2
One
typ1
100
I would like to filter this data by specific date first, then delete duplicates in Location while adding the Load for said duplicates.
For example, if I wanted to filter for 30/1/21. It would end up as follows:
Location
Load
Site
10
Office
7
I would then want to put it in one summary cell as follows:
Summary
10 Site, 7 Office
I was able to filter the original table into jagged arrays. The code for that is:
For j = numberSkipD To numberRowsD
If Worksheets("Disposal Fees").Range("F" & j).Value = Worksheets("Daily Tracking").Range("B2").Value Then
For k = numberDisposalInformationRaw To numberDisposalLocation
ReDim Preserve disposalLocation(numberDisposalLocation)
disposalLocation(numberDisposalLocation) = Worksheets("Disposal Fees").Range("I" & j).Value
Next
numberDisposalLocation = numberDisposalLocation + 1
For k = numberDisposalInformationRaw To numberDisposalLoad
ReDim Preserve disposalLoad(numberDisposalLoad)
disposalLoad(numberDisposalLoad) = Worksheets("Disposal Fees").Range("K" & j).Value
Next
numberDisposalLoad = numberDisposalLoad + 1
End If
Next
I then tried to do the second table above (deleting duplicates and adding the values for said duplicates together) but it is giving me errors, not sure how to solve them. I know they're index errors, but don't know how to fix them. (Please help me with this part, here is the code)
Dim disposalInformationRaw As Variant
Dim disposalInformationCooked As Variant
Dim FoundIndex As Variant, MaxRow As Long, m As Long
ReDim disposalInformationCooked(1 To UBound(disposalInformationRaw, 1), 1 To UBound(disposalInformationRaw, 2))
MaxRow = 0
For m = 1 To UBound(disposalInformationRaw, 1)
FoundIndex = Application.Match(disposalInformationRaw(m, 1), Application.Index(disposalInformationCooked, 0, 1), 0)
If IsError(FoundIndex) Then
MaxRow = MaxRow + 1
FoundIndex = MaxRow
disposalInformationCooked(FoundIndex, 1) = disposalInformationRaw(m, 1)
End If
disposalInformationCooked(FoundIndex, 2) = Val(disposalInformationCooked(FoundIndex, 2)) + Val(disposalInformationRaw(i, 2))
Next m
Range("G1").Resize(MaxRow, UBound(disposalInformationCooked, 2)).Value = disposalInformationCooked
I don't think I'd have much trouble finalizing the third part (the summary), but if you know how to do it, please feel free to share how you would approach it. I mostly need help with the second part. I would be more than happy to edit and provide more information if needed. Thanks in advance.
Here's one approach using a dictionary.
dim dict, rw as range, locn, k, msg, theDate
set dict= createobject("scripting.dictionary")
theDate = Worksheets("Daily Tracking").Range("B2").Value
'adjust table range as required
for each rw in worksheets("Disposal Fees").range("F6:K100").rows
if rw.cells(3).Value = theDate Then 'date match?
locn = rw.cells(4).Value 'read location
dict(locn) = dict(locn) + rw.cells(6).Value 'add load to sum
end if
next rw
'loop over the dictionary keys and build the output
for each k in dict
msg = msg & IIf(len(msg) > 0, ", ", "") & dict(k) & " " & k
next k
debug.print msg
Sum Unique
Disposal Fees
Daily Tracking
Adjust the values in the constants section.
The Code
Option Explicit
Sub TESTsumByValue()
' Source
Const srcName As String = "Disposal Fees"
Const lCol As Long = 3
Const kCol As Long = 4
Const sCol As Long = 6
Const SumFirst As Boolean = True
Const KSDel As String = ":"
Const IDel As String = ", "
' Destination
Const dstName As String = "Daily Tracking"
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' Workbook containing this code.
' Define Source Range (You may have to do something different).
Dim srg As Range: Set srg = wb.Worksheets(srcName).Range("A1").CurrentRegion
' Write Criteria to variable.
Dim drg As Range: Set drg = wb.Worksheets(dstName).Range("B2")
Dim Criteria As Variant: Criteria = drg.Value
' Use function to get the result.
Dim s As String
s = sumByValue(Criteria, srg, lCol, kCol, sCol, SumFirst, KSDel, IDel)
Debug.Print s ' "10:Site, 4:Bathroom, 4:Office"
drg.Offset(, 3).Value = s ' writes to 'E2'
End Sub
Function sumByValue( _
ByVal LookupValue As Variant, _
rng As Range, _
ByVal LookupColumn As Long, _
ByVal KeyColumn As Long, _
ByVal SumColumn As Long, _
Optional ByVal SumFirst As Boolean = False, _
Optional ByVal KeySumDelimiter As String = ": ", _
Optional ByVal ItemsDelimiter As String = ", ") _
As String
' Validate range ('rng').
If rng Is Nothing Then Exit Function
' Write values from range to Data Array ('Data').
Dim Data As Variant: Data = rng.Value ' 2D one-based array
' Declare additional variables.
Dim vKey As Variant ' Current Key Value
Dim vSum As Variant ' Current Sum Value
Dim i As Long ' Data Array Row Counter
' Create a reference to Unique Sum Dictionary (no variable).
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare ' 'A = a'
' Loop through Data Array ('Data') and write and sumup unique values
' to Unique Sum Dictionary.
For i = 1 To UBound(Data, 1)
If Data(i, LookupColumn) = LookupValue Then
vKey = Data(i, KeyColumn)
If Not IsError(vKey) Then
If Len(vKey) > 0 Then
vSum = Data(i, SumColumn)
If IsNumeric(vSum) Then
.Item(vKey) = .Item(vKey) + vSum
Else
.Item(vKey) = .Item(vKey) + 0
End If
End If
End If
End If
Next i
' Validate Unique Sum Dictionary.
If .Count = 0 Then Exit Function
' Redefine variables to be reused.
ReDim Data(1 To .Count) ' Result Array: 1D one-based array
i = 0 ' Result Array Elements Counter
' Write results to Result Array.
If SumFirst Then
For Each vKey In .Keys
i = i + 1
Data(i) = .Item(vKey) & KeySumDelimiter & vKey
Next vKey
Else
For Each vKey In .Keys
i = i + 1
Data(i) = vKey & KeySumDelimiter & .Item(vKey)
Next vKey
End If
End With
' Write the elements of Data Array to Result String.
sumByValue = Join(Data, ItemsDelimiter)
End Function

Make a new array that contains only selected rows from a previous array based on a variable in a column

I'm trying to make a new array that contains only selected values from a previous array based on a variable.
For instance, I have this as an array:
Using a selection box from a user form, I want to be able to pick item # 15 for instance (in column 1) and get a new array of just the rows that contain item # 15 (new array would be 3 rows by 9 columns).
any ideas how to do that? also allowing it to be dynamic since I want to be able to do this for different sets of Data. I'm not sure if it would be better to sort on two columns column 1 which is item # and the last column that corresponds to what sheet it is on.
Please try this code. It should be installed in a standard code module. Adjust the enumerations at the top to show where the data are (presumed to be at A2:I13). The code asks you to specify an Item to extract and will print the extracted data to an area 5 rows below the original.
Option Explicit
Enum Nws ' worksheet navigation
' modify as required
NwsFirstDataRow = 2
' columns and Array elements:-
NwsItm = 1 ' indicate column A
NwsTab = 9 ' indicate column I
End Enum
Sub Test_DataSelection()
Dim Ws As Worksheet
Dim Rng As Range
Dim Arr As Variant
Dim Itm As String
Set Ws = ThisWorkbook.Worksheets("Sheet1") ' modify as required
With Ws
Set Rng = .Range(.Cells(NwsFirstDataRow, NwsItm), _
.Cells(.Rows.Count, NwsTab).End(xlUp))
End With
Arr = Rng.Value
Itm = InputBox("Enter a valid Item number", "Select data", 5)
Arr = SelectedData(Itm, Arr)
With Ws ' may specify another sheet here
Set Rng = .Cells(.Rows.Count, NwsItm).End(xlUp).Offset(5)
Rng.Resize(UBound(Arr), UBound(Arr, 2)).Value = Arr
End With
End Sub
Function SelectedData(ByVal Itm As Variant, _
Arr As Variant) As Variant()
' Variatus #STO 21 Jan 2020
Dim Fun() As Variant
Dim Ub As Long
Dim i As Long
Dim R As Long, C As Long
On Error Resume Next
Ub = UBound(Arr)
If Err.Number = 0 Then
On Error GoTo 0
Itm = Val(Itm)
ReDim Fun(1 To UBound(Arr, 2), 1 To Ub)
For R = 1 To Ub
If Arr(R, 1) = Itm Then
i = i + 1
For C = 1 To UBound(Arr, 2)
Fun(C, i) = Arr(R, C)
Next C
End If
Next R
ReDim Preserve Fun(1 To UBound(Fun), 1 To i)
End If
SelectedData = Application.Transpose(Fun)
End Function
HerLow
A basic idea that you might be able to adapt to your needs…..to answer this …. I want to be able to pick item # 15 for instance (in column 1) and get a new array of just the rows that contain item # 15 (new array would be 3 rows by 9 columns).
Option Explicit
Sub ArrayBasedOnRowSelection()
Dim WsList As Worksheet, WsOut As Worksheet
Set WsList = ThisWorkbook.Worksheets("List"): Set WsOut = ThisWorkbook.Worksheets("Output")
Dim arrIn() As Variant, arrOut() As Variant
Let arrIn() = WsList.UsedRange
Dim Cnt As Long, strRws As String
For Cnt = 2 To WsList.UsedRange.Rows.Count
If arrIn(Cnt, 1) = "15" Then
Let strRws = strRws & Cnt & " "
Else
End If
Next Cnt
Let strRws = Left$(strRws, Len(strRws) - 1)
Dim SptStr() As String: Let SptStr() = Split(strRws, " ", -1, vbBinaryCompare)
Dim RwsT() As String: ReDim RwsT(1 To UBound(SptStr()) + 1, 1 To 1)
For Cnt = 1 To UBound(SptStr()) + 1
Let RwsT(Cnt, 1) = SptStr(Cnt - 1)
Next Cnt
Dim Clms() As Variant: Let Clms() = Evaluate("=Column(A:" & CL(WsList.UsedRange.Columns.Count) & ")") ' Evaluate("=Column(A:I)")
Let arrOut() = Application.Index(arrIn(), RwsT(), Clms())
WsOut.Cells.Clear
Let WsOut.Range("A2").Resize(UBound(arrOut(), 1), WsList.UsedRange.Columns.Count).Value = arrOut
End Sub
' http://www.excelfox.com/forum/showthread.php/1902-Function-Code-for-getting-Column-Letter-from-Column-Number?p=8824&viewfull=1#post8824
Public Function CL(ByVal lclm As Long) As String ' http://www.excelforum.com/development-testing-forum/1101544-thread-post-appendix-no-reply-needed-please-do-not-delete-thanks-4.html#post4213980
Do: Let CL = Chr(65 + (((lclm - 1) Mod 26))) & CL: Let lclm = (lclm - (1)) \ 26: Loop While lclm > 0
End Function
If you run that macro , it will paste out an array 3 rows by as many columns as your used range in worksheet “List”, based on the selection 15 from column 1.
File: ArrayfromRowsBasedOnPreviousArray.xlsm : https://app.box.com/s/h9ipfz2ngskjn1ygitu4zkqr1puuzba1
Explanation : https://www.excelforum.com/excel-new-users-basics/1099995-application-index-with-look-up-rows-and-columns-arguments-as-vba-arrays.html#post4571172
Alan

Run through multidimensional array and check data against a worksheet

Got an array which gets records the color of cells plus the ID associated with that row, which is specific to that row.
Now I want to run through the array picking up the ID and then using that to compare to another sheet (using the ID) to see if the color of the cell has changed.
I have tried to do this in a "hack" kind of way but I don't know how to step through each array record and pickup the ID extra to check.
Sub FindColourChange()
'this first bit is getting the data and putting in array
Dim newSheet As Worksheet
Dim r As Integer
Dim c As Integer
Set newSheet = ThisWorkbook.Worksheets("Combine")
intRowsNew = newSheet.UsedRange.Rows.Count
Dim newColourArray()
ReDim Preserve newColourArray(2 To intRowsNew, 7 To 19)
For r = 2 To intRowsNew ' this is the number of rows in your range
newColourArray(r, 7) = newSheet.Cells(r, 1).Value
Debug.Print "New is " & newColourArray(r, 7) & ", "
For c = 8 To 19
newColourArray(r, c) = newSheet.Cells(r, c).Interior.ColorIndex
Debug.Print "Colour of new is " & newColourArray(r, c) & ", "
Next
Next
'HERE IS WHERE I AM HAVING ISSUES - TRYING TO GET THE DATA FROM ARRAY TO COMPARE TO THE "Old Data" SHEET but cant figure a way out to go through each individual array record and get the first column value...
Dim result As String
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Sheets("Old Data")
Dim currentRow As Integer
'result = Application.VLookup(newColourArray(r, 1), sheet.Range("A:S"), 8, False)
Sheets("Combine").Select
For r = 2 To newColourArray
Columns("A:A").Select
Selection.Find(What:=newColourArray(r, 7), After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
currentRow = ActiveCell.Row
For c = 8 To 19
If newColourArray(r, c) <> oldSheet.Cells(currentRow, c).Interior.ColorIndex Then
Sheets("Combine").Select
End If
Next
Next
End Sub
Thanks for posting as a new question. I was thinking about it yesterday and the code below might do the trick for you:
Private Const ID_COLUMN As Integer = 1
Private Const FIRST_VALUE_COLUMN As Integer = 8
Private Const LAST_VALUE_COLUMN As Integer = 19
Private Type RowFields
ItemID As Variant
ColourOfValues(LAST_VALUE_COLUMN - _
FIRST_VALUE_COLUMN) As Variant
SheetRow As Long
End Type
Private mOldSheet As Worksheet
Private mNewSheet As Worksheet
Private mOldRowFields() As RowFields
Private mNewRowFields() As RowFields
Sub RunMe()
Set mOldSheet = ThisWorkbook.Worksheets("Old Data")
Set mNewSheet = ThisWorkbook.Worksheets("Combine")
' Read the desired values
ReadIDsColoursAndValues
' Acquire the cells where there's a colour change
AcquireColourChanges
End Sub
Private Sub ReadIDsColoursAndValues()
Dim firstRow As Integer
Dim lastRow As Integer
Dim r As Long
Dim c As Integer
Dim rowIndex As Long
Dim valueIndex As Integer
' ------------------
' Read the old sheet
' ------------------
' Define the row range
firstRow = 2 ' change this if different
lastRow = mOldSheet.Cells(mOldSheet.Rows.Count, 1).End(xlUp).Row
' Redimension the RowFields array
ReDim mOldRowFields(lastRow - firstRow) ' adjust if not zero-based
' Iterate through the rows to acquire data
For r = firstRow To lastRow
' Populate the row fields object
rowIndex = r - firstRow ' adjust if not zero-based
With mOldRowFields(rowIndex)
.ItemID = mOldSheet.Cells(r, ID_COLUMN).Value2
.SheetRow = r
' Iterate through the columns to acquire the colours
For c = FIRST_VALUE_COLUMN To LAST_VALUE_COLUMN
valueIndex = c - FIRST_VALUE_COLUMN ' adjust if not zero-based
.ColourOfValues(valueIndex) = _
mOldSheet.Cells(r, c).Interior.ColorIndex
Next
End With
Next
' ------------------
' Read the new sheet
' ------------------
' Define the row range
firstRow = 2 ' change this if different
lastRow = mNewSheet.Cells(mNewSheet.Rows.Count, 1).End(xlUp).Row
' Redimension the RowFields array
ReDim mNewRowFields(lastRow - firstRow) ' adjust if not zero-based
' Iterate through the rows to acquire data
For r = firstRow To lastRow
' Populate the row fields object
rowIndex = r - firstRow ' adjust if not zero-based
With mNewRowFields(rowIndex)
.ItemID = mNewSheet.Cells(r, ID_COLUMN).Value2
.SheetRow = r
' Iterate through the columns to acquire the colours
For c = FIRST_VALUE_COLUMN To LAST_VALUE_COLUMN
valueIndex = c - FIRST_VALUE_COLUMN ' adjust if not zero-based
.ColourOfValues(valueIndex) = _
mNewSheet.Cells(r, c).Interior.ColorIndex
Next
End With
Next
End Sub
Private Sub AcquireColourChanges()
Dim rowIndex As Long
Dim refIndex As Long
Dim rowItem As RowFields
Dim refItem As RowFields
Dim valueIndex As Integer
Dim sheetColumn As Integer
Dim highlightCells As Range
Dim cell As Range
For rowIndex = LBound(mNewRowFields, 1) To UBound(mNewRowFields, 1)
rowItem = mNewRowFields(rowIndex)
' Find the matching ID RowFields from old sheet
For refIndex = LBound(mOldRowFields, 1) To UBound(mOldRowFields, 1)
refItem = mOldRowFields(refIndex)
If rowItem.ItemID = refItem.ItemID Then
' Check each value colour against the old row
For valueIndex = LBound(rowItem.ColourOfValues, 1) To _
UBound(rowItem.ColourOfValues, 1)
If rowItem.ColourOfValues(valueIndex) <> _
refItem.ColourOfValues(valueIndex) Then
' Small piece of code to highligh the cells.
' You can do anything you like at this point.
sheetColumn = valueIndex + FIRST_VALUE_COLUMN ' adjust if not zero-based
Set cell = mNewSheet.Cells(rowItem.SheetRow, sheetColumn)
If highlightCells Is Nothing Then
Set highlightCells = cell
Else
Set highlightCells = Union(highlightCells, cell)
End If
End If
Next
' ID was found so we can break the search loop
Exit For
End If
Next
Next
mNewSheet.Activate
If highlightCells Is Nothing Then
MsgBox "No values have different colours."
Else
highlightCells.Select
MsgBox "The different coloured values have been highlighted." & vbCrLf & vbCrLf & _
highlightCells.Address(False, False)
End If
End Sub

Resources