Redundant prefrontal hemispheres adapt storage strategy to working memory demands.
The 16 matches
- [1] § Methods › Neural data analysis › Gaussian mixture model ↔ Figure5/Figure5.ipynb, lines 1508–1543 · score 0.74 · Gaussian mixture, dual reactivations, reactivation strengths, covariance, classes, GMM
- [2] § Methods › Neural data analysis › Higher-capacity model ↔ Figure7/MultiitemModel_HigherCapacity.py, lines 224–273 · score 0.71 · weight profile, stimulus width, 6.1 deg, 14.4 deg, capacity, excitation
- [3] § Methods › Neural data analysis › Higher-capacity model ↔ Figure7/TwoAreaModelVaryingConnect_MultiitemParams.py, lines 216–265 · score 0.67 · weight profile, stimulus width, 6.1 deg, 14.4 deg, excitation, 50 deg
- [4] § Methods › Neural data analysis › Decoder-behavior error correlations ↔ Figure3/Figure3.ipynb, lines 1157–1222 · score 0.67 · circ_corrcc, circularly correlated, response errors, ipsilateral, contralateral, behavioral
- [5] § Methods › Behavioral data analysis › Serial dependence strength fit ↔ Figure5/Figure5.ipynb, lines 804–896 · score 0.66 · fit behavioral, decoder errors, dog1, serial dependence, OLS, neural
- [6] § Results › Each prefrontal hemisphere had private history effects, suggesting weak interhemispheric connectivity ↔ Figure5/Figure5.ipynb, lines 804–896 · score 0.65 · model fit, history drift, decoder error, dog1, serial dependence, OLS
- [7] § Methods › Neural data analysis › Single-trial decoders ↔ Figure3/Figure3_Figure5_SingleTrialDecoder.py, lines 1–24 · score 0.60 · single trial decoders, cross validation, Delay Decoder, reactivations, prediction
- [8] § Results › Both hemispheres equally reflected trial-by-trial behavioral variability with weakly correlated memory representations ↔ Figure3/Figure3_CorrelateEyeResponse.py, lines 103–191 · score 0.59 · eye movements, gaze errors, error correlations, monkeys, connections
- [9] § Methods › Behavioral data analysis › Determining the most-interfering non-target ↔ Supplement/Supplement_DoGSerialBias.py, lines 55–142 · score 0.55 · relative location, dog1, optimal, hyperparameter, BIC, sigma
- [10] § Methods › Neural data analysis › Cross-temporal decoders ↔ Figure2/Figure2_DecoderMatrix.py, lines 28–177 · score 0.55 · decoder matrices, linear regression, trained, model
- [11] § Methods › Neural data analysis › Uninstructed eye-movement controls ↔ Figure3/Figure3_CorrelateEyeResponse.py, lines 103–191 · score 0.54 · eye movements, error correlations, gaze
- [12] § Methods › Neural data analysis › Population decoding of stimulus location ↔ Figure2/Figure2_DecoderMatrix.py, lines 28–177 · score 0.54 · trained model, randomly shuffled, MSE, score, decoding
- [13] § Results › Each prefrontal hemisphere had private history effects, suggesting weak interhemispheric connectivity ↔ Figure5/Figure5.ipynb, lines 1342–1380 · score 0.53 · Gaussian Mixture, dual reactivations, class, GMM, diagonal, strength
- [14] § Methods › Neural data analysis › Bump-attractor model ↔ Figure7/TwoAreaModelVaryingConnect_MultiitemParams.py, lines 267–306 · score 0.52 · excitatory neurons, GABA, NMDA, AMPA, profile, std
- [15] § Methods › Behavioral data analysis › Serial dependence strength fit ↔ Supplement/Supplement_DoGSerialBias.py, lines 55–142 · score 0.52 · OLS model, dog1, derivative, serial, Gaussian, fit
- [16] § Results › Each hemisphere stably represented the full visual space with a contralateral preference ↔ Figure2/Figure2.ipynb, lines 549–638 · score 0.51 · contra ipsi, shorter stimulus, scored, shuffle, Pe, trained
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 1,797 lines · 82 KB · no license · 4 matches
- # %%
- %cd ..
- # %%
- import matplotlib.pyplot as plt
- plt.style.use("stylefile.mplstyle")
- # Define colors
- colors = {
- "Single": "#333333",
- "Left": "#246EB9",
- "Right": "#8B1E3F",
- "Ipsilateral": "#1B9E77",
- "Contralateral": "#D95F02",
- "Within": "#2B4162",
- "Across": "#E89D0B",
- 'SerialBiasWeak': '#254441',
- 'SerialBias': '#43AA8B',\
- 'ReactivationWeak': '#7A8A99',
- 'Reactivation': '#12719E'
- }
- from matplotlib.colors import LinearSegmentedColormap
- left_right_cmap = LinearSegmentedColormap.from_list("LeftRight",\
- [colors['Left'], "white", colors['Right']])
- ipsi_contra_cmap = LinearSegmentedColormap.from_list("IpsiContra",\
- [colors['Ipsilateral'], "white", colors['Contralateral']])
- within_cmap = LinearSegmentedColormap.from_list("Within",\
- ["white", colors['Within']])
- across_cmap = LinearSegmentedColormap.from_list("Across",\
- ["white", colors['Across']])
- # %%
- ## circstats
- def len2(x):
- if type(x) is not type([]):
- if type(x) is not type(array([])):
- return -1
- return len(x)
- def phase2(x):
- if not np.isnan(x):
- return phase(x)
- return nan
- def circdist(angles1,angles2):
- ''' calculates circular distance of angles [rad]'''
- if len2(angles2) < 0:
- if len2(angles1) > 0:
- angles2 = [angles2]*len(angles1)
- else:
- angles2 = [angles2]
- angles1 = [angles1]
- if len2(angles1) < 0:
- angles1 = [angles1]*len(angles2)
- return array(list(map(lambda a1,a2: phase2(np.exp(1j*a1)/np.exp(1j*a2)), angles1,angles2)))
- # %%
- def sig_bar(sigs,axis,y,ax,color):
- w=np.diff(axis)[0]
- for s in sigs:
- beg =axis[s]-w/2
- end = axis[s]+w/2
- ax.fill_between([beg,end],[y[0],y[0]],[y[1],y[1]],color=color)
- # %%
- def sign_rl(rel_loc):
- # returns sign of array unless value=0, returns 1
- sign = [np.sign(rel_loc[rl]) if rel_loc[rl]!=0.0 else 1 for rl in range(len(rel_loc))]
- return np.array(sign)
- # %%
- import numpy as np
- def plot_twolines_full(R=[],base=[], bins=0.2,labelR='',labelB='', errorbars='SEM', MeanType = np.nanmean,\
- borders=[], ylabel='decoding', yticks = False, titel='', significances=False,\
- currentTrial=False, end_border = 15, shorten_delay = 0,\
- refline =0, colors=[colors['Within'], colors['Across']]):
- offset_start = borders[1]-borders[0]
- x = (np.linspace(borders[1], borders[5]-shorten_delay, borders[5]-borders[1]-shorten_delay)-\
- (borders[2]-borders[1])-offset_start)*bins
- x3 = (np.linspace(borders[5]+shorten_delay, borders[8], borders[8]-borders[5]+1-shorten_delay)-\
- (borders[7]-borders[1])-offset_start)*bins
- if end_border < 8:# need end of one trial, start of next trial
- x3 = (np.linspace(borders[5]+shorten_delay, borders[end_border], borders[end_border]-borders[5]-shorten_delay)-\
- (borders[7]-borders[1])-offset_start)*bins
- width_radius = [len(x), len(x3)]
- # only add 3rd axis if we look at start of next trial
- if end_border > 12:
- end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-\
- (borders[13]-borders[1])-offset_start)*bins
- start_curr = (np.linspace(borders[12], borders[end_border], borders[end_border]-borders[12])-\
- (borders[13]-borders[1])-offset_start)*bins
- x5 = np.append(end_prev,start_curr)
- width_radius = [len(x), len(x3), len(x5)]# len(x3),
- elif end_border >=10:
- end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-offset_start)*bins
- x5 = end_prev
- width_radius = [len(x), len(x3), len(x5)]# len(x3),
- compare = 0.05#/len(np.append(np.append(x,x2),x5))
- # TTESTS:
- pvals = ttest_rel(R,base, axis=0, nan_policy='omit')[1]
- pvals1 = ttest_1samp(R, 0, axis=0, nan_policy='omit')[1]
- pvals2 = ttest_1samp(base, 0, axis=0, nan_policy='omit')[1]
- # compute mean and errorbars
- mean_R = MeanType(R, axis=0)
- mean_base = MeanType(base, axis=0)
- if MeanType == circmean:
- mean_R = MeanType(R, axis=0, low=-np.pi, high=np.pi)
- mean_base = MeanType(base, axis=0, low=-np.pi, high=np.pi)
- if errorbars=='CI':
- std_R = 2*sem(R, axis=0, nan_policy='omit')
- std_base = 2*sem(base, axis=0, nan_policy='omit')
- else:
- std_R = sem(R, axis=0, nan_policy='omit')
- std_base = sem(base, axis=0, nan_policy='omit')
- #f, (ax1,ax5) = plt.subplots(1, 2,sharey=True,figsize=(3.8,1.6), gridspec_kw={'width_ratios': width_radius})
- #f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(16,5), gridspec_kw={'width_ratios': width_radius})
- if end_border < 10:
- f, (ax1,ax3) = plt.subplots(1, 2,sharey=True,figsize=(3.,2), gridspec_kw={'width_ratios': width_radius})
- else:
- f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(3.5, 2), gridspec_kw={'width_ratios': width_radius})
- ###################### PLOT FIXTION TO DELAY PREVIOUS
- cut = range(borders[1], borders[5]-shorten_delay)
- # within
- ax1.plot(x,mean_R[cut], color=colors[0], label=labelR)#+', '+errorbars
- ax1.fill_between(x, mean_R[cut]-np.array(std_R[cut]), mean_R[cut]+np.array(std_R[cut]), color=colors[0], alpha=0.3)
- # across
- ax1.plot(x,mean_base[cut], color=colors[1], label=labelB)#+', '+errorbars
- ax1.fill_between(x, mean_base[cut]-np.array(std_base[cut]), mean_base[cut]+np.array(std_base[cut]),\
- color=colors[1], alpha=0.3)
- if currentTrial:
- ax1.set_xlabel('fix$_{n+1}$', labelpad=0)
- else:
- ax1.set_xlabel('fix$_{n}$', labelpad=0)
- ax1.spines['right'].set_visible(False)
- ax1.spines['top'].set_visible(False)
- ax1.xaxis.set_ticks_position('bottom')
- ax1.yaxis.set_ticks_position('left')
- ax1.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
- ax1.set_ylabel(ylabel)
- if yticks != False:
- ax1.set_yticks(yticks)
- if (len(np.unique(base)) != 1):
- ax1.legend()
- ###################### PLOT DELAY TO SACCADE PREVIOUS
- cut = range(borders[5]+shorten_delay, borders[8]+1)
- if end_border < 8:
- cut = range(borders[5]+shorten_delay, borders[end_border])
- # within
- ax3.plot(x3,mean_R[cut], color=colors[0], label=labelR)
- ax3.fill_between(x3, mean_R[cut]-np.array(std_R[cut]), mean_R[cut]+np.array(std_R[cut]), color=colors[0], alpha=0.3)
- # across
- ax3.plot(x3,mean_base[cut], color=colors[1], label=labelB)
- ax3.fill_between(x3, mean_base[cut]-np.array(std_base[cut]), mean_base[cut]+np.array(std_base[cut]), color=colors[1], alpha=0.3)
- if currentTrial:
- ax3.set_xlabel('response$_{n+1}$', labelpad=0)
- else:
- ax3.set_xlabel('response$_{n}$', labelpad=0)
- ax3.spines['right'].set_visible(False)
- ax3.spines['left'].set_visible(False)
- ax3.spines['top'].set_visible(False)
- ax3.xaxis.set_ticks_position('bottom')
- ax3.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
- #ax3.set_ylabel(ylabel)
- #ax3.set_yticks([0,0.1])
- ###################### PLOT ITI PREVIOUS TO DELAY CURRENT TRIAL
- if end_border >= 10:
- pc_base = mean_base[borders[9]:borders[10]+1]
- pcstd_base = std_base[borders[9]:borders[10]+1]
- pc_R = mean_R[borders[9]:borders[10]+1]
- pcstd_R = std_R[borders[9]:borders[10]+1]
- if end_border > 12:
- pc_base = np.append(mean_base[borders[9]:borders[10]+1],mean_base[borders[12]:borders[15]])
- pcstd_base = np.append(std_base[borders[9]:borders[10]+1],std_base[borders[12]:borders[15]])
- pc_R = np.append(mean_R[borders[9]:borders[10]+1],mean_R[borders[12]:borders[15]])
- pcstd_R = np.append(std_R[borders[9]:borders[10]+1],std_R[borders[12]:borders[15]])
- # plot reactivaton period
- # within
- ax5.plot(x5,pc_R, color=colors[0])
- ax5.fill_between(x5, pc_R-np.array(pcstd_R), pc_R+np.array(pcstd_R), color=colors[0], alpha=0.3)
- # across
- ax5.plot(x5,pc_base, color=colors[1])
- ax5.fill_between(x5, pc_base-np.array(pcstd_base), pc_base+np.array(pcstd_base), color=colors[1], alpha=0.3)
- # plot baseline
- ax5.set_xlabel('fix$_{n+1}$', labelpad=0)
- #ax5.set_xticks([])
- ax5.spines['right'].set_visible(False)
- ax5.spines['left'].set_visible(False)
- ax5.spines['top'].set_visible(False)
- ax5.xaxis.set_ticks_position('bottom')
- ax5.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
- y0=ax5.get_ylim()[0]
- y1=ax5.get_ylim()[1]
- ax5.fill_between([(borders[14]-borders[12])*bins, (borders[15]-borders[12])*bins],\
- y0, y1, color='grey', alpha=0.2)
- ###################### MARK IMPORTANT TIME PERIODS
- f.text(0.55, 0.02, 'time (s) from', ha='center', fontsize=8)
- # # # plot significances
- y0=ax3.get_ylim()[0]
- y1=ax3.get_ylim()[1]
- off = (y1-y0)/10
- marker=250
- ax1.fill_between([(borders[3]-borders[2])*bins, (borders[4]-borders[2])*bins], y0, y1, color='grey', alpha=0.2)
- w = (y1-y0)/50
- off = [y1-w, y1]
- sigs = np.where(pvals[borders[1]:borders[5]-shorten_delay]<compare)[0]
- sig_bar(sigs,x,off,ax1,'#333333')
- sigs = np.where(pvals[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
- sig_bar(sigs,x3,off,ax3,'#333333')
- if end_border >= 10:
- sigs = np.where(pvals[borders[9]:borders[10]+1]<compare)[0]
- sig_bar(sigs,x5,off,ax5,'#333333')
- f.text(0.23, 0.87, 'Stim$_{n}$', ha='center', fontsize=8)
- f.text(0.46, 0.87, 'Delay$_{n}$', ha='center', fontsize=8)
- f.text(0.85, 0.87, 'ITI$_{n}$', ha='center', fontsize=8)
- elif end_border > 12:
- sigs = np.where(np.append(pvals[borders[9]:borders[10]+1],pvals[borders[12]:borders[15]])<compare)[0]
- sig_bar(sigs,x5,off,ax5,'#333333')
- f.text(0.94, 0.87, 'Stim$_{n+1}$', ha='center', fontsize=8)
- else:
- f.text(0.29, 0.87, 'Stim$_{n}$', ha='center', fontsize=8)
- f.text(0.63, 0.87, 'Delay$_{n}$', ha='center', fontsize=8)
- if significances:
- # within area
- off = [y1-2.5*w, y1-1.5*w]
- sigs = np.where(pvals1[borders[1]:borders[5]-shorten_delay]<compare)[0]
- sig_bar(sigs,x,off,ax1,colors[0])
- sigs = np.where(pvals1[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
- sig_bar(sigs,x3,off,ax3,colors[0])
- if end_border >= 10:
- sigs = np.where(pvals1[borders[9]:borders[10]+1]<compare)[0]
- sig_bar(sigs,x5,off,ax5,colors[0])
- elif end_border > 12:
- sigs = np.where(np.append(pvals1[borders[9]:borders[10]+1],pvals1[borders[12]:borders[15]])<compare)[0]
- sig_bar(sigs,x5,off,ax5,colors[0])
- # across areas
- off = [y1-4*w, y1-3*w]
- sigs = np.where(pvals2[borders[1]:borders[5]-shorten_delay]<compare)[0]
- sig_bar(sigs,x,off,ax1,colors[1])
- sigs = np.where(pvals2[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
- sig_bar(sigs,x3,off,ax3,colors[1])
- if end_border >= 10:
- sigs = np.where(pvals2[borders[9]:borders[10]+1]<compare)[0]
- sig_bar(sigs,x5,off,ax5,colors[1])
- elif end_border > 12:
- sigs = np.where(np.append(pvals2[borders[9]:borders[10]+1],pvals2[borders[12]:borders[15]])<compare)[0]
- sig_bar(sigs,x5,off,ax5,colors[1])
- plt.suptitle(titel)
- return
- # %%
- def plot_threelines_full(R=[],base=[],base2=[], bins=200,labelR='',labelB='',labelB2='',\
- errorbars='SEM', MeanType = np.nanmean, shorten_delay = 0,\
- borders=[], ylabel='decoding', cutyaxis = False, titel='',\
- colors=['#333333', colors['Within'], colors['Across']]):
- offset_start = borders[1]-borders[0]
- x = (np.linspace(borders[1], borders[5]-shorten_delay, borders[5]-borders[1]+1-shorten_delay)-(borders[2]-borders[1])-offset_start)*bins
- x3 = (np.linspace(borders[5]+shorten_delay, borders[8], borders[8]-borders[5]+1-shorten_delay)-(borders[7]-1-borders[1])-offset_start)*bins
- # need end of one trial, start of next trial
- end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-(borders[13]-borders[1])-offset_start)*bins
- start_curr = (np.linspace(borders[12], borders[15], borders[15]-borders[12])-(borders[13]-borders[1])-offset_start)*bins
- x5 = np.append(end_prev,start_curr)
- len(x5)
- width_radius = [len(x), len(x3), len(x5)]# len(x3),
- compare = 0.05#/len(np.append(np.append(x,x2),x5))
- # TTESTS:
- pvals = ttest_rel(R,base, axis=0, nan_policy='omit')[1]
- pvals1 = ttest_1samp(R, 0, axis=0, nan_policy='omit')[1]
- pvals2 = ttest_1samp(base, 0, axis=0, nan_policy='omit')[1]
- mean_R = MeanType(R, axis=0)
- mean_base = MeanType(base, axis=0)
- mean_base2 = MeanType(base2, axis=0)
- if MeanType == circmean:
- mean_R = MeanType(R, axis=0, low=-np.pi, high=np.pi)
- mean_base = MeanType(base, axis=0, low=-np.pi, high=np.pi)
- mean_base2 = MeanType(base2, axis=0, low=-np.pi, high=np.pi)
- if errorbars=='CI':
- std_R = 2*sem(R, axis=0, nan_policy='omit')
- std_base = 2*sem(base, axis=0, nan_policy='omit')
- std_base2 = 2*sem(base2, axis=0, nan_policy='omit')
- else:
- std_R = sem(R, axis=0, nan_policy='omit')
- std_base = sem(base, axis=0, nan_policy='omit')
- std_base2 = sem(base2, axis=0, nan_policy='omit')
- f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(4.8, 2.1), gridspec_kw={'width_ratios': width_radius})
- ###################### PLOT FIXTION TO DELAY PREVIOUS
- cut = range(borders[1], borders[5]+1-shorten_delay)
- # within
- ax1.plot(x,mean_base[cut], color=colors[1], label=labelB)#+', '+errorbars
- ax1.fill_between(x, mean_base[cut]-np.array(std_base[cut]), mean_base[cut]+np.array(std_base[cut]), color=colors[1], alpha=0.2)
- # base2
- ax1.plot(x,mean_base2[cut], color=colors[2], label=labelB2)#+', '+errorbars
- ax1.fill_between(x, mean_base2[cut]-np.array(std_base2[cut]), mean_base2[cut]+np.array(std_base2[cut]), color=colors[2], alpha=0.2)
- # across
- ax1.plot(x,mean_R[cut], color=colors[0], label=labelR)#+', '+errorbars
- ax1.fill_between(x, mean_R[cut]-np.array(std_R[cut]), mean_R[cut]+np.array(std_R[cut]), color=colors[0], alpha=0.2)
- ax1.set_xlabel('fix$_{n}$', labelpad=0)
- ax1.spines['right'].set_visible(False)
- ax1.spines['top'].set_visible(False)
- ax1.xaxis.set_ticks_position('bottom')
- ax1.yaxis.set_ticks_position('left')
- ax1.axhline(0, color='#333333', linestyle='--', alpha=0.7)
- ax1.set_ylabel(ylabel)
- ax1.legend()
- # ###################### PLOT DELAY TO SACCADE PREVIOUS
- cut = range(borders[5]+shorten_delay, borders[8]+1)
- # within
- ax3.plot(x3,mean_base[cut], color=colors[1], label=labelB)
- ax3.fill_between(x3, mean_base[cut]-np.array(std_base[cut]), mean_base[cut]+np.array(std_base[cut]), color=colors[1], alpha=0.2)
- # base2
- ax3.plot(x3,mean_base2[cut], color=colors[2], label=labelB2)
- ax3.fill_between(x3, mean_base2[cut]-np.array(std_base2[cut]), mean_base2[cut]+np.array(std_base2[cut]), color=colors[2], alpha=0.2)
- # across
- ax3.plot(x3,mean_R[cut], color=colors[0], label=labelR)
- ax3.fill_between(x3, mean_R[cut]-np.array(std_R[cut]), mean_R[cut]+np.array(std_R[cut]), color=colors[0], alpha=0.2)
- ax3.set_xlabel('response$_{n}$', labelpad=0)
- ax3.spines['right'].set_visible(False)
- ax3.spines['left'].set_visible(False)
- ax3.spines['top'].set_visible(False)
- ax3.xaxis.set_ticks_position('bottom')
- ax3.axhline(0, color='#333333', linestyle='--', alpha=0.7)
- ###################### PLOT ITI PREVIOUS TO DELAY CURRENT TRIAL
- pc_base = np.append(mean_base[borders[9]:borders[10]+1],mean_base[borders[12]:borders[15]])
- std_base = np.append(std_base[borders[9]:borders[10]+1],std_base[borders[12]:borders[15]])
- pc_base2 = np.append(mean_base2[borders[9]:borders[10]+1],mean_base2[borders[12]:borders[15]])
- std_base2 = np.append(std_base2[borders[9]:borders[10]+1],std_base2[borders[12]:borders[15]])
- pc_R = np.append(mean_R[borders[9]:borders[10]+1],mean_R[borders[12]:borders[15]])
- std_R = np.append(std_R[borders[9]:borders[10]+1],std_R[borders[12]:borders[15]])
- # plot
- # within
- ax5.plot(x5,pc_base, color=colors[1])
- ax5.fill_between(x5, pc_base-np.array(std_base), pc_base+np.array(std_base), color=colors[1], alpha=0.2)
- # base2
- ax5.plot(x5,pc_base2, color=colors[2])
- ax5.fill_between(x5, pc_base2-np.array(std_base2), pc_base2+np.array(std_base2), color=colors[2], alpha=0.2)
- # across
- ax5.plot(x5,pc_R, color=colors[0])
- ax5.fill_between(x5, pc_R-np.array(std_R), pc_R+np.array(std_R), color=colors[0], alpha=0.2)
- # plot baseline
- ax5.set_xlabel('fix$_{n+1}$', labelpad=0)
- #ax5.set_xticks([])
- ax5.spines['right'].set_visible(False)
- ax5.spines['left'].set_visible(False)
- ax5.spines['top'].set_visible(False)
- ax5.xaxis.set_ticks_position('bottom')
- ax5.axhline(0, color='#333333', linestyle='--', alpha=0.7)
- if cutyaxis != False:
- ax5.set_yticks(cutyaxis)
- ax5.set_ylim(cutyaxis)
- ###################### MARK IMPORTANT TIME PERIODS
- y0=ax5.get_ylim()[0]
- y1=ax5.get_ylim()[1]
- off = (y1-y0)/10
- #marker=30
- marker=250
- # ax1.plot(0, color='midnightblue', alpha=0.5)
- ax1.fill_between([(borders[3]-borders[2])*bins, (borders[4]-borders[2])*bins], y0, y1, color='grey', alpha=0.2)
- ax3.plot(0, color='#A0B2A6', alpha=0.5)
- ax5.plot(0, color='midnightblue', alpha=0.5)
- ax5.fill_between([(borders[14]-borders[12])*bins, (borders[15]-borders[12])*bins], y0, y1, color='grey', alpha=0.2)
- f.text(0.55, -0., 'time (ms) from', ha='center', fontsize=12)
- f.text(0.23, 0.75, 'Stim$_{n}$', ha='center', fontsize=10)
- f.text(0.46, 0.75, 'Delay$_{n}$', ha='center', fontsize=10)
- f.text(0.85, 0.75, 'ITI$_{n}$', ha='center', fontsize=10)
- f.text(0.94, 0.75, 'Stim$_{n+1}$', ha='center', fontsize=10)
- plt.suptitle(titel)
- return
- # %% [markdown]
- # # LOAD DATA
- # %%
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sns
- import scipy
- from scipy.io import loadmat
- from scipy.stats import *
- from scipy.optimize import curve_fit
- from cmath import phase
- from numpy import array
- from scipy.sparse import csr_matrix
- import urllib
- import pickle
- from scipy.io import loadmat
- import glob
- import sklearn
- from sklearn.linear_model import LinearRegression
- from sklearn.linear_model import LogisticRegression
- from sklearn.model_selection import train_test_split
- #from pymicro.view.vol_utils import compute_affine_transform
- from sklearn.model_selection import LeaveOneOut
- from sklearn.metrics import accuracy_score
- from sklearn import preprocessing
- import statsmodels.formula.api as sf
- from sklearn import metrics
- from random import randint
- from numpy.linalg import inv
- import math
- import io
- #import h5py
- from circ_stats import *
- from patsy import dmatrices
- import statsmodels.api as sm
- import helpers as hf
- import statsmodels.formula.api as smf
- import copy
- import matplotlib.ticker as ticker
- from pingouin import circ_corrcc
- #monkeys = ["Sa", "Pe", "Wa"]
- #for m in monkeys:
- # files = np.sort(glob.glob('../Data/new/%s*.mat' %m))
- # for f in files:
- # print(f)
- with open('./Results_Full/df_serial.pickle', 'rb') as handle:
- #with open('./Results/df_serial_Sa0.pickle', 'rb') as handle:
- df_sb = pickle.load(handle)
- df_sb = df_sb.reset_index()
- df_sb.rel_loc = np.round(df_sb.rel_loc,3)
- monkeys=df_sb.monkey.unique()
- #df_behav = df_behav.loc[(df_behav.monkey=='Sa') | (df_behav.monkey=='Wa')].reset_index(drop=True)
- df_sb['session_continuous'] = [df_sb.monkey[i]+str(df_sb.session[i]) for i in df_sb.index]
- same_id = 1
- opp_id = 0
- border_id = 2
- # DEFINE DOG FIT PARAMETERS FOR EACH ANIMAL (based on BIC, questionable for Pe, Wa (delta BIC <2)
- sigma={'Sa':0.9, 'Pe':2.15, 'Wa':0.45}
- neural_sigma = {'Sa': 1.35, 'Pe': 0.9, 'Wa': 1.75}#
- reactivation_sigma={'Sa':2.95, 'Pe':1.15, 'Wa':1.55}
- # %%
- def cut_task_timings(timecourse=[], timeperiods=np.array([]), max_time=np.nan):
- """
- Cuts the variable timecourse together based on the varying task timing in multiple sessions.
- Aligns to shortest session timing
- timecourse : np.array() of shape (sessions x trials x time)
- timeperiods: np.array() of shape (sessions x time)
- cut_array: np.array() of shape (sessions x trials x minimum_time)
- """
- # find minimum taskperiod onsets / durations across sessions
- min_duration = np.min(np.diff(timeperiods),axis=0)
- min_onset = np.append(0,np.cumsum(min_duration))#add one more 0 at start so borders and diff add up
- # if no end time is given, do for all timesteps
- if np.isnan(max_time):
- max_time = timeperiods.shape[-1]
- # cut sessions together
- # cut_array = np.array([np.concatenate([timecourse[sess][timing:timing+min_duration[idx]]\
- # if idx not in [5, 9, 16, 20]\
- # else timecourse[sess][timing-min_duration[idx]:timing]
- # for idx,timing in enumerate(timeperiods[sess][:max_time])])\
- # for sess in range(len(timecourse))])
- cut_array = np.array([np.concatenate([timecourse[sess][timing:timing+min_duration[idx]]\
- if idx not in [5, 9, 16, 20]\
- else timecourse[sess][timeperiods[sess][idx+1]-min_duration[idx]:timeperiods[sess][idx+1]]
- for idx,timing in enumerate(timeperiods[sess][:max_time])])\
- for sess in range(len(timecourse))])
- #cut_array = np.array([cut_array])
- return cut_array, min_onset
- # %% [markdown]
- # ----
- # %% [markdown]
- # # Single trial analyses
- # %%
- def abs_err(prediction, all_targets):
- return np.abs([circdist(prediction, targ) for targ in all_targets])
- df_behav = df_sb.copy()
- #df_behav = df_behav.loc[(df_behav.monkey=='Sa') | (df_behav.monkey=='Wa')].reset_index(drop=True)
- df_behav['session_continuous'] = df_behav['monkey']+df_behav['session'].astype(str)
- # drop session with shorter stimulus
- df_behav.drop(index = np.where(df_behav.session_continuous == 'Pe2')[0], inplace=True)
- df_behav.reset_index(drop=True, inplace=True)
- # LOAD NEURAL DECODER DATA
- # INSTEAD OF RESPONSE ORTHO USE DELAY DECODER
- with open('../../Desktop/PhD/2_Smith/Results/SingleTrialDecoding/SingletrialDecoder.pickle', 'rb') as handle:
- #with open('./Results/Figure3/SingletrialDecoder_Sa0.pickle', 'rb') as handle:
- df = pickle.load(handle)
- df['session_continuous'] = df['monkey']+df['session'].astype(str)
- bins=200
- # compute errors and shuffles
- for neuron_type in ['combined', 'left', 'right']:
- print('Computing neuron_type: '+neuron_type)
- pred_prev=[]
- pred_prevortho=[]
- pred_curr=[]
- basecorr_prev=[]
- basecorr_prevortho=[]
- basecorr_curr=[]
- for i in df.index:
- targ_prev = np.round(np.angle(df.loc[i, 'targ_prev_xy']),3)
- targ_curr = np.round(np.angle(df.loc[i, 'targ_curr_xy']), 3)
- pprev = np.angle(df.loc[i, 'pred_complex_prev_'+neuron_type])
- # TODO! Change for all that re not delay decoders
- #pprevortho = np.angle(df.loc[i,'pred_complex_prev_ortho_'+neuron_type])
- pprevortho = np.angle(df.loc[i,'pred_complex_delay_'+neuron_type])
- pcurr = np.angle(df.loc[i,'pred_complex_curr_'+neuron_type])
- # compute error of prediction to target
- err_prev = circdist(pprev,targ_prev)
- err_prevortho = circdist(pprevortho,targ_prev)
- err_curr = circdist(pcurr,targ_curr)
- pred_prev.append(err_prev)
- pred_prevortho.append(err_prevortho)
- pred_curr.append(err_curr)
- df['prederror_prev_'+neuron_type] = pred_prev
- df['prederror_ortho_'+neuron_type] = pred_prevortho
- df['prederror_curr_'+neuron_type] = pred_curr
- # define start/end decoer errors for bumpdrift
- for neuron_type in ['combined', 'left', 'right']:
- df[neuron_type+'DecoderErr'] = [df.loc[i,'prederror_prev_'+neuron_type]\
- for i in df.index]
- df['leftHemi'] = df['hemifield_prev_left']
- df['rightHemi'] = df['hemifield_prev_right']
- IPSI = 1
- BORDER = 0
- CONTRA = -1
- # drop session with shorter stimulus
- df.drop(index = np.where(df.session_continuous == 'Pe2')[0], inplace=True)
- df.reset_index(drop=True, inplace=True)
- assert list(df.index) == list(df_behav.index), 'For merging dataframes: must be of same length'
- df['behav_response_prev'] = df_behav['response_prev']
- df['behav_response_curr'] = df_behav['response_curr']
- df['response_prev_curr'] = np.round(circdist(df.behav_response_prev, np.angle(df.targ_curr_xy)),3)
- df['delay_curr'] = df_behav.delay_curr
- # drop border trials (for ipsi contra analysis)
- df_pred_noBorder = df.drop(index=np.where(df.leftHemi==BORDER)[0])
- df.head()
- # %% [markdown]
- # ---
- # %% [markdown]
- # # Serial dependence
- # %% [markdown]
- # ### Fig 5a: Neural SD at stim., delay
- # %%
- # decoder error of delay_n+1 to target_n+1
- folded=False
- for m,monkey in enumerate(monkeys):#enumerate(['Sa', 'Wa']):#
- borders_mono=[]
- df_mono = df.loc[(df.monkey==monkey)].copy().reset_index(drop=True)
- df_mono['sign_prevcurr'] = sign_rl(df_mono.prev_curr.values)
- if folded==True:
- df_mono['prev_curr'] = np.abs(df_mono.prev_curr)
- prevcurr = np.unique(df.loc[df.session_continuous=='Sa0'].prev_curr)#np.unique(df_mono.prev_curr)#np.unique(df_mono.loc[df_mono.session==0].prev_curr)#np.unique(df_mono.prev_curr) max_num_delaysteps = np.min((np.array([df_mono.borders_full.values[i][16]-df_mono.borders_full.values[i][15]\
- # shape: (sessions x delay groups x prev-curr differences)
- decodersd = np.empty((len(np.unique(df_mono.session)), 2, len(prevcurr)))*np.nan
- mono_prederr = {'early':np.empty((len(df_mono)))*np.nan, 'late':np.empty((len(df_mono)))*np.nan}
- # compute SD for sessions separately
- for session_id,session in enumerate(np.unique(df_mono.session)):#
- df_sess = df_mono.loc[(df_mono.session==session)].copy()
- borders_full = df_sess.borders_full.values[0]
- d_start = borders_full[14]+1
- d_end = borders_full[18]-2
- # start of delay
- prederr = [circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_start]),\
- np.angle(df_sess.shufflepred_complex_curr_combined[i][d_start]))\
- for i in df_sess.index]
- mono_prederr['early'][df_sess.index] = np.squeeze(prederr)
- df_sess['prederr_currdelay_start'] = np.squeeze(prederr)
- # end of delay
- prederr = [circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
- np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end]))\
- for i in df_sess.index]
- mono_prederr['late'][df_sess.index] = np.squeeze(prederr)
- df_sess['prederr_currdelay_end'] = np.squeeze(prederr)
- if folded==True: # flip error, rel_loc in case of folded
- df_sess['prederr_currdelay_start'] = df_sess['prederr_currdelay_start'].values*\
- df_sess.sign_prevcurr
- df_sess['prederr_currdelay_end'] = df_sess['prederr_currdelay_end'].values*\
- df_sess.sign_prevcurr
- # get mean PREDICTION error at each relative location for each session, delay split
- for loc_id,loc in enumerate(prevcurr):
- decodersd[session_id, 0, loc_id] = circmean(df_sess.loc[df_sess.prev_curr==loc]['prederr_currdelay_start'],\
- low=-np.pi, high=np.pi)
- decodersd[session_id, 1, loc_id] = circmean(df_sess.loc[df_sess.prev_curr==loc]['prederr_currdelay_end'],\
- low=-np.pi, high=np.pi)
- # plt.hist(df_sess['prederr_currdelay'+str(delay_id)].values, alpha=0.3)
- # plt.show()
- # get session-mean in each relative location (nans per session if the target didn't appear)
- mean = np.nanmean(decodersd, axis=0)
- # TODO change errorbars
- errorbars = 'SEM'
- if errorbars=='SEM':
- std = sem(decodersd, axis=0, nan_policy='omit')
- elif errorbars == "CI":
- std = 2*np.nanstd(decodersd, axis=0)
- # make consecutive colors for diff delays
- label = ['early', 'late']
- xx = np.linspace(-np.pi, np.pi, 1000)
- f, ax = plt.subplots(figsize=(2.1,1.95))
- plt.axhline(0, color='#333333', alpha=0.5)
- plt.axvline(0, color='#333333', alpha=0.5)
- plt.errorbar(np.rad2deg(prevcurr), np.rad2deg(mean[0]), yerr=np.rad2deg(std[0]),\
- color=colors['SerialBiasWeak'], label='stim.')
- plt.errorbar(np.rad2deg(prevcurr), np.rad2deg(mean[1]), yerr=np.rad2deg(std[1]),\
- color=colors['SerialBias'], label='delay')
- # fit DoG
- df_mono['neural_error'] = mono_prederr[label[0]]
- df_mono['neural_error_late'] = mono_prederr[label[1]]
- para = neural_sigma[monkey]
- df_mono['bias_estimate'] = -hf.dog1(para, df_mono.prev_curr)
- # fit stimulus
- model = smf.ols('neural_error ~ bias_estimate', data=df_mono).fit()
- plt.plot(np.rad2deg(xx), np.rad2deg(model.params['Intercept']+\
- model.params['bias_estimate']*-hf.dog1(para, xx)),\
- color=colors['SerialBiasWeak'], dashes=[1,1])
- # reporting summary EARLY
- ci_low, ci_high = model.conf_int().loc['bias_estimate']
- print('beta = '+str(np.round(np.rad2deg(model.params['bias_estimate']),2))+\
- ', 95% CI ['+str(np.round(np.rad2deg(ci_low),2))+', '+str(np.round(np.rad2deg(ci_high),2))+'], '+\
- 't('+str(int(model.df_resid))+') = '+str(np.round(model.tvalues['bias_estimate'],2))+\
- ', p = '+str("{:.2e}".format(model.pvalues['bias_estimate'])) +\
- ', # trials: '+str(len(df_mono))+', '+str(len(df_mono.session.unique()))+' sessions.')
- # fit delay
- model = smf.ols('neural_error_late ~ bias_estimate', data=df_mono).fit()
- plt.plot(np.rad2deg(xx), np.rad2deg(model.params['Intercept']+\
- model.params['bias_estimate']*-hf.dog1(para, xx)),\
- color=colors['SerialBias'], dashes=[1,1])
- # reporting summary LATE
- ci_low, ci_high = model.conf_int().loc['bias_estimate']
- print('beta = '+str(np.round(np.rad2deg(model.params['bias_estimate']),2))+\
- ', 95% CI ['+str(np.round(np.rad2deg(ci_low),2))+', '+str(np.round(np.rad2deg(ci_high),2))+'], '+\
- 't('+str(int(model.df_resid))+') = '+str(np.round(model.tvalues['bias_estimate'],2))+\
- ', p = '+str("{:.2e}".format(model.pvalues['bias_estimate'])) +\
- ', # trials: '+str(len(df_mono))+', '+str(len(df_mono.session.unique()))+' sessions.')
- plt.xlabel('rel. previous location (°)')
- plt.ylabel('decoder err. (°)')
- plt.legend()
- sns.despine()
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- if monkey=='Sa':
- plt.ylim([-5, 5])
- plt.tight_layout()
- #plt.savefig(DATAPATH+'./Figures/Figure5/INLAYSerialBiasDrift_NeuralFitDoG_'+monkey+'.svg')
- sns.despine()
- plt.show()
- # %% [markdown]
- # ### Fig 5b: neural SD
- # %%
- # compute correlations of neuron and behavior
- # for each session get a correlation of neurons / behavior separately
- sd_all = {m: [] for m in monkeys}
- sd_joint = []
- SD_slopes = []
- borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
- for _, sess_df in df[df.monkey == monkey].groupby('session')]
- borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
- min_stimDelay = borders_mean[16] - borders_mean[14]
- for m,monkey in enumerate(['Sa', 'Wa']):#enumerate(['Sa','Wa']):#, 'Wa']):#
- print('Computing monkey '+monkey+'...')
- df_mono = df.loc[df.monkey==monkey]
- # compute SD for sessions separately
- for session_id,session in enumerate(np.unique(df_mono.session)):#
- df_sess = df_mono.loc[(df_mono.session==session)].copy().reset_index(drop=True)
- df_sess['bias_estimate'] = -hf.dog1(neural_sigma[monkey], df_sess.prev_curr)
- # compute DECODER SD for different time points in the CURRENT delay
- sd_time=[]
- for delay_id,delay in enumerate(range(len(df_sess.pred_complex_curr_combined[1]))):
- # get average prediction error in defined timesteps
- df_sess['prederr_delaystep'] = circdist([np.angle(df_sess.pred_complex_curr_combined[i][delay])\
- for i in df_sess.index],\
- [np.angle(df_sess.shufflepred_complex_curr_combined[i][delay])\
- for i in df_sess.index])
- # fit model
- model = smf.ols('prederr_delaystep ~ bias_estimate', data=df_sess).fit()
- #save parameter of model
- sd_time.append(np.rad2deg(model.params['bias_estimate']))
- sd_joint.append(sd_time)
- sd_all[monkey].append(sd_time)
- # fit slopr through time
- # select timing (from stim-start of each session to minimum delay length)
- x_coords = np.array(range(df_sess.borders_full[0][14], df_sess.borders_full[0][14] + min_stimDelay))*bins/1000
- sd_stim2end = sd_time[df_sess.borders_full[0][14]:df_sess.borders_full[0][14] + min_stimDelay]
- slopel, intercept, r_value, p_value, std_err = stats.linregress(x_coords, sd_stim2end)
- SD_slopes.append(slopel)
- # cut sessions to same length
- sd_cut,borders_mean = cut_task_timings(sd_joint, borders_all, 19)
- # only look at 2nd trial's delay
- sd_trial2 = sd_cut[:, borders_mean[11]:]
- plot_twolines_full(R=sd_trial2,base=np.zeros((sd_trial2.shape)), bins=bins,\
- labelR='',labelB='', errorbars='SEM', end_border=6,shorten_delay = 2, currentTrial=True,\
- borders=borders_mean, ylabel='history drift (°)', colors=[colors['SerialBias'], '#333333'])
- plt.tight_layout()
- #plt.savefig('./Figures/Figure5/SerialBiasDelayDrift_SaWa_200ms.svg')
- plt.show()
- ## STATISTICAL TESTING
- print('Slope computed on '+str(min_stimDelay)+' independent '+str(bins)+'ms time bins.')
- degf = len(SD_slopes) - 1
- test, pval = ttest_1samp(SD_slopes, 0)
- mean_x = np.mean(SD_slopes)
- se = stats.sem(SD_slopes) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('SD slope 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ", mean = "+str(np.round(mean_x))+\
- ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
- # %% [markdown]
- # ### Supplement: Correlate behavioral SD vs neural SD
- # %%
- from sklearn.model_selection import KFold
- # decoder error of delay_n+1 to target_n+1
- folded=False
- # define delay timesteps
- delay_steps = int(600/bins)# size of each delay step
- colors_SD = ['#254441ff', '#43aa8bff', '#b2b09bff']
- all_behavfits=[]
- all_neuralfits=[]
- f, ax = plt.subplots(figsize=(2.,1.95))
- ax.axhline(0, color='#333333', alpha=0.5, lw=0.5)
- ax.axvline(0, color='#333333', alpha=0.5, lw=0.5)
- for m,monkey in enumerate(monkeys):#enumerate(['Pe','Sa', 'Wa']):#
- borders_mono=[]
- df_mono = df.loc[(df.monkey==monkey)].copy().reset_index(drop=True)
- df_mono['sign_prevcurr'] = sign_rl(df_mono.prev_curr.values)
- if folded==True:
- df_mono['prev_curr'] = np.abs(df_mono.prev_curr)
- prevcurr = np.unique(df_mono.loc[df_mono.session==0].prev_curr)#np.unique(df_mono.prev_curr) max_num_delaysteps = np.min((np.array([df_mono.borders_full.values[i][16]-df_mono.borders_full.values[i][15]\
- # SD FITS ARE OPTIMIZED DIFFERENTLY FOR NEURAL, BEHAV. SD
- df_mono['behav_error_curr_deg'] = np.rad2deg(df_mono.behav_error_curr)
- df_mono['behav_bias_estimate'] = -hf.dog1(sigma[monkey], df_mono.prev_curr)#sign_rl(df_sess.prev_curr.values)#
- df_mono['neural_bias_estimate'] = -hf.dog1(neural_sigma[monkey], df_mono.prev_curr)#sign_rl(df_sess.prev_curr.values)#
- mono_prederr = np.empty((len(df_mono)))*np.nan
- # compute SD for sessions separately
- for session_id,session in enumerate(np.unique(df_mono.session)):#
- df_sess = df_mono.loc[(df_mono.session==session)].copy()
- borders_full = df_sess.borders_full.values[0]
- d_end = range(borders_full[17]-2, borders_full[17])
- #d_end = borders_full[17]-1
- #mono_prederr[df_sess.index] = np.squeeze([circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
- # np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end]))\
- # for i in df_sess.index])
- mono_prederr[df_sess.index] = np.squeeze([circmean(circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
- np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end])),\
- low=-np.pi, high=np.pi)\
- for i in df_sess.index])
- df_mono['prederr'] = mono_prederr
- ################ FIT SERIAL DEPENDENCE ##########################
- kf = KFold(n_splits=5, shuffle=True)
- behavfits=[]
- neuralfits=[]
- for k, (train_idx, test_idx) in enumerate(kf.split(df_mono.index)):
- df_split = df_mono.loc[train_idx]
- # fit BEHAVIORAL model
- #behavmodel = smf.mixedlm('behav_error_curr_deg ~ behav_bias_estimate', data=df_mono, groups=df_mono['session']).fit()
- behavmodel = smf.ols('behav_error_curr_deg ~ behav_bias_estimate', data=df_split).fit()
- behavfits.append(behavmodel.params[1])
- # fit NEURAL model# fit BEHAVIORAL model
- df_split['prederr_end_deg'] = np.rad2deg(df_split['prederr'])
- neuralmodel = smf.ols('prederr_end_deg ~ neural_bias_estimate', data=df_split).fit()
- #if (behavmodel.pvalues[1]<0.05) & (neuralmodel.pvalues[1]<0.05):
- behavfits.append(behavmodel.params[1])
- neuralfits.append(neuralmodel.params[1])
- all_behavfits.append(behavfits)
- all_neuralfits.append(neuralfits)
- ax.errorbar(np.mean(all_behavfits[m]), np.mean(all_neuralfits[m]),\
- xerr= sem(all_behavfits[m]),yerr=2*sem(all_neuralfits[m]), color=colors_SD[m], marker='o',\
- label=monkey, markersize=4)
- ax.set_xlabel('behavioral bias (°)')
- ax.set_ylabel('history drift (°)')
- x0, x1 = ax.get_xlim()
- #ax.set_xlim([x0, x1])
- #ax.set_ylim([x0, x1])
- y0, y1 = ax.get_ylim()
- ax.plot([y0, y1], [y0, y1], color='#333333', alpha=0.3, dashes=[5,5], lw=0.5)
- ax.legend()
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./NeuralBehavSerialBias_compareMonkeys.svg', dpi=300)
- plt.show()
- # %% [markdown]
- # ---
- # %% [markdown]
- # # Reactivation drift
- # %%
- # get minimum trial durations across all sessions
- borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
- for _, sess_df in df[df.monkey == monkey].groupby('session')]
- borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
- # get reactivation location / strength for each hemisphere
- min_Reactlen = (borders_mean[14]+1) - borders_mean[13]
- print('Reactivation average is averaged across '+str(min_Reactlen)+' independent '+str(bins)+'ms time bins.')
- Rtime = [range(df.borders_full[i][13], df.borders_full[i][13]+min_Reactlen) for i in df.index]
- df['react'] = [np.angle(np.mean(df['pred_complex_delay_combined'][i][Rtime[i]]))\
- for i in df.index]
- df['react_left'] = [np.angle(np.mean(df['pred_complex_delay_left'][i][Rtime[i]]))\
- for i in df.index]
- df['react_right'] = [np.angle(np.mean(df['pred_complex_delay_right'][i][Rtime[i]]))\
- for i in df.index]
- df['react_strength'] = [np.abs(np.mean(df['pred_complex_delay_combined'][i][Rtime[i]]))\
- for i in df.index]
- df['react_strength_left'] = [np.abs(np.mean(df['pred_complex_delay_left'][i][Rtime[i]]))\
- for i in df.index]
- df['react_strength_right'] = [np.abs(np.mean(df['pred_complex_delay_right'][i][Rtime[i]]))\
- for i in df.index]
- # get delay angle, shuffle for each hemisphere
- min_Delaylen = borders_mean[17] - borders_mean[16]
- DELAYEND = [range(df.borders_full[trial][17] - min_Delaylen, df.borders_full[trial][17])\
- for trial in df.index]
- print('Delay average is averaged across '+str(min_Delaylen)+' independent '+str(bins)+'ms time bins.')
- df['delayAvg'] = [np.angle(np.mean(df.pred_complex_curr_combined[trial][DELAYEND[trial]]))\
- for trial in df.index]
- df['delayAvg_shuffle'] = [np.angle(np.mean(df.shufflepred_complex_curr_combined[trial][DELAYEND[trial]]))\
- for trial in df.index]
- df['delayAvg_left'] = [np.angle(np.mean(df.pred_complex_curr_left[trial][DELAYEND[trial]]))\
- for trial in df.index]
- df['delayAvg_shuffle_left'] = [np.angle(np.mean(df.shufflepred_complex_curr_left[trial][DELAYEND[trial]]))\
- for trial in df.index]
- df['delayAvg_right'] = [np.angle(np.mean(df.pred_complex_curr_right[trial][DELAYEND[trial]]))\
- for trial in df.index]
- df['delayAvg_shuffle_right'] = [np.angle(np.mean(df.shufflepred_complex_curr_right[trial][DELAYEND[trial]]))\
- for trial in df.index]
- # %% [markdown]
- # ### Fig 5c: Reactivation strength and precision
- # %%
- df_mono = df.loc[df.monkey=='Sa'].copy().reset_index(drop=True)
- # compute reactivation error (distance to previous target)
- df_mono['react_error'] = circdist(df_mono['react'].values, np.round(np.angle(df_mono.targ_prev_xy), 3))
- # determine cut-off for each hemisphere, session
- perc = 20
- df_mono[['cut']] = df_mono.groupby('session_continuous') \
- ['react_strength'].transform(lambda x: np.percentile(x, perc))
- df_high = df_mono.loc[df_mono.react_strength > df_mono.cut]
- df_low = df_mono.loc[df_mono.react_strength <= df_mono.cut]
- f,ax = plt.subplots(figsize=(2.1,1.95))
- ax.hist(np.rad2deg(df_high['react_error'].values), label='strong',\
- bins=17, density=True, histtype='step', color= colors['Reactivation'], linewidth= 1)
- ax.hist(np.rad2deg(df_low['react_error'].values), label='weak',\
- bins=17, density=True, histtype='step', color=colors['ReactivationWeak'], linewidth= 1)
- ax.legend()
- ax.set_xlim([-180, 180])
- ax.set_xlabel('reactivation error (°)')
- ax.set_ylabel('density')
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./ReactivationAngleHist_Sa.svg', dpi=300)
- plt.show()
- # %% [markdown]
- # ### Fig 5d: Within vs across attraction to reactivation
- # %%
- # compute correlations of neuron and behavior
- # for each session get a correlation of neurons / behavior separately
- hemispheres = np.array(['left', 'right'])
- react_all = {'within':[], 'across':[]}
- react_term = {m: {side: [] for side in ['within', 'across']} for m in monkeys}
- borders_mono={m:[] for m in monkeys}
- borders_all=[]
- for m,monkey in enumerate(['Sa', 'Wa']):#enumerate(['Sa', 'Wa']):#
- print('Computing monkey '+monkey+'...')
- df_mono = df.loc[df.monkey==monkey]
- for session_id,session in enumerate(np.unique(df_mono.session)):#
- df_sess = df_mono.loc[(df_mono.session==session)].copy().reset_index(drop=True)
- borders_all.append(df_sess.borders_full[0])
- borders_mono[monkey].append(df_sess.borders_full[0])
- # get relative distance: reactivation - current target
- reactivationprevcurr_left = circdist(df_sess.react_left, np.angle(df_sess.targ_curr_xy))
- df_sess['bias_left'] = -hf.dog1(reactivation_sigma[monkey],\
- reactivationprevcurr_left)
- reactivationprevcurr_right = circdist(df_sess.react_right, np.angle(df_sess.targ_curr_xy))
- df_sess['bias_right'] = -hf.dog1(reactivation_sigma[monkey],\
- reactivationprevcurr_right)
- # subtract shuffle from delay error for all times
- df_sess['pred_shufflesub_left'] = [circdist(np.angle(df_sess['pred_complex_curr_left'][i]),\
- np.angle(df_sess.shufflepred_complex_curr_left[i]))\
- for i in df_sess.index]
- df_sess['pred_shufflesub_right'] = [circdist(np.angle(df_sess['pred_complex_curr_right'][i]),\
- np.angle(df_sess.shufflepred_complex_curr_right[i]))\
- for i in df_sess.index]
- for s, same in enumerate(['within', 'across']):
- sess_estimates=[]
- # get estimates for each time step
- for delay_id,delay in enumerate(range(len(df_sess.pred_complex_curr_combined[1]))):
- bias_estimate = []
- prederr_delaystep=[]
- timeon=time.time()
- for R,RSide in enumerate(hemispheres):
- DSide = RSide if same == 'within' else hemispheres[hemispheres != RSide][0]
- bias_estimate.append(df_sess['bias_'+RSide])
- prederr_delaystep.append([df_sess['pred_shufflesub_'+DSide][i][delay]\
- for i in df_sess.index])
- #prederr_delaystep.append(np.squeeze([circdist(np.angle(df_sess['pred_complex_curr_'+DSide][i][delay]),\
- # np.angle(df_sess.targ_curr_xy[i]))\
- # for i in df_sess.index]))
- # fit model on combined areas react/delay err
- df_model = pd.DataFrame({'bias_estimate': np.concatenate(bias_estimate),
- 'prederr_delaystep_deg': np.rad2deg(np.concatenate(prederr_delaystep))})
- assert len(df_model) == 2*len(df_sess), "Using both left and right predictions needs to double trials (e.g. within = left-left, right-right)"
- # fit model
- model = smf.ols('prederr_delaystep_deg ~ bias_estimate', data=df_model).fit()
- sess_estimates.append(model.params['bias_estimate'])
- react_all[same].append(sess_estimates)
- react_term[monkey][same].append(sess_estimates)
- # cut sessions to same length
- react_within_cut,borders_mean = cut_task_timings(react_all['within'], borders_all, 19)
- react_across_cut,borders_mean = cut_task_timings(react_all['across'], borders_all, 19)
- # only look at 2nd trial's delay
- react_within_trial2 = react_within_cut[:, borders_mean[11]:]
- react_across_trial2 = react_across_cut[:, borders_mean[11]:]
- plot_twolines_full(R=react_within_trial2,base=react_across_trial2, bins=bins, currentTrial=True,\
- labelR='within',labelB='across', errorbars='SEM', end_border=6,shorten_delay = 2,\
- borders=borders_mean, ylabel='reactivation drift (°)',\
- significances = True)
- plt.tight_layout()
- #plt.savefig('./Figures/Figure5/ReactivationBiasWithinAcross_SaWa_200ms.svg')
- plt.show()
- # %%
- react_within_cut,borders_mean = cut_task_timings(react_all['within'], borders_all, 19)
- react_across_cut,borders_mean = cut_task_timings(react_all['across'], borders_all, 19)
- within_delayavg = np.mean(react_within_cut[:, borders_mean[16]:borders_mean[17]], axis=1)
- across_delayavg = np.mean(react_across_cut[:, borders_mean[16]:borders_mean[17]], axis=1)
- # plot
- f, ax = plt.subplots(figsize=(1.1,2.1))
- plt.axhline(0, color='#333333', alpha=0.5)
- #within
- ax.plot([np.zeros((len(within_delayavg))), np.ones((len(across_delayavg)))],\
- [within_delayavg, across_delayavg], color='#333333', alpha=0.2)
- ax.errorbar(0, np.mean(within_delayavg), yerr= 2*sem(within_delayavg), color=colors['Within'], marker='o')
- # across
- ax.errorbar(1, np.mean(across_delayavg), yerr= 2*sem(across_delayavg), color=colors['Across'], marker='o')
- ax.set_xticks([0,1])
- ax.set_xticklabels(['within', 'across'], rotation=25)
- ax.set_ylabel('reactivation bias (°)')
- ax.set_xlabel('hemisphere')
- # pvalues
- stats_w = ['***' if ttest_1samp(within_delayavg, 0)[1] < 0.005\
- else '**' if ttest_1samp(within_delayavg, 0)[1] < 0.01\
- else '*' if ttest_1samp(within_delayavg, 0)[1] < 0.05 else 'n.s.'][0]
- stats_a = ['***' if ttest_1samp(across_delayavg, 0)[1] < 0.005\
- else '**' if ttest_1samp(across_delayavg, 0)[1] < 0.01\
- else '*' if ttest_1samp(across_delayavg, 0)[1] < 0.05 else 'n.s.'][0]
- stats_diff = ['***' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.005\
- else '**' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.01\
- else '*' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.05 else 'n.s.'][0]
- y0, y1 = ax.get_ylim()
- ax.annotate(stats_w,(0, y1-0.3), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
- ax.annotate(stats_a,(1, y1-0.3), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
- ax.plot([0,0,1,1],\
- [y1+2*y1/10,y1+3*y1/10, y1+3*y1/10, y1+2*y1/10], linewidth=1, color='#333333')
- ax.annotate(stats_diff,\
- (0.5, y1+3*y1/10), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
- ax.set_xlim([-.2,1.2])
- ax.set_ylim([-3.5,8])
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./Quantify_ReactivationBiasWithinAcross_SaWa_200ms.svg')
- plt.show()
- # STATISTICAL TESTING
- print('Avg. of '+str(borders_mean[17] - borders_mean[16])+' independent '+str(bins)+' ms time bins.')
- # WITHIN
- degf = len(within_delayavg) - 1
- test, pval = ttest_1samp(within_delayavg, 0)
- mean_x = np.mean(within_delayavg)
- se = stats.sem(within_delayavg) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('WITHIN 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
- # ACROSS
- test, pval = ttest_1samp(across_delayavg, 0)
- mean_x = np.mean(across_delayavg)
- se = stats.sem(across_delayavg) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('ACROSS 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
- # WITHIN - ACROSS
- test, pval = ttest_rel(within_delayavg, across_delayavg)
- mean_x = np.mean(within_delayavg - across_delayavg)
- se = stats.sem(within_delayavg - across_delayavg) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('WITHIN - ACROSS paired: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
- # %% [markdown]
- # ### Supplement: Split by percentage of dual trials
- # %%
- df_helper = df.copy().reset_index(drop=True)
- colors_wa = {'within': 'darkgreen', 'across':'darkorange'}
- hemispheres = np.array(['left', 'right'])
- percent = 30
- print('Cut-off: '+str(percent)+'%...')
- # determine cut-off for each session
- df_helper[['cut_left', 'cut_right']] = df_helper.groupby('session_continuous') \
- [['react_strength_left', 'react_strength_right']] \
- .transform(lambda x: np.percentile(x, percent))
- # mark reactivations as weak, strong
- df_helper['R_strong_left'] = df_helper.react_strength_left > df_helper.cut_left
- df_helper['R_strong_right'] = df_helper.react_strength_right > df_helper.cut_right
- # assign reactivation types
- chosen_trials = {'none': df_helper[(~df_helper.R_strong_left) & (~df_helper.R_strong_right)],\
- 'single': df_helper[((df_helper.R_strong_left) & (~df_helper.R_strong_right)) |\
- ((~df_helper.R_strong_left) & (df_helper.R_strong_right))],\
- 'dual': df_helper[(df_helper.R_strong_left) & (df_helper.R_strong_right)]}
- ['within', 'across']
- sd_monkeys = {'within':[],'across':[]}
- sd_term = {key:{'within': np.empty((len(np.unique(df_helper.session_continuous))))*np.nan,\
- 'across': np.empty((len(np.unique(df_helper.session_continuous))))*np.nan}\
- for key in chosen_trials.keys()}
- pid=-1
- f,ax = plt.subplots(figsize=(1.85,1.95), sharey=True)
- for x, react_combi in enumerate(chosen_trials.keys()):
- df_type = chosen_trials[react_combi]
- sides=[]
- for s, same in enumerate(['within', 'across']):
- for session_id,session in enumerate(df_type.session_continuous.unique()):#
- df_sess = df_type.loc[(df_type.session_continuous==session)].copy()
- monkey = df_sess.monkey.unique()[0]
- sess_estimates, reactivationprev_curr, delayerr = [], [], []
- # for each hemisphere
- for R,RSide in enumerate(hemispheres):
- # use either same hemisphere during delay (within) or opposite (across)
- DSide = RSide if same == 'within' else hemispheres[hemispheres != RSide][0]
- reactivations = df_sess['react_'+RSide].values
- delay = df_sess['delayAvg_'+DSide].values
- shuffledelay = df_sess['delayAvg_shuffle_'+DSide].values# np.angle(df_sess.targ_curr_xy)
- # RELATIVE LOCATION OF PREVIOUS REACTIVATION TO CURRENT TARGET
- if (react_combi == 'single'): # if single, only view attraction to reactivated side
- df_react_side = df_sess.loc[df_sess['R_strong_'+RSide]]
- reactivationprev_curr.append(circdist(df_react_side['react_'+RSide].values,\
- np.angle(df_react_side.targ_curr_xy)))
- delay_react_side = df_react_side['delayAvg_'+DSide].values
- shuffle_react_side = df_react_side['delayAvg_shuffle_'+DSide].values
- delayerr.append(circdist(delay_react_side, shuffle_react_side))
- else:
- reactivationprev_curr.append(circdist(reactivations, np.angle(df_sess.targ_curr_xy)))
- delayerr.append(circdist(delay, shuffledelay))
- # combine left right in each condition
- reactivationprev_curr = np.concatenate(reactivationprev_curr)
- delayerr = np.concatenate(delayerr)
- # MODEL HOW THE DELAY ACTIVITY IS ATTRACTED TO THE PREVIOUS REACTIVATION LOCATION
- bias_estimate = -hf.dog1(reactivation_sigma[monkey], reactivationprev_curr)#sign_rl(reactivationprev_curr)#
- # Prepare DataFrame for model fitting
- df_model = pd.DataFrame({'bias_estimate': bias_estimate,
- 'prederr_delaystep_deg': np.rad2deg(delayerr)})
- # fit model
- model = smf.ols('prederr_delaystep_deg ~ bias_estimate', data=df_model).fit()
- #save parameter of model
- sd_term[react_combi][same][session_id] = model.params['bias_estimate']
- sess_estimates.append(model.params['bias_estimate'])
- sd_monkeys[same].append(sess_estimates)
- # get session-mean in each relative location (nans per session if the target didn't appear)
- sd_timing = sd_term[react_combi][same]
- mean, CI = np.nanmean(sd_timing, axis=0), sem(sd_timing, axis=0, nan_policy='omit')
- ax.errorbar(x+s*0.2, mean, yerr=CI, color=colors[same.title()], marker='o', label=same)
- #if (react_combi=='none'):
- # plt.legend()
- ax.axhline(0, color='#333333', alpha=0.5)
- # pvalues of each condition against 0
- y0, y1 = ax.get_ylim()
- for x, react_combi in enumerate(chosen_trials.keys()):
- test_0 = ttest_rel(sd_term[react_combi]['within'], sd_term[react_combi]['across'])[1]
- if test_0 < 0.05:
- ax.plot([x,x,x+0.2,x+0.2],\
- [y1-0.2,y1-0.1, y1-0.1, y1-0.2], linewidth=1, color='#333333')
- ax.annotate('*',\
- (x+0.1, y1-0.2), ha='center', va='bottom', fontsize=10, color='#333333')
- # pvalues of single vs dual across condition (n.s.)
- ttest_across = ttest_rel(sd_term['single']['across'], sd_term['dual']['across'])[1]
- stats_a = ['***' if ttest_across < 0.005\
- else '**' if ttest_across < 0.01\
- else '*' if ttest_across < 0.05 else 'n.s.'][0]
- ax.plot([1.2,1.2,2.2,2.2],\
- [1.7,1.8, 1.8, 1.7], linewidth=1, color='#333333')
- ax.annotate(stats_a,\
- (1.7, 1.8), ha='center', va='bottom', fontsize=10, color='#333333')
- plt.xticks([0,1,2], chosen_trials.keys())#, rotation=25
- plt.xlabel('reactivation type')
- plt.ylabel('react. drift (°)')
- sns.despine()
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- plt.tight_layout()
- #plt.savefig('./ReactivationBiasSplitByType_'+str(percent)+'%.svg')
- plt.show()
- # STATISTICAL TESTING
- # WITHIN - ACROSS
- print('##### Across-0 statistics: #####')
- for x, react_combi in enumerate(chosen_trials.keys()):
- test, pval = ttest_1samp(sd_term[react_combi]['across'], 0)
- mean_x = np.mean(sd_term[react_combi]['across'])
- se = stats.sem(sd_term[react_combi]['across']) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print(react_combi+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
- print('##### Within-Across statistics: #####')
- for x, react_combi in enumerate(chosen_trials.keys()):
- test, pval = ttest_rel(sd_term[react_combi]['within'], sd_term[react_combi]['across'])
- mean_x = np.mean(sd_term[react_combi]['within'] - sd_term[react_combi]['across'])
- se = stats.sem(sd_term[react_combi]['within'] - sd_term[react_combi]['across']) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print(react_combi+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
- print('##### Single vs Dual Across #####')
- test, pval = ttest_rel(sd_term['dual']['across'], sd_term['single']['across'])
- mean_x = np.mean(sd_term['dual']['within'] - sd_term['single']['across'])
- se = stats.sem(sd_term['dual']['within'] - sd_term['single']['across']) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('Single vs Dual Across: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
- print('##### Single vs Dual Within-Across Difference #####')
- single_WA = sd_term['single']['within'] - sd_term['single']['across']
- dual_WA = sd_term['dual']['within'] - sd_term['dual']['across']
- test, pval = ttest_rel(single_WA, dual_WA)
- mean_x = np.mean(dual_WA - single_WA)
- se = stats.sem(dual_WA - single_WA) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print('Single vs Dual within-across: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ', mean = '+str(np.round(mean_x,2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
- # %% [markdown]
- # ### Fig 5e: GMM - Single or Dual Reactivations
- # %%
- from sklearn.mixture import GaussianMixture
- from sklearn.model_selection import train_test_split
- from sklearn.model_selection import GridSearchCV
- # get reactivation strengths
- # get minimum trial durations across all sessions
- borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
- for _, sess_df in df[df.monkey == monkey].groupby('session')]
- borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
- # get reactivation location / strength for each hemisphere
- min_Reactlen = (borders_mean[14]+1) - borders_mean[13]
- print('Reactivation average is averaged across '+str(min_Reactlen)+' independent '+str(bins)+'ms time bins.')
- Rtimes = [range(df.borders_full[i][13], df.borders_full[i][13]+min_Reactlen) for i in df.index]
- # Rtimes = [range(df.borders_full.values[trial][13], df.borders_full.values[trial][14]+1)\
- # for trial in df.index]
- # REACTIVATION
- # get reactivations: predict PREV target during current fixation time (Rtimes)
- df['react_strength_left'] = [np.abs(np.mean(df.pred_complex_delay_left[trial][Rtimes[trial]]))\
- for trial in df.index]
- df['react_strength_right'] = [np.abs(np.mean(df.pred_complex_delay_right[trial][Rtimes[trial]]))\
- for trial in df.index]
- df['react_angle_left'] = [np.angle(np.mean(df.pred_complex_delay_left[trial][Rtimes[trial]]))\
- for trial in df.index]
- df['react_angle_right'] = [np.angle(np.mean(df.pred_complex_delay_right[trial][Rtimes[trial]]))\
- for trial in df.index]
- # create vector of left, right react strength per trial
- X = np.array([df.react_strength_left.values, df.react_strength_right.values]).T
- # %%
- # use four components, expect: None, single-left, single-right, Dual
- chosen_components = 4
- # get left, right reactivation strengths
- df_mono = df.copy().reset_index(drop=True)
- # only plot random subsample of datapoints to get smaller svg file
- randomIDs = np.random.choice(range(len(X)), 2000)
- # figure
- f,ax=plt.subplots(figsize=(2.1,1.95))
- X_subsample = X[randomIDs]
- ax.scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
- # fit 250 GMMs on full data with differnt start points to check stability
- gm = GaussianMixture(n_components=chosen_components)
- for i in range(100): # repeat for random starting points
- gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X)#, covariance_type='spherical'
- # highlight the found classes
- # highlight the found classes
- dual_class = np.argmax(np.sum(gm.means_, axis=1))
- for m_id,m in enumerate(gm.means_):
- #if m_id == dual_class :
- # ax.scatter(m[0], m[1], color='r', marker='x', s=50, alpha=0.1)
- #else:
- ax.scatter(m[0], m[1], color='#333333', marker='x', s=50, alpha=0.1)
- # plot diagonal for showing dual reactivation line
- x0,x1 = ax.get_xlim()
- y0,y1 = ax.get_ylim()
- ax.plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
- ax.set_xlabel('left reactivation strength')
- ax.set_ylabel('right reactivation strength')
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./GMM4FitsSingleVSDualReact.svg')
- plt.show()
- # %% [markdown]
- # ### Supplement: Individual monkeys with three classes
- # %%
- # use four components, expect: None, single-left, single-right, Dual
- chosen_components = 4
- f,ax=plt.subplots(1,3,figsize=(5.8,1.95), sharex=True, sharey=True)
- for m, monkey in enumerate(monkeys):
- # get left, right reactivation strengths
- df_mono = df.loc[df.monkey==monkey].copy().reset_index(drop=True)
- X_mono = np.array([df_mono.react_strength_left.values, df_mono.react_strength_right.values]).T
- # only plot random subsample of datapoints to get smaller svg file
- randomIDs = np.random.choice(range(len(X_mono)), 2000)
- # figure
- X_subsample = X_mono[randomIDs]
- ax[m].scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
- # fit 250 GMMs on full data with differnt start points to check stability
- gm = GaussianMixture(n_components=chosen_components)
- for i in range(100): # repeat for random starting points
- gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_mono)#, covariance_type='spherical'
- # highlight the found classes
- # highlight the found classes
- dual_class = np.argmax(np.sum(gm.means_, axis=1))
- for m_id,means in enumerate(gm.means_):
- ax[m].scatter(means[0], means[1], color='#333333', marker='x', s=50, alpha=0.1)
- # plot diagonal for showing dual reactivation line
- x0,x1 = ax[m].get_xlim()
- y0,y1 = ax[m].get_ylim()
- ax[m].plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
- ax[m].set_xlabel('left reactivation strength')
- ax[m].set_ylabel('right reactivation strength')
- ax[m].xaxis.set_ticks_position('bottom')
- ax[m].yaxis.set_ticks_position('left')
- ax[m].set_title(monkey)
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./SupplementGMM4FitsSingleVSDualReact.svg')
- plt.show()
- # %%
- # use four components, expect: None, single-left, single-right, Dual
- chosen_components = 3
- f,ax=plt.subplots(1,3,figsize=(5.8,1.95), sharex=True, sharey=True)
- for m, monkey in enumerate(monkeys):
- # get left, right reactivation strengths
- df_mono = df.loc[df.monkey==monkey].copy().reset_index(drop=True)
- X_mono = np.array([df_mono.react_strength_left.values, df_mono.react_strength_right.values]).T
- # only plot random subsample of datapoints to get smaller svg file
- randomIDs = np.random.choice(range(len(X_mono)), 2000)
- # figure
- X_subsample = X_mono[randomIDs]
- ax[m].scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
- # fit 250 GMMs on full data with differnt start points to check stability
- gm = GaussianMixture(n_components=chosen_components)
- for i in range(100): # repeat for random starting points
- gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_mono)#, covariance_type='spherical'
- # highlight the found classes
- # highlight the found classes
- dual_class = np.argmax(np.sum(gm.means_, axis=1))
- for m_id,means in enumerate(gm.means_):
- ax[m].scatter(means[0], means[1], color='#333333', marker='x', s=50, alpha=0.1)
- # plot diagonal for showing dual reactivation line
- x0,x1 = ax[m].get_xlim()
- y0,y1 = ax[m].get_ylim()
- ax[m].plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
- ax[m].set_xlabel('left reactivation strength')
- ax[m].set_ylabel('right reactivation strength')
- ax[m].xaxis.set_ticks_position('bottom')
- ax[m].yaxis.set_ticks_position('left')
- ax[m].set_title(monkey)
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./SupplementGMM3FitsSingleVSDualReact.svg')
- plt.show()
- # %% [markdown]
- # ### Supplement: Illustration of cut-off
- # %%
- percentile = 30
- cut_off = np.percentile(X, percentile)
- f,ax=plt.subplots(figsize=(2.,2))
- plt.scatter(X_subsample[:,0], X_subsample[:,1], color='#333333', alpha=0.2, marker='.')
- plt.axhline(cut_off, lw=1.25, dashes=[5,1], color='lightcoral')
- plt.axvline(cut_off, lw=1.25, dashes=[5,1], color='lightcoral')
- ax.set_xlabel('left reactivation strength')
- ax.set_ylabel('right reactivation strength')
- plt.xlim([0, 300])
- plt.ylim([0, 300])
- plt.title('All monkeys')
- ax.xaxis.set_ticks_position('bottom')
- ax.yaxis.set_ticks_position('left')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./Figures/Supplement/Illustration_cutOff.svg')
- # %% [markdown]
- # ### Figure 5f: Delay GMM
- # %%
- borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
- for _, sess_df in df[df.monkey == monkey].groupby('session')]
- borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
- # get reactivation location / strength for each hemisphere
- min_Delaylen = borders_mean[6] - borders_mean[5]
- print('Delay average is averaged across '+str(min_Delaylen)+' independent '+str(bins)+'ms time bins.')
- # get delay strengths
- Dtimes = [range(df.borders_full.values[trial][6]-min_Delaylen, df.borders_full.values[trial][6])\
- for trial in df.index]
- # REACTIVATION
- # get reactivations: predict PREV target during current fixation time (Rtimes)
- df['delay_strength_left'] = [np.abs(np.mean(df.pred_complex_delay_left[trial][Dtimes[trial]]))\
- for trial in df.index]
- df['delay_strength_right'] = [np.abs(np.mean(df.pred_complex_delay_right[trial][Dtimes[trial]]))\
- for trial in df.index]
- df['delay_strength_combined'] = [np.abs(np.mean(df.pred_complex_delay_combined[trial][Dtimes[trial]]))\
- for trial in df.index]
- X_delay = np.array([df.delay_strength_left.values, df.delay_strength_right.values]).T
- # %%
- # use four components, expect: None, single-left, single-right, Dual
- chosen_components = 4
- # get left, right reactivation strengths
- df_mono = df.copy().reset_index(drop=True)
- # only plot random subsample of datapoints to get smaller svg file
- randomIDs = np.random.choice(range(len(X_delay)), 2000)
- # figure
- f,ax=plt.subplots(figsize=(2.1,1.95))
- X_subsample = X_delay[randomIDs]
- ax.scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color='#0B506F', marker='.')
- # fit 250 GMMs on full data with differnt start points to check stability
- gm = GaussianMixture(n_components=chosen_components)
- for i in range(250): # repeat for random starting points
- gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_delay)#, covariance_type='spherical'
- # highlight the found classes
- dual_class = np.argmax(np.sum(gm.means_, axis=1))
- for m_id,m in enumerate(gm.means_):
- #if m_id == dual_class :
- # ax.scatter(m[0], m[1], color='r', marker='x', s=50, alpha=0.1)
- #else:
- ax.scatter(m[0], m[1], color='#333333', marker='x', s=50, alpha=0.1)
- # plot diagonal for showing dual reactivation line
- x0,x1 = ax.get_xlim()
- y0,y1 = ax.get_ylim()
- ax.plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
- ax.set_xlabel('left delay strength')
- ax.set_ylabel('right delay strength')
- sns.despine()
- plt.tight_layout()
- #plt.savefig('./GMM4FitsSingleVSDualReact_DELAY.svg')
- plt.show()
- # %% [markdown]
- # # Fig. 6d: Behavioral precision based on memory in either or both hemispheres
- # %%
- #colors_ic={'ipsi':'#EF8354', 'contra':'#2E9FDC'}
- # PREDICTONS
- contraPred = [df_pred_noBorder.pred_complex_delay_left[idx] if df_pred_noBorder.leftHemi[idx]==CONTRA\
- else df_pred_noBorder.pred_complex_delay_right[idx] if df_pred_noBorder.rightHemi[idx]==CONTRA\
- else [np.nan for i in df_pred_noBorder.pred_complex_delay_left[idx]] for idx in df_pred_noBorder.index]
- ipsiPred = [df_pred_noBorder.pred_complex_delay_left[idx] if df_pred_noBorder.leftHemi[idx]==IPSI\
- else df_pred_noBorder.pred_complex_delay_right[idx] if df_pred_noBorder.rightHemi[idx]==IPSI\
- else [np.nan for i in df_pred_noBorder.pred_complex_delay_left[idx]] for idx in df_pred_noBorder.index]
- df_pred_noBorder['contraDecoderPred'] = contraPred
- df_pred_noBorder['ipsiDecoderPred'] = ipsiPred
- df_pred_noBorder['combinedDecoderPred'] = [np.mean([contraPred[i], ipsiPred[i]], axis=0)\
- for i in range(len(contraPred))]
- # ERRORS
- contraErr = [df_pred_noBorder.prederror_ortho_left[idx] if df_pred_noBorder.leftHemi[idx]==CONTRA\
- else df_pred_noBorder.prederror_ortho_right[idx] if df_pred_noBorder.rightHemi[idx]==CONTRA\
- else [np.nan for i in df_pred_noBorder.prederror_ortho_left[idx]] for idx in df_pred_noBorder.index]
- ipsiErr = [df_pred_noBorder.prederror_ortho_left[idx] if df_pred_noBorder.leftHemi[idx]==IPSI\
- else df_pred_noBorder.prederror_ortho_right[idx] if df_pred_noBorder.rightHemi[idx]==IPSI\
- else [np.nan for i in df_pred_noBorder.prederror_ortho_left[idx]] for idx in df_pred_noBorder.index]
- df_pred_noBorder['contraDecoderErr'] = contraErr
- df_pred_noBorder['ipsiDecoderErr'] = ipsiErr
- df_pred_noBorder['combinedDecoderErr'] = [circdist(np.angle(df_pred_noBorder['combinedDecoderPred'][i]),\
- np.angle(df_pred_noBorder.targ_prev_xy[i]))\
- for i in df_pred_noBorder.index]
- # %%
- precision = {'none':[], 'ipsi':[], 'contra':[], 'both':[]}
- precisionerrors = {'none':[], 'ipsi':[], 'contra':[], 'both':[]}
- binspace = np.linspace(-np.pi/10, np.pi/10, 50)
- precision_mono = {m: {'none':[], 'ipsi':[], 'contra':[], 'both':[]} for m in monkeys}
- precision_orig = []
- labels, label_name, label_errors = [], [], []
- cc=[]
- h=0
- for monkey in monkeys:
- df_mono = df_pred_noBorder.loc[df_pred_noBorder.monkey==monkey]
- for s, session in enumerate(np.unique(df_mono.session_continuous)): # for each session
- df_sess = df_mono.loc[(df_mono.session_continuous==session)].copy().reset_index(drop=True)
- borders = df_sess.borders_full[0]
- # define end of delay
- baseline = range(df_sess.borders_full[0][0], df_sess.borders_full[0][3])
- latedelay = range(df_sess.borders_full[0][6]-3, df_sess.borders_full[0][6])
- # get decoder prediction error at the end of the delay
- contra_strength = [np.mean(cP[latedelay])\
- for cP in df_sess['contraDecoderErr']]#- np.mean(np.abs(cP[baseline]))
- ipsi_strength = [np.mean(iP[latedelay])\
- for iP in df_sess['ipsiDecoderErr']]# - np.mean(np.abs(iP[baseline]))
- errors = np.rad2deg(df_sess.behav_error_prev.values)
- err_std = np.std(errors)
- precision_orig.append(err_std)
- # split into high vs low memory
- cut_perc = 20
- contra_cut, ipsi_cut = np.percentile(np.abs(contra_strength), cut_perc), np.percentile(np.abs(ipsi_strength), cut_perc)
- cc.append(contra_cut)
- # good memory means precision errors < cut_perc
- none_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
- ipsi_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
- contra_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
- both_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
- # save
- precision['none'].append(np.std(errors[none_trials]))
- precision['ipsi'].append(np.std(errors[ipsi_trials]))
- precision['contra'].append(np.std(errors[contra_trials]))
- precision['both'].append(np.std(errors[both_trials]))
- precisionerrors['none'].append(errors[none_trials])
- precisionerrors['ipsi'].append(errors[ipsi_trials])
- precisionerrors['contra'].append(errors[contra_trials])
- precisionerrors['both'].append(errors[both_trials])
- precision_mono[monkey]['none'].append(np.std(errors[none_trials]))
- precision_mono[monkey]['ipsi'].append(np.std(errors[ipsi_trials]))
- precision_mono[monkey]['contra'].append(np.std(errors[contra_trials]))
- precision_mono[monkey]['both'].append(np.std(errors[both_trials]))
- # labels for each trial
- sess_labels = np.empty(len(errors), dtype=object)
- sess_labels[none_trials] = 'none'
- sess_labels[ipsi_trials] = 'ipsi'
- sess_labels[contra_trials] = 'contra'
- sess_labels[both_trials] = 'both'
- assert len(errors) == len(sess_labels)
- labels.append(sess_labels)
- label_name.append([session for _ in sess_labels])
- label_errors.append(errors)
- h+=1
- # %%
- from scipy import stats
- def print_stats(a=[], b = [], ttest=ttest_rel, text=''):
- test, pval = ttest(a, b)
- degf = len(a) - 1
- mean_x = np.mean(a - np.array(b))
- se = stats.sem(a - np.array(b)) # standard error of the mean
- ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
- print(text+', '+str(ttest)+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
- ", mean = "+str(np.round(mean_x, 2))+\
- ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
- return
- f, ax = plt.subplots(figsize=(1.7,1.3))
- ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%0.1f'))
- colors_mono = {'Sa': 'darkred', 'Pe': 'lightsalmon', 'Wa':'k'}
- for monkey in monkeys:
- sesss = len(precision_mono[monkey]['none'])
- precisions = [precision_mono[monkey]['none'], precision_mono[monkey]['ipsi'],\
- precision_mono[monkey]['contra'], precision_mono[monkey]['both']]
- #plt.plot([np.zeros((sesss)), np.ones((sesss)), 2*np.ones((sesss)), 3*np.ones((sesss))],\
- # precisions, color=colors_mono[monkey], alpha=0.3)
- plt.axhline(np.mean(precision_orig), color='k', lw=0.5)
- plt.errorbar(0,np.mean(precision['none']), yerr=sem(precision['none']), color='k', marker='o')
- plt.errorbar(1,np.mean(precision['ipsi']), yerr=sem(precision['ipsi']), color=colors['Across'], marker='o')
- plt.errorbar(2,np.mean(precision['contra']), yerr=sem(precision['contra']), color=colors['Across'], marker='o')
- plt.errorbar(3,np.mean(precision['both']), yerr=sem(precision['both']), color=colors['Reactivation'], marker='o')
- plt.ylabel('error std. (°)')
- plt.xticks([0,1,2,3], ['none', 'ipsi', 'contra', 'dual'])
- plt.xlabel('hemispheric memories')
- # p-values
- y0, y1 = ax.get_ylim()
- for l, label in enumerate(['none', 'ipsi', 'contra', 'both']):
- stat = ['***' if ttest_rel(precision[label], precision_orig)[1] < 0.005\
- else '**' if ttest_rel(precision[label], precision_orig)[1] < 0.01\
- else '*' if ttest_rel(precision[label], precision_orig)[1] < 0.05 else 'n.s.'][0]
- ax.annotate(stat,(l, y1), ha='center', va='bottom', fontsize=10, color='#333333')
- ax.plot([2,2,3,3],\
- [y1+ 1*y1/8,y1 + 1*y1/5, y1 + 1*y1/5, y1+ 1*y1/8], linewidth=1, color='#333333')
- ax.annotate('p = '+str(np.round(ttest_rel(precision['both'], precision['contra'])[1],3)),\
- (2.5, y1+y1/5), ha='center', va='bottom', fontsize=9, color='#333333')
- # PRINT STATS
- # BOTH
- print_stats(a=precision['both'], b = precision_orig, ttest=ttest_rel, text='Both-Orig')
- # IPSI
- print_stats(a=precision['ipsi'], b = precision_orig, ttest=ttest_rel, text='Ipsi-Orig')
- # CONTRA
- print_stats(a=precision['contra'], b = precision_orig, ttest=ttest_rel, text='Contra-Orig')
- # BOTH - CONTRA
- print_stats(a=precision['both'], b = precision['contra'], ttest=ttest_rel, text='Both-Contra')
- # NONE
- print_stats(a=precision['none'], b = precision_orig, ttest=ttest_rel, text='None-Orig')
- # BOTH - NONE
- print_stats(a=precision['both'], b = precision['none'], ttest=ttest_rel, text='Both-None')
- plt.tight_layout()
- #plt.savefig(DATAPATH+'Figures/Paper/Neurons/BumpDrift/DualMemoryImprovement'+'.svg')
- # %% [markdown]
- # ### Supplement 9: Different cut-offs
- # %%
- percentages = np.arange(20, 80, 10)
- precision = {'none':[[] for p in percentages], 'ipsi':[[] for p in percentages],\
- 'contra':[[] for p in percentages], 'both':[[] for p in percentages]}
- precisionerrors = {'none':[[] for p in percentages], 'ipsi':[[] for p in percentages],\
- 'contra':[[] for p in percentages], 'both':[[] for p in percentages]}
- binspace = np.linspace(-np.pi/10, np.pi/10, 50)
- precision_orig = []
- labels, label_name, label_errors = [], [], []
- cc=[]
- h=0
- for monkey in monkeys:
- df_mono = df_pred_noBorder.loc[df_pred_noBorder.monkey==monkey]
- for s, session in enumerate(np.unique(df_mono.session_continuous)): # for each session
- df_sess = df_mono.loc[(df_mono.session_continuous==session)].copy().reset_index(drop=True)
- borders = df_sess.borders_full[0]
- # define end of delay
- baseline = range(df_sess.borders_full[0][0], df_sess.borders_full[0][3])
- latedelay = range(df_sess.borders_full[0][6]-3, df_sess.borders_full[0][6])
- # get decoder prediction error at the end of the delay
- contra_strength = [np.mean(cP[latedelay])\
- for cP in df_sess['contraDecoderErr']]#- np.mean(np.abs(cP[baseline]))
- ipsi_strength = [np.mean(iP[latedelay])\
- for iP in df_sess['ipsiDecoderErr']]# - np.mean(np.abs(iP[baseline]))
- errors = np.rad2deg(df_sess.behav_error_prev.values)
- err_std = np.std(errors)
- precision_orig.append(err_std)
- # split into high vs low memory
- for p, cut_perc in enumerate(percentages):
- contra_cut, ipsi_cut = np.percentile(np.abs(contra_strength), cut_perc), np.percentile(np.abs(ipsi_strength), cut_perc)
- cc.append(contra_cut)
- # good memory means precision errors < cut_perc
- none_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
- ipsi_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
- contra_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
- both_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
- # save
- precision['none'][p].append(np.std(errors[none_trials])-err_std)
- precision['ipsi'][p].append(np.std(errors[ipsi_trials])-err_std)
- precision['contra'][p].append(np.std(errors[contra_trials])-err_std)
- precision['both'][p].append(np.std(errors[both_trials])-err_std)
- precisionerrors['none'][p].append(errors[none_trials])
- precisionerrors['ipsi'][p].append(errors[ipsi_trials])
- precisionerrors['contra'][p].append(errors[contra_trials])
- precisionerrors['both'][p].append(errors[both_trials])
- # labels for each trial
- sess_labels = np.empty(len(errors), dtype=object)
- sess_labels[none_trials] = 'none'
- sess_labels[ipsi_trials] = 'ipsi'
- sess_labels[contra_trials] = 'contra'
- sess_labels[both_trials] = 'both'
- assert len(errors) == len(sess_labels)
- labels.append(sess_labels)
- label_name.append([session for _ in sess_labels])
- label_errors.append(errors)
- h+=1
- cmap_ACC = matplotlib.cm.get_cmap('Greys')
- colors_perc = [cmap_ACC(0.3+i/(len(percentages)+1)) for i in range(len(percentages))]
- f, ax = plt.subplots(figsize=(1.7,2))
- plt.axhline(0, color='k', lw=0.5)
- for p, cut_perc in enumerate(percentages):
- precisions = [precision['none'][p], precision['ipsi'][p],\
- precision['contra'][p], precision['both'][p]]
- plt.plot([0,1,2,3],np.mean(precisions, axis=1), color=colors_perc[p],\
- marker='o', linestyle='None', label=str(cut_perc)+'%')
- plt.ylabel('$\Delta$error std. (°)')
- plt.xticks([0,1,2,3], ['none', 'ipsi', 'contra', 'same'])
- plt.xlabel('hemispheric memories')
- plt.legend(fontsize=5, frameon=True)
- plt.tight_layout()
- #plt.savefig(DATAPATH+'Figures/Paper/Neurons/BumpDrift/SUPPLEMENT_DualMemoryImprovement'+'.svg')
- # %%
Figure5.ipynb at commit bd7d532, no license · at the source
Overview
- Institut d’Investigacions Biomèdiques August Pi i Sunyer (IDIBAPS), Barcelona, Spain
- Programa de doctorat en Biomedicina, Universitat de Barcelona (UB), Barcelona, Spain
- Center for the Neural Basis of Cognition, Carnegie Mellon University & University of Pittsburgh, Pittsburgh, PA USA
- Carnegie Mellon University Neuroscience Institute, Pittsburgh, PA USA
- Carnegie Mellon University Department of Biomedical Engineering, Pittsburgh, PA USA
- Laboratoire de Neurosciences Cognitives et Computationnelles, INSERM U960, Ecole Normale Superieure - PSL Research University, Paris, France
- Cognitive Neuroimaging Unit, INSERM, CEA, CNRS, Université Paris-Saclay, NeuroSpin center, Gif/Yvette, France
- Institut de neuromodulation, GHU Paris, psychiatrie et neurosciences, centre hospitalier Sainte-Anne, pôle hospitalo-universitaire 15, Université Paris Cité, Paris, France
- Institut d’Investigacions Biomèdiques de Barcelona (IIBB), CSIC, Barcelona, Spain
Abstract
The prefrontal hemispheres must coordinate dynamically to maintain a unified representation of visual space. Recently, two opposing theories using distinct storage strategies have been proposed: A high-capacity specialized architecture, where each hemisphere governs contralateral behavior, and a fail-safe redundant one, where both hemispheres jointly guide behavior across the visual space. Here, we analyzed simultaneous bilateral prefrontal cortex recordings from three male macaque monkeys performing a visuo-spatial working memory task. Both hemispheres equally predicted behavioral imprecision, decoding errors were weakly correlated between hemispheres, and serial dependence remained local within hemispheres, suggesting a redundant, weakly coupled organization. Attractor network simulations showed that redundancy improved precision when task demands were below memory capacity, while weak interhemispheric coupling increased capacity in more demanding tasks by allowing hemispheric specialization. These predicted patterns were validated in human and monkey data, reconciling previous findings and revealing a versatile interhemispheric architecture that adapts to varying cognitive demands.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 16 matches between paragraphs and lines of code.
Zenodo 20513995
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
35 files
- Figure1/
Figure1.ipynb , Jupyter, 623 lines - Figure2/
Figure2.ipynb , Jupyter, 868 lines - Figure2/
Figure2_DecoderMatrix.py , Python, 381 lines - Figure2/
Figure2_DelayDecoder.py , Python, 407 lines - Figure2/
Figure2_PercentSelective , Python, 192 lines.py - Figure2/
Figure2abc.ipynb , Jupyter, 621 lines - Figure3/
Figure3.ipynb , Jupyter, 1,933 lines - Figure3/
Figure3_CorrelateEyeResp , Python, 191 linesonse.py - Figure3/
Figure3_DecoderErrorCorr , Python, 367 lineselations.py - Figure3/
Figure3_EyeControlledCor , Python, 415 linesrelations.py - Figure3/
Figure3_Figure5_SingleTr , Python, 471 linesialDecoder.py - Figure3/
Figure3_SaccadeDecoding. , Python, 271 linespy - Figure3/
Figure3_SameTimeDecoder. , Python, 424 linespy - Figure4/
Figure4.ipynb , Jupyter, 1,022 lines - Figure4/
Figure4_SingleItemModel. , Python, 991 linespy - Figure5/
Figure5.ipynb , Jupyter, 1,797 lines - Figure6/
Figure6.ipynb , Jupyter, 408 lines - Figure6/
Figure6_MultiitemMoel.py , Python, 1,051 lines - Figure7/
MultiitemModel_HigherCap , Python, 1,032 linesacity.py - Figure7/
TwoAreaModelVaryingConne , Python, 991 linesct_MultiitemParams.py - Figure7/
ValidateSchneegans.ipynb , Jupyter, 867 lines - Figure7/
eval_HighCapacityModel.i , Jupyter, 657 linespynb - Supplement/
Supplement_DoGNeural.py , Python, 107 lines - Supplement/
Supplement_DoGReactivati , Python, 120 lineson.py - Supplement/
Supplement_DoGSerialBias , Python, 142 lines.py - Supplement/
circ_stats.py , Python, 29 lines - Supplement/
helpers.py , Python, 128 lines - Supplement/
helpers2.py , Python, 159 lines - circ_stats.py, Python, 29 lines
- create_sequentialDatafra
me.py , Python, 180 lines - filepaths.py, Python, 8 lines
- helpers.py, Python, 128 lines
- model_fcts.py, Python, 85 lines
- read_MatFiles.py, Python, 333 lines
- README.md, Text, 39 lines
melanietschiersch/redundantprefrontalhemispheres
bd7d5329176b24441eb457aecef734d02967ee04, 2 June 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
35 files
- Figure1/
Figure1.ipynb , Jupyter, 623 lines - Figure2/
Figure2.ipynb , Jupyter, 868 lines, 1 match - Figure2/
Figure2_DecoderMatrix.py , Python, 381 lines, 2 matches - Figure2/
Figure2_DelayDecoder.py , Python, 407 lines - Figure2/
Figure2_PercentSelective , Python, 192 lines.py - Figure2/
Figure2abc.ipynb , Jupyter, 621 lines - Figure3/
Figure3.ipynb , Jupyter, 1,933 lines, 1 match - Figure3/
Figure3_CorrelateEyeResp , Python, 191 lines, 2 matchesonse.py - Figure3/
Figure3_DecoderErrorCorr , Python, 367 lineselations.py - Figure3/
Figure3_EyeControlledCor , Python, 415 linesrelations.py - Figure3/
Figure3_Figure5_SingleTr , Python, 471 lines, 1 matchialDecoder.py - Figure3/
Figure3_SaccadeDecoding. , Python, 271 linespy - Figure3/
Figure3_SameTimeDecoder. , Python, 424 linespy - Figure4/
Figure4.ipynb , Jupyter, 1,022 lines - Figure4/
Figure4_SingleItemModel. , Python, 991 linespy - Figure5/
Figure5.ipynb , Jupyter, 1,797 lines, 4 matches - Figure6/
Figure6.ipynb , Jupyter, 408 lines - Figure6/
Figure6_MultiitemMoel.py , Python, 1,051 lines - Figure7/
MultiitemModel_HigherCap , Python, 1,032 lines, 1 matchacity.py - Figure7/
TwoAreaModelVaryingConne , Python, 991 lines, 2 matchesct_MultiitemParams.py - Figure7/
ValidateSchneegans.ipynb , Jupyter, 867 lines - Figure7/
eval_HighCapacityModel.i , Jupyter, 657 linespynb - Supplement/
Supplement_DoGNeural.py , Python, 107 lines - Supplement/
Supplement_DoGReactivati , Python, 120 lineson.py - Supplement/
Supplement_DoGSerialBias , Python, 142 lines, 2 matches.py - Supplement/
circ_stats.py , Python, 29 lines - Supplement/
helpers.py , Python, 128 lines - Supplement/
helpers2.py , Python, 159 lines - circ_stats.py, Python, 29 lines
- create_sequentialDatafra
me.py , Python, 180 lines - filepaths.py, Python, 8 lines
- helpers.py, Python, 128 lines
- model_fcts.py, Python, 85 lines
- read_MatFiles.py, Python, 333 lines
- README.md, Text, 39 lines
Code availability
The code accompanying the paper, specifying analyses and models is available on Github111.
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 68 scripts, each with its path and the digest of its content;
- 16 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
Data Availability Statement
The non-human primate data54 analyzed in this study are available at the KiltHub database from Carnegie Mellon University55. The human data90,91 analyzed in this study have been retrieved from the OSF database under accession code krv7g and 67tn3 (retrieved as OSF data sets osf.io/
The code accompanying the paper, specifying analyses and models is available on Github111.
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 5 keywords, 7 MeSH terms, 11 funders, 107 references.
Cite
This paper
Tschiersch, M., Umakantha, A., Williamson, R. C., Smith, M. A., Barbosa, J., & Compte, A. (2026). Redundant prefrontal hemispheres adapt storage strategy to working memory demands. Nature communications, 17(1), 8858. https://
BibTeX
@article{tschiersch2026r
author = {Tschiersch, Melanie and Umakantha, Akash and Williamson, Ryan C and Smith, Matthew A and Barbosa, Joao and Compte, Albert},
title = {{Redundant prefrontal hemispheres adapt storage strategy to working memory demands}},
journal = {Nature communications},
year = {2026},
month = jul,
volume = {17},
number = {1},
pages = {8858},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/
url = {https://
pmid = {42477323},
pmcid = {PMC13500473}
}
RIS
TY - JOUR
AU - Tschiersch, Melanie
AU - Umakantha, Akash
AU - Williamson, Ryan C
AU - Smith, Matthew A
AU - Barbosa, Joao
AU - Compte, Albert
TI - Redundant prefrontal hemispheres adapt storage strategy to working memory demands
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/
VL - 17
IS - 1
SP - 8858
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Redundant prefrontal hemispheres adapt storage strategy to working memory demands",
"container-title": "Nature communications",
"author": [
{
"family": "Tschiersch",
"given": "Melanie"
},
{
"family": "Umakantha",
"given": "Akash"
},
{
"family": "Williamson",
"given": "Ryan C"
},
{
"family": "Smith",
"given": "Matthew A"
},
{
"family": "Barbosa",
"given": "Joao"
},
{
"family": "Compte",
"given": "Albert"
}
],
"container-title-short":
"volume": "17",
"issue": "1",
"page": "8858",
"DOI": "10.1038/
"PMID": "42477323",
"PMCID": "PMC13500473",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
20
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s42003-026-10071-9 [code]
- Alpha phase coding supports feature binding during working memory maintenance.Journal: Communications biologyIn common: Brian 2, scikit-learn, SciPy, 2 other tools, cognitive, 6 references, 2 authors
- [2] doi:10.1371/journal.pbio.3003333
- Attractive serial dependence arises during decision-makingJournal: n/aIn common: cognitive, 14 references
- [3] doi:10.1038/s41467-026-71725-0 [code]
- Interactions across hemispheres in prefrontal cortex reflect global cognitive processing.Journal: Nature communicationsIn common: scikit-learn, pandas, SciPy, 2 other tools, non-human primate, cognitive, 6 references, author Matthew A Smith
- [4] doi:10.1186/s12915-026-02515-9 [code]
- Different modality-specific mechanisms mediate serial dependence effects in visual and auditory perception.Journal: BMC biologyIn common: cognitive, 9 references
- [5] doi:10.1038/s41467-026-75704-3 [code]
- A minimal model of working memory in neural systems and neuromorphic circuits.Journal: Nature communicationsIn common: Brian 2, SciPy, Matplotlib, 1 other tool, computational modeling (no new data), 4 references
- [6] doi:10.1016/j.isci.2026.117492 [code]
- Neural subspace reorganization reflects value-based decision-making.Journal: iScienceIn common: statsmodels, scikit-learn, pandas, 3 other tools, cognitive, 4 references
- [7] doi:10.1038/s41562-026-02414-7 [code]
- Optimized feature gains explain and predict successes and failures of human selective listening.Journal: Nature human behaviourIn common: Pingouin, h5py, statsmodels, 6 other tools, cognitive, 1 reference
- [8] doi:10.7554/elife.99278 [code]
- Brain-wide arousal signals are segregated from movement planning in the superior colliculus of the macaque.Journal: eLifeIn common: h5py, NumPy, non-human primate, 2 references, author Matthew A Smith
- [9] doi:10.1038/s41593-026-02314-z [code]
- Low-dimensional population dynamics in the brainstem gate REM sleep.Journal: Nature neuroscienceIn common: Pingouin, h5py, statsmodels, 6 other tools, 1 reference
- [10] doi:10.1038/s41467-026-74823-1 [code]
- Cerebellar activity is triggered by reach endpoint during learning of a complex locomotor task.Journal: Nature communicationsIn common: Pingouin, h5py, statsmodels, 6 other tools, 1 reference
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 68 scripts, and 16 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:991a5da4c3c79933…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
