Skip to content

Instantly share code, notes, and snippets.

@jdkoen
Last active July 21, 2021 19:00
Show Gist options
  • Save jdkoen/2e94645a086477896daa1726fe3324c7 to your computer and use it in GitHub Desktop.
Save jdkoen/2e94645a086477896daa1726fe3324c7 to your computer and use it in GitHub Desktop.
This is a script that creates a randomized list of stimuli (here, faces, scenes, and objects), that are repeated with a lag in a specified range. This is generic and can be modified for more complex constraints.
import numpy as np
import pandas as pd
from random import shuffle
from pathlib import Path
# Out directory
out_dir = Path('lagged_lists')
out_dir.mkdir(exist_ok=True)
# Make a dummy stimulus list
n_stim_per_group = 120 # Number of stimuli per grouping factor
face_list = [f'face-{x:03}' for x in np.arange(n_stim_per_group)]
scene_list = [f'scene-{x:03}' for x in np.arange(n_stim_per_group)]
object_list = [f'object-{x:03}' for x in np.arange(n_stim_per_group)]
stim_list = pd.DataFrame({'trial': face_list + scene_list + object_list})
n_trials = len(stim_list) * 2
# Options for controlling the lag
min_lag = 10 # Minimum lag between repeats (number of trials between, not including repeat)
max_lag = 50 # Maximum lag between repeats (number of trials between, not including repeat)
t1_cutoff = n_trials - min_lag # Avoids 1st presentation being in an impossible spot
n_lists_needed = 1 # How many lists are needed
# Start a while loop to find good randomizations
# Note: if a iteration fails to place all items in a spot with an
# in range lag, the randomization is ignored and it starts a new
# randomization. It goes until the number of good lists/randomizations
# found equals the number of lists needed (n_lists_needed)
good_lists = 0 # Keeps track of good lists.
while good_lists != n_lists_needed:
# Randomize the stim_list
stim_list = stim_list.sample(frac=1)
# Make an 'empty' data frame
trials = pd.DataFrame({'trial': [''] * n_trials}) # Makes empty data frame
# Loop through all the trials in the (randomized) stim list
# To find a home for them
for i, row in stim_list.iterrows():
# Find empty rows/trials that have not been assigned
open_rows = np.where(trials.applymap(lambda x: x == ''))[0]
# Make a copy of open_rows and remove unneeded ones
# The 1 is for the 1st presentation
open_rows1 = open_rows.copy()
open_rows1 = open_rows1[open_rows1 < t1_cutoff]
# Search through the open rows (open_rows1)
while True:
# Break the loop if no more valid open rows for
# 1st repetition
# Note: open_rows1 changes shape if no valid
# lags are found below, so it can continue, and
# fail, only when there are no valid positionings
# for any single trial
if open_rows1.shape[0] == 0:
break
# Select a first row and remove from open_rows1
first_row = open_rows1[0]
open_rows1 = open_rows1[open_rows1 != first_row]
# Find the possible lags, and omit lags that have been filled
min_row = first_row + min_lag + 1 # Plus 1 for min_lag spacing
max_row = first_row + max_lag + 2 # Plus 2 for max_lag spacing
possible_lags = np.arange(min_row, max_row)
possible_lags = [x for x in possible_lags if x in open_rows]
possible_lags = np.array(possible_lags)
# If there are no possibl lags, skip this first trial position
# Otherwise, randomly select a valid lag
if possible_lags.shape[0] == 0:
continue # Move to next iteration
else:
shuffle(possible_lags)
second_row = possible_lags[0]
trials['trial'].iloc[first_row] = row['trial']
trials['trial'].iloc[second_row] = row['trial']
break
# One the for loop is done, see if there are still empty values in trials.
# If there are, then it is not a good_list and nothing is done.
# If, however, there are no blanks (which is the convoluted if conditional),
# then it is a good list which is written to a csv file.
if np.where(trials.applymap(lambda x: x == ''))[0].shape[0] == 0:
good_lists += 1
print('# Good Lists:', good_lists)
trials.to_csv(out_dir / f'list{good_lists}.csv', index=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment