Source code for psychopy_tools.stim_gen

# -*- coding: utf-8 -*-

"""Functions associated with stimulus generation and randomization."""

from __future__ import division
import numpy as np
import pandas as pd

[docs]def random_jitter(num_trials,desired_mean=6,iti_min=1,iti_max=None,discrete=True,tolerance=.05,nsim=20000,plot=True): """ Create a series of ITIs (in seconds) with a given mean, number of trials and optionally minimum and maximum. ITIs follow either a discrete geometric distribution or a continuous exponential based on user settings. Either way produces a sequence with more shorter ITIs and fewer longer ITIs. Perhaps better for fast event-related designs where a mean ITI is desired that comes from a skewed distribution with many shorter ITIs and a few long ITIs. *NOTE*: The min ITI is guaranteed, but the max ITI is an upper bound. This means the generated maximum may actually be lower than the desired max. Setting the maximum too low will result in harder/no solutions and will cause the distribution to be less well behaved. Args: num_trials (int): number of trials (number of ITIs to create) desired_mean (float): desired mean ITI; default 6 iti_min (int/float): minimum ITI length; guaranteed; default 1 iti_max (int/float): maximum ITI length; only guaranteed that ITIs will not be longer than this; default None discrete (bool): should ITIs be integers only (discrete geometric) or floats (continuous exponential); default discrete tolerance (float): acceptable difference from desired mean; default 0.05 nsim (int): number of search iterations; default 10,000 plot (bool): plot the distribution for visual inspection; default True Returns: seq (np.ndarray): sequence """ assert isinstance(num_trials,int), "Number of trials must be an integer" assert desired_mean > iti_min, "Desired mean must be greater than min ITI!" if discrete: assert isinstance(iti_min,int), "Minimum ITI from discrete distribution must be an integer" if iti_max is not None: assert isinstance(iti_max,int), "Maximum ITI from discrete distribution must be an integer" else: iti_max = np.inf # Initialize seq_mean = 0 i = 0 converged = False while not converged: # Generate random sequence with desired mean accounting for minimum adjusted_mean = desired_mean - (iti_min - 1) if discrete: # Sampling from geometric distribution where relationship between # p param and mean is: # mean = 1/p; p = 1/mean seq = np.random.geometric(1./adjusted_mean,size=num_trials) else: # Sampling from exponential distribution where relationship between # lambda param and mean is: # mean = 1/lambda; lambda = 1/mean; but numpy already uses 1/lambda implicitly seq = np.random.exponential(adjusted_mean,size=num_trials) seq += iti_min - 1 seq_mean = seq.mean() # Check it if (np.allclose(seq_mean,desired_mean,atol=tolerance)) and (seq.max() <= iti_max): converged = True elif i == nsim: break else: i += 1 if converged: print("Solution found in {} iterations".format(i)) else: print("No solution found after {} interations.\nTry increasing tolerance.".format(nsim)) if plot: if discrete: pd.Series(seq).plot(kind='hist',bins=len(np.unique(seq)),xticks=np.arange(iti_min,seq.max())) else: pd.Series(seq).plot(kind='hist',bins=10,xlim = (iti_min,seq.max())) return seq
[docs]def random_uniform_jitter(num_trials,desired_mean=6, iti_min=2,iti_max=None,discrete=True,tolerance=.05,nsim=20000,plot=True): """ Create a series of ITIs (in seconds) from a uniform distribution. You must provid any **two** of the following three inputs: desired_mean, iti_min, iti_max. This is because for uniform sampling, the third parameter is necessarily constrained by the first two. Perhaps useful for slow event related designs, where a longish mean ITI is desired with some variability around that mean. ITIs follow either a discrete or continuous uniform distribution. If a small amount of variability is desired around a particular mean, simply provide a desired_mean, and a iti_min (or iti_max) that is very close to desired_mean. Args: num_trials (int): number of trials (number of ITIs to create) desired_mean (float): desired mean ITI; default 6 iti_min (int/float): minimum ITI length; guaranteed; default 1 iti_max (int/float): maximum ITI length; guaranteed; default 10 (computed) discrete (bool): should ITIs be integers only (discrete) or floats (continuous); default discrete tolerance (float): acceptable difference from desired mean; default 0.05 nsim (int): number of search iterations; default 20,000 plot (bool): plot the distribution for visual inspection; default True Returns: seq (np.ndarray): sequence """ # For uniform distributions: # mean = .5 * (min + max) # min = (2 * mean) - max # max = (2 * mean) - min # Simplify everything to min and max assert isinstance(num_trials,int), "Number of trials must be an integer" if desired_mean and iti_min: assert iti_max is None, "Must provide only 2 of the following: desired_mean, iti_min, iti_max" iti_max = (2 * desired_mean) - iti_min elif desired_mean and iti_max: assert iti_min is None, "Must provide only 2 of the following: desired_mean, iti_min, iti_max" iti_min = (2 * desired_mean) - iti_max elif iti_min and iti_max: assert desired_mean is None, "Must provide only 2 of the following: desired_mean, iti_min, iti_max" desired_mean = .5 * (iti_min + iti_max) assert desired_mean > iti_min, "Desired mean must be greater than min ITI!" if discrete: if iti_min: assert isinstance(iti_min,int), "Minimum ITI from discrete distribution must be an integer" elif iti_max: assert isinstance(iti_max,int), "Maximum ITI from discrete distribution must be an integer" # Initialize seq_mean = 0 i = 0 converged = False while not converged: if discrete: # Sampling from discrete uniform distribution seq = np.random.randint(iti_min,iti_max+1,size=num_trials) else: # Sampling from continuous uniform distribution seq = np.random.uniform(iti_min, iti_max+.000000000001,size=num_trials) seq_mean = seq.mean() # Check it if (np.allclose(seq_mean,desired_mean,atol=tolerance)): converged = True elif i == nsim: break else: i += 1 if converged: print("Solution found in {} iterations".format(i)) else: print("No solution found after {} interations.\nTry increasing tolerance.".format(nsim)) if plot: pd.Series(seq).plot(kind='hist',bins=len(np.unique(seq)),xlim = (iti_min,iti_max)) return seq