exp.addData() is saving only one value

Hi, good evening.

I want to export the data at the end of the Trial (loop) but for each data saved, the values of “target”, “cueside” and “tarside” are always the same.

For cueside, for example:

_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)

# Info about the experiment 

expName = "Attention Network Task"
expInfo = {"Participant": "","Age": ""}
dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)
if dlg.OK == False:
    core.quit()
expInfo['date'] = data.getDateStr() 
expInfo['expName'] = expName


# Data file name stem 
filename = _thisDir + os.sep + u"data/%s_%s_%s" % (expInfo["Participant"],     expName, expInfo["date"])

#ExperimentHandler 
exp = data.ExperimentHandler(name=expName, extraInfo=expInfo,     savePickle=True, saveWideText=True,
    dataFileName=filename)

# save a log 
logFile = logging.LogFile(filename+'.log', level=logging.EXP)
logging.console.setLevel(logging.WARNING)

endExpNow = False 

#Cue locations
CUELOCS = ["upper", "both","lower","centre","blank"] 

# All possible cues 
cuestim = {}
cuestim["blank"] = ImageStim(disp, size=(300,300), image="blank.png")
cuestim["centre"] = ImageStim(disp, size=(300,300), image="centre.png")
cuestim["upper"] = ImageStim(disp, size=(300,300), image="upper.png")  
cuestim["lower"] = ImageStim(disp, size=(300,300), image="lower.png")  
cuestim["both"] = ImageStim(disp, size=(300,300), image="both.png")  

# Create an empty list to contain the training trial
training = []
# Loop
for cueside in CUELOCS:
    for tarside in TARLOCS:
        for soa in SOAS:
            for tar in TARGETS:
                #for flank in FLANKERS:
                    # Training dictionary
                    trialtraining = {"cueside":cueside, "tarside":tarside, "target":tar, "soa":soa}
                    # add the trial dictionary to the list
                    training.extend (TRAININGREPEATS * [trialtraining])
# Randomise
random.shuffle (training)

In the end of the loop:

# Loop FIRST TRIAL
for trial_number, trialtraining in enumerate(training):
    if trial_number == 96:
        break
    
    # Present fixmark
    imgfix.draw()
    fixonset = disp.flip ()
    wait (FIXTIME)

    # Draw the fixation mark and the cues
    imgfix.draw()
    cuestim[trialtraining["cueside"]].draw()
    cueonset = disp.flip()
    wait (CUETIME)

    # Draw the fixation mark
    imgfix.draw()
    # Update the monitor 
    cueoffset = disp.flip()
    #wait for the SOA minus the cue duration
    wait (trialtraining ["soa"] - CUETIME) 

    # Draw the fixation mark
    imgfix.draw()
    # Draw a target stimullus
    tarstim[trialtraining ["tarside"]].draw()
    # Update the monitor
    taronset = disp.flip()

    # Wait for a response
    resplist = waitKeys (maxWait=float("inf"), keyList = ["left", "right"],timeStamped =     True)
    if 'escape' in getKeys(): quit()

    # Select the first response from the response list
    response, presstime = resplist[0]
    # Calculate de RT
    RT = presstime - taronset

        # Valid cue?
    if trialtraining ["cueside"] == trialtraining ["tarside"]:
        validity = 1
    else:
        validity = 0
    
    # Check if the response was correct
    if response == tarstim [trialtraining ["tarside"]].correctAns:
        correct = 1
    else:
        correct = 0

# Extract data
 
    exp.addData('cueside', cueside)

    exp.nextEntry()
disp.flip()

But the cueside is saved only with the “blank” (not “upper”, “centre”, etc) value for all trials.

I hope I could let clear what is happening. I tried to summarize the code, but if needed I can put more information.

Thanks in advance

The code you posted above, to construct your conditions, looks good. But we really need to see is where the data saving code is relative to your main trial loop.

Hi Michael, thanks for your response. I added the log and loop code. Maybe the data saving code is actually not complete?

The issue is that you are always saving the same value, which is the last value that was loaded into the name cueside. You aren’t updating that variable during your loop, so its value will not change. i.e. instead of this, which will always store the same thing:

exp.addData('cueside', cueside)

you need something like this:

exp.addData('cueside', trialtraining['cueside'])

to get the value associated with the current trial.

But actually you should look at using the ExperimentHandler or TrialHandler classes directly to control your trials. i.e. at the moment you are just looping through a list of dictionaries and then calling exp.addData() and exp.nextEntry(), I guess since you have set up an ExperimentHandler earlier. Instead you should define a TrialHandler object, and pass it your carefully constructed list of dictionaries to iterate over, and add that TrialHandler to the experiment handler (this step is optional ifs you only have one loop of trials).

The structure of your code will be much more clear. You also won’t need to call .addData() manually, as all variables listed in the conditions list will be saved for you automatically. And .nextEntry() won’t be needed, as iterating over the experiment or trial handler takes care of that automatically.

Look at the Coder demos for the TrialHandler class - it is set up in the same way as for you, with the conditions defined in a list of dictionaries, and passed to the constructor of the TrialHandler.

Unrelated: also note that this line:

if 'escape' in getKeys(): quit()

is unlikely to do anything, as it comes immediately after a call to event.waitKeys(), so it is highly unlikely that a new key has been pressed in the infinitesimal time since event.waitKeys() completed.

1 Like

Hi Michael, Thanks for your patience and for your explanation. It worked fine but your suggestion really fits to my experiment. Actually I have 3 loop of trials and the loop is happening in a very strange way (sometimes the cue come “upper” for 6 trials in a row) and it won’t help when I analyze the data.

Sorry, this is my very first time programming and I am still confused about the Trial/Experiment Handler and how I can construct the code in a more efficient way.

In my case, how could I construct a TrialHandler ? I tried to code it but unfortunately with no success. Should I do something like this? but without the ExperimentHandler?:

trails = data.TrialHandler(trialtraining, method='random')

trails.data.addDataType('target', trialtraining['target'])
trails.data.addDataType("cueside", trialtraining["cueside"])
trails.data.addDataType('tarside', trialtraining['tarside'])

trials.setExp(exp)

You need to tell us exactly what the problem is, otherwise it is difficult to suggest how to fix it.

You need to pass it your whole list of trials, not just a single trial. e.g.

trials = data.TrialHandler(trialList = training, method = 'random')

e.g. it doesn’t make sense to try to randomly present a set of trials, if you only gave it information about one trial. It’s important to read the documentation carefully, e.g. where it describes the trialList parameter like this "trialList: a simple list (or flat array) of dictionaries" psychopy.data - functions for storing/saving/analysing data — PsychoPy v2023.2.3

But because the TrialHandler now contains your list of conditions, you can easily run your loop like this:

for trial in trials:

    # the trial handler keeps track of its trial number and so on, e.g.
    if trials.thisN == 96:
        break

    # do stuff on each trial, as before, e.g.

    # Draw the fixation mark and the cues
    imgfix.draw()
    cuestim[trial['cueside']].draw()
    cueonset = disp.flip()
    wait(CUETIME)

    # Now will automatically go on to the next trial.
    # No need to save the condition variables, as that happens 
    # automatically. You will need to manually save anything 
    # else you measure or calculate, like reaction times and so on.

Those lines are unnecessary, as the TrialHandler already knows about these variable names, as they are included in the trialList of dictionaries you gave it when it was created.

I think it is only necessary to have an ExperimentHandler if you are going to have more than one loop (which I think you are).

Alternatively, you can do it in the other direction, like this:

exp.addLoop(trials) # tell the experiment that it owns this TrialHandler
1 Like

Thanks for your patience, Michael. All the information really helped me and I also could learn a lot.

Everything about the loop is working fine. Now I found some small errors that I did before in my code. I will try to fix it and if it doesn’t work I will create another topic.