OSCR

Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions.

Code ↔ Paper

15 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 15 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § STAR★Methods › Method details › Identification of nerve projections ↔ axonal_projections_pipeline.py, lines 355–400 · score 0.85 · Granger causality, optimal lag, co occurrences, Cross correlation, pipeline, peak
  2. [2] § STAR★Methods › Quantification and statistical analysis ↔ figure_neuron_factors.py, lines 134–157 · score 0.79 · Holm Sidak, Kruskal Wallis, post hoc, neuron factor, Dunn, animals
  3. [3] § STAR★Methods › Quantification and statistical analysis ↔ figure_neuron_factors_fractional.py, lines 85–105 · score 0.79 · Holm Sidak, Kruskal Wallis, post hoc, neuron factor, Dunn, animals
  4. [4] § STAR★Methods › Method details › Analysis of VSD imaging data ↔ Intan_gui.m, lines 2250–2344 · score 0.75 · 5–80 Hz, passband ripple, attenuation, lowpass, stopband, bandpass
  5. [5] § STAR★Methods › Method details › Transition analysis ↔ figure_neuron_factors.py, lines 478–506 · score 0.72 · JS divergence, Monte Carlo, neuron factors, matrices, transition
  6. [6] § STAR★Methods › Method details › Tensor component analysis ↔ tca_run_concatenated.py, the whole file · a weak match · score 0.69 · fitting TCA, factor matrices, neuron factor, rank, tensor, components
  7. [7] § STAR★Methods › Method details › Tensor component analysis ↔ tca_run_egestive_priming.py, the whole file · a weak match · score 0.69 · fitting TCA, factor matrices, neuron factor, rank, tensor, components
  8. [8] § Results › TCA reveals three distinct neuronal ensembles associated with phases of feeding behavior ↔ plot_identified_neurons.py, lines 1–87 · score 0.69 · B10, B43, B62, B67, neurons contributed, B47
  9. [9] § Results › Priming leads to more rapid ensemble transitions and increases neuronal contributions to all three ensembles ↔ figure_neuron_factors.py, lines 134–157 · score 0.63 · Post hoc Dunn, Kruskal Wallis, Neuron factor, EP
  10. [10] § Results › Priming leads to more rapid ensemble transitions and increases neuronal contributions to all three ensembles ↔ figure_neuron_factors_fractional.py, lines 85–105 · score 0.63 · Post hoc Dunn, Kruskal Wallis, Neuron factor, EP
  11. [11] § Results › TCA reveals three distinct neuronal ensembles associated with phases of feeding behavior ↔ plot_rasters_identified.ipynb, lines 12–84 · score 0.61 · B10, B43, B62, B67, B47, B44
  12. [12] § STAR★Methods › Method details › Analysis of extracellular nerve activity ↔ Intan_gui.m, lines 2250–2344 · score 0.59 · passband ripple, attenuation, stopband, notch, Intan, filtered
  13. [13] § STAR★Methods › Method details › Analysis of VSD imaging data ↔ fVSD/detect_spikes_vsd.m, lines 1–27 · score 0.54 · standard deviation, cell, amplitude, VSD, traces, peak
  14. [14] § STAR★Methods › Method details › Analysis of extracellular nerve activity ↔ read_Intan_RHS2000_file.m, lines 1–51 · score 0.53 · notch filter, amplified, Stimulus, Intan, Stimulation, MATLAB
  15. [15] § STAR★Methods › Method details › Concatenated analysis ↔ helper_functions_image.py, lines 190–274 · score 0.50 · vertically flipped, linear, rotated, maps, Neuron

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

Python · 615 lines · 24 KB · no license · 3 matches

  1. from cv2 import exp
  2. from matplotlib.pylab import nan
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. import pandas as pd
  6. from matplotlib_venn import venn3
  7. from collections import defaultdict
  8. import pingouin as pg
  9. import networkx as nx
  10. import matplotlib.colors as mcolors
  11. import matplotlib as mpl
  12. from scipy.io import savemat
  13. import seaborn as sns
  14. import matplotlib.patches as patches
  15. from scipy.spatial.distance import jensenshannon
  16. from scipy.stats import kruskal
  17. import scikit_posthocs as sp
  18. pd.set_option("display.max_rows", None)
  19. pd.set_option("display.max_columns", None)
  20. pd.set_option("display.width", None)
  21. pd.set_option("display.max_colwidth", None)
  22. num_components = 3
  23. joint = False # joint or conditional probability?
  24. pre_path = f"figures_pre_test_{num_components}/neuron_contributions"
  25. ep_path = f"figures_egestive_priming_{num_components}/neuron_contributions"
  26. post_path = f"figures_post_test_{num_components}/neuron_contributions"
  27. pre_path_pkl = f"figures_pre_test_{num_components}/pkl_files"
  28. ep_path_pkl = f"figures_egestive_priming_{num_components}/pkl_files"
  29. post_path_pkl = f"figures_post_test_{num_components}/pkl_files"
  30. df_mapping = pd.read_csv("data_folder/identified_neurons.csv", header=None)
  31. df_mapping.columns = ["experiment", "vsd_mapping", "neuron_name"]
  32. contribution_array = []
  33. for animal in range(1, 13):
  34. if animal in [5, 6, 9]:
  35. continue
  36. exp_pre = pd.read_csv(f'{pre_path}/animal_{animal}_contributions.csv')
  37. exp_ep = pd.read_csv(f'{ep_path}/animal_{animal}_contributions.csv')
  38. exp_post = pd.read_csv(f'{post_path}/animal_{animal}_contributions.csv')
  39. pre_vals = exp_pre.drop(columns=['Neuron']).values.flatten()
  40. ep_vals = exp_ep.drop(columns=['Neuron']).values.flatten()
  41. post_vals = exp_post.drop(columns=['Neuron']).values.flatten()
  42. contribution_array.extend(pre_vals)
  43. contribution_array.extend(ep_vals)
  44. contribution_array.extend(post_vals)
  45. contribution_array = np.array(contribution_array)
  46. thr = np.percentile(contribution_array, 75)
  47. print("Threshold:", thr)
  48. print('\n')
  49. if num_components == 2:
  50. required_cols = ['Neuron', 'Group_1', 'Group_2']
  51. elif num_components == 3:
  52. required_cols = ['Neuron', 'Group_1', 'Group_2', 'Group_3']
  53. elif num_components == 4:
  54. required_cols = ['Neuron', 'Group_1', 'Group_2', 'Group_3', 'Group_4']
  55. elif num_components == 5:
  56. required_cols = ['Neuron', 'Group_1', 'Group_2', 'Group_3', 'Group_4', 'Group_5']
  57. # Figure 1: Change in neuron contribution across conditions, per group
  58. print('\n Change in neuron contribution across conditions')
  59. all_group_dfs = {}
  60. for animal in range(1, 13):
  61. if animal in [5, 6, 9]:
  62. continue
  63. exp_pre = pd.read_csv(f'{pre_path}/animal_{animal}_contributions.csv')
  64. exp_ep = pd.read_csv(f'{ep_path}/animal_{animal}_contributions.csv')
  65. exp_post = pd.read_csv(f'{post_path}/animal_{animal}_contributions.csv')
  66. assert(set(exp_pre['Neuron']) == set(exp_ep['Neuron']) == set(exp_post['Neuron'])), f"Neuron mismatch in animal {animal}"
  67. group_cols = [col for col in exp_pre.columns if col != 'Neuron' and col in exp_ep.columns and col in exp_post.columns]
  68. print(f"\n Animal {animal}, group_cols: {group_cols}")
  69. all_neurons = set(exp_pre['Neuron'])
  70. print(f" Total neurons: {len(all_neurons)}")
  71. for group in group_cols: # for each group that exists in all three conditions
  72. rows = []
  73. for neuron in all_neurons: # for each neuron
  74. pre_val = exp_pre.loc[exp_pre['Neuron'] == neuron, group].values[0]
  75. ep_val = exp_ep.loc[exp_ep['Neuron'] == neuron, group].values[0]
  76. post_val = exp_post.loc[exp_post['Neuron'] == neuron, group].values[0]
  77. values = [pre_val, ep_val, post_val]
  78. if pre_val == 0 and ep_val == 0 and post_val == 0: # alternatiely, filter using threshold thr
  79. continue
  80. # print(f"{group}, Neuron {neuron}, Pre: {pre_val:.2f}, EP: {ep_val:.2f}, Post: {post_val:.2f}")
  81. rows.append({
  82. 'Neuron': f"{neuron}_{animal}", # make neuron unique per animal
  83. 'Pre': pre_val,
  84. 'EP': ep_val,
  85. 'Post': post_val
  86. })
  87. df_group = pd.DataFrame(rows, columns=['Neuron', 'Pre', 'EP', 'Post'])
  88. if group not in all_group_dfs:
  89. all_group_dfs[group] = {}
  90. all_group_dfs[group][animal] = df_group
  91. # print(all_group_dfs['Group_1'][1])
  92. # Statistics: repeated measures ANOVA
  93. conditions = ['Pre', 'EP', 'Post']
  94. for group, animals in all_group_dfs.items():
  95. df_all = pd.concat(animals.values(), ignore_index=True)
  96. df_melt = pd.melt(df_all.reset_index(), id_vars=['Neuron'], value_vars=conditions, var_name='Stage', value_name='Contribution')
  97. n_neurons = df_melt['Neuron'].nunique()
  98. anova_results = pg.rm_anova(data=df_melt, dv='Contribution', within='Stage', subject='Neuron', detailed=True)
  99. print(f"\nGroup: {group}")
  100. print(f"n = {n_neurons} neurons")
  101. print(anova_results)
  102. if anova_results['p-unc'].iloc[0] < 0.05:
  103. posthoc = pg.pairwise_tests(data=df_melt, dv='Contribution', within='Stage', subject='Neuron', padjust='holm') # parametric = True by default
  104. print("\nPost-hoc pairwise comparisons:")
  105. print(posthoc)
  106. # Statistics: Kruskal-Wallis test (non-parametric)
  107. conditions = ['Pre', 'EP', 'Post']
  108. for group, animals in all_group_dfs.items():
  109. df_all = pd.concat(animals.values(), ignore_index=True)
  110. df_melt = pd.melt(df_all.reset_index(), id_vars=['Neuron'], value_vars=conditions, var_name='Stage', value_name='Contribution')
  111. n_neurons = df_melt['Neuron'].nunique()
  112. n_animals = len(animals)
  113. print(f"n = {n_neurons} neurons across {n_animals} experiments")
  114. print(f"\nGroup: {group}")
  115. print(f"n = {n_neurons} neurons")
  116. pre_vals = df_all['Pre'].values
  117. ep_vals = df_all['EP'].values
  118. post_vals = df_all['Post'].values
  119. stat, p_val = kruskal(pre_vals, ep_vals, post_vals)
  120. print(f"Kruskal-Wallis: H = {stat:.4f}, p = {p_val:.6f}")
  121. if p_val < 0.05:
  122. posthoc = sp.posthoc_dunn(df_melt, val_col='Contribution', group_col='Stage', p_adjust='holm-sidak')
  123. print("\nPost-hoc Dunn's test:")
  124. print(posthoc)
  125. # Sanity check means
  126. """
  127. # part 1 (post filtering)
  128. for group, animals in all_group_dfs.items():
  129. df_all = pd.concat(animals.values(), ignore_index=True)
  130. print(f"{group}: Pre={df_all['Pre'].mean():.4f}, EP={df_all['EP'].mean():.4f}, Post={df_all['Post'].mean():.4f}")
  131. # part 2 (original)
  132. group_vals = defaultdict(lambda: {'Pre': [], 'EP': [], 'Post': []})
  133. for animal in range(1, 13):
  134. if animal in [5, 6, 9]:
  135. continue
  136. exp_pre = pd.read_csv(f'{pre_path}/animal_{animal}_contributions.csv')
  137. exp_ep = pd.read_csv(f'{ep_path}/animal_{animal}_contributions.csv')
  138. exp_post = pd.read_csv(f'{post_path}/animal_{animal}_contributions.csv')
  139. group_cols = [col for col in exp_pre.columns if col != 'Neuron' and col in exp_ep.columns and col in exp_post.columns]
  140. for g in group_cols:
  141. group_vals[g]['Pre'].extend(exp_pre[g].values)
  142. group_vals[g]['EP'].extend(exp_ep[g].values)
  143. group_vals[g]['Post'].extend(exp_post[g].values)
  144. for g, vals in group_vals.items():
  145. print(f"{g}: Pre={np.mean(vals['Pre']):.4f}, EP={np.mean(vals['EP']):.4f}, Post={np.mean(vals['Post']):.4f}")
  146. """
  147. # Plotting
  148. fig, axes = plt.subplots(1, num_components, figsize=(15, 5), sharey=False)
  149. for ax, (group, animals) in zip(axes, all_group_dfs.items()):
  150. df_all = pd.concat(animals.values(), ignore_index=True)
  151. df_melt = df_all.melt(id_vars='Neuron', value_vars=conditions, var_name='Stage', value_name='Contribution')
  152. df_melt['Stage'] = pd.Categorical(df_melt['Stage'], categories=conditions, ordered=True)
  153. sns.boxplot(data=df_melt, x='Stage', y='Contribution', ax=ax, showfliers=False, color='lightgray', width=0.5, showmeans=True, meanprops=dict(marker='D', markerfacecolor='red', markeredgecolor='red', markersize=5))
  154. ax.set_title(group)
  155. ax.set_xlabel('')
  156. ax.set_ylabel('Contribution')
  157. ax.spines['top'].set_visible(False)
  158. ax.spines['right'].set_visible(False)
  159. if group == 'Group_1':
  160. ax.set_ylim(0, 1)
  161. ax.set_yticks([0, 0.5, 1])
  162. if group == 'Group_2':
  163. ax.set_ylim(0, 4)
  164. ax.set_yticks([0, 2, 4])
  165. if group == 'Group_3':
  166. ax.set_ylim(0, 1.5)
  167. ax.set_yticks([0, 0.75, 1.5])
  168. fig.tight_layout()
  169. plt.savefig('figures_paper/neuronal_delta.pdf', dpi=300, bbox_inches='tight')
  170. plt.show()
  171. # Figure 2: Transition probabilities of neurons between groups across conditions
  172. transition_pre_ep = defaultdict(int)
  173. transition_ep_post = defaultdict(int)
  174. from_counts_pre = defaultdict(int)
  175. from_counts_ep = defaultdict(int)
  176. to_counts_ep = defaultdict(int)
  177. to_counts_post = defaultdict(int)
  178. total_neurons = 0
  179. for animal in range(1, 13):
  180. if animal in [5, 6, 9]:
  181. continue
  182. exp_pre = pd.read_csv(f'{pre_path}/animal_{animal}_contributions.csv')
  183. exp_ep = pd.read_csv(f'{ep_path}/animal_{animal}_contributions.csv')
  184. exp_post = pd.read_csv(f'{post_path}/animal_{animal}_contributions.csv')
  185. all_neurons = set(exp_pre['Neuron']) # same neurons across all three conditions
  186. pre_cols = [c for c in exp_pre.columns if c != 'Neuron']
  187. ep_cols = [c for c in exp_ep.columns if c != 'Neuron']
  188. post_cols = [c for c in exp_post.columns if c != 'Neuron']
  189. # group_cols = [col for col in exp_pre.columns if col != 'Neuron' and col in exp_ep.columns and col in exp_post.columns]
  190. for neuron in all_neurons:
  191. total_neurons += 1
  192. pre_val = exp_pre.loc[exp_pre['Neuron'] == neuron, pre_cols].values[0]
  193. if np.nanmax(pre_val) < thr:
  194. g_pre = 'Group_0'
  195. else:
  196. g_pre = pre_cols[np.nanargmax(pre_val)]
  197. ep_val = exp_ep.loc[exp_ep['Neuron'] == neuron, ep_cols].values[0]
  198. if np.nanmax(ep_val) < thr:
  199. g_ep = 'Group_0'
  200. else:
  201. g_ep = ep_cols[np.nanargmax(ep_val)]
  202. post_val = exp_post.loc[exp_post['Neuron'] == neuron, post_cols].values[0]
  203. if np.nanmax(post_val) < thr:
  204. g_post = 'Group_0'
  205. else:
  206. g_post = post_cols[np.nanargmax(post_val)]
  207. # Count FROM occurrences
  208. from_counts_pre[g_pre] += 1
  209. from_counts_ep[g_ep] += 1
  210. transition_pre_ep[(g_pre, g_ep)] += 1 # Pre → EP (only one edge per neuron)
  211. transition_ep_post[(g_ep, g_post)] += 1 # EP → Post (only one edge per neuron)
  212. to_counts_ep[g_ep] += 1
  213. to_counts_post[g_post] += 1
  214. print(total_neurons)
  215. # Build probability DataFrames
  216. pre_ep_df = pd.DataFrame([(k[0], k[1], v / from_counts_pre[k[0]]) for k, v in transition_pre_ep.items()], columns=["From", "To", "Prob"])
  217. ep_post_df = pd.DataFrame([(k[0], k[1], v / from_counts_ep[k[0]]) for k, v in transition_ep_post.items()], columns=["From", "To", "Prob"])
  218. # joint probability distribution
  219. total_pre = sum(transition_pre_ep.values())
  220. pre_ep_joint_df = pd.DataFrame([(k[0], k[1], v / total_pre) for k, v in transition_pre_ep.items()], columns=["From", "To", "Prob"])
  221. total_ep = sum(transition_ep_post.values())
  222. ep_post_joint_df = pd.DataFrame([(k[0], k[1], v / total_ep) for k, v in transition_ep_post.items()], columns=["From", "To", "Prob"])
  223. if joint == True:
  224. pre_ep_df = pre_ep_joint_df
  225. ep_post_df = ep_post_joint_df
  226. print("\nPre → EP transition probabilities:")
  227. print(pre_ep_df.sort_values("Prob", ascending=False))
  228. print("\nEP → Post transition probabilities:")
  229. print(ep_post_df.sort_values("Prob", ascending=False))
  230. # Build figure
  231. if num_components == 2:
  232. groups = ["Group_0", "Group_1", "Group_2"]
  233. elif num_components == 3:
  234. groups = ["Group_0", "Group_1", "Group_2", "Group_3"]
  235. elif num_components == 4:
  236. groups = ["Group_0", "Group_1", "Group_2", "Group_3", "Group_4"]
  237. elif num_components == 5:
  238. groups = ["Group_0", "Group_1", "Group_2", "Group_3", "Group_4", "Group_5"]
  239. G = nx.DiGraph()
  240. for stage in conditions:
  241. for g in groups:
  242. G.add_node(f"{stage}_{g}", stage=stage, group=g)
  243. for _, row in pre_ep_df.iterrows():
  244. G.add_edge(f"Pre_{row['From']}", f"EP_{row['To']}", weight=row["Prob"])
  245. for _, row in ep_post_df.iterrows():
  246. G.add_edge(f"EP_{row['From']}", f"Post_{row['To']}", weight=row["Prob"])
  247. pos = {}
  248. for j, stage in enumerate(conditions): # x-axis: stage
  249. for i, g in enumerate(groups): # y-axis: group
  250. pos[f"{stage}_{g}"] = (j, -i) # flip y for nicer layout
  251. node_colors = ['#FFC0C0'] * len(G.nodes())
  252. # node_labels = {node: ("Not\nRecruited" if data.get("group") == "Group_0" else data.get("group", node)) for node, data in G.nodes(data=True)}
  253. fig, ax = plt.subplots(figsize=(10, 8))
  254. """# compute node sizes
  255. scale_factor = 20
  256. node_sizes = {}
  257. if joint:
  258. for g in groups:
  259. # to_counts_ep = from_counts_ep
  260. node_sizes[f"Pre_{g}"] = from_counts_pre[g] * scale_factor
  261. node_sizes[f"EP_{g}"] = from_counts_ep[g] * scale_factor
  262. node_sizes[f"Post_{g}"] = to_counts_post[g] * scale_factor
  263. else:
  264. for n in G.nodes():
  265. node_sizes[n] = 2200
  266. """
  267. # compute node sizes
  268. scale_factor = 20
  269. node_sizes = {}
  270. node_counts = {}
  271. if joint:
  272. for g in groups:
  273. node_counts[f"Pre_{g}"] = from_counts_pre[g]
  274. node_counts[f"EP_{g}"] = from_counts_ep[g]
  275. node_counts[f"Post_{g}"] = to_counts_post[g]
  276. node_sizes[f"Pre_{g}"] = from_counts_pre[g] * scale_factor
  277. node_sizes[f"EP_{g}"] = from_counts_ep[g] * scale_factor
  278. node_sizes[f"Post_{g}"] = to_counts_post[g] * scale_factor
  279. else:
  280. for n in G.nodes():
  281. node_sizes[n] = 2200
  282. node_labels = {
  283. node: (
  284. ("Not\nRecruited" if data.get("group") == "Group_0" else data.get("group", node))
  285. + (f"\nn={node_counts[node]}" if node in node_counts else "")
  286. )
  287. for node, data in G.nodes(data=True)
  288. }
  289. group0_nodes = [n for n, d in G.nodes(data=True) if d.get("group") == "Group_0"] # Non-Group_0 nodes (filled pink)
  290. other_nodes = [n for n, d in G.nodes(data=True) if d.get("group") != "Group_0"]
  291. nx.draw_networkx_nodes(G, pos, nodelist=other_nodes, node_size=[node_sizes[n] for n in other_nodes], node_color="#FFC0C0", edgecolors="#FFC0C0", ax=ax)
  292. nx.draw_networkx_nodes(G, pos, nodelist=group0_nodes, node_size=[node_sizes[n] for n in group0_nodes], node_color="white", edgecolors="#FFC0C0", linewidths=2.5, ax=ax)
  293. nx.draw_networkx_labels(G, pos, labels=node_labels, font_size=10, font_weight="bold")
  294. edges = G.edges()
  295. # scale weight and color based on prob
  296. if not joint:
  297. norm = mcolors.Normalize(vmin=0, vmax=1)
  298. if joint:
  299. norm = mcolors.Normalize(vmin=0, vmax=0.3)
  300. cmap = mpl.colormaps["Reds"]
  301. edges = list(G.edges(data=True)) # get edges with data
  302. edges_sorted = sorted(edges, key=lambda x: x[2]['weight']) # sort edges from lightest to heaviest
  303. edge_list = [(u, v) for u, v, d in edges_sorted] # extract edge list, widths, and colors in sorted order
  304. scale = 50 if joint else 10
  305. weights = [d['weight'] * scale for u, v, d in edges_sorted] # 10 for conditional
  306. colors = [cmap(norm(d['weight'])) for u, v, d in edges_sorted]
  307. nx.draw_networkx_edges(G, pos, edgelist=edge_list, width=weights, edge_color=colors, arrows=False, ax=ax)
  308. # colorbar
  309. sm = mpl.cm.ScalarMappable(cmap=cmap, norm=norm)
  310. sm.set_array([])
  311. cbar = fig.colorbar(sm, ax=ax, fraction=0.03, pad=0.04)
  312. cbar.set_label("Transition Probability", fontsize=12)
  313. if not joint:
  314. cbar.set_ticks([0, 0.25, 0.5, 0.75, 1])
  315. cbar.set_ticklabels([0, 0.25, 0.5, 0.75, 1])
  316. if joint:
  317. cbar.set_ticks([0, 0.15, 0.3])
  318. cbar.set_ticklabels([0, 0.15, 0.3])
  319. ax.margins(0.2)
  320. ax.set_title("Neuron Transition Probabilities", fontsize=14, weight="bold")
  321. ax.axis("off")
  322. if joint:
  323. plt.savefig('figures_paper/neuronal_transitions_joint.pdf', dpi=300, bbox_inches='tight')
  324. else:
  325. plt.savefig('figures_paper/neuronal_transitions_conditional.pdf', dpi=300, bbox_inches='tight')
  326. plt.show()
  327. # Figure 3: Transitions of identified neurons
  328. total_neurons = 0
  329. records = []
  330. for animal in range(1, 13):
  331. if animal in [5, 6, 9]:
  332. continue
  333. exp_pre = pd.read_csv(f'{pre_path}/animal_{animal}_contributions.csv')
  334. exp_ep = pd.read_csv(f'{ep_path}/animal_{animal}_contributions.csv')
  335. exp_post = pd.read_csv(f'{post_path}/animal_{animal}_contributions.csv')
  336. df_mapping_exp = df_mapping[df_mapping['experiment'] == animal]
  337. pre_cols = [c for c in exp_pre.columns if c != 'Neuron']
  338. ep_cols = [c for c in exp_ep.columns if c != 'Neuron']
  339. post_cols = [c for c in exp_post.columns if c != 'Neuron']
  340. all_neurons = set(exp_pre['Neuron']) # same neurons across all three conditions
  341. for neuron in all_neurons:
  342. if neuron not in df_mapping_exp['vsd_mapping'].values:
  343. continue
  344. neuron_name = df_mapping_exp.loc[df_mapping_exp['vsd_mapping'] == neuron, 'neuron_name'].values[0]
  345. total_neurons += 1
  346. pre_val = exp_pre.loc[exp_pre['Neuron'] == neuron, pre_cols].values[0]
  347. if np.nanmax(pre_val) < thr:
  348. g_pre = "Group_0"
  349. else:
  350. g_pre = pre_cols[np.nanargmax(pre_val)]
  351. ep_val = exp_ep.loc[exp_ep['Neuron'] == neuron, ep_cols].values[0]
  352. if np.nanmax(ep_val) < thr:
  353. g_ep = "Group_0"
  354. else:
  355. g_ep = ep_cols[np.nanargmax(ep_val)]
  356. post_val = exp_post.loc[exp_post['Neuron'] == neuron, post_cols].values[0]
  357. if np.nanmax(post_val) < thr:
  358. g_post = "Group_0"
  359. else:
  360. g_post = post_cols[np.nanargmax(post_val)]
  361. first_transition = f"{g_pre}-{g_ep}"
  362. second_transition = f"{g_ep}-{g_post}"
  363. records.append({"animal": animal, "neuron_name": neuron_name, "neuron": neuron, "Pre": g_pre, "EP": g_ep, "Post": g_post, "first_transition": first_transition, "second_transition": second_transition})
  364. df_transitions = pd.DataFrame(records)
  365. print("Total neurons:", total_neurons)
  366. print(df_transitions.head())
  367. # group by first transition
  368. first_transition_counts = (df_transitions.groupby(["neuron_name", "first_transition"]).size().reset_index(name="count"))
  369. print('\n First transition')
  370. print(first_transition_counts)
  371. # group by second transition
  372. second_transition_counts = (df_transitions.groupby(["neuron_name", "second_transition"]).size().reset_index(name="count"))
  373. print('\n Second transition')
  374. print(second_transition_counts)
  375. def to_matrix(df, groups):
  376. mat = pd.DataFrame(0, index=groups, columns=groups, dtype=float)
  377. for _, row in df.iterrows():
  378. mat.loc[row['From'], row['To']] = row['Prob']
  379. return mat
  380. def neuron_p_value(pop_df, neuron_df, total_transitions, n_sim=10000):
  381. np.random.seed(111) # for reproducibility
  382. groups = sorted(set(pop_df['From']).union(pop_df['To']).union(neuron_df['From']).union(neuron_df['To']))
  383. pop_mat = to_matrix(pop_df, groups).values.flatten()
  384. neuron_mat = to_matrix(neuron_df, groups).values.flatten()
  385. n_obs = total_transitions
  386. # print(n_obs)
  387. # print(f"pop_mat sum: {pop_mat.sum():.6f}")
  388. # print(f"neuron_mat sum: {neuron_mat.sum():.6f}")
  389. # print(f"neuron_df Prob sum: {neuron_df['Prob'].sum():.6f}")
  390. # print(f"Expected neuron_dist sum should be: {neuron_df['Prob'].sum():.6f}")
  391. # print("-" * 40)
  392. assert np.isclose(pop_mat.sum(), 1.0), f"pop_mat sums to {pop_mat.sum()}"
  393. obs_js = jensenshannon(neuron_mat, pop_mat) # Test statistic: JS divergence
  394. # print(neuron_mat)
  395. # print(pop_mat)
  396. # Monte Carlo null distribution
  397. sim_js = []
  398. for _ in range(n_sim):
  399. sample = np.random.multinomial(n_obs, pop_mat)
  400. sample_dist = sample / n_obs
  401. sim_js.append(jensenshannon(sample_dist, pop_mat))
  402. p_val = np.mean(np.array(sim_js) >= obs_js)
  403. return obs_js, p_val
  404. def plot_neuron_transition_graph(df_transitions, neuron, num_components, ax=None):
  405. df_neuron = df_transitions[df_transitions["neuron_name"] == neuron]
  406. print("\n", neuron)
  407. # Count transitions
  408. transition_pre_ep = {}
  409. transition_ep_post = {}
  410. for _, row in df_neuron.iterrows():
  411. if pd.notna(row["first_transition"]):
  412. src, dst = row["first_transition"].split("-")
  413. transition_pre_ep[(src, dst)] = transition_pre_ep.get((src, dst), 0) + 1
  414. if pd.notna(row["second_transition"]):
  415. src, dst = row["second_transition"].split("-")
  416. transition_ep_post[(src, dst)] = transition_ep_post.get((src, dst), 0) + 1
  417. total_pre_ep = sum(transition_pre_ep.values())
  418. total_ep_post = sum(transition_ep_post.values())
  419. # Probabilities
  420. pre_ep_df_neuron = pd.DataFrame([(k[0], k[1], v / total_pre_ep) for k, v in transition_pre_ep.items()], columns=["From", "To", "Prob"])
  421. ep_post_df_neuron = pd.DataFrame([(k[0], k[1], v / total_ep_post) for k, v in transition_ep_post.items()], columns=["From", "To", "Prob"])
  422. num_comparison = 28 # 14 neurons, 2 transitions
  423. obs_js, p_val = neuron_p_value(pre_ep_joint_df, pre_ep_df_neuron, total_pre_ep)
  424. p_val_adj = min(p_val*num_comparison, 1.0)
  425. print(f"Pre -> EP, Observed JS: {obs_js}, p-value: {p_val_adj}")
  426. obs_js, p_val = neuron_p_value(ep_post_joint_df, ep_post_df_neuron, total_ep_post)
  427. p_val_adj = min(p_val*num_comparison, 1.0)
  428. print(f"EP -> Post, Observed JS: {obs_js}, p-value: {p_val_adj}")
  429. # Define groups/stages
  430. groups = [f"Group_{i}" for i in range(0, num_components+1)]
  431. stages = ["Pre", "EP", "Post"]
  432. # Build graph
  433. G = nx.DiGraph()
  434. for stage in stages:
  435. for g in groups:
  436. G.add_node(f"{stage}_{g}", stage=stage, group=g)
  437. for _, row in pre_ep_df_neuron.iterrows():
  438. G.add_edge(f"Pre_{row['From']}", f"EP_{row['To']}", weight=row["Prob"])
  439. for _, row in ep_post_df_neuron.iterrows():
  440. G.add_edge(f"EP_{row['From']}", f"Post_{row['To']}", weight=row["Prob"])
  441. # Fixed layout
  442. pos = {}
  443. for j, stage in enumerate(stages): # x-axis
  444. for i, g in enumerate(groups): # y-axis
  445. pos[f"{stage}_{g}"] = (j * 0.25, -i * 0.25)
  446. # Determine node colors based on connectivity
  447. node_colors = []
  448. for node in G.nodes():
  449. if G.in_edges(node) or G.out_edges(node):
  450. node_colors.append("#FFC0C0")
  451. else:
  452. node_colors.append("#A9A9A9")
  453. node_labels = {node: ("Not\nRecruited" if data.get("group") == "Group_0" else data.get("group", node)) for node, data in G.nodes(data=True)}
  454. if ax is None:
  455. fig, ax = plt.subplots(figsize=(6, 4))
  456. # nx.draw_networkx_nodes(G, pos, node_size=1500, node_color=node_colors, ax=ax)
  457. other_nodes = [n for n, d in G.nodes(data=True) if d.get("group") != "Group_0"]
  458. nx.draw_networkx_nodes(G, pos, nodelist=other_nodes, node_size=1000, node_color=[node_colors[list(G.nodes()).index(n)] for n in other_nodes], ax=ax)
  459. group0_nodes = [n for n, d in G.nodes(data=True) if d.get("group") == "Group_0"]
  460. nx.draw_networkx_nodes(G, pos, nodelist=group0_nodes, node_size=1000, node_color="white", edgecolors=[node_colors[list(G.nodes()).index(n)] for n in group0_nodes], linewidths=2.5, ax=ax)
  461. nx.draw_networkx_labels(G, pos, labels=node_labels, font_size=6, font_weight="bold", ax=ax)
  462. # Edge weights/colors
  463. norm = mcolors.Normalize(vmin=0, vmax=1)
  464. cmap = mpl.colormaps["Reds"]
  465. edges_sorted = sorted(G.edges(data=True), key=lambda x: x[2]['weight'])
  466. edge_list = [(u, v) for u, v, d in edges_sorted]
  467. weights = [d['weight'] * 5 for u, v, d in edges_sorted]
  468. colors = [cmap(norm(d['weight'])) for u, v, d in edges_sorted]
  469. nx.draw_networkx_edges(G, pos, edgelist=edge_list, width=weights, edge_color=colors, arrows=False, ax=ax)
  470. ax.set_title(f"Neuron {neuron}", fontsize=10, weight="bold")
  471. ax.axis("off")
  472. ax.set_xlim(-0.1, 0.7)
  473. ax.set_ylim(-1.2, 0.1)
  474. ax.set_aspect("equal")
  475. ax.margins(0)
  476. ax.autoscale(False)
  477. return ax
  478. unique_neurons = df_transitions["neuron_name"].unique()
  479. nrows = (len(unique_neurons) + 2) // 3
  480. fig, axes = plt.subplots(nrows=nrows, ncols=3, figsize=(8, 5*nrows))
  481. axes = axes.flatten()
  482. for ax, neuron in zip(axes, unique_neurons):
  483. ax = plot_neuron_transition_graph(df_transitions, neuron, num_components, ax=ax)
  484. for ax in axes[len(unique_neurons):]:
  485. ax.axis("off")
  486. plt.tight_layout()
  487. plt.savefig('figures_paper/neuronal_transitions_identified.pdf', dpi=300, bbox_inches='tight')
  488. plt.show()

figure_neuron_factors.py at commit 7086df8, no license · at the source

Overview

Authors: Archit Gupta1,2, Curtis L Neveu1,2, Elizabeth C Cropper3, John H Byrne1,2
  1. Department of Neurobiology and Anatomy, W. M. Keck Center for the Neurobiology of Learning and Memory, McGovern Medical School at The University of Texas Health Science Center at Houston, Houston, TX 77030, USA
  2. The University of Texas MD Anderson UTHealth Houston Graduate School of Biomedical Sciences, Houston, TX 77030, USA
  3. Department of Neuroscience and Friedman Brain Institute, Mount Sinai School of Medicine, New York, NY 10029, USA
Journal: iScience, volume 29, issue 9, article 117375
Dates: received 21 May 2026; accepted 13 August 2026; published online 29 August 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1016/j.isci.2026.117375 · PMID 42713042 · PMCID PMC13551948 · OpenAlex W7204648160
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: other (organism), systems (subfield)
Methods: Spectral & time-frequency, Preprocessing, Statistics, Smoothing, state filtering, decompositions, Machine learning, fMRI & imaging, Single-unit activity, calcium imaging
Keywords: motor learning, tensor component analysis, Aplysia
Topic: Action Observation and Synchronization (Social Psychology, Psychology), according to OpenAlex
Funding: NINDS NIH HHS (R01 NS101356, R01 NS118606, RF1 NS118606)
Citations: not cited yet (Europe PMC); 81 references in the paper

Abstract

Repetition priming is a ubiquitous form of implicit learning in which repetition leads to increased performance. We investigated egestive priming of feeding behavior in Aplysia californica using an isolated cerebral and buccal ganglia preparation and recorded the activities of numerous neurons by using voltage-sensitive dye (VSD) imaging. The high-dimensional VSD data were subjected to tensor component analysis, which characterized three neuronal ensembles with distinct spatial and temporal domains. The activities of these ensembles corresponded to three motor phases: radula protraction, early retraction, and late retraction. Priming resulted in repetition enhancement characterized by increased neural activity during early retraction (i.e., gain modulation), as well as a more rapid transition into the late retraction ensemble. In addition, priming recruited previously inactive neurons into the protraction and early retraction ensembles. These results indicate that priming-induced neural plasticity is multifaceted, operating across multiple axes of plasticity, and each axis modifies specific subsets of ensembles.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repositories

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

Zenodo 21875573

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: “Data and code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (26 files), Matplotlib (19 files), SciPy (17 files), pandas (12 files), seaborn (7 files), Pingouin (6 files), h5py (5 files), OpenCV (4 files), statsmodels (4 files), scikit-learn (2 files), scikit-posthocs (2 files), NetworkX (1 file), PyTorch (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
29 files

Byrne-Lab/tca_egestive_priming_2025

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 7086df89cf190032630f282d4ab9d9d36b5c46eb, 4 August 2026
Languages: Python (24), Jupyter (2), MATLAB (2)
Size: 91 files, 28 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: README, environment (requirements.txt), tests, 2 notebooks
Not found: license file, CITATION.cff, continuous integration, documentation
Tools: NumPy (26 files), Matplotlib (19 files), SciPy (17 files), pandas (12 files), seaborn (7 files), Pingouin (6 files), h5py (5 files), OpenCV (4 files), statsmodels (4 files), scikit-learn (2 files), scikit-posthocs (2 files), NetworkX (1 file), PyTorch (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
29 files

Byrne-Lab/VSD_CFE_analysis

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 208d3fc4cfa99c10be4e91c8c336a2911a493a80, 25 September 2026
Languages: MATLAB (30)
Size: 52 files, 30 scripts
Software Heritage: not archived
Found in: the text, “Analysis of VSD imaging data”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
30 files

The paper's code and data availability statement is in the Data section.

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:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 86 scripts, each with its path and the digest of its content;
  • 15 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

No dataset and no data link were found in the paper.

Data and code availability

Data and original code has been deposited at GitHub and is publicly available at Zenodo: https://doi.org/10.5281/zenodo.21875573 (also available on GitHub: https://github.com/Byrne-Lab/tca_egestive_priming_2025). Any additional information would be available from lead contact upon request.

Reproduced under the paper's license (CC BY-NC), 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 2, 28 September 2026

  • Authors: added Archit Gupta (0009-0001-5152-3421); John H Byrne (0000-0001-7947-5890); removed Archit Gupta; John H Byrne

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 3 keywords, 1 funder, 80 references.

Cite

This paper

Gupta, A., Neveu, C. L., Cropper, E. C., & Byrne, J. H. (2026). Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions. iScience, 29(9), 117375. https://doi.org/10.1016/j.isci.2026.117375

BibTeX

@article{gupta2026motor,
author = {Gupta, Archit and Neveu, Curtis L and Cropper, Elizabeth C and Byrne, John H},
title = {{Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions}},
journal = {iScience},
year = {2026},
month = aug,
volume = {29},
number = {9},
pages = {117375},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/j.isci.2026.117375},
url = {https://doi.org/10.1016/j.isci.2026.117375},
pmid = {42713042},
pmcid = {PMC13551948}
}

RIS

TY - JOUR
AU - Gupta, Archit
AU - Neveu, Curtis L
AU - Cropper, Elizabeth C
AU - Byrne, John H
TI - Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/08/29
VL - 29
IS - 9
SP - 117375
SN - 2589-0042
PB - Elsevier
DO - 10.1016/j.isci.2026.117375
UR - https://doi.org/10.1016/j.isci.2026.117375
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.isci.2026.117375",
"type": "article-journal",
"title": "Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions",
"container-title": "iScience",
"author": [
{
"family": "Gupta",
"given": "Archit"
},
{
"family": "Neveu",
"given": "Curtis L"
},
{
"family": "Cropper",
"given": "Elizabeth C"
},
{
"family": "Byrne",
"given": "John H"
}
],
"container-title-short": "iScience",
"volume": "29",
"issue": "9",
"page": "117375",
"DOI": "10.1016/j.isci.2026.117375",
"PMID": "42713042",
"PMCID": "PMC13551948",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.isci.2026.117375",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
29
]
]
}
}

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.3389/fncom.2026.1771884 [code]
NEURONpyxl: fast, flexible, Python-integrated simulation of biophysical neural networks with complex plastic synapses.
Journal: Frontiers in computational neuroscience
In common: pandas, SciPy, Matplotlib, 1 other tool, other, 11 references
[2] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: Pingouin, Optimization Toolbox, NetworkX, 13 other tools, systems
[3] doi:10.1016/j.neuron.2026.03.034 [code]
Dentate gyrus interneurons modulate winner-take-all network dynamics in freely behaving mice.
Journal: Neuron
In common: Optimization Toolbox, NetworkX, h5py, 10 other tools, systems
[4] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Optimization Toolbox, NetworkX, OpenCV, 10 other tools
[5] doi:10.1038/s41593-026-02362-5 [code]
Replay of procedural memory is independent of the hippocampus.
Journal: Nature neuroscience
In common: scikit-posthocs, Pingouin, h5py, 9 other tools
[6] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: Pingouin, NetworkX, OpenCV, 9 other tools, systems
[7] doi:10.1038/s42003-026-10276-y [code]
The cellular correlates and adolescent reorganisation of cortical myelination networks in the common marmoset.
Journal: Communications biology
In common: Pingouin, Optimization Toolbox, Image Processing Toolbox, 9 other tools
[8] doi:10.1371/journal.pbio.3003831 [code]
Disinhibitory signaling enables flexible coding of top-down information in cortical networks.
Journal: PLoS biology
In common: scikit-posthocs, NetworkX, h5py, 8 other tools, systems
[9] doi:10.1016/j.nicl.2026.104012 [code]
Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.
Journal: NeuroImage. Clinical
In common: Pingouin, NetworkX, Image Processing Toolbox, 9 other tools
[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: scikit-posthocs, Pingouin, OpenCV, 8 other tools

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.