Which is the best way to record keyboard reaction time?


I have an experiment (an n-back task) in which participants are shown a letter for 0.5 seconds followed by a 2 second blank screen. In each trial, participants have to press spacebar if the letter shown is the SAME as that shown just before and otherwise not respond. I want to code it so that the reaction time (from the beginning of the presentation of the letter to the spacebar press) is recorded.

Currently I have done so using two methods:
(1) Just doing nback_clock.reset() and then nback_clock.gettime() when the spacebar is pressed
(2) recorded keys_n.rt. When I look at the csv, they are different, with the (1) method being slower. I have put my code below and just wanted to check that the way I was doing it was correct or if there were any suggestions as to the correct way to do this. I am on OSX.

Side note: should the port writing (I’ve marked it with “#PORT_WRITING”) be call on flip or simply port.write?

I would appreciate any help, thank you!

for version in range(0, 3): 
    loop = version+1
    filename = 'nback_letters' + str(loop) + '.csv'
    letter_list = list(chunks(pd.read_csv(filename)['letters'].tolist(), 30))
    correct_answers = list(chunks(pd.read_csv(filename)['corrAns'].tolist(), 30))

    if loop == 2:
        showimage(win, 'twobacktaskinstructions.png',size=(1.7, 1.9))
    if loop == 3:
        showimage(win, 'threebacktaskinstructions.png',size=(1.7, 1.9))
    for (a,b) in zip(letter_list, correct_answers):
        block_number = letter_list.index(a)
        win.callOnFlip(port.write, baseline_triggersnback[version].encode())
        win.callOnFlip(port.write, start_block_triggers[version].encode())
        for j in range(0, len(a)):
            nback_start = datetime.now()
            if kb.getKeys(keyList=['escape']):
            corrAns = b[j]
            thisLetter = a[j]
            letter_present = TextStim(win, text=thisLetter, height=0.5)

            # Use clock-based timing
            start_marker_sent = False
            key_pressed = False
            while nback_clock.getTime() < 2.5: # HOW LONG TO MAKE TRIALS
                if nback_clock.getTime() < 0.5:
                    if start_marker_sent == False:
                        win.callOnFlip(port.write, start_triggers[version].encode()) #PORT_WRITING
                        start_marker_sent = True
                if key_pressed == False:
                    keys_n = nback_kb.getKeys(keyList = ['space'], waitRelease=False) 
                    RT2 = '0'
                    if len(keys_n) > 0:
                        key_pressed = True
                        RT2 = nback_clock.getTime()
            if key_pressed == False:
                response = 'none'
                RT = '0'
                if str(corrAns) == 'none':
                    answer = 1
                    answer = 0
            elif key_pressed == True:
                response = str(keys_n[-1].name)
                RT = str(keys_n[-1].rt)
                if str(corrAns) == response:
                    answer = 1
                elif str(corrAns) != response:
                    answer = 0                          
                answer = 'error'
            # Saving trial data
            thisExp.addData('N back RT',RT)
            thisExp.addData('N back RT2',RT2)
            thisExp.addData('Stimulus presented',thisLetter)
            thisExp.addData('N back response',answer)
            thisExp.addData('N back start',nback_start)
            thisExp.addData('Key pressed', response)
            thisExp.addData('Trial number', j)
            thisExp.addData('Block number', block_number)

Hi Sallicap,

I think it is normal that you observe a difference in RT here, as the nback_clock is reset before the start of the while loop. The nback_kb.clock is only reset after the first flip within the while loop. Assuming a refresh rate of 60 Hz I would expect a difference between 1 - 15 ms here?

I would think that the best approach is to reset the clock after the first flip (which would be the closest to the actual physical refresh), and pass that clock in the getKeys function using the timeStamped argument:

nback_kb.getKeys(keyList = ['space'], waitRelease=False, timeStamped = nback_kb.clock)

Great, thank you! Is that then stored as nback_kb.rt or nback_kb.timeStamped?

Based on your code, I think you first store the result of the getKeys function in a variable and then access that variable:

keys_n = nback_kb.getKeys(keyList = ['space'], waitRelease=False, timeStamped = nback_kb.clock) 
if len(keys_n) > 0:
   rt = keys_n[0][1]
   key_name = keys_n[0][0]