Reputation: 111
This is a screenshot of my data.
Dim dBT As Object 'global dictionary
Sub buttonpresscount()
'constants for column positions
Const COL_BLOCK As Long = 1
Const COL_TRIAL As Long = 2
Const COL_ACT As Long = 7
Const COL_AOI As Long = 8
Const COL_RT As Long = 16
Const COL_FT As Long = 17
Dim rng As Range, lastrow As Long, sht As Worksheet
Dim d, r As Long, k, resBT()
Set sht = Worksheets("full test")
lastrow = sht.Cells(Rows.Count, 3).End(xlUp).Row
Set dBT = CreateObject("scripting.dictionary")
Set rng = sht.Range("B7:T" & lastrow)
d = rng.Value 'get the data into an array
ReDim resBT(1 To UBound(d), 1 To 1) 'resize the array which will
' be placed in ColT
'get unique combinations of Block and Trial and pressedcounts for each
For r = 1 To UBound(d, 1)
k = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
dBT(k) = dBT(k) + IIf(d(r, COL_ACT) <> "", 1, 0)
Next r
'populate array with appropriate counts for each row
For r = 1 To UBound(d, 1)
k = d(r, 1) & "|" & d(r, 2) 'create key
resBT(r, 1) = dBT(k) 'get the count
Next r
'place array to sheet
sht.Range("T7").Resize(UBound(resBT, 1), 1) = resBT
'clear dictionary
dBT.RemoveAll
'count AOI entries
For r = 1 To UBound(d, 1)
k = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
If resBT(r, 1) = 1 Then 'only proceed with trials with 1 button press
dBT(k) = dBT(k) + IIf(d(r, COL_AOI) = "AOI Entry", 1, 0) 'get count
Else: dBT(k) = ""
End If
Next r
'populate array with appropriate counts for each row
For r = 1 To UBound(d, 1)
k = d(r, 1) & "|" & d(r, 2) 'create key
resBT(r, 1) = dBT(k) 'get the count
Next r
'place array to sheet
sht.Range("U7").Resize(UBound(resBT, 1), 1) = resBT
Call createsummarytable
Call PopSummaryAOI(dBT)
dBT.RemoveAll
'retrieve and print reaction times to data summary sheet
For r = 1 To UBound(d, 1)
If resBT(r, 1) <> "" Then 'if buttonpresscount = 1 and AOI count exists
k = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
dBT(k) = d(r, COL_RT)
End If
Next r
'Populate array with last row reaction time for each trial
For r = 1 To UBound(d, 1)
k = d(r, 1) & "|" & d(r, 2) 'create key
resBT(r, 1) = dBT(k) 'get the count
Next r
Call PopSummaryRT(dBT)
dBT.RemoveAll
'work out avg fixation time per trial
For r = 1 To UBound(d, 1)
If resBT(r, 1) <> "" Then
k = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
dBT(k) = Application.AverageIf(d(r, COL_FT), (d(r, COL_AOI) = "AOI Entry"))
End If
Next r
'populate array
For r = 1 To UBound(d, 1)
k = d(r, 1) & "|" & d(r, 2) 'create key
resBT(r, 1) = dBT(k) 'get the count
Next r
Call PopSummaryFT(dBT)
End Sub
Referring to the above macro, the following lines of code are meant to work out an average of values in Column R per dict(key) (read: per trial):
For r = 1 To UBound(d, 1)
If resBT(r, 1) <> "" Then
k = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
dBT(k) = Application.AverageIf(d(r, COL_FT), (d(r, COL_AOI) = "AOI Entry"))
End If
Next r
This is resulting in #VALUE!
being printed in the relevant cells, rather than the expected number.
What is causing this? What is the correct way to code this formula?
Upvotes: 1
Views: 328
Reputation: 23994
Your current issue with the line
dBT(k) = Application.AverageIf(d(r, COL_FT), (d(r, COL_AOI) = "AOI Entry"))
is due to the fact that you are trying to take the average of a single value, and only if that value is True
or False
. E.g. when r
is 1, your code equates to
dbt("Block 1|Trial, 8") = Application.AverageIf(-2484, ("" = "AOI Entry"))
or
dbt("Block 1|Trial, 8") = Application.AverageIf(-2484, False)
As none of the values in the average range (i.e. the value -2484
) matches the criteria (i.e. False
), the function attempts to divide the sum of matching values (i.e. 0
) by the count of matching values (i.e. 0
) and errors out.
Similarly, when r
is 2, the code equates to
dbt("Block 1|Trial, 1") = Application.AverageIf(31, ("AOI Entry" = "AOI Entry"))
or
dbt("Block 1|Trial, 1") = Application.AverageIf(31, True)
Again, 31
does not equal True
, and you end up trying to divide 0 by 0.
You would have got an answer (although not a meaningful answer) if you had used a formula of
dBT(k) = Application.AverageIf(d(r, COL_AOI), "AOI Entry", d(r, COL_FT))
which would have summed d(r, COL_FT)
(the average range) if d(r, COL_AOI)
(the range to test against the criteria) matched AOI Entry"
(the criteria). (Summing a single number is a bit pointless, but it would still have done it.) However, this would still have given a division by zero error when d(r, COL_AOI)
was not "AOI Entry"
, and would give a meaningless answer in the cases when it did work.
To get a meaningful average, you need to divide a sum of values by a count of values. Your code is not set up in a way that makes it easy to use Excel's built-in functions to calculate the sum and count, so you will need to calculate the sum and count yourself.
In the following code I have added two dictionaries (one called Sums
, and the other called Cnts
) to keep track of those numbers. The average can then be easily derived by dividing Sums(k)
by Cnts(k)
.
I have also taken the liberty of changing your variable k
to be an array. Your current code was calculating the key in at least 8 positions, so I changed it to calculate it once, and then use the same value in each other position.
Dim dBT As Object 'global dictionary
Sub buttonpresscount()
Dim Sums As Object
Dim Cnts As Object
'constants for column positions
Const COL_BLOCK As Long = 1
Const COL_TRIAL As Long = 2
Const COL_ACT As Long = 7
Const COL_AOI As Long = 8
Const COL_RT As Long = 16
Const COL_FT As Long = 17
Dim rng As Range, lastrow As Long, sht As Worksheet
Dim d, r As Long, resBT()
Dim k() As String
Set sht = Worksheets("full test")
lastrow = sht.Cells(Rows.Count, 3).End(xlUp).Row
Set dBT = CreateObject("scripting.dictionary")
Set Sums = CreateObject("scripting.dictionary")
Set Cnts = CreateObject("scripting.dictionary")
Set rng = sht.Range("B7:T" & lastrow)
d = rng.Value 'get the data into an array
ReDim resBT(1 To UBound(d), 1 To 1) 'resize the array which will
' be placed in ColT
ReDim k(1 To UBound(d, 1)) As String
'get unique combinations of Block and Trial and pressedcounts for each
For r = 1 To UBound(d, 1)
'Calculate the key once, then it can be used in every other loop
k(r) = d(r, COL_BLOCK) & "|" & d(r, COL_TRIAL) 'create key
dBT(k(r)) = dBT(k(r)) + IIf(d(r, COL_ACT) <> "", 1, 0)
Next r
'populate array with appropriate counts for each row
For r = 1 To UBound(d, 1)
resBT(r, 1) = dBT(k(r)) 'get the count
Next r
'place array to sheet
sht.Range("T7").Resize(UBound(resBT, 1), 1) = resBT
'clear dictionary
dBT.RemoveAll
'count AOI entries
For r = 1 To UBound(d, 1)
If resBT(r, 1) = 1 Then 'only proceed with trials with 1 button press
If d(r, COL_AOI) = "AOI Entry" Then
dBT(k(r)) = dBT(k(r)) + 1 'get count
Cnts(k(r)) = Cnts(k(r)) + 1 'get count
Sums(k(r)) = Sums(k(r)) + d(r, COL_FT) 'sum column R
End If
Else
dBT(k(r)) = ""
End If
Next r
'populate array with appropriate counts for each row
For r = 1 To UBound(d, 1)
resBT(r, 1) = dBT(k(r)) 'get the count
Next r
'place array to sheet
sht.Range("U7").Resize(UBound(resBT, 1), 1) = resBT
createsummarytable
PopSummaryAOI dBT
dBT.RemoveAll
'retrieve and print reaction times to data summary sheet
For r = 1 To UBound(d, 1)
If resBT(r, 1) <> "" Then 'if buttonpresscount = 1 and AOI count exists
dBT(k(r)) = d(r, COL_RT)
End If
Next r
'Populate array with last row reaction time for each trial
For r = 1 To UBound(d, 1)
resBT(r, 1) = dBT(k(r)) 'get the count
Next r
PopSummaryRT dBT
dBT.RemoveAll
'work out avg fixation time per trial
For r = 1 To UBound(d, 1)
If resBT(r, 1) <> "" Then
If Cnts(k(r)) < 1 Then
'Error if no results
dBT(k(r)) = CVErr(xlErrDiv0)
Else
'Determine average
dBT(k(r)) = Sums(k(r)) / Cnts(k(r))
End If
End If
Next r
'populate array
For r = 1 To UBound(d, 1)
resBT(r, 1) = dBT(k(r)) 'get the count
Next r
PopSummaryFT dBT
End Sub
Upvotes: 1