def fit_psychfunc(df): choicedat = df.groupby('signed_contrast').agg({ 'choice': 'count', 'choice2': 'mean' }).reset_index() pars, L = psy.mle_fit_psycho( choicedat.values.transpose(), P_model='erf_psycho_2gammas', parstart=np.array( [choicedat['signed_contrast'].mean(), 20., 0.05, 0.05]), parmin=np.array([choicedat['signed_contrast'].min(), 0., 0., 0.]), parmax=np.array([choicedat['signed_contrast'].max(), 100., 1, 1])) df2 = { 'bias': pars[0], 'threshold': pars[1], 'lapselow': pars[2], 'lapsehigh': pars[3] } df2 = pd.DataFrame(df2, index=[0]) # # add some stuff # df2['easy_correct'] = df.loc[np.abs( # df['signed_contrast'] > 50), 'correct'].mean(skipna=True) # df2['zero_contrast'] = df.loc[np.abs( # df['signed_contrast'] == 0), 'choice2'].mean(skipna=True) # df2['median_rt'] = df['rt'].median(skipna=True) # df2['mean_rt'] = df['rt'].mean(skipna=True) # # number of trials per day # df4 = df.groupby(['session_start_time'])['correct'].count().reset_index() # df2['ntrials_perday'] = [df4['correct'].values] df2['ntrials'] = df['choice'].count() return df2
def ibl_psychometric(psy_df, ax=None, **kwargs): """Calculates and plot psychometic curve from dataframe datajoint independent assumes that there is not signed contrast in dataframe INPUTS: Dataframe where index is the trial number OUTPUTS: psychometic fit using IBL function from Miles and fit parameters""" #1st calculate some useful data... psy_df.loc[:, 'contrastRight'] = psy_df['contrastRight'].fillna(0) psy_df.loc[:, 'contrastLeft'] = psy_df['contrastLeft'].fillna(0) psy_df.loc[:, 'signed_contrasts'] = (psy_df['contrastRight'] - psy_df['contrastLeft']) * 100 unique_signed_contrasts = sorted(psy_df['signed_contrasts'].unique()) right_choices = psy_df['choice'] == -1 psy_df.loc[:, 'right_choices'] = right_choices total_trials = [] right_trials = [] for cont in unique_signed_contrasts: matching = (psy_df['signed_contrasts'] == cont) total_trials.append(np.sum(matching)) right_trials.append(np.sum(right_choices[matching])) prop_right_trials = np.divide(right_trials, total_trials) pars, L = psy.mle_fit_psycho( np.vstack([unique_signed_contrasts, total_trials, prop_right_trials]), P_model='erf_psycho_2gammas', parstart=np.array([np.mean(unique_signed_contrasts), 20., 0.05, 0.05]), parmin=np.array([np.min(unique_signed_contrasts), 0., 0., 0.]), parmax=np.array([np.max(unique_signed_contrasts), 100., 1, 1])) return pars, L
def fit_psychfunc(df): choicedat = df.groupby('signedContrast').agg({'trial':'count', 'choice2':'mean'}).reset_index() pars, L = psy.mle_fit_psycho(choicedat.values.transpose(), P_model='erf_psycho_2gammas', parstart=np.array([choicedat['signedContrast'].mean(), 20., 0.05, 0.05]), parmin=np.array([choicedat['signedContrast'].min(), 0., 0., 0.]), parmax=np.array([choicedat['signedContrast'].max(), 100., 1, 1])) df2 = {'bias':pars[0],'threshold':pars[1], 'lapselow':pars[2], 'lapsehigh':pars[3]} return pd.DataFrame(df2, index=[0])
def fit_psychfunc(stim_levels, n_trials, proportion): # Fit a psychometric function with two lapse rates # # Returns vector pars with [bias, threshold, lapselow, lapsehigh] from ibl_pipeline.utils import psychofit as psy assert (stim_levels.shape == n_trials.shape == proportion.shape) if stim_levels.max() <= 1: stim_levels = stim_levels * 100 pars, _ = psy.mle_fit_psycho(np.vstack( (stim_levels, n_trials, proportion)), P_model='erf_psycho_2gammas', parstart=np.array([0, 20, 0.05, 0.05]), parmin=np.array([-100, 5, 0, 0]), parmax=np.array([100, 100, 1, 1])) return pars
def plot_psychometric(df, color='black', ax=None, **kwargs): """ Plots psychometric data for a given DataFrame of behavioural trials If the data contains more than six different contrasts (or > three per side) the data are fit with an erf function. The x-axis is percent contrast and the y-axis is the proportion of 'rightward choices', i.e. trials where the subject turned the wheel clockwise to threshold. Example: df = alf.load_behaviour('2018-09-11_1_Mouse1', r'\\server\SubjectData') plot_psychometric(df) Args: df (DataFrame): DataFrame constructed from an ALF trials object. ax (Axes): Axes to plot to. If None, a new figure is created. Returns: ax (Axes): The plot axes """ if len(df['signedContrast'].unique()) > 4: df2 = df.groupby(['signedContrast']).agg({'choice':'count', 'choice2':'mean'}).reset_index() df2.rename(columns={"choice2": "fraction", "choice": "ntrials"}, inplace=True) pars, L = psy.mle_fit_psycho(df2.transpose().values, # extract the data from the df P_model='erf_psycho_2gammas', parstart=np.array([df2['signedContrast'].mean(), 20., 0.05, 0.05]), parmin=np.array([df2['signedContrast'].min(), 0., 0., 0.]), parmax=np.array([df2['signedContrast'].max(), 100., 1, 1])) sns.lineplot(np.arange(-100,100), psy.erf_psycho_2gammas( pars, np.arange(-100,100)), color=color, ax=ax) # plot datapoints on top sns.lineplot(x='signedContrast', y='choice2', err_style="bars", linewidth=0, linestyle='None', mew=0.5, marker='.', ci=68, data=df, color=color, ax=ax) # Reduce the clutter ax.set_xticks([-100, -50, 0, 50, 100]) ax.set_xticklabels(['-100', '-50', '0', '50', '100']) ax.set_yticks([0, .5, 1]) # Set the limits ax.set_xlim([-110, 110]) ax.set_ylim([-0.03, 1.03]) ax.set_xlabel('Contrast (%)') return ax
def plot_from_spots(spotlist, psychoSpotData, spots, color, ax, plotType='psycho', bs=False): """ Function to plot three psychometrics on a single plot, with the dots that they were generated with. usually I'll use this with a group of left spots, a group of right spots and a group of control spots. Inputs: spotlists: an array containing the spots that you want to group together to plot the psychometric for. a n by 2 numpy array that gives [[x1,y1],[x2,y2],[xn,yn]] psychoSpotData: a list of len(numSpots) each with a list lenght 4 containing 1, an array of signed contrasts, 2, a list of number of presentations for that contrast, 3, a list of arrays containing a bool for if choice was CCW (list len num contrast, array len num presentations) 4, a list of the same form as above with the reaction times spots: a dataframe of length num spots, column 0: laserPosX, column 1: laserPosY, column3, count color: the color for the line you want to plot, string eg 'b' ax: the matplotlib axis to plot onto plotType: defualt is psycho for plotting psychometric curves, other option is 'chrono' """ if plotType == 'psycho': psycho = [[], [0 for i in range(len(psychoSpotData[0][1]))], [np.array([])] * len(psychoSpotData[0][1])] controlSpotPsycho = [[], [ 0 for i in range(len(psychoSpotData[0][1])) ], [np.nan for i in range(len(psychoSpotData[0][1]))]] tempPsych = [np.array([])] * len(psychoSpotData[0][1]) for i in range(len(spots)): spotX = spots.iloc[i, [0]][0] spotY = spots.iloc[i, [1]][0] for spot in range(len(spotlist)): if spotX == spotlist[spot][0] and spotY == spotlist[spot][1]: psycho[0] = psychoSpotData[i][0] psycho[1] = [ temp + j for temp, j in zip(psychoSpotData[i][1], psycho[1]) ] for contrast in range(len(psycho[0])): con = int(contrast) tempPsych[con] = np.append(tempPsych[con], psychoSpotData[i][2][con]) sems = [] for c in range(len(tempPsych)): psycho[2][c] = np.nanmean(tempPsych[c]) sems.append(stats.sem(tempPsych[c])) if bs: ## Bootstrap confidence intervals nboots = 10 bootFits = pd.DataFrame( columns=['threshold', 'slope', 'gamma', 'lambda'], index=range(nboots)) bootData = [[], [0 for i in range(len(psychoSpotData[0][1]))], [np.array([])] * len(psychoSpotData[0][1])] bootData[0] = psycho[0] cnt = 0 print('bootstrapping errorbars...', sep=' ', end='') for i in range(nboots): if not (cnt % 5): print(int(cnt / nboots * 100), sep=' ', end='%,', flush=True) for j in range(len(tempPsych)): bootData[2][j] = np.random.choice( tempPsych[j], size=int(len(tempPsych[j]) / 1.25), replace=True) bootData[1][j] = len(bootData[2][j]) bootData[2][j] = np.mean(bootData[2][j]) fitParams = [] fitLikes = [] for repeat in range(5): parStart = np.array([ -5 + np.random.rand() * 10, 0 + np.random.rand() * 100, 0 + np.random.rand(), 0 + np.random.rand() ]) pars, L = mle_fit_psycho(bootData, P_model='erf_psycho_2gammas', parstart=np.array([0, 50, .5, .5]), parmin=np.array([-5, 0., 0., 0.]), parmax=np.array([5, 100., 1, 1]), nfits=2) fitParams.append(pars) fitLikes.append(L) cnt += 1 bootFits.iloc[i] = fitParams[np.where(min(fitLikes))[0][0]] a = .05 CIs = [] for i in bootFits.columns: CIs.append([ np.percentile(bootFits[i], 100 - a / 2), np.percentile( bootFits[i], a / 2, ) ]) else: CIs = None ## plotting psychometrics for different cortical groups lines = [] fitParams = [] fitLikes = [] for repeat in range(10): parStart = np.array([ -5 + np.random.rand() * 10, 0 + np.random.rand() * 100, 0 + np.random.rand(), 0 + np.random.rand() ]) params, L = psychofit.mle_fit_psycho( psycho, P_model='erf_psycho_2gammas', parstart=parStart, parmin=np.array([-5, 0., 0., 0.]), parmax=np.array([5, 100., 1, 1]), nfits=25) fitParams.append(params) fitLikes.append(L) # find the best params (with the lowest neg likelihood) params = fitParams[np.where(min(fitLikes))[0][0]] spotFits.append(params) #plot the psychometrics fitx = np.linspace(-1, 1, 100) fity = psychofit.erf_psycho_2gammas(params, fitx) line = ax.plot(fitx, fity, color=color) lines.append(line) ax.errorbar(psycho[0], np.array(psycho[2]), yerr=sems, color=color, marker='.', ms=4, ls='') plt.ylim(-0.1, 1.1) plt.xlim(-1.1, 1.1) elif plotType == 'chrono': chrono = [[], [0 for i in range(len(psychoSpotData[0][1]))], [np.array([])] * len(psychoSpotData[0][1])] tempChrono = [np.array([])] * len(psychoSpotData[0][1]) for i in range(len(spots)): spotX = spots.iloc[i, [0]][0] spotY = spots.iloc[i, [1]][0] for spot in range(len(spotlist)): if spotX == spotlist[spot][0] and spotY == spotlist[spot][1]: chrono[0] = psychoSpotData[i][0] chrono[1] = [ temp + j for temp, j in zip(psychoSpotData[i][1], chrono[1]) ] for contrast in range(len(chrono[0])): con = int(contrast) tempChrono[con] = np.append(tempChrono[con], psychoSpotData[i][3][con]) sems = [] for c in range(len(tempChrono)): chrono[2][c] = np.nanmedian(tempChrono[c]) sems.append(stats.sem(tempChrono[c])) ax.errorbar(chrono[0], np.array(chrono[2]), yerr=sems, color=color, marker='.', ms=4) ax.set_ylim(.15, .5) # the params I use here are RT at 0 contrast, 'RT bias' which is pairwise RT left- RT right, and # peakiness which is the ratio of max RT to the average of the two 100% RTs p2 = (chrono[2][0] - chrono[2][-1]) + (chrono[2][1] - chrono[2][-2] + (chrono[2][2] - chrono[2][-3]) + (chrono[2][3] - chrono[2][-4])) params = [ chrono[2][4], p2, max(chrono[2]) / np.mean([chrono[2][0], chrono[2][-1]]), chrono[2] ] CIs = None else: raise Exception( "This is not a supported plot type, choose 'psycho' or 'chrono'") return params, CIs
def plot_psychometric(x, y, col, point=False, line=True, mark='.', al=1, ax=None, **kwargs): if not ax: ax = plt.sca(ax[0]) # summary stats - average psychfunc over observers df = pd.DataFrame({'signed_contrast': x, 'choice': y, 'choice2': y}) df2 = df.groupby(['signed_contrast']).agg({ 'choice2': 'count', 'choice': 'mean' }).reset_index() df2.rename(columns={ "choice2": "ntrials", "choice": "fraction" }, inplace=True) df2 = df2.groupby(['signed_contrast']).mean().reset_index() df2 = df2[['signed_contrast', 'ntrials', 'fraction']] # fit psychfunc pars, L = psy.mle_fit_psycho( df2.transpose().values, # extract the data from the df P_model='erf_psycho_2gammas', parstart=np.array([df2['signed_contrast'].mean(), 20., 0.05, 0.05]), parmin=np.array([df2['signed_contrast'].min(), 5, 0., 0.]), parmax=np.array([df2['signed_contrast'].max(), 100., 1, 1])) if line: # plot psychfunc sns.lineplot(np.arange(-29, 29), psy.erf_psycho_2gammas(pars, np.arange(-29, 29)), color=col, alpha=al, ax=ax) # plot psychfunc: -100, +100 sns.lineplot(np.arange(-37, -32), psy.erf_psycho_2gammas(pars, np.arange(-103, -98)), color=col, alpha=al, ax=ax) sns.lineplot(np.arange(32, 37), psy.erf_psycho_2gammas(pars, np.arange(98, 103)), color=col, alpha=al, ax=ax) # now break the x-axis # if 100 in df.signed_contrast.values and not 50 in # df.signed_contrast.values: df['signed_contrast'] = df['signed_contrast'].replace(-100, -35) df['signed_contrast'] = df['signed_contrast'].replace(100, 35) # PLOT DATAPOINTS if point == True: sns.lineplot(df['signed_contrast'], df['choice'], err_style="bars", linewidth=0, linestyle='None', mew=0.5, marker=mark, ci=95, color=col, alpha=al, markersize=3, ax=ax) ax.set_xticks([-35, -25, -12.5, 0, 12.5, 25, 35]) ax.set_xticklabels(['-100', ' ', ' ', '0', ' ', ' ', '100'], size='small', rotation=45) ax.set_xlim([-40, 40]) ax.set_ylim([0, 1]) ax.set_yticks([0, 0.25, 0.5, 0.75, 1]) ax.set_yticklabels(['0', ' ', '50', ' ', '100'])
def plot_psychos_from_spots(spotlists, psychoSpotData, spots, labels): """ Function to plot three psychometrics on a single plot, with the dots that they were generated with. usually I'll use this with a group of left spots, a group of right spots and a group of control spots. Inputs: spotlists: a list of length 3 that has the spots """ Psycho1 = [[],[0 for i in range(len(psychoSpotData[0][1]))],[np.nan for i in range(len(psychoSpotData[0][1]))]] Psycho2 = [[],[0 for i in range(len(psychoSpotData[0][1]))],[np.nan for i in range(len(psychoSpotData[0][1]))]] controlSpotPsycho = [[],[0 for i in range(len(psychoSpotData[0][1]))],[np.nan for i in range(len(psychoSpotData[0][1]))]] for i in range(len(spots)): spotX = spots.iloc[i, [0]][0] spotY = spots.iloc[i, [1]][0] for leftSpots in range(len(visLeftSpots)): if spotX == visLeftSpots[leftSpots][0] and spotY == visLeftSpots[leftSpots][1]: Psycho1[0] = psychoSpotData[i][0] Psycho1[1] = [i+j for i,j in zip(psychoSpotData[i][1],Psycho1[1])] Psycho1[2] = [np.nanmean([i,j]) for i,j in zip(psychoSpotData[i][2],Psycho1[2])] for rightSpots in range(len(visRightSpots)): if spotX == visRightSpots[rightSpots][0] and spotY == visRightSpots[rightSpots][1]: Psycho2[0] = psychoSpotData[i][0] Psycho2[1] = [i+j for i,j in zip(psychoSpotData[i][1],Psycho2[1])] Psycho2[2] = [np.nanmean([i,j]) for i,j in zip(psychoSpotData[i][2],Psycho2[2])] for ii in controlSpots: if spotX == spots.iloc[ii][0] and spotY == spots.iloc[ii][1]: controlSpotPsycho[0] = psychoSpotData[ii][0] controlSpotPsycho[1] = [i+j for i,j in zip(psychoSpotData[ii][1],controlSpotPsycho[1])] controlSpotPsycho[2] = [np.nanmean([i,j]) for i,j in zip(psychoSpotData[ii][2],controlSpotPsycho[2])] ## plotting psychometrics for different cortical groups lines = [] print('Fitting psychometrics, {}/{} Done'.format(subIdx,len(data))) fig = plt.figure() dotColors = ['.b','.r','.g'] lineColors = ['-b','-r','-g'] plotCount = 0 for psychoPlot in [Psycho1, Psycho2, controlSpotPsycho]: fitParams = [] fitLikes = [] for repeat in range(10): params, L = psychofit.mle_fit_psycho(psychoPlot, P_model='erf_psycho_2gammas', parstart=np.array([0,20,.05,.05]), parmin=np.array([-5, 0., 0., 0.]), parmax=np.array([5, 100., 1, 1]), nfits=50) fitParams.append(params) fitLikes.append(L) # find the best params (with the lowest neg likelihood) params = fitParams[np.where(min(fitLikes))[0][0]] spotBias[i][subIdx] = params[0] spotSlope[i][subIdx] = params[1] spotLapseLow[i][subIdx] = params[2] spotLapseHigh[i][subIdx] = params[3] spotFits.append(params) #plot the psychometrics fitx = np.linspace(-1, 1, 100) fity = psychofit.erf_psycho_2gammas(params, fitx) line = plt.plot(fitx, fity, lineColors[plotCount]) lines.append(line) plt.plot(psychoPlot[0], np.array(psychoPlot[2]), dotColors[plotCount]) plotCount+=1 ## Formatting the psychometric figure LvisLine = mpl.lines.Line2D([],[],color='blue',marker='.',label=labels[0]) RvisLine = mpl.lines.Line2D([],[],color='red',marker='.',label=labels[1]) cLine = mpl.lines.Line2D([],[],color='green',marker='.',label=labels[2]) plt.legend(handles=[LvisLine,RvisLine,cLine])
## plotting psychometrics for different cortical groups lines = [] print('Fitting psychometrics, {}/{} Done'.format(subIdx,len(data))) fig,axs = plt.subplots(nrows=2,ncols=2) ax=axs[0,0] dotColors = ['.r','.b','.g'] lineColors = ['-r','-b','-g'] plotCount = 0 for psychoPlot in [visLeftPsycho, visRightPsycho, controlSpotPsycho]: fitParams = [] fitLikes = [] for repeat in range(10): params, L = psychofit.mle_fit_psycho(psychoPlot, P_model='erf_psycho_2gammas', parstart=np.array([0,20,.05,.05]), parmin=np.array([-5, 0., 0., 0.]), parmax=np.array([5, 100., 1, 1]), nfits=50) fitParams.append(params) fitLikes.append(L) # find the best params (with the lowest neg likelihood) params = fitParams[np.where(min(fitLikes))[0][0]] spotBias[i][subIdx] = params[0] spotSlope[i][subIdx] = params[1] spotLapseLow[i][subIdx] = params[2] spotLapseHigh[i][subIdx] = params[3] spotFits.append(params) #plot the psychometrics fitx = np.linspace(-1, 1, 100) fity = psychofit.erf_psycho_2gammas(params, fitx)
def model_psychometric_history(behav): select = behav.copy() select['t-1'] = select['trial_feedback_type'].shift(periods=1).to_numpy() select.loc[select['choice'] == -1, 'choice'] = 0 select = select.iloc[1:,:] #select['t-1'].fillna(0, inplace=True) select['t-1'] = select['t-1'].astype(int) plot_psychometric(select.loc[select['signed_contrast'], select.loc[select['probabilityLeft'] ==i, 'signed_contrast'], palette = ['red', 'green'], ci = 68) sns.lineplot(data = select_50, hue = 't-1', x = select_50['signed_contrast'], y = select_50['simulation_prob'], palette = ['red', 'green'], ci = 68) ## Functions def run_glm(behav, example, correction = True, bias = False, cross_validation = True): for i, nickname in enumerate(np.unique(behav['subject_nickname'])): if np.mod(i+1, 10) == 0: print('Loading data of subject %d of %d' % (i+1, len( np.unique(behav['subject_nickname'])))) # Get the trials of the sessions around criterion trials = behav.loc[behav['subject_nickname'] == nickname].copy() if bias == True: neutral_n = fit_psychfunc(behav[(behav['subject_nickname'] == nickname) & (behav['probabilityLeft'] == 50)]) left_fit = fit_psychfunc(behav[(behav['subject_nickname'] == nickname) & (behav['probabilityLeft'] == 80)]) right_fit = fit_psychfunc(behav[(behav['subject_nickname'] == nickname) & (behav['probabilityLeft'] == 20)]) behav.loc[behav['subject_nickname'] == nickname, 'bias_n'] = \ neutral_n.loc[0, 'bias'] behav.loc[behav['subject_nickname'] == nickname, 'bias_r'] = \ right_fit.loc[0, 'bias'] behav.loc[behav['subject_nickname'] == nickname, 'bias_l'] = \ left_fit.loc[0, 'bias'] else: fit_df = dj2pandas(trials.copy()) fit_result = fit_psychfunc(fit_df) behav.loc[behav['subject_nickname'] == nickname, 'threshold'] = \ fit_result.loc[0, 'threshold'] ## GLM #make separate datafrme data = trials[['index', 'trial_feedback_type', 'signed_contrast', 'choice', 'probabilityLeft']].copy() #drop trials with odd probabilities of left data.drop( data['probabilityLeft'][~data['probabilityLeft'].isin([50,20,80])].index, inplace=True) # Rewardeded choices: data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()), 'rchoice'] = 0 # NoGo trials data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == -1), 'rchoice'] = 0 data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == 1), 'rchoice'] = -1 data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == 1), 'rchoice'] = 1 data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()) , 'rchoice'] = 0 # NoGo trials data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == -1), 'rchoice'] = 0 # Unrewarded choices: data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()), 'uchoice'] = 0 # NoGo trials data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == -1), 'uchoice'] = -1 data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == 1), 'uchoice'] = 0 data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == 1), 'uchoice'] = 0 data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()) , 'uchoice'] = 0 # NoGo trials data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == -1) , 'uchoice'] = 1 # Apply correction if correction == True: data['rchoice+1'] = \ data['rchoice'].shift(periods=-1).to_numpy() data['uchoice+1'] = \ data['uchoice'].shift(periods=-1).to_numpy() # Shift rewarded and unrewarded predictors by one data.loc[:, ['rchoice', 'uchoice']] = \ data[['rchoice', 'uchoice']].shift(periods=1).to_numpy() # Drop any nan trials data.dropna(inplace=True) # Make sensory predictors (no 0 predictor) contrasts = [ 25, 100, 12.5, 6.25] for i in contrasts: data.loc[(data['signed_contrast'].abs() == i), i] = \ np.sign(data.loc[(data['signed_contrast'].abs() == i), 'signed_contrast'].to_numpy()) data_con = data[i].copy() data[i] = data_con.fillna(0) # If contrast missing break for i in contrasts: if np.sum(data[i]) == 0: print('missing contrast') missing_contrast = True else: missing_contrast = False if missing_contrast == True: continue # Make block identity (across predictors right is positive, hence logic below) if bias == True: data.loc[(data['probabilityLeft'] == 50), 'block'] = 0 data.loc[(data['probabilityLeft'] == 20), 'block'] = 1 data.loc[(data['probabilityLeft'] == 80), 'block'] = -1 # Make choice in between 0 and 1 -> 1 for right and 0 for left data.loc[data['choice'] == -1, 'choice'] = 0 # Store index index = data['index'].copy() # Create predictor matrix endog = data['choice'].copy() exog = data.copy() exog.drop(columns=['trial_feedback_type', 'signed_contrast', 'choice', 'probabilityLeft'], inplace=True) exog = sm.add_constant(exog) if cross_validation == False: X_train = exog.copy() X_test = exog.copy() y_train = endog.copy() y_test = endog.copy() else: X_train = exog.iloc[:int(len(exog)*0.70),:].copy() X_test = exog.iloc[int(len(endog)*0.70):,:].copy() y_train = endog.iloc[:int(len(endog)*0.70)].copy() y_test = endog.iloc[int(len(endog)*0.70):].copy() # Store index index = X_test['index'].to_numpy() X_train.drop(columns=['index'], inplace=True) X_test.drop(columns=['index'], inplace=True) # Fit model try: logit_model = sm.Logit(y_train, X_train) result = logit_model.fit_regularized() # print(result.summary2()) # Store model weights behav.loc[behav['subject_nickname'] == nickname, 'intercept'] = result.params['const'].copy() behav.loc[behav['subject_nickname'] == nickname, 'rchoice'] = result.params['rchoice'].copy() behav.loc[behav['subject_nickname'] == nickname, 'uchoice'] = result.params['uchoice'].copy() mask = result.params.index.get_level_values(0) behav.loc[behav['subject_nickname'] == nickname, '25'] = result.params[25].copy() behav.loc[behav['subject_nickname'] == nickname, '6'] = result.params.loc[mask == 6.25][0] behav.loc[behav['subject_nickname'] == nickname, '100'] = result.params[100].copy() behav.loc[behav['subject_nickname'] == nickname, '12'] = result.params.loc[mask == 12.5][0] if bias == True: behav.loc[behav['subject_nickname'] == nickname, 'block'] = result.params['block'].copy() if correction == True: behav.loc[behav['subject_nickname'] == nickname, 'rchoice+1'] = result.params['rchoice+1'].copy() behav.loc[behav['subject_nickname'] == nickname, 'uchoice+1'] = result.params['uchoice+1'].copy() # Probabilities on test data prob = result.predict(X_test).to_numpy() if nickname == example: example_model = result # Propagate to storing dataframe behav.loc[behav['index'].isin(index), 'simulation_prob'] = prob except: print('singular matrix') return behav, example_model def data_2_X_test (behav, correction = True, bias = True): data = behav[['index','trial_feedback_type', 'signed_contrast', 'choice', 'probabilityLeft']].copy() #drop trials with odd probabilities of left data.drop( data['probabilityLeft'][~data['probabilityLeft'].isin([50,20,80])].index, inplace=True) # Rewardeded choices: data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()), 'rchoice'] = 0 # NoGo trials data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == -1), 'rchoice'] = 0 data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == 1), 'rchoice'] = -1 data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == 1), 'rchoice'] = 1 data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()) , 'rchoice'] = 0 # NoGo trials data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == -1), 'rchoice'] = 0 # Unrewarded choices: data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()), 'uchoice'] = 0 # NoGo trials data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == -1), 'uchoice'] = -1 data.loc[(data['choice'] == -1) & (data['trial_feedback_type'] == 1), 'uchoice'] = 0 data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == 1), 'uchoice'] = 0 data.loc[(data['choice'] == 0) & (data['trial_feedback_type'].isnull()) , 'uchoice'] = 0 # NoGo trials data.loc[(data['choice'] == 1) & (data['trial_feedback_type'] == -1) , 'uchoice'] = 1 # Apply correction if correction == True: data['rchoice+1'] = \ data['rchoice'].shift(periods=-1).to_numpy() data['uchoice+1'] = \ data['uchoice'].shift(periods=-1).to_numpy() # Shift rewarded and unrewarded predictors by one data.loc[:, ['rchoice', 'uchoice']] = \ data[['rchoice', 'uchoice']].shift(periods=1).to_numpy() # Drop any nan trials data.dropna(inplace=True) # Make sensory predictors (no 0 predictor) contrasts = [ 25, 100, 12.5, 6.25] for i in contrasts: data.loc[(data['signed_contrast'].abs() == i), i] = \ np.sign(data.loc[(data['signed_contrast'].abs() == i), 'signed_contrast'].to_numpy()) data_con = data[i].copy() data[i] = data_con.fillna(0) # Make block identity (across predictors right is positive, hence logic below) if bias == True: data.loc[(data['probabilityLeft'] == 50), 'block'] = 0 data.loc[(data['probabilityLeft'] == 20), 'block'] = 1 data.loc[(data['probabilityLeft'] == 80), 'block'] = -1 # Make choice in between 0 and 1 -> 1 for right and 0 for left data.loc[data['choice'] == -1, 'choice'] = 0 index = data['index'].copy() # Create predictor matrix endog = data['choice'].copy() exog = data.copy() exog.drop(columns=['index', 'trial_feedback_type', 'signed_contrast', 'choice', 'probabilityLeft'], inplace=True) exog = sm.add_constant(exog) return exog, index def plot_psychometric(x, y, col, point = False, mark = 'o', al =1): # summary stats - average psychfunc over observers df = pd.DataFrame({'signed_contrast': x, 'choice': y, 'choice2': y}) df2 = df.groupby(['signed_contrast']).agg( {'choice2': 'count', 'choice': 'mean'}).reset_index() df2.rename(columns={"choice2": "ntrials", "choice": "fraction"}, inplace=True) df2 = df2.groupby(['signed_contrast']).mean().reset_index() df2 = df2[['signed_contrast', 'ntrials', 'fraction']] # fit psychfunc pars, L = psy.mle_fit_psycho(df2.transpose().values, # extract the data from the df P_model='erf_psycho_2gammas', parstart=np.array( [df2['signed_contrast'].mean(), 20., 0.05, 0.05]), parmin=np.array( [df2['signed_contrast'].min(), 5, 0., 0.]), parmax=np.array([df2['signed_contrast'].max(), 100., 1, 1])) # plot psychfunc g = sns.lineplot(np.arange(-29, 29), psy.erf_psycho_2gammas(pars, np.arange(-29, 29)), color = col, alpha = al) # plot psychfunc: -100, +100 sns.lineplot(np.arange(-37, -32), psy.erf_psycho_2gammas(pars, np.arange(-103, -98)), color = col, alpha = al) sns.lineplot(np.arange(32, 37), psy.erf_psycho_2gammas(pars, np.arange(98, 103)), color = col, alpha = al) # now break the x-axis # if 100 in df.signed_contrast.values and not 50 in # df.signed_contrast.values: df['signed_contrast'] = df['signed_contrast'].replace(-100, -35) df['signed_contrast'] = df['signed_contrast'].replace(100, 35) if point == True: sns.lineplot(df['signed_contrast'], df['choice'], err_style="bars", linewidth=0, linestyle='None', mew=0.5, marker=mark, ci=68, color = col, alpha = al) g.set_xticks([-35, -25, -12.5, 0, 12.5, 25, 35]) g.set_xticklabels(['-100', '-25', '-12.5', '0', '12.5', '25', '100'], size='small', rotation=45) g.set_xlim([-40, 40]) g.set_ylim([0, 1]) g.set_yticks([0, 0.25, 0.5, 0.75, 1]) g.set_yticklabels(['0', '25', '50', '75', '100']) # FUNCTION UNDER DEVELOPMENT def updating: select = behav.copy() select['signed_contrast-1'] = select['signed_contrast'].shift(periods=1).to_numpy() select['signed_contrast+1'] = select['signed_contrast'].shift(periods=-1).to_numpy() select['t-1'] = select['trial_feedback_type'].shift(periods=1).to_numpy() select['t+1'] = select['trial_feedback_type'].shift(periods=-1).to_numpy() select = select.iloc[1:-1,:] # First and last trial will have nan for history select['t-1'] = select['t-1'].astype(int) select['simulation_prob'] = select['simulation_prob']*100 #select = select.loc[select['signed_contrast-1'] >= 0] for mouse in select['subject_nickname'].unique(): for c in select['signed_contrast-1'].unique(): for r in select['t-1'].unique(): sub_select = select.loc[(select['signed_contrast-1'] == c) & (select['t-1'] == r) & (select['subject_nickname'] == mouse)] fit_result = fit_psychfunc(sub_select) select.loc[select['subject_nickname'] == nickname, 'updating'] = \ fit_result['bias'][0] for c in select['signed_contrast+1'].unique(): for r in select['t+1'].unique(): sub_select = select.loc[(select['signed_contrast+1'] == c) & (select['t+1'] == r) & (select['subject_nickname'] == mouse)] fit_result = fit_psychfunc(sub_select) select.loc[select['subject_nickname'] == nickname, 'updating_correction'] = \ fit_result['bias'][0] sns.lineplot(data = select, hue = 't-1', x = select['signed_contrast-1'], y = select['simulation_prob'], ci = 68) sns.lineplot(data = select, hue = 't-1', x = select['signed_contrast-1'], y = select[select['probabilityLeft'] == 80],'choice'] - select.loc[select['probabilityLeft'] == 20 ,'choice'])
def plot_psychometric(x, y, subj, **kwargs): # summary stats - average psychfunc over observers df = pd.DataFrame({ 'signed_contrast': x, 'choice': y, 'choice2': y, 'subject_nickname': subj }) df2 = df.groupby(['signed_contrast', 'subject_nickname']).agg({ 'choice2': 'count', 'choice': 'mean' }).reset_index() df2.rename(columns={ "choice2": "ntrials", "choice": "fraction" }, inplace=True) df2 = df2.groupby(['signed_contrast']).mean().reset_index() df2 = df2[['signed_contrast', 'ntrials', 'fraction']] # fit psychfunc pars, L = psy.mle_fit_psycho( df2.transpose().values, # extract the data from the df P_model='erf_psycho_2gammas', parstart=np.array([df2['signed_contrast'].mean(), 20., 0.05, 0.05]), parmin=np.array([df2['signed_contrast'].min(), 0., 0., 0.]), parmax=np.array([df2['signed_contrast'].max(), 100., 0.5, 0.5])) # plot psychfunc sns.lineplot(np.arange(-29, 29), psy.erf_psycho_2gammas(pars, np.arange(-29, 29)), **kwargs) # plot psychfunc: -100 sns.lineplot(np.arange(-37, -32), psy.erf_psycho_2gammas(pars, np.arange(-103, -98)), **kwargs) sns.lineplot(np.arange(32, 37), psy.erf_psycho_2gammas(pars, np.arange(98, 103)), **kwargs) # now break the x-axis # if 100 in df.signed_contrast.values and not 50 in df.signed_contrast.values: df['signed_contrast'] = df['signed_contrast'].replace(-100, -35) df['signed_contrast'] = df['signed_contrast'].replace(100, 35) df3 = df.groupby(['signed_contrast', 'subject_nickname']).agg({ 'choice2': 'count', 'choice': 'mean' }).reset_index() # plot datapoints with errorbars on top g = sns.lineplot(df3['signed_contrast'], df3['choice'], err_style="bars", linewidth=0, linestyle='None', mew=0.5, marker='o', ci=68, **kwargs) g.set_yticks([0, 0.25, 0.5, 0.75, 1]) # # ADD TEXT WITH THE PSYCHOMETRIC FUNCTION PARAMETERS # if len(df['subject_nickname'].unique()) == 1: # try: # # add text with parameters into the plot # if kwargs['label'] == '50': # ypos = 0.5 # # ADD PSYCHOMETRIC FUNCTION PARAMS # plt.text(-35, ypos, r'$\mu\/ %.2f,\/ \sigma\/ %.2f,$'%(pars[0], pars[1]) + '\n' + r'$\gamma \/%.2f,\/ \lambda\/ %.2f$'%(pars[2], pars[3]), # fontweight='normal', fontsize=5, color=kwargs['color']) # elif kwargs['label'] == '20': # ypos = 0.3 # # ADD PSYCHOMETRIC FUNCTION PARAMS # plt.text(-35, ypos, r'$\mu\/ %.2f,\/ \sigma\/ %.2f,$'%(pars[0], pars[1]) + '\n' + r'$\gamma \/%.2f,\/ \lambda\/ %.2f$'%(pars[2], pars[3]), # fontweight='normal', fontsize=5, color=kwargs['color']) # elif kwargs['label'] == '80': # ypos = 0.7 # # ADD PSYCHOMETRIC FUNCTION PARAMS # plt.text(-35, ypos, r'$\mu\/ %.2f,\/ \sigma\/ %.2f,$'%(pars[0], pars[1]) + '\n' + r'$\gamma \/%.2f,\/ \lambda\/ %.2f$'%(pars[2], pars[3]), # fontweight='normal', fontsize=5, color=kwargs['color']) # except: # when there is no label # # pass # ypos = 0.5 # # ADD PSYCHOMETRIC FUNCTION PARAMS # plt.text(-35, ypos, r'$\mu\/ %.2f,\/ \sigma\/ %.2f,$'%(pars[0], pars[1]) + '\n' + r'$\gamma \/%.2f,\/ \lambda\/ %.2f$'%(pars[2], pars[3]), # fontweight='normal', fontsize=8, color=kwargs['color']) # # plt.text(12, 0.1, '1 mouse', fontsize=10, color='k') # print the number of mice if df['subject_nickname'].nunique() == 1: plt.text(12, 0.1, '1 mouse', fontsize=10, color='k') else: plt.text(12, 0.1, '%d mice' % (df['subject_nickname'].nunique()), fontsize=10, color='k') #if brokenXaxis: g.set_xticks([-35, -25, -12.5, 0, 12.5, 25, 35]) g.set_xticklabels(['-100', '-25', '-12.5', '0', '12.5', '25', '100'], size='small', rotation=45) g.set_xlim([-40, 40]) g.set_ylim([0, 1]) g.set_yticks([0, 0.25, 0.5, 0.75, 1]) g.set_yticklabels(['0', '25', '50', '75', '100'])
def plot_psychometric(x, y, subj, **kwargs): # summary stats - average psychfunc over observers df = pd.DataFrame({'signed_contrast': x, 'choice': y, 'choice2': y, 'subject_nickname': subj}) df2 = df.groupby(['signed_contrast', 'subject_nickname']).agg( {'choice2': 'count', 'choice': 'mean'}).reset_index() df2.rename(columns={"choice2": "ntrials", "choice": "fraction"}, inplace=True) df2 = df2.groupby(['signed_contrast']).mean().reset_index() df2 = df2[['signed_contrast', 'ntrials', 'fraction']] # only 'break' the x-axis and remove 50% contrast when 0% is present # print(df2.signed_contrast.unique()) if 0. in df2.signed_contrast.values: brokenXaxis = True else: brokenXaxis = False # fit psychfunc pars, L = psy.mle_fit_psycho(df2.transpose().values, # extract the data from the df P_model='erf_psycho_2gammas', parstart=np.array( [0, 20., 0.05, 0.05]), parmin=np.array( [df2['signed_contrast'].min(), 5, 0., 0.]), parmax=np.array([df2['signed_contrast'].max(), 40., 1, 1])) if brokenXaxis: # plot psychfunc g = sns.lineplot(np.arange(-29, 29), psy.erf_psycho_2gammas(pars, np.arange(-29, 29)), **kwargs) # plot psychfunc: -100, +100 sns.lineplot(np.arange(-37, -32), psy.erf_psycho_2gammas(pars, np.arange(-103, -98)), **kwargs) sns.lineplot(np.arange(32, 37), psy.erf_psycho_2gammas(pars, np.arange(98, 103)), **kwargs) # now break the x-axis # if 100 in df.signed_contrast.values and not 50 in # df.signed_contrast.values: df['signed_contrast'] = df['signed_contrast'].replace(-100, -35) df['signed_contrast'] = df['signed_contrast'].replace(100, 35) else: # plot psychfunc g = sns.lineplot(np.arange(-103, 103), psy.erf_psycho_2gammas(pars, np.arange(-103, 103)), **kwargs) df3 = df.groupby(['signed_contrast', 'subject_nickname']).agg( {'choice2': 'count', 'choice': 'mean'}).reset_index() # plot datapoints with errorbars on top if df['subject_nickname'].nunique() > 1: sns.lineplot(df3['signed_contrast'], df3['choice'], err_style="bars", linewidth=0, linestyle='None', mew=0.5, marker='o', ci=68, **kwargs) if brokenXaxis: g.set_xticks([-35, -25, -12.5, 0, 12.5, 25, 35]) g.set_xticklabels(['-100', '-25', '-12.5', '0', '12.5', '25', '100'], size='small', rotation=60) g.set_xlim([-40, 40]) else: g.set_xticks([-100, -50, 0, 50, 100]) g.set_xticklabels(['-100', '-50', '0', '50', '100'], size='small', rotation=60) g.set_xlim([-110, 110]) g.set_ylim([0, 1.02]) g.set_yticks([0, 0.25, 0.5, 0.75, 1]) g.set_yticklabels(['0', '25', '50', '75', '100'])