Word n-back paradigm

Dear discourse community,

I am having problems creating a working memory paradigm using words and wondered if anyone could provide some advice.

Description of paradigm:

1-back working memory word paradigm. Using a list of 600 words from 60 categories (10 words from each category). Participants will be required to press a key when a word is semantically related to the word previously presented and another key if the word is semantically unrelated to the word presented before.

Currently my code will randomly choose a category from an array (e.g; ugly, I know):
categories = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,
10,10,10,10,10,10,10,10,10,10]

This is used to create an index:
indx = random.choice(range(0,len(categories)))

From this indx is then used to draw a word from a text file, set out like:
diamond
ruby
opal
etc…
and loaded in using:

def load_words(words):
print "Loading word list from file…"
wordlist = list()
with open(words) as f:
for line in f:
wordlist.append(line.rstrip(’\n’))
print " ",len(wordlist), "words loaded."
print ‘\n’.join(wordlist)
return wordlist

wordlist = load_words(‘test_word_list.txt’)

the categories variable (the array of 1,1,1,1,1,etc…) defines the category that the words belong to (i.e. the first ten 1’s refer to precious stones).

A for loop then selects a word based on indx to display.

However, Given the amount of different words, it is unlikely that the presentation of the second word will be semantically related to the first (i.e. from the same category). Therefore I have tried to create a way of increasing the probability of the loop drawing from the same category.

I have done this via:

numCats = len(set(categories)) #number of unique categories
rangeCats = range(0,numCats)
myProb = 0.25
nReps = round(((numCats-1)/(1-myProb))-(numCats-1)) - 1 #(numCats - 1)/(1-myProb) is to get the 75% chance of picking out of the number of categories
nReps = int(nReps)

following the selection of the word based on the indx the word will be presented and then the selection of that category number
will be re-added back into the categories array

for i in range(0,20):
myFix.draw()
myWin.flip()
core.wait(1.5)
indx = random.choice(range(0,len(categories)))
#indx = 1
word_presentation = visual.TextStim(myWin, pos=[0,0],text=wordlist[indx])
if categories[indx] == 1:
print "precious stone"
x = 1
elif categories[indx] == 2:
print "time"
x = 2
elif categories[indx] == 3:
print "relative"
x = 3
elif categories[indx] == 4:
print "unit of distance"
x = 4
elif categories[indx] == 5:
print "metal"
x = 5
elif categories[indx] == 6:
print "reading"
x = 6
elif categories[indx] == 7:
print "military"
x = 7
elif categories[indx] == 8:
print "animal"
x = 8
elif categories[indx] == 9:
print "fabric"
x = 9
elif categories[indx] == 10:
print "colour"
x = 10
else:
print categories[indx]
if x == previous:
print “foobar”

categories.extend([x]*nReps)
categories = sorted(categories)
word_presentation.draw()
myWin.flip() #send eeg trigger after this
if x == previous:
    y =-1
previous = x
core.wait(.5)
y  = event.getKeys(keyList='space', timeStamped=False)
print y

However this is where the problems start to arise. I have found that the indx no longer matches the word list
as the categories array start to get additional numbers added to it. Furthermore I don’t think this method will
work as it will continually add more numbers to categories list. After a couple of loops through the for loop the it crashes with the following
error:

word_presentation = visual.TextStim(myWin, pos=[0,0],text=wordlist[indx])

IndexError: list index out of range

So, my problem is:

  1. How can I increase the probability of a word being drawn from the same category?
  2. Is there a better way of matching my list of words to their categories?
  3. I also believe that the ‘space’ key is only being sent after the entire for loop and therefore
    may not accurately record when the space key has actually been sent; any ideas on this?

Thank you in advance for any help or ideas!

Mark

Hi Mark, I for one get a bit lost with long posts like this if the code isn’t formatted (especially indenting). Could you edit it to get all the code formatted (like one section is). Just indexing text by four spaces is enough to get it interpreted and formatted as code. Making it more readable makes a response more likely.

Hi Michael, thanks for the reply. Please see below for the full code:

from __future__ import division
from psychopy import core, visual, gui, data, event, parallel
from psychopy.tools.filetools import fromFile, toFile 
import time, random
import math
import numpy as np
import win32api
from numpy.random import choice as npchoice 

#word list
def load_words(words):
    print "Loading word list from file..."
    wordlist = list()
    with open(words) as f:
        for line in f:
            wordlist.append(line.rstrip('\n'))
    print " ",len(wordlist), "words loaded."
    print '\n'.join(wordlist)
    return wordlist 

def choose_word(wordlist):
    return random.choice(wordlist)

categories = [1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,
10,10,10,10,10,10,10,10,10,10]

wordlist = load_words('test_word_list.txt')

#window
myWin = visual.Window([1600,900],allowGUI=True, monitor='testMonitor', units='deg')

# fixation cross
myFix = visual.ShapeStim(myWin, 
    vertices=((0, -0.5), (0, 0.5), (0,0), (-0.5,0), (0.5, 0)),
    lineWidth=5,
    closeShape=False,
    lineColor='white'
)

#number of reps for weighting probability
numCats = len(set(categories)) #number of unique categories
rangeCats = range(0,numCats)
myProb = 0.20
nReps = round(((numCats-1)/(1-myProb))-(numCats-1)) - 1 #(numCats - 1)/(1-myProb) is to get the 75% chance of        picking out of the number of categories  
nReps = int(nReps)

print "nReps:"
print nReps

#trials
previous = -1
K = len(wordlist)
probs = np.array([1.0]*K)
probs = probs/sum(probs)

for i in range(0,20): 
    myFix.draw()
    myWin.flip()
    core.wait(1.5)
    indx = random.choice(range(0,len(categories)))
    #indx = 1
    word_presentation = visual.TextStim(myWin, pos=[0,0],text=wordlist[indx])
    if categories[indx] == 1:
        print "precious stone"
        x = 1
    elif categories[indx] == 2:
        print "time"
        x = 2
    elif categories[indx] == 3:
        print "relative"
        x = 3
    elif categories[indx] == 4:
        print "unit of distance"
        x = 4
    elif categories[indx] == 5:
        print "metal"
        x = 5
    elif categories[indx] == 6:
        print "reading"
        x = 6
    elif categories[indx] == 7:
        print "military"
        x = 7
    elif categories[indx] == 8:
        print "animal"
        x = 8
    elif categories[indx] == 9:
        print "fabric"
        x = 9
    elif categories[indx] == 10:
        print "colour"
        x = 10
    else:
        print categories[indx]  
    if x == previous:
        print "foobar" 

    categories.extend([x]*nReps)
    categories = sorted(categories)
    word_presentation.draw()
    myWin.flip() #send eeg trigger after this
    if x == previous:
        y =-1
    previous = x
    core.wait(.5)
    y  = event.getKeys(keyList='space', timeStamped=False)
    print y 
    allKeys=event.getKeys()
    for thisKey in allKeys:
        if thisKey in ['q', 'escape']:
            core.quit() #abort experiment
            print 'Escape key has been pressed'

Thanks again for any help and advice.

This is not a complete answer, as there are several questions here and there are many things to talk about. Right now I’m not going to touch your question about probabilities, but I can talk to random choices and a better way of keeping the category and the word together. I for one would avoid keeping the list of categories and the words associated with them in different objects, for the exact problem you’re seeing: they can get out of sync.

Keeping categories and words in sync

There are many ways of doing this. Depending on the complexity of the your “words” you could think about turning them into objects, but if you’re not familiar with that and you really only care about their category, you could read them in as tuples. Each word would be a tuple: (‘word’, category). So your wordlist would be:

[('dog', 1), ('hamburger', 2)] # etc.

If you had this information in a separate column in the file you read in (here a comma-separated .csv), you could do this (I’m also going to use the “codecs” library to open the file, so that if your words were to have accents or something someday this wouldn’t break. And sorry, I’m just going to use code I’m more familiar with):

import codecs

def load_words(words):
    print "Loading word list from file ..."
    wordlist = []
    with codecs.open(words, 'r', 'utf-8') as f:
        data = f.read().splitlines()
        for row in data:
            rowSp = row.split(',')
            word = (rowSp[0], rowSp[1])
            wordlist.append(word)
    return wordlist

Now if you wanted to loop over this:

for wordTup in wordlist:
    word = wordTup[0]
    category = wordTup[1]

Randomizing

Someone correct me if I’m missing something, but I see no advantage to using a list like this: [1,1,1,2,2,2]. If you have ten of each number, then there should be no difference between that and choosing a number randomly from the range(1, 11), right?

# note that to get 1-10, we use 1-11 due to
# python's 0-indexing
currentCategory = random.choice(1, 11) 

The next step would be being able to quickly select an item from that category. Again, there should be many ways of doing this, but I would like a dictionary for this purpose.

# make this dictionary before the loop, of course

# key will be an integer,
# value will be a list of words  
catWordsDict = {}
for wordTup in wordlist:
    word = wordTup[0]
    cat = wordTup[1]
    if not cat in catWordsDict:
        # create an empty list
        # if this category isn't in the
        # dictionary yet
        catWordsDict[cat] = []

    # append the word to the list for 
    # its category
    catWordsDict[cat].append(word)

Now you could select at random from these categories (or use .pop() to get an item and remove it from the list, though then you’d have to plan for empty lists… Depends on what you want to do.) :

# get random number from 1 to 10
randomCategory = random.choice(range(1, numCategories + 1))
# get random word from the list for that
# category
randomWord = random.choice(catWordsDict[randomCategory])

Updating the stimulus

Lastly, so you know, it’s not recommended to create your TextStim every time through the loop unless you have to. Create it once outside of the loop, and then just update its “text” attribute.:

# before the loop
word_presentation = visual.TextStim(myWin, 
    pos=[0,0], text=u'')

# ----- in the loop -------#
    word_presentation.text = wordTup[0]

So hopefully this will help, and maybe will give you some different ideas for how to select your words. I’m not understanding, for example, why you would be extending the list of categories (and if you follow what I’ve given here, this list wouldn’t really exist now anyways). Hope this helps.

1 Like

Thank you very much Daniel! I’m in the process of implementing your suggestions.