5

Core Question

How can I perform repeated CountIf()s on a range as efficiently (performance-wise) as possible?


Concerns in Detail

Range to Array

Since each read/write to a spreadsheet results in slower VBA code, it is advisable to do so as few times as possible. Normally, if someone is repeatedly reading/writing to a range, he or she should first save that range to an array, perform the operations to the array, and then do a final read or write to the spreadsheet if necessary.


Example Values and Code

Example Values

How can I use perform CountIf()s on the range of A2:A11 above to calculate the count of each value and write them to D2:D7? I would expect the below code to work:

Sub M1ArrayCount()

Dim arrNumbers() As Variant
Dim Long1 As Long
Dim Loop1 As Long

arrNumbers() = ThisWorkbook.Sheets(1).Range("A2:A11").Value

With ThisWorkbook.Sheets(1)
    For Loop1 = 1 To 6
        .Cells(Loop1 + 1, 4).Value = Application.CountIf(arrNumbers(), Loop1)
    Next Loop1
End With

End Sub

CountIf() Doesn't Work with Arrays

However, because Application.CountIf() only works with ranges and not arrays, D2:D7 all show #VALUE! errors after running the above code. A substitute must be found.

Teamothy
  • 2,000
  • 3
  • 16
  • 26
puzzlepiece87
  • 1,537
  • 2
  • 19
  • 36
  • The answer to this will depend on how big your data set is. How many rows in"Range To Test" and how many rows in "Values"? – chris neilsen May 11 '16 at 23:46
  • That's true - I intend this answer for large data sets and didn't specifically say so. My own data set was 90,000 rows. – puzzlepiece87 May 12 '16 at 02:45

2 Answers2

10

Solution - Count(Match())

The solution we are looking for is:

Application.Count(Application.Match(SavedArray(), Array([lookup_value]), 0))


What? How does that work? --- How can a function that normally returns a row number be paired with an array and count to return the correct answer? How can you count row numbers?

Explanation of Mechanics

With many thanks to @Jeeped, here's how it works:

It's an undocumented feature of Match(lookup value, lookup array, 0) that if you put an array as the lookup value, it will Match() each value in the array you entered against the lookup array. Thus, for the above example, for Loop = 1, it will become:

{match(A2, Array("1"), 0),match(A3, Array("1"), 0), ... match(A11, Array("1"), 0)}

Then, per @Jeeped:

Each match will either return a number or an error. The Count() function counts numbers, not errors. So you get a count of whether any of the values in A1:A11 match Loop1.

Final Code and Values

Sub M1ArrayCount()

Dim arrNumbers() As Variant
Dim Long1 As Long
Dim Loop1 As Long

arrNumbers() = ThisWorkbook.Sheets(1).Range("A2:A11").Value

With ThisWorkbook.Sheets(1)
    For Loop1 = 1 To 6
        .Cells(Loop1 + 1, 4).Value = Application.Count(Application.Match(arrNumbers(), Array(Loop1), 0))
    Next Loop1
End With

End Sub

Final Results


References --- This comment

This answer

Question

Example:

myarray = array("First","Second","Second","Third","Fourth")

then what would the countif(myarray,"second") syntax be? (the result should equal 2 counts)

Answer

Try also:

MsgBox Application.Count(Application.Match(myArray, Array("Second"), 0))

This chat

Community
  • 1
  • 1
puzzlepiece87
  • 1,537
  • 2
  • 19
  • 36
  • http://chat.stackoverflow.com/transcript/message/30485994#30485994 Also from @Jeeped: "I finally found some time to bench test that method a little more thoroughly and I have found a limitation. Using the parameters backwards limits the array as the lookup_value to 255 elements. This makes it prohibitive to use as an array-based substitution to countif in large blocks of data." – puzzlepiece87 May 12 '16 at 14:14
  • Nice; came across your interesting approach by chance +1) – T.M. Jan 08 '19 at 20:02
  • Beautiful solution. If you get an error, makes sure you are using `Application.Match` and **not** `Application.WorksheetFunction.Match`. The former won't work, because it throws a VBA error for the none-matches as I found out here: https://stackoverflow.com/a/27302888/12418842 – Michael Wycisk Jan 20 '22 at 14:00
0
Dim rowin as integer 'this is a counter for the row index
For rowin = 2 To Sheets("Sheet1").UsedRange.Rows.Count
Sheets("Sheet1").Range("D" & rowin).Value = WorksheetFunction.CountIf(Range("A:A"), Range("C" & rowin))
Next
Guest
  • 1
  • 1