psychopy.org | Reference | Downloads | Github

Logging reaction times for longer than one routine

Hi all,

I have an experiment in which participants are shown a visual metronome (pulsating dot). I created a routine for the stimulus presentation, which is part of a loop. Participants are asked to press a key each time they see the dot. At the beginning of the routine I inserted a code component to write the reaction times, the variable of interest, to a data file.

This works fine, as long as participants press the key within the time window of 1 routine since in Builder the clock gets reset after each routine. What I would like to see is the clock being reset only if a key is pressed. So, each data point is then the elapsed time since the last keypress, independent of stimulus presentation.

I created a separate clock (see code below) but this attempt was not successful yet.

Many thanks for every insight!

Best,
Benjamin

Begin Experiment:

# define clock to time keypresses
testClock = core.Clock()

Each Frame:

# collect keys
keys = event.getKeys()

if keys:
    if 'k' in keys:
        RT = testClock.getTime()
        thisExp.addData('RT in ms', RT*1000)
        testClock.reset()

Please state precisely what your actual problem/error is.

(Although by looking at the code, I suspect that you need to reset the timer at the start of the first iteration of your trial loop so that the first key press is measured relative to the correct time.)

Hi Michael,

Is the following code ok to reset the timer at the start of the first iteration of the loop? The obtained RT for the first trial seems reasonable (around 200ms) imo.

# reset clock at start of loop
if test_trials.thisN < 0:
    testClock.reset()

The main issue of only logging the reaction time within 1 routine remains though.

Below a screenshot of how the reaction times are logged at the moment:

For 4 stimulus presentations (trial 5 till 8) I did not press the required key so no reaction time is logged, which is fine. But when I pressed the key again (at trial 9 here) a low reaction time is logged (263ms) but this number should be higher since I was not pressing a key for the 4 stimulus presentations before and the elapsed time since the last keypress at trial 4 should have been added.

Any ideas about this?

Best,
Benjamin

I’m actually surprised that this (seems) to work, as test_trials.thisN should be zero on the first iteration of the loop. Where is that code placed (e.g. in the “begin routine” tab?)

I’d suggest amending it to if test_trials.thisN == 0: and see what happens.

If your original code as posted above was pasted in correctly (i.e. has the correct indentation), then it looks good to me (as you only reset the timer on trials when you save the reaction time). So yes, it is a bit mysterious how you don’t end up with a larger reaction time on trial 9.

  • Could you post a screen shot of your flow panel?
  • can you verify that the thisExp.addData('RT in ms', RT*1000) and testClock.reset() lines are indeed indented exactly the same amount in your code component?
  • Can you verify where the # reset clock at start of loop is located, and test what changing it does?
  • Could you check if there is any other custom code anywhere else that might be inadvertently resetting the clock?

Hi Michael,

If I place if test_trials.thisN == 0: in the Each Frame tab, I got an implausible RT for the first keypress, which did not happen with < 0.

The issue of the timer not being properly reset when the key is not pressed during a routine is still present:

  • The code of my experiment is indented the same way as it is in this thread.
  • # reset clock at start of loop is located in the Each frame tab
  • There is no other piece of code that may reset the clock. The only other code present in the experiment is code to determine the period of the stimulus presentation and code to let the loop end after x number of keypresses.

Below you can find an overview of the routine and flow panel:

Best,
Benjamin

Yes, because that would be resetting the clock many times (e.g. typically 60 times per second) on the first trial. As suggested, that code needs to be in the Begin routine tab, so it runs only once.

In that same vein, are you sure that the code to create testClock runs only in the Begin experiment tab?
Is that a unique name (i.e. hopefully it isn’t the same name as the clock that Builder automatically creates for that routine)?
i.e. you need to ensure that clock is created only once during the experiment.

Hi Michael,

The name of the clock was indeed the same as the one that Builder created…
As you see below in the output, the RT logging for a keypress that spans multiple routines works fine now:
RT
I placed the code in the Begin Routine tab, but to be honest, I do not understand why this works.

If the clock is reset at the start of each routine, it seems to me that it is reset at the start of each stimulus presentation, while what I want to achieve (and seems to be the case in the output) is that the clock is reset after each keypress, independent of the stimulus presentation?

But now I detected another issue: if the keys are rapidly pressed after each other the loop ends (as intended) after 10 keypresses, but not all 10 RT’s are logged (here only 4 while I pressed 10 times):

I would think this should not be allowed to happen since the RT logging code is placed in the Each Frame tab (see first post)?
So, the question is then how to make it also possible to log multiple keypresses during one routine (and not just once during one or multiple routines)?

If it may be useful: I used the following code to determine the period duration (time that stimulus is not shown) and to let the loop end after a certain number of keypresses:

Begin Experiment:

# set keypress counter to 0
count = 0

Each Frame:

if count < 11:
    # period update
    if period_test.status == STARTED and frameN >= (period_test.frameNStart + period_dur):
        period_test.setAutoDraw(False)
    # with period_dur a number (float) derived from the experimenter input at the start of the experiment

# end loop after X keystrokes
if count == 10:
    continueRoutine = False
    test_trials.finished = True

Best,
Benjamin

because the resetting is conditional:

# reset clock at start of loop
if test_trials.thisN == 0:
    your_clock.reset()

i.e. this resetting will happen only once, at the start of the first (i.e. zeroth) trial. This is necessary because the timer was created at the start of the experiment, and has been running since then. In fact, you could delete that code code from the “start experiment” tab, and instead create the timer here at the beginning of the first routine, instead of resetting the existing one.

I’m lost now, as the code has been amended across multiple posts, and in the previous post above you are only supplying partial code, and don’t have comments explaining what it is doing (e.g. what is period_test?)

We need to be able to see all of the current code to able to follow the logic.

Got it! I confused .thisRepN and thisN…

The complete code looks like this right now:

Begin Experiment:

# convert frequency input into float
freq = float(expInfo['Hz'])

# define period (ISI) in number of frames based on frequency input
period_dur = (1/freq)*60

# set keypress counter to 0
count = 0

Begin Routine:

# start clock at first trial to collect keypresses
if test_trials.thisN == 0:
    my_testClock = core.Clock()

Each Frame:

# collect keys
keys = event.getKeys()

# show stimulus and period as long as the number of 10 keypresses is not reached
if count < 11:
    # period update
    # period_test is a blank text component
    # period_test is displayed for the amount of time set by period_dur
    # (if the number of frames exceeds the period duration,
    # the blank text component is not drawn and the white dot is shown again)
    if period_test.status == STARTED and frameN >= (period_test.frameNStart + period_dur):
        period_test.setAutoDraw(False)

# if a key is pressed:    
if keys:
    if 'escape' in keys:
        core.quit()
    if 'k' in keys:
        # get RT
        RT = my_testClock.getTime()
        # write RT in ms to output
        thisExp.addData('RT in ms', RT*1000)
        # reset clock
        my_testClock.reset()
        # increment counter by 1
        count += 1

# end loop after X keystrokes
if count == 10:
    continueRoutine = False
    test_trials.finished = True

The question was then why it seems to be that not always the required number of RT are logged (e.g., if one presses too fast).

Best,
Benjamin

OK thanks, that message was very clear and comprehensive.

I suspect the issue here is that if multiple keypresses occur within a trial, then only the last will be recorded in the data. i.e. the second or subsequent time thisExp.addData('RT in ms', RT*1000) is called, it just overwrites any value that was previously there.

What do you want to happen? If you want to record multiple keypresses per trial, then instead of saving a single value (which will over-write the previously-recorded one), then you need to be saving a list of RTs (it is OK if that overwrites the previous entry, as the list contains the earlier values).

NB This does introduce a complication for the analysis stage, as sometimes the RT column in your data will include just a single value, sometimes it will include no value, and other times, it will include a list of multiple values.

Something like this:

Begin routine:

# start clock at first trial to collect keypresses
if test_trials.thisN == 0:
    my_testClock = core.Clock()

RT_list = [] # initialise a list for potential multiple values

Each frame:


if 'k' in keys:
    # get RT
    RT = my_testClock.getTime() * 1000

    # write RT in ms to output
    if RT_list: # multiple entries
        RT_list.append(RT)
        thisExp.addData('RT in ms', RT_list)
    else: # single entry
        RT_list.append(RT)
        thisExp.addData('RT in ms', RT)

    # reset clock
    my_testClock.reset()

    # increment counter by 1
    count += 1

Hi Michael,

I tried your code (and made sure everything was indented correctly) but it still results in the same issue (not logging all RTs with fast keypresses).

But actually, I do not prefer this option with the possibility to have none, one or multiple RTs per trial, making the analysis indeed more challenging.

What I would like is a column (or list) indicating the number of taps (1,2,…,n) and in another one the corresponding RTs, independent of the stimulus presentation (which is represented by 1 trial/routine in the experiment).

Perhaps it may be more feasible to write a separate code to create a .csv file and save the data instead of using the thisExp.addData command?

Best,
Benjamin