VBA counting number of occurrences in a list of strings - vba

I have a list of 1000+ names in a single column in excel where the names repeat occasionally. I am trying to count how many times each name occurs. This is what I have currently and it populates the desired sheet but it seems to mess up when counting the number of times the names show up. Anything helps!
m = 2
n = 1
person = Worksheets("Sheet1").Cells(m, 6).Value
Worksheets("Sorted_Data").Cells(n, 2).Value = person
Worksheets("Sorted_Data").Cells(n, 3).Value = 1
n = n + 1
m = m + 1
For i = 0 To Total_Tickets
person = Worksheets("Sheet1").Cells(m, 6).Value
y = 1
d = 0
Do While d <= i
comp = Worksheets("Sorted_Data").Cells(y, 2).Value
x = StrComp(person, comp, vbTextCompare)
If x = 0 Then
Worksheets("Sorted_Data").Cells(n - 1, 3).Value = Worksheets("Sorted_Data").Cells(n - 1, 3).Value + 1
m = m + 1
d = 10000
ElseIf x = 1 Or x = -1 Then
If comp = "" Then
Worksheets("Sorted_Data").Cells(n, 2).Value = person
Worksheets("Sorted_Data").Cells(n, 3).Value = 1
n = n + 1
m = m + 1
d = 10000
End If
y = y + 1
d = d + 1
End If
Loop
Next i

You're managing a lot of counters there, and that makes the logic more difficult to follow.
You could consider something like this instead:
Sub Tester()
Dim wsData As Worksheet, wsList As Worksheet, arr, m, i As Long, nm
Set wsData = ThisWorkbook.Sheets("Sheet1")
Set wsList = ThisWorkbook.Sheets("Sorted_Data")
'grab all the names in an array
arr = wsData.Range("A2:A" & wsData.Cells(Rows.Count, "A").End(xlUp).Row).Value
For i = 1 To UBound(arr, 1) 'loop over the array
nm = arr(i, 1) 'grab the name
m = Application.Match(nm, wsList.Columns("A"), 0) 'existing name on the summary sheet?
If IsError(m) Then
'name was not found: add it to the summary sheet
With wsList.Cells(Rows.Count, "A").End(xlUp).Offset(1)
.Value = nm
m = .Row
End With
End If
With wsList.Cells(m, "B")
.Value = .Value + 1 'update the count
End With
Next i
End Sub

Related

VBA - If two of three cells are true

I am trying to construct and If statement that turns a tab Red if two of three cells are colored, or Turns green if only on is colored. I was hoping that there would be an easier way to right it than three if statements like this.
Dim dateRng As String, num As Integer, j As Integer, irng As Range, frng As Range
dateRng = Sheets("Input Raw Data").Range("B" & counter + 2).Value
num = Sheets("Tool Setup").Range("C18").Value
NumPts = num * 3
For s = 1 To Sheets.Count
With Sheets(s)
For j = 1 To num
If .Name = j Then
.Range("A1:C1").Merge
.Range("A1") = dateRng
.Name = Sheets("Point Names").Range("B" & (3 * j - 1))
End If
Next j
End With
Next s
For s = 1 to Sheets.Count
With Sheets(s)
For y = 1 To NumPts
If .Name = Sheets("Reporting").Range("B" & (12 * y - 5)) Then
For k = 6 To -1
Set irng = Sheets("Reporting").Range("A" & (12 * y - k))
Set irng = Sheets("Reporting").Range(irng, irng.End(xlToRight).End(xlToRight))
irng.Copy (.Range("A2"))
Next k
.Columns("A:A").ColumnWidth = 12
.Columns("B:B").EntireColumn.AutoFit
If .Range("B7").Interior.ColorIndex > 0 Then
a = 1
End If
If .Range("B8").Interior.ColorIndex > 0 Then
a = a + 1
End If
If .Range("B9").Interior.ColorIndex > 0 Then
a = a + 1
End If
If a >= 2 Then
.Tab.ColorIndex = 3
ElseIf a <= 1 Then
.Tab.ColorIndex = 4
End If
End If
y = y + 2
Next y
End With
Next s
Something like this may help you. It still has multiple if statements. But the statements are simple and don't have to deal with how the combinations of different cells being colored.
Also, I used colorindex > 0 as the condition for having color filling.
a = 0
If .Range("B7").Interior.ColorIndex > 0 Then
a = 1
End If
If .Range("B8").Interior.ColorIndex > 0 Then
a = a + 1
End If
If .Range("B9").Interior.ColorIndex > 0 Then
a = a + 1
End If
If a = 2 Then
.Range("B10").Interior.ColorIndex = 3
ElseIf a = 1 Then
.Range("B10").Interior.ColorIndex = 43
End If

excel vba loop max value and location more sheets

I need the location of max value (one column) or address, so i can locate two cells left of the max value cell. next is finding the higher value of the two new cells and dividing the higher value with the max value. last step is returning the value to sheet "List1". that s the basic logic :)
thx for any help
the locating of max value and locating cells left of it, that is my main concern.
i cant figure it out. been looking for it but cant get it to work.
Sub DoIt()
'ONE MAIN SHEET (List1)
'MORE SECONDARY SHEETS WHERE DATA FOR MAX VALUE IS
Dim strSheet As String
Dim rng As Range
Dim dblMax As Double
Dim r As Range
i = 4
j = 8
g = 4
h = 20
s = 22
Dim cell As Range
Dim col, row
Do While Cells(i, j).Value <> ""
strSheet = Sheets(1).Cells(i, j)
Sheets(strSheet).Activate
'w = 2
'e = 27
'a = 2
'Do While Cells(a, s).Value >= "0"
'Range("AA1") = "IT WORKS"
'Cells(w, e) = Cells(a, s).Value
'a = a + 1
'w = w + 1
'Loop
Set rng = Sheets(strSheet).Range("V2:V8761")
dblMax = Application.WorksheetFunction.Max(rng)
'CODE FOR MAX VALUE LOCATION
'LOCATING TWO LEFT CELLS OF LOCATION MAX VALUE CELL
'DETERMINING THE HIGHER VALUE
'DIVIDING
Range("Z2") = dblMax 'CONTROL
i = i + 1
Range("Z1") = "IT WORKS" 'CONTROL
Sheets("List1").Activate
Cells(g, h) = "AAA" 'result of higher value cell by max value cell
g = g + 1
Loop
End Sub
thx for help i did it. code is not refind. here is the code:
Sub DoIt()
Dim strSheet As String
Dim rng As Range
Dim dblMax As Double
Dim r As Range
Dim dely As Double
i = 4
j = 8
g = 4
h = 20
l = 21
s = 22
Dim cell As Range
Dim col, row
Do While Cells(i, j).Value <> ""
strSheet = Sheets(1).Cells(i, j)
Sheets(strSheet).Activate 'error on no strsheet
w = 2
e = 27
a = 2
sumall = Application.Sum(Range("v2:v9000"))
'Do While Cells(a, s).Value >= "0"
'Range("AA1") = "nekaj dela"
'Cells(w, e) = Cells(a, s).Value
'a = a + 1
'w = w + 1
'Loop
Set rng = Sheets(strSheet).Range("V2:V9000")
dblMax = Application.WorksheetFunction.Max(rng)
mmm = Application.WorksheetFunction.Match(dblMax, Sheets(strSheet).Range("v:v"), 0)
positionRange = Sheets(strSheet).Range("v:v")
'iii = Application.WorksheetFunction.Index(positionRange, mmm)
'ooo = mmm.Offset(0, -1)
sum1 = Cells(mmm, 12)
sum2 = Cells(mmm, 21)
If sum1 > sum2 Then
'sumall = Application.Sum(Range("l2:l8761"))
PLDP = sumall / 365
dely = sum1 / PLDP * 100
smer = "1"
Else
'sumall = Application.Sum(Range("u2:u8761"))
PLDP = sumall / 365
dely = sum2 / PLDP * 100
smer = "2"
End If
'Range("AA2") = iii
Range("AB2") = sum1
Range("AC2") = sum2
Range("ad2") = dely
Range("ae2") = sumall
Range("af2") = PLDP
Range("Z2") = dblMax 'test cell
i = i + 1
Range("Z1") = "IT WORKS"
Sheets("List1").Activate
Cells(g, h) = smer
Cells(g, l) = dely
g = g + 1
Loop
End Sub

Looping & Adding Values

I am trying to run a loop that looks for a value in a column and if it is found then another value is entered in the cell to the right of it.
m = 2
h = 1
Cells(m, 23).Select
Do
Cells(m, 23).Select
If ActiveCells <> " " Then
Cells(m, 24) = "Test"
End If
If InStr(Cells(m, 24).Text, "-") Then
h = h + 1
End If
m = m + 1
What I am finding is the script runs and does not seem to identify when the cell contains the word "Region". It is just skipping over as if the cell is empty.
Still pretty new to VBA's so this may or may not be an easy fix.
Thank you!
Try this:
Dim lr As Long
Dim m As Long: m = 2
Dim h As Long: h = 1
'Properly reference objects
With Sheets("YourActualSheetName")
'To add better control, identify boundaries of your loop,
'so find the last row that contain data.
lr = .Columns(23).Find(What:="*", _
After:=.Cells(1, 23), _
SearchDirection:=xlPrevious).Row
Do
'You can use one liner If's for some cases like this one
If .Cells(m, 23) = "Region" Then .Cells(m, 24) = "Type"
If InStr(.Cells(m, 24), "-") <> 0 Then h = h + 1
m = m + 1
Loop Until m > lr
End With
Edit1: As stated in the comments
Dim m As Long: m = 2
Dim h As Long: h = 1
With Sheets("YourActualSheetName")
Do
If .Cells(m, 23) = "Region" Then .Cells(m, 24) = "Type"
If InStr(.Cells(m, 24), "-") <> 0 Then h = h + 1
m = m + 1
Loop Until h = 9
End With

How to get average by cell content on VBA

In excel, I have column 1 with tickers, and column 2 with numbers, like this:
A B
1 AAA 10
2 AAA 12
3 AAA 14
4 BBB 9
5 BBB 10
6 BBB 11
I need a piece of code to calculate average BY TICKER, which means that in this case I would have AAA average : 12 and BBB average = 10, etc etc etc. Up to now all i got is this code which tries to calculate the sums, I will do the divisions later, but something's wrong:
For row = 2 to 6
Ticker = Cells(row - 1, 1)
If Cells(row, 1) = Cells(row - 1, 1) Then
sum = sum + Cells(row, 2)
Else
Cells(row, 6) = sum
sum = 0
row = row + 1
Next
I get an error saying "For is missing"
Maybe something like this in C1.
=IF(A1<>A2,AVERAGEIF(A:A,A1,B:B),"")
        
In your code:
sum ignored the first AAA
missed the End If
incremented row twice, once by row = row+1 and then by next
and some other not used variables
Try this:
Prev = "***"
For row = 1 to 6
If Cells(row, 1) = prev Then
sum = sum + Cells(row, 2)
Else
Cells(row, 6) = sum
sum = 0
End If
prev = Cells(row,1)
Next
To extend the answer from Jeeped and do it with a list which is not ordered, you also could do it like this:
C1: =A1
C2: =IF(LEN(C1),IFERROR(INDEX(A:A,MATCH(1,(COUNTIF(C$1:C1,A$1:A$1000)=0)*(A$1:A$1000<>""),0)),""),"")}
C3....Cn: copy down from C2
D1: =IF(LEN(C1),AVERAGEIF(A:A,C1,B:B),"")
D2...Dn: copy down from D1
C2 is an array formula and needs to be confirmed with Ctrl+Shift+Enter
to do it via VBA (should be faster than my formula for really big tables) you can use something like that: (put this in a "Module" in the VBA-Window, the same like your recorded macros ar written)
Option Explicit
Public Function getAllAvg(rng As Range) As Variant
Set rng = Intersect(rng.Parent.UsedRange, rng)
Dim varInput As Variant
varInput = rng.Value
Dim varOutput() As Variant
ReDim varOutput(1 To UBound(varInput), 1 To 2)
varOutput(1, 1) = ""
Dim i As Long, j As Long
For i = 1 To UBound(varInput)
If Len(varInput(i, 1)) Then
j = 1
While Len(varOutput(j, 1)) And (varOutput(j, 1) <> varInput(i, 1)) And (j < UBound(varOutput))
j = j + 1
Wend
If Len(varOutput(j, 1)) = 0 Then
varOutput(j, 1) = varInput(i, 1)
varOutput(j, 2) = Application.AverageIf(rng.Columns(1), varOutput(j, 1), rng.Columns(2))
varOutput(j + 1, 1) = ""
End If
End If
Next
While Len(varOutput(j, 1)) And (j < UBound(varOutput))
j = j + 1
Wend
If Len(varOutput(j, 1)) = 0 Then
For i = j To UBound(varOutput)
varOutput(i, 1) = ""
varOutput(i, 2) = ""
Next
End If
getAllAvg = varOutput
End Function
then select a range like C2:D12 and enter:
=getAllAvg(A:B)
and confirm with Ctrl+Shift+Enter. it will directly output the whole list (and recalculate if needed)
EDIT:
If your list is always in a sorted order, you also could use this code:
Option Explicit
Public Function getAllAvgSorted(rng As Range) As Variant
Set rng = Intersect(rng.Parent.UsedRange, rng)
Dim varInput As Variant
varInput = rng.Value
Dim varOutput() As Variant
ReDim varOutput(1 To UBound(varInput), 1 To 2)
varOutput(1, 1) = ""
Dim i As Long, j As Long
j = 1
For i = 1 To UBound(varInput)
If Len(varInput(i, 1)) Then
If varOutput(j, 1) <> varInput(i, 1) Then
If Len(varOutput(j, 1)) Then j = j + 1
varOutput(j, 1) = varInput(i, 1)
varOutput(j, 2) = Application.AverageIf(rng.Columns(1), varOutput(j, 1), rng.Columns(2))
End If
End If
Next
While j < UBound(varOutput)
j = j + 1
varOutput(j, 1) = ""
varOutput(j, 2) = ""
Wend
getAllAvgSorted = varOutput
End Function

Finding no. of time a value occurs with other combination

I have a column containing departments and another column containing Appt, Can, No show. I want to calculate how many time every department has occurred for Appt, Can and No Show. The code I am currently using extracts unique values of department and using If statements counts the value of Appt, Can and No Show.
Data Set:
http://bit.ly/1HkvAxR
Code to get unique departments:
Public Sub Getting_Unique_Departments()
Dim X
Dim objDict As Object
Dim lngRow As Long
If Len("E") > 0 And Len("Y") > 0 Then
Set objDict = CreateObject("Scripting.Dictionary")
X = Application.Transpose(Range("E" & 2, Cells(Rows.Count, "E").End(xlUp)))
For lngRow = 1 To UBound(X, 1)
objDict(X(lngRow)) = 1
Next
Range("Y" & 2 & ":" & "Y" & objDict.Count + 1) = Application.Transpose(objDict.keys)
End If
End Sub
Code to check Appt, Can, walk and No Show for each department.
Sub Calculation()
nName0 = "Department"
nName1 = "Appt"
nName2 = "Walk"
nName3 = "Can"
nName4 = "No Show"
Cells(1, 25).Value = nName0
Cells(1, 26).Value = nName1
Cells(1, 27).Value = nName2
Cells(1, 28).Value = nName3
Cells(1, 29).Value = nName4
For Dept_Row_number = 2 To Dept_lastRow
'Dept_lastRow finds last Row of unique department listed in Y col and Sheet_lastRow finds the last Row of input data sheet.
nCount1 = 0
nCount1 = 0
nCount2 = 0
nCount3 = 0
nCount4 = 0
Row_number = 1
search_string1 = ActiveSheet.Cells(Dept_Row_number, 25)
Do
DoEvents
Row_number = Row_number + 1
item_in_review1 = ActiveSheet.Cells(Row_number, 5).Value
item_in_review2 = ActiveSheet.Cells(Row_number, 3).Value
If InStr(item_in_review1, search_string1) > 0 And InStr(item_in_review2, "Appt") > 0 Then
nCount1 = nCount1 + 1
ElseIf InStr(item_in_review1, search_string1) > 0 And InStr(item_in_review2, "Walk") > 0 Then
nCount2 = nCount2 + 1
ElseIf InStr(item_in_review1, search_string1) > 0 And InStr(item_in_review2, "Can") > 0 Then
nCount3 = nCount3 + 1
ElseIf InStr(item_in_review1, search_string1) > 0 And InStr(item_in_review2, "No Show") > 0 Then
nCount4 = nCount4 + 1
End If
Loop Until Row_number = Sheet_lastRow
Cells(Dept_Row_number, 26).Value = nCount1
Cells(Dept_Row_number, 27).Value = nCount2
Cells(Dept_Row_number, 28).Value = nCount3
Cells(Dept_Row_number, 29).Value = nCount4
Next
Is there any easy way to this because in case I have to do this for more than one column, the code would be too cucumbersome.
Byron Wall is correct that Pivot Tables are a natural choice -- but you can also streamline the VBA. You are aware of dictionaries but could be exploiting them more. I recommend using early binding -- in Tools/References add a reference to Microsoft Scripting Runtime and then you can write code along the following lines. The main loop populates a dictionary keyed to departments. The values of this dictionary are themselves dictionaries keyed by your categories ("No Show", etc.). The values of those dictionaries are the counts that you are after. At the end of the code I show how you can extract data from this data structure:
Function MakeCountDict(categories As Variant) As Dictionary
Dim d As New Dictionary
Dim i As Long
For i = LBound(categories) To UBound(categories)
d.Add categories(i), 0
Next i
Set MakeCountDict = d
End Function
Sub MakeDepartmentCounts()
Dim Dcounts As New Dictionary
Dim R As Range
Dim dept As Variant, cat As String
Dim categories As Variant
Dim i As Long, n As Long
Dim report As String
categories = Array("No Show", "Appt", "Can", "walk")
n = Range("H:H").Rows.Count
n = Range("H" & n).End(xlUp).Row 'last used row in column H
For i = 2 To n
dept = Trim(Cells(i, "H").Value)
If Not Dcounts.Exists(dept) Then
Dcounts.Add dept, MakeCountDict(categories)
End If
cat = Trim(Cells(i, "C").Value)
Dcounts(dept)(cat) = Dcounts(dept)(cat) + 1
Next i
report = "Report:"
For Each dept In Dcounts.Keys
report = report & vbCrLf & dept & ": "
For i = 0 To 3
cat = categories(i)
report = report & cat & " = " & Dcounts(dept)(cat) & IIf(i < 3, ", ", "")
Next i
Next dept
MsgBox report
End Sub
To test it I created random data in columns C and H that had the format of your linked picture then ran it. My output:
Department 5: No Show = 1, Appt = 1, Can = 1, walk = 2
Department 3: No Show = 5, Appt = 2, Can = 1, walk = 2
Department 4: No Show = 2, Appt = 1, Can = 0, walk = 1
Department 2: No Show = 2, Appt = 1, Can = 2, walk = 1
Department 1: No Show = 1, Appt = 1, Can = 0, walk = 2
This shows that the order of keys when you iterate is a bit random -- but you could do something like have a for j = 1 to 5 loop rather than a for each dept in keys loop.