OSCR

Redundant prefrontal hemispheres adapt storage strategy to working memory demands.

Code ↔ Paper

16 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 16 matches
  1. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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. [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

  1. # %%
  2. %cd ..
  3. # %%
  4. import matplotlib.pyplot as plt
  5. plt.style.use("stylefile.mplstyle")
  6. # Define colors
  7. colors = {
  8. "Single": "#333333",
  9. "Left": "#246EB9",
  10. "Right": "#8B1E3F",
  11. "Ipsilateral": "#1B9E77",
  12. "Contralateral": "#D95F02",
  13. "Within": "#2B4162",
  14. "Across": "#E89D0B",
  15. 'SerialBiasWeak': '#254441',
  16. 'SerialBias': '#43AA8B',\
  17. 'ReactivationWeak': '#7A8A99',
  18. 'Reactivation': '#12719E'
  19. }
  20. from matplotlib.colors import LinearSegmentedColormap
  21. left_right_cmap = LinearSegmentedColormap.from_list("LeftRight",\
  22. [colors['Left'], "white", colors['Right']])
  23. ipsi_contra_cmap = LinearSegmentedColormap.from_list("IpsiContra",\
  24. [colors['Ipsilateral'], "white", colors['Contralateral']])
  25. within_cmap = LinearSegmentedColormap.from_list("Within",\
  26. ["white", colors['Within']])
  27. across_cmap = LinearSegmentedColormap.from_list("Across",\
  28. ["white", colors['Across']])
  29. # %%
  30. ## circstats
  31. def len2(x):
  32. if type(x) is not type([]):
  33. if type(x) is not type(array([])):
  34. return -1
  35. return len(x)
  36. def phase2(x):
  37. if not np.isnan(x):
  38. return phase(x)
  39. return nan
  40. def circdist(angles1,angles2):
  41. ''' calculates circular distance of angles [rad]'''
  42. if len2(angles2) < 0:
  43. if len2(angles1) > 0:
  44. angles2 = [angles2]*len(angles1)
  45. else:
  46. angles2 = [angles2]
  47. angles1 = [angles1]
  48. if len2(angles1) < 0:
  49. angles1 = [angles1]*len(angles2)
  50. return array(list(map(lambda a1,a2: phase2(np.exp(1j*a1)/np.exp(1j*a2)), angles1,angles2)))
  51. # %%
  52. def sig_bar(sigs,axis,y,ax,color):
  53. w=np.diff(axis)[0]
  54. for s in sigs:
  55. beg =axis[s]-w/2
  56. end = axis[s]+w/2
  57. ax.fill_between([beg,end],[y[0],y[0]],[y[1],y[1]],color=color)
  58. # %%
  59. def sign_rl(rel_loc):
  60. # returns sign of array unless value=0, returns 1
  61. sign = [np.sign(rel_loc[rl]) if rel_loc[rl]!=0.0 else 1 for rl in range(len(rel_loc))]
  62. return np.array(sign)
  63. # %%
  64. import numpy as np
  65. def plot_twolines_full(R=[],base=[], bins=0.2,labelR='',labelB='', errorbars='SEM', MeanType = np.nanmean,\
  66. borders=[], ylabel='decoding', yticks = False, titel='', significances=False,\
  67. currentTrial=False, end_border = 15, shorten_delay = 0,\
  68. refline =0, colors=[colors['Within'], colors['Across']]):
  69. offset_start = borders[1]-borders[0]
  70. x = (np.linspace(borders[1], borders[5]-shorten_delay, borders[5]-borders[1]-shorten_delay)-\
  71. (borders[2]-borders[1])-offset_start)*bins
  72. x3 = (np.linspace(borders[5]+shorten_delay, borders[8], borders[8]-borders[5]+1-shorten_delay)-\
  73. (borders[7]-borders[1])-offset_start)*bins
  74. if end_border < 8:# need end of one trial, start of next trial
  75. x3 = (np.linspace(borders[5]+shorten_delay, borders[end_border], borders[end_border]-borders[5]-shorten_delay)-\
  76. (borders[7]-borders[1])-offset_start)*bins
  77. width_radius = [len(x), len(x3)]
  78. # only add 3rd axis if we look at start of next trial
  79. if end_border > 12:
  80. end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-\
  81. (borders[13]-borders[1])-offset_start)*bins
  82. start_curr = (np.linspace(borders[12], borders[end_border], borders[end_border]-borders[12])-\
  83. (borders[13]-borders[1])-offset_start)*bins
  84. x5 = np.append(end_prev,start_curr)
  85. width_radius = [len(x), len(x3), len(x5)]# len(x3),
  86. elif end_border >=10:
  87. end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-offset_start)*bins
  88. x5 = end_prev
  89. width_radius = [len(x), len(x3), len(x5)]# len(x3),
  90. compare = 0.05#/len(np.append(np.append(x,x2),x5))
  91. # TTESTS:
  92. pvals = ttest_rel(R,base, axis=0, nan_policy='omit')[1]
  93. pvals1 = ttest_1samp(R, 0, axis=0, nan_policy='omit')[1]
  94. pvals2 = ttest_1samp(base, 0, axis=0, nan_policy='omit')[1]
  95. # compute mean and errorbars
  96. mean_R = MeanType(R, axis=0)
  97. mean_base = MeanType(base, axis=0)
  98. if MeanType == circmean:
  99. mean_R = MeanType(R, axis=0, low=-np.pi, high=np.pi)
  100. mean_base = MeanType(base, axis=0, low=-np.pi, high=np.pi)
  101. if errorbars=='CI':
  102. std_R = 2*sem(R, axis=0, nan_policy='omit')
  103. std_base = 2*sem(base, axis=0, nan_policy='omit')
  104. else:
  105. std_R = sem(R, axis=0, nan_policy='omit')
  106. std_base = sem(base, axis=0, nan_policy='omit')
  107. #f, (ax1,ax5) = plt.subplots(1, 2,sharey=True,figsize=(3.8,1.6), gridspec_kw={'width_ratios': width_radius})
  108. #f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(16,5), gridspec_kw={'width_ratios': width_radius})
  109. if end_border < 10:
  110. f, (ax1,ax3) = plt.subplots(1, 2,sharey=True,figsize=(3.,2), gridspec_kw={'width_ratios': width_radius})
  111. else:
  112. f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(3.5, 2), gridspec_kw={'width_ratios': width_radius})
  113. ###################### PLOT FIXTION TO DELAY PREVIOUS
  114. cut = range(borders[1], borders[5]-shorten_delay)
  115. # within
  116. ax1.plot(x,mean_R[cut], color=colors[0], label=labelR)#+', '+errorbars
  117. 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)
  118. # across
  119. ax1.plot(x,mean_base[cut], color=colors[1], label=labelB)#+', '+errorbars
  120. ax1.fill_between(x, mean_base[cut]-np.array(std_base[cut]), mean_base[cut]+np.array(std_base[cut]),\
  121. color=colors[1], alpha=0.3)
  122. if currentTrial:
  123. ax1.set_xlabel('fix$_{n+1}$', labelpad=0)
  124. else:
  125. ax1.set_xlabel('fix$_{n}$', labelpad=0)
  126. ax1.spines['right'].set_visible(False)
  127. ax1.spines['top'].set_visible(False)
  128. ax1.xaxis.set_ticks_position('bottom')
  129. ax1.yaxis.set_ticks_position('left')
  130. ax1.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
  131. ax1.set_ylabel(ylabel)
  132. if yticks != False:
  133. ax1.set_yticks(yticks)
  134. if (len(np.unique(base)) != 1):
  135. ax1.legend()
  136. ###################### PLOT DELAY TO SACCADE PREVIOUS
  137. cut = range(borders[5]+shorten_delay, borders[8]+1)
  138. if end_border < 8:
  139. cut = range(borders[5]+shorten_delay, borders[end_border])
  140. # within
  141. ax3.plot(x3,mean_R[cut], color=colors[0], label=labelR)
  142. 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)
  143. # across
  144. ax3.plot(x3,mean_base[cut], color=colors[1], label=labelB)
  145. 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)
  146. if currentTrial:
  147. ax3.set_xlabel('response$_{n+1}$', labelpad=0)
  148. else:
  149. ax3.set_xlabel('response$_{n}$', labelpad=0)
  150. ax3.spines['right'].set_visible(False)
  151. ax3.spines['left'].set_visible(False)
  152. ax3.spines['top'].set_visible(False)
  153. ax3.xaxis.set_ticks_position('bottom')
  154. ax3.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
  155. #ax3.set_ylabel(ylabel)
  156. #ax3.set_yticks([0,0.1])
  157. ###################### PLOT ITI PREVIOUS TO DELAY CURRENT TRIAL
  158. if end_border >= 10:
  159. pc_base = mean_base[borders[9]:borders[10]+1]
  160. pcstd_base = std_base[borders[9]:borders[10]+1]
  161. pc_R = mean_R[borders[9]:borders[10]+1]
  162. pcstd_R = std_R[borders[9]:borders[10]+1]
  163. if end_border > 12:
  164. pc_base = np.append(mean_base[borders[9]:borders[10]+1],mean_base[borders[12]:borders[15]])
  165. pcstd_base = np.append(std_base[borders[9]:borders[10]+1],std_base[borders[12]:borders[15]])
  166. pc_R = np.append(mean_R[borders[9]:borders[10]+1],mean_R[borders[12]:borders[15]])
  167. pcstd_R = np.append(std_R[borders[9]:borders[10]+1],std_R[borders[12]:borders[15]])
  168. # plot reactivaton period
  169. # within
  170. ax5.plot(x5,pc_R, color=colors[0])
  171. ax5.fill_between(x5, pc_R-np.array(pcstd_R), pc_R+np.array(pcstd_R), color=colors[0], alpha=0.3)
  172. # across
  173. ax5.plot(x5,pc_base, color=colors[1])
  174. ax5.fill_between(x5, pc_base-np.array(pcstd_base), pc_base+np.array(pcstd_base), color=colors[1], alpha=0.3)
  175. # plot baseline
  176. ax5.set_xlabel('fix$_{n+1}$', labelpad=0)
  177. #ax5.set_xticks([])
  178. ax5.spines['right'].set_visible(False)
  179. ax5.spines['left'].set_visible(False)
  180. ax5.spines['top'].set_visible(False)
  181. ax5.xaxis.set_ticks_position('bottom')
  182. ax5.axhline(refline, color='#333333', linestyle='--', alpha=0.7)
  183. y0=ax5.get_ylim()[0]
  184. y1=ax5.get_ylim()[1]
  185. ax5.fill_between([(borders[14]-borders[12])*bins, (borders[15]-borders[12])*bins],\
  186. y0, y1, color='grey', alpha=0.2)
  187. ###################### MARK IMPORTANT TIME PERIODS
  188. f.text(0.55, 0.02, 'time (s) from', ha='center', fontsize=8)
  189. # # # plot significances
  190. y0=ax3.get_ylim()[0]
  191. y1=ax3.get_ylim()[1]
  192. off = (y1-y0)/10
  193. marker=250
  194. ax1.fill_between([(borders[3]-borders[2])*bins, (borders[4]-borders[2])*bins], y0, y1, color='grey', alpha=0.2)
  195. w = (y1-y0)/50
  196. off = [y1-w, y1]
  197. sigs = np.where(pvals[borders[1]:borders[5]-shorten_delay]<compare)[0]
  198. sig_bar(sigs,x,off,ax1,'#333333')
  199. sigs = np.where(pvals[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
  200. sig_bar(sigs,x3,off,ax3,'#333333')
  201. if end_border >= 10:
  202. sigs = np.where(pvals[borders[9]:borders[10]+1]<compare)[0]
  203. sig_bar(sigs,x5,off,ax5,'#333333')
  204. f.text(0.23, 0.87, 'Stim$_{n}$', ha='center', fontsize=8)
  205. f.text(0.46, 0.87, 'Delay$_{n}$', ha='center', fontsize=8)
  206. f.text(0.85, 0.87, 'ITI$_{n}$', ha='center', fontsize=8)
  207. elif end_border > 12:
  208. sigs = np.where(np.append(pvals[borders[9]:borders[10]+1],pvals[borders[12]:borders[15]])<compare)[0]
  209. sig_bar(sigs,x5,off,ax5,'#333333')
  210. f.text(0.94, 0.87, 'Stim$_{n+1}$', ha='center', fontsize=8)
  211. else:
  212. f.text(0.29, 0.87, 'Stim$_{n}$', ha='center', fontsize=8)
  213. f.text(0.63, 0.87, 'Delay$_{n}$', ha='center', fontsize=8)
  214. if significances:
  215. # within area
  216. off = [y1-2.5*w, y1-1.5*w]
  217. sigs = np.where(pvals1[borders[1]:borders[5]-shorten_delay]<compare)[0]
  218. sig_bar(sigs,x,off,ax1,colors[0])
  219. sigs = np.where(pvals1[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
  220. sig_bar(sigs,x3,off,ax3,colors[0])
  221. if end_border >= 10:
  222. sigs = np.where(pvals1[borders[9]:borders[10]+1]<compare)[0]
  223. sig_bar(sigs,x5,off,ax5,colors[0])
  224. elif end_border > 12:
  225. sigs = np.where(np.append(pvals1[borders[9]:borders[10]+1],pvals1[borders[12]:borders[15]])<compare)[0]
  226. sig_bar(sigs,x5,off,ax5,colors[0])
  227. # across areas
  228. off = [y1-4*w, y1-3*w]
  229. sigs = np.where(pvals2[borders[1]:borders[5]-shorten_delay]<compare)[0]
  230. sig_bar(sigs,x,off,ax1,colors[1])
  231. sigs = np.where(pvals2[borders[5]+shorten_delay:borders[8]+1]<compare)[0]
  232. sig_bar(sigs,x3,off,ax3,colors[1])
  233. if end_border >= 10:
  234. sigs = np.where(pvals2[borders[9]:borders[10]+1]<compare)[0]
  235. sig_bar(sigs,x5,off,ax5,colors[1])
  236. elif end_border > 12:
  237. sigs = np.where(np.append(pvals2[borders[9]:borders[10]+1],pvals2[borders[12]:borders[15]])<compare)[0]
  238. sig_bar(sigs,x5,off,ax5,colors[1])
  239. plt.suptitle(titel)
  240. return
  241. # %%
  242. def plot_threelines_full(R=[],base=[],base2=[], bins=200,labelR='',labelB='',labelB2='',\
  243. errorbars='SEM', MeanType = np.nanmean, shorten_delay = 0,\
  244. borders=[], ylabel='decoding', cutyaxis = False, titel='',\
  245. colors=['#333333', colors['Within'], colors['Across']]):
  246. offset_start = borders[1]-borders[0]
  247. x = (np.linspace(borders[1], borders[5]-shorten_delay, borders[5]-borders[1]+1-shorten_delay)-(borders[2]-borders[1])-offset_start)*bins
  248. x3 = (np.linspace(borders[5]+shorten_delay, borders[8], borders[8]-borders[5]+1-shorten_delay)-(borders[7]-1-borders[1])-offset_start)*bins
  249. # need end of one trial, start of next trial
  250. end_prev = (np.linspace(borders[9], borders[10], borders[10]-borders[9]+1)-(borders[13]-borders[1])-offset_start)*bins
  251. start_curr = (np.linspace(borders[12], borders[15], borders[15]-borders[12])-(borders[13]-borders[1])-offset_start)*bins
  252. x5 = np.append(end_prev,start_curr)
  253. len(x5)
  254. width_radius = [len(x), len(x3), len(x5)]# len(x3),
  255. compare = 0.05#/len(np.append(np.append(x,x2),x5))
  256. # TTESTS:
  257. pvals = ttest_rel(R,base, axis=0, nan_policy='omit')[1]
  258. pvals1 = ttest_1samp(R, 0, axis=0, nan_policy='omit')[1]
  259. pvals2 = ttest_1samp(base, 0, axis=0, nan_policy='omit')[1]
  260. mean_R = MeanType(R, axis=0)
  261. mean_base = MeanType(base, axis=0)
  262. mean_base2 = MeanType(base2, axis=0)
  263. if MeanType == circmean:
  264. mean_R = MeanType(R, axis=0, low=-np.pi, high=np.pi)
  265. mean_base = MeanType(base, axis=0, low=-np.pi, high=np.pi)
  266. mean_base2 = MeanType(base2, axis=0, low=-np.pi, high=np.pi)
  267. if errorbars=='CI':
  268. std_R = 2*sem(R, axis=0, nan_policy='omit')
  269. std_base = 2*sem(base, axis=0, nan_policy='omit')
  270. std_base2 = 2*sem(base2, axis=0, nan_policy='omit')
  271. else:
  272. std_R = sem(R, axis=0, nan_policy='omit')
  273. std_base = sem(base, axis=0, nan_policy='omit')
  274. std_base2 = sem(base2, axis=0, nan_policy='omit')
  275. f, (ax1,ax3,ax5) = plt.subplots(1, 3,sharey=True,figsize=(4.8, 2.1), gridspec_kw={'width_ratios': width_radius})
  276. ###################### PLOT FIXTION TO DELAY PREVIOUS
  277. cut = range(borders[1], borders[5]+1-shorten_delay)
  278. # within
  279. ax1.plot(x,mean_base[cut], color=colors[1], label=labelB)#+', '+errorbars
  280. 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)
  281. # base2
  282. ax1.plot(x,mean_base2[cut], color=colors[2], label=labelB2)#+', '+errorbars
  283. 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)
  284. # across
  285. ax1.plot(x,mean_R[cut], color=colors[0], label=labelR)#+', '+errorbars
  286. 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)
  287. ax1.set_xlabel('fix$_{n}$', labelpad=0)
  288. ax1.spines['right'].set_visible(False)
  289. ax1.spines['top'].set_visible(False)
  290. ax1.xaxis.set_ticks_position('bottom')
  291. ax1.yaxis.set_ticks_position('left')
  292. ax1.axhline(0, color='#333333', linestyle='--', alpha=0.7)
  293. ax1.set_ylabel(ylabel)
  294. ax1.legend()
  295. # ###################### PLOT DELAY TO SACCADE PREVIOUS
  296. cut = range(borders[5]+shorten_delay, borders[8]+1)
  297. # within
  298. ax3.plot(x3,mean_base[cut], color=colors[1], label=labelB)
  299. 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)
  300. # base2
  301. ax3.plot(x3,mean_base2[cut], color=colors[2], label=labelB2)
  302. 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)
  303. # across
  304. ax3.plot(x3,mean_R[cut], color=colors[0], label=labelR)
  305. 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)
  306. ax3.set_xlabel('response$_{n}$', labelpad=0)
  307. ax3.spines['right'].set_visible(False)
  308. ax3.spines['left'].set_visible(False)
  309. ax3.spines['top'].set_visible(False)
  310. ax3.xaxis.set_ticks_position('bottom')
  311. ax3.axhline(0, color='#333333', linestyle='--', alpha=0.7)
  312. ###################### PLOT ITI PREVIOUS TO DELAY CURRENT TRIAL
  313. pc_base = np.append(mean_base[borders[9]:borders[10]+1],mean_base[borders[12]:borders[15]])
  314. std_base = np.append(std_base[borders[9]:borders[10]+1],std_base[borders[12]:borders[15]])
  315. pc_base2 = np.append(mean_base2[borders[9]:borders[10]+1],mean_base2[borders[12]:borders[15]])
  316. std_base2 = np.append(std_base2[borders[9]:borders[10]+1],std_base2[borders[12]:borders[15]])
  317. pc_R = np.append(mean_R[borders[9]:borders[10]+1],mean_R[borders[12]:borders[15]])
  318. std_R = np.append(std_R[borders[9]:borders[10]+1],std_R[borders[12]:borders[15]])
  319. # plot
  320. # within
  321. ax5.plot(x5,pc_base, color=colors[1])
  322. ax5.fill_between(x5, pc_base-np.array(std_base), pc_base+np.array(std_base), color=colors[1], alpha=0.2)
  323. # base2
  324. ax5.plot(x5,pc_base2, color=colors[2])
  325. ax5.fill_between(x5, pc_base2-np.array(std_base2), pc_base2+np.array(std_base2), color=colors[2], alpha=0.2)
  326. # across
  327. ax5.plot(x5,pc_R, color=colors[0])
  328. ax5.fill_between(x5, pc_R-np.array(std_R), pc_R+np.array(std_R), color=colors[0], alpha=0.2)
  329. # plot baseline
  330. ax5.set_xlabel('fix$_{n+1}$', labelpad=0)
  331. #ax5.set_xticks([])
  332. ax5.spines['right'].set_visible(False)
  333. ax5.spines['left'].set_visible(False)
  334. ax5.spines['top'].set_visible(False)
  335. ax5.xaxis.set_ticks_position('bottom')
  336. ax5.axhline(0, color='#333333', linestyle='--', alpha=0.7)
  337. if cutyaxis != False:
  338. ax5.set_yticks(cutyaxis)
  339. ax5.set_ylim(cutyaxis)
  340. ###################### MARK IMPORTANT TIME PERIODS
  341. y0=ax5.get_ylim()[0]
  342. y1=ax5.get_ylim()[1]
  343. off = (y1-y0)/10
  344. #marker=30
  345. marker=250
  346. # ax1.plot(0, color='midnightblue', alpha=0.5)
  347. ax1.fill_between([(borders[3]-borders[2])*bins, (borders[4]-borders[2])*bins], y0, y1, color='grey', alpha=0.2)
  348. ax3.plot(0, color='#A0B2A6', alpha=0.5)
  349. ax5.plot(0, color='midnightblue', alpha=0.5)
  350. ax5.fill_between([(borders[14]-borders[12])*bins, (borders[15]-borders[12])*bins], y0, y1, color='grey', alpha=0.2)
  351. f.text(0.55, -0., 'time (ms) from', ha='center', fontsize=12)
  352. f.text(0.23, 0.75, 'Stim$_{n}$', ha='center', fontsize=10)
  353. f.text(0.46, 0.75, 'Delay$_{n}$', ha='center', fontsize=10)
  354. f.text(0.85, 0.75, 'ITI$_{n}$', ha='center', fontsize=10)
  355. f.text(0.94, 0.75, 'Stim$_{n+1}$', ha='center', fontsize=10)
  356. plt.suptitle(titel)
  357. return
  358. # %% [markdown]
  359. # # LOAD DATA
  360. # %%
  361. import pandas as pd
  362. import matplotlib.pyplot as plt
  363. import seaborn as sns
  364. import scipy
  365. from scipy.io import loadmat
  366. from scipy.stats import *
  367. from scipy.optimize import curve_fit
  368. from cmath import phase
  369. from numpy import array
  370. from scipy.sparse import csr_matrix
  371. import urllib
  372. import pickle
  373. from scipy.io import loadmat
  374. import glob
  375. import sklearn
  376. from sklearn.linear_model import LinearRegression
  377. from sklearn.linear_model import LogisticRegression
  378. from sklearn.model_selection import train_test_split
  379. #from pymicro.view.vol_utils import compute_affine_transform
  380. from sklearn.model_selection import LeaveOneOut
  381. from sklearn.metrics import accuracy_score
  382. from sklearn import preprocessing
  383. import statsmodels.formula.api as sf
  384. from sklearn import metrics
  385. from random import randint
  386. from numpy.linalg import inv
  387. import math
  388. import io
  389. #import h5py
  390. from circ_stats import *
  391. from patsy import dmatrices
  392. import statsmodels.api as sm
  393. import helpers as hf
  394. import statsmodels.formula.api as smf
  395. import copy
  396. import matplotlib.ticker as ticker
  397. from pingouin import circ_corrcc
  398. #monkeys = ["Sa", "Pe", "Wa"]
  399. #for m in monkeys:
  400. # files = np.sort(glob.glob('../Data/new/%s*.mat' %m))
  401. # for f in files:
  402. # print(f)
  403. with open('./Results_Full/df_serial.pickle', 'rb') as handle:
  404. #with open('./Results/df_serial_Sa0.pickle', 'rb') as handle:
  405. df_sb = pickle.load(handle)
  406. df_sb = df_sb.reset_index()
  407. df_sb.rel_loc = np.round(df_sb.rel_loc,3)
  408. monkeys=df_sb.monkey.unique()
  409. #df_behav = df_behav.loc[(df_behav.monkey=='Sa') | (df_behav.monkey=='Wa')].reset_index(drop=True)
  410. df_sb['session_continuous'] = [df_sb.monkey[i]+str(df_sb.session[i]) for i in df_sb.index]
  411. same_id = 1
  412. opp_id = 0
  413. border_id = 2
  414. # DEFINE DOG FIT PARAMETERS FOR EACH ANIMAL (based on BIC, questionable for Pe, Wa (delta BIC <2)
  415. sigma={'Sa':0.9, 'Pe':2.15, 'Wa':0.45}
  416. neural_sigma = {'Sa': 1.35, 'Pe': 0.9, 'Wa': 1.75}#
  417. reactivation_sigma={'Sa':2.95, 'Pe':1.15, 'Wa':1.55}
  418. # %%
  419. def cut_task_timings(timecourse=[], timeperiods=np.array([]), max_time=np.nan):
  420. """
  421. Cuts the variable timecourse together based on the varying task timing in multiple sessions.
  422. Aligns to shortest session timing
  423. timecourse : np.array() of shape (sessions x trials x time)
  424. timeperiods: np.array() of shape (sessions x time)
  425. cut_array: np.array() of shape (sessions x trials x minimum_time)
  426. """
  427. # find minimum taskperiod onsets / durations across sessions
  428. min_duration = np.min(np.diff(timeperiods),axis=0)
  429. min_onset = np.append(0,np.cumsum(min_duration))#add one more 0 at start so borders and diff add up
  430. # if no end time is given, do for all timesteps
  431. if np.isnan(max_time):
  432. max_time = timeperiods.shape[-1]
  433. # cut sessions together
  434. # cut_array = np.array([np.concatenate([timecourse[sess][timing:timing+min_duration[idx]]\
  435. # if idx not in [5, 9, 16, 20]\
  436. # else timecourse[sess][timing-min_duration[idx]:timing]
  437. # for idx,timing in enumerate(timeperiods[sess][:max_time])])\
  438. # for sess in range(len(timecourse))])
  439. cut_array = np.array([np.concatenate([timecourse[sess][timing:timing+min_duration[idx]]\
  440. if idx not in [5, 9, 16, 20]\
  441. else timecourse[sess][timeperiods[sess][idx+1]-min_duration[idx]:timeperiods[sess][idx+1]]
  442. for idx,timing in enumerate(timeperiods[sess][:max_time])])\
  443. for sess in range(len(timecourse))])
  444. #cut_array = np.array([cut_array])
  445. return cut_array, min_onset
  446. # %% [markdown]
  447. # ----
  448. # %% [markdown]
  449. # # Single trial analyses
  450. # %%
  451. def abs_err(prediction, all_targets):
  452. return np.abs([circdist(prediction, targ) for targ in all_targets])
  453. df_behav = df_sb.copy()
  454. #df_behav = df_behav.loc[(df_behav.monkey=='Sa') | (df_behav.monkey=='Wa')].reset_index(drop=True)
  455. df_behav['session_continuous'] = df_behav['monkey']+df_behav['session'].astype(str)
  456. # drop session with shorter stimulus
  457. df_behav.drop(index = np.where(df_behav.session_continuous == 'Pe2')[0], inplace=True)
  458. df_behav.reset_index(drop=True, inplace=True)
  459. # LOAD NEURAL DECODER DATA
  460. # INSTEAD OF RESPONSE ORTHO USE DELAY DECODER
  461. with open('../../Desktop/PhD/2_Smith/Results/SingleTrialDecoding/SingletrialDecoder.pickle', 'rb') as handle:
  462. #with open('./Results/Figure3/SingletrialDecoder_Sa0.pickle', 'rb') as handle:
  463. df = pickle.load(handle)
  464. df['session_continuous'] = df['monkey']+df['session'].astype(str)
  465. bins=200
  466. # compute errors and shuffles
  467. for neuron_type in ['combined', 'left', 'right']:
  468. print('Computing neuron_type: '+neuron_type)
  469. pred_prev=[]
  470. pred_prevortho=[]
  471. pred_curr=[]
  472. basecorr_prev=[]
  473. basecorr_prevortho=[]
  474. basecorr_curr=[]
  475. for i in df.index:
  476. targ_prev = np.round(np.angle(df.loc[i, 'targ_prev_xy']),3)
  477. targ_curr = np.round(np.angle(df.loc[i, 'targ_curr_xy']), 3)
  478. pprev = np.angle(df.loc[i, 'pred_complex_prev_'+neuron_type])
  479. # TODO! Change for all that re not delay decoders
  480. #pprevortho = np.angle(df.loc[i,'pred_complex_prev_ortho_'+neuron_type])
  481. pprevortho = np.angle(df.loc[i,'pred_complex_delay_'+neuron_type])
  482. pcurr = np.angle(df.loc[i,'pred_complex_curr_'+neuron_type])
  483. # compute error of prediction to target
  484. err_prev = circdist(pprev,targ_prev)
  485. err_prevortho = circdist(pprevortho,targ_prev)
  486. err_curr = circdist(pcurr,targ_curr)
  487. pred_prev.append(err_prev)
  488. pred_prevortho.append(err_prevortho)
  489. pred_curr.append(err_curr)
  490. df['prederror_prev_'+neuron_type] = pred_prev
  491. df['prederror_ortho_'+neuron_type] = pred_prevortho
  492. df['prederror_curr_'+neuron_type] = pred_curr
  493. # define start/end decoer errors for bumpdrift
  494. for neuron_type in ['combined', 'left', 'right']:
  495. df[neuron_type+'DecoderErr'] = [df.loc[i,'prederror_prev_'+neuron_type]\
  496. for i in df.index]
  497. df['leftHemi'] = df['hemifield_prev_left']
  498. df['rightHemi'] = df['hemifield_prev_right']
  499. IPSI = 1
  500. BORDER = 0
  501. CONTRA = -1
  502. # drop session with shorter stimulus
  503. df.drop(index = np.where(df.session_continuous == 'Pe2')[0], inplace=True)
  504. df.reset_index(drop=True, inplace=True)
  505. assert list(df.index) == list(df_behav.index), 'For merging dataframes: must be of same length'
  506. df['behav_response_prev'] = df_behav['response_prev']
  507. df['behav_response_curr'] = df_behav['response_curr']
  508. df['response_prev_curr'] = np.round(circdist(df.behav_response_prev, np.angle(df.targ_curr_xy)),3)
  509. df['delay_curr'] = df_behav.delay_curr
  510. # drop border trials (for ipsi contra analysis)
  511. df_pred_noBorder = df.drop(index=np.where(df.leftHemi==BORDER)[0])
  512. df.head()
  513. # %% [markdown]
  514. # ---
  515. # %% [markdown]
  516. # # Serial dependence
  517. # %% [markdown]
  518. # ### Fig 5a: Neural SD at stim., delay
  519. # %%
  520. # decoder error of delay_n+1 to target_n+1
  521. folded=False
  522. for m,monkey in enumerate(monkeys):#enumerate(['Sa', 'Wa']):#
  523. borders_mono=[]
  524. df_mono = df.loc[(df.monkey==monkey)].copy().reset_index(drop=True)
  525. df_mono['sign_prevcurr'] = sign_rl(df_mono.prev_curr.values)
  526. if folded==True:
  527. df_mono['prev_curr'] = np.abs(df_mono.prev_curr)
  528. 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]\
  529. # shape: (sessions x delay groups x prev-curr differences)
  530. decodersd = np.empty((len(np.unique(df_mono.session)), 2, len(prevcurr)))*np.nan
  531. mono_prederr = {'early':np.empty((len(df_mono)))*np.nan, 'late':np.empty((len(df_mono)))*np.nan}
  532. # compute SD for sessions separately
  533. for session_id,session in enumerate(np.unique(df_mono.session)):#
  534. df_sess = df_mono.loc[(df_mono.session==session)].copy()
  535. borders_full = df_sess.borders_full.values[0]
  536. d_start = borders_full[14]+1
  537. d_end = borders_full[18]-2
  538. # start of delay
  539. prederr = [circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_start]),\
  540. np.angle(df_sess.shufflepred_complex_curr_combined[i][d_start]))\
  541. for i in df_sess.index]
  542. mono_prederr['early'][df_sess.index] = np.squeeze(prederr)
  543. df_sess['prederr_currdelay_start'] = np.squeeze(prederr)
  544. # end of delay
  545. prederr = [circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
  546. np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end]))\
  547. for i in df_sess.index]
  548. mono_prederr['late'][df_sess.index] = np.squeeze(prederr)
  549. df_sess['prederr_currdelay_end'] = np.squeeze(prederr)
  550. if folded==True: # flip error, rel_loc in case of folded
  551. df_sess['prederr_currdelay_start'] = df_sess['prederr_currdelay_start'].values*\
  552. df_sess.sign_prevcurr
  553. df_sess['prederr_currdelay_end'] = df_sess['prederr_currdelay_end'].values*\
  554. df_sess.sign_prevcurr
  555. # get mean PREDICTION error at each relative location for each session, delay split
  556. for loc_id,loc in enumerate(prevcurr):
  557. decodersd[session_id, 0, loc_id] = circmean(df_sess.loc[df_sess.prev_curr==loc]['prederr_currdelay_start'],\
  558. low=-np.pi, high=np.pi)
  559. decodersd[session_id, 1, loc_id] = circmean(df_sess.loc[df_sess.prev_curr==loc]['prederr_currdelay_end'],\
  560. low=-np.pi, high=np.pi)
  561. # plt.hist(df_sess['prederr_currdelay'+str(delay_id)].values, alpha=0.3)
  562. # plt.show()
  563. # get session-mean in each relative location (nans per session if the target didn't appear)
  564. mean = np.nanmean(decodersd, axis=0)
  565. # TODO change errorbars
  566. errorbars = 'SEM'
  567. if errorbars=='SEM':
  568. std = sem(decodersd, axis=0, nan_policy='omit')
  569. elif errorbars == "CI":
  570. std = 2*np.nanstd(decodersd, axis=0)
  571. # make consecutive colors for diff delays
  572. label = ['early', 'late']
  573. xx = np.linspace(-np.pi, np.pi, 1000)
  574. f, ax = plt.subplots(figsize=(2.1,1.95))
  575. plt.axhline(0, color='#333333', alpha=0.5)
  576. plt.axvline(0, color='#333333', alpha=0.5)
  577. plt.errorbar(np.rad2deg(prevcurr), np.rad2deg(mean[0]), yerr=np.rad2deg(std[0]),\
  578. color=colors['SerialBiasWeak'], label='stim.')
  579. plt.errorbar(np.rad2deg(prevcurr), np.rad2deg(mean[1]), yerr=np.rad2deg(std[1]),\
  580. color=colors['SerialBias'], label='delay')
  581. # fit DoG
  582. df_mono['neural_error'] = mono_prederr[label[0]]
  583. df_mono['neural_error_late'] = mono_prederr[label[1]]
  584. para = neural_sigma[monkey]
  585. df_mono['bias_estimate'] = -hf.dog1(para, df_mono.prev_curr)
  586. # fit stimulus
  587. model = smf.ols('neural_error ~ bias_estimate', data=df_mono).fit()
  588. plt.plot(np.rad2deg(xx), np.rad2deg(model.params['Intercept']+\
  589. model.params['bias_estimate']*-hf.dog1(para, xx)),\
  590. color=colors['SerialBiasWeak'], dashes=[1,1])
  591. # reporting summary EARLY
  592. ci_low, ci_high = model.conf_int().loc['bias_estimate']
  593. print('beta = '+str(np.round(np.rad2deg(model.params['bias_estimate']),2))+\
  594. ', 95% CI ['+str(np.round(np.rad2deg(ci_low),2))+', '+str(np.round(np.rad2deg(ci_high),2))+'], '+\
  595. 't('+str(int(model.df_resid))+') = '+str(np.round(model.tvalues['bias_estimate'],2))+\
  596. ', p = '+str("{:.2e}".format(model.pvalues['bias_estimate'])) +\
  597. ', # trials: '+str(len(df_mono))+', '+str(len(df_mono.session.unique()))+' sessions.')
  598. # fit delay
  599. model = smf.ols('neural_error_late ~ bias_estimate', data=df_mono).fit()
  600. plt.plot(np.rad2deg(xx), np.rad2deg(model.params['Intercept']+\
  601. model.params['bias_estimate']*-hf.dog1(para, xx)),\
  602. color=colors['SerialBias'], dashes=[1,1])
  603. # reporting summary LATE
  604. ci_low, ci_high = model.conf_int().loc['bias_estimate']
  605. print('beta = '+str(np.round(np.rad2deg(model.params['bias_estimate']),2))+\
  606. ', 95% CI ['+str(np.round(np.rad2deg(ci_low),2))+', '+str(np.round(np.rad2deg(ci_high),2))+'], '+\
  607. 't('+str(int(model.df_resid))+') = '+str(np.round(model.tvalues['bias_estimate'],2))+\
  608. ', p = '+str("{:.2e}".format(model.pvalues['bias_estimate'])) +\
  609. ', # trials: '+str(len(df_mono))+', '+str(len(df_mono.session.unique()))+' sessions.')
  610. plt.xlabel('rel. previous location (°)')
  611. plt.ylabel('decoder err. (°)')
  612. plt.legend()
  613. sns.despine()
  614. ax.xaxis.set_ticks_position('bottom')
  615. ax.yaxis.set_ticks_position('left')
  616. if monkey=='Sa':
  617. plt.ylim([-5, 5])
  618. plt.tight_layout()
  619. #plt.savefig(DATAPATH+'./Figures/Figure5/INLAYSerialBiasDrift_NeuralFitDoG_'+monkey+'.svg')
  620. sns.despine()
  621. plt.show()
  622. # %% [markdown]
  623. # ### Fig 5b: neural SD
  624. # %%
  625. # compute correlations of neuron and behavior
  626. # for each session get a correlation of neurons / behavior separately
  627. sd_all = {m: [] for m in monkeys}
  628. sd_joint = []
  629. SD_slopes = []
  630. borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
  631. for _, sess_df in df[df.monkey == monkey].groupby('session')]
  632. borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
  633. min_stimDelay = borders_mean[16] - borders_mean[14]
  634. for m,monkey in enumerate(['Sa', 'Wa']):#enumerate(['Sa','Wa']):#, 'Wa']):#
  635. print('Computing monkey '+monkey+'...')
  636. df_mono = df.loc[df.monkey==monkey]
  637. # compute SD for sessions separately
  638. for session_id,session in enumerate(np.unique(df_mono.session)):#
  639. df_sess = df_mono.loc[(df_mono.session==session)].copy().reset_index(drop=True)
  640. df_sess['bias_estimate'] = -hf.dog1(neural_sigma[monkey], df_sess.prev_curr)
  641. # compute DECODER SD for different time points in the CURRENT delay
  642. sd_time=[]
  643. for delay_id,delay in enumerate(range(len(df_sess.pred_complex_curr_combined[1]))):
  644. # get average prediction error in defined timesteps
  645. df_sess['prederr_delaystep'] = circdist([np.angle(df_sess.pred_complex_curr_combined[i][delay])\
  646. for i in df_sess.index],\
  647. [np.angle(df_sess.shufflepred_complex_curr_combined[i][delay])\
  648. for i in df_sess.index])
  649. # fit model
  650. model = smf.ols('prederr_delaystep ~ bias_estimate', data=df_sess).fit()
  651. #save parameter of model
  652. sd_time.append(np.rad2deg(model.params['bias_estimate']))
  653. sd_joint.append(sd_time)
  654. sd_all[monkey].append(sd_time)
  655. # fit slopr through time
  656. # select timing (from stim-start of each session to minimum delay length)
  657. x_coords = np.array(range(df_sess.borders_full[0][14], df_sess.borders_full[0][14] + min_stimDelay))*bins/1000
  658. sd_stim2end = sd_time[df_sess.borders_full[0][14]:df_sess.borders_full[0][14] + min_stimDelay]
  659. slopel, intercept, r_value, p_value, std_err = stats.linregress(x_coords, sd_stim2end)
  660. SD_slopes.append(slopel)
  661. # cut sessions to same length
  662. sd_cut,borders_mean = cut_task_timings(sd_joint, borders_all, 19)
  663. # only look at 2nd trial's delay
  664. sd_trial2 = sd_cut[:, borders_mean[11]:]
  665. plot_twolines_full(R=sd_trial2,base=np.zeros((sd_trial2.shape)), bins=bins,\
  666. labelR='',labelB='', errorbars='SEM', end_border=6,shorten_delay = 2, currentTrial=True,\
  667. borders=borders_mean, ylabel='history drift (°)', colors=[colors['SerialBias'], '#333333'])
  668. plt.tight_layout()
  669. #plt.savefig('./Figures/Figure5/SerialBiasDelayDrift_SaWa_200ms.svg')
  670. plt.show()
  671. ## STATISTICAL TESTING
  672. print('Slope computed on '+str(min_stimDelay)+' independent '+str(bins)+'ms time bins.')
  673. degf = len(SD_slopes) - 1
  674. test, pval = ttest_1samp(SD_slopes, 0)
  675. mean_x = np.mean(SD_slopes)
  676. se = stats.sem(SD_slopes) # standard error of the mean
  677. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  678. print('SD slope 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  679. ", mean = "+str(np.round(mean_x))+\
  680. ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
  681. # %% [markdown]
  682. # ### Supplement: Correlate behavioral SD vs neural SD
  683. # %%
  684. from sklearn.model_selection import KFold
  685. # decoder error of delay_n+1 to target_n+1
  686. folded=False
  687. # define delay timesteps
  688. delay_steps = int(600/bins)# size of each delay step
  689. colors_SD = ['#254441ff', '#43aa8bff', '#b2b09bff']
  690. all_behavfits=[]
  691. all_neuralfits=[]
  692. f, ax = plt.subplots(figsize=(2.,1.95))
  693. ax.axhline(0, color='#333333', alpha=0.5, lw=0.5)
  694. ax.axvline(0, color='#333333', alpha=0.5, lw=0.5)
  695. for m,monkey in enumerate(monkeys):#enumerate(['Pe','Sa', 'Wa']):#
  696. borders_mono=[]
  697. df_mono = df.loc[(df.monkey==monkey)].copy().reset_index(drop=True)
  698. df_mono['sign_prevcurr'] = sign_rl(df_mono.prev_curr.values)
  699. if folded==True:
  700. df_mono['prev_curr'] = np.abs(df_mono.prev_curr)
  701. 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]\
  702. # SD FITS ARE OPTIMIZED DIFFERENTLY FOR NEURAL, BEHAV. SD
  703. df_mono['behav_error_curr_deg'] = np.rad2deg(df_mono.behav_error_curr)
  704. df_mono['behav_bias_estimate'] = -hf.dog1(sigma[monkey], df_mono.prev_curr)#sign_rl(df_sess.prev_curr.values)#
  705. df_mono['neural_bias_estimate'] = -hf.dog1(neural_sigma[monkey], df_mono.prev_curr)#sign_rl(df_sess.prev_curr.values)#
  706. mono_prederr = np.empty((len(df_mono)))*np.nan
  707. # compute SD for sessions separately
  708. for session_id,session in enumerate(np.unique(df_mono.session)):#
  709. df_sess = df_mono.loc[(df_mono.session==session)].copy()
  710. borders_full = df_sess.borders_full.values[0]
  711. d_end = range(borders_full[17]-2, borders_full[17])
  712. #d_end = borders_full[17]-1
  713. #mono_prederr[df_sess.index] = np.squeeze([circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
  714. # np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end]))\
  715. # for i in df_sess.index])
  716. mono_prederr[df_sess.index] = np.squeeze([circmean(circdist(np.angle(df_sess.pred_complex_curr_combined[i][d_end]),\
  717. np.angle(df_sess.shufflepred_complex_curr_combined[i][d_end])),\
  718. low=-np.pi, high=np.pi)\
  719. for i in df_sess.index])
  720. df_mono['prederr'] = mono_prederr
  721. ################ FIT SERIAL DEPENDENCE ##########################
  722. kf = KFold(n_splits=5, shuffle=True)
  723. behavfits=[]
  724. neuralfits=[]
  725. for k, (train_idx, test_idx) in enumerate(kf.split(df_mono.index)):
  726. df_split = df_mono.loc[train_idx]
  727. # fit BEHAVIORAL model
  728. #behavmodel = smf.mixedlm('behav_error_curr_deg ~ behav_bias_estimate', data=df_mono, groups=df_mono['session']).fit()
  729. behavmodel = smf.ols('behav_error_curr_deg ~ behav_bias_estimate', data=df_split).fit()
  730. behavfits.append(behavmodel.params[1])
  731. # fit NEURAL model# fit BEHAVIORAL model
  732. df_split['prederr_end_deg'] = np.rad2deg(df_split['prederr'])
  733. neuralmodel = smf.ols('prederr_end_deg ~ neural_bias_estimate', data=df_split).fit()
  734. #if (behavmodel.pvalues[1]<0.05) & (neuralmodel.pvalues[1]<0.05):
  735. behavfits.append(behavmodel.params[1])
  736. neuralfits.append(neuralmodel.params[1])
  737. all_behavfits.append(behavfits)
  738. all_neuralfits.append(neuralfits)
  739. ax.errorbar(np.mean(all_behavfits[m]), np.mean(all_neuralfits[m]),\
  740. xerr= sem(all_behavfits[m]),yerr=2*sem(all_neuralfits[m]), color=colors_SD[m], marker='o',\
  741. label=monkey, markersize=4)
  742. ax.set_xlabel('behavioral bias (°)')
  743. ax.set_ylabel('history drift (°)')
  744. x0, x1 = ax.get_xlim()
  745. #ax.set_xlim([x0, x1])
  746. #ax.set_ylim([x0, x1])
  747. y0, y1 = ax.get_ylim()
  748. ax.plot([y0, y1], [y0, y1], color='#333333', alpha=0.3, dashes=[5,5], lw=0.5)
  749. ax.legend()
  750. ax.xaxis.set_ticks_position('bottom')
  751. ax.yaxis.set_ticks_position('left')
  752. sns.despine()
  753. plt.tight_layout()
  754. #plt.savefig('./NeuralBehavSerialBias_compareMonkeys.svg', dpi=300)
  755. plt.show()
  756. # %% [markdown]
  757. # ---
  758. # %% [markdown]
  759. # # Reactivation drift
  760. # %%
  761. # get minimum trial durations across all sessions
  762. borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
  763. for _, sess_df in df[df.monkey == monkey].groupby('session')]
  764. borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
  765. # get reactivation location / strength for each hemisphere
  766. min_Reactlen = (borders_mean[14]+1) - borders_mean[13]
  767. print('Reactivation average is averaged across '+str(min_Reactlen)+' independent '+str(bins)+'ms time bins.')
  768. Rtime = [range(df.borders_full[i][13], df.borders_full[i][13]+min_Reactlen) for i in df.index]
  769. df['react'] = [np.angle(np.mean(df['pred_complex_delay_combined'][i][Rtime[i]]))\
  770. for i in df.index]
  771. df['react_left'] = [np.angle(np.mean(df['pred_complex_delay_left'][i][Rtime[i]]))\
  772. for i in df.index]
  773. df['react_right'] = [np.angle(np.mean(df['pred_complex_delay_right'][i][Rtime[i]]))\
  774. for i in df.index]
  775. df['react_strength'] = [np.abs(np.mean(df['pred_complex_delay_combined'][i][Rtime[i]]))\
  776. for i in df.index]
  777. df['react_strength_left'] = [np.abs(np.mean(df['pred_complex_delay_left'][i][Rtime[i]]))\
  778. for i in df.index]
  779. df['react_strength_right'] = [np.abs(np.mean(df['pred_complex_delay_right'][i][Rtime[i]]))\
  780. for i in df.index]
  781. # get delay angle, shuffle for each hemisphere
  782. min_Delaylen = borders_mean[17] - borders_mean[16]
  783. DELAYEND = [range(df.borders_full[trial][17] - min_Delaylen, df.borders_full[trial][17])\
  784. for trial in df.index]
  785. print('Delay average is averaged across '+str(min_Delaylen)+' independent '+str(bins)+'ms time bins.')
  786. df['delayAvg'] = [np.angle(np.mean(df.pred_complex_curr_combined[trial][DELAYEND[trial]]))\
  787. for trial in df.index]
  788. df['delayAvg_shuffle'] = [np.angle(np.mean(df.shufflepred_complex_curr_combined[trial][DELAYEND[trial]]))\
  789. for trial in df.index]
  790. df['delayAvg_left'] = [np.angle(np.mean(df.pred_complex_curr_left[trial][DELAYEND[trial]]))\
  791. for trial in df.index]
  792. df['delayAvg_shuffle_left'] = [np.angle(np.mean(df.shufflepred_complex_curr_left[trial][DELAYEND[trial]]))\
  793. for trial in df.index]
  794. df['delayAvg_right'] = [np.angle(np.mean(df.pred_complex_curr_right[trial][DELAYEND[trial]]))\
  795. for trial in df.index]
  796. df['delayAvg_shuffle_right'] = [np.angle(np.mean(df.shufflepred_complex_curr_right[trial][DELAYEND[trial]]))\
  797. for trial in df.index]
  798. # %% [markdown]
  799. # ### Fig 5c: Reactivation strength and precision
  800. # %%
  801. df_mono = df.loc[df.monkey=='Sa'].copy().reset_index(drop=True)
  802. # compute reactivation error (distance to previous target)
  803. df_mono['react_error'] = circdist(df_mono['react'].values, np.round(np.angle(df_mono.targ_prev_xy), 3))
  804. # determine cut-off for each hemisphere, session
  805. perc = 20
  806. df_mono[['cut']] = df_mono.groupby('session_continuous') \
  807. ['react_strength'].transform(lambda x: np.percentile(x, perc))
  808. df_high = df_mono.loc[df_mono.react_strength > df_mono.cut]
  809. df_low = df_mono.loc[df_mono.react_strength <= df_mono.cut]
  810. f,ax = plt.subplots(figsize=(2.1,1.95))
  811. ax.hist(np.rad2deg(df_high['react_error'].values), label='strong',\
  812. bins=17, density=True, histtype='step', color= colors['Reactivation'], linewidth= 1)
  813. ax.hist(np.rad2deg(df_low['react_error'].values), label='weak',\
  814. bins=17, density=True, histtype='step', color=colors['ReactivationWeak'], linewidth= 1)
  815. ax.legend()
  816. ax.set_xlim([-180, 180])
  817. ax.set_xlabel('reactivation error (°)')
  818. ax.set_ylabel('density')
  819. ax.xaxis.set_ticks_position('bottom')
  820. ax.yaxis.set_ticks_position('left')
  821. sns.despine()
  822. plt.tight_layout()
  823. #plt.savefig('./ReactivationAngleHist_Sa.svg', dpi=300)
  824. plt.show()
  825. # %% [markdown]
  826. # ### Fig 5d: Within vs across attraction to reactivation
  827. # %%
  828. # compute correlations of neuron and behavior
  829. # for each session get a correlation of neurons / behavior separately
  830. hemispheres = np.array(['left', 'right'])
  831. react_all = {'within':[], 'across':[]}
  832. react_term = {m: {side: [] for side in ['within', 'across']} for m in monkeys}
  833. borders_mono={m:[] for m in monkeys}
  834. borders_all=[]
  835. for m,monkey in enumerate(['Sa', 'Wa']):#enumerate(['Sa', 'Wa']):#
  836. print('Computing monkey '+monkey+'...')
  837. df_mono = df.loc[df.monkey==monkey]
  838. for session_id,session in enumerate(np.unique(df_mono.session)):#
  839. df_sess = df_mono.loc[(df_mono.session==session)].copy().reset_index(drop=True)
  840. borders_all.append(df_sess.borders_full[0])
  841. borders_mono[monkey].append(df_sess.borders_full[0])
  842. # get relative distance: reactivation - current target
  843. reactivationprevcurr_left = circdist(df_sess.react_left, np.angle(df_sess.targ_curr_xy))
  844. df_sess['bias_left'] = -hf.dog1(reactivation_sigma[monkey],\
  845. reactivationprevcurr_left)
  846. reactivationprevcurr_right = circdist(df_sess.react_right, np.angle(df_sess.targ_curr_xy))
  847. df_sess['bias_right'] = -hf.dog1(reactivation_sigma[monkey],\
  848. reactivationprevcurr_right)
  849. # subtract shuffle from delay error for all times
  850. df_sess['pred_shufflesub_left'] = [circdist(np.angle(df_sess['pred_complex_curr_left'][i]),\
  851. np.angle(df_sess.shufflepred_complex_curr_left[i]))\
  852. for i in df_sess.index]
  853. df_sess['pred_shufflesub_right'] = [circdist(np.angle(df_sess['pred_complex_curr_right'][i]),\
  854. np.angle(df_sess.shufflepred_complex_curr_right[i]))\
  855. for i in df_sess.index]
  856. for s, same in enumerate(['within', 'across']):
  857. sess_estimates=[]
  858. # get estimates for each time step
  859. for delay_id,delay in enumerate(range(len(df_sess.pred_complex_curr_combined[1]))):
  860. bias_estimate = []
  861. prederr_delaystep=[]
  862. timeon=time.time()
  863. for R,RSide in enumerate(hemispheres):
  864. DSide = RSide if same == 'within' else hemispheres[hemispheres != RSide][0]
  865. bias_estimate.append(df_sess['bias_'+RSide])
  866. prederr_delaystep.append([df_sess['pred_shufflesub_'+DSide][i][delay]\
  867. for i in df_sess.index])
  868. #prederr_delaystep.append(np.squeeze([circdist(np.angle(df_sess['pred_complex_curr_'+DSide][i][delay]),\
  869. # np.angle(df_sess.targ_curr_xy[i]))\
  870. # for i in df_sess.index]))
  871. # fit model on combined areas react/delay err
  872. df_model = pd.DataFrame({'bias_estimate': np.concatenate(bias_estimate),
  873. 'prederr_delaystep_deg': np.rad2deg(np.concatenate(prederr_delaystep))})
  874. 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)"
  875. # fit model
  876. model = smf.ols('prederr_delaystep_deg ~ bias_estimate', data=df_model).fit()
  877. sess_estimates.append(model.params['bias_estimate'])
  878. react_all[same].append(sess_estimates)
  879. react_term[monkey][same].append(sess_estimates)
  880. # cut sessions to same length
  881. react_within_cut,borders_mean = cut_task_timings(react_all['within'], borders_all, 19)
  882. react_across_cut,borders_mean = cut_task_timings(react_all['across'], borders_all, 19)
  883. # only look at 2nd trial's delay
  884. react_within_trial2 = react_within_cut[:, borders_mean[11]:]
  885. react_across_trial2 = react_across_cut[:, borders_mean[11]:]
  886. plot_twolines_full(R=react_within_trial2,base=react_across_trial2, bins=bins, currentTrial=True,\
  887. labelR='within',labelB='across', errorbars='SEM', end_border=6,shorten_delay = 2,\
  888. borders=borders_mean, ylabel='reactivation drift (°)',\
  889. significances = True)
  890. plt.tight_layout()
  891. #plt.savefig('./Figures/Figure5/ReactivationBiasWithinAcross_SaWa_200ms.svg')
  892. plt.show()
  893. # %%
  894. react_within_cut,borders_mean = cut_task_timings(react_all['within'], borders_all, 19)
  895. react_across_cut,borders_mean = cut_task_timings(react_all['across'], borders_all, 19)
  896. within_delayavg = np.mean(react_within_cut[:, borders_mean[16]:borders_mean[17]], axis=1)
  897. across_delayavg = np.mean(react_across_cut[:, borders_mean[16]:borders_mean[17]], axis=1)
  898. # plot
  899. f, ax = plt.subplots(figsize=(1.1,2.1))
  900. plt.axhline(0, color='#333333', alpha=0.5)
  901. #within
  902. ax.plot([np.zeros((len(within_delayavg))), np.ones((len(across_delayavg)))],\
  903. [within_delayavg, across_delayavg], color='#333333', alpha=0.2)
  904. ax.errorbar(0, np.mean(within_delayavg), yerr= 2*sem(within_delayavg), color=colors['Within'], marker='o')
  905. # across
  906. ax.errorbar(1, np.mean(across_delayavg), yerr= 2*sem(across_delayavg), color=colors['Across'], marker='o')
  907. ax.set_xticks([0,1])
  908. ax.set_xticklabels(['within', 'across'], rotation=25)
  909. ax.set_ylabel('reactivation bias (°)')
  910. ax.set_xlabel('hemisphere')
  911. # pvalues
  912. stats_w = ['***' if ttest_1samp(within_delayavg, 0)[1] < 0.005\
  913. else '**' if ttest_1samp(within_delayavg, 0)[1] < 0.01\
  914. else '*' if ttest_1samp(within_delayavg, 0)[1] < 0.05 else 'n.s.'][0]
  915. stats_a = ['***' if ttest_1samp(across_delayavg, 0)[1] < 0.005\
  916. else '**' if ttest_1samp(across_delayavg, 0)[1] < 0.01\
  917. else '*' if ttest_1samp(across_delayavg, 0)[1] < 0.05 else 'n.s.'][0]
  918. stats_diff = ['***' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.005\
  919. else '**' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.01\
  920. else '*' if ttest_rel(within_delayavg, across_delayavg)[1] < 0.05 else 'n.s.'][0]
  921. y0, y1 = ax.get_ylim()
  922. ax.annotate(stats_w,(0, y1-0.3), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
  923. ax.annotate(stats_a,(1, y1-0.3), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
  924. ax.plot([0,0,1,1],\
  925. [y1+2*y1/10,y1+3*y1/10, y1+3*y1/10, y1+2*y1/10], linewidth=1, color='#333333')
  926. ax.annotate(stats_diff,\
  927. (0.5, y1+3*y1/10), ha='center', va='bottom', fontsize=10, weight='bold', color='#333333')
  928. ax.set_xlim([-.2,1.2])
  929. ax.set_ylim([-3.5,8])
  930. ax.xaxis.set_ticks_position('bottom')
  931. ax.yaxis.set_ticks_position('left')
  932. sns.despine()
  933. plt.tight_layout()
  934. #plt.savefig('./Quantify_ReactivationBiasWithinAcross_SaWa_200ms.svg')
  935. plt.show()
  936. # STATISTICAL TESTING
  937. print('Avg. of '+str(borders_mean[17] - borders_mean[16])+' independent '+str(bins)+' ms time bins.')
  938. # WITHIN
  939. degf = len(within_delayavg) - 1
  940. test, pval = ttest_1samp(within_delayavg, 0)
  941. mean_x = np.mean(within_delayavg)
  942. se = stats.sem(within_delayavg) # standard error of the mean
  943. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  944. print('WITHIN 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  945. ', mean = '+str(np.round(mean_x,2))+\
  946. ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
  947. # ACROSS
  948. test, pval = ttest_1samp(across_delayavg, 0)
  949. mean_x = np.mean(across_delayavg)
  950. se = stats.sem(across_delayavg) # standard error of the mean
  951. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  952. print('ACROSS 1-sample: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  953. ', mean = '+str(np.round(mean_x,2))+\
  954. ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
  955. # WITHIN - ACROSS
  956. test, pval = ttest_rel(within_delayavg, across_delayavg)
  957. mean_x = np.mean(within_delayavg - across_delayavg)
  958. se = stats.sem(within_delayavg - across_delayavg) # standard error of the mean
  959. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  960. print('WITHIN - ACROSS paired: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  961. ', mean = '+str(np.round(mean_x,2))+\
  962. ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
  963. # %% [markdown]
  964. # ### Supplement: Split by percentage of dual trials
  965. # %%
  966. df_helper = df.copy().reset_index(drop=True)
  967. colors_wa = {'within': 'darkgreen', 'across':'darkorange'}
  968. hemispheres = np.array(['left', 'right'])
  969. percent = 30
  970. print('Cut-off: '+str(percent)+'%...')
  971. # determine cut-off for each session
  972. df_helper[['cut_left', 'cut_right']] = df_helper.groupby('session_continuous') \
  973. [['react_strength_left', 'react_strength_right']] \
  974. .transform(lambda x: np.percentile(x, percent))
  975. # mark reactivations as weak, strong
  976. df_helper['R_strong_left'] = df_helper.react_strength_left > df_helper.cut_left
  977. df_helper['R_strong_right'] = df_helper.react_strength_right > df_helper.cut_right
  978. # assign reactivation types
  979. chosen_trials = {'none': df_helper[(~df_helper.R_strong_left) & (~df_helper.R_strong_right)],\
  980. 'single': df_helper[((df_helper.R_strong_left) & (~df_helper.R_strong_right)) |\
  981. ((~df_helper.R_strong_left) & (df_helper.R_strong_right))],\
  982. 'dual': df_helper[(df_helper.R_strong_left) & (df_helper.R_strong_right)]}
  983. ['within', 'across']
  984. sd_monkeys = {'within':[],'across':[]}
  985. sd_term = {key:{'within': np.empty((len(np.unique(df_helper.session_continuous))))*np.nan,\
  986. 'across': np.empty((len(np.unique(df_helper.session_continuous))))*np.nan}\
  987. for key in chosen_trials.keys()}
  988. pid=-1
  989. f,ax = plt.subplots(figsize=(1.85,1.95), sharey=True)
  990. for x, react_combi in enumerate(chosen_trials.keys()):
  991. df_type = chosen_trials[react_combi]
  992. sides=[]
  993. for s, same in enumerate(['within', 'across']):
  994. for session_id,session in enumerate(df_type.session_continuous.unique()):#
  995. df_sess = df_type.loc[(df_type.session_continuous==session)].copy()
  996. monkey = df_sess.monkey.unique()[0]
  997. sess_estimates, reactivationprev_curr, delayerr = [], [], []
  998. # for each hemisphere
  999. for R,RSide in enumerate(hemispheres):
  1000. # use either same hemisphere during delay (within) or opposite (across)
  1001. DSide = RSide if same == 'within' else hemispheres[hemispheres != RSide][0]
  1002. reactivations = df_sess['react_'+RSide].values
  1003. delay = df_sess['delayAvg_'+DSide].values
  1004. shuffledelay = df_sess['delayAvg_shuffle_'+DSide].values# np.angle(df_sess.targ_curr_xy)
  1005. # RELATIVE LOCATION OF PREVIOUS REACTIVATION TO CURRENT TARGET
  1006. if (react_combi == 'single'): # if single, only view attraction to reactivated side
  1007. df_react_side = df_sess.loc[df_sess['R_strong_'+RSide]]
  1008. reactivationprev_curr.append(circdist(df_react_side['react_'+RSide].values,\
  1009. np.angle(df_react_side.targ_curr_xy)))
  1010. delay_react_side = df_react_side['delayAvg_'+DSide].values
  1011. shuffle_react_side = df_react_side['delayAvg_shuffle_'+DSide].values
  1012. delayerr.append(circdist(delay_react_side, shuffle_react_side))
  1013. else:
  1014. reactivationprev_curr.append(circdist(reactivations, np.angle(df_sess.targ_curr_xy)))
  1015. delayerr.append(circdist(delay, shuffledelay))
  1016. # combine left right in each condition
  1017. reactivationprev_curr = np.concatenate(reactivationprev_curr)
  1018. delayerr = np.concatenate(delayerr)
  1019. # MODEL HOW THE DELAY ACTIVITY IS ATTRACTED TO THE PREVIOUS REACTIVATION LOCATION
  1020. bias_estimate = -hf.dog1(reactivation_sigma[monkey], reactivationprev_curr)#sign_rl(reactivationprev_curr)#
  1021. # Prepare DataFrame for model fitting
  1022. df_model = pd.DataFrame({'bias_estimate': bias_estimate,
  1023. 'prederr_delaystep_deg': np.rad2deg(delayerr)})
  1024. # fit model
  1025. model = smf.ols('prederr_delaystep_deg ~ bias_estimate', data=df_model).fit()
  1026. #save parameter of model
  1027. sd_term[react_combi][same][session_id] = model.params['bias_estimate']
  1028. sess_estimates.append(model.params['bias_estimate'])
  1029. sd_monkeys[same].append(sess_estimates)
  1030. # get session-mean in each relative location (nans per session if the target didn't appear)
  1031. sd_timing = sd_term[react_combi][same]
  1032. mean, CI = np.nanmean(sd_timing, axis=0), sem(sd_timing, axis=0, nan_policy='omit')
  1033. ax.errorbar(x+s*0.2, mean, yerr=CI, color=colors[same.title()], marker='o', label=same)
  1034. #if (react_combi=='none'):
  1035. # plt.legend()
  1036. ax.axhline(0, color='#333333', alpha=0.5)
  1037. # pvalues of each condition against 0
  1038. y0, y1 = ax.get_ylim()
  1039. for x, react_combi in enumerate(chosen_trials.keys()):
  1040. test_0 = ttest_rel(sd_term[react_combi]['within'], sd_term[react_combi]['across'])[1]
  1041. if test_0 < 0.05:
  1042. ax.plot([x,x,x+0.2,x+0.2],\
  1043. [y1-0.2,y1-0.1, y1-0.1, y1-0.2], linewidth=1, color='#333333')
  1044. ax.annotate('*',\
  1045. (x+0.1, y1-0.2), ha='center', va='bottom', fontsize=10, color='#333333')
  1046. # pvalues of single vs dual across condition (n.s.)
  1047. ttest_across = ttest_rel(sd_term['single']['across'], sd_term['dual']['across'])[1]
  1048. stats_a = ['***' if ttest_across < 0.005\
  1049. else '**' if ttest_across < 0.01\
  1050. else '*' if ttest_across < 0.05 else 'n.s.'][0]
  1051. ax.plot([1.2,1.2,2.2,2.2],\
  1052. [1.7,1.8, 1.8, 1.7], linewidth=1, color='#333333')
  1053. ax.annotate(stats_a,\
  1054. (1.7, 1.8), ha='center', va='bottom', fontsize=10, color='#333333')
  1055. plt.xticks([0,1,2], chosen_trials.keys())#, rotation=25
  1056. plt.xlabel('reactivation type')
  1057. plt.ylabel('react. drift (°)')
  1058. sns.despine()
  1059. ax.xaxis.set_ticks_position('bottom')
  1060. ax.yaxis.set_ticks_position('left')
  1061. plt.tight_layout()
  1062. #plt.savefig('./ReactivationBiasSplitByType_'+str(percent)+'%.svg')
  1063. plt.show()
  1064. # STATISTICAL TESTING
  1065. # WITHIN - ACROSS
  1066. print('##### Across-0 statistics: #####')
  1067. for x, react_combi in enumerate(chosen_trials.keys()):
  1068. test, pval = ttest_1samp(sd_term[react_combi]['across'], 0)
  1069. mean_x = np.mean(sd_term[react_combi]['across'])
  1070. se = stats.sem(sd_term[react_combi]['across']) # standard error of the mean
  1071. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  1072. print(react_combi+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  1073. ', mean = '+str(np.round(mean_x,2))+\
  1074. ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
  1075. print('##### Within-Across statistics: #####')
  1076. for x, react_combi in enumerate(chosen_trials.keys()):
  1077. test, pval = ttest_rel(sd_term[react_combi]['within'], sd_term[react_combi]['across'])
  1078. mean_x = np.mean(sd_term[react_combi]['within'] - sd_term[react_combi]['across'])
  1079. se = stats.sem(sd_term[react_combi]['within'] - sd_term[react_combi]['across']) # standard error of the mean
  1080. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  1081. print(react_combi+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  1082. ', mean = '+str(np.round(mean_x,2))+\
  1083. ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
  1084. print('##### Single vs Dual Across #####')
  1085. test, pval = ttest_rel(sd_term['dual']['across'], sd_term['single']['across'])
  1086. mean_x = np.mean(sd_term['dual']['within'] - sd_term['single']['across'])
  1087. se = stats.sem(sd_term['dual']['within'] - sd_term['single']['across']) # standard error of the mean
  1088. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  1089. print('Single vs Dual Across: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  1090. ', mean = '+str(np.round(mean_x,2))+\
  1091. ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
  1092. print('##### Single vs Dual Within-Across Difference #####')
  1093. single_WA = sd_term['single']['within'] - sd_term['single']['across']
  1094. dual_WA = sd_term['dual']['within'] - sd_term['dual']['across']
  1095. test, pval = ttest_rel(single_WA, dual_WA)
  1096. mean_x = np.mean(dual_WA - single_WA)
  1097. se = stats.sem(dual_WA - single_WA) # standard error of the mean
  1098. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  1099. print('Single vs Dual within-across: t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  1100. ', mean = '+str(np.round(mean_x,2))+\
  1101. ", 95% CI ["+str(np.round(ci_low,2))+", "+str(np.round(ci_high, 2))+"]")
  1102. # %% [markdown]
  1103. # ### Fig 5e: GMM - Single or Dual Reactivations
  1104. # %%
  1105. from sklearn.mixture import GaussianMixture
  1106. from sklearn.model_selection import train_test_split
  1107. from sklearn.model_selection import GridSearchCV
  1108. # get reactivation strengths
  1109. # get minimum trial durations across all sessions
  1110. borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
  1111. for _, sess_df in df[df.monkey == monkey].groupby('session')]
  1112. borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
  1113. # get reactivation location / strength for each hemisphere
  1114. min_Reactlen = (borders_mean[14]+1) - borders_mean[13]
  1115. print('Reactivation average is averaged across '+str(min_Reactlen)+' independent '+str(bins)+'ms time bins.')
  1116. Rtimes = [range(df.borders_full[i][13], df.borders_full[i][13]+min_Reactlen) for i in df.index]
  1117. # Rtimes = [range(df.borders_full.values[trial][13], df.borders_full.values[trial][14]+1)\
  1118. # for trial in df.index]
  1119. # REACTIVATION
  1120. # get reactivations: predict PREV target during current fixation time (Rtimes)
  1121. df['react_strength_left'] = [np.abs(np.mean(df.pred_complex_delay_left[trial][Rtimes[trial]]))\
  1122. for trial in df.index]
  1123. df['react_strength_right'] = [np.abs(np.mean(df.pred_complex_delay_right[trial][Rtimes[trial]]))\
  1124. for trial in df.index]
  1125. df['react_angle_left'] = [np.angle(np.mean(df.pred_complex_delay_left[trial][Rtimes[trial]]))\
  1126. for trial in df.index]
  1127. df['react_angle_right'] = [np.angle(np.mean(df.pred_complex_delay_right[trial][Rtimes[trial]]))\
  1128. for trial in df.index]
  1129. # create vector of left, right react strength per trial
  1130. X = np.array([df.react_strength_left.values, df.react_strength_right.values]).T
  1131. # %%
  1132. # use four components, expect: None, single-left, single-right, Dual
  1133. chosen_components = 4
  1134. # get left, right reactivation strengths
  1135. df_mono = df.copy().reset_index(drop=True)
  1136. # only plot random subsample of datapoints to get smaller svg file
  1137. randomIDs = np.random.choice(range(len(X)), 2000)
  1138. # figure
  1139. f,ax=plt.subplots(figsize=(2.1,1.95))
  1140. X_subsample = X[randomIDs]
  1141. ax.scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
  1142. # fit 250 GMMs on full data with differnt start points to check stability
  1143. gm = GaussianMixture(n_components=chosen_components)
  1144. for i in range(100): # repeat for random starting points
  1145. gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X)#, covariance_type='spherical'
  1146. # highlight the found classes
  1147. # highlight the found classes
  1148. dual_class = np.argmax(np.sum(gm.means_, axis=1))
  1149. for m_id,m in enumerate(gm.means_):
  1150. #if m_id == dual_class :
  1151. # ax.scatter(m[0], m[1], color='r', marker='x', s=50, alpha=0.1)
  1152. #else:
  1153. ax.scatter(m[0], m[1], color='#333333', marker='x', s=50, alpha=0.1)
  1154. # plot diagonal for showing dual reactivation line
  1155. x0,x1 = ax.get_xlim()
  1156. y0,y1 = ax.get_ylim()
  1157. ax.plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
  1158. ax.set_xlabel('left reactivation strength')
  1159. ax.set_ylabel('right reactivation strength')
  1160. ax.xaxis.set_ticks_position('bottom')
  1161. ax.yaxis.set_ticks_position('left')
  1162. sns.despine()
  1163. plt.tight_layout()
  1164. #plt.savefig('./GMM4FitsSingleVSDualReact.svg')
  1165. plt.show()
  1166. # %% [markdown]
  1167. # ### Supplement: Individual monkeys with three classes
  1168. # %%
  1169. # use four components, expect: None, single-left, single-right, Dual
  1170. chosen_components = 4
  1171. f,ax=plt.subplots(1,3,figsize=(5.8,1.95), sharex=True, sharey=True)
  1172. for m, monkey in enumerate(monkeys):
  1173. # get left, right reactivation strengths
  1174. df_mono = df.loc[df.monkey==monkey].copy().reset_index(drop=True)
  1175. X_mono = np.array([df_mono.react_strength_left.values, df_mono.react_strength_right.values]).T
  1176. # only plot random subsample of datapoints to get smaller svg file
  1177. randomIDs = np.random.choice(range(len(X_mono)), 2000)
  1178. # figure
  1179. X_subsample = X_mono[randomIDs]
  1180. ax[m].scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
  1181. # fit 250 GMMs on full data with differnt start points to check stability
  1182. gm = GaussianMixture(n_components=chosen_components)
  1183. for i in range(100): # repeat for random starting points
  1184. gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_mono)#, covariance_type='spherical'
  1185. # highlight the found classes
  1186. # highlight the found classes
  1187. dual_class = np.argmax(np.sum(gm.means_, axis=1))
  1188. for m_id,means in enumerate(gm.means_):
  1189. ax[m].scatter(means[0], means[1], color='#333333', marker='x', s=50, alpha=0.1)
  1190. # plot diagonal for showing dual reactivation line
  1191. x0,x1 = ax[m].get_xlim()
  1192. y0,y1 = ax[m].get_ylim()
  1193. ax[m].plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
  1194. ax[m].set_xlabel('left reactivation strength')
  1195. ax[m].set_ylabel('right reactivation strength')
  1196. ax[m].xaxis.set_ticks_position('bottom')
  1197. ax[m].yaxis.set_ticks_position('left')
  1198. ax[m].set_title(monkey)
  1199. sns.despine()
  1200. plt.tight_layout()
  1201. #plt.savefig('./SupplementGMM4FitsSingleVSDualReact.svg')
  1202. plt.show()
  1203. # %%
  1204. # use four components, expect: None, single-left, single-right, Dual
  1205. chosen_components = 3
  1206. f,ax=plt.subplots(1,3,figsize=(5.8,1.95), sharex=True, sharey=True)
  1207. for m, monkey in enumerate(monkeys):
  1208. # get left, right reactivation strengths
  1209. df_mono = df.loc[df.monkey==monkey].copy().reset_index(drop=True)
  1210. X_mono = np.array([df_mono.react_strength_left.values, df_mono.react_strength_right.values]).T
  1211. # only plot random subsample of datapoints to get smaller svg file
  1212. randomIDs = np.random.choice(range(len(X_mono)), 2000)
  1213. # figure
  1214. X_subsample = X_mono[randomIDs]
  1215. ax[m].scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color=colors['Reactivation'], marker='.')
  1216. # fit 250 GMMs on full data with differnt start points to check stability
  1217. gm = GaussianMixture(n_components=chosen_components)
  1218. for i in range(100): # repeat for random starting points
  1219. gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_mono)#, covariance_type='spherical'
  1220. # highlight the found classes
  1221. # highlight the found classes
  1222. dual_class = np.argmax(np.sum(gm.means_, axis=1))
  1223. for m_id,means in enumerate(gm.means_):
  1224. ax[m].scatter(means[0], means[1], color='#333333', marker='x', s=50, alpha=0.1)
  1225. # plot diagonal for showing dual reactivation line
  1226. x0,x1 = ax[m].get_xlim()
  1227. y0,y1 = ax[m].get_ylim()
  1228. ax[m].plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
  1229. ax[m].set_xlabel('left reactivation strength')
  1230. ax[m].set_ylabel('right reactivation strength')
  1231. ax[m].xaxis.set_ticks_position('bottom')
  1232. ax[m].yaxis.set_ticks_position('left')
  1233. ax[m].set_title(monkey)
  1234. sns.despine()
  1235. plt.tight_layout()
  1236. #plt.savefig('./SupplementGMM3FitsSingleVSDualReact.svg')
  1237. plt.show()
  1238. # %% [markdown]
  1239. # ### Supplement: Illustration of cut-off
  1240. # %%
  1241. percentile = 30
  1242. cut_off = np.percentile(X, percentile)
  1243. f,ax=plt.subplots(figsize=(2.,2))
  1244. plt.scatter(X_subsample[:,0], X_subsample[:,1], color='#333333', alpha=0.2, marker='.')
  1245. plt.axhline(cut_off, lw=1.25, dashes=[5,1], color='lightcoral')
  1246. plt.axvline(cut_off, lw=1.25, dashes=[5,1], color='lightcoral')
  1247. ax.set_xlabel('left reactivation strength')
  1248. ax.set_ylabel('right reactivation strength')
  1249. plt.xlim([0, 300])
  1250. plt.ylim([0, 300])
  1251. plt.title('All monkeys')
  1252. ax.xaxis.set_ticks_position('bottom')
  1253. ax.yaxis.set_ticks_position('left')
  1254. sns.despine()
  1255. plt.tight_layout()
  1256. #plt.savefig('./Figures/Supplement/Illustration_cutOff.svg')
  1257. # %% [markdown]
  1258. # ### Figure 5f: Delay GMM
  1259. # %%
  1260. borders_all = [sess_df.borders_full.iloc[0] for monkey in ['Sa', 'Wa']\
  1261. for _, sess_df in df[df.monkey == monkey].groupby('session')]
  1262. borders_mean = cut_task_timings(timeperiods = borders_all, max_time = 19)[1]
  1263. # get reactivation location / strength for each hemisphere
  1264. min_Delaylen = borders_mean[6] - borders_mean[5]
  1265. print('Delay average is averaged across '+str(min_Delaylen)+' independent '+str(bins)+'ms time bins.')
  1266. # get delay strengths
  1267. Dtimes = [range(df.borders_full.values[trial][6]-min_Delaylen, df.borders_full.values[trial][6])\
  1268. for trial in df.index]
  1269. # REACTIVATION
  1270. # get reactivations: predict PREV target during current fixation time (Rtimes)
  1271. df['delay_strength_left'] = [np.abs(np.mean(df.pred_complex_delay_left[trial][Dtimes[trial]]))\
  1272. for trial in df.index]
  1273. df['delay_strength_right'] = [np.abs(np.mean(df.pred_complex_delay_right[trial][Dtimes[trial]]))\
  1274. for trial in df.index]
  1275. df['delay_strength_combined'] = [np.abs(np.mean(df.pred_complex_delay_combined[trial][Dtimes[trial]]))\
  1276. for trial in df.index]
  1277. X_delay = np.array([df.delay_strength_left.values, df.delay_strength_right.values]).T
  1278. # %%
  1279. # use four components, expect: None, single-left, single-right, Dual
  1280. chosen_components = 4
  1281. # get left, right reactivation strengths
  1282. df_mono = df.copy().reset_index(drop=True)
  1283. # only plot random subsample of datapoints to get smaller svg file
  1284. randomIDs = np.random.choice(range(len(X_delay)), 2000)
  1285. # figure
  1286. f,ax=plt.subplots(figsize=(2.1,1.95))
  1287. X_subsample = X_delay[randomIDs]
  1288. ax.scatter(X_subsample[:,0], X_subsample[:,1], alpha=0.2, color='#0B506F', marker='.')
  1289. # fit 250 GMMs on full data with differnt start points to check stability
  1290. gm = GaussianMixture(n_components=chosen_components)
  1291. for i in range(250): # repeat for random starting points
  1292. gm = GaussianMixture(n_components=chosen_components, covariance_type='full', max_iter=500).fit(X_delay)#, covariance_type='spherical'
  1293. # highlight the found classes
  1294. dual_class = np.argmax(np.sum(gm.means_, axis=1))
  1295. for m_id,m in enumerate(gm.means_):
  1296. #if m_id == dual_class :
  1297. # ax.scatter(m[0], m[1], color='r', marker='x', s=50, alpha=0.1)
  1298. #else:
  1299. ax.scatter(m[0], m[1], color='#333333', marker='x', s=50, alpha=0.1)
  1300. # plot diagonal for showing dual reactivation line
  1301. x0,x1 = ax.get_xlim()
  1302. y0,y1 = ax.get_ylim()
  1303. ax.plot(np.linspace(0,np.max([x1, y1]),10), np.linspace(0,np.max([x1, y1]),10),color='#333333', dashes=[1,1])
  1304. ax.set_xlabel('left delay strength')
  1305. ax.set_ylabel('right delay strength')
  1306. sns.despine()
  1307. plt.tight_layout()
  1308. #plt.savefig('./GMM4FitsSingleVSDualReact_DELAY.svg')
  1309. plt.show()
  1310. # %% [markdown]
  1311. # # Fig. 6d: Behavioral precision based on memory in either or both hemispheres
  1312. # %%
  1313. #colors_ic={'ipsi':'#EF8354', 'contra':'#2E9FDC'}
  1314. # PREDICTONS
  1315. contraPred = [df_pred_noBorder.pred_complex_delay_left[idx] if df_pred_noBorder.leftHemi[idx]==CONTRA\
  1316. else df_pred_noBorder.pred_complex_delay_right[idx] if df_pred_noBorder.rightHemi[idx]==CONTRA\
  1317. else [np.nan for i in df_pred_noBorder.pred_complex_delay_left[idx]] for idx in df_pred_noBorder.index]
  1318. ipsiPred = [df_pred_noBorder.pred_complex_delay_left[idx] if df_pred_noBorder.leftHemi[idx]==IPSI\
  1319. else df_pred_noBorder.pred_complex_delay_right[idx] if df_pred_noBorder.rightHemi[idx]==IPSI\
  1320. else [np.nan for i in df_pred_noBorder.pred_complex_delay_left[idx]] for idx in df_pred_noBorder.index]
  1321. df_pred_noBorder['contraDecoderPred'] = contraPred
  1322. df_pred_noBorder['ipsiDecoderPred'] = ipsiPred
  1323. df_pred_noBorder['combinedDecoderPred'] = [np.mean([contraPred[i], ipsiPred[i]], axis=0)\
  1324. for i in range(len(contraPred))]
  1325. # ERRORS
  1326. contraErr = [df_pred_noBorder.prederror_ortho_left[idx] if df_pred_noBorder.leftHemi[idx]==CONTRA\
  1327. else df_pred_noBorder.prederror_ortho_right[idx] if df_pred_noBorder.rightHemi[idx]==CONTRA\
  1328. else [np.nan for i in df_pred_noBorder.prederror_ortho_left[idx]] for idx in df_pred_noBorder.index]
  1329. ipsiErr = [df_pred_noBorder.prederror_ortho_left[idx] if df_pred_noBorder.leftHemi[idx]==IPSI\
  1330. else df_pred_noBorder.prederror_ortho_right[idx] if df_pred_noBorder.rightHemi[idx]==IPSI\
  1331. else [np.nan for i in df_pred_noBorder.prederror_ortho_left[idx]] for idx in df_pred_noBorder.index]
  1332. df_pred_noBorder['contraDecoderErr'] = contraErr
  1333. df_pred_noBorder['ipsiDecoderErr'] = ipsiErr
  1334. df_pred_noBorder['combinedDecoderErr'] = [circdist(np.angle(df_pred_noBorder['combinedDecoderPred'][i]),\
  1335. np.angle(df_pred_noBorder.targ_prev_xy[i]))\
  1336. for i in df_pred_noBorder.index]
  1337. # %%
  1338. precision = {'none':[], 'ipsi':[], 'contra':[], 'both':[]}
  1339. precisionerrors = {'none':[], 'ipsi':[], 'contra':[], 'both':[]}
  1340. binspace = np.linspace(-np.pi/10, np.pi/10, 50)
  1341. precision_mono = {m: {'none':[], 'ipsi':[], 'contra':[], 'both':[]} for m in monkeys}
  1342. precision_orig = []
  1343. labels, label_name, label_errors = [], [], []
  1344. cc=[]
  1345. h=0
  1346. for monkey in monkeys:
  1347. df_mono = df_pred_noBorder.loc[df_pred_noBorder.monkey==monkey]
  1348. for s, session in enumerate(np.unique(df_mono.session_continuous)): # for each session
  1349. df_sess = df_mono.loc[(df_mono.session_continuous==session)].copy().reset_index(drop=True)
  1350. borders = df_sess.borders_full[0]
  1351. # define end of delay
  1352. baseline = range(df_sess.borders_full[0][0], df_sess.borders_full[0][3])
  1353. latedelay = range(df_sess.borders_full[0][6]-3, df_sess.borders_full[0][6])
  1354. # get decoder prediction error at the end of the delay
  1355. contra_strength = [np.mean(cP[latedelay])\
  1356. for cP in df_sess['contraDecoderErr']]#- np.mean(np.abs(cP[baseline]))
  1357. ipsi_strength = [np.mean(iP[latedelay])\
  1358. for iP in df_sess['ipsiDecoderErr']]# - np.mean(np.abs(iP[baseline]))
  1359. errors = np.rad2deg(df_sess.behav_error_prev.values)
  1360. err_std = np.std(errors)
  1361. precision_orig.append(err_std)
  1362. # split into high vs low memory
  1363. cut_perc = 20
  1364. contra_cut, ipsi_cut = np.percentile(np.abs(contra_strength), cut_perc), np.percentile(np.abs(ipsi_strength), cut_perc)
  1365. cc.append(contra_cut)
  1366. # good memory means precision errors < cut_perc
  1367. none_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
  1368. ipsi_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
  1369. contra_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
  1370. both_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
  1371. # save
  1372. precision['none'].append(np.std(errors[none_trials]))
  1373. precision['ipsi'].append(np.std(errors[ipsi_trials]))
  1374. precision['contra'].append(np.std(errors[contra_trials]))
  1375. precision['both'].append(np.std(errors[both_trials]))
  1376. precisionerrors['none'].append(errors[none_trials])
  1377. precisionerrors['ipsi'].append(errors[ipsi_trials])
  1378. precisionerrors['contra'].append(errors[contra_trials])
  1379. precisionerrors['both'].append(errors[both_trials])
  1380. precision_mono[monkey]['none'].append(np.std(errors[none_trials]))
  1381. precision_mono[monkey]['ipsi'].append(np.std(errors[ipsi_trials]))
  1382. precision_mono[monkey]['contra'].append(np.std(errors[contra_trials]))
  1383. precision_mono[monkey]['both'].append(np.std(errors[both_trials]))
  1384. # labels for each trial
  1385. sess_labels = np.empty(len(errors), dtype=object)
  1386. sess_labels[none_trials] = 'none'
  1387. sess_labels[ipsi_trials] = 'ipsi'
  1388. sess_labels[contra_trials] = 'contra'
  1389. sess_labels[both_trials] = 'both'
  1390. assert len(errors) == len(sess_labels)
  1391. labels.append(sess_labels)
  1392. label_name.append([session for _ in sess_labels])
  1393. label_errors.append(errors)
  1394. h+=1
  1395. # %%
  1396. from scipy import stats
  1397. def print_stats(a=[], b = [], ttest=ttest_rel, text=''):
  1398. test, pval = ttest(a, b)
  1399. degf = len(a) - 1
  1400. mean_x = np.mean(a - np.array(b))
  1401. se = stats.sem(a - np.array(b)) # standard error of the mean
  1402. ci_low, ci_high = t.interval(0.95, degf, loc=mean_x, scale=se)
  1403. print(text+', '+str(ttest)+': t('+str(degf)+') = '+str(np.round(test, 2))+', p = '+"{:.2e}".format(pval)+\
  1404. ", mean = "+str(np.round(mean_x, 2))+\
  1405. ", 95% CI ["+str(np.round(ci_low,2))+","+str(np.round(ci_high, 2))+"]")
  1406. return
  1407. f, ax = plt.subplots(figsize=(1.7,1.3))
  1408. ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%0.1f'))
  1409. colors_mono = {'Sa': 'darkred', 'Pe': 'lightsalmon', 'Wa':'k'}
  1410. for monkey in monkeys:
  1411. sesss = len(precision_mono[monkey]['none'])
  1412. precisions = [precision_mono[monkey]['none'], precision_mono[monkey]['ipsi'],\
  1413. precision_mono[monkey]['contra'], precision_mono[monkey]['both']]
  1414. #plt.plot([np.zeros((sesss)), np.ones((sesss)), 2*np.ones((sesss)), 3*np.ones((sesss))],\
  1415. # precisions, color=colors_mono[monkey], alpha=0.3)
  1416. plt.axhline(np.mean(precision_orig), color='k', lw=0.5)
  1417. plt.errorbar(0,np.mean(precision['none']), yerr=sem(precision['none']), color='k', marker='o')
  1418. plt.errorbar(1,np.mean(precision['ipsi']), yerr=sem(precision['ipsi']), color=colors['Across'], marker='o')
  1419. plt.errorbar(2,np.mean(precision['contra']), yerr=sem(precision['contra']), color=colors['Across'], marker='o')
  1420. plt.errorbar(3,np.mean(precision['both']), yerr=sem(precision['both']), color=colors['Reactivation'], marker='o')
  1421. plt.ylabel('error std. (°)')
  1422. plt.xticks([0,1,2,3], ['none', 'ipsi', 'contra', 'dual'])
  1423. plt.xlabel('hemispheric memories')
  1424. # p-values
  1425. y0, y1 = ax.get_ylim()
  1426. for l, label in enumerate(['none', 'ipsi', 'contra', 'both']):
  1427. stat = ['***' if ttest_rel(precision[label], precision_orig)[1] < 0.005\
  1428. else '**' if ttest_rel(precision[label], precision_orig)[1] < 0.01\
  1429. else '*' if ttest_rel(precision[label], precision_orig)[1] < 0.05 else 'n.s.'][0]
  1430. ax.annotate(stat,(l, y1), ha='center', va='bottom', fontsize=10, color='#333333')
  1431. ax.plot([2,2,3,3],\
  1432. [y1+ 1*y1/8,y1 + 1*y1/5, y1 + 1*y1/5, y1+ 1*y1/8], linewidth=1, color='#333333')
  1433. ax.annotate('p = '+str(np.round(ttest_rel(precision['both'], precision['contra'])[1],3)),\
  1434. (2.5, y1+y1/5), ha='center', va='bottom', fontsize=9, color='#333333')
  1435. # PRINT STATS
  1436. # BOTH
  1437. print_stats(a=precision['both'], b = precision_orig, ttest=ttest_rel, text='Both-Orig')
  1438. # IPSI
  1439. print_stats(a=precision['ipsi'], b = precision_orig, ttest=ttest_rel, text='Ipsi-Orig')
  1440. # CONTRA
  1441. print_stats(a=precision['contra'], b = precision_orig, ttest=ttest_rel, text='Contra-Orig')
  1442. # BOTH - CONTRA
  1443. print_stats(a=precision['both'], b = precision['contra'], ttest=ttest_rel, text='Both-Contra')
  1444. # NONE
  1445. print_stats(a=precision['none'], b = precision_orig, ttest=ttest_rel, text='None-Orig')
  1446. # BOTH - NONE
  1447. print_stats(a=precision['both'], b = precision['none'], ttest=ttest_rel, text='Both-None')
  1448. plt.tight_layout()
  1449. #plt.savefig(DATAPATH+'Figures/Paper/Neurons/BumpDrift/DualMemoryImprovement'+'.svg')
  1450. # %% [markdown]
  1451. # ### Supplement 9: Different cut-offs
  1452. # %%
  1453. percentages = np.arange(20, 80, 10)
  1454. precision = {'none':[[] for p in percentages], 'ipsi':[[] for p in percentages],\
  1455. 'contra':[[] for p in percentages], 'both':[[] for p in percentages]}
  1456. precisionerrors = {'none':[[] for p in percentages], 'ipsi':[[] for p in percentages],\
  1457. 'contra':[[] for p in percentages], 'both':[[] for p in percentages]}
  1458. binspace = np.linspace(-np.pi/10, np.pi/10, 50)
  1459. precision_orig = []
  1460. labels, label_name, label_errors = [], [], []
  1461. cc=[]
  1462. h=0
  1463. for monkey in monkeys:
  1464. df_mono = df_pred_noBorder.loc[df_pred_noBorder.monkey==monkey]
  1465. for s, session in enumerate(np.unique(df_mono.session_continuous)): # for each session
  1466. df_sess = df_mono.loc[(df_mono.session_continuous==session)].copy().reset_index(drop=True)
  1467. borders = df_sess.borders_full[0]
  1468. # define end of delay
  1469. baseline = range(df_sess.borders_full[0][0], df_sess.borders_full[0][3])
  1470. latedelay = range(df_sess.borders_full[0][6]-3, df_sess.borders_full[0][6])
  1471. # get decoder prediction error at the end of the delay
  1472. contra_strength = [np.mean(cP[latedelay])\
  1473. for cP in df_sess['contraDecoderErr']]#- np.mean(np.abs(cP[baseline]))
  1474. ipsi_strength = [np.mean(iP[latedelay])\
  1475. for iP in df_sess['ipsiDecoderErr']]# - np.mean(np.abs(iP[baseline]))
  1476. errors = np.rad2deg(df_sess.behav_error_prev.values)
  1477. err_std = np.std(errors)
  1478. precision_orig.append(err_std)
  1479. # split into high vs low memory
  1480. for p, cut_perc in enumerate(percentages):
  1481. contra_cut, ipsi_cut = np.percentile(np.abs(contra_strength), cut_perc), np.percentile(np.abs(ipsi_strength), cut_perc)
  1482. cc.append(contra_cut)
  1483. # good memory means precision errors < cut_perc
  1484. none_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
  1485. ipsi_trials = np.where((np.abs(contra_strength) >= contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
  1486. contra_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) >= ipsi_cut))[0]
  1487. both_trials = np.where((np.abs(contra_strength) < contra_cut) & (np.abs(ipsi_strength) < ipsi_cut))[0]
  1488. # save
  1489. precision['none'][p].append(np.std(errors[none_trials])-err_std)
  1490. precision['ipsi'][p].append(np.std(errors[ipsi_trials])-err_std)
  1491. precision['contra'][p].append(np.std(errors[contra_trials])-err_std)
  1492. precision['both'][p].append(np.std(errors[both_trials])-err_std)
  1493. precisionerrors['none'][p].append(errors[none_trials])
  1494. precisionerrors['ipsi'][p].append(errors[ipsi_trials])
  1495. precisionerrors['contra'][p].append(errors[contra_trials])
  1496. precisionerrors['both'][p].append(errors[both_trials])
  1497. # labels for each trial
  1498. sess_labels = np.empty(len(errors), dtype=object)
  1499. sess_labels[none_trials] = 'none'
  1500. sess_labels[ipsi_trials] = 'ipsi'
  1501. sess_labels[contra_trials] = 'contra'
  1502. sess_labels[both_trials] = 'both'
  1503. assert len(errors) == len(sess_labels)
  1504. labels.append(sess_labels)
  1505. label_name.append([session for _ in sess_labels])
  1506. label_errors.append(errors)
  1507. h+=1
  1508. cmap_ACC = matplotlib.cm.get_cmap('Greys')
  1509. colors_perc = [cmap_ACC(0.3+i/(len(percentages)+1)) for i in range(len(percentages))]
  1510. f, ax = plt.subplots(figsize=(1.7,2))
  1511. plt.axhline(0, color='k', lw=0.5)
  1512. for p, cut_perc in enumerate(percentages):
  1513. precisions = [precision['none'][p], precision['ipsi'][p],\
  1514. precision['contra'][p], precision['both'][p]]
  1515. plt.plot([0,1,2,3],np.mean(precisions, axis=1), color=colors_perc[p],\
  1516. marker='o', linestyle='None', label=str(cut_perc)+'%')
  1517. plt.ylabel('$\Delta$error std. (°)')
  1518. plt.xticks([0,1,2,3], ['none', 'ipsi', 'contra', 'same'])
  1519. plt.xlabel('hemispheric memories')
  1520. plt.legend(fontsize=5, frameon=True)
  1521. plt.tight_layout()
  1522. #plt.savefig(DATAPATH+'Figures/Paper/Neurons/BumpDrift/SUPPLEMENT_DualMemoryImprovement'+'.svg')
  1523. # %%

Figure5.ipynb at commit bd7d532, no license · at the source

Overview

Authors: Melanie Tschiersch1,2, Akash Umakantha3,4, Ryan C Williamson3,4, Matthew A Smith3,4,5, Joao Barbosa6,7,8, Albert Compte1,9
  1. Institut d’Investigacions Biomèdiques August Pi i Sunyer (IDIBAPS), Barcelona, Spain
  2. Programa de doctorat en Biomedicina, Universitat de Barcelona (UB), Barcelona, Spain
  3. Center for the Neural Basis of Cognition, Carnegie Mellon University & University of Pittsburgh, Pittsburgh, PA USA
  4. Carnegie Mellon University Neuroscience Institute, Pittsburgh, PA USA
  5. Carnegie Mellon University Department of Biomedical Engineering, Pittsburgh, PA USA
  6. Laboratoire de Neurosciences Cognitives et Computationnelles, INSERM U960, Ecole Normale Superieure - PSL Research University, Paris, France
  7. Cognitive Neuroimaging Unit, INSERM, CEA, CNRS, Université Paris-Saclay, NeuroSpin center, Gif/Yvette, France
  8. Institut de neuromodulation, GHU Paris, psychiatrie et neurosciences, centre hospitalier Sainte-Anne, pôle hospitalo-universitaire 15, Université Paris Cité, Paris, France
  9. Institut d’Investigacions Biomèdiques de Barcelona (IIBB), CSIC, Barcelona, Spain
Journal: Nature communications, volume 17, issue 1, article 8858
Dates: received 4 August 2025; accepted 7 July 2026; published online 20 July 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-75705-2 · PMID 42477323 · PMCID PMC13500473 · OpenAlex W7169787509
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: computational modeling (no new data) (modality), human (organism), non-human primate (organism), cognitive (subfield)
Methods: Connectivity, Statistics, Machine learning, Preprocessing, Single-unit activity, calcium imaging, Smoothing, state filtering, decompositions, Physiology & signal measures
Keywords: Network models, Short-term memory, Working memory, Cortex, Neural circuits
MeSH: Functional Laterality*, Memory, Short-Term*, Prefrontal Cortex*, Animals, Humans, Macaca mulatta, Male (* major topic)
Topic: Neural and Behavioral Psychology Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Generalitat de Catalunya (2021SGR01522, 2017SGR01565); Ministry of Economy and Competitiveness | Agencia Estatal de Investigación (Spanish Agencia Estatal de Investigación) (PID2021-125453OB-I00, PCI2025-167127-2, FPI Program); U.S. Department of Health &amp; Human Services | National Institutes of Health (R01 NS147766, R01 EB026953, R01 EY022928, R01 MH118929); Ministry of Economy and Competitiveness | Instituto de Salud Carlos III (AC20/00071); National Science Foundation (NCS BCS 1734916/1954107); NEI NIH HHS (R01 EY022928); NIMH NIH HHS (R01 MH118929); U.S. Department of Health & Human Services | National Institutes of Health (NIH) (R01 MH118929, R01 EY022928, R01 EB026953, R01 NS147766); NINDS NIH HHS (R01 NS147766); Simons Foundation; NIBIB NIH HHS (R01 EB026953)
Citations: cited by 1 paper (Europe PMC); 111 references in the paper

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

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (33 files), SciPy (32 files), Matplotlib (29 files), pandas (28 files), seaborn (19 files), scikit-learn (17 files), statsmodels (16 files), Pingouin (5 files), Brian 2 (4 files), h5py (4 files)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
35 files
At the source:

melanietschiersch/redundantprefrontalhemispheres

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: bd7d5329176b24441eb457aecef734d02967ee04, 2 June 2026
Languages: Python (25), Jupyter (9)
Size: 40 files, 34 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, environment (environment.yml), 9 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (33 files), SciPy (32 files), Matplotlib (29 files), pandas (28 files), seaborn (19 files), scikit-learn (17 files), statsmodels (16 files), Pingouin (5 files), Brian 2 (4 files), h5py (4 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
35 files

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/krv7g, and osf.io/67tn3).

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://doi.org/10.1038/s41467-026-75705-2

BibTeX

@article{tschiersch2026redundant,
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/s41467-026-75705-2},
url = {https://doi.org/10.1038/s41467-026-75705-2},
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/07/20
VL - 17
IS - 1
SP - 8858
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-75705-2
UR - https://doi.org/10.1038/s41467-026-75705-2
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-75705-2",
"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": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "8858",
"DOI": "10.1038/s41467-026-75705-2",
"PMID": "42477323",
"PMCID": "PMC13500473",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-75705-2",
"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 biology
In 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-making
Journal: n/a
In 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 communications
In 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 biology
In 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 communications
In 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: iScience
In 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 behaviour
In 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: eLife
In 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 neuroscience
In 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 communications
In 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.

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.