def get_nobrainer_trials(subject, bidsdir): """ Return the trials where a decision is a "nobrainer", a trial where both the reward probability and magnitude of one option is higher than that of the other option. :param subject: str, subject identifier :param bidsdir: str, path to BIDS dir with logfiles :return: """ df = read_bids_logfile(subject=subject, bidsdir=bidsdir) # where is the both Magnitude and Probability of reward greater for one # option over the other? -> nobrainer trials right = df['trial_no'][(df.RoptMag > df.LoptMag) & (df.RoptProb > df.LoptProb)].values left = df['trial_no'][(df.LoptMag > df.RoptMag) & (df.LoptProb > df.RoptProb)].values # the remaining trials require information integration ('brainer' trials) brainer = [i for i in df['trial_no'].values if i not in right and i not in left] # brainer and no-brainer trials should add up assert len(brainer) + len(right) + len(left) == len(df['trial_no'].values) # make sure that right and left no brainers do not intersect - if they have # common values, something went wrong assert not bool(set(right) & set(left)) # make sure to only take those no-brainer trials where participants actually # behaved as expected. Those trials where it would be a nobrainer to pick X, # but the subject chose Y, are excluded with this. consistent_nobrainer_left = \ [trial for trial in left if df['choice'][df['trial_no'] == trial].values == 1] consistent_nobrainer_right = \ [trial for trial in right if df['choice'][df['trial_no'] == trial].values == 2] logging.info(f"Subject {subject} underwent a total of {len(right)} " f"no-brainer trials for right choices, and a total of " f"{len(left)} no-brainer trials for left choices. The subject " f"chose consistently according to the no-brainer nature of " f"the trial in N={len(consistent_nobrainer_right)} cases for " f"right no-brainers, and in " f"N={len(consistent_nobrainer_left)} cases for left " f"no-brainers.") # create a dictionary with brainer and no-brainer trials. We leave out all # no-brainer trials where the participant hasn't responded in accordance to # the no-brain nature of the trial choices = {'brainer': brainer, 'nobrainer_left': consistent_nobrainer_left, 'nobrainer_right': consistent_nobrainer_right} return choices
def bigdf(bidsdir): """ aggregate all log files into one big dataframe :param bidsdir: :return: """ # need a list of sub ids, can't think of something less convoluted atm subs = sorted([sub[-3:] for sub in glob(bidsdir + '/' + 'sub-*')]) dfs = [] for subject in subs: df = read_bids_logfile(subject, bidsdir) # add a subject identifier df['subject'] = subject dfs.append(df) # merge the dfs. Some subjects have more column keys than others, join=outer # fills them with nans where they don't exist return pd.concat(dfs, axis=0, join='outer')
def get_leftright_trials(subject, bidsdir): """ Return the trials where a left choice and where a right choice was made. Logdata coding for left and right is probably 1 = left, 2 = right (based on experiment file) :param subject: str, subject identifier, e.g., '001' :param bidsdir: str, Path to the root of a BIDS raw dataset :return: choices; dict of trial numbers belonging to left or right choices """ df = read_bids_logfile(subject=subject, bidsdir=bidsdir) # get a list of epochs in which the participants pressed left and right left_choice = df['trial_no'][df['choice'] == 1].values right_choice = df['trial_no'][df['choice'] == 2].values choices = {'left (1)': left_choice, 'right (2)': right_choice} return choices
def stats_per_subject(subject, bidsdir, results=None): """ Compute summary statistics for a subject :param subject: :param bidsdir: :return: """ df = read_bids_logfile(subject, bidsdir) if results is None: results = {} # median reaction time over all trials results[subject] = {'median RT global': np.nanmedian(df['RT'])} results[subject] = {'mean RT global': np.nanmean(df['RT'])} results[subject] = {'Std RT global': np.nanstd(df['RT'])} # no-brainer trials right = df['RT'][(df.RoptMag > df.LoptMag) & (df.RoptProb > df.LoptProb)].values left = df['RT'][(df.LoptMag > df.RoptMag) & (df.LoptProb > df.RoptProb)].values nobrainer = np.append(right, left) results[subject] = {'median RT nobrainer': np.nanmedian(nobrainer)} results[subject] = {'mean RT nobrainer': np.nanmean(nobrainer)} results[subject] = {'Std RT nobrainer': np.nanstd(nobrainer)}
def get_decision_timespan_on_and_offsets(subject, bidsdir): """ For each trial, get a time frame around the point in time that a decision was made. :param subject; str, subject identifier :param bidsdir: str, path to BIDS dir with log files :return: trials_to_rts: dict, and association of trial numbers to 800ms time slices around the time of decision in the given trial """ logs = read_bids_logfile(subject=subject, bidsdir=bidsdir) trials_and_rts = logs[['trial_no', 'RT']].values logging.info(f'The average reaction time was ' f'{np.nanmean(trials_and_rts[:, 1])}') # mark nans with larger RTs logging.info('Setting nan reaction times to implausibly large values.') np.nan_to_num(trials_and_rts, nan=100, copy=False) # collect the trial numbers where reaction times are too large to fit into # the trial. For now, this is at 3 seconds. trials_to_remove = trials_and_rts[np.where(trials_and_rts[:, 1] > 3)][:, 0] # initialize a dict to hold all information trials_to_rts = {} for trial, rt in trials_and_rts: # Now, add RT to the start of the second visual stimulus to get the # approximate decision time from trial onset # (0.7s + 2.0s = 2.7s) decision_time = rt + 2.7 # plausibility check, no decision is made before a decision is possible assert decision_time > 2.7 # calculate the slice needed for indexing the data for the specific # trial. We round down so that the specific upper or lower time point # can be used as an index to subset the data frame window = [decision_time - 0.4, decision_time + 0.4] if trial not in trials_to_remove: trials_to_rts[trial] = window return trials_to_rts