OSCR

Sensorimotor remapping drives task specialization in prefrontal cortex.

Code ↔ Paper

21 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 21 matches
  1. [1] § Results › mPFC neurons are most involved in the representation of variables in their preferred task ↔ Response-switch/Pseudo_pop/regression.ipynb, lines 762–793 · score 0.70 · post stimulus activity, pre stimulus, task variables, task selective, motor, trained
  2. [2] § Methods › Ridge regression › cvR2 scores and statistics ↔ Response-switch/Individuals/regression.ipynb, lines 68–212 · score 0.68 · cvR2, cross validation, variance explained, permutation, pipeline, splitting
  3. [3] § Methods › Ridge regression › cvR2 scores and statistics ↔ Response-switch/Pseudo_pop/regression.ipynb, lines 102–243 · score 0.68 · cvR2, cross validation, variance explained, permutation, pipeline, splitting
  4. [4] § Results › Population modularity can be explained by an interaction between task signal and global inhibition ↔ Simulation/model_response_switch.ipynb, lines 207–285 · score 0.65 · activation function, projection weight, standard deviations, task signal, networks, modulation
  5. [5] § Methods › Subpopulations and Gaussian mixture › Gaussian mixture ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 128–148 · score 0.65 · BayesianGaussianMixture, cross validated, split, GM, components, scores
  6. [6] § Methods › Subpopulations and Gaussian mixture › Gaussian mixture ↔ Rule-switch/Pseudo_pop/gaussian_mixture_rs.ipynb, lines 134–154 · score 0.65 · BayesianGaussianMixture, cross validated, split, GM, components, scores
  7. [7] § Results › Population modularity can be explained by an interaction between task signal and global inhibition ↔ Simulation/model_rule_switch.ipynb, lines 192–277 · score 0.65 · activation function, projection weight, standard deviations, task signal, networks, Rule switch
  8. [8] § Results › Task selectivity delineates three neural subpopulations with distinct functional roles ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 654–689 · score 0.63 · population LR, Gaussian Mixture, Population GNG, NoGo, GM
  9. [9] § Methods › Analyses of neural selectivity ↔ Response-switch/Pseudo_pop/regression.ipynb, lines 762–793 · score 0.62 · pre stimulus, post stimulus, task selectivity, interaction, Lick, trained
  10. [10] § Methods › Dimension reduction ↔ Response-switch/Pseudo_pop/PCA.ipynb, lines 226–279 · score 0.62 · cross validated, variance explained, favored, cvPCA, covariance, trained
  11. [11] § Methods › Recurrent neural network modeling › Model performance on each dual-task paradigm ↔ Simulation/model_response_switch.ipynb, lines 1188–1208 · score 0.62 · direction readout, Lick readout, perceptron, spout, simulated, response switch
  12. [12] § Results › Population modularity can be explained by an interaction between task signal and global inhibition ↔ Simulation/model_response_switch.ipynb, lines 207–285 · score 0.60 · activation functions, projections weights, task signal, networks, modulated, model
  13. [13] § Results › Task selectivity delineates three neural subpopulations with distinct functional roles ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 809–921 · score 0.60 · Support Vector Classifiers, classifiers trained, Gaussian mixture, SVCs, decoding, GNG
  14. [14] § Results › Population modularity can be explained by an interaction between task signal and global inhibition ↔ Simulation/model_rule_switch.ipynb, lines 192–277 · score 0.59 · activation functions, projections weights, task signal, networks, modulated, model
  15. [15] § Methods › Ridge regression › Regressor matrix construction ↔ Response-switch/Individuals/regression.ipynb, lines 68–212 · score 0.58 · standard scaler, cross validation, fold, predict, R2, regressor
  16. [16] § Methods › Ridge regression › Regressor matrix construction ↔ Response-switch/Pseudo_pop/regression.ipynb, lines 102–243 · score 0.58 · standard scaler, cross validation, fold, predict, R2, regressor
  17. [17] § Methods › Ridge regression › Regressor matrix construction ↔ Response-switch/Pseudo_pop/regression.ipynb, lines 433–467 · score 0.55 · Akaike Information Criterion, AIC, split, regressors, scores, model
  18. [18] § Methods › Pseudo-population ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 217–245 · score 0.53 · conservative criterion, Gaussian mixture, pseudo, Response switch, neurons, population
  19. [19] § Methods › Analyses of neural selectivity ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 548–569 · score 0.52 · Lick LR, absolute selectivity, variables, regression
  20. [20] § Results › Modularity of mPFC population decreases in a paradigm with less sensorimotor remapping ↔ Rule-switch/Pseudo_pop/gaussian_mixture_rs.ipynb, lines 579–668 · score 0.51 · Support vector classifiers, Gaussian mixture, SVCs, decoding, splits, trained
  21. [21] § Results › Modularity of mPFC population decreases in a paradigm with less sensorimotor remapping ↔ Response-switch/Pseudo_pop/gaussian_mixture.ipynb, lines 809–921 · score 0.51 · Support vector classifiers, Gaussian mixture, SVCs, decoding, splits, 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,366 lines · 45 KB · no license · 6 matches

  1. # %%
  2. import numpy as np
  3. import pandas as pd
  4. import pickle
  5. import matplotlib.pyplot as plt
  6. from tqdm.auto import tqdm
  7. import matplotlib as mpl
  8. from matplotlib.patches import Ellipse
  9. from matplotlib.lines import Line2D
  10. import matplotlib.transforms as transforms
  11. import seaborn as sns
  12. from scipy.interpolate import make_interp_spline, BSpline, splrep, splev
  13. from scipy.stats import mannwhitneyu, wilcoxon
  14. plt.style.use('seaborn-v0_8-ticks')
  15. mpl_config = pd.read_csv('../../mpl_config.csv').to_dict(orient='records')[0]
  16. mpl.rcParams.update(mpl_config)
  17. from pathlib import Path
  18. import sys
  19. ROOT = Path.cwd().parents[1] # go up 2 levels
  20. sys.path.insert(0, str(ROOT))
  21. from export_source import export_plot_data
  22. # %%
  23. def standardize(M) :
  24. """ Standardization by column for 2D matrices """
  25. M_copy = np.copy(M)
  26. M_copy = M_copy.astype(float)
  27. for j in range(np.shape(M)[1]) :
  28. if np.std(M[:,j]) != 0 :
  29. M_copy[:,j] = (M[:,j].astype(float) - float(np.mean(M[:,j])))/float(np.std(M[:,j]))
  30. else :
  31. M_copy[:,j] = M[:,j].astype(float) - float(np.mean(M[:,j]))
  32. return M_copy
  33. def confidence_ellipse(x, y, ax, n_std=2.0, edgecolor = 'black',facecolor='none', **kwargs):
  34. """
  35. Create a plot of the covariance confidence ellipse of `x` and `y`
  36. """
  37. if x.size != y.size:
  38. raise ValueError("x and y must be the same size")
  39. cov = np.cov(x, y)
  40. pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1])
  41. # Using a special case to obtain the eigenvalues of this
  42. # two-dimensionl dataset.
  43. ell_radius_x = np.sqrt(1 + pearson)
  44. ell_radius_y = np.sqrt(1 - pearson)
  45. ellipse = Ellipse((0, 0),
  46. width=ell_radius_x * 2,
  47. height=ell_radius_y * 2,
  48. facecolor=facecolor,
  49. edgecolor=edgecolor,
  50. **kwargs)
  51. # Calculating the stdandard deviation of x from
  52. # the squareroot of the variance and multiplying
  53. # with the given number of standard deviations.
  54. scale_x = np.sqrt(cov[0, 0]) * n_std
  55. mean_x = np.mean(x)
  56. # calculating the stdandard deviation of y ...
  57. scale_y = np.sqrt(cov[1, 1]) * n_std
  58. mean_y = np.mean(y)
  59. transf = transforms.Affine2D() \
  60. .rotate_deg(45) \
  61. .scale(scale_x, scale_y) \
  62. .translate(mean_x, mean_y)
  63. ellipse.set_transform(transf + ax.transData)
  64. return ax.add_patch(ellipse)
  65. def smooth(x,y,nb_point) :
  66. """Smooth a (x,y) serie by inserting nb_point with spline interpolation"""
  67. x_smooth = np.linspace(np.min(x), np.max(x), nb_point)
  68. y_smooth = make_interp_spline(x, y, k=3)(x_smooth)
  69. return x_smooth, y_smooth
  70. # %%
  71. ## We load here the selected ridge and PCA models
  72. with open('Models/ridge.pickle', 'rb') as f:
  73. ridge_output = pickle.load(f)
  74. with open('Models/pca_notime.pickle', 'rb') as f:
  75. pca1 = pickle.load(f)
  76. with open('../DATA/Dataframes/df_pseudo.pickle', 'rb') as f:
  77. merged_data = pickle.load(f)
  78. res = ridge_output['Models']
  79. reg_scores = ridge_output['Scores']
  80. # %% [markdown]
  81. # ### Selectivity matrix construction
  82. # %%
  83. plt.figure(figsize=(7,7))
  84. m = res[np.argmax(reg_scores)]
  85. S = m['Ridge'].coef_.T
  86. ax = plt.gca()
  87. plt.scatter(S[0,:],S[5,:],color='grey', s = 4)
  88. ax.spines['left'].set_position('center')
  89. ax.spines['bottom'].set_position('center')
  90. ax.spines['left'].set_linewidth(1.4)
  91. ax.spines['bottom'].set_linewidth(1.4)
  92. ax.spines['right'].set_color('none')
  93. ax.spines['top'].set_color('none')
  94. plt.xticks([])
  95. plt.yticks([])
  96. # %% [markdown]
  97. # ### Components estimation
  98. # %%
  99. from sklearn.model_selection import KFold, GridSearchCV, RepeatedKFold, cross_validate
  100. from sklearn.mixture import GaussianMixture, BayesianGaussianMixture
  101. from tqdm.auto import tqdm
  102. def gm_components_estimation(S_mat, n_repeat=50) :
  103. """Determine the number of components to keep in Gaussian mixture through a 2-fold cross-validation procedure (50 reps)"""
  104. gm_scores = []
  105. rkf = RepeatedKFold(n_splits = 2, n_repeats = n_repeat)
  106. for n_comp in tqdm(range(1,6)) :
  107. gm = BayesianGaussianMixture(n_components=n_comp ,n_init = 5, max_iter=100)
  108. cv_scores = cross_validate(gm,S_mat.T,cv=rkf)
  109. gm_scores.append(cv_scores['test_score'])
  110. return np.array(gm_scores)
  111. # %%
  112. import warnings
  113. warnings.filterwarnings("ignore")
  114. gm_scores_full = gm_components_estimation(S)
  115. diff_gm_scores = np.diff(gm_scores_full,axis=0) ## Computing relative increase in model performance after the addition of each component
  116. # %%
  117. import matplotlib as mpl
  118. plt.figure(figsize=(8,3))
  119. yerr = np.abs(np.mean(diff_gm_scores,axis=1) - np.percentile(diff_gm_scores,(5,95),axis=1))
  120. _,caps,_ = plt.errorbar([2,3,4,5],np.mean(diff_gm_scores,axis=1),yerr=yerr,linewidth=3,capsize=5,elinewidth=2.5,color='#323232')
  121. for cap in caps:
  122. cap.set_color('#323232')
  123. cap.set_markeredgewidth(2)
  124. plt.scatter([2,3,4,5],np.mean(diff_gm_scores,axis=1),marker = 's',s=150, color='#323232')
  125. #plt.scatter([2,3,4,5],np.percentile(np.diff(gm_scores_full,axis=0),5,axis=1),marker='s', s=120,color='#276690')
  126. plt.fill_between([1.9,3,4,5.1],-0.2,0, color='black',alpha=0.05)
  127. plt.axhline(0,color="black",linestyle='--',alpha=0.2)
  128. plt.xlabel('# Subpopulations',fontsize=30)
  129. plt.xlim((1.9,5.1))
  130. plt.xticks([2,3,4,5],fontsize=28)
  131. plt.ylabel('LL increase \n in model fitting',fontsize=30)
  132. #plt.ylim((-0.15,0.4))
  133. plt.ylim((-0.1,0.6))
  134. #plt.yticks([-0.05,0,0.05,0.1])
  135. plt.yticks([0,0.2,0.4,0.6],fontsize=28)
  136. #plt.savefig('Plots/SVG/gm_comp.SVG', dpi = 300,bbox_inches='tight')
  137. #plt.savefig('Plots/PNG/gm_comp.PNG', dpi = 300,bbox_inches='tight')
  138. # %%
  139. # Store data (serialize)
  140. with open('Models/gm_components.pickle', 'wb') as handle:
  141. pickle.dump(np.diff(gm_scores_full,axis=0) , handle, protocol=pickle.HIGHEST_PROTOCOL)
  142. # %%
  143. with open('Models/gm_components.pickle', 'rb') as f:
  144. diff_gm_scores = pickle.load(f)
  145. # %%
  146. export_plot_data('../../Source_Data.xlsx','Figure4c',LL_increase=diff_gm_scores)
  147. # %% [markdown]
  148. # ### GM model
  149. # %%
  150. from sklearn.mixture import GaussianMixture, BayesianGaussianMixture
  151. gm = BayesianGaussianMixture(n_components = 3,n_init = 20, max_iter=100) ## Keeping 3 components (see above)
  152. gm.fit(S.T)
  153. prob = gm.predict_proba(S.T)
  154. # %% [markdown]
  155. # ### Define population
  156. # %%
  157. pop1 = prob[:,0] >= 0.9 #Conservative criterion to associate each neuron with a GM component.
  158. pop2 = prob[:,1] >= 0.9 #A neuron is associated to a population if the weight associated to it is more than 90%
  159. pop3 = prob[:,2] >= 0.9
  160. pops = [pop1,pop2,pop3]
  161. S_pop = [S[:,pop1],S[:,pop2],S[:,pop3]]
  162. mean_ctx_s = [np.mean(S_pop[0][5,:]),np.mean(S_pop[1][5,:]),np.mean(S_pop[2][5,:])]
  163. print(mean_ctx_s)
  164. S_B = S_pop[np.argsort(mean_ctx_s)[2]]
  165. popB = pops[np.argsort(mean_ctx_s)[2]]
  166. S_A = S_pop[np.argsort(mean_ctx_s)[0]]
  167. popA = pops[np.argsort(mean_ctx_s)[0]]
  168. S_0 = S_pop[np.argsort(mean_ctx_s)[1]]
  169. pop0 = pops[np.argsort(mean_ctx_s)[1]]
  170. print(len(S.T))
  171. print(np.sum(popA),np.sum(popA)/len(S.T))
  172. print(np.sum(popB),np.sum(popB)/len(S.T))
  173. print(np.sum(pop0),np.sum(pop0)/len(S.T))
  174. # %%
  175. with open('../DATA/Dataframes/df_full.pickle', 'rb') as f:
  176. data_full = pickle.load(f)
  177. files = ['M12','M13','M14','M15','M16','M17','M18','M19','M20']
  178. neuron_per_animal = []
  179. for file in files :
  180. neuron_per_animal.append(len(data_full[file]['Spike rate'][0]))
  181. animal_slice = np.insert(np.cumsum(neuron_per_animal), 0, 0, axis=0)
  182. popA_per_animal = [np.sum(popA[animal_slice[i]:animal_slice[i+1]]) for i in range(len(animal_slice)-1)]
  183. popB_per_animal = [np.sum(popB[animal_slice[i]:animal_slice[i+1]]) for i in range(len(animal_slice)-1)]
  184. pop0_per_animal = [np.sum(pop0[animal_slice[i]:animal_slice[i+1]]) for i in range(len(animal_slice)-1)]
  185. def plot_composition_bars(list1, list2, list3, labels=("Population GNG", "Population LR", "Population 0")):
  186. list1 = np.array(list1)
  187. list2 = np.array(list2)
  188. list3 = np.array(list3)
  189. n = len(list1)
  190. x = np.arange(n)
  191. plt.figure(figsize=(10, 7))
  192. # Bottom segment
  193. plt.bar(x, list1, label=labels[0],color='#2E548A')
  194. # Middle segment
  195. plt.bar(x, list2, bottom=list1, label=labels[1],color='#E63946')
  196. # Top segment
  197. plt.bar(x, list3, bottom=list1 + list2, label=labels[2],color='#5F8162')
  198. plt.xticks(range(len(files)),files)
  199. plt.ylabel("# Neurons")
  200. plt.ylim(0,400)
  201. plt.legend()
  202. plt.tight_layout()
  203. plot_composition_bars(popA_per_animal,popB_per_animal,pop0_per_animal)
  204. #plt.savefig('Plots/SVG/pop_repartition_resp.SVG', dpi = 300,bbox_inches='tight')
  205. #plt.savefig('Plots/PNG/pop_repartition_resp.PNG', dpi = 300,bbox_inches='tight')
  206. # %%
  207. from scipy.stats import binomtest
  208. from statsmodels.stats.multitest import multipletests
  209. pop_table = np.stack((popA_per_animal,popB_per_animal,pop0_per_animal)).T
  210. def binomial_enrichment_pmatrix(contingency, method = "bonferroni"):
  211. """
  212. Binomial enrichment test for all (sample, category) pairs.
  213. Parameters
  214. ----------
  215. contingency : np.ndarray
  216. 2D array with shape (n_samples, n_categories)
  217. method : str
  218. Multiple testing correction method (default: FDR Benjamini–Hochberg)
  219. Returns
  220. -------
  221. np.ndarray
  222. 2D array of corrected p-values with same shape as contingency
  223. """
  224. contingency = np.asarray(contingency, dtype=int)
  225. T = contingency.sum()
  226. row_totals = contingency.sum(axis=1) # samples
  227. col_totals = contingency.sum(axis=0) # categories
  228. n_samples, n_categories = contingency.shape
  229. pvals = np.ones((n_samples, n_categories))
  230. for i in range(n_samples):
  231. p0 = row_totals[i] / T if T > 0 else 0.0
  232. for j in range(n_categories):
  233. k = contingency[i, j]
  234. n = col_totals[j]
  235. if n > 0 and p0 > 0:
  236. pvals[i, j] = binomtest(
  237. k, int(n), p=0.5, alternative="greater"
  238. ).pvalue
  239. else:
  240. pvals[i, j] = 1.0
  241. # multiple testing correction
  242. pvals_corr = multipletests(
  243. pvals.ravel(), method=method
  244. )[1].reshape(pvals.shape)
  245. return pvals_corr
  246. binomial_enrichment_pmatrix(pop_table)
  247. # %%
  248. def permutation_global_driver_test(
  249. counts,
  250. threshold = 0.5,
  251. n_permutations = 10000,
  252. random_state = None):
  253. """
  254. Global permutation test for whether at least one category
  255. is mainly driven by one sample.
  256. Parameters
  257. ----------
  258. counts : np.ndarray
  259. Contingency table of shape (n_samples, n_categories)
  260. threshold : float
  261. Dominance threshold (default: 0.5)
  262. n_permutations : int
  263. Number of permutations
  264. random_state : int or None
  265. Random seed
  266. Returns
  267. -------
  268. float
  269. Global p-value
  270. """
  271. rng = np.random.default_rng(random_state)
  272. counts = np.asarray(counts, dtype=int)
  273. n_samples, n_categories = counts.shape
  274. col_totals = counts.sum(axis=0)
  275. grand_total = counts.sum()
  276. # global sample proportions (null model)
  277. sample_probs = counts.sum(axis=1) / grand_total
  278. # observed global statistic
  279. with np.errstate(divide="ignore", invalid="ignore"):
  280. observed_shares = counts / col_totals
  281. observed_shares[:, col_totals == 0] = 0.0
  282. T_obs = observed_shares.max()
  283. # early exit: nothing exceeds the threshold
  284. if T_obs <= threshold:
  285. return 1.0
  286. exceed_count = 0
  287. for _ in range(n_permutations):
  288. T_perm = 0.0
  289. for j in range(n_categories):
  290. n_j = col_totals[j]
  291. if n_j == 0:
  292. continue
  293. # multinomial draw under null
  294. perm_counts = rng.multinomial(n_j, sample_probs)
  295. T_perm = max(T_perm, perm_counts.max() / n_j)
  296. # early stopping for speed
  297. if T_perm >= T_obs:
  298. break
  299. if T_perm >= T_obs:
  300. exceed_count += 1
  301. # +1 correction for unbiased p-value
  302. p_value = (exceed_count + 1) / (n_permutations + 1)
  303. return p_value
  304. permutation_global_driver_test(pop_table)
  305. # %% [markdown]
  306. # ### Plots of the cloud of dots
  307. # %%
  308. plt.figure(figsize=(6,6))
  309. plt.xlim((-1.2,1.2))
  310. plt.ylim((-1.2,1.2))
  311. ax = plt.gca()
  312. mode = 'center'
  313. if mode == 'center' :
  314. ax.spines['left'].set_position('center')
  315. ax.spines['bottom'].set_position('center')
  316. ax.spines['left'].set_linewidth(1.4)
  317. ax.spines['bottom'].set_linewidth(1.4)
  318. ax.spines['right'].set_color('none')
  319. ax.spines['top'].set_color('none')
  320. plt.xticks([])
  321. plt.yticks([])
  322. elif mode == 'normal' :
  323. ax.spines[['top','right']].set_visible(False)
  324. plt.yticks([-1,0,1],fontsize=25)
  325. plt.xticks([-1,0,1],fontsize=25)
  326. ax.set_xlabel(r'$\beta_{Category G/NG}$')
  327. ax.set_ylabel(r'$\beta_{Task}$')
  328. plt.scatter(S_0[0,:],S_0[5,:], color = 'green', s = 8, alpha = 0.4,label='Population 0'\
  329. ,edgecolors='#EEEEEE',lw=0.7,antialiased=True, zorder=3)
  330. plt.scatter(S_B[0,:],S_B[5,:], color = 'red', s = 20, alpha = 1,label='Population LR'\
  331. ,edgecolors='#EEEEEE',lw=0.7,antialiased=True, zorder=3)
  332. plt.scatter(S_A[0,:],S_A[5,:], color = 'blue', s = 20, alpha = 1,label='Population GNG'\
  333. ,edgecolors='#EEEEEE',lw=0.7,antialiased=True, zorder=3)
  334. #plt.legend(frameon=False)
  335. confidence_ellipse(S_B[0,:],S_B[5,:],ax, edgecolor='red', n_std=2, alpha = 0.5,linewidth=2)
  336. confidence_ellipse(S_A[0,:],S_A[5,:],ax, edgecolor='blue', n_std=2, alpha = 0.5,linewidth=2)
  337. plt.savefig('Plots/SVG/gm_catGNG_task_' + mode + '.SVG', dpi = 300,bbox_inches='tight')
  338. plt.savefig('Plots/PNG/gm_catGNG_task_' + mode + '.PNG', dpi = 300,bbox_inches='tight')
  339. # %% [markdown]
  340. # #### 3D
  341. # %%
  342. pv.set_jupyter_backend('client')
  343. p = pv.Plotter(window_size = (800,600))
  344. p.set_background('white')
  345. p.subplot(0,0)
  346. #p.set_position(np.mean(S[GNG_trials,:3],axis=0))
  347. cord = [2,3,5]
  348. #labels = dict(ztitle=r'$\beta_{Task}$', xtitle=r'$\beta_{Category GNG}$', ytitle=r'$\beta_{Choice GNG}$')
  349. #----- This block generates the sphere for each trial label and render them in the 3D plot----
  350. radius = 0.01
  351. n_perpop = 400
  352. popGNG_spheres = [pv.Sphere(center = S[:,popA][cord,i], radius = radius) for i in range(n_perpop)]
  353. popLR_spheres = [pv.Sphere(center = S[:,popB][cord,i], radius = radius) for i in range(n_perpop)]
  354. pop0_spheres = [pv.Sphere(center = S[:,pop0][cord,i], radius = radius) for i in range(n_perpop)]
  355. for sphere in popGNG_spheres :
  356. p.add_mesh(sphere, color='blue', opacity = 0.4, lighting=False)
  357. for sphere in popLR_spheres :
  358. p.add_mesh(sphere, color='red', opacity = 0.4, lighting=False)
  359. for sphere in pop0_spheres :
  360. p.add_mesh(sphere, color='green', opacity = 0.4, lighting=False)
  361. torus_GNG = pv.ParametricTorus(ringradius=2*np.std(S[:,popA][cord[:2],:n_perpop]), crosssectionradius=5*10**(-3),center=np.mean(S[:,popA][cord,:n_perpop],axis=1))
  362. p.add_mesh(torus_GNG, color='blue', lighting=False)
  363. torus_LR = pv.ParametricTorus(ringradius=2*np.std(S[:,popB][cord[:2],:n_perpop]), crosssectionradius=5*10**(-3),center=np.mean(S[:,popB][cord,:n_perpop],axis=1))
  364. p.add_mesh(torus_LR, color='red', lighting=False)
  365. torus_0 = pv.ParametricTorus(ringradius=2*np.std(S[:,pop0][cord[:2],:n_perpop]), crosssectionradius=5*10**(-3),center=np.mean(S[:,pop0][cord,:n_perpop],axis=1))
  366. p.add_mesh(torus_0, color='green', lighting=False)
  367. #p.set_position(np.array([40,0,0]))
  368. #p.show_grid(**labels)
  369. p.show()
  370. # %% [markdown]
  371. # ### Violin
  372. # %%
  373. ## Building a dataframe with data to use seaborn module for prettier plots
  374. plot_labels=['Category GNG', 'Choice GNG','Category LR', 'Choice LR', 'Lick LR', 'Task']
  375. s_todf = []
  376. reg_todf = []
  377. cluster_todf = []
  378. for k in range(np.shape(S)[1]) :
  379. s_todf.append(S[:6,k])
  380. reg_todf.append(plot_labels)
  381. if popA[k] :
  382. cluster_todf.append(['A']*(np.shape(S)[0] - 1))
  383. elif popB[k]:
  384. cluster_todf.append(['B']*(np.shape(S)[0] - 1))
  385. else :
  386. cluster_todf.append(['else']*(np.shape(S)[0] - 1))
  387. df_const = {'Selectivity' : np.array(s_todf).flatten(), 'Abs selectivity' : np.array(np.abs(s_todf)).flatten() ,'Regressor' : np.array(reg_todf).flatten(), 'Cluster' : np.array(cluster_todf).flatten()}
  388. plot_df = pd.DataFrame(df_const)
  389. # %%
  390. ## Absolute selectivity across task variables for different populations
  391. plt.figure(figsize=(12,9))
  392. plot_dfAB = plot_df[(plot_df['Cluster'] != 'else')&(plot_df['Regressor'] != 'Task')&(plot_df['Regressor'] != 'Lick LR')]
  393. p = sns.violinplot(data = plot_dfAB, x='Regressor',y='Abs selectivity',hue='Cluster',\
  394. hue_order=['A','B'],alpha=0.7,palette=['blue','red'],cut=0,common_norm=True,\
  395. inner_kws=dict(box_width=7, whis_width=2))
  396. plt.ylabel(r'|$\beta$|',fontsize=40)
  397. plt.xlabel('')
  398. plt.legend([],[], frameon=False)
  399. plt.axvline(x = 1.5, color = 'black', linewidth=1.6)
  400. plt.ylim(0,1)
  401. plt.yticks([0,0.2,0.4,0.6,0.8,1])
  402. plt.savefig('Plots/SVG/violin_absolute.SVG', dpi = 300,bbox_inches='tight')
  403. plt.savefig('Plots/PNG/violin_absolute.PNG', dpi = 300,bbox_inches='tight')
  404. # %%
  405. export_plot_data('../../Source_Data.xlsx','Figure4f',CategoryGNG_popA=np.abs(S[0,popA]),CategoryGNG_popB=np.abs(S[0,popB]),
  406. ChoiceGNG_popA=np.abs(S[1,popA]),ChoiceGNG_popB=np.abs(S[1,popB]),
  407. CategoryLR_popA=np.abs(S[2,popA]),CategoryLR_popB=np.abs(S[2,popB]),
  408. ChoiceLR_popA=np.abs(S[3,popA]),ChoiceLR_popB=np.abs(S[3,popB]))
  409. # %%
  410. ## Absolute selectivity across task variables for different populations
  411. plt.figure(figsize=(12,8))
  412. plot_dfAB = plot_df[(plot_df['Regressor'] != 'Task')&(plot_df['Regressor'] != 'Lick LR')]
  413. p = sns.violinplot(data = plot_dfAB, x='Regressor',y='Abs selectivity',hue='Cluster',\
  414. hue_order=['A','B','else'],alpha=0.7,palette=['blue','red','green'],cut=0,common_norm=False,\
  415. inner_kws=dict(box_width=7, whis_width=2))
  416. plt.ylabel(r'|$\beta$|',fontsize=40)
  417. plt.xlabel('')
  418. plt.legend([],[], frameon=False)
  419. plt.axvline(x = 1.5, color = 'black', linewidth=1.6)
  420. plt.ylim(0,1)
  421. plt.yticks([0,0.2,0.4,0.6,0.8,1])
  422. plt.savefig('Plots/SVG/violin_absolute_all.SVG', dpi = 300,bbox_inches='tight')
  423. plt.savefig('Plots/PNG/violin_absolute_all.PNG', dpi = 300,bbox_inches='tight')
  424. # %%
  425. from scipy.stats import mannwhitneyu
  426. for reg_label in plot_dfAB['Regressor'].unique() :
  427. one_reg = plot_dfAB[(plot_dfAB['Regressor'] == reg_label)]
  428. print(reg)
  429. print(mannwhitneyu(one_reg[one_reg['Cluster'] == 'A']['Abs selectivity'],\
  430. one_reg[one_reg['Cluster'] == 'B']['Abs selectivity']))
  431. # %%
  432. plt.figure(figsize=(6,10))
  433. plot_dfall = plot_df
  434. task_only = True
  435. if task_only :
  436. plot_dfall = plot_dfall[plot_dfall['Regressor'] == 'Task']
  437. plt.figure(figsize=(2.5,8))
  438. p = sns.violinplot(data = plot_dfall, x='Regressor',y='Selectivity',hue='Cluster',hue_order=['A','B','else'],\
  439. alpha=0.7,palette=['blue','red','green'],inner_kws=dict(box_width=7, whis_width=2))
  440. plt.ylabel(r'$\beta_i$', fontsize=35)
  441. plt.xlabel('')
  442. plt.ylim((-1.2,1.2))
  443. plt.legend([],[], frameon=False)
  444. if task_only :
  445. plt.axhline(0,color='black',linestyle='--',linewidth=1.5)
  446. plt.ylabel(r'$\beta_{Task}$', fontsize=32)
  447. plt.xticks([])
  448. ax = plt.gca()
  449. ax.spines['bottom'].set_visible(False)
  450. ax.get_xaxis().set_visible(False)
  451. plt.savefig('Plots/SVG/violin_task.SVG', dpi = 300,bbox_inches='tight')
  452. plt.savefig('Plots/PNG/violin_task.PNG', dpi = 300,bbox_inches='tight')
  453. else :
  454. plt.axvline(x = 1.5, color = 'black', linewidth=1.5)
  455. plt.axvline(x = 4.5, color = 'black', linewidth=1.5)
  456. plt.savefig('Plots/SVG/violin.SVG', dpi = 300,bbox_inches='tight')
  457. plt.savefig('Plots/PNG/violin.PNG', dpi = 300,bbox_inches='tight')
  458. # %%
  459. print(mannwhitneyu(S[5,popA],S[5,popB],alternative = 'two-sided'))
  460. # %%
  461. export_plot_data('../../Source_Data.xlsx','Figure4b',popA = S[5,popA], popB = S[5,popB], pop0 = S[5,pop0])
  462. # %% [markdown]
  463. # ### Variance across time
  464. # %%
  465. fig, ax = plt.subplots(figsize=(7,7))
  466. M = np.stack(merged_data['Trajectory'])
  467. t_GNG = merged_data['Context'] == 0
  468. t_LR = merged_data['Context'] == 1
  469. var_GNG = np.std(M[t_GNG,:,:],axis=(0,1))
  470. var_LR = np.std(M[t_LR,:,:],axis=(0,1))
  471. nb_neurons = 200
  472. ax.scatter(var_GNG[pop0][:nb_neurons],var_LR[pop0][:nb_neurons],color='green',s=35, alpha = 0.65,\
  473. edgecolors='white',lw=1,antialiased=True,label='Population 0')
  474. ax.scatter(var_GNG[popA][:nb_neurons],var_LR[popA][:nb_neurons],color='blue',s=35, alpha = 0.65,\
  475. edgecolors='white',lw=1,antialiased=True,label='Population GNG')
  476. ax.scatter(var_GNG[popB][:nb_neurons],var_LR[popB][:nb_neurons],color='red',s=35, alpha = 0.65,\
  477. edgecolors='white',lw=1,antialiased=True,label='Population LR')
  478. confidence_ellipse(var_GNG[popA][:nb_neurons],var_LR[popA][:nb_neurons],ax, edgecolor='blue', n_std=1, alpha = 0.6,linewidth=3, zorder=3)
  479. confidence_ellipse(var_GNG[popB][:nb_neurons],var_LR[popB][:nb_neurons],ax, edgecolor='red', n_std=1, alpha = 0.6,linewidth=3, zorder=3)
  480. confidence_ellipse(var_GNG[pop0][:nb_neurons],var_LR[pop0][:nb_neurons],ax, edgecolor='green', n_std=1, alpha = 0.6,linewidth=3, zorder=3)
  481. plt.xlim((0,0.035))
  482. plt.ylim((0,0.035))
  483. plt.plot(np.linspace(0,0.035,100),np.linspace(0,0.035,100),alpha=0.5,color='black',linestyle='--')
  484. plt.xlabel('Time variance in Go/NoGo')
  485. plt.ylabel('Time variance in Left/Right')
  486. plt.xticks([0,0.035],[0,0.035])
  487. plt.yticks([0,0.035],[0,0.035])
  488. lgnd = plt.legend(frameon=False,fontsize=18)
  489. #plt.savefig('Plots/PNG/variance_time_gm.PNG', dpi = 300, bbox_inches='tight')
  490. #plt.savefig('Plots/SVG/variance_time_gm.SVG', dpi = 300, bbox_inches='tight')
  491. # %%
  492. plt.figure(figsize=(6,5))
  493. u = np.array([1/np.sqrt(2),-1/np.sqrt(2)])
  494. M_A = np.array([var_GNG[popA][:nb_neurons],var_LR[popA][:nb_neurons]]).T
  495. M_B = np.array([var_GNG[popB][:nb_neurons],var_LR[popB][:nb_neurons]]).T
  496. M_0 = np.array([var_GNG[pop0][:nb_neurons],var_LR[pop0][:nb_neurons]]).T
  497. ymin,ymax = -0.01, 0.01
  498. nb_bin=20
  499. hA = plt.hist(M_A@u,color='blue',density=True,range=(ymin,ymax), bins=nb_bin,alpha = 0.3,edgecolor = "black");
  500. hB = plt.hist(M_B@u,color='red',density=True, range=(ymin,ymax), bins=nb_bin,alpha = 0.3,edgecolor = "black");
  501. h0 = plt.hist(M_0@u,color='green',density=True,range=(ymin,ymax), bins=nb_bin,alpha = 0.3,edgecolor = "black");
  502. plt.clf()
  503. spl_A = splrep(hA[1], list(hA[0]) + [0], s=0.01, per=False)
  504. spl_B = splrep(hB[1], list(hB[0]) + [0], s=0.01, per=False)
  505. spl_0 = splrep(h0[1], list(h0[0]) + [0], s=0.01, per=False)
  506. x = np.linspace(ymin, ymax, 200)
  507. yA = splev(x, spl_A)
  508. yB = splev(x, spl_B)
  509. y0 = splev(x, spl_0)
  510. plt.fill_between(x,y1=0,y2=yA,color='blue',alpha=0.5)
  511. plt.fill_between(x,y1=0,y2=yB,color='red',alpha=0.5)
  512. plt.fill_between(x,y1=0,y2=y0,color='green',alpha=0.5)
  513. plt.ylim((0,300))
  514. plt.yticks([])
  515. plt.ylabel("Density")
  516. plt.xlim((ymin,ymax))
  517. plt.xticks([0],[''])
  518. #plt.savefig('Plots/PNG/variance_time_hist.PNG', dpi = 300, bbox_inches='tight')
  519. #plt.savefig('Plots/SVG/variance_time_hist.SVG', dpi = 300, bbox_inches='tight')
  520. # %%
  521. nb_bin=25
  522. bin_v = np.array([(k+0.5)*np.pi/(2*nb_bin) for k in range(0,nb_bin)])
  523. thetas = np.arctan(var_GNG/var_LR)
  524. hA = plt.hist(thetas[popA],color='blue',density=True,range=(0,np.pi/2), bins=nb_bin,alpha = 0.4);
  525. hB = plt.hist(thetas[popB],color='red',density=True, range=(0,np.pi/2), bins=nb_bin,alpha = 0.4);
  526. h0 = plt.hist(thetas[pop0],color='green',density=True,range=(0,np.pi/2), bins=nb_bin,alpha = 0.4);
  527. plt.clf()
  528. # %%
  529. from scipy.optimize import curve_fit
  530. plt.figure(figsize=(7,7))
  531. plt.hist(thetas[popA],color='red',density=True,range=(0,np.pi/2), bins=nb_bin, alpha = 0.3,edgecolor = "black")
  532. #plt.plot(bin_v,fit_y_popA,color='red',linewidth=3)
  533. plt.hist(thetas[popB],color='blue',density=True,range=(0,np.pi/2), bins=nb_bin, alpha = 0.3,edgecolor = "black")
  534. #plt.plot(bin_v,fit_y_popB,color='blue',linewidth=3)
  535. plt.hist(thetas[pop0],color='green',density=True,range=(0,np.pi/2), bins=nb_bin, alpha = 0.3,edgecolor = "black")
  536. #plt.plot(bin_v,fit_y_pop0,color='green',linewidth=3)
  537. plt.xlabel(r'$\alpha$',fontsize=30)
  538. plt.xticks([0,np.pi/4,np.pi/2],['0',r'$\frac{\pi}{4}$',r'$\frac{\pi}{2}$'])
  539. plt.ylabel('Density',fontsize=25)
  540. plt.xlim(0,np.pi/2)
  541. plt.yticks([0,1,2,3])
  542. """
  543. plt.savefig('Plots/PNG/variance_time_angle_gm.PNG', dpi = 300, bbox_inches='tight')
  544. plt.savefig('Plots/SVG/variance_time_angle_gm.SVG', dpi = 300, bbox_inches='tight')
  545. """
  546. # %%
  547. from scipy.stats import ttest_ind
  548. print(ttest_ind(thetas[popA],thetas[pop0]))
  549. print(ttest_ind(thetas[popB],thetas[pop0]))
  550. print(ttest_ind(thetas[popA],thetas[popB]))
  551. # %% [markdown]
  552. # ### Population decoding
  553. # %%
  554. M = np.stack(merged_data['Spike rate'])
  555. M = pca1.transform(M)
  556. M = pca1.inverse_transform(M)
  557. M = standardize(M)
  558. hit_GNG = merged_data['Label'] == 1
  559. cr_GNG = merged_data['Label'] == 3
  560. left_LR = merged_data['Label'] == 5
  561. right_LR = merged_data['Label'] == 8
  562. GNG_task = merged_data['Context'] == 0
  563. LR_task = 1-GNG_task
  564. M_GNG_hit = M[GNG_task&hit_GNG,:]
  565. M_GNG_cr = M[GNG_task&cr_GNG,:]
  566. M_LR_left = M[~GNG_task&left_LR,:]
  567. M_LR_right = M[~GNG_task&right_LR,:]
  568. X_GNG = np.concatenate((M_GNG_hit,M_GNG_cr),axis=0)
  569. X_LR = np.concatenate((M_LR_right,M_LR_left),axis=0)
  570. Y_GNG = np.array([1 for k in range(len(M_GNG_hit))] + [-1 for k in range(len(M_GNG_cr))])
  571. Y_LR = np.array([1 for k in range(len(M_LR_right))] + [-1 for k in range(len(M_LR_left))])
  572. # %%
  573. from tqdm.auto import tqdm
  574. from sklearn.svm import SVC
  575. nb_decoding = 100
  576. nb_neuron = 100
  577. def pop_decoding(nb_neuron = nb_neuron, nb_decoding = nb_decoding, return_inter_task=False) :
  578. """Method performing population decoding with Support Vector Classifier trained on nb_neuron different cells .
  579. We train and test models on every combinations of epochs"""
  580. scores_A_GNG = []
  581. scores_A_LR = []
  582. scores_B_GNG = []
  583. scores_B_LR = []
  584. scores_0_GNG = []
  585. scores_0_LR = []
  586. scores_A_GNG_to_LR = []
  587. scores_A_LR_to_GNG = []
  588. scores_B_GNG_to_LR = []
  589. scores_B_LR_to_GNG = []
  590. scores_0_GNG_to_LR = []
  591. scores_0_LR_to_GNG = []
  592. masks = []
  593. for n in tqdm(range(nb_decoding)) :
  594. svc_GNG_popA = SVC(kernel='linear',gamma='auto')
  595. svc_GNG_popB = SVC(kernel='linear',gamma='auto')
  596. svc_GNG_pop0 = SVC(kernel='linear',gamma='auto')
  597. svc_LR_popA = SVC(kernel='linear',gamma='auto')
  598. svc_LR_popB = SVC(kernel='linear',gamma='auto')
  599. svc_LR_pop0 = SVC(kernel='linear',gamma='auto')
  600. mask_A = np.random.choice(range(np.sum(popA)),nb_neuron,replace = False) # We do not replace to ensure independent decoding scores
  601. mask_B = np.random.choice(range(np.sum(popB)),nb_neuron,replace = False)
  602. mask_0 = np.random.choice(range(np.sum(pop0)),nb_neuron,replace = False)
  603. split_GNG = np.random.choice(range(len(X_GNG)),len(X_GNG)//2,replace=False)
  604. split_LR = np.random.choice(range(len(X_LR)),len(X_LR)//2,replace=False)
  605. X_GNG_train = X_GNG[split_GNG,:]
  606. Y_GNG_train = Y_GNG[split_GNG]
  607. X_GNG_test = np.delete(X_GNG,split_GNG,axis=0)
  608. Y_GNG_test = np.delete(Y_GNG,split_GNG,axis=0)
  609. X_LR_train = X_LR[split_LR,:]
  610. Y_LR_train = Y_LR[split_LR]
  611. X_LR_test = np.delete(X_LR,split_LR,axis=0)
  612. Y_LR_test = np.delete(Y_LR,split_LR,axis=0)
  613. svc_GNG_popA.fit(X_GNG_train[:,popA][:,mask_A],Y_GNG_train)
  614. svc_GNG_popB.fit(X_GNG_train[:,popB][:,mask_B],Y_GNG_train)
  615. svc_GNG_pop0.fit(X_GNG_train[:,pop0][:,mask_0],Y_GNG_train)
  616. svc_LR_popA.fit(X_LR_train[:,popA][:,mask_A],Y_LR_train)
  617. svc_LR_popB.fit(X_LR_train[:,popB][:,mask_B],Y_LR_train)
  618. svc_LR_pop0.fit(X_LR_train[:,pop0][:,mask_0],Y_LR_train)
  619. scores_A_GNG.append(svc_GNG_popA.score(X_GNG_test[:,popA][:,mask_A],Y_GNG_test))
  620. scores_B_GNG.append(svc_GNG_popB.score(X_GNG_test[:,popB][:,mask_B],Y_GNG_test))
  621. scores_0_GNG.append(svc_GNG_pop0.score(X_GNG_test[:,pop0][:,mask_0],Y_GNG_test))
  622. scores_A_LR.append(svc_LR_popA.score(X_LR_test[:,popA][:,mask_A],Y_LR_test))
  623. scores_B_LR.append(svc_LR_popB.score(X_LR_test[:,popB][:,mask_B],Y_LR_test))
  624. scores_0_LR.append(svc_LR_pop0.score(X_LR_test[:,pop0][:,mask_0],Y_LR_test))
  625. masks.append([mask_A, mask_B, mask_0])
  626. if return_inter_task :
  627. scores_A_GNG_to_LR.append(svc_GNG_popA.score(X_LR_test[:,popA][:,mask_A],Y_LR_test))
  628. scores_B_GNG_to_LR.append(svc_GNG_popB.score(X_LR_test[:,popB][:,mask_B],Y_LR_test))
  629. scores_0_GNG_to_LR.append(svc_GNG_pop0.score(X_LR_test[:,pop0][:,mask_0],Y_LR_test))
  630. scores_A_LR_to_GNG.append(svc_LR_popA.score(X_GNG_test[:,popA][:,mask_A],Y_GNG_test))
  631. scores_B_LR_to_GNG.append(svc_LR_popB.score(X_GNG_test[:,popB][:,mask_B],Y_GNG_test))
  632. scores_0_LR_to_GNG.append(svc_LR_pop0.score(X_GNG_test[:,pop0][:,mask_0],Y_GNG_test))
  633. scores_A_GNG = np.array(scores_A_GNG)
  634. scores_A_LR = np.array(scores_A_LR)
  635. scores_B_GNG = np.array(scores_B_GNG)
  636. scores_B_LR = np.array(scores_B_LR)
  637. scores_0_GNG = np.array(scores_0_GNG)
  638. scores_0_LR = np.array(scores_0_LR)
  639. scores_A_GNG_to_LR = np.array(scores_A_GNG_to_LR)
  640. scores_A_LR_to_GNG = np.array(scores_A_LR_to_GNG)
  641. scores_B_GNG_to_LR = np.array(scores_B_GNG_to_LR)
  642. scores_B_LR_to_GNG = np.array(scores_B_LR_to_GNG)
  643. scores_0_GNG_to_LR = np.array(scores_0_GNG_to_LR)
  644. scores_0_LR_to_GNG = np.array(scores_0_LR_to_GNG)
  645. masks = np.array(masks)
  646. if return_inter_task :
  647. return [scores_A_GNG, scores_A_LR, scores_A_GNG_to_LR, scores_A_LR_to_GNG],\
  648. [scores_B_GNG, scores_B_LR, scores_B_GNG_to_LR, scores_B_LR_to_GNG],\
  649. [scores_0_GNG, scores_0_LR, scores_0_GNG_to_LR, scores_0_LR_to_GNG], masks
  650. else :
  651. return [scores_A_GNG, scores_A_LR], [scores_B_GNG, scores_B_LR], [scores_0_GNG, scores_0_LR]
  652. # %%
  653. scores_A, scores_B, scores_0, _ = pop_decoding(2,100,return_inter_task=True)
  654. scores_A_many, scores_B_many, scores_0_many, _ = pop_decoding(20,return_inter_task=True)
  655. # %% [markdown]
  656. # ### Go/NoGo - Go/NoGo
  657. # %%
  658. from scipy.interpolate import splev, splrep
  659. fig,axs = plt.subplots(1,2,figsize=(6,8),gridspec_kw={'width_ratios': [3, 1]})
  660. #fig.subplots_adjust(hspace=0)
  661. df_dict = {'Population\nGNG' : scores_A[0], 'Population\nLR' : scores_B[0]}
  662. test = pd.DataFrame(df_dict)
  663. sns.stripplot(test,alpha=0.7,palette=['blue','red'],jitter=0.2,size=12,ax=axs[0])
  664. axs[0].errorbar([0,1],[np.mean(scores_A[0]),np.mean(scores_B[0])],yerr=[np.std(scores_A[0])/np.sqrt(nb_decoding),np.std(scores_B[0])/np.sqrt(nb_decoding)],\
  665. color='black',marker='_',markersize=85,elinewidth=3.5,capsize=7,zorder=10,linestyle='',markeredgewidth=3.5)
  666. ymin,ymax = 0.3,1
  667. axs[0].set_xlim((-0.5,1.5))
  668. axs[0].set_ylim((ymin,ymax+0.05))
  669. axs[0].set_yticks([0.5,0.75,1],['50%','75%','100%'])
  670. axs[0].set_ylabel('Population decoding accuracy')
  671. axs[0].fill_between([-0.5,1.5],ymin,0.5, color='black',alpha=0.05)
  672. axs[0].axhline(0.5,color="black",linestyle='--',alpha=0.3,linewidth=1.5)
  673. nb_bins=25
  674. histA = axs[1].hist(scores_A[0],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='blue',alpha=0.5,density=True)
  675. histB = axs[1].hist(scores_B[0],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='red',alpha=0.5,density=True)
  676. axs[1].clear()
  677. axs[1].set_yticks([])
  678. axs[1].set_xticks([])
  679. axs[1].spines['bottom'].set_visible(False)
  680. axs[1].set_ylim((ymin,ymax+0.02))
  681. axs[1].set_xlim((0,70))
  682. # Interpolation
  683. spl_A = splrep(histA[1], list(histA[0]) + [0], s=0.01, per=False)
  684. spl_B = splrep(histB[1], list(histB[0]) + [0], s=0.01, per=False)
  685. x = np.linspace(ymin, ymax, 200)
  686. yA = splev(x, spl_A)
  687. yB = splev(x, spl_B)
  688. axs[1].fill_betweenx(x,x1=0,x2=yA,color='blue',alpha=0.5)
  689. axs[1].fill_betweenx(x,x1=0,x2=yB,color='red',alpha=0.5)
  690. #plt.savefig('Plots/SVG/decoding_2neuron_GNG.SVG', dpi = 300,bbox_inches='tight')
  691. #plt.savefig('Plots/PNG/decoding_2neuron_GNG.PNG', dpi = 300,bbox_inches='tight')
  692. print(mannwhitneyu(scores_A[0],scores_B[0]))
  693. # %%
  694. from scipy.interpolate import splev, splrep
  695. fig,ax = plt.subplots(1,1,figsize=(6.2,8))
  696. #fig.subplots_adjust(hspace=0)
  697. df_dict = {'Population\nGNG' : scores_A[0], 'Population\nLR' : scores_B[0], 'Population\n0' : scores_0[0]}
  698. test = pd.DataFrame(df_dict)
  699. sns.stripplot(test,alpha=0.7,palette=['blue','red','green'],jitter=0.2,size=12,ax=ax)
  700. ax.errorbar([0,1,2],[np.mean(scores_A[0]),np.mean(scores_B[0]),np.mean(scores_0[0])],yerr=[np.std(scores_A[0])/np.sqrt(nb_decoding),np.std(scores_B[0])/np.sqrt(nb_decoding),np.std(scores_0[0])/np.sqrt(nb_decoding)],\
  701. color='black',marker='_',markersize=85,elinewidth=3.5,capsize=7,zorder=10,linestyle='',markeredgewidth=3.5)
  702. ymin,ymax = 0.3,1
  703. ax.set_xlim((-0.5,2.5))
  704. ax.set_ylim((ymin,ymax+0.05))
  705. ax.set_yticks([0.5,0.75,1],['50%','75%','100%'])
  706. ax.set_ylabel('Population decoding accuracy')
  707. ax.fill_between([-0.5,2.5],ymin,0.5, color='black',alpha=0.05)
  708. ax.axhline(0.5,color="black",linestyle='--',alpha=0.3,linewidth=1.5)
  709. plt.savefig('Plots/SVG/decoding_2neuron_GNG.SVG', dpi = 300,bbox_inches='tight')
  710. plt.savefig('Plots/PNG/decoding_2neuron_GNG.PNG', dpi = 300,bbox_inches='tight')
  711. print(mannwhitneyu(scores_A[0],scores_B[0]))
  712. print(mannwhitneyu(scores_A[0],scores_0[0]))
  713. print(mannwhitneyu(scores_B[0],scores_0[0]))
  714. # %%
  715. export_plot_data('../../Source_Data.xlsx','Figure4g',scores_A = scores_A[0], scores_B = scores_B[0], scores_0 = scores_0[0])
  716. # %% [markdown]
  717. # ### LR - LR
  718. # %%
  719. from scipy.interpolate import splev, splrep
  720. fig,axs = plt.subplots(1,2,figsize=(6,8),gridspec_kw={'width_ratios': [3, 1]})
  721. #fig.subplots_adjust(hspace=0)
  722. df_dict = {'Population\nGNG' : scores_A[1], 'Population\nLR' : scores_B[1]}
  723. test = pd.DataFrame(df_dict)
  724. sns.stripplot(test,alpha=0.7,palette=['blue','red'],jitter=0.2,size=12,ax=axs[0])
  725. axs[0].errorbar([0,1],[np.mean(scores_A[1]),np.mean(scores_B[1])],yerr=[np.std(scores_A[1])/np.sqrt(nb_decoding),np.std(scores_B[1])/np.sqrt(nb_decoding)],\
  726. color='black',marker='_',markersize=85,elinewidth=3.5,capsize=7,zorder=10,linestyle='',markeredgewidth=3.5)
  727. ymin,ymax = 0.3,1
  728. axs[0].set_xlim((-0.5,1.5))
  729. axs[0].set_ylim((ymin,ymax+0.05))
  730. axs[0].set_yticks([0.5,0.75,1],['50%','75%','100%'])
  731. axs[0].set_ylabel('Population decoding accuracy')
  732. axs[0].fill_between([-0.5,1.5],ymin,0.5, color='black',alpha=0.05)
  733. axs[0].axhline(0.5,color="black",linestyle='--',alpha=0.3,linewidth=1.5)
  734. nb_bins=25
  735. histA = axs[1].hist(scores_A[1],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='blue',alpha=0.5,density=True)
  736. histB = axs[1].hist(scores_B[1],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='red',alpha=0.5,density=True)
  737. axs[1].clear()
  738. axs[1].set_yticks([])
  739. axs[1].set_xticks([])
  740. axs[1].spines['bottom'].set_visible(False)
  741. axs[1].set_ylim((ymin,ymax+0.02))
  742. axs[1].set_xlim((0,30))
  743. # Interpolation
  744. spl_A = splrep(histA[1], list(histA[0]) + [0], s=0.01, per=False)
  745. spl_B = splrep(histB[1], list(histB[0]) + [0], s=0.01, per=False)
  746. x = np.linspace(ymin, ymax, 200)
  747. yA = splev(x, spl_A)
  748. yB = splev(x, spl_B)
  749. axs[1].fill_betweenx(x,x1=0,x2=yA,color='blue',alpha=0.5)
  750. axs[1].fill_betweenx(x,x1=0,x2=yB,color='red',alpha=0.5)
  751. #plt.savefig('Plots/SVG/decoding_2neuron_LR.SVG', dpi = 300,bbox_inches='tight')
  752. #plt.savefig('Plots/PNG/decoding_2neuron_LR.PNG', dpi = 300,bbox_inches='tight')
  753. print(mannwhitneyu(scores_A[1],scores_B[1]))
  754. # %%
  755. from scipy.interpolate import splev, splrep
  756. fig,ax = plt.subplots(1,1,figsize=(6.2,8))
  757. df_dict = {'Population\nGNG' : scores_A[1], 'Population\nLR' : scores_B[1], 'Population\n0' : scores_0[1]}
  758. test = pd.DataFrame(df_dict)
  759. sns.stripplot(test,alpha=0.7,palette=['blue','red','green'],jitter=0.2,size=12,ax=ax)
  760. ax.errorbar([0,1,2],[np.mean(scores_A[1]),np.mean(scores_B[1]),np.mean(scores_0[1])],yerr=[np.std(scores_A[1])/np.sqrt(nb_decoding),np.std(scores_B[1])/np.sqrt(nb_decoding),np.std(scores_0[1])/np.sqrt(nb_decoding)],\
  761. color='black',marker='_',markersize=85,elinewidth=3.5,capsize=7,zorder=10,linestyle='',markeredgewidth=3.5)
  762. ymin,ymax = 0.3,1
  763. ax.set_xlim((-0.5,2.5))
  764. ax.set_ylim((ymin,ymax+0.05))
  765. ax.set_yticks([0.5,0.75,1],['50%','75%','100%'])
  766. ax.set_ylabel('Population decoding accuracy')
  767. ax.fill_between([-0.5,2.5],ymin,0.5, color='black',alpha=0.05)
  768. ax.axhline(0.5,color="black",linestyle='--',alpha=0.3,linewidth=1.5)
  769. plt.savefig('Plots/SVG/decoding_2neuron_LR.SVG', dpi = 300,bbox_inches='tight')
  770. plt.savefig('Plots/PNG/decoding_2neuron_LR.PNG', dpi = 300,bbox_inches='tight')
  771. print(mannwhitneyu(scores_A[1],scores_B[1]))
  772. print(mannwhitneyu(scores_A[1],scores_0[1]))
  773. print(mannwhitneyu(scores_B[1],scores_0[1]))
  774. # %%
  775. export_plot_data('../../Source_Data.xlsx','Figure4h',scores_A = scores_A[1], scores_B = scores_B[1], scores_0 = scores_0[1])
  776. # %% [markdown]
  777. # ### Go/NoGo - LR
  778. # %%
  779. from scipy.interpolate import splev, splrep
  780. fig,axs = plt.subplots(1,2,figsize=(10,8),gridspec_kw={'width_ratios': [3, 1]})
  781. #fig.subplots_adjust(hspace=0)
  782. df_dict = {'Population\nGNG' : scores_A_many[2], 'Population\nLR' : scores_B_many[2],'Population\n0' : scores_0_many[2]}
  783. df_plot = pd.DataFrame(df_dict)
  784. sns.stripplot(df_plot,alpha=0.7,palette=['blue','red','green'],jitter=0.2,size=12,ax=axs[0])
  785. axs[0].errorbar([0,1,2],[np.mean(scores_A_many[2]),np.mean(scores_B_many[2]),np.mean(scores_0_many[2])],\
  786. yerr=[np.std(scores_A_many[2])/np.sqrt(nb_decoding),np.std(scores_B_many[2])/np.sqrt(nb_decoding),np.std(scores_0_many[2])/np.sqrt(nb_decoding)],\
  787. color='black',marker='_',markersize=80,elinewidth=3.5,capsize=7,zorder=10,linestyle='',markeredgewidth=3.5)
  788. ymin,ymax = 0.3,1
  789. axs[0].set_xlim((-0.5,2.5))
  790. axs[0].set_ylim((ymin,ymax+0.05))
  791. axs[0].set_yticks([0.5,0.75,1],['50%','75%','100%'],fontsize=25)
  792. axs[0].set_ylabel('Population decoding accuracy')
  793. axs[0].fill_between([-0.5,2.5],ymin,0.5, color='black',alpha=0.05)
  794. axs[0].axhline(0.5,color="black",linestyle='--',alpha=0.3,linewidth=1.5)
  795. nb_bins=25
  796. histA = axs[1].hist(scores_A_many[2],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='blue',alpha=0.5,density=True)
  797. histB = axs[1].hist(scores_B_many[2],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='red',alpha=0.5,density=True)
  798. hist0 = axs[1].hist(scores_0_many[2],range=(ymin,ymax),bins=nb_bins,orientation='horizontal',color='green',alpha=0.5,density=True)
  799. axs[1].clear()
  800. axs[1].set_yticks([])
  801. axs[1].set_xticks([])
  802. axs[1].spines['bottom'].set_visible(False)
  803. axs[1].set_ylim((ymin,ymax+0.02))
  804. axs[1].set_xlim((0,30))
  805. # Interpolation
  806. spl_A = splrep(histA[1], list(histA[0]) + [0], s=0.01, per=False)
  807. spl_B = splrep(histB[1], list(histB[0]) + [0], s=0.01, per=False)
  808. spl_0 = splrep(hist0[1], list(hist0[0]) + [0], s=0.01, per=False)
  809. x = np.linspace(ymin, ymax, 200)
  810. yA = splev(x, spl_A)
  811. yB = splev(x, spl_B)
  812. y0 = splev(x, spl_0)
  813. axs[1].fill_betweenx(x,x1=0,x2=yA,color='blue',alpha=0.5)
  814. axs[1].fill_betweenx(x,x1=0,x2=yB,color='red',alpha=0.5)
  815. axs[1].fill_betweenx(x,x1=0,x2=y0,color='green',alpha=0.5)
  816. #plt.savefig('Plots/SVG/decoding_20neuron_GNG_to_LR.SVG', dpi = 300,bbox_inches='tight')
  817. #plt.savefig('Plots/PNG/decoding_20neuron_GNG_to_LR.PNG', dpi = 300,bbox_inches='tight')
  818. print(mannwhitneyu(scores_0_many[2],scores_A_many[2]))
  819. print(mannwhitneyu(scores_0_many[2],scores_B_many[2]))
  820. print(wilcoxon(scores_0_many[2]-0.5,alternative='greater'))
  821. # %%
  822. export_plot_data('../../Source_Data.xlsx','Figure4i',scores_A = scores_A_many[2], scores_B = scores_B_many[2], scores_0 = scores_0_many[2])
  823. # %% [markdown]
  824. # ### Information
  825. # %%
  826. ## Exploration on the nature of information each population provides
  827. nb_bins = 20
  828. def compute_prob(data) :
  829. hist = np.histogram(data,nb_bins,range=(-2,2))[0]
  830. return hist/np.sum(hist)
  831. eps = 0
  832. def MI(XY_mat,pX,pY) :
  833. # XY matrix must be the matrix of X conditionned on Y
  834. mi = 0
  835. for i in range(np.shape(XY_mat)[0]) :
  836. for j in range(np.shape(XY_mat)[1]) :
  837. if (pY[i] <= eps) or (pX[j] <= eps) or (XY_mat[i,j] <= eps) :
  838. mi+=0
  839. else :
  840. #print(pY[i])
  841. #print(pX[j])
  842. mi+=pY[i]*XY_mat[i,j]*np.log2(pY[i]*XY_mat[i,j]/(pY[i]*pX[j]))
  843. return mi
  844. def cond_MI(XYZ_mat,XZ_mat,pX,pY,pZ) :
  845. # We compute the MI between X and Y conditionned on Z
  846. # Here we suppose the independence between Y and Z. XYZ matrix is the matrix of X conditionned on Y and Z.
  847. mi = 0
  848. for i in range(np.shape(XYZ_mat)[0]) :
  849. for j in range(np.shape(XYZ_mat)[1]) :
  850. for k in range(np.shape(XYZ_mat)[2]) :
  851. if (XZ_mat[j,k] <= eps) or (XYZ_mat[i,j,k] <= eps):
  852. mi += 0
  853. else :
  854. mi += pY[i]*pZ[j]*XYZ_mat[i,j,k]*np.log2(XYZ_mat[i,j,k]/XZ_mat[j,k])
  855. return mi
  856. # %%
  857. from sklearn.mixture import GaussianMixture, BayesianGaussianMixture
  858. from tqdm.auto import tqdm
  859. def get_pop(prob,S) :
  860. pop1 = prob[:,0] >= 0.9
  861. pop2 = prob[:,1] >= 0.9
  862. pop3 = prob[:,2] >= 0.9
  863. pops = [pop1,pop2,pop3]
  864. S_pop = [S[:,pop1],S[:,pop2],S[:,pop3]]
  865. mean_ctx_s = [np.mean(S_pop[0][0,:]),np.mean(S_pop[1][0,:]),np.mean(S_pop[2][0,:])]
  866. popA = pops[np.argsort(mean_ctx_s)[0]]
  867. popB = pops[np.argsort(mean_ctx_s)[2]]
  868. pop0 = pops[np.argsort(mean_ctx_s)[1]]
  869. return popA, popB, pop0
  870. def compute_UI_SYN_pop(S_grid,A_data,s_data,c_data) :
  871. pC = np.array([0.5,0.5])
  872. pS = np.array([0.5,0.5])
  873. MI_AC_popA = []
  874. MI_AS_popA = []
  875. MI_AC_S_popA = []
  876. MI_AC_popB = []
  877. MI_AS_popB = []
  878. MI_AC_S_popB = []
  879. MI_AC_pop0 = []
  880. MI_AS_pop0 = []
  881. MI_AC_S_pop0 = []
  882. A_matrices = np.array([compute_prob(A_data[:,n]) for n in range(np.shape(A_data)[1])])
  883. AS_matrices = np.array([[compute_prob(A_data[s_data==-1,n]),compute_prob(A_data[s_data==1,n])] for n in range(np.shape(A_data)[1])])
  884. AC_matrices = np.array([[compute_prob(A_data[c_data==-1,n]),compute_prob(A_data[c_data==1,n])] for n in range(np.shape(A_data)[1])])
  885. ACS_matrices = np.array([[[compute_prob(A_data[(s_data==-1)&(c_data==-1),n]),compute_prob(A_data[(s_data==1)&(c_data==-1),n])],\
  886. [compute_prob(A_data[(s_data==-1)&(c_data==1),n]),compute_prob(A_data[(s_data==1)&(c_data==1),n])]] for n in range(np.shape(A_data)[1])])
  887. MI_AS_s = []
  888. MI_AC_s = []
  889. MI_AC_S_s = []
  890. for n in range(np.shape(A_matrices)[0]) :
  891. pA = A_matrices[n]
  892. AS_mat = AS_matrices[n]
  893. AC_mat = AC_matrices[n]
  894. ACS_mat = ACS_matrices[n]
  895. MI_AS_s.append(MI(AS_mat,pA,pS))
  896. MI_AC_s.append(MI(AC_mat,pA,pC))
  897. MI_AC_S_s.append(cond_MI(ACS_mat,AS_mat,pA,pC,pS))
  898. MI_AS_s = np.array(MI_AS_s)
  899. MI_AC_s = np.array(MI_AC_s)
  900. MI_AC_S_s = np.array(MI_AC_S_s)
  901. MI_AC_popA.append(MI_AC_s[popA])
  902. MI_AC_popB.append(MI_AC_s[popB])
  903. MI_AC_pop0.append(MI_AC_s[pop0])
  904. MI_AS_popA.append(MI_AS_s[popA])
  905. MI_AS_popB.append(MI_AS_s[popB])
  906. MI_AS_pop0.append(MI_AS_s[pop0])
  907. MI_AC_S_popA.append(MI_AC_S_s[popA])
  908. MI_AC_S_popB.append(MI_AC_S_s[popB])
  909. MI_AC_S_pop0.append(MI_AC_S_s[pop0])
  910. MI_AC_popA = np.array(MI_AC_popA)
  911. MI_AC_popB = np.array(MI_AC_popB)
  912. MI_AC_pop0 = np.array(MI_AC_pop0)
  913. MI_AS_popA = np.array(MI_AS_popA)
  914. MI_AS_popB = np.array(MI_AS_popB)
  915. MI_AS_pop0 = np.array(MI_AS_pop0)
  916. MI_AC_S_popA = np.array(MI_AC_S_popA)
  917. MI_AC_S_popB = np.array(MI_AC_S_popB)
  918. MI_AC_S_pop0 = np.array(MI_AC_S_pop0)
  919. return np.squeeze(np.array([MI_AC_popA, MI_AS_popA, MI_AC_S_popA])), \
  920. np.squeeze(np.array([MI_AC_popB, MI_AS_popB, MI_AC_S_popB])), \
  921. np.squeeze(np.array([MI_AC_pop0, MI_AS_pop0, MI_AC_S_pop0]))
  922. # %%
  923. M = np.stack(merged_data['Spike rate'])
  924. M = standardize(M)
  925. M = pca1.inverse_transform(pca1.transform(M))
  926. c_d = 2*merged_data['Context']-1
  927. s_d = 2*merged_data['Category ID']-3
  928. MI_popA, MI_popB, MI_pop0 = compute_UI_SYN_pop(S, M, s_d, c_d)
  929. # %%
  930. plt.figure(figsize=(10,7))
  931. colors=['blue','red','green']
  932. def set_color(bplot) :
  933. for patch, color in zip(bplot['boxes'], colors):
  934. patch.set_facecolor(color)
  935. patch.set_alpha(0.3)
  936. for median, color in zip(bplot['medians'], colors):
  937. median.set_color(color)
  938. median.set_linewidth(2)
  939. for mean, color in zip(bplot['means'], colors):
  940. mean.set_color(color)
  941. mean.set_alpha(0.5)
  942. mean.set_linewidth(1.5)
  943. plt.ylim(-0.05,1.05)
  944. bplot = plt.boxplot([MI_popA[0,:],MI_popB[0,:],MI_pop0[0,:]], positions=[0,0.3,0.6],\
  945. patch_artist=True,notch=True,bootstrap=1000,sym='',showmeans=True,meanline=True)
  946. set_color(bplot)
  947. bplot = plt.boxplot([MI_popA[1,:],MI_popB[1,:],MI_pop0[1,:]], positions=[1.5,1.8,2.1],\
  948. patch_artist=True,notch=True,bootstrap=1000,sym='',showmeans=True,meanline=True)
  949. set_color(bplot)
  950. bplot = plt.boxplot([MI_popA[2,:]-MI_popA[0,:],MI_popB[2,:]-MI_popB[0,:],MI_pop0[2,:]-MI_pop0[0,:]], positions=[3,3.3,3.6],\
  951. patch_artist=True,notch=True,bootstrap=1000,sym='',showmeans=True,meanline=True)
  952. set_color(bplot)
  953. plt.xticks([0.3,1.8,3.3],[r'$UI_{Category}$',r'$UI_{Task}$',r'$Syn_{\{Category,Task\}}$'])
  954. plt.ylabel('Information (bits)')
  955. plt.yticks([0,0.2,0.4,0.6,0.8,1])
  956. plt.savefig('Plots/SVG/MI_pop.SVG', dpi = 300,bbox_inches='tight')
  957. plt.savefig('Plots/PNG/MI_pop.PNG', dpi = 300,bbox_inches='tight')
  958. # %%
  959. from scipy.stats import mannwhitneyu, permutation_test
  960. print('------- UI category -------')
  961. print('A-B : ' + str(mannwhitneyu(MI_popA[0,:],MI_popB[0,:])))
  962. print('A-0 : ' + str(mannwhitneyu(MI_popA[0,:],MI_pop0[0,:])))
  963. print('B-0 : ' + str(mannwhitneyu(MI_popB[0,:],MI_pop0[0,:])))
  964. print('------- UI task -------')
  965. print('A-B : ' + str(mannwhitneyu(MI_popA[1,:],MI_popB[1,:])))
  966. print('A-0 : ' + str(mannwhitneyu(MI_popA[1,:],MI_pop0[1,:])))
  967. print('B-0 : ' + str(mannwhitneyu(MI_popB[1,:],MI_pop0[1,:])))
  968. print('------- Synergy -------')
  969. print('A-B : ' + str(mannwhitneyu(MI_popA[2,:]-MI_popA[0,:],MI_popB[2,:]-MI_popB[0,:])))
  970. print('A-0 : ' + str(mannwhitneyu(MI_popA[2,:]-MI_popA[0,:],MI_pop0[2,:]-MI_pop0[0,:])))
  971. print('B-0 : ' + str(mannwhitneyu(MI_popB[2,:]-MI_popB[0,:],MI_pop0[2,:]-MI_pop0[0,:])))

gaussian_mixture.ipynb at commit 852d401, no license · at the source

Overview

  1. Laboratoire des Systemes Perceptifs, Ecole Normale Superieure, PSL University, CNRS, Paris, France
  2. Ear Institute, University College London, London, UK
  3. Max Planck Institute for Biological Intelligence, Martinsried, Germany
  4. Present Address: Sainsbury Wellcome Centre, University College London, London, UK
Journal: Nature communications, volume 17, issue 1, article 9904
Dates: received 21 August 2025; accepted 20 July 2026; published online 19 August 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-76104-3 · PMID 42749711 · PMCID PMC13582938 · OpenAlex W4414383722
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: computational modeling (no new data) (modality), non-human primate (organism), systems (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Single-unit activity, calcium imaging, Connectivity
Keywords: Neural encoding, Neural circuits, Network models, Decision
MeSH: Decision Making*, Prefrontal Cortex*, Animals, Macaca mulatta, Male, Models, Neurological, Neurons (* major topic)
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Agence Nationale de la Recherche (French National Research Agency) (ANR-10-IDEX- 0001-02, ANR-17-EURE-0017, ANR-24-CE45-7453); Université de Recherche Paris Sciences et Lettres (PSL Research University) (PSL-NEURO, qLife)
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 21 matches between paragraphs and lines of code.

HTissot42/Sensorimotor-remapping-drives-task-specialization

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 852d401f2c575a49377ae0e7b91e2b309db210a5, 23 June 2026
Languages: Jupyter (66), Python (2)
Size: 345 files, 68 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, 34 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (58 files), pandas (58 files), Matplotlib (45 files), SciPy (38 files), scikit-learn (37 files), seaborn (24 files), statsmodels (4 files), NetworkX (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
59 files

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-76104-3.

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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 58 scripts, each with its path and the digest of its content;
  • 21 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 paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-76104-3.

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, 5 authors, 4 keywords, 7 MeSH terms, 2 funders, 49 references.

Cite

This paper

Tissot, H., Boucher, J., Reinert, S., Goltstein, P. M., & Boubenec, Y. (2026). Sensorimotor remapping drives task specialization in prefrontal cortex. Nature communications, 17(1), 9904. https://doi.org/10.1038/s41467-026-76104-3

BibTeX

@article{tissot2026sensorimotor,
author = {Tissot, Hugo and Boucher, Jeff and Reinert, Sandra and Goltstein, Pieter M and Boubenec, Yves},
title = {{Sensorimotor remapping drives task specialization in prefrontal cortex}},
journal = {Nature communications},
year = {2026},
month = aug,
volume = {17},
number = {1},
pages = {9904},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-76104-3},
url = {https://doi.org/10.1038/s41467-026-76104-3},
pmid = {42749711},
pmcid = {PMC13582938}
}

RIS

TY - JOUR
AU - Tissot, Hugo
AU - Boucher, Jeff
AU - Reinert, Sandra
AU - Goltstein, Pieter M
AU - Boubenec, Yves
TI - Sensorimotor remapping drives task specialization in prefrontal cortex
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/08/19
VL - 17
IS - 1
SP - 9904
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-76104-3
UR - https://doi.org/10.1038/s41467-026-76104-3
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-76104-3",
"type": "article-journal",
"title": "Sensorimotor remapping drives task specialization in prefrontal cortex",
"container-title": "Nature communications",
"author": [
{
"family": "Tissot",
"given": "Hugo"
},
{
"family": "Boucher",
"given": "Jeff"
},
{
"family": "Reinert",
"given": "Sandra"
},
{
"family": "Goltstein",
"given": "Pieter M"
},
{
"family": "Boubenec",
"given": "Yves"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "9904",
"DOI": "10.1038/s41467-026-76104-3",
"PMID": "42749711",
"PMCID": "PMC13582938",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-76104-3",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
19
]
]
}
}

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/s41593-026-02333-w [code]
Learning shapes neural geometry in the primate prefrontal cortex.
Journal: Nature neuroscience
In common: seaborn, scikit-learn, pandas, 3 other tools, non-human primate, 9 references
[2] doi:10.1371/journal.pbio.3003831 [code]
Disinhibitory signaling enables flexible coding of top-down information in cortical networks.
Journal: PLoS biology
In common: NetworkX, scikit-learn, pandas, 3 other tools, systems, 6 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, 6 references
[4] 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, 5 references
[5] doi:10.1038/s41467-026-74347-8 [code]
Compositionality of social gaze in the prefrontal-amygdala circuits.
Journal: Nature communications
In common: statsmodels, seaborn, scikit-learn, 4 other tools, non-human primate, systems, 3 references
[6] doi:10.1371/journal.pcbi.1014162 [code]
Exploring neural manifolds across a wide range of intrinsic dimensions.
Journal: PLoS computational biology
In common: scikit-learn, pandas, SciPy, 2 other tools, 5 references
[7] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In common: NetworkX, statsmodels, seaborn, 5 other tools, systems, 1 reference
[8] doi:10.1038/s41540-026-00727-x [code]
Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia.
Journal: NPJ systems biology and applications
In common: statsmodels, seaborn, pandas, 3 other tools, 3 references
[9] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: statsmodels, seaborn, scikit-learn, 4 other tools, systems, 2 references
[10] doi:10.1038/s41467-026-72057-9 [code]
Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.
Journal: Nature communications
In common: statsmodels, seaborn, scikit-learn, 4 other tools, systems, 2 references

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.