Pseudo-randomize items using code component in builder?

Hi,

I am building a distributional learning experiment with an exposure/training phase, a categorization task, and a production task. For the training phase, participants are exposed to combinations of sounds and images, but some combinations occur more often than others. I have created an excel sheet that lists all 11 combinations at different frequencies (with some combinations listed 5 or 6 times, others just once or twice), and the loop for this trial is now set to random, with nReps = 1.

BUT: I don’t want the same combinations to occur after another, so I’d like to add a restriction on the randomization. (I saw @Becca 's suggestion to create the different excel sheets of sequences that are allowed and then set the loop to sequential. This is not really an option for me as I have 44 items, so the number of possible orders gets a bit silly.

Is there a way to do this in builder by adding a code component or something? Can a loop in builder be replaced by a code component? Other suggestions are welcome too.

-Sabine

I often use loops that don’t point to an Excel file but instead cycle through an array.

You could have a random selection which picks the combination at the start of the loop but also checks against the previous selection.

For example:

choiceNum=random()

if choiceNum < .1 and oldChoice != 0:
     thisChoice = 0
elif choiceNum < .2 and oldChoice != 1:
     thisChoice = 1
elif choiceNum < .25 and oldChoice != 2:
     thisChoice = 2
elif choiceNum < .30 and oldChoice != 3:
     thisChoice = 3
etc.
thisSound = allSounds[thisChoice]
thisImage= allImages[thisChoice]

oldChoice=thisChoice

Ah yes, that makes total sense. I can add counters to check how often the combinations have occurred.

Great, thanks a bunch!

One approach would be to have a 11 shuffled lists of trials, each missing one trial type and having the others in the correct proportions. You could then pop the next trial type from the appropriate list based on the previous trial type.

I’m not sure if I understand what you mean.

I think the first solution is what I want and given my very limited coding knowledge it’s the most feasible, so I will give that a shot. Either way, thinking about loops while being unable to use the loops in builder makes me appreciate the builder environment even more :slight_smile:

The solution proposed earlier had an artifact that made it into a sub-optimal solution.

My new solution is a function that creates a list of items that is shuffled, and if there are two or more consecutive items that are are the same, then the function will swap the item with the next one. It then goes over the list again and checks until there are no two of the same items after another.

Here’s the code, in case it might be of any use to anyone:

# list of the stimuli
Images = ["1A.png", "2A.png", "3A.png", "4A.png", "5A.png", "6A.png", "7A.png", "8A.png", "9A.png", "10A.png", "11A.png", "1B.png", "2B.png", "3B.png", "4B.png", "5B.png", "6B.png", "7B.png", "8B.png", "9B.png", "10B.png", "11B.png"]        

# how often the images should be repeated
reps = [ 1,2,5,6,5,2,1,0,0,0,0, \
         0,0,0,0,1,2,5,6,5,2,1 ]


def repair( indices, Images ):
    found = True
    for index in range(1,len( indices ) ):
        if( Images[ indices[ index ] ] == Images[ indices[ index-1]]):
            found = False
            for indey in range( index+1, len(indices )):
                if Images[ indices[ index ]] != Images[ indices[ indey ]]:
                    indices[ index ], indices[ indey ] = indices[ indey ], indices[ index ]
                    found = True
                    break
        if found == False :
            break
    return( found )


# define some variables here
indices =[]
NumStims = len( Images )


# creates a list of all your images, and adds the correct number of each image in the list
for  i in range(0, NumStims):
    for j in range(0, reps[ i ]):
        indices.append(i)


random.shuffle( indices )

while( not repair( indices, Images ) ):
    random.shuffle( indices )

for i in Stim_indices:
    thisOrder.append(Images[i])'

So, I now have a pseudo randomized list of images names that meets my requirements, called thisOrder, perfect!

I thought I could easily call these images by simply putting

myImage = thisOrder[n]
myImage.draw()
n = n+1

in the begin routine tab of a code component, but I get: AttributeError: ‘str’ object has no attribute ‘draw’. How do I tell psychopy that I am referring to a file path rather than a string in itself? Or rather: how can I call an image while using that precious pseudo randomized list?

-Sabine

.draw() is used for objects not file names.

You could have an image component set to $myImage every repeat. This should work so long as it appears below when you set myImage.

Ha i can’t believe I missed that. Thanks for the help again.

-S