I was going through some excel files today to do some harddrive clean up and I stumbled across an older excel file from last year that I made in response to a question from a member here.
The member asked if I knew of a faster way to have excel calculate 100k cells of SUMIFS formulas.
The formula in question is: =SUMIFS($D$2:$D$100001,$A$2:$A$100001,A2,$B$2:$B$100001,B2,$C$2:$C$100001,C2)
Column Data to be used:
Column A (Criteria 1) contains random single letters in the range A2:A100,001
Column B (Criteria 2) contains a string that is the Row() & Column() repeated after row 21 in the range B2:B100,001
Column C (Criteria 3) contains randomly generated numbers from 1 to 100 in the range C2:C100,001
Column D (Range to sum) contains randomly generated numbers from 1 to 10 in the range D2:D100,001
If you were to fill-down the formula from E2 to E100,001 it takes excel several minutes to calculate the results. My old laptop took over 12 1/2 minutes to complete it with the following code when I applied it to column F:
The member that mentioned the challenge to me said that a solution had been offered by Tim Williams over at StackOverflow that spit out the results in under 1 second:
I was just wondering if anyone here wanted to take a stab at the challenge to see if they could come up with any other solutions that may be faster/as fast/almost as fast.
I will offer up some code to set up a test file that you can play with if you want to take a stab at it.
The member asked if I knew of a faster way to have excel calculate 100k cells of SUMIFS formulas.
The formula in question is: =SUMIFS($D$2:$D$100001,$A$2:$A$100001,A2,$B$2:$B$100001,B2,$C$2:$C$100001,C2)
Column Data to be used:
Column A (Criteria 1) contains random single letters in the range A2:A100,001
Column B (Criteria 2) contains a string that is the Row() & Column() repeated after row 21 in the range B2:B100,001
Column C (Criteria 3) contains randomly generated numbers from 1 to 100 in the range C2:C100,001
Column D (Range to sum) contains randomly generated numbers from 1 to 10 in the range D2:D100,001
If you were to fill-down the formula from E2 to E100,001 it takes excel several minutes to calculate the results. My old laptop took over 12 1/2 minutes to complete it with the following code when I applied it to column F:
VBA Code:
Application.ScreenUpdating = False
Range("F2:F" & Range("A" & Rows.Count).End(xlUp).Row).Formula = "=SUMIFS(R2C4:R100001C4,R2C1:R100001C1,RC[-5],R2C2:R100001C2,RC[-4],R2C3:R100001C3,RC[-3])"
Application.ScreenUpdating = True
The member that mentioned the challenge to me said that a solution had been offered by Tim Williams over at StackOverflow that spit out the results in under 1 second:
VBA Code:
Option Explicit
Sub SumCountAvg()
Dim arr, ws, dict, arrOut, arrValues, v, tmp, t, keyCols
Dim n As Long, i As Long, valueCol As Long, destCol As Long
Dim str As String, sep As String, rng As Range
keyCols = Array(1, 2, 3) [I]'column(s) for the dictionary key[/I]
valueCol = 4 [I]'column with values (for sum)[/I]
destCol = 5 [I]'destination for calculated values[/I]
t = Timer
Set ws = ActiveSheet
Set rng = ws.Range("A1").CurrentRegion
n = rng.Rows.Count - 1
Set rng = rng.Offset(1, 0).Resize(n) [I]'exclude headers[/I]
[I]'build the formula to create the dictionary key[/I]
For i = 0 To UBound(keyCols)
str = str & sep & rng.Columns(keyCols(i)).Address
sep = "&""|""&"
Next i
arr = ws.Evaluate(str) [I]'get an array of keys by evaluating the formula[/I]
arrValues = rng.Columns(valueCol).Value [I]'values to be summed[/I]
ReDim arrOut(1 To n, 1 To 1) [I]'output array[/I]
Set dict = CreateObject("scripting.dictionary")
[I]'first loop over the array counts the keys[/I]
For i = 1 To n
v = arr(i, 1)
If Not dict.exists(v) Then dict(v) = Array(0, 0) [I]'count, sum[/I]
tmp = dict(v) [I]'can't modify an array stored in a dictionary - pull it out first[/I]
tmp(0) = tmp(0) + 1 [I]'increment count[/I]
tmp(1) = tmp(1) + arrValues(i, 1) [I]'increment sum[/I]
dict(v) = tmp [I]'return the modified array[/I]
Next i
[I]'second loop populates the output array from the dictionary[/I]
For i = 1 To n
arrOut(i, 1) = dict(arr(i, 1))(1) [I] [/I] [I]'sumifs[/I]
'arrOut(i, 1) = dict(arr(i, 1))(0) [I] [/I] [I] [/I] [I]'cou[/I]n[I]tifs[/I]
'arrOut(i, 1) = dict(arr(i, 1))(1) / dict(arr(i, 1))(0) [I]'averageifs[/I]
Next i
[I]'populate the results[/I]
rng.Columns(destCol).Value = arrOut
MsgBox "Checked " & n & " rows in " & Timer - t & " seconds"[/B]
[B]End Sub
I was just wondering if anyone here wanted to take a stab at the challenge to see if they could come up with any other solutions that may be faster/as fast/almost as fast.
I will offer up some code to set up a test file that you can play with if you want to take a stab at it.
VBA Code:
Sub GenerateRandomInfoColumnsA_Thru_D()
'
Dim HeaderArray As Variant
'
Application.ScreenUpdating = False ' Turn ScreenUpdating off
Application.Calculation = xlManual ' Turn Calculation mode to manual
'
HeaderArray = Array("Criteria 1", "Criteria 2", "Criteria 3", "Value Range To Sum", "Results") ' Create Array of Headers
'
Sheets("Sheet1").UsedRange.Clear ' Clear the sheet to be used
'
Sheets("Sheet1").Range("A1").Resize(, UBound(HeaderArray) + 1) = HeaderArray ' Display Header to Sheet
'
' ColumnARandomLetters
With Sheets("Sheet1").Range("A2:A100001") ' With Range("A2:A100001") ...
.Formula = "=CHAR(RANDBETWEEN(97,122))" ' Generate lowercase letters a - z
.Value = .Value ' Remove formulas, leave values
End With
'
' Column_B_RowNumberColumnNumber
With Sheets("Sheet1").Range("B2:B100001") ' With Range("B2:B100001") ...
.Formula = "=Row() & ""2""" ' Save Row# & '2' to cell
.Value = .Value ' Remove formulas, leave values
End With
'
' ColumnCRandomNumbers1to100
With Sheets("Sheet1").Range("C2:C100001") ' With Range("C2:C100001") ...
.Formula = "=RANDBETWEEN(1,100)" ' Generate random whole number between 1 - 100
.Value = .Value ' Remove formulas, leave values
End With
'
' ColumnDRandomNumbers1to10
With Sheets("Sheet1").Range("D2:D100001") ' With Range("D2:D100001") ...
.Formula = "=RANDBETWEEN(1,10)" ' Generate random whole number between 1 - 10
.Value = .Value ' Remove formulas, leave values
End With
'
' Set Columns B Thru D to text
Sheets("Sheet1").Columns("B:D").NumberFormat = "@" ' Set Columns B:D to text
'
Sheets("Sheet1").Columns.AutoFit
'
Application.Calculation = xlAutomatic ' Turn Calculation back on
Application.ScreenUpdating = True ' Turn ScreenUpdating back on
End Sub