OSCR

Entorhinal cortex represents task-relevant remote locations independently of CA1.

Code ↔ Paper

19 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 19 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › Neural data preprocessing ↔ qualityMetrics/eaj_qualityParamValues.m, the whole file · a weak match · score 0.87 · BombCell, spatial decay, noise ratio, spikes missing, halfwidth, violations
  2. [2] § Methods › LFP analysis ↔ src/ripple_detection/literature_methods.py, lines 2506–2540 · score 0.83 · selected CA1 channel, Hilbert envelope, equiripple filtered, 125 Hz, trace, SWRs
  3. [3] § Methods › Neural data preprocessing ↔ qualityMetrics/bc_qualityParamValues.m, the whole file · a weak match · score 0.81 · spatial decay, noise ratio, spikes missing, BombCell, violations, global
  4. [4] § Methods › Single unit analysis ↔ Figures_published_edition.ipynb, lines 2248–2315 · score 0.80 · Spatial aperiodic cells, Border scores, Speed scores, angle, cm, locations
  5. [5] § Methods › Single unit analysis ↔ Yggdrasil/Spikes/spikes.py, lines 394–434 · score 0.77 · co fire, CA1 spike, MEC spike, 1–10 ms, monosynaptic, shuffled
  6. [6] § Results › CA1 decouples from MEC during nonlocal coding ↔ Yggdrasil/Spikes/spikes.py, lines 394–434 · score 0.68 · co fired, CA1 spike, MEC spike, 1–10 ms, shuffle, summed
  7. [7] § Methods › Position tracking ↔ Yggdrasil/Position/arena.py, lines 438–558 · score 0.67 · arena boundaries, track graph, videos, connected, behavior, edges
  8. [8] § Methods › Electrophysiology ↔ Yggdrasil/LFP/lfp.py, lines 58–140 · score 0.65 · 0.5–500 Hz, spikeGLX, Imec, streamed, preprocessing, cat
  9. [9] § Methods › LFP analysis ↔ src/ripple_detection/detectors/_lfp.py, lines 57–124 · score 0.60 · Hilbert envelope, ripple filtered, Hz, LFP, trace, score
  10. [10] § Methods › Position tracking ↔ src/track_linearization/core.py, lines 1036–1097 · score 0.60 · track graph, jumped, network, adjacent, linearized, behavior
  11. [11] § Methods › Decoding linearized position from population spiking ↔ Yggdrasil/Sequences/sequences.py, lines 232–271 · score 0.59 · acausal posterior, position bin, stationary, fragmented, decoder, probability
  12. [12] § Methods › Behavioral training ↔ Figures_published_edition.ipynb, lines 192–275 · score 0.57 · linear track, open field, ran, surgery, day, maze
  13. [13] § Methods › LFP analysis ↔ Yggdrasil/LFP/lfp.py, lines 58–140 · score 0.57 · hardware filter, downsampled, shift, reversed, stream, temporal
  14. [14] § Methods › Single unit analysis ↔ Yggdrasil/Sequences/sequences.py, lines 629–713 · score 0.56 · consecutive bins, position bin, shuffling, Gaussian, movement, cm
  15. [15] § Results › Nonlocal content represents task-relevant information ↔ extract_sequences_published_edition.ipynb, lines 646–709 · score 0.53 · naive Bayesian, Bayesian decoder, model, decoding, MEC, immobility
  16. [16] § Results › MEC represents nonlocal positions during immobility ↔ extract_sequences_published_edition.ipynb, lines 646–709 · score 0.52 · naive Bayesian decoder, immobility bout, classified, model, MEC, decoded
  17. [17] § Results › Characterizing cells involved in nonlocal coding ↔ Figures_published_edition.ipynb, lines 725–821 · score 0.52 · nonlocal intervals, active cells, Decoded position, ratio, fields, immobility
  18. [18] § Results › Nonlocal content represents task-relevant information ↔ Figures_published_edition.ipynb, lines 725–821 · score 0.52 · acausal posterior, active cells, decoded position, immobility, Nonlocal, location
  19. [19] § Results › MEC nonlocal coding occurs largely outside of SWRs ↔ src/ripple_detection/detectors/_hse.py, lines 111–167 · score 0.51 · high synchrony events, active units, Kernel, plus, traces, movement

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 · 3,709 lines · 195 KB · GPL-3.0 · 4 matches

  1. # %% [markdown]
  2. # Calculate and display all figures for paper
  3. # %%
  4. # imports
  5. from os.path import exists, join
  6. from os import chdir
  7. import numpy as np
  8. import pandas as pd
  9. from datetime import datetime
  10. from copy import deepcopy
  11. import matplotlib.pyplot as plt
  12. import matplotlib as mpl
  13. import re
  14. from scipy import signal, stats
  15. import time
  16. from sklearn.metrics.pairwise import cosine_similarity
  17. from sklearn.linear_model import LogisticRegression, LinearRegression
  18. from sklearn.model_selection import train_test_split
  19. from sklearn.metrics import confusion_matrix
  20. from Yggdrasil.Position.position import Position
  21. import Yggdrasil.Position.position as pos
  22. from Yggdrasil.Position.arena import Box, BoxWithObject, LinearTrack, DoubleYMaze
  23. from Yggdrasil.Task.task import LinearTrackTask, DoubleYMazeTask
  24. from Yggdrasil.Spikes.spikes import Spikes
  25. import Yggdrasil.Spikes.spikes as sp
  26. from Yggdrasil.Position.spatial_functions import *
  27. from Yggdrasil.Sequences.sequences import *
  28. from Yggdrasil.Sequences.plot import *
  29. from Yggdrasil.statistics import calc_lmm
  30. from Yggdrasil.LFP.lfp import LFP
  31. import Yggdrasil.LFP.lfp as lf
  32. from Yggdrasil.Electrodes.electrodes import Electrodes
  33. from Yggdrasil.utilities import get_starts
  34. from ripple_detection import Kay_ripple_detector, multiunit_HSE_detector, get_multiunit_population_firing_rate
  35. # reload modules without restarting the kernel
  36. %load_ext autoreload
  37. %autoreload 2
  38. import warnings
  39. warnings.filterwarnings("ignore", category=DeprecationWarning)
  40. #chdir(r'C:\Users\emily\OneDrive - Stanford\GitHub\GiocomoLab')
  41. chdir(r'C:\Users\Niflheim\Documents\GitHub\Giocomo')
  42. # %%
  43. # matplotlib style sheet
  44. from cycler import cycler
  45. from matplotlib import rcParams
  46. rcParams['lines.linewidth'] = 1
  47. rcParams['axes.linewidth'] = 1
  48. rcParams['font.size'] = 9
  49. rcParams['font.family'] = 'Arial'
  50. rcParams['figure.autolayout'] = True
  51. rcParams['pdf.fonttype'] = 42
  52. #rcParams['xtick.major.pad'] = 2
  53. #rcParams['ytick.major.pad'] = 2
  54. rcParams['xtick.bottom'] = False
  55. rcParams['ytick.left'] = False
  56. rcParams['axes.spines.top'] = False
  57. rcParams['axes.spines.right'] = False
  58. rcParams['axes.grid'] = False
  59. rcParams['lines.markersize'] = 2
  60. # %% [markdown]
  61. # 👉 Set the figure output path and path to the list of sessions and list of animals
  62. # %%
  63. # figure_path = r"C:\Users\emily\Dropbox\Giocomo Lab\WT Sequences\Figures"
  64. figure_path = r"Z:\WT_Sequences\Analysis"
  65. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  66. subjects = pd.read_csv('Z:/WT_Sequences/subjects.csv')
  67. n_animals = len(subjects)
  68. mpl.rcParams['axes.prop_cycle'] = plt.cycler("color", plt.cm.gray(np.linspace(0, 0.7, n_animals)))
  69. colors = ['#B21F67','#DF5B25','#FFB000','#648FFF','#785EF0']
  70. # %%
  71. # specify animals
  72. animal_dict = {
  73. 'StickPin': 0,
  74. 'TopCoat': 1,
  75. 'TopHat': 2,
  76. 'AppleBottom': 3,
  77. 'BaggySweatpants': 4,
  78. 'StrappyReeboks': 5,
  79. 'Curie': 6,
  80. 'Lovelace': 7,
  81. 'Noether': 8,
  82. 'Franklin': 9,
  83. 'Lamarr': 10,
  84. 'Payne': 11
  85. }
  86. border_dict = {
  87. 'StickPin': 1780,
  88. 'TopCoat': 1900,
  89. 'TopHat': 1800,
  90. 'AppleBottom': (2100+2280)/2,
  91. 'BaggySweatpants': (1740+1660)/2,
  92. 'StrappyReeboks': 2040,
  93. 'Curie': [np.NaN, np.NaN, 2670, np.NaN],
  94. 'Lovelace': [np.NaN, 336/2*15, 348/2*15, np.NaN],
  95. 'Noether': [np.NaN, np.NaN, 364/2*15, np.NaN],
  96. 'Franklin': [np.NaN, np.NaN, 256/2*15, 268/2*15],
  97. 'Lamarr': [np.NaN, 276/2*15, 292/2*15, 300/2*15],
  98. 'Payne': [np.NaN, 254/2*15, 268/2*15, np.NaN]
  99. }
  100. # channels with highest theta power (and in MEC near units) across all sessions, 1 per animal
  101. theta_channel = {
  102. 'StickPin': 192,
  103. 'TopCoat': 183,
  104. 'TopHat': 177,
  105. 'AppleBottom': 159,
  106. 'BaggySweatpants': 159,
  107. 'StrappyReeboks': 155,
  108. 'Curie': 107,
  109. 'Lovelace': 59,
  110. 'Noether': 122,
  111. 'Franklin': 25,
  112. 'Lamarr': 161,
  113. 'Payne': 300
  114. }
  115. LOCAL_REMOTE_THRESH = 20
  116. # %% [markdown]
  117. # # Figure 1 & S1
  118. # %% [markdown]
  119. # ### Task performance over days
  120. # %%
  121. # set these values
  122. task_type = 'Single choice' #Single choice, Reversal or Cued
  123. n_sessions = 10 #10 days of Single choice or Reversal, 3 days of Cued
  124. # extract %corr into an array
  125. # NOTE: assumes list is in order by animal
  126. # this could be re-written to load values into a df where each col is an animal
  127. # and session indices are read from file names
  128. # but I am lazy and this works fine
  129. curr_animal = sessions['Animal'].iloc[0]
  130. animal_idx = 0
  131. session_idx = 0
  132. percent_correct = np.empty((n_sessions,n_animals))
  133. percent_correct[:] = np.NaN
  134. trials_per_min = np.empty((n_sessions,n_animals))
  135. trials_per_min[:] = np.NaN
  136. for i, row in sessions.iterrows():
  137. if row['Task'] == 'X Maze':
  138. behavior_output_path = join(row['Base_Directory'], 'Preprocessed_Data/Task')
  139. ecephys_path = join(row['Base_Directory'], 'Preprocessed_Data/Spikes')
  140. task_file = join(behavior_output_path, row['File']+'_task.txt')
  141. task = DoubleYMazeTask(name=task_file)
  142. start = 0
  143. end = task.trials['Start'].iloc[-1]
  144. if task.task_type == task_type:
  145. # advance to next column if next animal
  146. if not row['Animal'] == curr_animal:
  147. curr_animal = row['Animal']
  148. animal_idx += 1
  149. session_idx = 0
  150. percent_correct[session_idx, animal_idx] = task.percent_correct
  151. trials_per_min[session_idx, animal_idx] = task.ntrials/((end-start)/60)
  152. session_idx += 1
  153. # %%
  154. fig, ax = plt.subplots()
  155. ax.plot(np.arange(1,len(percent_correct)+1), percent_correct)
  156. ax.plot(np.arange(1,len(percent_correct)+1), np.nanmean(percent_correct,1), 'k', linewidth=3)
  157. ax.plot([0, 10], [50, 50], 'k--')
  158. ax.set_xlim([0, 10])
  159. ax.set_ylim([0, 100])
  160. ax.set_xlabel('Day')
  161. ax.set_ylabel('% Correct')
  162. fig.savefig(join(figure_path,'DY_percent_correct.pdf'), format='pdf')
  163. fig, ax = plt.subplots()
  164. ax.plot(np.arange(1, len(trials_per_min)+1), trials_per_min)
  165. ax.plot(np.arange(1,len(trials_per_min)+1), np.nanmean(trials_per_min,1), 'k', linewidth=3)
  166. ax.set_xlim([0, 10])
  167. ax.set_ylim([0, 8])
  168. ax.set_xlabel('Day')
  169. ax.set_ylabel('# Trials/Min')
  170. fig.savefig(join(figure_path, 'DY_trials_per_minute.pdf'), format='pdf')
  171. # %% [markdown]
  172. # ### Simultaneous cells recorded per day
  173. # %%
  174. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_full.csv')
  175. surgery_date_list = []
  176. n_sessions = 50 # placeholder, must be ># days post-op that recordings ran
  177. # get surgery dates
  178. for i, row in subjects.iterrows():
  179. # read surgery date from csv (Excel formats as MM/DD/YYYY)
  180. surgery_date_list.append(datetime.strptime(row['Surgery_Date'], '%m/%d/%Y'))
  181. # NOTE: assumes list is in order by animal
  182. # this could be re-written to load values into a df where each col is an animal
  183. # and session indices are read from file names
  184. # but I am lazy and this works fine
  185. curr_animal = sessions['Animal'].iloc[0]
  186. animal_idx = 0
  187. #n_units = np.zeros((n_sessions,n_animals))
  188. n_units_ca1 = np.full((n_sessions,n_animals), np.NaN)
  189. n_units_ca3 = np.full((n_sessions,n_animals), np.NaN)
  190. n_units_mec = np.full((n_sessions,n_animals), np.NaN)
  191. # count # of good & MUA units on each post-op day
  192. for i, row in sessions.iterrows():
  193. if not row['Recording_Error'] and not row['Position_Error'] and \
  194. (row['Task'] == 'X Maze' or row['Task'] == 'Linear Track' or \
  195. row['Epoch_Description']=='Environment A' or row['Epoch_Description']=='Open Field Test'):
  196. rec_date = datetime.strptime(re.findall('(\d{8})', row['File'])[0], '%Y%m%d')
  197. # advance to next column if next animal
  198. if not row['Animal'] == curr_animal:
  199. curr_animal = row['Animal']
  200. animal_idx += 1
  201. session_idx = (rec_date-surgery_date_list[animal_idx]).days
  202. #n_units[session_idx, animal_idx] += len(spikes.spikes)
  203. # subset by area
  204. if ("2023_spring" in row['Base_Directory']) or ("2024_winter" in row['Base_Directory']):
  205. if (row['Animal']=='Lamarr') and (int(row['Session'][-2:])>=8):
  206. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  207. row['File']+'_imec0_spikes.txt'))
  208. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  209. row['File']+'_imec0_electrodes.txt'))
  210. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  211. spikes_copy = deepcopy(spikes)
  212. spikes_copy.subset_by_channel(MEC_channels)
  213. n_units_mec[session_idx, animal_idx] = len(spikes_copy.spikes)
  214. else:
  215. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  216. row['File']+'_imec0_spikes.txt'))
  217. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  218. row['File']+'_imec0_electrodes.txt'))
  219. CA1_channels = electrodes.subset_by_location(regions=['CA1','alv','ccb'])
  220. spikes_copy = deepcopy(spikes)
  221. spikes_copy.subset_by_channel(CA1_channels)
  222. n_units_ca1[session_idx, animal_idx] = len(spikes_copy.spikes)
  223. CA3_channels = electrodes.subset_by_location(regions=['CA3','DG-po'])
  224. spikes_copy = deepcopy(spikes)
  225. spikes_copy.subset_by_channel(CA3_channels)
  226. n_units_ca3[session_idx, animal_idx] = len(spikes_copy.spikes)
  227. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  228. row['File']+'_imec1_spikes.txt'))
  229. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  230. row['File']+'_imec1_electrodes.txt'))
  231. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  232. spikes_copy = deepcopy(spikes)
  233. spikes_copy.subset_by_channel(MEC_channels)
  234. n_units_mec[session_idx, animal_idx] = len(spikes_copy.spikes)
  235. else:
  236. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1',
  237. row['File']+'_imec0_spikes.txt'))
  238. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  239. row['File']+'_electrodes.txt'))
  240. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  241. spikes_copy = deepcopy(spikes)
  242. spikes_copy.subset_by_channel(channels)
  243. n_units_mec[session_idx, animal_idx] = len(spikes_copy.spikes)
  244. # %%
  245. # plot
  246. days = np.arange(0,50)
  247. fig, ax = plt.subplots(figsize=(10, 5))
  248. ind = ~np.isnan(np.asarray(n_units_ca1.astype(float)))
  249. for a in range(n_animals):
  250. ax.plot(days[ind[:,a]], n_units_ca1[ind[:,a], a])
  251. ax.plot(days, np.nanmean(n_units_ca1,1), 'k', linewidth=3)
  252. ax.set(xlabel='Days Post-op', ylabel='# CA1 Units')
  253. ax.set(xlim=[0, 50], ylim=[0, 250])
  254. fig.savefig(join(figure_path,'Units_per_day_CA1.pdf'), format='pdf')
  255. fig, ax = plt.subplots(figsize=(10, 5))
  256. ind = ~np.isnan(np.asarray(n_units_ca3.astype(float)))
  257. for a in range(n_animals):
  258. ax.plot(days[ind[:,a]], n_units_ca3[ind[:,a], a])
  259. ax.plot(days, np.nanmean(n_units_ca3,1), 'k', linewidth=3)
  260. ax.set(xlabel='Days Post-op', ylabel='# CA3 Units')
  261. ax.set(xlim=[0, 50], ylim=[0, 200])
  262. fig.savefig(join(figure_path,'Units_per_day_CA3.pdf'), format='pdf')
  263. fig, ax = plt.subplots(figsize=(10, 5))
  264. ind = ~np.isnan(np.asarray(n_units_mec.astype(float)))
  265. for a in range(n_animals):
  266. ax.plot(days[ind[:,a]], n_units_mec[ind[:,a], a])
  267. ax.plot(days, np.nanmean(n_units_mec,1), 'k', linewidth=3)
  268. ax.set(xlabel='Days Post-op', ylabel='# MEC Units')
  269. ax.set(xlim=[0, 50], ylim=[0, 525])
  270. fig.savefig(join(figure_path,'Units_per_day_MEC.pdf'), format='pdf')
  271. # %%
  272. days = np.arange(0,50)
  273. n_sites_ca1 = [np.NaN, np.NaN, 130, 202, 240, 152, 118, 208, np.NaN, np.NaN, np.NaN, np.NaN]
  274. n_sites_ca3 = [np.NaN, np.NaN, 30, 108, 102, 38, 92, 6, np.NaN, np.NaN, np.NaN, np.NaN]
  275. n_sites_mec = [144, 218, 364, 364, 304, 384, 364, 198, 202, 116, 194, 204] #192,
  276. fig, ax = plt.subplots(figsize=(10, 5))
  277. ind = ~np.isnan(np.asarray(n_units_ca1.astype(float)))
  278. for a in range(n_animals):
  279. ax.plot(days[ind[:,a]], (n_units_ca1[ind[:,a], a]+n_units_ca3[ind[:,a], a]) \
  280. /(n_sites_ca1[a]+n_sites_ca3[a]))
  281. ax.plot(days, np.nanmean(n_units_ca1/n_sites_ca1,1), 'k', linewidth=3)
  282. ax.set(xlabel='Days Post-op', ylabel='# Units/Site in CA1')
  283. ax.set(xlim=[0, 50], ylim=[0, 1.75], yticks=np.arange(0,1.75,0.25))
  284. fig.savefig(join(figure_path,'Units_per_site_CA1.pdf'), format='pdf')
  285. fig, ax = plt.subplots(figsize=(10, 5))
  286. ind = ~np.isnan(np.asarray(n_units_mec.astype(float)))
  287. for a in range(n_animals):
  288. ax.plot(days[ind[:,a]], n_units_mec[ind[:,a], a]/n_sites_mec[a])
  289. ax.plot(days, np.nanmean(n_units_mec/n_sites_mec,1), 'k', linewidth=3)
  290. ax.set(xlabel='Days Post-op', ylabel='# Units/Site in MEC')
  291. ax.set(xlim=[0, 50], ylim=[0, 1.75], yticks=np.arange(0,1.75,0.25))
  292. fig.savefig(join(figure_path,'Units_per_site_MEC.pdf'), format='pdf')
  293. # %% [markdown]
  294. # ### Example heatmaps & rasters
  295. # %%
  296. bin_cm = 2
  297. for i, row in sessions.iloc[[109]].iterrows(): #118
  298. print(row['File'])
  299. for probe in range(2):
  300. spikes = Spikes(name=join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  301. row['File']+'_imec'+str(probe)+'_spikes.txt'))
  302. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  303. row['File']+'_position.txt'))
  304. if probe==0:
  305. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  306. row['File']+'_imec0_electrodes.txt'))
  307. CA1_channels = electrodes.subset_by_location(regions=['CA1','alv','ccb'])
  308. spikes.subset_by_channel(CA1_channels)
  309. else:
  310. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  311. row['File']+'_imec1_electrodes.txt'))
  312. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  313. spikes.subset_by_channel(MEC_channels)
  314. for u in range(len(spikes.spikes)):
  315. if probe==1:
  316. if u in [26,234,250]: #CA1 0,28,30; MEC 26,234,250
  317. fr_map, xbins, ybins, max_fr = calc_fr_map(spikes.spikes[u], position, bin_cm, smooth=True)
  318. if max_fr>1:
  319. print(f'Unit {u} Channel {spikes.spike_channel[u]} Max FR {max_fr}')
  320. f = position.plot_position(spikes=spikes.spikes[u])
  321. f.savefig(join(figure_path,f'Lovelace_DY01_MEC_{u}_raster.pdf'), format='pdf')
  322. f = plot_heatmap(fr_map, xbins, ybins)
  323. f.savefig(join(figure_path,f'Lovelace_DY01_MEC_{u}_heatmap.pdf'), format='pdf')
  324. # %% [markdown]
  325. # ### Speed distribution over days and locations
  326. # %%
  327. n_days = 10
  328. n_bins = 50
  329. vel_bins = np.arange(0,n_bins*2+2,2)
  330. speed_hist = np.zeros(n_bins)
  331. session_count = 0
  332. speed_hist_by_days = np.zeros((n_days,n_bins))
  333. day_counts = np.zeros(n_days)
  334. speed_hist_by_mouse = np.zeros((n_animals,n_bins))
  335. animal_counts = np.zeros(n_animals)
  336. for i, row in sessions.iterrows():
  337. session_idx = int(row['Session'][-2:])-1
  338. if not row['Recording_Error'] and not row['Position_Error'] and row['Task'] == 'X Maze' and session_idx<10:
  339. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  340. row['File']+'_position.txt'))
  341. this_session_hist = (pd.cut(position.position.Velocity, bins=vel_bins).value_counts().sort_index()/len(position.position.Velocity)).values
  342. speed_hist += this_session_hist
  343. session_count += 1
  344. speed_hist_by_days[session_idx, :] += this_session_hist
  345. day_counts[session_idx] += 1
  346. animal_idx = animal_dict[row['Animal']]
  347. speed_hist_by_mouse[animal_idx,:] += this_session_hist
  348. animal_counts[animal_idx] += 1
  349. # %%
  350. fig, ax = plt.subplots()
  351. for a in range(n_animals):
  352. ax.stairs(speed_hist_by_mouse[a]/animal_counts[a]*100, vel_bins)
  353. ax.stairs(speed_hist/session_count*100, vel_bins, color='k', linewidth=3)
  354. ax.set_xlim([0,80])
  355. ax.set_ylim([0,35])
  356. ax.set_xlabel('Velocity (cm/s)')
  357. ax.set_ylabel('% Time Spent')
  358. fig.savefig(join(figure_path,'Velocity_hist.pdf'), format='pdf')
  359. # %%
  360. ## Heatmap of speed over 3 examples days
  361. row = sessions.iloc[155]
  362. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  363. row['File']+'_position.txt'))
  364. speed_by_2D_location, x_bins, y_bins = position.calc_feature_map(bin_cm=2)
  365. f = plot_heatmap(speed_by_2D_location, x_bins, y_bins)
  366. f.savefig(join(figure_path,f'Payne_DY01_speed_heatmap.pdf'), format='pdf')
  367. print(np.nanmax(speed_by_2D_location))
  368. row = sessions.iloc[222]
  369. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  370. row['File']+'_position.txt'))
  371. speed_by_2D_location, x_bins, y_bins = position.calc_feature_map(bin_cm=2)
  372. f = plot_heatmap(speed_by_2D_location, x_bins, y_bins)
  373. f.savefig(join(figure_path,f'TopCoat_DY05_speed_heatmap.pdf'), format='pdf')
  374. print(np.nanmax(speed_by_2D_location))
  375. row = sessions.iloc[9]
  376. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  377. row['File']+'_position.txt'))
  378. speed_by_2D_location, x_bins, y_bins = position.calc_feature_map(bin_cm=2)
  379. f = plot_heatmap(speed_by_2D_location, x_bins, y_bins)
  380. f.savefig(join(figure_path,f'AppleBottom_DY10_speed_heatmap.pdf'), format='pdf')
  381. print(np.nanmax(speed_by_2D_location))
  382. # %%
  383. ## immobility by location
  384. velocity_thresh = 2 # cm/s
  385. n_sessions = 10
  386. frac_immob = np.full((n_sessions,n_animals), np.NaN)
  387. frac_immob_at_reward = np.full((n_sessions,n_animals), np.NaN)
  388. frac_immob_outside_reward = np.full((n_sessions,n_animals), np.NaN)
  389. frac_immob_at_decision = np.full((n_sessions,n_animals), np.NaN)
  390. for i, row in sessions.iterrows():
  391. session_idx = int(row['Session'][-2:])-1
  392. animal_idx = animal_dict[row['Animal']]
  393. if not row['Recording_Error'] and not row['Position_Error'] and row['Task'] == 'X Maze' and session_idx<10:
  394. position = Position(join(row['Base_Directory'], 'Preprocessed_Data/Position', row['File']+'_position.txt'))
  395. dist_to_reward = position.arena.get_dist_to_nearest_poke(position.position.X, position.position.Y)
  396. frac_immob[session_idx, animal_idx] = np.mean((position.position.Velocity.values<velocity_thresh))
  397. frac_immob_at_reward[session_idx, animal_idx] = sum((position.position.Velocity.values<velocity_thresh) & (dist_to_reward<10))/sum(dist_to_reward<10)
  398. frac_immob_outside_reward[session_idx, animal_idx] = sum((position.position.Velocity.values<velocity_thresh) & (dist_to_reward>=10))/sum(dist_to_reward>=10)
  399. dist_to_decision = position.arena.get_dist_to_decision(position.position.X, position.position.Y)
  400. frac_immob_at_decision[session_idx, animal_idx] = sum((position.position.Velocity.values<velocity_thresh) & (dist_to_decision<10))/sum(dist_to_decision<10)
  401. # %%
  402. fig, ax = plt.subplots()
  403. days = np.arange(1, 11)
  404. ind = ~np.isnan(np.asarray(frac_immob.astype(float)))
  405. for a in range(n_animals):
  406. ax.plot(days[ind[:,a]], frac_immob[ind[:,a], a]*100, linewidth=0.5)
  407. ax.plot(days, np.nanmean(frac_immob,1)*100, 'k', linewidth=2)
  408. ax.set(xlabel='Day', ylabel='% Time Immobile', ylim=[0, 100])
  409. fig.savefig(join(figure_path,f'Immobility_time_over_days.pdf'), format='pdf')
  410. fig, ax = plt.subplots()
  411. days = np.arange(1, 11)
  412. ind = ~np.isnan(np.asarray(frac_immob_at_reward.astype(float)))
  413. for a in range(n_animals):
  414. ax.plot(days[ind[:,a]], frac_immob_at_reward[ind[:,a], a]*100, linewidth=0.5)
  415. ax.plot(days, np.nanmean(frac_immob_at_reward,1)*100, 'k', linewidth=2)
  416. ax.set(xlabel='Day', ylabel='% Time Immobile at Reward', ylim=[0, 100])
  417. fig.savefig(join(figure_path,f'Immobility_time_at_reward_over_days.pdf'), format='pdf')
  418. fig, ax = plt.subplots()
  419. days = np.arange(1, 11)
  420. ind = ~np.isnan(np.asarray(frac_immob_outside_reward.astype(float)))
  421. for a in range(n_animals):
  422. ax.plot(days[ind[:,a]], frac_immob_outside_reward[ind[:,a], a]*100, linewidth=0.5)
  423. ax.plot(days, np.nanmean(frac_immob_outside_reward,1)*100, 'k', linewidth=2)
  424. ax.set(xlabel='Day', ylabel='% Time Immobile >10cm from Reward', ylim=[0, 100])
  425. fig.savefig(join(figure_path,f'Immobility_time_outside_reward_over_days.pdf'), format='pdf')
  426. # %% [markdown]
  427. # ### No overrepresentation: example heatmap of summation of normalized firing rates, summation by location over days
  428. # %%
  429. bin_cm = 2
  430. for i, row in sessions.iloc[[109]].iterrows():
  431. if not row['Recording_Error'] and not row['Position_Error'] and row['Task'] == 'X Maze':
  432. print(row['File'])
  433. ca1_spikes = None
  434. mec_spikes = None
  435. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  436. row['File']+'_position.txt'))
  437. if ("2023_spring" in row['Base_Directory']) or ("2024_winter" in row['Base_Directory']):
  438. if (row['Animal']=='Lamarr') and (int(row['Session'][-2:])>=8):
  439. mec_spikes = Spikes(name=join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  440. row['File']+'_imec0_spikes.txt'))
  441. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  442. row['File']+'_imec0_electrodes.txt'))
  443. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  444. mec_spikes.subset_by_channel(MEC_channels)
  445. else:
  446. ca1_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  447. row['File']+'_imec0_spikes.txt'))
  448. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  449. row['File']+'_imec0_electrodes.txt'))
  450. CA1_channels = electrodes.subset_by_location(regions=['CA1','alv','ccb'])
  451. ca1_spikes.subset_by_channel(CA1_channels)
  452. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  453. row['File']+'_imec1_spikes.txt'))
  454. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  455. row['File']+'_imec1_electrodes.txt'))
  456. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  457. mec_spikes.subset_by_channel(MEC_channels)
  458. else:
  459. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1',
  460. row['File']+'_imec0_spikes.txt'))
  461. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  462. row['File']+'_electrodes.txt'))
  463. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  464. mec_spikes.subset_by_channel(channels)
  465. fr_map, xbins, ybins, max_fr = calc_fr_map(mec_spikes.spikes[0], position, bin_cm, smooth=True)
  466. summed_mec_map = np.zeros(fr_map.shape)
  467. summed_ca1_map = np.zeros(fr_map.shape)
  468. if ca1_spikes is not None:
  469. for u in range(len(ca1_spikes.spikes)):
  470. fr_map, xbins, ybins, max_fr = calc_fr_map(ca1_spikes.spikes[u], position, bin_cm, smooth=False)
  471. summed_ca1_map += fr_map/np.max(fr_map)
  472. f = plot_heatmap(summed_ca1_map, xbins, ybins)
  473. f.savefig(join(figure_path,'Lovelace_DY01_CA1_heatmap.pdf'), format='pdf')
  474. if mec_spikes is not None:
  475. for u in range(len(mec_spikes.spikes)):
  476. fr_map, xbins, ybins, max_fr = calc_fr_map(mec_spikes.spikes[u], position, bin_cm, smooth=False)
  477. summed_mec_map += fr_map/np.max(fr_map)
  478. f = plot_heatmap(summed_mec_map, xbins, ybins)
  479. f.savefig(join(figure_path,'Lovelace_DY01_MEC_heatmap.pdf'), format='pdf')
  480. # %%
  481. # calculate normalized ratemap in each linearized position bin
  482. # using linearized data will make it easier to combine across sessions
  483. # arena borders were re-drawn for each of the 4 cohorts
  484. # thus each cohort has slightly different linear bins,
  485. # but these can be easily combined across segments
  486. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  487. n_sessions = 20
  488. n_animals = len(animal_dict)
  489. norm_fr_map = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  490. for _, row in sessions.iterrows():
  491. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  492. seq = Sequences(join(row['Base_Directory'],
  493. 'Preprocessed_Data/Sequences',
  494. row['File']+'_MEC_nonlocal_immobility_sequences.txt'),
  495. load_objects=True)
  496. animal_idx = animal_dict[row['Animal']]
  497. sess_idx = int(row['Session'][-2:])-1
  498. # get linearized bin position of animal at each binned timestamp
  499. linear_to_bins = np.asarray(seq.classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position'])
  500. linear_pos = np.asarray(seq.binned_data.position["Linear"])
  501. animal_bins = np.argmin(np.abs(linear_to_bins[:,np.newaxis]-linear_pos), axis=0)
  502. animal_bins = animal_bins.astype(dtype=float)
  503. animal_bins[np.isnan(linear_pos)] = np.NaN
  504. # same as calc_occupancy, but for 1D position
  505. bins = np.arange(np.nanmax(animal_bins)+1)
  506. occupancy, _ = np.histogram(animal_bins, bins=bins)
  507. # convert to seconds
  508. occupancy = occupancy.astype(float)
  509. occupancy *= seq.position.us_per_frame/10**3
  510. # same as get_spike_map, but for 1D position
  511. spike_map = np.zeros((len(seq.binned_data.spikes), len(bins)-1))
  512. norm_fr_map[animal_idx][sess_idx] = np.zeros(len(bins)-1)
  513. for u in np.arange(len(seq.binned_data.spikes)):
  514. spikes_per_bin = []
  515. for n_spk in np.arange(1, np.max(seq.binned_data.spikes[u,:]) + 1):
  516. spikes_per_bin += animal_bins[seq.binned_data.spikes[u,:] == n_spk].tolist() * int(n_spk)
  517. spike_map[u,:], _ = np.histogram(spikes_per_bin, bins)
  518. unit_fr_map = get_fr_map(spike_map[u,:], occupancy)
  519. norm_fr_map[animal_idx][sess_idx] += unit_fr_map/np.nanmax(unit_fr_map)
  520. norm_fr_map[animal_idx][sess_idx] /= len(seq.binned_data.spikes)
  521. print(row['File'])
  522. with open(join(figure_path,'MEC_norm_fr_map.pkl'), 'wb') as file:
  523. pickle.dump(norm_fr_map, file)
  524. # %%
  525. with open(join(figure_path,'MEC_norm_fr_map.pkl'), 'rb') as file:
  526. norm_fr_map = pickle.load(file)
  527. norm_fr_map[6][6] = norm_fr_map[6][6][:123]
  528. norm_fr_map[6][4] = norm_fr_map[6][6][:123]
  529. norm_fr_map[6][18] = norm_fr_map[6][6][:123]
  530. # sum over sessions
  531. all_sessions_fr = np.full((n_animals,n_sessions,123), np.NaN)
  532. for a in range(n_animals):
  533. for s in range(n_sessions):
  534. if len(norm_fr_map[a][s])>0:
  535. # mask bins where occupancy was 0
  536. norm_fr_map[a][s][norm_fr_map[a][s]==0] = np.NaN
  537. all_sessions_fr[a,s,0:len(norm_fr_map[a][s])] = norm_fr_map[a][s]
  538. sum_over_sessions_fr = np.nanmean(all_sessions_fr, axis=1)
  539. # mask out bins with too few sessions
  540. empty_bins = []
  541. for a in range(n_animals):
  542. empty_bins.extend(np.where(np.isnan(sum_over_sessions_fr[a]))[0])
  543. n_empty, _ = np.histogram(empty_bins, bins=np.arange(0,124))
  544. keep_locs = np.arange(0,123)[n_empty<=1]
  545. # plot over spatial bins
  546. spatial_bins = np.arange(1,len(keep_locs)+1)
  547. sum_over_sessions_fr = sum_over_sessions_fr[:,keep_locs]
  548. fig, ax = plt.subplots()
  549. for a in range(n_animals):
  550. ax.plot(spatial_bins[~np.isnan(sum_over_sessions_fr[a,:])], \
  551. sum_over_sessions_fr[a,~np.isnan(sum_over_sessions_fr[a,:])], linewidth=0.5)
  552. ax.plot(spatial_bins, np.nanmean(sum_over_sessions_fr, axis=0), 'k', linewidth=2)
  553. ax.set(xlabel='Spatial Bin', ylabel='Summed Normalized FR', ylim=[0, 0.5])
  554. fig.savefig(join(figure_path,f'MEC_norm_fr_per_spatial_bin.pdf'), format='pdf')
  555. # %%
  556. # repeat for CA1
  557. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  558. norm_fr_map = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  559. for _, row in sessions.iterrows():
  560. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  561. seq = Sequences(join(row['Base_Directory'],
  562. 'Preprocessed_Data/Sequences',
  563. row['File']+'_CA1_immobility_sequences.txt'),
  564. load_objects=True)
  565. animal_idx = animal_dict[row['Animal']]
  566. sess_idx = int(row['Session'][-2:])-1
  567. # get linearized bin position of animal at each binned timestamp
  568. linear_to_bins = np.asarray(seq.classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position'])
  569. linear_pos = np.asarray(seq.binned_data.position["Linear"])
  570. animal_bins = np.argmin(np.abs(linear_to_bins[:,np.newaxis]-linear_pos), axis=0)
  571. animal_bins = animal_bins.astype(dtype=float)
  572. animal_bins[np.isnan(linear_pos)] = np.NaN
  573. # same as calc_occupancy, but for 1D position
  574. bins = np.arange(np.nanmax(animal_bins)+1)
  575. occupancy, _ = np.histogram(animal_bins, bins=bins)
  576. # convert to seconds
  577. occupancy = occupancy.astype(float)
  578. occupancy *= seq.position.us_per_frame/10**3
  579. # same as get_spike_map, but for 1D position
  580. spike_map = np.zeros((len(seq.binned_data.spikes), len(bins)-1))
  581. norm_fr_map[animal_idx][sess_idx] = np.zeros(len(bins)-1)
  582. for u in np.arange(len(seq.binned_data.spikes)):
  583. spikes_per_bin = []
  584. for n_spk in np.arange(1, np.max(seq.binned_data.spikes[u,:]) + 1):
  585. spikes_per_bin += animal_bins[seq.binned_data.spikes[u,:] == n_spk].tolist() * int(n_spk)
  586. spike_map[u,:], _ = np.histogram(spikes_per_bin, bins)
  587. unit_fr_map = get_fr_map(spike_map[u,:], occupancy)
  588. norm_fr_map[animal_idx][sess_idx] += unit_fr_map/np.nanmax(unit_fr_map)
  589. norm_fr_map[animal_idx][sess_idx] /= len(seq.binned_data.spikes)
  590. with open(join(figure_path,'CA1_norm_fr_map.pkl'), 'wb') as file:
  591. pickle.dump(norm_fr_map, file)
  592. # %%
  593. with open(join(figure_path,'CA1_norm_fr_map.pkl'), 'rb') as file:
  594. norm_fr_map = pickle.load(file)
  595. norm_fr_map[6][6] = norm_fr_map[6][6][:123]
  596. norm_fr_map[6][4] = norm_fr_map[6][6][:123]
  597. norm_fr_map[6][18] = norm_fr_map[6][6][:123]
  598. # sum over sessions
  599. all_sessions_fr = np.full((n_animals,n_sessions,123), np.NaN)
  600. for a in range(n_animals):
  601. for s in range(n_sessions):
  602. if len(norm_fr_map[a][s])>0:
  603. # mask bins where occupancy was 0
  604. norm_fr_map[a][s][norm_fr_map[a][s]==0] = np.NaN
  605. all_sessions_fr[a,s,0:len(norm_fr_map[a][s])] = norm_fr_map[a][s]
  606. sum_over_sessions_fr = np.nanmean(all_sessions_fr, axis=1)
  607. # mask out bins with too few sessions
  608. empty_bins = []
  609. for a in range(n_animals):
  610. empty_bins.extend(np.where(np.isnan(sum_over_sessions_fr[a]))[0])
  611. n_empty, _ = np.histogram(empty_bins, bins=np.arange(0,124))
  612. keep_locs = np.arange(0,123)[n_empty<=6+1]
  613. # plot over spatial bins
  614. spatial_bins = np.arange(1,len(keep_locs)+1)
  615. sum_over_sessions_fr = sum_over_sessions_fr[:,keep_locs]
  616. fig, ax = plt.subplots()
  617. for a in range(n_animals):
  618. ax.plot(spatial_bins[~np.isnan(sum_over_sessions_fr[a,:])], \
  619. sum_over_sessions_fr[a,~np.isnan(sum_over_sessions_fr[a,:])], linewidth=0.5)
  620. ax.plot(spatial_bins, np.nanmean(sum_over_sessions_fr, axis=0), 'k', linewidth=2)
  621. ax.set(xlabel='Spatial Bin', ylabel='Summed Normalized FR', ylim=[0, 0.5])
  622. fig.savefig(join(figure_path,f'CA1_norm_fr_per_spatial_bin.pdf'), format='pdf')
  623. # %% [markdown]
  624. # # Figure 2
  625. # %% [markdown]
  626. # ### Example decode
  627. # %%
  628. # open example
  629. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  630. row = sessions.iloc[133] #109 #141
  631. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  632. row['File']+'_MEC_immobility_sequences.txt'),
  633. load_objects=True)
  634. print(row['File'])
  635. # %%
  636. # plot spikes, position, decoded position, decode distance, and speed for snippet of data
  637. # subset data
  638. start_time = 1260
  639. end_time = 1290
  640. start_idx = np.argmin(np.abs(seq.binned_data.timestamps-start_time))
  641. end_idx = np.argmin(np.abs(seq.binned_data.timestamps-end_time))
  642. subset_timestamps = seq.binned_data.timestamps[start_idx:end_idx]
  643. f, ax = plt.subplots(4, 1, constrained_layout=True, sharex=False,
  644. gridspec_kw={"height_ratios": [3,3,1,1]})
  645. # order cells by peak location along track and plot raster
  646. cell_order = order_cells_by_field(seq.classifier.classifier)
  647. neuron_ind, spike_time_ind = np.nonzero(seq.binned_data.spikes[cell_order,start_idx:end_idx])
  648. ax[0].scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  649. neuron_ind, color='black', zorder=1,
  650. marker='|', linewidth=1)
  651. n_active_cells = len(np.unique(neuron_ind))
  652. # label spikes during non-local immobility
  653. time_slice = slice(start_time,end_time)
  654. dist = seq.calc_dist_from_decode(ts_to_include=seq.binned_data.position.loc[time_slice].index)
  655. ds_factor = 5
  656. binned_ds_dist = np.nanmean(np.reshape(dist[:-1],(-1,ds_factor)), axis=1)
  657. nonlocal_neuron_ind = np.empty(0, dtype=int)
  658. nonlocal_intervals = pos.filtered_timestamps_to_intervals(np.arange(0,3000), binned_ds_dist>20)
  659. for i in range(4,len(nonlocal_intervals)):
  660. neuron_ind, spike_time_ind = np.nonzero(seq.binned_data.spikes[:,
  661. int(start_idx+nonlocal_intervals[i][0]*5):int(start_idx+nonlocal_intervals[i][1]*5)])
  662. # ax[1].scatter(1000*(subset_timestamps[nonlocal_intervals[i][0]*5+spike_time_ind]-start_time),
  663. # neuron_ind, color='darkorange', zorder=1,
  664. # marker='|', linewidth=1)
  665. nonlocal_neuron_ind = np.hstack((nonlocal_neuron_ind, neuron_ind))
  666. nonlocal_neuron_ind = np.unique(nonlocal_neuron_ind)
  667. neuron_ind, spike_time_ind = np.nonzero(seq.binned_data.spikes[nonlocal_neuron_ind,start_idx:end_idx])
  668. # map onto cell order
  669. for n in range(len(neuron_ind)):
  670. neuron_ind[n] = np.argmin(np.abs(cell_order-nonlocal_neuron_ind[neuron_ind[n]]))
  671. # ax[0].scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  672. # neuron_ind, color='darkorange', zorder=1,
  673. # marker='|', linewidth=1)
  674. ax[0].set(xlim=[0,30000], ylabel='Neuron')
  675. # add classifier
  676. results = seq.classifier.classifier_results.sel(time=time_slice)
  677. time = (results.time-start_time) * 1000
  678. max_time = time.max()
  679. cmap = copy.copy(plt.cm.get_cmap('bone_r'))
  680. cmap.set_bad(color="lightgrey", alpha=1.0)
  681. (
  682. results
  683. .assign_coords(time=time)
  684. .acausal_posterior.sum("state")
  685. .plot(
  686. x="time",
  687. y="position",
  688. robust=True,
  689. add_colorbar=False,
  690. zorder=0,
  691. rasterized=True,
  692. cmap=cmap,
  693. ax=ax[1]
  694. )
  695. )
  696. seq_position = seq.binned_data.position['Linear'].loc[time_slice]
  697. max_position = int(
  698. np.ceil(seq.binned_data.position['Linear'].max()))
  699. ax[1].plot(time, seq_position, linestyle="--", linewidth=2,
  700. color="magenta", clip_on=False)
  701. rtc.plot_graph_as_1D(seq.position.arena.track_graph,
  702. edge_spacing=seq.position.arena.edge_spacing,
  703. ax=ax[1], axis="y", other_axis_start=max_time+50)
  704. # overlay: distance between decode and real positions
  705. ax[2].plot(pd.DataFrame(binned_ds_dist).interpolate())
  706. ax[2].set(xlim=[0,3000], ylabel='Distance from decoded position (cm)')
  707. # highlight nonlocal times
  708. for i in range(len(nonlocal_intervals)):
  709. ax[2].axvspan(nonlocal_intervals[i][0], nonlocal_intervals[i][1], alpha=0.4, zorder=2, color='darkorange')
  710. # overlay: velocity
  711. velocity = seq.binned_data.position['Velocity'].iloc[start_idx:end_idx]
  712. ds_factor = 5
  713. binned_ds_vel = np.nanmean(np.reshape(velocity.values,(-1,ds_factor)), axis=1)
  714. ax[3].plot(pd.DataFrame(binned_ds_vel).interpolate())
  715. ax[3].set(xlim=[0,3000], ylabel='Speed (cm/s)')
  716. # highlight immobility times
  717. immobility_intervals = pos.filtered_timestamps_to_intervals(np.arange(0,3000), binned_ds_vel<2)
  718. for i in range(len(immobility_intervals)):
  719. ax[3].axvspan(immobility_intervals[i][0], immobility_intervals[i][1], alpha=0.3, zorder=2)
  720. f.savefig(join(figure_path,'Noether_DY02_decode_snippet.pdf'), format='pdf')
  721. print(f"{len(nonlocal_neuron_ind)} of {n_active_cells} cells active during nonlocal immobility")
  722. # %% [markdown]
  723. # ### Decoder error vs velocity bins & # of cells
  724. # %%
  725. distance, velocity, session, animal, n_units = [np.array([]) for _ in range(5)]
  726. for i, row in sessions.iterrows():
  727. if not row['Recording_Error'] and not row['Position_Error'] and row['Task'] == 'X Maze':
  728. # load any sequences file, doesn't matter which interval (we're just using the
  729. # classifier & binned data associated with the file, which is the same for
  730. # immobility, movement, and SWR sequences)
  731. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  732. row['File']+'_MEC_immobility_sequences.txt'),
  733. load_objects=True)
  734. # exclude times when the decode is fragmented
  735. cr = seq.classifier.classifier_results
  736. non_frag = cr.acausal_posterior.sum('position').argmax('state')!=1
  737. non_frag_ts = non_frag.coords['time'].values[np.where(non_frag)]
  738. # calculate distance between animal and decoded position
  739. dist = seq.calc_dist_from_decode(ts_to_include=non_frag_ts)
  740. # remove times when position couldn't be tracked
  741. vel = np.asarray(seq.binned_data.position["Velocity"].loc[non_frag_ts])
  742. dist = dist[~np.isnan(vel)]
  743. vel = vel[~np.isnan(vel)]
  744. # append
  745. distance = np.append(distance, dist)
  746. velocity = np.append(velocity, vel)
  747. session = np.append(session, [int(row['Session'][-2:])-1] * len(dist))
  748. animal = np.append(animal, [animal_dict[row['Animal']]] * len(dist))
  749. n_units = np.append(n_units, [seq.binned_data.spikes.shape[0]] * len(dist))
  750. df = pd.DataFrame({'Velocity': velocity, 'Distance': distance,
  751. 'Session': session, 'Animal': animal,
  752. 'N_Units': n_units})
  753. # %%
  754. df.to_csv(join(figure_path,'Decoded_distance_binned.csv'), index=False)
  755. #df = pd.read_csv(join(figure_path,'Decoded_distance_binned.csv'))
  756. # %%
  757. heatmap, xedges, yedges = np.histogram2d(df['Velocity'], df['Distance'], bins=[35,26])
  758. norm_heatmap = heatmap.T/np.sum(heatmap, axis=1)
  759. extent = [yedges[0], yedges[-2], xedges[0], xedges[-19]]
  760. cmap = mpl.cm.get_cmap("viridis").copy()
  761. #cmap.set_under('k')
  762. fig, ax = plt.subplots()
  763. hmap = ax.imshow(np.transpose(norm_heatmap[0:-2,0:-19]), extent=extent, origin='lower', cmap=cmap, norm=mpl.colors.LogNorm(0.01, 0.05))
  764. fig.colorbar(hmap, extend='max')
  765. ax.set(ylabel='Speed (cm/s)', xlabel='Distance from Animal (cm)')
  766. fig.savefig(join(figure_path,'Decode_distance_vs_velocity_heatmap.pdf'), format='pdf')
  767. # %%
  768. heatmap, xedges, yedges = np.histogram2d(df['Velocity'], df['Distance'], bins=[35,26])
  769. extent = [xedges[0], xedges[-19], yedges[0], yedges[-1]]
  770. fig, ax = plt.subplots()
  771. h, edges = np.histogram(df.Distance[df.Velocity<2], bins=np.arange(0,yedges[-1]+5,5))
  772. ax.stairs(h/h.sum()*100, edges, color='grey')
  773. frac_local_immob = np.sum(h[0:4]/h.sum())*100
  774. h, edges = np.histogram(df.Distance[df.Velocity>=2], bins=np.arange(0,yedges[-1]+5,5))
  775. ax.stairs(h/h.sum()*100, edges, color='black')
  776. frac_local_move = np.sum(h[0:4]/h.sum())*100
  777. ax.set(xlim=[edges[0],edges[-2]+1], ylim=[0,50], xlabel='Distance from decoded position', ylabel='% of Time')
  778. print(f"{frac_local_move:0.2f}% local during movement and {frac_local_immob:0.2f}% local during immobility")
  779. fig.savefig(join(figure_path,'Decode_distance_immob_vs_move.pdf'), format='pdf')
  780. fig, ax = plt.subplots()
  781. h, edges = np.histogram(df.Distance[df.Velocity<2], bins=np.arange(0,yedges[-1]+5,5))
  782. ax.stairs(h/h.sum()*100, edges, color='grey')
  783. h, edges = np.histogram(df.Distance[df.Velocity>=2], bins=np.arange(0,yedges[-1]+5,5))
  784. ax.stairs(h/h.sum()*100, edges, color='black')
  785. ax.set(xlim=[20,edges[-2]+1], ylim=[0,4], xlabel='Distance from decoded position', ylabel='% of Time')
  786. fig.savefig(join(figure_path,'Decode_distance_immob_vs_move_inset.pdf'), format='pdf')
  787. # %% [markdown]
  788. # ### Example raster, 1D & 2D decodes of immobility bouts with nonlocal coding
  789. # %%
  790. row = sessions.iloc[133]
  791. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  792. row['File']+'_MEC_immobility_sequences.txt'),
  793. load_objects=True)
  794. print(seq.name)
  795. # %%
  796. seq.plot_sequence(indices=[1,33,41,305], figure_path=f"{figure_path}/Noether_DY02_MEC")
  797. # %%
  798. # repeat raster with color coding by local/nonlocal
  799. # first, load the interval
  800. start_time = seq.intervals.intervals.iloc[1].start_time
  801. end_time = seq.intervals.intervals.iloc[1].end_time
  802. start_idx = np.argmin(np.abs(seq.binned_data.timestamps-start_time))
  803. end_idx = np.argmin(np.abs(seq.binned_data.timestamps-end_time))
  804. subset_timestamps = seq.binned_data.timestamps[start_idx:end_idx]
  805. # order cells by peak location along track and plot raster
  806. cell_order, max_bin = order_cells_by_field(seq.classifier.classifier)
  807. # %%
  808. # then, determine which spatial bins are local & nonlocal
  809. cr = seq.classifier.classifier_results.sel(time=slice(start_time, end_time))
  810. map_position_ind = cr.sum("state").acausal_posterior.argmax("position").values
  811. plt.plot(map_position_ind)
  812. print(np.unique(map_position_ind))
  813. # %%
  814. # then, identify cells that have significant activity at the nonlocal (contributing_cells) and local (noncontributing_cells) positions
  815. # set decoded_bin manually for each interval
  816. # this method finds cells which have firing at the target spatial bin(s) that is >95th percentile of their firing across all spatial bins
  817. # also tried >mean across all spatial bins
  818. # also tried nonspatial approach: identify cells whose firing during this particular interval > mean or 95th percentile of shuffle across all immobility
  819. # this mainly identified high FR cells, as low FR cells are less likely to participate in any given interval
  820. # 1: local 59, nonlocal 29, 92-96
  821. # 33: local 0, nonlocal 27
  822. # 41: local 49-51 (58, 60); nonlocal 29, 78-79
  823. # 305: local 121, nonlocal 101,102
  824. field_locs, field_peaks, field_sizes, field_spacing = seq.find_fields()
  825. pf = seq.classifier.classifier.place_fields_[('', 0)].sel(neuron=cell_order)
  826. field_locs_ordered = [field_locs[i] for i in cell_order]
  827. n_cells = pf.shape[1]
  828. decoded_bin = [59]
  829. local_cells = []
  830. for u in range(n_cells):
  831. # if np.any(pf.sel(neuron=u).values[decoded_bin] - np.nanpercentile(pf.sel(neuron=u).values, 95) > 0): # Old Method
  832. for f in field_locs_ordered[u]:
  833. if np.any(np.isin(decoded_bin, f)):
  834. local_cells.append(u)
  835. local_cells = np.unique(np.asarray(local_cells))
  836. decoded_bin = [29,92,93,94,95,96]
  837. nonlocal_cells = []
  838. for u in range(n_cells):
  839. for f in field_locs_ordered[u]:
  840. if np.any(np.isin(decoded_bin, f)):
  841. nonlocal_cells.append(u)
  842. nonlocal_cells = np.unique(np.asarray(nonlocal_cells))
  843. # %%
  844. f, ax = plt.subplots(2, 1, constrained_layout=True, sharex=False,
  845. gridspec_kw={"height_ratios": [5,5]})
  846. # label spikes during non-local immobility
  847. time_slice = slice(start_time,end_time)
  848. dist = seq.calc_dist_from_decode(ts_to_include=seq.binned_data.position.loc[time_slice].index)
  849. ds_factor = 5
  850. binned_ds_dist = np.nanmean(np.reshape(dist,(-1,ds_factor)), axis=1)
  851. nonlocal_neuron_ind = np.empty(0, dtype=int)
  852. nonlocal_intervals = pos.filtered_timestamps_to_intervals(np.arange(0,len(binned_ds_dist)), binned_ds_dist>20)
  853. interval_spikes = seq.binned_data.spikes[cell_order,start_idx:end_idx]
  854. neuron_ind, spike_time_ind = np.nonzero(interval_spikes)
  855. ax[0].scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  856. neuron_ind, color='grey', zorder=1,
  857. marker='|', linewidth=1)
  858. # plot spikes from cells contributing to decode (over all time) in black
  859. neuron_ind, spike_time_ind = np.nonzero(interval_spikes[nonlocal_cells, :])
  860. neuron_ind = nonlocal_cells[neuron_ind]
  861. ax[0].scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  862. neuron_ind, color='black', zorder=1,
  863. marker='|', linewidth=1)
  864. neuron_ind, spike_time_ind = np.nonzero(interval_spikes[local_cells, :])
  865. neuron_ind = local_cells[neuron_ind]
  866. ax[0].scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  867. neuron_ind, color='black', zorder=1,
  868. marker='|', linewidth=1)
  869. # plot spikes contributing to nonlocal decode in cyan during non-local times
  870. for i in range(len(nonlocal_intervals)):
  871. interval_spikes = seq.binned_data.spikes[cell_order,int(start_idx+nonlocal_intervals[i][0]*5):int(start_idx+nonlocal_intervals[i][1]*5)]
  872. neuron_ind_nl, spike_time_ind_nl = np.nonzero(interval_spikes[nonlocal_cells, :])
  873. neuron_ind_nl = nonlocal_cells[neuron_ind_nl]
  874. ax[0].scatter(1000*(subset_timestamps[nonlocal_intervals[i][0]*5+spike_time_ind_nl]-start_time),
  875. neuron_ind_nl, color='cyan', zorder=1,
  876. marker='|', linewidth=2)
  877. # plot spikes contributing to local decode in magenta during local times
  878. # before and after last nonlocal interval
  879. interval_spikes_l = seq.binned_data.spikes[cell_order,start_idx:int(start_idx+nonlocal_intervals[0][0]*5)]
  880. neuron_ind_l, spike_time_ind_l = np.nonzero(interval_spikes_l[local_cells, :])
  881. neuron_ind_l = local_cells[neuron_ind_l]
  882. ax[0].scatter(1000*(subset_timestamps[spike_time_ind_l]-start_time),
  883. neuron_ind_l, color='magenta', zorder=1,
  884. marker='|', linewidth=2)
  885. interval_spikes_l = seq.binned_data.spikes[cell_order,int(start_idx+nonlocal_intervals[-1][1]*5):end_idx]
  886. neuron_ind_l, spike_time_ind_l = np.nonzero(interval_spikes_l[local_cells, :])
  887. neuron_ind_l = local_cells[neuron_ind_l]
  888. ax[0].scatter(1000*(subset_timestamps[nonlocal_intervals[i][1]*5+spike_time_ind_l]-start_time),
  889. neuron_ind_l, color='magenta', zorder=1,
  890. marker='|', linewidth=2)
  891. # between nonlocal lintervals
  892. if len(nonlocal_intervals)>1:
  893. for i in range(1,len(nonlocal_intervals)):
  894. interval_spikes_l = seq.binned_data.spikes[cell_order,int(start_idx+nonlocal_intervals[i-1][1]*5):int(start_idx+nonlocal_intervals[i][0]*5)]
  895. neuron_ind_l, spike_time_ind_l = np.nonzero(interval_spikes_l[local_cells, :])
  896. neuron_ind_l = local_cells[neuron_ind_l]
  897. ax[0].scatter(1000*(subset_timestamps[nonlocal_intervals[i-1][1]*5+spike_time_ind_l]-start_time),
  898. neuron_ind_l, color='magenta', zorder=1,
  899. marker='|', linewidth=2)
  900. ax[0].set(xlim=[0,int((end_time-start_time)*1000)], ylabel='Neuron')
  901. f.savefig(join(figure_path,'Noether_DY02_decode_snippet_pf_percentile_1.pdf'), format='pdf')
  902. # %% [markdown]
  903. # ### Nonlocal decoding per immobility bout and total % of immobility nonlocal
  904. # %%
  905. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  906. immob_data_by_session, immob_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility')
  907. local_data_by_session, local_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_local_immobility')
  908. nonlocal_data_by_session, nonlocal_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_nonlocal_immobility')
  909. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  910. SWR_data_by_session, SWR_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'CA1_SWR')
  911. HSE_data_by_session, HSE_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'CA1_HSE')
  912. # %%
  913. plot_seq_metrics_over_events(immob_data_by_seq.Animal, immob_data_by_seq.Percent_non_local_content, figure_path,
  914. 'perc_immob_content_nl_per_bout', '% of Each Immobility Bout Decoded Non-Locally', [0,1])
  915. # %%
  916. print(f"{np.nanmean(immob_data_by_session['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session['non_local'],axis=1)):.02f}" +\
  917. f"% of immobility bouts have nonlocal coding,\n" +\
  918. f"and {np.nanmean(immob_data_by_session['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session['frac_non_local'],axis=1)):.02f}" +\
  919. f"% of time during immobility "+\
  920. "is spent representing nonlocal positions.")
  921. # %%
  922. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session['non_local'], figure_path,
  923. 'immob_with_nl', '% Immobility Bouts with NonLocal Decode', [0,100])
  924. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session['frac_non_local'], figure_path,
  925. 'perc_immob_content_nl', '% Immobility Time with NonLocal Decode', [0,100])
  926. # %% [markdown]
  927. # ### Duration of non-local coding
  928. # %%
  929. mean_dur = np.mean(nonlocal_data_by_seq.groupby(['Animal'])['Duration'].mean().values)
  930. sem_dur = stats.sem(nonlocal_data_by_seq.groupby(['Animal'])['Duration'].mean().values)
  931. print(f"Periods of non-local coding during immobility last on average "+
  932. f"{mean_dur:.03f} +/- {sem_dur:.03f} seconds.")
  933. # %%
  934. mean_dur = np.mean(SWR_data_by_seq.groupby(['Animal'])['Duration'].mean().values)
  935. sem_dur = stats.sem(SWR_data_by_seq.groupby(['Animal'])['Duration'].mean().values)
  936. print(f"SWRs last on average "+
  937. f"{mean_dur:.03f} +/- {sem_dur:.03f} seconds.")
  938. # %%
  939. plot_seq_metrics_over_events(nonlocal_data_by_seq.Animal, nonlocal_data_by_seq.Duration, figure_path,
  940. 'nl_dur', 'Length of Non-Local Decodes (s)', [0,1])
  941. # %% [markdown]
  942. # ### Immobility duration vs decode distance
  943. # %%
  944. # Non-local coding is more common during longer bouts of immobility
  945. plot_seq_metrics_comparison_over_events(immob_data_by_seq, 'Duration', 'Non_local',
  946. figure_path, 'immob_local_vs_nonlocal_duration', 'Duration (s)', [0,10], [0,4])
  947. # %% [markdown]
  948. # # Figure S3
  949. # %% [markdown]
  950. # ### Confusion Matrix: actual vs decoded position in MEC during movement
  951. # %%
  952. actual_pos = np.array([])
  953. decoded_pos = np.array([])
  954. start_time = time.time()
  955. for _, row in sessions.iterrows():
  956. sess_idx = int(row['Session'][-2:])-1
  957. if not row['Recording_Error'] and not row['Position_Error'] and \
  958. row['Task'] == 'X Maze' and sess_idx<=10:
  959. animal_idx = animal_dict[row['Animal']]
  960. binned_data = Binned_Data(join(row['Base_Directory'],
  961. 'Preprocessed_Data/Sequences/Binned_Data',
  962. row['File']+'_MEC_binned_data.txt'))
  963. classifier = Classifier_Model(join(row['Base_Directory'],
  964. 'Preprocessed_Data/Sequences/Classifier_Model',
  965. row['File']+'_MEC_classifier_model.txt'))
  966. # get movement times
  967. move_ts = binned_data.position.index[binned_data.position.Velocity>=2].values
  968. # get current position
  969. actual_pos = np.append(actual_pos, binned_data.position.Linear[move_ts])
  970. # get decoded linearized bins and map to linearized position
  971. cr = classifier.classifier_results.sel(time=move_ts)
  972. decoded_bins = cr.sum("state").acausal_posterior.argmax("position").values
  973. bins_to_linear = classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position']
  974. decoded_pos = np.append(decoded_pos, bins_to_linear[decoded_bins])
  975. print(f"{row['File']} {time.time()-start_time}")
  976. df = pd.DataFrame({'actual_position': actual_pos,
  977. 'MEC_decode_position': decoded_pos})
  978. # %%
  979. df.to_csv(join(figure_path,'position_vs_MEC_decode_during_movement.csv'), index=False)
  980. # %%
  981. # heatmap of actual vs decoded positions
  982. heatmap, xedges, yedges = np.histogram2d(df.loc[:,'actual_position'], df.loc[:,'MEC_decode_position'], bins=[26,26])
  983. extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
  984. cmap = mpl.cm.get_cmap("viridis").copy()
  985. fig, ax = plt.subplots()
  986. norm_heatmap = heatmap.T/np.sum(heatmap, axis=1)
  987. hmap = ax.imshow(norm_heatmap, extent=extent, origin='lower', cmap=cmap, vmax=0.5)
  988. fig.colorbar(hmap)
  989. ax.set(xlabel='Actual Linearized Position (cm)', ylabel='MEC Decoded Linearized Position (cm)')
  990. fig.savefig(join(figure_path,'position_vs_MEC_decode_during_movement_positionnorm.pdf'), format='pdf')
  991. # %% [markdown]
  992. # ### non-local coding is common across days
  993. # %%
  994. days = np.arange(1,11)
  995. fig, ax = plt.subplots()
  996. for _, animal_metric in enumerate(immob_data_by_session['non_local']):
  997. animal_metric = animal_metric[:10]
  998. ax.plot(days[~np.isnan(animal_metric)], animal_metric[~np.isnan(animal_metric)])
  999. ax.plot(np.arange(1,11), np.nanmean(immob_data_by_session['non_local'].T[:10],1), 'k', linewidth=2)
  1000. ax.set(xlabel='Day', ylabel='% Immobility Bouts with NonLocal Decode', ylim=[0, 100])
  1001. fig.savefig(join(figure_path,f'immob_with_nl_over_days.pdf'), format='pdf')
  1002. fig, ax = plt.subplots()
  1003. for _, animal_metric in enumerate(immob_data_by_session['frac_non_local']):
  1004. animal_metric = animal_metric[:10]
  1005. ax.plot(days[~np.isnan(animal_metric)], animal_metric[~np.isnan(animal_metric)])
  1006. ax.plot(np.arange(1,11), np.nanmean(immob_data_by_session['frac_non_local'].T[:10],1), 'k', linewidth=2)
  1007. ax.set(xlabel='Day', ylabel='% Immobility Time with NonLocal Decode', ylim=[0, 100])
  1008. fig.savefig(join(figure_path,f'perc_immob_content_nl.pdf'), format='pdf')
  1009. # %% [markdown]
  1010. # ### pie chart: % of local & non-local immobility classified as stationary, continuous, fragmented
  1011. # %%
  1012. f, ax = plt.subplots(1,3)
  1013. ax[0].pie([np.sum(immob_data_by_seq['Percent_stationary']*immob_data_by_seq['Duration']), \
  1014. np.sum(immob_data_by_seq['Percent_continuous']*immob_data_by_seq['Duration']), \
  1015. np.sum(immob_data_by_seq['Percent_fragmented']*immob_data_by_seq['Duration'])], \
  1016. labels=['stat','cont','frag'])
  1017. ax[1].pie([np.sum(local_data_by_seq['Percent_stationary']*local_data_by_seq['Duration']), \
  1018. np.sum(local_data_by_seq['Percent_continuous']*local_data_by_seq['Duration']), \
  1019. np.sum(local_data_by_seq['Percent_fragmented']*local_data_by_seq['Duration'])], \
  1020. labels=['stat','cont','frag'])
  1021. ax[2].pie([np.sum(nonlocal_data_by_seq['Percent_stationary']*nonlocal_data_by_seq['Duration']), \
  1022. np.sum(nonlocal_data_by_seq['Percent_continuous']*nonlocal_data_by_seq['Duration']), \
  1023. np.sum(nonlocal_data_by_seq['Percent_fragmented']*nonlocal_data_by_seq['Duration'])], \
  1024. labels=['stat','cont','frag'])
  1025. print(f"{np.sum(immob_data_by_seq['Percent_fragmented']*immob_data_by_seq['Duration'])/np.sum(immob_data_by_seq['Duration']):.02f}% of all immobility, " +
  1026. f"{np.sum(local_data_by_seq['Percent_fragmented']*local_data_by_seq['Duration'])/np.sum(local_data_by_seq['Duration']):.02f}% of local immobility, " +
  1027. f"and {np.sum(nonlocal_data_by_seq['Percent_fragmented']*nonlocal_data_by_seq['Duration'])/np.sum(nonlocal_data_by_seq['Duration']):.02f}% of nonlocal immobility " +
  1028. "is fragmented.")
  1029. f.savefig(join(figure_path,'classification.pdf'), format='pdf')
  1030. # %% [markdown]
  1031. # ### local vs nonlocal quality check: variance of posteriors
  1032. # %%
  1033. # match durations
  1034. nonlocal_equivalent = []
  1035. for a in local_data_by_seq.Animal.unique():
  1036. start_idx = np.min(np.where(nonlocal_data_by_seq.Animal==a))
  1037. for __, dur in local_data_by_seq.loc[local_data_by_seq.Animal==a].Duration.items():
  1038. idx = np.abs(nonlocal_data_by_seq.loc[nonlocal_data_by_seq.Animal==a].Duration - dur).argmin()
  1039. nonlocal_equivalent.append(start_idx + idx)
  1040. is_non_local = []
  1041. is_non_local.extend([False] * len(local_data_by_seq))
  1042. is_non_local.extend([True] * len(nonlocal_data_by_seq.iloc[nonlocal_equivalent]))
  1043. local_nonlocal_equivalent_data_by_seq = pd.concat([local_data_by_seq, nonlocal_data_by_seq.iloc[nonlocal_equivalent]], ignore_index=True)
  1044. local_nonlocal_equivalent_data_by_seq.Non_local = is_non_local
  1045. local_nonlocal_equivalent_data_by_seq['Posterior_density_spread_percent_corrected'] = \
  1046. local_nonlocal_equivalent_data_by_seq.Posterior_density_spread_percent/local_nonlocal_equivalent_data_by_seq.Spatial_coverage_percent
  1047. # %%
  1048. plot_seq_metrics_comparison_over_events(local_nonlocal_equivalent_data_by_seq, 'Posterior_density_spread_percent_corrected', 'Non_local', figure_path,
  1049. 'local_vs_nonlocal_posterior_density_corrected', '% Track Covered by Posterior Density >0.95% Per % Track Covered', [0,100])
  1050. # %%
  1051. # these data structures are generated in Figure 3
  1052. with open(join(figure_path,'nonlocal_fields_data_by_seq2.pkl'), 'rb') as file:
  1053. nonlocal_fields_data_by_seq2 = pickle.load(file)
  1054. with open(join(figure_path,'local_fields_data_by_seq2.pkl'), 'rb') as file:
  1055. local_fields_data_by_seq2 = pickle.load(file)
  1056. # %%
  1057. # add posterior variance and spatial coverage
  1058. posterior_variance = []
  1059. spatial_coverage = []
  1060. for _, row in sessions.iterrows():
  1061. if not row['Recording_Error'] and not row['Position_Error'] and \
  1062. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1063. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1064. row['File']+'_MEC_local_immobility_sequences.txt'))
  1065. posterior_variance.extend(seq.stats.Posterior_density_spread_percent)
  1066. spatial_coverage.extend(seq.stats.Spatial_coverage_percent)
  1067. local_fields_data_by_seq2['Posterior_density_spread_percent'] = posterior_variance
  1068. local_fields_data_by_seq2['Spatial_coverage_percent'] = spatial_coverage
  1069. # repeat for nonlocal
  1070. posterior_variance = []
  1071. spatial_coverage = []
  1072. for _, row in sessions.iterrows():
  1073. if not row['Recording_Error'] and not row['Position_Error'] and \
  1074. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1075. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1076. row['File']+'_MEC_nonlocal_immobility_sequences.txt'))
  1077. posterior_variance.extend(seq.stats.Posterior_density_spread_percent)
  1078. spatial_coverage.extend(seq.stats.Spatial_coverage_percent)
  1079. nonlocal_fields_data_by_seq2['Posterior_density_spread_percent'] = posterior_variance
  1080. nonlocal_fields_data_by_seq2['Spatial_coverage_percent'] = spatial_coverage
  1081. # %%
  1082. # match durations
  1083. nonlocal_equivalent = []
  1084. for a in local_fields_data_by_seq2.Animal.unique():
  1085. start_idx = np.min(np.where(nonlocal_fields_data_by_seq2.Animal==a))
  1086. for __, dur in local_fields_data_by_seq2.loc[local_fields_data_by_seq2.Animal==a].Duration.items():
  1087. idx = np.abs(nonlocal_fields_data_by_seq2.loc[nonlocal_fields_data_by_seq2.Animal==a].Duration - dur).argmin()
  1088. nonlocal_equivalent.append(start_idx + idx)
  1089. is_non_local = []
  1090. is_non_local.extend([False] * len(local_fields_data_by_seq2))
  1091. is_non_local.extend([True] * len(nonlocal_fields_data_by_seq2.iloc[nonlocal_equivalent]))
  1092. local_nonlocal_fields_equivalent_data_by_seq = pd.concat([local_fields_data_by_seq2, nonlocal_fields_data_by_seq2.iloc[nonlocal_equivalent]], ignore_index=True)
  1093. local_nonlocal_fields_equivalent_data_by_seq['Non_local'] = is_non_local
  1094. local_nonlocal_fields_equivalent_data_by_seq['Posterior_density_spread_percent_corrected'] = \
  1095. local_nonlocal_fields_equivalent_data_by_seq.Posterior_density_spread_percent/local_nonlocal_fields_equivalent_data_by_seq.Spatial_coverage_percent
  1096. # %%
  1097. # calculate mean over 2D bins
  1098. local_fields_df = local_nonlocal_fields_equivalent_data_by_seq[~local_nonlocal_fields_equivalent_data_by_seq.Non_local]
  1099. local_cells = pd.cut(local_fields_df['Local_percent_cells']*100, bins=np.arange(0,110,10))
  1100. nonlocal_cells = pd.cut(local_fields_df['Nonlocal_percent_cells']*100, bins=np.arange(0,110,10))
  1101. local_fields_ppv = local_fields_df.groupby([local_cells, nonlocal_cells])['Posterior_density_spread_percent_corrected'].mean().unstack().T
  1102. # repeat for nonlocal coding events
  1103. nonlocal_fields_df = local_nonlocal_fields_equivalent_data_by_seq[local_nonlocal_fields_equivalent_data_by_seq.Non_local]
  1104. local_cells = pd.cut(nonlocal_fields_df['Local_percent_cells']*100, bins=np.arange(0,110,10))
  1105. nonlocal_cells = pd.cut(nonlocal_fields_df['Nonlocal_percent_cells']*100, bins=np.arange(0,110,10))
  1106. nonlocal_fields_ppv = nonlocal_fields_df.groupby([local_cells, nonlocal_cells])['Posterior_density_spread_percent_corrected'].mean().unstack().T
  1107. # %%
  1108. # heatmap over coding events, local vs nonlocal: x=% local cells, y=% nonlocal cells, z/color= mean corrected posterior probability variance
  1109. cmap = mpl.cm.get_cmap("viridis").copy()
  1110. cmap.set_bad(color='lightgrey')
  1111. fig, ax = plt.subplots()
  1112. sns.heatmap(local_fields_ppv, fmt=".2f", cmap=cmap, vmax=100, cbar_kws={'label': '% Track Covered by Posterior Density'})
  1113. plt.gca().invert_yaxis()
  1114. ax.set(xlabel='% Local Units', ylabel='% Non-Local Units', xticklabels=np.arange(0,100,10), yticklabels=np.arange(0,100,10))
  1115. fig.savefig(join(figure_path,'ppv_heatmap_local.pdf'), format='pdf')
  1116. fig, ax = plt.subplots()
  1117. sns.heatmap(nonlocal_fields_ppv, fmt=".2f", cmap=cmap, vmax=100, cbar_kws={'label': '% Track Covered by Posterior Density'})
  1118. plt.gca().invert_yaxis()
  1119. ax.set(xlabel='% Local Units', ylabel='% Non-Local Units', xticklabels=np.arange(0,100,10), yticklabels=np.arange(0,100,10))
  1120. fig.savefig(join(figure_path,'ppv_heatmap_nonlocal.pdf'), format='pdf')
  1121. cmap = mpl.cm.get_cmap("seismic").copy()
  1122. cmap.set_bad(color='lightgrey')
  1123. fig, ax = plt.subplots()
  1124. sns.heatmap((local_fields_ppv-nonlocal_fields_ppv), fmt=".2f", vmin=-50, vmax=50, cmap=cmap, cbar_kws={'label': '% Track Covered by Posterior Density: Local Events Minus Non-Local Events'})
  1125. plt.gca().invert_yaxis()
  1126. ax.set(xlabel='% Local Units', ylabel='% Non-Local Units', xticklabels=np.arange(0,100,10), yticklabels=np.arange(0,100,10))
  1127. fig.savefig(join(figure_path,'ppv_heatmap_delta.pdf'), format='pdf')
  1128. # %% [markdown]
  1129. # ### local vs nonlocal quality check: changes in # spikes, # active cells, or spatial info
  1130. # %%
  1131. is_non_local = []
  1132. is_non_local.extend([False] * len(local_data_by_seq))
  1133. is_non_local.extend([True] * len(nonlocal_data_by_seq))
  1134. local_nonlocal_data_by_seq = pd.concat([local_data_by_seq, nonlocal_data_by_seq], ignore_index=True)
  1135. local_nonlocal_data_by_seq.Non_local = is_non_local
  1136. # %%
  1137. # match durations
  1138. nonlocal_equivalent = []
  1139. for a in local_data_by_seq.Animal.unique():
  1140. start_idx = np.min(np.where(nonlocal_data_by_seq.Animal==a))
  1141. for __, dur in local_data_by_seq.loc[local_data_by_seq.Animal==a].Duration.items():
  1142. idx = np.abs(nonlocal_data_by_seq.loc[nonlocal_data_by_seq.Animal==a].Duration - dur).argmin()
  1143. nonlocal_equivalent.append(start_idx + idx)
  1144. is_non_local = []
  1145. is_non_local.extend([False] * len(local_data_by_seq))
  1146. is_non_local.extend([True] * len(nonlocal_data_by_seq.iloc[nonlocal_equivalent]))
  1147. local_nonlocal_equivalent_data_by_seq = pd.concat([local_data_by_seq, nonlocal_data_by_seq.iloc[nonlocal_equivalent]], ignore_index=True)
  1148. local_nonlocal_equivalent_data_by_seq.Non_local = is_non_local
  1149. # %%
  1150. plot_seq_metrics_comparison_over_events(local_nonlocal_equivalent_data_by_seq, 'Percent_units', 'Non_local', figure_path,
  1151. 'local_vs_nonlocal_active_units', '% Active Units', [0,100])
  1152. plot_seq_metrics_comparison_over_events(local_nonlocal_equivalent_data_by_seq, 'FR', 'Non_local', figure_path,
  1153. 'local_vs_nonlocal_FR_matched', 'FR', [0,50])
  1154. plot_seq_metrics_comparison_over_events(local_nonlocal_equivalent_data_by_seq, 'Spatial_information', 'Non_local', figure_path,
  1155. 'local_vs_nonlocal_spatial_info_matched', 'Spatial Information (bits/sec)', [0,1.5])
  1156. # %% [markdown]
  1157. # ### Non-local content with different decoders
  1158. # %%
  1159. # decoder trained on movement + immobility
  1160. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1161. immob_data_by_session_alt_decoder, _ = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility', modifier='_cv')
  1162. print(f"{np.nanmean(immob_data_by_session_alt_decoder['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['non_local'],axis=1)):.02f}" +\
  1163. f"% of immobility bouts have nonlocal coding,\n" +\
  1164. f"and {np.nanmean(immob_data_by_session_alt_decoder['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['frac_non_local'],axis=1)):.02f}" +\
  1165. f"% of time during immobility "+\
  1166. "is spent representing nonlocal positions.")
  1167. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['non_local'], figure_path,
  1168. 'immob_with_nl_alt_decoder_cv', '% Immobility Bouts with NonLocal Decode', [0,100])
  1169. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['frac_non_local'], figure_path,
  1170. 'perc_immob_content_nl_alt_decoder_cv', '% Immobility Time with NonLocal Decode', [0,100])
  1171. # %%
  1172. # plot position and decoded position for snippet of data
  1173. # open example
  1174. row = sessions.iloc[133]
  1175. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1176. row['File']+'_MEC_immobility_sequences_cv.txt'),
  1177. load_objects=True)
  1178. seq.plot_sequence(indices=[1,33,41], figure_path=f"{figure_path}/Noether_DY02_MEC_alt_decoder_CV")
  1179. # %%
  1180. # decoder trained on movement variance = 6 instead of 1
  1181. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1182. immob_data_by_session_alt_decoder, _ = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility', modifier='_var6')
  1183. print(f"{np.nanmean(immob_data_by_session_alt_decoder['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['non_local'],axis=1)):.02f}" +\
  1184. f"% of immobility bouts have nonlocal coding,\n" +\
  1185. f"and {np.nanmean(immob_data_by_session_alt_decoder['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['frac_non_local'],axis=1)):.02f}" +\
  1186. f"% of time during immobility "+\
  1187. "is spent representing nonlocal positions.")
  1188. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['non_local'], figure_path,
  1189. 'immob_with_nl_alt_decoder_var6', '% Immobility Bouts with NonLocal Decode', [0,100])
  1190. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['frac_non_local'], figure_path,
  1191. 'perc_immob_content_nl_alt_decoder_var6', '% Immobility Time with NonLocal Decode', [0,100])
  1192. # %%
  1193. # plot position and decoded position for snippet of data
  1194. # open example
  1195. row = sessions.iloc[133]
  1196. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1197. row['File']+'_MEC_immobility_sequences_var6.txt'),
  1198. load_objects=True)
  1199. seq.plot_sequence(indices=[1,33,41], figure_path=f"{figure_path}/Noether_DY02_MEC_alt_decoder_var6")
  1200. # %%
  1201. # Bayesian decoder
  1202. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1203. immob_data_by_session_alt_decoder, _ = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility', modifier='_bayes')
  1204. print(f"{np.nanmean(immob_data_by_session_alt_decoder['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['non_local'],axis=1)):.02f}" +\
  1205. f"% of immobility bouts have nonlocal coding,\n" +\
  1206. f"and {np.nanmean(immob_data_by_session_alt_decoder['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['frac_non_local'],axis=1)):.02f}" +\
  1207. f"% of time during immobility "+\
  1208. "is spent representing nonlocal positions.")
  1209. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['non_local'], figure_path,
  1210. 'immob_with_nl_alt_decoder_bayes', '% Immobility Bouts with NonLocal Decode', [0,100])
  1211. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['frac_non_local'], figure_path,
  1212. 'perc_immob_content_nl_alt_decoder_bayes', '% Immobility Time with NonLocal Decode', [0,100])
  1213. # %%
  1214. # plot position and decoded position for snippet of data
  1215. # open example
  1216. row = sessions.iloc[133]
  1217. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1218. row['File']+'_MEC_immobility_sequences_bayes.txt'),
  1219. load_objects=True)
  1220. # subset data
  1221. start_time = 1260
  1222. end_time = 1290
  1223. start_idx = np.argmin(np.abs(seq.binned_data.timestamps-start_time))
  1224. end_idx = np.argmin(np.abs(seq.binned_data.timestamps-end_time))
  1225. subset_timestamps = seq.binned_data.timestamps[start_idx:end_idx]
  1226. f, ax = plt.subplots()
  1227. time_slice = slice(start_time,end_time)
  1228. # add classifier
  1229. results = seq.classifier.classifier_results.sel(time=time_slice)
  1230. time = (results.time-start_time) * 1000
  1231. max_time = time.max()
  1232. cmap = copy.copy(plt.cm.get_cmap('bone_r'))
  1233. cmap.set_bad(color="lightgrey", alpha=1.0)
  1234. (
  1235. results
  1236. .assign_coords(time=time)
  1237. .acausal_posterior.sum("state")
  1238. .plot(
  1239. x="time",
  1240. y="position",
  1241. robust=True,
  1242. add_colorbar=False,
  1243. zorder=0,
  1244. rasterized=True,
  1245. cmap=cmap,
  1246. ax=ax
  1247. )
  1248. )
  1249. seq_position = seq.binned_data.position['Linear'].loc[time_slice]
  1250. max_position = int(
  1251. np.ceil(seq.binned_data.position['Linear'].max()))
  1252. ax.plot(time, seq_position, linestyle="--", linewidth=2,
  1253. color="magenta", clip_on=False)
  1254. rtc.plot_graph_as_1D(seq.position.arena.track_graph,
  1255. edge_spacing=seq.position.arena.edge_spacing,
  1256. ax=ax, axis="y", other_axis_start=max_time+50)
  1257. f.savefig(join(figure_path,'Noether_DY02_decode_snippet_alt_decoder_bayes.pdf'), format='pdf')
  1258. # %%
  1259. # 2D decoder
  1260. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1261. immob_data_by_session_alt_decoder, _ = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility', modifier='_2D')
  1262. print(f"{np.nanmean(immob_data_by_session_alt_decoder['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['non_local'],axis=1)):.02f}" +\
  1263. f"% of immobility bouts have nonlocal coding,\n" +\
  1264. f"and {np.nanmean(immob_data_by_session_alt_decoder['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_alt_decoder['frac_non_local'],axis=1)):.02f}" +\
  1265. f"% of time during immobility "+\
  1266. "is spent representing nonlocal positions.")
  1267. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['non_local'], figure_path,
  1268. 'immob_with_nl_alt_decoder_2D', '% Immobility Bouts with NonLocal Decode', [0,100])
  1269. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_alt_decoder['frac_non_local'], figure_path,
  1270. 'perc_immob_content_nl_alt_decoder_2D', '% Immobility Time with NonLocal Decode', [0,100])
  1271. # %%
  1272. # plot position and decoded position for snippet of data
  1273. # open example
  1274. row = sessions.iloc[133]
  1275. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1276. row['File']+'_MEC_immobility_sequences_2D.txt'),
  1277. load_objects=True)
  1278. # subset data
  1279. start_time = 1260
  1280. end_time = 1290
  1281. start_idx = np.argmin(np.abs(seq.binned_data.timestamps-start_time))
  1282. end_idx = np.argmin(np.abs(seq.binned_data.timestamps-end_time))
  1283. subset_timestamps = seq.binned_data.timestamps[start_idx:end_idx]
  1284. f, ax = plt.subplots()
  1285. time_slice = slice(start_time,end_time)
  1286. # add classifier
  1287. results = seq.classifier.classifier_results.sel(time=time_slice)
  1288. time = (results.time-start_time) * 1000
  1289. max_time = time.max()
  1290. cmap = copy.copy(plt.cm.get_cmap('bone_r'))
  1291. cmap.set_bad(color="lightgrey", alpha=1.0)
  1292. (
  1293. results
  1294. .assign_coords(time=time)
  1295. .acausal_posterior.sum("state")
  1296. .plot(
  1297. x="time",
  1298. y="position",
  1299. robust=True,
  1300. add_colorbar=False,
  1301. zorder=0,
  1302. rasterized=True,
  1303. cmap=cmap,
  1304. ax=ax
  1305. )
  1306. )
  1307. seq_position = seq.binned_data.position['Linear'].loc[time_slice]
  1308. max_position = int(
  1309. np.ceil(seq.binned_data.position['Linear'].max()))
  1310. ax.plot(time, seq_position, linestyle="--", linewidth=2,
  1311. color="magenta", clip_on=False)
  1312. rtc.plot_graph_as_1D(seq.position.arena.track_graph,
  1313. edge_spacing=seq.position.arena.edge_spacing,
  1314. ax=ax, axis="y", other_axis_start=max_time+50)
  1315. f.savefig(join(figure_path,'Noether_DY02_decode_snippet_alt_decoder_2D.pdf'), format='pdf')
  1316. # %% [markdown]
  1317. # # Figure 3 & S4
  1318. # %% [markdown]
  1319. # ### example cells with fields at local and non-local positions
  1320. # %%
  1321. # repeat raster with color coding by local/nonlocal
  1322. # first, load the interval
  1323. row = sessions.iloc[133]
  1324. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1325. row['File']+'_MEC_immobility_sequences.txt'),
  1326. load_objects=True)
  1327. print(seq.name)
  1328. start_time = seq.intervals.intervals.iloc[1].start_time
  1329. end_time = seq.intervals.intervals.iloc[1].end_time
  1330. start_idx = np.argmin(np.abs(seq.binned_data.timestamps-start_time))
  1331. end_idx = np.argmin(np.abs(seq.binned_data.timestamps-end_time))
  1332. subset_timestamps = seq.binned_data.timestamps[start_idx:end_idx]
  1333. # %%
  1334. # rasters: single cells
  1335. field_locs, field_peaks, field_sizes, field_spacing = seq.find_fields()
  1336. pf = seq.classifier.classifier.place_fields_[('', 0)]
  1337. n_cells = pf.shape[1]
  1338. decoded_bin = [59]
  1339. local_cells = []
  1340. for u in range(n_cells):
  1341. for f in field_locs[u]:
  1342. if np.any(np.isin(decoded_bin, f)):
  1343. local_cells.append(u)
  1344. local_cells = np.unique(np.asarray(local_cells))
  1345. decoded_bin = [29,92,93,94,95,96]
  1346. nonlocal_cells = []
  1347. for u in range(n_cells):
  1348. for f in field_locs[u]:
  1349. if np.any(np.isin(decoded_bin, f)):
  1350. nonlocal_cells.append(u)
  1351. nonlocal_cells = np.unique(np.asarray(nonlocal_cells))
  1352. # %%
  1353. for ex_cell in [20, 53, 30]:
  1354. interval_spikes = seq.binned_data.spikes[:,start_idx:end_idx]
  1355. f, ax = plt.subplots(1, figsize=(8,3))
  1356. # plot spikes from cells contributing to decode (over all time) in black
  1357. spike_time_ind = np.nonzero(interval_spikes[ex_cell, :])[0]
  1358. ax.scatter(1000*(subset_timestamps[spike_time_ind]-start_time),
  1359. np.ones(len(spike_time_ind)), color='black', zorder=1,
  1360. marker='|', linewidth=1)
  1361. # plot spikes contributing to nonlocal decode in cyan during non-local times
  1362. for i in range(len(nonlocal_intervals)):
  1363. interval_spikes = seq.binned_data.spikes[:,int(start_idx+nonlocal_intervals[i][0]*5):int(start_idx+nonlocal_intervals[i][1]*5)]
  1364. spike_time_ind_nl = np.nonzero(interval_spikes[ex_cell, :])[0]
  1365. ax.scatter(1000*(subset_timestamps[nonlocal_intervals[i][0]*5+spike_time_ind_nl]-start_time),
  1366. np.ones(len(spike_time_ind_nl)), color='cyan', zorder=1,
  1367. marker='|', linewidth=2)
  1368. ax.set(xlim=[0,int((end_time-start_time)*1000)], ylabel='Neuron')
  1369. f.savefig(join(figure_path,f'Noether_DY02_MEC_{ex_cell}_raster.pdf'), format='pdf')
  1370. # %%
  1371. bin_cm = 2
  1372. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1373. row['File']+'_imec1_electrodes.txt'))
  1374. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  1375. seq.spikes.subset_by_channel(MEC_channels)
  1376. for u in [20, 53, 30]:
  1377. fr_map, xbins, ybins, max_fr = calc_fr_map(seq.spikes.spikes[u], seq.position, bin_cm, smooth=True)
  1378. print(f'Unit {u} Max FR {max_fr}')
  1379. f = plot_heatmap(fr_map, xbins, ybins)
  1380. f.savefig(join(figure_path,f'Noether_DY02_MEC_{u}_heatmap.pdf'), format='pdf')
  1381. # %% [markdown]
  1382. # ### Cells that decode to non-local vs local positions during non-local vs local times
  1383. # %%
  1384. # participation of L vs NL coding cells during L vs NL intervals (L + NL multi-field cells?, L always active?)
  1385. #### for non-local times
  1386. nl_perc_cells, nl_perc_spikes, l_perc_cells, l_perc_spikes, lnl_perc_cells, lnl_perc_spikes, \
  1387. animal, session, dur = [[] for _ in range(9)]
  1388. for _, row in sessions.iterrows():
  1389. if not row['Recording_Error'] and not row['Position_Error'] and \
  1390. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1391. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1392. row['File']+'_MEC_nonlocal_immobility_sequences.txt'),
  1393. load_objects=True)
  1394. animal_idx = animal_dict[row['Animal']]
  1395. sess_idx = int(row['Session'][-2:])-1
  1396. animal.extend([animal_idx] * len(seq.stats))
  1397. session.extend([sess_idx] * len(seq.stats))
  1398. dur.extend(seq.stats.Duration)
  1399. linear_to_bins = np.array(seq.classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position'])
  1400. pf = seq.classifier.classifier.place_fields_[('', 0)]
  1401. n_cells = pf.shape[1]
  1402. for s, interval in seq.intervals.intervals.iterrows():
  1403. cr = seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  1404. map_position_ind = cr.sum("state").acausal_posterior.argmax("position").values
  1405. decoded_bins = np.unique(map_position_ind)
  1406. linear_pos = np.asarray(seq.binned_data.position["Linear"].loc[interval.start_time:interval.end_time])
  1407. animal_bins = np.unique(np.argmin(np.abs(linear_to_bins[:,np.newaxis]-linear_pos), axis=0))
  1408. idx = seq.intervals.indices.iloc[s]
  1409. # find cells with significant fields at decoded locations
  1410. field_cells = []
  1411. for u in range(n_cells):
  1412. for f in field_locs[animal_idx][sess_idx][u]:
  1413. if np.any(np.isin(decoded_bins, f)):
  1414. field_cells.append(u)
  1415. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1416. # calculate proportion of all cells with fields,
  1417. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1418. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1419. nl_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1420. nl_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1421. # repeat for cells with fields at current location
  1422. field_cells = []
  1423. for u in range(n_cells):
  1424. for f in field_locs[animal_idx][sess_idx][u]:
  1425. if np.any(np.isin(animal_bins, f)):
  1426. field_cells.append(u)
  1427. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1428. # calculate proportion of all cells with fields,
  1429. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1430. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1431. l_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1432. l_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1433. # repeat for cells with fields at current AND decoded location
  1434. local_cells = field_cells
  1435. field_cells = []
  1436. for u in local_cells:
  1437. for f in field_locs[animal_idx][sess_idx][u]:
  1438. if np.any(np.isin(decoded_bins, f)):
  1439. field_cells.append(u)
  1440. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1441. # calculate proportion of all cells with fields,
  1442. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1443. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1444. lnl_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1445. lnl_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1446. print(row['File'])
  1447. # append onto larger data_seq
  1448. nonlocal_fields_data_by_seq2 = pd.DataFrame({'Animal': animal, 'Session': session, 'Duration': dur,
  1449. 'Nonlocal_percent_cells': nl_perc_cells,
  1450. 'Nonlocal_percent_spikes': nl_perc_spikes,
  1451. 'Local_percent_cells': l_perc_cells,
  1452. 'Local_percent_spikes': l_perc_spikes,
  1453. 'Both_percent_cells': lnl_perc_cells,
  1454. 'Both_percent_spikes': lnl_perc_spikes})
  1455. # %%
  1456. #### for local times
  1457. nl_perc_cells, nl_perc_spikes, l_perc_cells, l_perc_spikes, lnl_perc_cells, lnl_perc_spikes, \
  1458. animal, session, dur = [[] for _ in range(9)]
  1459. for _, row in sessions.iterrows():
  1460. if not row['Recording_Error'] and not row['Position_Error'] and \
  1461. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1462. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1463. row['File']+'_MEC_local_immobility_sequences.txt'),
  1464. load_objects=True)
  1465. animal_idx = animal_dict[row['Animal']]
  1466. sess_idx = int(row['Session'][-2:])-1
  1467. animal.extend([animal_idx] * len(seq.stats))
  1468. session.extend([sess_idx] * len(seq.stats))
  1469. dur.extend(seq.stats.Duration)
  1470. linear_to_bins = np.array(seq.classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position'])
  1471. pf = seq.classifier.classifier.place_fields_[('', 0)]
  1472. n_cells = pf.shape[1]
  1473. for s, interval in seq.intervals.intervals.iterrows():
  1474. cr = seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  1475. map_position_ind = cr.sum("state").acausal_posterior.argmax("position").values
  1476. decoded_bins = np.unique(map_position_ind)
  1477. linear_pos = np.asarray(seq.binned_data.position["Linear"].loc[interval.start_time:interval.end_time])
  1478. animal_bins = np.unique(np.argmin(np.abs(linear_to_bins[:,np.newaxis]-linear_pos), axis=0))
  1479. idx = seq.intervals.indices.iloc[s]
  1480. # find cells with significant fields at decoded locations
  1481. field_cells = []
  1482. for u in range(n_cells):
  1483. for f in field_locs[animal_idx][sess_idx][u]:
  1484. if np.any(np.isin(decoded_bins, f)):
  1485. field_cells.append(u)
  1486. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1487. # calculate proportion of all cells with fields,
  1488. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1489. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1490. nl_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1491. nl_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1492. # repeat for cells with fields at current location
  1493. field_cells = []
  1494. for u in range(n_cells):
  1495. for f in field_locs[animal_idx][sess_idx][u]:
  1496. if np.any(np.isin(animal_bins, f)):
  1497. field_cells.append(u)
  1498. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1499. # calculate proportion of all cells with fields,
  1500. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1501. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1502. l_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1503. l_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1504. # repeat for cells with fields at current AND decoded location
  1505. local_cells = field_cells
  1506. field_cells = []
  1507. for u in local_cells:
  1508. for f in field_locs[animal_idx][sess_idx][u]:
  1509. if np.any(np.isin(decoded_bins, f)):
  1510. field_cells.append(u)
  1511. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1512. # calculate proportion of all cells with fields,
  1513. # and of those: proportion active, fr, number of fields per cell, and size and spacing of those fields
  1514. seq_spikes = seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']]
  1515. lnl_perc_cells.append(np.sum(np.sum(seq_spikes, axis=1)>0)/seq.stats.Num_units[s])
  1516. lnl_perc_spikes.append(np.sum(seq_spikes)/seq.stats.Num_spikes[s])
  1517. print(row['File'])
  1518. # append onto larger data_seq
  1519. local_fields_data_by_seq2 = pd.DataFrame({'Animal': animal, 'Session': session, 'Duration': dur,
  1520. 'Nonlocal_percent_cells': nl_perc_cells,
  1521. 'Nonlocal_percent_spikes': nl_perc_spikes,
  1522. 'Local_percent_cells': l_perc_cells,
  1523. 'Local_percent_spikes': l_perc_spikes,
  1524. 'Both_percent_cells': lnl_perc_cells,
  1525. 'Both_percent_spikes': lnl_perc_spikes})
  1526. # %%
  1527. with open(join(figure_path,'nonlocal_fields_data_by_seq2.pkl'), 'wb') as file:
  1528. pickle.dump(nonlocal_fields_data_by_seq2, file)
  1529. with open(join(figure_path,'local_fields_data_by_seq2.pkl'), 'wb') as file:
  1530. pickle.dump(local_fields_data_by_seq2, file)
  1531. # %%
  1532. plot_compare_paired_metrics('Local_percent_cells', 'Nonlocal_percent_cells', nonlocal_fields_data_by_seq2, figure_path,
  1533. 'nonlocal_l_vs_nl_field_cells', '% Active Cells That Have Fields at Location', [0,1])
  1534. plot_compare_paired_metrics('Nonlocal_percent_cells', 'Both_percent_cells', nonlocal_fields_data_by_seq2, figure_path,
  1535. 'nonlocal_both_vs_nl_field_cells', '% Active Cells That Have Fields at Location', [0,1])
  1536. plot_compare_paired_metrics('Local_percent_spikes', 'Nonlocal_percent_spikes', nonlocal_fields_data_by_seq2, figure_path,
  1537. 'nonlocal_l_vs_nl_field_spikes', '% Spikes from Cells That Have Fields at Location', [0,1])
  1538. plot_compare_paired_metrics('Nonlocal_percent_spikes', 'Both_percent_spikes', nonlocal_fields_data_by_seq2, figure_path,
  1539. 'nonlocal_both_vs_nl_field_spikes', '% Spikes from Cells That Have Fields at Location', [0,1])
  1540. # plot_compare_paired_metrics('Local_percent_cells', 'Nonlocal_percent_cells', local_fields_data_by_seq2, figure_path,
  1541. # 'local_l_vs_nl_field_cells', '% Active Cells That Have Fields at Location', [0,1])
  1542. # plot_compare_paired_metrics('Nonlocal_percent_cells', 'Both_percent_cells', local_fields_data_by_seq2, figure_path,
  1543. # 'local_both_vs_nl_field_cells', '% Active Cells That Have Fields at Location', [0,1])
  1544. # plot_compare_paired_metrics('Local_percent_spikes', 'Nonlocal_percent_spikes', local_fields_data_by_seq2, figure_path,
  1545. # 'local_l_vs_nl_field_spikes', '% Spikes from Cells That Have Fields at Location', [0,1])
  1546. # plot_compare_paired_metrics('Nonlocal_percent_spikes', 'Both_percent_spikes', local_fields_data_by_seq2, figure_path,
  1547. # 'local_both_vs_nl_field_spikes', '% Spikes from Cells That Have Fields at Location', [0,1])
  1548. # %%
  1549. is_non_local = []
  1550. is_non_local.extend([False] * len(local_fields_data_by_seq2))
  1551. is_non_local.extend([True] * len(nonlocal_fields_data_by_seq2))
  1552. local_nonlocal_fields_equivalent_data_by_seq2 = pd.concat([local_fields_data_by_seq2, nonlocal_fields_data_by_seq2], ignore_index=True)
  1553. local_nonlocal_fields_equivalent_data_by_seq2.loc[:,'Non_local'] = is_non_local
  1554. plot_seq_metrics_comparison_over_events(local_nonlocal_fields_equivalent_data_by_seq2, 'Local_percent_cells', 'Non_local', figure_path,
  1555. 'local_vs_nonlocal_l_perc_cells', '% Active Cells That Have Fields at Location', [0,1])
  1556. plot_seq_metrics_comparison_over_events(local_nonlocal_fields_equivalent_data_by_seq2, 'Local_percent_spikes', 'Non_local', figure_path,
  1557. 'local_vs_nonlocal_l_perc_spikes', '% Spikes from Cells That Have Fields at Location', [0,1])
  1558. # %% [markdown]
  1559. # ### are decodes less non-local without non-local spiking
  1560. # %%
  1561. # remove these spikes from test set to show now decodes are only local
  1562. for _, row in sessions.iterrows():
  1563. if not row['Recording_Error'] and not row['Position_Error'] and \
  1564. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1565. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1566. row['File']+'_MEC_nonlocal_immobility_sequences.txt'),
  1567. load_objects=True)
  1568. animal_idx = animal_dict[row['Animal']]
  1569. sess_idx = int(row['Session'][-2:])-1
  1570. linear_to_bins = np.array(seq.classifier.classifier.environments[0].place_bin_centers_nodes_df_['linear_position'])
  1571. pf = seq.classifier.classifier.place_fields_[('', 0)]
  1572. n_cells = pf.shape[1]
  1573. for s, interval in seq.intervals.intervals.iterrows():
  1574. cr = seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  1575. map_position_ind = cr.sum("state").acausal_posterior.argmax("position").values
  1576. decoded_bins = np.unique(map_position_ind)
  1577. idx = seq.intervals.indices.iloc[s]
  1578. # find cells with significant fields at decoded locations
  1579. field_cells = []
  1580. for u in range(n_cells):
  1581. for f in field_locs[animal_idx][sess_idx][u]:
  1582. if np.any(np.isin(decoded_bins, f)):
  1583. field_cells.append(u)
  1584. field_cells = np.unique(np.asarray(field_cells, dtype=int))
  1585. # remove non-local decode spikes
  1586. seq.binned_data.spikes[field_cells, idx['start_index']:idx['end_index']] = 0
  1587. # save binned data
  1588. binned_data = Binned_Data(seq.binned_data.name)
  1589. binned_data.spikes = seq.binned_data.spikes
  1590. binned_data.name = seq.binned_data.name[:-4]+'_NLspikesremoved.txt'
  1591. binned_data.save()
  1592. print(row['File'])
  1593. # %%
  1594. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1595. immob_data_by_session_CLtest, immob_data_by_seq_CLtest = combine_metrics_over_sessions(sessions, animal_dict, 'MEC_immobility_NLspikesremoved')
  1596. # %%
  1597. print(f"{np.nanmean(immob_data_by_session_CLtest['non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_CLtest['non_local'],axis=1)):.02f}" +\
  1598. f"% of immobility bouts have nonlocal coding,\n" +\
  1599. f"and {np.nanmean(immob_data_by_session_CLtest['frac_non_local']):.02f} +/- {stats.sem(np.nanmean(immob_data_by_session_CLtest['frac_non_local'],axis=1)):.02f}" +\
  1600. f"% of time during immobility "+\
  1601. "is spent representing nonlocal positions.")
  1602. # %%
  1603. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_CLtest['non_local'], figure_path,
  1604. 'immob_with_nl_CLtest', '% Immobility Bouts with NonLocal Decode', [0,100])
  1605. plot_seq_metrics_over_sessions(n_animals, immob_data_by_session_CLtest['frac_non_local'], figure_path,
  1606. 'perc_immob_content_nl_CLtest', '% Immobility Time with NonLocal Decode', [0,100])
  1607. # %% [markdown]
  1608. # ### Preferentially enriched cells
  1609. # %%
  1610. seq_type = 'MEC_nonlocal_immobility'
  1611. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1612. n_shuffles = 1000
  1613. n_sessions = 20
  1614. n_animals = len(animal_dict)
  1615. over_fr = np.full((n_animals, n_sessions), np.NaN)
  1616. over_fr_list = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  1617. over_percent_intervals = np.full((n_animals, n_sessions), np.NaN)
  1618. over_percent_intervals_list = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  1619. for _, row in sessions.iterrows():
  1620. if not row['Recording_Error'] and not row['Position_Error'] and \
  1621. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1622. # load spikes and intervals
  1623. binned_data = Binned_Data(join(row['Base_Directory'],
  1624. 'Preprocessed_Data/Sequences/Binned_Data',
  1625. row['File']+'_MEC_binned_data.txt'))
  1626. intervals = Sequence_Intervals(join(row['Base_Directory'],
  1627. 'Preprocessed_Data/Sequences/Intervals',
  1628. row['File']+'_'+seq_type+'_intervals.txt'))
  1629. seq = Sequences(join(row['Base_Directory'],
  1630. 'Preprocessed_Data/Sequences',
  1631. row['File']+'_'+seq_type+'_sequences.txt'))
  1632. interval_dur = intervals.intervals.end_time-intervals.intervals.start_time
  1633. animal_idx = animal_dict[row['Animal']]
  1634. sess_idx = int(row['Session'][-2:])-1
  1635. shuffled_fr = np.zeros((n_shuffles, binned_data.spikes.shape[0]))
  1636. shuffled_percent_intervals = np.zeros((n_shuffles, binned_data.spikes.shape[0]))
  1637. for s in range(n_shuffles):
  1638. # shuffle each cell separately
  1639. shuffled_spikes = binned_data.spikes[:, np.random.permutation(binned_data.spikes.shape[1])]
  1640. # calculate fr during intervals & participation in intervals
  1641. # (same code as calc_cell_metrics in Sequences class)
  1642. fr = []
  1643. percent_intervals = []
  1644. for c in range(binned_data.spikes.shape[0]):
  1645. fr_by_interval = 0
  1646. participating_seq = 0
  1647. for i, idx in intervals.indices.iterrows():
  1648. num_spikes = np.sum(shuffled_spikes[c, idx['start_index']:idx['end_index']])
  1649. fr_by_interval += num_spikes/interval_dur.iloc[i]
  1650. if num_spikes>0:
  1651. participating_seq += 1
  1652. fr.append(fr_by_interval/intervals.n_seq)
  1653. percent_intervals.append(participating_seq/intervals.n_seq)
  1654. # append to shuffle distribution
  1655. shuffled_fr[s,:] = fr
  1656. shuffled_percent_intervals[s,:] = percent_intervals
  1657. # calculate 95th percentile
  1658. fr_95_percentile = np.percentile(shuffled_fr, 95, axis=0)
  1659. percent_intervals_95_percentile = np.percentile(shuffled_percent_intervals, 95, axis=0)
  1660. # count how many cells are above this threshold
  1661. over_fr[animal_idx, sess_idx] = np.nanmean(seq.stats_by_cell.FR>fr_95_percentile)
  1662. over_fr_list[animal_idx][sess_idx] = np.where(seq.stats_by_cell.FR>fr_95_percentile)[0].tolist()
  1663. over_percent_intervals[animal_idx, sess_idx] = \
  1664. np.nanmean(seq.stats_by_cell.Percent_intervals>percent_intervals_95_percentile)
  1665. over_percent_intervals_list[animal_idx][sess_idx] = \
  1666. np.where(seq.stats_by_cell.Percent_intervals>percent_intervals_95_percentile)[0].tolist()
  1667. print(row['File'])
  1668. # %%
  1669. with open(join(figure_path,'over_fr.pkl'), 'wb') as file:
  1670. pickle.dump(over_fr, file)
  1671. with open(join(figure_path,'over_percent_intervals.pkl'), 'wb') as file:
  1672. pickle.dump(over_percent_intervals, file)
  1673. with open(join(figure_path,'over_fr_list.pkl'), 'wb') as file:
  1674. pickle.dump(over_fr_list, file)
  1675. with open(join(figure_path,'over_percent_intervals_list.pkl'), 'wb') as file:
  1676. pickle.dump(over_percent_intervals_list, file)
  1677. # %%
  1678. print(f"{np.nanmean(over_fr)*100:.02f} +/- {stats.sem(np.nanmean(over_fr,axis=1))*100:.02f}" +\
  1679. f"% of cells have > 95th percentile of shuffled FR during nonlocal coding.")
  1680. # %%
  1681. # plot
  1682. plot_seq_metrics_over_sessions(n_animals, over_fr*100, figure_path,
  1683. 'recruited_fr', '% Units with FR > 95th Percentile', [0,100])
  1684. # %% [markdown]
  1685. # ### DV axis
  1686. # %%
  1687. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1688. n_sessions = 10
  1689. n_animals = len(animal_dict)
  1690. dist_from_border = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  1691. for _, row in sessions.iterrows():
  1692. if not row['Recording_Error'] and not row['Position_Error'] and \
  1693. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1694. animal_idx = animal_dict[row['Animal']]
  1695. sess_idx = int(row['Session'][-2:])-1
  1696. seq = Sequences(join(row['Base_Directory'],
  1697. 'Preprocessed_Data/Sequences',
  1698. row['File']+'_MEC_nonlocal_immobility_sequences.txt'))
  1699. if ('2021_pilot' in row['Base_Directory']) or ('2022_winter' in row['Base_Directory']):
  1700. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1701. row['File']+'_electrodes.txt'))
  1702. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  1703. spikes = Spikes(join(row['Base_Directory'],
  1704. 'Preprocessed_Data/Spikes/g1',
  1705. row['File']+'_imec0_spikes.txt'))
  1706. spikes.subset_by_channel(channels)
  1707. dist_from_border[animal_idx][sess_idx] = [depth - border_dict[row['Animal']] for depth in electrodes.electrodes.Rel_Z.iloc[spikes.spike_channel].values.tolist()]
  1708. else:
  1709. if (row['Animal']=='Lamarr') and (sess_idx>=7):
  1710. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1711. row['File']+'_imec0_electrodes.txt'))
  1712. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  1713. row['File']+'_imec0_spikes.txt'))
  1714. else:
  1715. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1716. row['File']+'_imec1_electrodes.txt'))
  1717. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  1718. row['File']+'_imec1_spikes.txt'))
  1719. channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  1720. spikes.subset_by_channel(channels)
  1721. shanks = (electrodes.electrodes.Rel_Y.iloc[spikes.spike_channel].values/250).astype(int)
  1722. depths = electrodes.electrodes.Rel_Z.iloc[spikes.spike_channel].values
  1723. for i, d in enumerate(depths):
  1724. dist_from_border[animal_idx][sess_idx].append(border_dict[row['Animal']][shanks[i]] - d)
  1725. # %%
  1726. with open(join(figure_path,'dist_from_border.pkl'), 'wb') as file:
  1727. pickle.dump(dist_from_border, file)
  1728. # %%
  1729. # bin cells by DV depth
  1730. n_sessions = 10
  1731. dv_bin1, dv_bin2, dv_bin3, dv_bin4 = [[[[] for _ in range(n_sessions)] for _ in range(n_animals)] for _ in range(4)]
  1732. for animal_idx in range(n_animals):
  1733. for sess_idx in range(n_sessions):
  1734. dv_bin1[animal_idx][sess_idx] = np.where([i<500 for i in dist_from_border[animal_idx][sess_idx]])[0]
  1735. dv_bin2[animal_idx][sess_idx] = np.where([i>=500 and i<1000 for i in dist_from_border[animal_idx][sess_idx]])[0]
  1736. dv_bin3[animal_idx][sess_idx] = np.where([i>=1000 and i<1500 for i in dist_from_border[animal_idx][sess_idx]])[0]
  1737. dv_bin4[animal_idx][sess_idx] = np.where([i>=1500 for i in dist_from_border[animal_idx][sess_idx]])[0]
  1738. # %%
  1739. # compare DV depths of enriched vs unenriched cells
  1740. sess_idx = 0
  1741. percentile = over_fr_list
  1742. enriched_cells = []
  1743. enriched_animals = []
  1744. unenriched_cells = []
  1745. unenriched_animals = []
  1746. for animal_idx in range(len(animal_dict)):
  1747. unenriched_cells.extend([dist_from_border[animal_idx][sess_idx][i] for i in range(len(dist_from_border[animal_idx][sess_idx])) \
  1748. if i not in percentile[animal_idx][sess_idx]])
  1749. unenriched_animals.extend([animal_idx]*(len(dist_from_border[animal_idx][sess_idx]) - len(percentile[animal_idx][sess_idx])))
  1750. enriched_cells.extend([dist_from_border[animal_idx][sess_idx][i] for i in percentile[animal_idx][sess_idx]])
  1751. enriched_animals.extend([animal_idx]*len(percentile[animal_idx][sess_idx]))
  1752. data_df = pd.DataFrame({'Animal': unenriched_animals + enriched_animals,
  1753. 'Session_Unique': [sess_idx]*len(unenriched_cells + enriched_cells),
  1754. 'Condition': [0]*len(unenriched_cells) + [1]*len(enriched_cells),
  1755. 'Indep_Var': unenriched_cells + enriched_cells})
  1756. data_df.loc[data_df.Indep_Var<0,'Indep_Var'] = 0
  1757. plot_compare_unpaired_metrics(unenriched_cells, enriched_cells, data_df, figure_path, \
  1758. 'nonlocal_enriched_cells_by_fr', 'Recruited > 95th percentile', [0, 3000])
  1759. # %%
  1760. # enrichment: what proportion of cells from each depth bin are on the enrichment lists?
  1761. enriched_cells = over_fr_list
  1762. enriched_dv_bin1, enriched_dv_bin2, enriched_dv_bin3, enriched_dv_bin4 = \
  1763. [np.full((len(animal_dict),n_sessions), np.NaN) for _ in range(4)]
  1764. for animal_idx in range(n_animals):
  1765. for sess_idx in range(n_sessions):
  1766. # proportion of cells in enrichment list
  1767. enriched_dv_bin1[animal_idx][sess_idx] = len(np.intersect1d(dv_bin1[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1768. len(dv_bin1[animal_idx][sess_idx]) if len(dv_bin1[animal_idx][sess_idx])>0 else np.NaN
  1769. enriched_dv_bin2[animal_idx][sess_idx] = len(np.intersect1d(dv_bin2[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1770. len(dv_bin2[animal_idx][sess_idx]) if len(dv_bin2[animal_idx][sess_idx])>0 else np.NaN
  1771. enriched_dv_bin3[animal_idx][sess_idx] = len(np.intersect1d(dv_bin3[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1772. len(dv_bin3[animal_idx][sess_idx]) if len(dv_bin3[animal_idx][sess_idx])>0 else np.NaN
  1773. enriched_dv_bin4[animal_idx][sess_idx] = len(np.intersect1d(dv_bin4[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1774. len(dv_bin4[animal_idx][sess_idx]) if len(dv_bin4[animal_idx][sess_idx])>0 else np.NaN
  1775. plot_feature_by_depth_bin(enriched_dv_bin1, enriched_dv_bin2, enriched_dv_bin3, enriched_dv_bin4, \
  1776. figure_path, 'proportion_enriched_by_depth_bin_fr', '% Units with Recruitment > 95th Percentile', [0,1])
  1777. # %%
  1778. # calculate field sizes and spacing
  1779. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  1780. n_sessions = 10
  1781. n_animals = len(animal_dict)
  1782. field_locs = np.empty((n_animals, n_sessions), dtype=object)
  1783. field_peaks = np.empty((n_animals, n_sessions), dtype=object)
  1784. field_sizes = np.empty((n_animals, n_sessions), dtype=object)
  1785. field_spacing = np.empty((n_animals, n_sessions), dtype=object)
  1786. for _, row in sessions.iterrows():
  1787. if not row['Recording_Error'] and not row['Position_Error'] and \
  1788. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1789. # any sequence will do; we're just using its contained objects
  1790. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  1791. row['File']+'_MEC_nonlocal_immobility_sequences.txt'),
  1792. load_objects=True)
  1793. animal_idx = animal_dict[row['Animal']]
  1794. sess_idx = int(row['Session'][-2:])-1
  1795. try:
  1796. field_locs[animal_idx, sess_idx], field_peaks[animal_idx, sess_idx], \
  1797. field_sizes[animal_idx, sess_idx], field_spacing[animal_idx, sess_idx] = seq.find_fields()
  1798. print(row['File'])
  1799. except:
  1800. print(f"ERROR: {row['File']}")
  1801. # %%
  1802. with open(join(figure_path,'field_locs.pkl'), 'wb') as file:
  1803. pickle.dump(field_locs, file)
  1804. with open(join(figure_path,'field_peaks.pkl'), 'wb') as file:
  1805. pickle.dump(field_peaks, file)
  1806. with open(join(figure_path,'field_sizes.pkl'), 'wb') as file:
  1807. pickle.dump(field_sizes, file)
  1808. with open(join(figure_path,'field_spacing.pkl'), 'wb') as file:
  1809. pickle.dump(field_spacing, file)
  1810. # %%
  1811. # is this due to enriched cells having higher field sizes?
  1812. # repeat with FR
  1813. percentile = over_fr_list
  1814. sess_idx = 0
  1815. # suppress the "Mean of an empty slice" warning that will occur thousands of times
  1816. # (due to cells with single fields having empty arrays for field_spacing)
  1817. with warnings.catch_warnings():
  1818. warnings.filterwarnings("ignore", category=RuntimeWarning)
  1819. enriched_cells = []
  1820. enriched_animals = []
  1821. unenriched_cells = []
  1822. unenriched_animals = []
  1823. for animal_idx in range(len(animal_dict)):
  1824. if field_sizes[animal_idx][sess_idx] is not None:
  1825. unenriched_cells.extend([np.nanmean(field_sizes[animal_idx][sess_idx][i]) for i in range(len(field_sizes[animal_idx][sess_idx])) \
  1826. if i not in percentile[animal_idx][sess_idx]])
  1827. unenriched_animals.extend([animal_idx]*(len(field_sizes[animal_idx][sess_idx]) - len(percentile[animal_idx][sess_idx])))
  1828. enriched_cells.extend([np.nanmean(field_sizes[animal_idx][sess_idx][i]) for i in percentile[animal_idx][sess_idx]])
  1829. enriched_animals.extend([animal_idx]*len(percentile[animal_idx][sess_idx]))
  1830. data_df = pd.DataFrame({'Animal': unenriched_animals + enriched_animals,
  1831. 'Session_Unique': [sess_idx]*len(unenriched_cells + enriched_cells),
  1832. 'Condition': [0]*len(unenriched_cells) + [1]*len(enriched_cells),
  1833. 'Indep_Var': unenriched_cells + enriched_cells})
  1834. data_df.loc[data_df.Indep_Var<0,'Indep_Var'] = 0
  1835. plot_compare_unpaired_metrics(unenriched_cells, enriched_cells, data_df, figure_path, \
  1836. 'field_sizes_nonlocal_enriched_cells_by_fr', 'Recruited > 95th percentile', [0, 100])
  1837. # %%
  1838. # bin cells by field size
  1839. n_sessions = 10
  1840. fs_bin1, fs_bin2, fs_bin3, fs_bin4 = [[[[] for _ in range(n_sessions)] for _ in range(n_animals)] for _ in range(4)]
  1841. # suppress the "Mean of an empty slice" warning that will occur thousands of times
  1842. with warnings.catch_warnings():
  1843. warnings.filterwarnings("ignore", category=RuntimeWarning)
  1844. for animal_idx in range(n_animals):
  1845. for sess_idx in range(n_sessions):
  1846. if field_sizes[animal_idx][sess_idx] is not None:
  1847. fs = [np.nanmean(field_sizes[animal_idx][sess_idx][i]) for i in range(len(field_sizes[animal_idx][sess_idx]))]
  1848. fs_bin1[animal_idx][sess_idx] = np.where([i<22 for i in fs])[0]
  1849. fs_bin2[animal_idx][sess_idx] = np.where([i>=22 and i<28 for i in fs])[0]
  1850. fs_bin3[animal_idx][sess_idx] = np.where([i>=28 and i<34 for i in fs])[0]
  1851. fs_bin4[animal_idx][sess_idx] = np.where([i>=34 for i in fs])[0]
  1852. # %%
  1853. # what proportion of cells from each field size bin are on the enrichment lists?
  1854. enriched_cells = over_fr_list
  1855. enriched_fs_bin1, enriched_fs_bin2, enriched_fs_bin3, enriched_fs_bin4 = \
  1856. [np.full((len(animal_dict),n_sessions), np.NaN) for _ in range(4)]
  1857. for animal_idx in range(n_animals):
  1858. for sess_idx in range(n_sessions):
  1859. # proportion of cells in enrichment list
  1860. enriched_fs_bin1[animal_idx][sess_idx] = len(np.intersect1d(fs_bin1[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1861. len(fs_bin1[animal_idx][sess_idx]) if len(fs_bin1[animal_idx][sess_idx])>0 else np.NaN
  1862. enriched_fs_bin2[animal_idx][sess_idx] = len(np.intersect1d(fs_bin2[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1863. len(fs_bin2[animal_idx][sess_idx]) if len(fs_bin2[animal_idx][sess_idx])>0 else np.NaN
  1864. enriched_fs_bin3[animal_idx][sess_idx] = len(np.intersect1d(fs_bin3[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1865. len(fs_bin3[animal_idx][sess_idx]) if len(fs_bin3[animal_idx][sess_idx])>0 else np.NaN
  1866. enriched_fs_bin4[animal_idx][sess_idx] = len(np.intersect1d(fs_bin4[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1867. len(fs_bin4[animal_idx][sess_idx]) if len(fs_bin4[animal_idx][sess_idx])>0 else np.NaN
  1868. plot_feature_by_depth_bin(enriched_fs_bin1, enriched_fs_bin2, enriched_fs_bin3, enriched_fs_bin4, \
  1869. figure_path, 'proportion_enriched_by_field_size_fr', '% Units with Recruitment > 95th Percentile', [0,1])
  1870. # %% [markdown]
  1871. # ### Spatial variables
  1872. # %%
  1873. # calculate enrichment during Xmaze non-local coding using Xmaze+OF-sorted sessions
  1874. start_time = time.time()
  1875. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze_celltypes_g1.csv')
  1876. n_shuffles = 1000
  1877. n_sessions = 10
  1878. n_animals = len(animal_dict)
  1879. over_fr = np.full((n_animals, n_sessions), np.NaN)
  1880. over_fr_list = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  1881. for _, row in sessions.iterrows():
  1882. if not row['Recording_Error'] and not row['Position_Error'] and \
  1883. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=10:
  1884. # load spikes and intervals
  1885. binned_data = Binned_Data(join(row['Base_Directory'],
  1886. 'Preprocessed_Data/Sequences/Binned_Data',
  1887. row['File']+'_MEC_g1+3_binned_data.txt'))
  1888. intervals = Sequence_Intervals(join(row['Base_Directory'],
  1889. 'Preprocessed_Data/Sequences/Intervals',
  1890. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  1891. seq = Sequences(join(row['Base_Directory'],
  1892. 'Preprocessed_Data/Sequences',
  1893. row['File']+'_MEC_g1+3_nonlocal_immobility_sequences.txt'))
  1894. interval_dur = intervals.intervals.end_time-intervals.intervals.start_time
  1895. animal_idx = animal_dict[row['Animal']]
  1896. sess_idx = int(row['Session'][-2:])-1
  1897. shuffled_fr = np.zeros((n_shuffles, binned_data.spikes.shape[0]))
  1898. for s in range(n_shuffles):
  1899. # shuffle each cell separately
  1900. shuffled_spikes = binned_data.spikes[:, np.random.permutation(binned_data.spikes.shape[1])]
  1901. # calculate fr during intervals & participation in intervals
  1902. # (same code as calc_cell_metrics in Sequences class)
  1903. fr = []
  1904. for c in range(binned_data.spikes.shape[0]):
  1905. fr_by_interval = 0
  1906. for i, idx in intervals.indices.iterrows():
  1907. num_spikes = np.sum(shuffled_spikes[c, idx['start_index']:idx['end_index']])
  1908. fr_by_interval += num_spikes/interval_dur.iloc[i]
  1909. fr.append(fr_by_interval/intervals.n_seq)
  1910. # append to shuffle distribution
  1911. shuffled_fr[s,:] = fr
  1912. # calculate 95th percentile
  1913. fr_95_percentile = np.percentile(shuffled_fr, 95, axis=0)
  1914. # count how many cells are above this threshold
  1915. over_fr[animal_idx, sess_idx] = np.nanmean(seq.stats_by_cell.FR>fr_95_percentile)
  1916. over_fr_list[animal_idx][sess_idx] = np.where(seq.stats_by_cell.FR>fr_95_percentile)[0].tolist()
  1917. print(f"{row['File']} {time.time() - start_time} seconds")
  1918. # %%
  1919. with open(join(figure_path,'over_fr_OF.pkl'), 'wb') as file:
  1920. pickle.dump(over_fr, file)
  1921. with open(join(figure_path,'over_fr_list_OF.pkl'), 'wb') as file:
  1922. pickle.dump(over_fr_list, file)
  1923. # %%
  1924. # refs: border (Solstad 2008), speed (Kropff 2015), HD (Taube 1990), grid (Hafting 2005), spatial aperiodic (Diehl 2017)
  1925. start_time = time.time()
  1926. bin_cm = 2.5
  1927. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_celltypes.csv')
  1928. n_sessions = 10
  1929. n_animals = len(animal_dict)
  1930. spatial_cells, spatial_aperiodic_cells, grid_cells, border_cells, speed_cells, hd_cells = \
  1931. [[[[] for _ in range(n_sessions)] for _ in range(n_animals)] for _ in range(6)]
  1932. for i, row in sessions.iterrows():
  1933. if not row['Recording_Error'] and not row['Position_Error']:
  1934. animal_idx = animal_dict[row['Animal']]
  1935. sess_idx = int(row['Session'][-2:])-1
  1936. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  1937. row['File']+'_position.txt'))
  1938. position.position = position.position[position.position.Velocity>2]
  1939. if ("2023_spring" in row['Base_Directory']) or ("2024_winter" in row['Base_Directory']):
  1940. if (row['Animal']=='Lamarr') and (int(row['Session'][-2:])>=8):
  1941. mec_spikes = Spikes(name=join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  1942. row['File']+'_imec0_spikes.txt'))
  1943. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1944. row['File']+'_imec0_electrodes.txt'))
  1945. else:
  1946. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  1947. row['File']+'_imec1_spikes.txt'))
  1948. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1949. row['File']+'_imec1_electrodes.txt'))
  1950. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  1951. mec_spikes.subset_by_channel(MEC_channels)
  1952. else:
  1953. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  1954. row['File']+'_imec0_spikes.txt'))
  1955. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  1956. row['File']+'_electrodes.txt'))
  1957. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  1958. mec_spikes.subset_by_channel(channels)
  1959. spatial_sig, stability_sig, grid_sig, border_sig, speed_sig, HD_sig = \
  1960. [np.full(len(mec_spikes.spikes), np.NaN) for _ in range(6)]
  1961. for u in range(len(mec_spikes.spikes)):
  1962. fr = bin_spikes(mec_spikes.spikes[u], position.position.Timestamp)
  1963. _, spatial_sig[u] = permutation_test_spatial_info(fr, position, bin_cm)
  1964. # _, stability_sig[u] = permutation_test_spatial_stability(fr, position, bin_cm)
  1965. try: # sometimes causes timeout error
  1966. _, grid_sig[u], _, _, _ = permutation_test_grid_score(fr, position, bin_cm)
  1967. except:
  1968. pass
  1969. _, border_sig[u] = permutation_test_border_score(fr, position, bin_cm)
  1970. _, speed_sig[u] = speed_score_traditional(position.position.Velocity, fr)
  1971. _, HD_sig[u], _, _, _, _ = angle_score_traditional(position.position.HD, fr)
  1972. spatial_cells[animal_idx][sess_idx] = np.where(spatial_sig<0.05)[0]
  1973. #np.intersect1d(np.where(spatial_sig<0.05)[0], np.where(stability_sig<0.05)[0])
  1974. grid_cells[animal_idx][sess_idx] = np.where(grid_sig<0.05)[0]
  1975. border_cells[animal_idx][sess_idx] = np.where(border_sig<0.05)[0]
  1976. spatial_aperiodic_cells[animal_idx][sess_idx] = np.array(list(set(spatial_cells[animal_idx][sess_idx]) - \
  1977. set(grid_cells[animal_idx][sess_idx]) - \
  1978. set(border_cells[animal_idx][sess_idx])))
  1979. speed_cells[animal_idx][sess_idx] = np.where(speed_sig<0.05)[0]
  1980. hd_cells[animal_idx][sess_idx] = np.where(HD_sig<0.05)[0]
  1981. print(f"{row['File']} {time.time() - start_time} seconds")
  1982. # %%
  1983. # enrichment: are any of these cells on the enrichment lists?
  1984. enriched_cells = over_fr_list
  1985. enriched_spatial, enriched_grid, enriched_border, enriched_spatial_aperiodic, \
  1986. enriched_speed, enriched_hd = [np.full((len(animal_dict),len(session_range)), np.NaN) for _ in range(6)]
  1987. for animal_idx in range(n_animals):
  1988. for sess_idx in range(n_sessions):
  1989. # proportion of cells in enrichment list
  1990. enriched_spatial[animal_idx][sess_idx] = len(np.intersect1d(spatial_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1991. len(spatial_cells[animal_idx][sess_idx]) if len(spatial_cells[animal_idx][sess_idx])>0 else np.NaN
  1992. enriched_grid[animal_idx][sess_idx] = len(np.intersect1d(grid_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1993. len(grid_cells[animal_idx][sess_idx]) if len(grid_cells[animal_idx][sess_idx])>0 else np.NaN
  1994. enriched_border[animal_idx][sess_idx] = len(np.intersect1d(border_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1995. len(border_cells[animal_idx][sess_idx]) if len(border_cells[animal_idx][sess_idx])>0 else np.NaN
  1996. enriched_spatial_aperiodic[animal_idx][sess_idx] = len(np.intersect1d(spatial_aperiodic_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1997. len(spatial_aperiodic_cells[animal_idx][sess_idx]) if len(spatial_aperiodic_cells[animal_idx][sess_idx])>0 else np.NaN
  1998. enriched_speed[animal_idx][sess_idx] = len(np.intersect1d(speed_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  1999. len(speed_cells[animal_idx][sess_idx]) if len(speed_cells[animal_idx][sess_idx])>0 else np.NaN
  2000. enriched_hd[animal_idx][sess_idx] = len(np.intersect1d(hd_cells[animal_idx][sess_idx], enriched_cells[animal_idx][sess_idx]))/ \
  2001. len(hd_cells[animal_idx][sess_idx]) if len(hd_cells[animal_idx][sess_idx])>0 else np.NaN
  2002. plot_feature_by_cell_type(enriched_spatial, enriched_grid, enriched_border, enriched_spatial_aperiodic, enriched_speed, enriched_hd, \
  2003. figure_path, 'proportion_enriched_by_cell_type_fr', '% Units with Recruitment > 95th Percentile', [0,1])
  2004. plot_feature_by_cell_type_3types(enriched_spatial, enriched_speed, enriched_hd, \
  2005. figure_path, 'proportion_enriched_by_cell_type_3categories_fr', '% Units with Recruitment > 95th Percentile', [0,1])
  2006. plot_feature_by_cell_type_3types(enriched_grid, enriched_border, enriched_spatial_aperiodic, \
  2007. figure_path, 'proportion_enriched_by_cell_type_3spatialcategories_fr', '% Units with Recruitment > 95th Percentile', [0,1])
  2008. # %%
  2009. # pie chart of categorzied % of enriched cells that represent each cell type (including overlaps)
  2010. # pie chart of % of categorized cells open field that represent each cell type (including overlaps)
  2011. from functools import reduce
  2012. enriched_cells = over_fr_list
  2013. enriched_frac = over_fr
  2014. enriched_spatial, enriched_grid, enriched_border, enriched_spatial_aperiodic, \
  2015. enriched_speed, enriched_hd, enriched_spatial_speed, enriched_spatial_hd, enriched_speed_hd, \
  2016. enriched_spatial_speed_hd, \
  2017. spatial, grid, border, spatial_aperiodic, \
  2018. speed, hd, spatial_speed, spatial_hd, speed_hd, \
  2019. spatial_speed_hd, = \
  2020. [np.full((len(animal_dict),n_sessions), np.NaN) for _ in range(20)]
  2021. for a in range(n_animals):
  2022. for s in range(n_sessions):
  2023. n_cells = len(over_fr_list[a][s])/over_fr[a][s]
  2024. if len(enriched_cells[a][s])>0:
  2025. # proportion of enriched cells of each type
  2026. enriched_grid[a][s] = len(np.intersect1d(grid_cells[a][s], enriched_cells[a][s]))/ \
  2027. len(enriched_cells[a][s])
  2028. enriched_border[a][s] = len(np.intersect1d(border_cells[a][s], enriched_cells[a][s]))/ \
  2029. len(enriched_cells[a][s])
  2030. enriched_spatial_aperiodic[a][s] = len(np.intersect1d(spatial_aperiodic_cells[a][s], enriched_cells[a][s]))/ \
  2031. len(enriched_cells[a][s])
  2032. enriched_spatial_speed_hd[a][s] = len(reduce(np.intersect1d, (spatial_cells[a][s], speed_cells[a][s], hd_cells[a][s], enriched_cells[a][s])))/ \
  2033. len(enriched_cells[a][s])
  2034. enriched_spatial_speed[a][s] = len(reduce(np.intersect1d, (spatial_cells[a][s], speed_cells[a][s], enriched_cells[a][s])))/ \
  2035. len(enriched_cells[a][s]) - enriched_spatial_speed_hd[a][s]
  2036. enriched_spatial_hd[a][s] = len(reduce(np.intersect1d, (spatial_cells[a][s], hd_cells[a][s], enriched_cells[a][s])))/ \
  2037. len(enriched_cells[a][s]) - enriched_spatial_speed_hd[a][s]
  2038. enriched_speed_hd[a][s] = len(reduce(np.intersect1d, (speed_cells[a][s], hd_cells[a][s], enriched_cells[a][s])))/ \
  2039. len(enriched_cells[a][s]) - enriched_spatial_speed_hd[a][s]
  2040. enriched_spatial[a][s] = len(np.intersect1d(spatial_cells[a][s], enriched_cells[a][s]))/len(enriched_cells[a][s]) \
  2041. - enriched_spatial_speed_hd[a][s] - enriched_spatial_speed[a][s] - enriched_spatial_hd[a][s]
  2042. enriched_speed[a][s] = len(np.intersect1d(speed_cells[a][s], enriched_cells[a][s]))/len(enriched_cells[a][s]) \
  2043. - enriched_spatial_speed_hd[a][s] - enriched_spatial_speed[a][s] - enriched_speed_hd[a][s]
  2044. enriched_hd[a][s] = len(np.intersect1d(hd_cells[a][s], enriched_cells[a][s]))/len(enriched_cells[a][s]) \
  2045. - enriched_spatial_speed_hd[a][s] - enriched_spatial_hd[a][s] - enriched_spatial_hd[a][s]
  2046. # proportion of all cells of each type
  2047. grid[a][s] = len(grid_cells[a][s])/n_cells
  2048. border[a][s] = len(border_cells[a][s])/n_cells
  2049. spatial_aperiodic[a][s] = len(spatial_aperiodic_cells[a][s])/n_cells
  2050. spatial_speed_hd[a][s] = len(reduce(np.intersect1d, (spatial_cells[a][s], speed_cells[a][s], hd_cells[a][s])))/ \
  2051. n_cells
  2052. spatial_speed[a][s] = len(np.intersect1d(spatial_cells[a][s], speed_cells[a][s]))/ \
  2053. n_cells - spatial_speed_hd[a][s]
  2054. spatial_hd[a][s] = len(np.intersect1d(spatial_cells[a][s], hd_cells[a][s]))/ \
  2055. n_cells - spatial_speed_hd[a][s]
  2056. speed_hd[a][s] = len(np.intersect1d(speed_cells[a][s], hd_cells[a][s]))/ \
  2057. n_cells - spatial_speed_hd[a][s]
  2058. spatial[a][s] = len(spatial_cells[a][s])/n_cells \
  2059. - spatial_speed_hd[a][s] - spatial_speed[a][s] - spatial_hd[a][s]
  2060. speed[a][s] = len(speed_cells[a][s])/n_cells \
  2061. - spatial_speed_hd[a][s] - spatial_speed[a][s] - speed_hd[a][s]
  2062. hd[a][s] = len(hd_cells[a][s])/n_cells \
  2063. - spatial_speed_hd[a][s] - spatial_hd[a][s] - spatial_hd[a][s]
  2064. f, ax = plt.subplots(4)
  2065. ax[0].pie([np.nansum(enriched_spatial), np.nansum(enriched_speed), np.nansum(enriched_hd), \
  2066. np.nansum(enriched_spatial_speed), np.nansum(enriched_spatial_hd), np.nansum(enriched_speed_hd), \
  2067. np.nansum(enriched_spatial_speed_hd)], \
  2068. labels=['Position','Speed','HD','Position x Speed', 'Position x HD', 'Speed x HD', 'Position x Speed x HD'])
  2069. ax[1].pie([np.nansum(enriched_spatial_aperiodic), \
  2070. np.nansum(enriched_grid), \
  2071. np.nansum(enriched_border)], \
  2072. labels=['Spatial Aperiodic', 'Grid', 'Border'])
  2073. ax[2].pie([np.nansum(spatial), np.nansum(speed), np.nansum(hd), \
  2074. np.nansum(spatial_speed), np.nansum(spatial_hd), np.nansum(speed_hd), \
  2075. np.nansum(spatial_speed_hd)], \
  2076. labels=['Position','Speed','HD','Position x Speed', 'Position x HD', 'Speed x HD', 'Position x Speed x HD'])
  2077. ax[3].pie([np.nansum(spatial_aperiodic), \
  2078. np.nansum(grid), \
  2079. np.nansum(border)], \
  2080. labels=['Spatial Aperiodic', 'Grid', 'Border'])
  2081. f.savefig(join(figure_path,'cell_types_pie_chart.pdf'), format='pdf')
  2082. # %%
  2083. # representative plots of spatial variable coding
  2084. # pick a session
  2085. row = sessions.iloc[17]
  2086. print(row['File'])
  2087. position = Position(name=join(row['Base_Directory'], 'Preprocessed_Data/Position',
  2088. row['File']+'_position.txt'))
  2089. if ("2023_spring" in row['Base_Directory']) or ("2024_winter" in row['Base_Directory']):
  2090. if (row['Animal']=='Lamarr') and (int(row['Session'][-2:])>=8):
  2091. mec_spikes = Spikes(name=join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  2092. row['File']+'_imec0_spikes.txt'))
  2093. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  2094. row['File']+'_imec0_electrodes.txt'))
  2095. else:
  2096. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  2097. row['File']+'_imec1_spikes.txt'))
  2098. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  2099. row['File']+'_imec1_electrodes.txt'))
  2100. MEC_channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  2101. mec_spikes.subset_by_channel(MEC_channels)
  2102. else:
  2103. mec_spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1_3',
  2104. row['File']+'_imec0_spikes.txt'))
  2105. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  2106. row['File']+'_electrodes.txt'))
  2107. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  2108. mec_spikes.subset_by_channel(channels)
  2109. sess = "BaggySweatpants_DY08"
  2110. # spatial firing
  2111. unit = 67
  2112. #[ 0, 136, 149] #[ 63, 67, 76, 95, 111, 163] #[70, 72, 43, 44, 46, 48, 54, 89, 27, 94]
  2113. #0, 2, 8, 117, 125, 136, 141, 149, 156, 180
  2114. fr_map, xbins, ybins, max_fr = calc_fr_map(mec_spikes.spikes[unit], position, bin_cm, smooth=True)
  2115. f = plot_heatmap(fr_map, xbins, ybins)
  2116. f.savefig(join(figure_path,sess+'_grid_example.pdf'), format='pdf', transparent=True,
  2117. dpi=300, bbox_inches='tight')
  2118. print(max_fr)
  2119. unit = 46
  2120. #28, 33, 63, 67, 76, 93, 95, 99, 100, 108, 111, 123, 130, 139, 147, 164, 169, 173, 179
  2121. fr_map, xbins, ybins, max_fr = calc_fr_map(mec_spikes.spikes[unit], position, bin_cm, smooth=True)
  2122. f = plot_heatmap(fr_map, xbins, ybins)
  2123. f.savefig(join(figure_path,sess+'_border_example.pdf'), format='pdf', transparent=True,
  2124. dpi=300, bbox_inches='tight')
  2125. print(max_fr)
  2126. unit = 94
  2127. #[ 11, 140, 142, 14, 22, 153, 26, 27, 25, 29, 161, 38, 43, 44, 45, 46, 48, 52, 54, 55, 58, 64, 70, 72, 78, 89, 91, 94, 107, 115]
  2128. fr_map, xbins, ybins, max_fr = calc_fr_map(mec_spikes.spikes[unit], position, bin_cm, smooth=True)
  2129. f = plot_heatmap(fr_map, xbins, ybins)
  2130. f.savefig(join(figure_path,sess+'_spatial_aperiodic_example.pdf'), format='pdf', transparent=True,
  2131. dpi=300, bbox_inches='tight')
  2132. print(max_fr)
  2133. # stability
  2134. fr = bin_spikes(mec_spikes.spikes[unit], position.position.Timestamp)
  2135. n_samps = len(fr)
  2136. split_idx = int(n_samps/2)
  2137. first_half_pos = deepcopy(position)
  2138. first_half_pos.position = first_half_pos.position.iloc[:split_idx]
  2139. second_half_pos = deepcopy(position)
  2140. second_half_pos.position = second_half_pos.position.iloc[split_idx:]
  2141. fr_map, xbins, ybins, max_fr = calc_fr_map(fr[:split_idx], first_half_pos, bin_cm, smooth=True, spikes_are_binned=True)
  2142. f = plot_heatmap(fr_map, xbins, ybins)
  2143. f.savefig(join(figure_path,sess+'_spatial_aperiodic_example_first_half.pdf'), format='pdf', transparent=True,
  2144. dpi=300, bbox_inches='tight')
  2145. fr_map, xbins, ybins, max_fr = calc_fr_map(fr[split_idx:], second_half_pos, bin_cm, smooth=True, spikes_are_binned=True)
  2146. f = plot_heatmap(fr_map, xbins, ybins)
  2147. f.savefig(join(figure_path,sess+'_spatial_aperiodic_example_second_half.pdf'), format='pdf', transparent=True,
  2148. dpi=300, bbox_inches='tight')
  2149. # speed
  2150. unit = 29 #[ 12, 29, 30, 48, 50, 53, 69, 72, 87, 98, 103, 126, 133, 137, 144, 146, 148, 161, 166, 171, 182, 192]
  2151. fr = bin_spikes(mec_spikes.spikes[unit], position.position.Timestamp)*60
  2152. bins = np.arange(0,100,5)
  2153. fr_by_speed = pd.DataFrame({'FR': fr, 'Speed': pd.cut(position.position.Velocity, bins=bins)})
  2154. fr_by_speed_mean = fr_by_speed.groupby(['Speed']).mean().reset_index()
  2155. fr_by_speed_sem = fr_by_speed.groupby(['Speed']).sem().reset_index()
  2156. f, ax = plt.subplots()
  2157. ax.plot(bins[:-1], fr_by_speed_mean.FR)
  2158. ax.fill_between(bins[:-1], fr_by_speed_mean.FR - fr_by_speed_sem.FR, fr_by_speed_mean.FR + fr_by_speed_sem.FR, \
  2159. color='grey', alpha=0.5, rasterized=True)
  2160. ax.set(xlim=[0,50])
  2161. f.savefig(join(figure_path,sess+'_speed_example.pdf'), format='pdf', transparent=True,
  2162. dpi=300, bbox_inches='tight')
  2163. # HD
  2164. unit = 32 #[ 10, 11, 14, 16, 19, 20, 22, 24, 26, 28, 32, 47, 49,
  2165. #53, 55, 62, 65, 66, 79, 90, 103, 107, 111, 121, 130, 139,
  2166. #150, 156, 164, 167, 175, 193, 194, 197]
  2167. fr = bin_spikes(mec_spikes.spikes[unit], position.position.Timestamp)*60
  2168. bins = np.linspace(-1*math.pi,math.pi,18)
  2169. fr_by_hd = pd.DataFrame({'FR': fr, 'HD': pd.cut(position.position.HD, bins=bins)})
  2170. fr_by_hd_mean = fr_by_hd.groupby(['HD']).mean().reset_index()
  2171. fr_by_hd_sem = fr_by_hd.groupby(['HD']).sem().reset_index()
  2172. means = np.append(fr_by_hd_mean.FR,fr_by_hd_mean.FR[0])
  2173. sems = np.append(fr_by_hd_sem.FR,fr_by_hd_sem.FR[0])
  2174. f, ax = plt.subplots(subplot_kw={'projection': 'polar'})
  2175. ax.plot(bins, means)
  2176. ax.fill_between(bins, means - sems, means + sems, \
  2177. color='grey', alpha=0.5, rasterized=True)
  2178. f.savefig(join(figure_path,sess+'_HD_example.pdf'), format='pdf', transparent=True,
  2179. dpi=300, bbox_inches='tight')
  2180. # %% [markdown]
  2181. # ### Reward coding
  2182. # %%
  2183. # find cells whose firing rates are significantly increased at every reward location
  2184. sessions = pd.read_csv('//oak-smb-giocomo.stanford.edu/groups/giocomo/emijones/WT_Sequences/all_sessions_Xmaze.csv')
  2185. n_shuffles = 1000
  2186. n_sessions = 20
  2187. n_animals = len(animal_dict)
  2188. all_rewards = np.full((n_animals, n_sessions), np.NaN)
  2189. reward_increase = np.full((n_animals, n_sessions), np.NaN)
  2190. all_rewards_list = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  2191. reward_increase_list = [[[] for _ in range(n_sessions)] for _ in range(n_animals)]
  2192. for _, row in sessions.iterrows():
  2193. sess_idx = int(row['Session'][-2:])-1
  2194. if not row['Recording_Error'] and not row['Position_Error'] and \
  2195. row['Task'] == 'X Maze' and sess_idx<=10:
  2196. # load spikes
  2197. binned_data = Binned_Data(join(row['Base_Directory'],
  2198. 'Preprocessed_Data/Sequences/Binned_Data',
  2199. row['File']+'_MEC_binned_data.txt'))
  2200. position = Position(join(row['Base_Directory'], 'Preprocessed_Data/Position',
  2201. row['File']+'_position.txt'))
  2202. task = DoubleYMazeTask(join(row['Base_Directory'], 'Preprocessed_Data/Task',
  2203. row['File']+'_task.txt'))
  2204. hd_shift = position.position.HD+math.pi
  2205. animal_idx = animal_dict[row['Animal']]
  2206. # calculate reward FR for each cell at each rewarded position
  2207. # set start and end indices and reward locations for later shuffles
  2208. # loop over reward visits
  2209. true_fr_by_loc = np.zeros((binned_data.spikes.shape[0],4))
  2210. true_fr_rew_delta = np.zeros(binned_data.spikes.shape[0])
  2211. reward_location, reward_start_idx, reward_end_idx = [np.zeros(len(task.trials), dtype=int) for _ in range(3)]
  2212. num_spikes_by_loc = np.zeros((binned_data.spikes.shape[0],4))
  2213. reward_dur_by_loc = np.zeros(4)
  2214. num_spikes_rew_or_not = np.zeros((binned_data.spikes.shape[0],2))
  2215. reward_dur_rew_or_not = np.zeros(2)
  2216. for t, trial in task.trials.iterrows():
  2217. # find timepoints of first entering poke + turning around to leave poke
  2218. reward_start_idx[t] = np.argmin(np.abs(position.position.Timestamp-trial.Start))
  2219. # if recording ended before mouse left reward, take last index
  2220. if (np.where(np.abs(hd_shift[reward_start_idx[t]:]-hd_shift[reward_start_idx[t]])>math.pi/2)[0]).size==0:
  2221. reward_end_idx[t] = len(hd_shift)-1
  2222. else:
  2223. reward_end_idx[t] = np.where(np.abs(hd_shift[reward_start_idx[t]:]-hd_shift[reward_start_idx[t]])>math.pi/2)[0][0] + reward_start_idx[t]
  2224. # which reward location
  2225. # TL, BL, TR, BR
  2226. if (trial['Inbound/Outbound']=='Outbound') and (trial['Trajectory']=='Top'):
  2227. reward_location[t] = 0
  2228. elif (trial['Inbound/Outbound']=='Outbound') and (trial['Trajectory']=='Bottom'):
  2229. reward_location[t] = 1
  2230. elif (trial['Inbound/Outbound']=='Inbound') and (trial['Trajectory']=='Top'):
  2231. reward_location[t] = 2
  2232. else:
  2233. reward_location[t] = 3
  2234. num_spikes_by_loc[:, reward_location[t]] += np.sum(binned_data.spikes[:, reward_start_idx[t]:reward_end_idx[t]], axis=1)
  2235. reward_dur_by_loc[reward_location[t]] += position.position.Timestamp[reward_end_idx[t]] - position.position.Timestamp[reward_start_idx[t]]
  2236. # choice reward, which could be rewarded or not
  2237. if (reward_location[t]==2) or (reward_location[t]==3):
  2238. if trial['Correct']:
  2239. num_spikes_rew_or_not[:,0] += num_spikes_by_loc[:, reward_location[t]]
  2240. reward_dur_rew_or_not[0] += reward_dur_by_loc[reward_location[t]]
  2241. else:
  2242. num_spikes_rew_or_not[:,1] += num_spikes_by_loc[:, reward_location[t]]
  2243. reward_dur_rew_or_not[1] += reward_dur_by_loc[reward_location[t]]
  2244. for r in range(4):
  2245. true_fr_by_loc[:,r] = num_spikes_by_loc[:,r]/reward_dur_by_loc[r]
  2246. true_fr_rew_delta = num_spikes_rew_or_not[:,0]/reward_dur_rew_or_not[0] - num_spikes_rew_or_not[:,1]/reward_dur_rew_or_not[1]
  2247. # loop over shuffles
  2248. shuffled_fr_list_by_loc = np.zeros((n_shuffles, binned_data.spikes.shape[0], 4))
  2249. shuffled_fr_list_rew_delta = np.zeros((n_shuffles, binned_data.spikes.shape[0]))
  2250. for s in range(n_shuffles):
  2251. # shuffle each cell separately
  2252. shuffled_spikes = binned_data.spikes[:, np.random.permutation(binned_data.spikes.shape[1])]
  2253. # calculate shuffled fr during rewards
  2254. num_spikes_by_loc = np.zeros((binned_data.spikes.shape[0],4))
  2255. reward_dur_by_loc = np.zeros(4)
  2256. num_spikes_rew_or_not = np.zeros((binned_data.spikes.shape[0],2))
  2257. reward_dur_rew_or_not = np.zeros(2)
  2258. for t, trial in task.trials.iterrows():
  2259. num_spikes_by_loc[:, reward_location[t]] += np.sum(shuffled_spikes[:, reward_start_idx[t]:reward_end_idx[t]], axis=1)
  2260. reward_dur_by_loc[reward_location[t]] += position.position.Timestamp[reward_end_idx[t]] - position.position.Timestamp[reward_start_idx[t]]
  2261. # choice reward, which could be rewarded or not
  2262. if (reward_location[t]==2) or (reward_location[t]==3):
  2263. if trial['Correct']:
  2264. num_spikes_rew_or_not[:,0] += num_spikes_by_loc[:, reward_location[t]]
  2265. reward_dur_rew_or_not[0] += reward_dur_by_loc[reward_location[t]]
  2266. else:
  2267. num_spikes_rew_or_not[:,1] += num_spikes_by_loc[:, reward_location[t]]
  2268. reward_dur_rew_or_not[1] += reward_dur_by_loc[reward_location[t]]
  2269. # append to shuffle distribution
  2270. for r in range(4):
  2271. shuffled_fr_list_by_loc[s,:,r] = num_spikes_by_loc[:,r]/reward_dur_by_loc[r]
  2272. shuffled_fr_list_rew_delta[s,:] = num_spikes_rew_or_not[:,0]/reward_dur_rew_or_not[0] - num_spikes_rew_or_not[:,1]/reward_dur_rew_or_not[1]
  2273. # calculate 95th percentile
  2274. fr_95_percentile_by_loc = np.zeros((binned_data.spikes.shape[0],4))
  2275. fr_95_percentile_rew_delta = np.zeros((binned_data.spikes.shape[0]))
  2276. for r in range(4):
  2277. fr_95_percentile_by_loc[:,r] = np.percentile(shuffled_fr_list_by_loc[:,:,r], 95, axis=0)
  2278. fr_95_percentile_rew_delta = np.percentile(shuffled_fr_list_rew_delta, 95, axis=0)
  2279. # count how many cells are above this threshold for all 4 rewards or delta FR (rewarded vs not)
  2280. all_rewards[animal_idx, sess_idx] = np.nanmean((true_fr_by_loc[:,0]>fr_95_percentile_by_loc[:,0]) &
  2281. (true_fr_by_loc[:,1]>fr_95_percentile_by_loc[:,1]) &
  2282. (true_fr_by_loc[:,2]>fr_95_percentile_by_loc[:,2]) &
  2283. (true_fr_by_loc[:,3]>fr_95_percentile_by_loc[:,3]))
  2284. all_rewards_list[animal_idx][sess_idx] = np.where((true_fr_by_loc[:,0]>fr_95_percentile_by_loc[:,0]) &
  2285. (true_fr_by_loc[:,1]>fr_95_percentile_by_loc[:,1]) &
  2286. (true_fr_by_loc[:,2]>fr_95_percentile_by_loc[:,2]) &
  2287. (true_fr_by_loc[:,3]>fr_95_percentile_by_loc[:,3]))[0].tolist()
  2288. reward_increase[animal_idx][sess_idx] = np.nanmean(true_fr_rew_delta>fr_95_percentile_rew_delta)
  2289. reward_increase_list[animal_idx][sess_idx] = np.where(true_fr_rew_delta>fr_95_percentile_rew_delta)[0].tolist()
  2290. print(row['File'])
  2291. # %%
  2292. with open(join(figure_path,'all_rewards.pkl'), 'wb') as file:
  2293. pickle.dump(all_rewards, file)
  2294. with open(join(figure_path,'all_rewards_list.pkl'), 'wb') as file:
  2295. pickle.dump(all_rewards_list, file)
  2296. with open(join(figure_path,'reward_increase.pkl'), 'wb') as file:
  2297. pickle.dump(reward_increase, file)
  2298. with open(join(figure_path,'reward_increase_list.pkl'), 'wb') as file:
  2299. pickle.dump(reward_increase_list, file)
  2300. # %%
  2301. print(f"{np.nanmean(all_rewards)*100:.02f} +/- {stats.sem(np.nanmean(all_rewards,axis=1))*100:.02f}" +\
  2302. f"% of cells have > 95th percentile of shuffled FR during all rewards,\n" +\
  2303. f"and {np.nanmean(reward_increase)*100:.02f} +/- {stats.sem(np.nanmean(reward_increase,axis=1))*100:.02f}" +\
  2304. f"% of cells have > 95th percentile of shuffled increase in FR during reward consumption vs no reward.")
  2305. # %%
  2306. with open(join(figure_path,'over_fr_list.pkl'), 'rb') as file:
  2307. over_fr_list = pickle.load(file)
  2308. # %%
  2309. # are preferentially recruited cells more likely to be reward coding?
  2310. n_sessions = 10
  2311. reward_enriched, reward_inc_enriched, reward_unenriched, reward_inc_unenriched = \
  2312. [np.full((n_animals, n_sessions), np.NaN) for _ in range(4)]
  2313. for animal_idx in range(n_animals):
  2314. for sess_idx in range(n_sessions):
  2315. # what proportion of recruited cells are reward coding
  2316. # number of reward coding cells in enriched list divided by number of total cells in enriched list
  2317. reward_enriched[animal_idx][sess_idx] = len(np.intersect1d(over_fr_list[animal_idx][sess_idx], all_rewards_list[animal_idx][sess_idx]))/ \
  2318. len(over_fr_list[animal_idx][sess_idx]) if len(over_fr_list[animal_idx][sess_idx])>0 else np.NaN
  2319. reward_inc_enriched[animal_idx][sess_idx] = len(np.intersect1d(over_fr_list[animal_idx][sess_idx], reward_increase_list[animal_idx][sess_idx]))/ \
  2320. len(over_fr_list[animal_idx][sess_idx]) if len(over_fr_list[animal_idx][sess_idx])>0 else np.NaN
  2321. # what proportion of non-recruited cells are reward coding
  2322. # number of reward coding cells not in enriched list divided by number of total cells not in enriched list
  2323. reward_unenriched[animal_idx][sess_idx] = np.sum(~np.isin(all_rewards_list[animal_idx][sess_idx], over_fr_list[animal_idx][sess_idx]))/ \
  2324. (len(all_rewards_list[animal_idx][sess_idx])/all_rewards[animal_idx][sess_idx]) if len(over_fr_list[animal_idx][sess_idx])>0 else np.NaN
  2325. reward_inc_unenriched[animal_idx][sess_idx] = np.sum(~np.isin(reward_increase_list[animal_idx][sess_idx], over_fr_list[animal_idx][sess_idx]))/ \
  2326. (len(all_rewards_list[animal_idx][sess_idx])/all_rewards[animal_idx][sess_idx]) if len(over_fr_list[animal_idx][sess_idx])>0 else np.NaN
  2327. # %%
  2328. # compare
  2329. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  2330. 'Session': np.tile(np.arange(n_sessions),n_animals),
  2331. 'Reward_enriched': reward_enriched.flatten()*100,
  2332. 'Reward_unenriched': reward_unenriched.flatten()*100,
  2333. 'Reward_inc_enriched': reward_inc_enriched.flatten()*100,
  2334. 'Reward_inc_unenriched': reward_inc_unenriched.flatten()*100})
  2335. plot_compare_paired_metrics('Reward_enriched', 'Reward_unenriched', data_by_session, \
  2336. figure_path, 'percent_rew_coding_recruited_vs_not', '% of Cells that Represent Reward', [0,100])
  2337. plot_compare_paired_metrics('Reward_inc_enriched', 'Reward_inc_unenriched', data_by_session, \
  2338. figure_path, 'percent_rew_inc_recruited_vs_not', '% of Cells that Represent Reward Consumption', [0,100])
  2339. # %% [markdown]
  2340. # # Figure S5
  2341. # %% [markdown]
  2342. # ### Decode with SWR trace
  2343. # %%
  2344. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze.csv')
  2345. row = sessions.iloc[156] #64
  2346. print(row['File'])
  2347. seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  2348. row['File']+'_MEC_immobility_sequences.txt'),
  2349. load_objects=True)
  2350. CA1_seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  2351. row['File']+'_CA1_immobility_sequences.txt'),
  2352. load_objects=True)
  2353. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2354. 'Preprocessed_Data/Sequences/Intervals',
  2355. row['File']+'_CA1_SWR_intervals.txt'))
  2356. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP',
  2357. row['File']+'_CA1_SWR_channel_lfp.txt'))
  2358. ripple_trace = lf.bandpass_filter(lfp.lfp, lfp.rate, lf.RIPPLE[0], lf.RIPPLE[1], lf.RIPPLE[2])
  2359. #seq.stats[seq.stats['Non_local'] & (seq.stats['Duration']<5)]
  2360. #SWR_intervals.intervals
  2361. # %%
  2362. # plot decode
  2363. idx = 108
  2364. seq.plot_sequence(indices=[idx], figure_path=f"{figure_path}/Payne_DY02_MEC")
  2365. CA1_seq.plot_sequence(indices=[idx], figure_path=f"{figure_path}/Payne_DY02_CA1")
  2366. # plot SWR trace
  2367. f, ax = plt.subplots(figsize=(6.3, 3))
  2368. ax.plot(lfp.timestamps, ripple_trace)
  2369. ax.set(xlabel='Time (s)', ylabel='Amplitude')
  2370. # label SWRs
  2371. for ripple in SWR_intervals.intervals.itertuples():
  2372. ax.axvspan(ripple.start_time, ripple.end_time, alpha=0.3, zorder=2)
  2373. # display only overlap with sequence
  2374. start_time = seq.intervals.intervals['start_time'].iloc[idx]
  2375. end_time = seq.intervals.intervals['end_time'].iloc[idx]
  2376. ax.set(xlim=[start_time, end_time])
  2377. f.savefig(join(figure_path,'Payne_DY02_SWR_example_ripple_trace_immobinterval108.pdf'), format='pdf')
  2378. # %% [markdown]
  2379. # ### % of nonlocal coding overlapping with SWRs and vice versa
  2380. # SWRs are not enriched during nonlocal coding. Nonlocal coding is equally likely to occur during an SWR as it is during an immobility bout. SWRs only overlap with a small fraction of nonlocal content.
  2381. # %%
  2382. # averaged over sessions
  2383. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2384. n_sessions = 20
  2385. n_animals = len(animal_dict)
  2386. ripple_overlap = np.zeros((n_animals, n_sessions))
  2387. ripple_time = np.full((n_animals, n_sessions), np.NaN)
  2388. for _, row in sessions.iterrows():
  2389. if not row['Recording_Error'] and not row['Position_Error'] and \
  2390. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2391. animal_idx = animal_dict[row['Animal']]
  2392. sess_idx = int(row['Session'][-2:])-1
  2393. # load intervals
  2394. nonlocal_immobility_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2395. 'Preprocessed_Data/Sequences/Intervals',
  2396. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  2397. immobility_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2398. 'Preprocessed_Data/Sequences/Intervals',
  2399. row['File']+'_immobility_intervals.txt'))
  2400. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2401. 'Preprocessed_Data/Sequences/Intervals',
  2402. row['File']+'_CA1_SWR_intervals.txt'))
  2403. # iterate over nonlocal intervals, then SWRs
  2404. for nl_immob in nonlocal_immobility_intervals.intervals.itertuples():
  2405. # find overlaps
  2406. for ripple in SWR_intervals.intervals.itertuples():
  2407. if ripple.start_time<nl_immob.end_time and ripple.end_time>nl_immob.start_time:
  2408. if ripple.start_time<=nl_immob.start_time:
  2409. ripple_overlap[animal_idx,sess_idx] += ripple.end_time-nl_immob.start_time
  2410. elif ripple.end_time<=nl_immob.end_time:
  2411. ripple_overlap[animal_idx,sess_idx] += ripple.end_time-ripple.start_time
  2412. else:
  2413. ripple_overlap[animal_idx,sess_idx] += nl_immob.end_time-ripple.start_time
  2414. elif ripple.end_time>end_time:
  2415. break
  2416. ripple_overlap[animal_idx,sess_idx] /= np.sum(nonlocal_immobility_intervals.intervals.end_time -\
  2417. nonlocal_immobility_intervals.intervals.start_time)
  2418. ripple_time[animal_idx,sess_idx] = (np.sum(SWR_intervals.intervals.end_time - SWR_intervals.intervals.start_time))/ \
  2419. (np.sum(immobility_intervals.intervals.end_time -\
  2420. immobility_intervals.intervals.start_time))
  2421. # remove sessions that don't have SWRs (e.g. no CA1 data)
  2422. ripple_overlap = np.where(ripple_overlap==0, np.nan, ripple_overlap)
  2423. ripple_overlap *= 100
  2424. ripple_time *=100
  2425. # %%
  2426. print(f"{np.nanmean(np.nanmean(SWR_data_by_session['non_local'])):.02f} +/- {stats.sem(np.nanmean(SWR_data_by_session['non_local'],axis=1), nan_policy='omit'):.02f}" +\
  2427. f"% of SWRs have nonlocal coding in MEC,\n" +\
  2428. f"and {np.nanmean(np.nanmean(ripple_overlap)):.02f} +/- {stats.sem(np.nanmean(ripple_overlap,axis=1), nan_policy='omit'):.02f}" +\
  2429. f"% of nonlocal decoded time overlaps with SWRs, \n" +\
  2430. f"and {np.nanmean(np.nanmean(ripple_time)):.02f} +/- {stats.sem(np.nanmean(ripple_time,axis=1), nan_policy='omit'):.02f}" +\
  2431. f"% of time during immobility is spent having SWRs.")
  2432. # %%
  2433. # plot
  2434. plot_seq_metrics_over_sessions(n_animals, ripple_overlap, figure_path,
  2435. 'nl_overlap_with_swr', '% NonLocal Decode Overlapping with SWRs', [0,100])
  2436. plot_seq_metrics_over_sessions(n_animals, SWR_data_by_session['non_local'], figure_path,
  2437. 'swr_overlap_with_nl', '% SWRs with NonLocal Decode', [0,100])
  2438. # %% [markdown]
  2439. # ### compare to shuffle: % nonlocal coding overlapping with SWRs and vice versa
  2440. # %%
  2441. n_shuffles = 1000
  2442. rng = np.random.default_rng()
  2443. n_sessions = 20
  2444. n_animals = len(animal_dict)
  2445. nonlocal_contain_SWRs_5, nonlocal_contain_SWRs_95, nonlocal_contain_SWRs_median, \
  2446. SWRs_contain_nonlocal_5, SWRs_contain_nonlocal_95, SWRs_contain_nonlocal_median =\
  2447. [np.full((n_animals, n_sessions), np.NaN) for _ in range(6)]
  2448. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2449. for _, row in sessions.iterrows():
  2450. if not row['Recording_Error'] and not row['Position_Error'] and \
  2451. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2452. animal_idx = animal_dict[row['Animal']]
  2453. sess_idx = int(row['Session'][-2:])-1
  2454. seq = Sequences(join(row['Base_Directory'],
  2455. 'Preprocessed_Data/Sequences',
  2456. row['File']+'_MEC_immobility_sequences.txt'),
  2457. load_objects=True)
  2458. dist = seq.calc_dist_from_decode()
  2459. ts = seq.binned_data.timestamps
  2460. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2461. 'Preprocessed_Data/Sequences/Intervals',
  2462. row['File']+'_CA1_SWR_intervals.txt'))
  2463. # 1000 shuffles
  2464. perc_SWRs_contain_nonlocal = np.zeros(1000)
  2465. perc_nonlocal_contain_SWRs = np.zeros(1000)
  2466. for b in range(n_shuffles):
  2467. ts_nonlocal = np.array([])
  2468. for ripple in SWR_intervals.indices.itertuples():
  2469. # pick a random immobility interval
  2470. seq_idx = int(rng.random()*len(seq.stats))
  2471. selected_interval = seq.intervals.indices.iloc[seq_idx]
  2472. # pick a random time within that interval of the same duration as the SWR
  2473. SWR_idx_len = ripple.end_index-ripple.start_index
  2474. index_range = selected_interval.end_index-selected_interval.start_index-SWR_idx_len
  2475. shuffled_start_idx = int(rng.random()*index_range)
  2476. ts_subset = ts[shuffled_start_idx:shuffled_start_idx+SWR_idx_len]
  2477. is_nonlocal = dist[shuffled_start_idx:shuffled_start_idx+SWR_idx_len]>20
  2478. count_nonlocal = np.nansum(is_nonlocal)
  2479. ts_nonlocal = np.concatenate((ts_nonlocal, ts_subset[is_nonlocal]))
  2480. if count_nonlocal>0:
  2481. perc_SWRs_contain_nonlocal[b] += 1
  2482. perc_nonlocal_contain_SWRs[b] = len(ts_nonlocal) #len(np.unique(ts_nonlocal))
  2483. perc_SWRs_contain_nonlocal = perc_SWRs_contain_nonlocal/len(SWR_intervals.intervals) * 100
  2484. perc_nonlocal_contain_SWRs = perc_nonlocal_contain_SWRs / \
  2485. (np.sum(nonlocal_immobility_intervals.indices.end_index -\
  2486. nonlocal_immobility_intervals.indices.start_index)) * 100
  2487. SWRs_contain_nonlocal_5[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 5)
  2488. SWRs_contain_nonlocal_95[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 95)
  2489. SWRs_contain_nonlocal_median[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 50)
  2490. nonlocal_contain_SWRs_5[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 5)
  2491. nonlocal_contain_SWRs_95[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 95)
  2492. nonlocal_contain_SWRs_median[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 50)
  2493. # %%
  2494. # % NonLocal Decode Overlapping with SWRs
  2495. n_nonnan = np.count_nonzero(~np.isnan(ripple_overlap))
  2496. n_more_than_95 = np.nansum(nonlocal_contain_SWRs_95 < ripple_overlap)
  2497. print(f"{n_more_than_95} of {n_nonnan} sessions ({n_more_than_95/n_nonnan*100:0.2f}%) had SWRs during non-local content than if SWRs were randomly allocated across immobility.")
  2498. # % SWRs with NonLocal Decode
  2499. n_more_than_95 = np.nansum(SWRs_contain_nonlocal_95 < SWR_data_by_session['non_local'])
  2500. n_nonnan = np.count_nonzero(~np.isnan(SWR_data_by_session['non_local']))
  2501. print(f"{n_more_than_95} of {n_nonnan} sessions ({n_more_than_95/n_nonnan*100:0.2f}%) had more non-local content during SWRs than if SWRs were randomly allocated across immobility.")
  2502. # %%
  2503. f, ax = plt.subplots(1, figsize=(4,4))
  2504. sns.kdeplot(nonlocal_contain_SWRs_95.flatten(), bw_adjust=0.75, color='red')
  2505. ax.set(xlabel='% SWRs with NonLocal Decode', ylabel='Proportion', xlim=[0,100])
  2506. f.savefig(join(figure_path,'swr_overlap_with_nl_over_events_shuffle.pdf'), format='pdf', transparent=True,
  2507. dpi=300, bbox_inches='tight')
  2508. f, ax = plt.subplots(1, figsize=(4,4))
  2509. sns.kdeplot(SWRs_contain_nonlocal_95.flatten(), bw_adjust=0.75, color='red')
  2510. ax.set(xlabel='% NonLocal Decode Overlapping with SWRs', ylabel='Proportion', xlim=[0,100])
  2511. f.savefig(join(figure_path,'nl_overlap_with_swr_over_events_shuffle.pdf'), format='pdf', transparent=True,
  2512. dpi=300, bbox_inches='tight')
  2513. # %% [markdown]
  2514. # ### SWR rate during immobility intervals with any nonlocal content vs only local content
  2515. # %%
  2516. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2517. animal = []
  2518. session = []
  2519. ripple_rate = []
  2520. seq_stats = None
  2521. for _, row in sessions.iterrows():
  2522. if not row['Recording_Error'] and not row['Position_Error'] and \
  2523. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2524. animal_idx = animal_dict[row['Animal']]
  2525. sess_idx = int(row['Session'][-2:])-1
  2526. # load intervals
  2527. seq = Sequences(join(row['Base_Directory'],
  2528. 'Preprocessed_Data/Sequences',
  2529. row['File']+'_MEC_immobility_sequences.txt'))
  2530. intervals = Sequence_Intervals(join(row['Base_Directory'],
  2531. 'Preprocessed_Data/Sequences/Intervals',
  2532. row['File']+'_immobility_intervals.txt'))
  2533. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2534. 'Preprocessed_Data/Sequences/Intervals',
  2535. row['File']+'_CA1_SWR_intervals.txt'))
  2536. ripple_rate_session = np.zeros(len(seq.stats))
  2537. # iterate over nonlocal intervals, then SWRs
  2538. if len(SWR_intervals.intervals>0):
  2539. for nl in range(len(seq.stats)):
  2540. # find overlaps
  2541. for ripple in SWR_intervals.intervals.itertuples():
  2542. if ripple.start_time<intervals.intervals.iloc[nl].end_time and \
  2543. ripple.end_time>intervals.intervals.iloc[nl].start_time:
  2544. ripple_rate_session[nl] +=1
  2545. elif ripple.end_time>end_time:
  2546. break
  2547. ripple_rate_session[nl] /= seq.stats.Duration.iloc[nl]
  2548. # build arrays over all sequences
  2549. animal_idx = animal_dict[row['Animal']]
  2550. animal.extend([animal_idx] * len(seq.stats))
  2551. session_idx = int(row['Session'][-2:])-1
  2552. session.extend([session_idx] * len(seq.stats))
  2553. ripple_rate.extend(ripple_rate_session)
  2554. if seq_stats is not None:
  2555. seq_stats = pd.concat([seq_stats, seq.stats], axis=0, ignore_index=True)
  2556. else:
  2557. seq_stats = seq.stats
  2558. # build full df
  2559. ripple_rate_by_seq = pd.DataFrame({'Animal': animal, 'Session': session, 'SWR_rate': ripple_rate})
  2560. ripple_rate_by_seq = pd.concat([ripple_rate_by_seq, seq_stats], axis=1)
  2561. # %%
  2562. plot_seq_metrics_comparison_over_events(ripple_rate_by_seq, 'SWR_rate', 'Non_local',
  2563. figure_path, 'local_vs_nonlocal_SWR_rate', 'SWR rate (Hz)', [0,4])
  2564. # %% [markdown]
  2565. # ### Distance between decoded positions in MEC and CA1 during SWRs
  2566. # %%
  2567. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2568. n_sessions = 20
  2569. n_animals = len(animal_dict)
  2570. n_shuffles = 1000
  2571. start_time = time.time()
  2572. loc_dist, loc_dist_vs_shuffle = \
  2573. [np.full((n_animals, n_sessions), np.NaN) for _ in range(2)]
  2574. # subset data
  2575. for _, row in sessions.iterrows():
  2576. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  2577. # load seq objects (any seq, we're just using its objects)
  2578. MEC_seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  2579. row['File']+'_MEC_SWR_sequences.txt'),
  2580. load_objects=True)
  2581. CA1_seq = Sequences(join(row['Base_Directory'], 'Preprocessed_Data/Sequences',
  2582. row['File']+'_CA1_SWR_sequences.txt'),
  2583. load_objects=True)
  2584. animal_idx = animal_dict[row['Animal']]
  2585. sess_idx = int(row['Session'][-2:])-1
  2586. # collect all CA1 decoded positions for shuffling
  2587. all_SWR_CA1_decoded_pos = np.array([])
  2588. for i, interval in CA1_seq.intervals.intervals.iterrows():
  2589. cr = CA1_seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  2590. all_SWR_CA1_decoded_pos = np.append(all_SWR_CA1_decoded_pos, cr.sum("state").acausal_posterior.argmax("position").values)
  2591. # calculate distance between decoded positions during each interval
  2592. loc_dist_by_seq, loc_dist_by_seq_sig = \
  2593. [np.full(len(CA1_seq.intervals.intervals), np.NaN) for _ in range(2)]
  2594. n_samps = len(all_SWR_CA1_decoded_pos)
  2595. for i, interval in CA1_seq.intervals.intervals.iterrows():
  2596. cr = MEC_seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  2597. MEC_decoded_pos = cr.sum("state").acausal_posterior.argmax("position").values
  2598. cr = CA1_seq.classifier.classifier_results.sel(time=slice(interval.start_time, interval.end_time))
  2599. CA1_decoded_pos = cr.sum("state").acausal_posterior.argmax("position").values
  2600. loc_dist_by_seq[i] = np.nanmean(np.abs(MEC_decoded_pos-CA1_decoded_pos))
  2601. # circularly shuffle decoded positions
  2602. loc_dist_shuffle = np.full(n_shuffles, np.NaN)
  2603. for s in range(n_shuffles):
  2604. CA1_decoded_pos_shuffle = np.roll(all_SWR_CA1_decoded_pos, np.random.randint(n_samps))
  2605. loc_dist_shuffle[s] = np.nanmean(np.abs(MEC_decoded_pos-CA1_decoded_pos_shuffle[0:len(MEC_decoded_pos)]))
  2606. loc_dist_by_seq_sig[i] = np.nanmean(loc_dist_by_seq[i]<loc_dist_shuffle)
  2607. loc_dist[animal_idx][sess_idx] = np.nanmean(loc_dist_by_seq)
  2608. loc_dist_vs_shuffle[animal_idx][sess_idx] = np.nanmean(loc_dist_by_seq_sig<0.05)
  2609. print(f"{row['File']} {time.time()-start_time}")
  2610. # %%
  2611. with open(join(figure_path,'SWR_loc_dist.pkl'), 'wb') as file:
  2612. pickle.dump(loc_dist, file)
  2613. with open(join(figure_path,'SWR_loc_dist_vs_shuffle.pkl'), 'wb') as file:
  2614. pickle.dump(loc_dist_vs_shuffle, file)
  2615. # %%
  2616. # plot: KDE of distance between decodes
  2617. printf(f"Decodes in MEC and CA1 were {np.nanmean(loc_dist):.02f} +/- {stats.sem(np.nanmean(loc_dist,axis=1), nan_policy='omit'):.02f}cm apart from each other during SWRs, " +\
  2618. f"and {np.nanmean(loc_dist_vs_shuffle)*100:.02f} +/- {stats.sem(np.nanmean(loc_dist_vs_shuffle,axis=1), nan_policy='omit'):.02f}% " +\
  2619. "of SWRs had distances below shuffle.")
  2620. plot_seq_metrics_over_sessions(n_animals, loc_dist, figure_path,
  2621. 'MEC_vs_CA1_decode_dist_during_SWRs', 'Distance between MEC and CA1 Decoded Positions (cm)', [0,130])
  2622. plot_seq_metrics_over_sessions(n_animals, loc_dist_vs_shuffle*100, figure_path,
  2623. 'MEC_vs_CA1_decode_dist_during_SWRs', 'Percent of Distances < 95th Percentile', [0,100])
  2624. # %% [markdown]
  2625. # # Figure S6
  2626. # %% [markdown]
  2627. # ### Repeat with HSEs
  2628. # %%
  2629. HSE_data_by_session, HSE_data_by_seq = combine_metrics_over_sessions(sessions, animal_dict, 'CA1_HSE')
  2630. # %%
  2631. # averaged over sessions
  2632. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2633. n_sessions = 20
  2634. n_animals = len(animal_dict)
  2635. hse_overlap = np.zeros((n_animals, n_sessions))
  2636. for _, row in sessions.iterrows():
  2637. if not row['Recording_Error'] and not row['Position_Error'] and \
  2638. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2639. animal_idx = animal_dict[row['Animal']]
  2640. sess_idx = int(row['Session'][-2:])-1
  2641. # load intervals
  2642. nonlocal_immobility_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2643. 'Preprocessed_Data/Sequences/Intervals',
  2644. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  2645. HSE_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2646. 'Preprocessed_Data/Sequences/Intervals',
  2647. row['File']+'_CA1_HSE_intervals.txt'))
  2648. # iterate over nonlocal intervals, then SWRs
  2649. for nl_immob in nonlocal_immobility_intervals.intervals.itertuples():
  2650. # find overlaps
  2651. for ripple in HSE_intervals.intervals.itertuples():
  2652. if ripple.start_time<nl_immob.end_time and ripple.end_time>nl_immob.start_time:
  2653. if ripple.start_time<=nl_immob.start_time:
  2654. hse_overlap[animal_idx,sess_idx] += ripple.end_time-nl_immob.start_time
  2655. elif ripple.end_time<=nl_immob.end_time:
  2656. hse_overlap[animal_idx,sess_idx] += ripple.end_time-ripple.start_time
  2657. else:
  2658. hse_overlap[animal_idx,sess_idx] += nl_immob.end_time-ripple.start_time
  2659. elif ripple.end_time>end_time:
  2660. break
  2661. hse_overlap[animal_idx,sess_idx] /= np.sum(nonlocal_immobility_intervals.intervals.end_time -\
  2662. nonlocal_immobility_intervals.intervals.start_time)
  2663. # remove sessions that don't have SWRs (e.g. no CA1 data)
  2664. hse_overlap = np.where(hse_overlap==0, np.nan, hse_overlap)
  2665. hse_overlap *= 100
  2666. # %%
  2667. print(f"{np.nanmean(np.nanmean(HSE_data_by_session['non_local'])):.02f} +/- {stats.sem(np.nanmean(HSE_data_by_session['non_local'],axis=1), nan_policy='omit'):.02f}" +\
  2668. f"% of HSEs have nonlocal coding in MEC,\n" +\
  2669. f"and {np.nanmean(np.nanmean(hse_overlap)):.02f} +/- {stats.sem(np.nanmean(hse_overlap,axis=1), nan_policy='omit'):.02f}" +\
  2670. f"% of nonlocal decoded time overlaps with HSEs.")
  2671. # %%
  2672. # plot
  2673. plot_seq_metrics_over_sessions(n_animals, hse_overlap, figure_path,
  2674. 'nl_overlap_with_hse', '% NonLocal Decode Overlapping with HSEs', [0,100])
  2675. plot_seq_metrics_over_sessions(n_animals, HSE_data_by_session['non_local'], figure_path,
  2676. 'hse_overlap_with_nl', '% HSEs with NonLocal Decode', [0,100])
  2677. # %% [markdown]
  2678. # ### compare to shuffle: % nonlocal coding overlapping with HSEs and vice versa
  2679. # %%
  2680. n_shuffles = 1000
  2681. rng = np.random.default_rng()
  2682. n_sessions = 20
  2683. n_animals = len(animal_dict)
  2684. nonlocal_contain_HSEs_5, nonlocal_contain_HSEs_95, nonlocal_contain_HSEs_median, \
  2685. HSEs_contain_nonlocal_5, HSEs_contain_nonlocal_95, HSEs_contain_nonlocal_median =\
  2686. [np.full((n_animals, n_sessions), np.NaN) for _ in range(6)]
  2687. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2688. for _, row in sessions.iterrows():
  2689. if not row['Recording_Error'] and not row['Position_Error'] and \
  2690. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2691. animal_idx = animal_dict[row['Animal']]
  2692. sess_idx = int(row['Session'][-2:])-1
  2693. seq = Sequences(join(row['Base_Directory'],
  2694. 'Preprocessed_Data/Sequences',
  2695. row['File']+'_MEC_immobility_sequences.txt'),
  2696. load_objects=True)
  2697. dist = seq.calc_dist_from_decode()
  2698. ts = seq.binned_data.timestamps
  2699. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2700. 'Preprocessed_Data/Sequences/Intervals',
  2701. row['File']+'_CA1_HSE_intervals.txt'))
  2702. # 1000 shuffles
  2703. perc_SWRs_contain_nonlocal = np.zeros(1000)
  2704. perc_nonlocal_contain_SWRs = np.zeros(1000)
  2705. for b in range(n_shuffles):
  2706. ts_nonlocal = np.array([])
  2707. for ripple in SWR_intervals.indices.itertuples():
  2708. # pick a random immobility interval
  2709. seq_idx = int(rng.random()*len(seq.stats))
  2710. selected_interval = seq.intervals.indices.iloc[seq_idx]
  2711. # pick a random time within that interval of the same duration as the SWR
  2712. SWR_idx_len = ripple.end_index-ripple.start_index
  2713. index_range = selected_interval.end_index-selected_interval.start_index-SWR_idx_len
  2714. shuffled_start_idx = int(rng.random()*index_range)
  2715. ts_subset = ts[shuffled_start_idx:shuffled_start_idx+SWR_idx_len]
  2716. is_nonlocal = dist[shuffled_start_idx:shuffled_start_idx+SWR_idx_len]>20
  2717. count_nonlocal = np.nansum(is_nonlocal)
  2718. ts_nonlocal = np.concatenate((ts_nonlocal, ts_subset[is_nonlocal]))
  2719. if count_nonlocal>0:
  2720. perc_SWRs_contain_nonlocal[b] += 1
  2721. perc_nonlocal_contain_SWRs[b] = len(ts_nonlocal) #len(np.unique(ts_nonlocal))
  2722. perc_SWRs_contain_nonlocal = perc_SWRs_contain_nonlocal/len(SWR_intervals.intervals) * 100
  2723. perc_nonlocal_contain_SWRs = perc_nonlocal_contain_SWRs / \
  2724. (np.sum(nonlocal_immobility_intervals.indices.end_index -\
  2725. nonlocal_immobility_intervals.indices.start_index)) * 100
  2726. HSEs_contain_nonlocal_5[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 5)
  2727. HSEs_contain_nonlocal_95[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 95)
  2728. HSEs_contain_nonlocal_median[animal_idx, sess_idx] = np.percentile(perc_SWRs_contain_nonlocal, 50)
  2729. nonlocal_contain_HSEs_5[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 5)
  2730. nonlocal_contain_HSEs_95[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 95)
  2731. nonlocal_contain_HSEs_median[animal_idx, sess_idx] = np.percentile(perc_nonlocal_contain_SWRs, 50)
  2732. # %%
  2733. # % NonLocal Decode Overlapping with SWRs
  2734. n_nonnan = np.count_nonzero(~np.isnan(hse_overlap))
  2735. n_more_than_95 = np.nansum(nonlocal_contain_HSEs_95 < hse_overlap)
  2736. print(f"{n_more_than_95} of {n_nonnan} sessions ({n_more_than_95/n_nonnan*100:0.2f}%) had HSEs during non-local content than if SWRs were randomly allocated across immobility.")
  2737. # % SWRs with NonLocal Decode
  2738. n_nonnan = np.count_nonzero(~np.isnan(HSE_data_by_session['non_local']))
  2739. n_more_than_95 = np.nansum(HSEs_contain_nonlocal_95 < HSE_data_by_session['non_local'])
  2740. print(f"{n_more_than_95} of {n_nonnan} sessions ({n_more_than_95/n_nonnan*100:0.2f}%) had more non-local content during HSEs than if SWRs were randomly allocated across immobility.")
  2741. # %%
  2742. f, ax = plt.subplots(1, figsize=(4,4))
  2743. sns.kdeplot(nonlocal_contain_HSEs_95.flatten(), bw_adjust=0.75, color='red')
  2744. ax.set(xlabel='% HSEs with NonLocal Decode', ylabel='Proportion', xlim=[0,100])
  2745. f.savefig(join(figure_path,'hse_overlap_with_nl_over_events_shuffle.pdf'), format='pdf', transparent=True,
  2746. dpi=300, bbox_inches='tight')
  2747. f, ax = plt.subplots(1, figsize=(4,4))
  2748. sns.kdeplot(HSEs_contain_nonlocal_95.flatten(), bw_adjust=0.75, color='red')
  2749. ax.set(xlabel='% NonLocal Decode Overlapping with HSEs', ylabel='Proportion', xlim=[0,100])
  2750. f.savefig(join(figure_path,'nl_overlap_with_hse_over_events_shuffle.pdf'), format='pdf', transparent=True,
  2751. dpi=300, bbox_inches='tight')
  2752. # %% [markdown]
  2753. # ### Non-local content that overlaps with SWRs vs not
  2754. # %%
  2755. sessions = pd.read_csv('Z:/WT_Sequences/all_sessions_Xmaze_CA1.csv')
  2756. animal = []
  2757. session = []
  2758. nl_ripple_overlap = []
  2759. seq_stats = None
  2760. for _, row in sessions.iterrows():
  2761. if not row['Recording_Error'] and not row['Position_Error'] and \
  2762. row['Task'] == 'X Maze' and int(row['Session'][-2:])<=20:
  2763. animal_idx = animal_dict[row['Animal']]
  2764. sess_idx = int(row['Session'][-2:])-1
  2765. # load intervals
  2766. seq = Sequences(join(row['Base_Directory'],
  2767. 'Preprocessed_Data/Sequences',
  2768. row['File']+'_MEC_nonlocal_immobility_sequences.txt'))
  2769. intervals = Sequence_Intervals(join(row['Base_Directory'],
  2770. 'Preprocessed_Data/Sequences/Intervals',
  2771. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  2772. SWR_intervals = Sequence_Intervals(join(row['Base_Directory'],
  2773. 'Preprocessed_Data/Sequences/Intervals',
  2774. row['File']+'_CA1_SWR_intervals.txt'))
  2775. nl_ripple_overlap_session = np.full(len(seq.stats), False)
  2776. # iterate over nonlocal intervals, then SWRs
  2777. if len(SWR_intervals.intervals>0):
  2778. for nl in range(len(seq.stats)):
  2779. # find overlaps
  2780. for ripple in SWR_intervals.intervals.itertuples():
  2781. if ripple.start_time<intervals.intervals.iloc[nl].end_time and \
  2782. ripple.end_time>intervals.intervals.iloc[nl].start_time:
  2783. nl_ripple_overlap_session[nl] = True
  2784. break
  2785. elif ripple.end_time>end_time:
  2786. break
  2787. # build arrays over all sequences
  2788. animal_idx = animal_dict[row['Animal']]
  2789. animal.extend([animal_idx] * len(seq.stats))
  2790. session_idx = int(row['Session'][-2:])-1
  2791. session.extend([session_idx] * len(seq.stats))
  2792. nl_ripple_overlap.extend(nl_ripple_overlap_session)
  2793. if seq_stats is not None:
  2794. seq_stats = pd.concat([seq_stats, seq.stats], axis=0, ignore_index=True)
  2795. else:
  2796. seq_stats = seq.stats
  2797. # build full df
  2798. ripple_overlap_by_seq = pd.DataFrame({'Animal': animal, 'Session': session, 'Ripple_overlap': nl_ripple_overlap})
  2799. ripple_overlap_by_seq = pd.concat([ripple_overlap_by_seq, seq_stats], axis=1)
  2800. # %%
  2801. # match durations
  2802. ripple_equivalent = []
  2803. yesripple_data_by_seq = ripple_overlap_by_seq.loc[ripple_overlap_by_seq.Ripple_overlap]
  2804. noripple_data_by_seq = ripple_overlap_by_seq.loc[~ripple_overlap_by_seq.Ripple_overlap]
  2805. for a in noripple_data_by_seq.Animal.unique():
  2806. start_idx = np.min(np.where(yesripple_data_by_seq.Animal==a))
  2807. for __, dur in noripple_data_by_seq.loc[noripple_data_by_seq.Animal==a].Duration.items():
  2808. idx = np.abs(yesripple_data_by_seq.loc[yesripple_data_by_seq.Animal==a].Duration - dur).argmin()
  2809. ripple_equivalent.append(start_idx + idx)
  2810. # %%
  2811. nl_ripple_overlap = []
  2812. nl_ripple_overlap.extend([False] * len(noripple_data_by_seq))
  2813. nl_ripple_overlap.extend([True] * len(yesripple_data_by_seq.iloc[ripple_equivalent]))
  2814. ripple_overlap_equivalent_by_seq = pd.concat([noripple_data_by_seq, yesripple_data_by_seq.iloc[ripple_equivalent]], ignore_index=True)
  2815. ripple_overlap_equivalent_by_seq.Ripple_overlap = nl_ripple_overlap
  2816. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'FR', 'Ripple_overlap',
  2817. figure_path, 'Nonlocal_SWR_vs_not_fr_matched', 'FR (Hz)', [0,50])
  2818. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'Percent_units', 'Ripple_overlap',
  2819. figure_path, 'Nonlocal_SWR_vs_not_percent_units_matched', '% Active Units', [0,100])
  2820. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'Spatial_information', 'Ripple_overlap',
  2821. figure_path, 'Nonlocal_SWR_vs_not_spatial_info_matched', 'Spatial information (bit/s)', [0,1.5])
  2822. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'Duration', 'Ripple_overlap',
  2823. figure_path, 'Nonlocal_SWR_vs_not_duration_matched', 'Duration (s)', [0,1])
  2824. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'Max_distance_from_animal', 'Ripple_overlap',
  2825. figure_path, 'Nonlocal_SWR_vs_not_lookahead_matched', 'Lookahead distance (cm)', [0,130])
  2826. plot_seq_metrics_comparison_over_events(ripple_overlap_equivalent_by_seq, 'Spatial_coverage_percent', 'Ripple_overlap',
  2827. figure_path, 'Nonlocal_SWR_vs_not_spatial_coverage_matched', '% Spatial coverage', [0,100])
  2828. # %% [markdown]
  2829. # ### SWRs that contain nonlocal content in MEC vs not
  2830. # %%
  2831. # match durations
  2832. nonlocal_SWR_equivalent = []
  2833. local_SWR_data_by_seq = SWR_data_by_seq.loc[~SWR_data_by_seq.Non_local]
  2834. nonlocal_SWR_data_by_seq = SWR_data_by_seq.loc[SWR_data_by_seq.Non_local]
  2835. for a in local_SWR_data_by_seq.Animal.unique():
  2836. start_idx = np.min(np.where(nonlocal_SWR_data_by_seq.Animal==a))
  2837. for __, dur in local_SWR_data_by_seq.loc[local_SWR_data_by_seq.Animal==a].Duration.items():
  2838. idx = np.abs(nonlocal_SWR_data_by_seq.loc[nonlocal_SWR_data_by_seq.Animal==a].Duration - dur).argmin()
  2839. nonlocal_SWR_equivalent.append(start_idx + idx)
  2840. # %%
  2841. non_local = []
  2842. non_local.extend([False] * len(local_SWR_data_by_seq))
  2843. non_local.extend([True] * len(nonlocal_SWR_data_by_seq.iloc[nonlocal_SWR_equivalent]))
  2844. SWR_data_equivalent_by_seq = pd.concat([local_SWR_data_by_seq, nonlocal_SWR_data_by_seq.iloc[nonlocal_SWR_equivalent]], ignore_index=True)
  2845. SWR_data_equivalent_by_seq.Non_local = non_local
  2846. plot_seq_metrics_comparison_over_events(SWR_data_equivalent_by_seq, 'FR', 'Non_local',
  2847. figure_path, 'SWR_local_vs_nonlocal_fr_matched', 'FR (Hz)', [0,50])
  2848. plot_seq_metrics_comparison_over_events(SWR_data_equivalent_by_seq, 'Percent_units', 'Non_local',
  2849. figure_path, 'SWR_local_vs_nonlocal_percent_units_matched', '% Active Units', [0,20])
  2850. plot_seq_metrics_comparison_over_events(SWR_data_equivalent_by_seq, 'Spatial_information', 'Non_local',
  2851. figure_path, 'SWR_local_vs_nonlocal_spatial_info_matched', 'Spatial information (bit/s)', [0,1.5])
  2852. plot_seq_metrics_comparison_over_events(SWR_data_equivalent_by_seq, 'Duration', 'Non_local',
  2853. figure_path, 'SWR_local_vs_nonlocal_duration_matched', 'Duration (s)', [0,0.2])
  2854. plot_seq_metrics_comparison_over_events(SWR_data_equivalent_by_seq, 'Animal_spatial_bin', 'Non_local',
  2855. figure_path, 'SWR_local_vs_nonlocal_animal_location_matched', 'Animal linearized position (cm)', [0,123])
  2856. # %% [markdown]
  2857. # # Figure S6 - part 2
  2858. # %% [markdown]
  2859. # ### example raw & filtered traces
  2860. # %%
  2861. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  2862. row = sessions.iloc[243]
  2863. nonlocal_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2864. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  2865. local_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2866. row['File']+'_MEC_local_immobility_intervals.txt'))
  2867. move_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2868. row['File']+'_movement_intervals.txt'))
  2869. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP', row['File']+'_MEC_theta_channel_lfp.txt'))
  2870. lfp_move = deepcopy(lfp)
  2871. lfp_move.subset_by_time(move_intervals.intervals.values)
  2872. lfp_nonlocal = deepcopy(lfp)
  2873. lfp_nonlocal.subset_by_time(nonlocal_intervals.intervals.values)
  2874. lfp_local = deepcopy(lfp)
  2875. lfp_local.subset_by_time(local_intervals.intervals.values)
  2876. # %%
  2877. print(lfp_move.name)
  2878. f, ax = plt.subplots(3)
  2879. high_theta_lfp = lf.bandpass_filter(lfp_local.lfp, low=lf.HIGH_THETA[0], high=lf.HIGH_THETA[1], pass_band=lf.HIGH_THETA[2])
  2880. low_theta_lfp = lf.bandpass_filter(lfp_local.lfp, low=lf.LOW_THETA[0], high=lf.LOW_THETA[1], pass_band=lf.LOW_THETA[2])
  2881. ax[0].plot(low_theta_lfp.squeeze(), color='b')
  2882. ax[0].plot(high_theta_lfp.squeeze(), color='r')
  2883. ax[0].plot(lfp_local.lfp)
  2884. ax[0].set(xlim=[1958,1958+625], ylim=[-150,150])
  2885. high_theta_lfp = lf.bandpass_filter(lfp_nonlocal.lfp, low=lf.HIGH_THETA[0], high=lf.HIGH_THETA[1], pass_band=lf.HIGH_THETA[2])
  2886. low_theta_lfp = lf.bandpass_filter(lfp_nonlocal.lfp, low=lf.LOW_THETA[0], high=lf.LOW_THETA[1], pass_band=lf.LOW_THETA[2])
  2887. ax[1].plot(low_theta_lfp.squeeze(), color='b')
  2888. ax[1].plot(high_theta_lfp.squeeze(), color='r')
  2889. ax[1].plot(lfp_nonlocal.lfp)
  2890. ax[1].set(xlim=[37798,37798+625], ylim=[-150,150])
  2891. high_theta_lfp = lf.bandpass_filter(lfp_move.lfp, low=lf.HIGH_THETA[0], high=lf.HIGH_THETA[1], pass_band=lf.HIGH_THETA[2])
  2892. low_theta_lfp = lf.bandpass_filter(lfp_move.lfp, low=lf.LOW_THETA[0], high=lf.LOW_THETA[1], pass_band=lf.LOW_THETA[2])
  2893. ax[2].plot(low_theta_lfp.squeeze(), color='b')
  2894. ax[2].plot(high_theta_lfp.squeeze(), color='r')
  2895. ax[2].plot(lfp_move.lfp)
  2896. ax[2].set(xlim=[625,2*625], ylim=[-150,150])
  2897. f.savefig(join(figure_path,'TopHat_DY06_theta_traces.pdf'), format='pdf', transparent=True,
  2898. dpi=300, bbox_inches='tight')
  2899. # %% [markdown]
  2900. # ### PSD in theta range
  2901. # %%
  2902. # PSD over movement vs immobiilty times
  2903. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  2904. n_sessions = 20
  2905. n_animals = len(animal_dict)
  2906. rate = 625
  2907. n_freqs = 129
  2908. immob_psd = np.full((n_animals, n_sessions, n_freqs), np.NaN)
  2909. move_psd = np.full((n_animals, n_sessions, n_freqs), np.NaN)
  2910. start_time = time.time()
  2911. for _, row in sessions.iterrows():
  2912. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  2913. animal_idx = animal_dict[row['Animal']]
  2914. sess_idx = int(row['Session'][-2:])-1
  2915. immob_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2916. row['File']+'_immobility_intervals.txt'))
  2917. move_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2918. row['File']+'_movement_intervals.txt'))
  2919. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP', row['File']+'_MEC_theta_channel_lfp.txt'))
  2920. lfp_immob = deepcopy(lfp)
  2921. lfp_immob.subset_by_time(immob_intervals.intervals.values)
  2922. freqs, immob_psd[animal_idx, sess_idx, :] = signal.welch(np.squeeze(lfp_immob.lfp), rate)
  2923. lfp_move = deepcopy(lfp)
  2924. lfp_move.subset_by_time(move_intervals.intervals.values)
  2925. _, move_psd[animal_idx, sess_idx, :] = signal.welch(np.squeeze(lfp_move.lfp), rate)
  2926. print(f"{row['File']} {time.time()-start_time}")
  2927. # %%
  2928. np.save(join(figure_path,'immob_psd.npy'), immob_psd)
  2929. np.save(join(figure_path,'move_psd.npy'), move_psd)
  2930. nonlocal_psd = np.load(join(figure_path,'nonlocal_psd.npy'))
  2931. local_psd = np.load(join(figure_path,'local_psd.npy'))
  2932. for a in range(n_animals):
  2933. for s in range(n_sessions):
  2934. immob_psd[a][s] /= np.nansum(immob_psd[a][s])
  2935. move_psd[a][s] /= np.nansum(move_psd[a][s])
  2936. nonlocal_psd[a][s] /= np.nansum(nonlocal_psd[a][s])
  2937. local_psd[a][s] /= np.nansum(local_psd[a][s])
  2938. freqs = freqs + (freqs[1]-freqs[0])/2 # plot at bin centers, not edges
  2939. # %%
  2940. # sum & plot over sessions
  2941. immob_psd_summed = np.nanmean(immob_psd, axis=1)
  2942. move_psd_summed = np.nanmean(move_psd, axis=1)
  2943. nonlocal_psd_summed = np.nanmean(nonlocal_psd, axis=1)
  2944. local_psd_summed = np.nanmean(local_psd, axis=1)
  2945. # comparison: mean + sem over animals
  2946. f, ax = plt.subplots()
  2947. ax.plot(freqs, np.nanmean(local_psd_summed, axis=0), color='black')
  2948. sem = stats.sem(local_psd_summed, nan_policy='omit')
  2949. ax.fill_between(freqs, np.nanmean(local_psd_summed, axis=0)-sem, \
  2950. np.nanmean(local_psd_summed, axis=0)+sem, color='grey', alpha=0.5, rasterized=True)
  2951. ax.plot(freqs, np.nanmean(nonlocal_psd_summed, axis=0), color='#1C968B')
  2952. sem = stats.sem(nonlocal_psd_summed, nan_policy='omit')
  2953. ax.fill_between(freqs, np.nanmean(nonlocal_psd_summed, axis=0)-sem, \
  2954. np.nanmean(nonlocal_psd_summed, axis=0)+sem, color='#1C968B', alpha=0.5, rasterized=True)
  2955. ax.plot(freqs, np.nanmean(move_psd_summed, axis=0), color='magenta')
  2956. sem = stats.sem(move_psd_summed, nan_policy='omit')
  2957. ax.fill_between(freqs, np.nanmean(move_psd_summed, axis=0)-sem, \
  2958. np.nanmean(move_psd_summed, axis=0)+sem, color='magenta', alpha=0.5, rasterized=True)
  2959. ax.set(xlabel='Frequency', ylabel='PSD')
  2960. f.savefig(join(figure_path,'local_nonlocal_move_psd.pdf'), format='pdf')
  2961. ax.set(xlim=[0,20])
  2962. f.savefig(join(figure_path,'local_nonlocal_move_psd_inset.pdf'), format='pdf')
  2963. # %% [markdown]
  2964. # ### Instantaneous frequency
  2965. # %%
  2966. # inst freq
  2967. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  2968. n_sessions = 20
  2969. n_animals = len(animal_dict)
  2970. nonlocal_freqs, local_freqs, move_freqs = \
  2971. [np.full((n_animals, n_sessions), np.NaN) for _ in range(3)]
  2972. start_time = time.time()
  2973. for _, row in sessions.iterrows():
  2974. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  2975. animal_idx = animal_dict[row['Animal']]
  2976. sess_idx = int(row['Session'][-2:])-1
  2977. nonlocal_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2978. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  2979. local_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2980. row['File']+'_MEC_local_immobility_intervals.txt'))
  2981. move_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  2982. row['File']+'_movement_intervals.txt'))
  2983. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP', row['File']+'_MEC_theta_channel_lfp.txt'))
  2984. lfp_nonlocal = deepcopy(lfp)
  2985. lfp_nonlocal.subset_by_time(nonlocal_intervals.intervals.values)
  2986. _, _, inst_freqs = lf.hilbert_envelope_phase_freq(lf.bandpass_filter(lfp_nonlocal.lfp, low=lf.THETA[0], high=lf.THETA[1], pass_band=lf.THETA[2]))
  2987. inst_freqs = inst_freqs.squeeze()
  2988. nonlocal_freqs[animal_idx, sess_idx] = np.mean(inst_freqs[inst_freqs<20])
  2989. lfp_local = deepcopy(lfp)
  2990. lfp_local.subset_by_time(local_intervals.intervals.values)
  2991. _, _, inst_freqs = lf.hilbert_envelope_phase_freq(lf.bandpass_filter(lfp_local.lfp, low=lf.THETA[0], high=lf.THETA[1], pass_band=lf.THETA[2]))
  2992. inst_freqs = inst_freqs.squeeze()
  2993. local_freqs[animal_idx, sess_idx] = np.mean(inst_freqs[inst_freqs<20])
  2994. lfp_move = deepcopy(lfp)
  2995. lfp_move.subset_by_time(move_intervals.intervals.values)
  2996. _, _, inst_freqs = lf.hilbert_envelope_phase_freq(lf.bandpass_filter(lfp_move.lfp, low=lf.THETA[0], high=lf.THETA[1], pass_band=lf.THETA[2]))
  2997. inst_freqs = inst_freqs.squeeze()
  2998. move_freqs[animal_idx, sess_idx] = np.mean(inst_freqs[inst_freqs<20])
  2999. print(f"{row['File']} {time.time()-start_time}")
  3000. # %%
  3001. delta_freqs = local_freqs - nonlocal_freqs
  3002. print(f"Instantaneous frequency decreases by {np.mean(np.nanmean(delta_freqs, axis=1)):.02f} +/- {stats.sem(np.nanmean(delta_freqs, axis=1)):.02f} Hz")
  3003. print(f"Instantaneous frequencies: {np.mean(np.nanmean(local_freqs, axis=1)):.02f} +/- {stats.sem(np.nanmean(local_freqs, axis=1)):.02f} Hz\n" + \
  3004. f"{np.mean(np.nanmean(nonlocal_freqs, axis=1)):.02f} +/- {stats.sem(np.nanmean(nonlocal_freqs, axis=1)):.02f} Hz\n" + \
  3005. f"{np.mean(np.nanmean(move_freqs, axis=1)):.02f} +/- {stats.sem(np.nanmean(move_freqs, axis=1)):.02f} Hz")
  3006. # %%
  3007. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3008. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3009. 'Feature_l': local_freqs.flatten(),
  3010. 'Feature_nl': nonlocal_freqs.flatten()})
  3011. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3012. figure_path, 'theta_inst_freq_l_vs_nl', 'Instantaneous Frequency (Hz)', [6,9])
  3013. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3014. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3015. 'Feature_l': nonlocal_freqs.flatten(),
  3016. 'Feature_nl': move_freqs.flatten()})
  3017. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3018. figure_path, 'theta_inst_freq_nl_vs_move', 'Instantaneous Frequency (Hz)', [6,9])
  3019. # %% [markdown]
  3020. # ### Power in theta bands
  3021. # %%
  3022. # Z-score of low and high theta bands
  3023. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  3024. n_sessions = 20
  3025. n_animals = len(animal_dict)
  3026. window = 0.5
  3027. nl_oldmethod_theta, nl_newmethod_theta, l_oldmethod_theta, l_newmethod_theta = \
  3028. [np.full((n_animals, n_sessions), np.NaN) for _ in range(4)]
  3029. start_time = time.time()
  3030. for _, row in sessions.iterrows():
  3031. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  3032. animal_idx = animal_dict[row['Animal']]
  3033. sess_idx = int(row['Session'][-2:])-1
  3034. nonlocal_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  3035. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  3036. local_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  3037. row['File']+'_MEC_local_immobility_intervals.txt'))
  3038. immob_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  3039. row['File']+'_immobility_intervals.txt'))
  3040. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP', row['File']+'_MEC_theta_channel_lfp.txt'))
  3041. lfp_nonlocal = deepcopy(lfp)
  3042. lfp_nonlocal.subset_by_time(nonlocal_intervals.intervals.values)
  3043. lfp_local = deepcopy(lfp)
  3044. lfp_local.subset_by_time(local_intervals.intervals.values)
  3045. lfp_immob = deepcopy(lfp)
  3046. lfp_immob.subset_by_time(immob_intervals.intervals.values)
  3047. oldmethod_immob_theta, oldmethod_theta_mean = lf.multitaper_filtered_power(lfp_immob.lfp, low=lf.NONLOCAL[0], high=lf.NONLOCAL[1], window=1)
  3048. oldmethod_theta_std = np.nanstd(oldmethod_immob_theta)
  3049. _, nl_oldmethod_theta_power = lf.multitaper_filtered_power(lfp_nonlocal.lfp, low=lf.NONLOCAL[0], high=lf.NONLOCAL[1], window=1)
  3050. nl_oldmethod_theta[animal_idx, sess_idx] = (nl_oldmethod_theta_power-oldmethod_theta_mean)/oldmethod_theta_std
  3051. _, l_oldmethod_theta_power = lf.multitaper_filtered_power(lfp_local.lfp, low=lf.NONLOCAL[0], high=lf.NONLOCAL[1], window=1)
  3052. l_oldmethod_theta[animal_idx, sess_idx] = (l_oldmethod_theta_power-oldmethod_theta_mean)/oldmethod_theta_std
  3053. nl_newmethod_theta_power, l_newmethod_theta_power = \
  3054. [[] for _ in range(2)]
  3055. # subset on intervals that are at least the size of the window
  3056. intervals = nonlocal_intervals.intervals.values
  3057. idx = np.where((intervals[:,1]-intervals[:,0])>window)[0]
  3058. for _, i in enumerate(idx):
  3059. power, _ = lf.multitaper_filtered_power(lfp.lfp[np.where((lfp.timestamps >= intervals[i, 0]) \
  3060. & (lfp.timestamps <= intervals[i, 1]))[0]], \
  3061. low=lf.NONLOCAL[0], high=lf.NONLOCAL[1], window=window)
  3062. nl_newmethod_theta_power.extend(power)
  3063. # repeat for local
  3064. intervals = local_intervals.intervals.values
  3065. idx = np.where((intervals[:,1]-intervals[:,0])>window)[0]
  3066. for _, i in enumerate(idx):
  3067. power, _ = lf.multitaper_filtered_power(lfp.lfp[np.where((lfp.timestamps >= intervals[i, 0]) \
  3068. & (lfp.timestamps <= intervals[i, 1]))[0]], \
  3069. low=lf.NONLOCAL[0], high=lf.NONLOCAL[1], window=window)
  3070. l_newmethod_theta_power.extend(power)
  3071. # z-score and add to per-session calculation
  3072. newmethod_theta_mean = np.mean(nl_newmethod_theta_power+l_newmethod_theta_power)
  3073. newmethod_theta_std = np.std(nl_newmethod_theta_power+l_newmethod_theta_power)
  3074. nl_newmethod_theta[animal_idx, sess_idx] = (np.mean(nl_newmethod_theta_power)-theta_mean)/theta_std
  3075. l_newmethod_theta[animal_idx, sess_idx] = (np.mean(l_newmethod_theta_power)-theta_mean)/theta_std
  3076. print(f"{row['File']} {time.time()-start_time}")
  3077. # %%
  3078. print(f"Type II theta power changes by {np.mean(np.nanmean(nl_oldmethod_theta-l_oldmethod_theta, axis=1)):.02f}" +\
  3079. f" +/- {stats.sem(np.nanmean(nl_oldmethod_theta-l_oldmethod_theta, axis=1)):.04f}")
  3080. print(f"Z-score Power: {np.mean(np.nanmean(l_oldmethod_theta, axis=1)):.02f} +/- {stats.sem(np.nanmean(l_oldmethod_theta, axis=1)):.04f}\n" + \
  3081. f"{np.mean(np.nanmean(nl_oldmethod_theta, axis=1)):.02f} +/- {stats.sem(np.nanmean(nl_oldmethod_theta, axis=1)):.04f}")
  3082. # %%
  3083. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3084. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3085. 'Feature_l': l_oldmethod_theta.flatten(),
  3086. 'Feature_nl': nl_oldmethod_theta.flatten()})
  3087. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3088. figure_path, 'low_theta_zscore_l_vs_nl', '3-7 Hz Power (Z-score)', [-1,1])
  3089. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3090. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3091. 'Feature_l': l_newmethod_theta.flatten(),
  3092. 'Feature_nl': nl_newmethod_theta.flatten()})
  3093. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3094. figure_path, 'high_theta_zscore_l_vs_nl', '3-7 Hz Power (Z-score)', [-1,1])
  3095. # %% [markdown]
  3096. # ### Spike-field coherence in theta band
  3097. # %%
  3098. # calculate
  3099. sessions = pd.read_csv(r'Z:/WT_Sequences/all_sessions_Xmaze.csv')
  3100. n_sessions = 20
  3101. n_animals = len(animal_dict)
  3102. nl_low_theta_coh, nl_high_theta_coh, l_low_theta_coh, l_high_theta_coh, \
  3103. delta_low_theta_coh, delta_high_theta_coh = \
  3104. [np.full((n_animals, n_sessions), np.NaN) for _ in range(6)]
  3105. start_time = time.time()
  3106. for _, row in sessions.iterrows():
  3107. if not row['Recording_Error'] and not row['Position_Error'] and int(row['Session'][-2:])<=20:
  3108. animal_idx = animal_dict[row['Animal']]
  3109. sess_idx = int(row['Session'][-2:])-1
  3110. # load intervals
  3111. nonlocal_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  3112. row['File']+'_MEC_nonlocal_immobility_intervals.txt'))
  3113. local_intervals = Sequence_Intervals(join(row['Base_Directory'], 'Preprocessed_Data/Sequences/Intervals',
  3114. row['File']+'_MEC_local_immobility_intervals.txt'))
  3115. # load LFP and subset on intervals
  3116. lfp = LFP(join(row['Base_Directory'], 'Preprocessed_Data/LFP', row['File']+'_MEC_theta_channel_lfp.txt'))
  3117. lfp_nonlocal = deepcopy(lfp)
  3118. lfp_nonlocal.subset_by_time(nonlocal_intervals.intervals.values)
  3119. lfp_local = deepcopy(lfp)
  3120. lfp_local.subset_by_time(local_intervals.intervals.values)
  3121. # load spikes
  3122. if ('2021_pilot' in row['Base_Directory']) or ('2022_winter' in row['Base_Directory']):
  3123. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  3124. row['File']+'_electrodes.txt'))
  3125. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes/g1', row['File']+'_imec0_spikes.txt'))
  3126. channels = electrodes.subset_by_location(regions=['Entorhinal area medial part dorsal zone'])
  3127. else:
  3128. if (row['Animal']=='Lamarr') and (sess_idx>=7):
  3129. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  3130. row['File']+'_imec0_electrodes.txt'))
  3131. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  3132. row['File']+'_imec0_spikes.txt'))
  3133. else:
  3134. electrodes = Electrodes(join(row['Base_Directory'], 'Preprocessed_Data/Electrodes',
  3135. row['File']+'_imec1_electrodes.txt'))
  3136. spikes = Spikes(join(row['Base_Directory'], 'Preprocessed_Data/Spikes',
  3137. row['File']+'_imec1_spikes.txt'))
  3138. channels = electrodes.subset_by_location(regions=['ENTm1','ENTm2','ENTm3','ENTm4','ENTm5','ENTm6'])
  3139. spikes.subset_by_channel(channels)
  3140. # bin spikes and subset on intervals
  3141. spikes_nonlocal_binned, _ = sp.calc_binned_spikes(spikes.spikes, \
  3142. intervals=np.append(lfp_nonlocal.timestamps, lfp_nonlocal.timestamps[-1]+1/lfp.rate))
  3143. spikes_nonlocal_fr = np.mean(spikes_nonlocal_binned, axis=1)
  3144. spikes_local_binned, _ = sp.calc_binned_spikes(spikes.spikes, \
  3145. intervals=np.append(lfp_local.timestamps, lfp_local.timestamps[-1]+1/lfp.rate))
  3146. spikes_local_fr = np.mean(spikes_local_binned, axis=1)
  3147. nl_low_theta_coh_units, nl_high_theta_coh_units, l_low_theta_coh_units, l_high_theta_coh_units = \
  3148. [np.full(len(spikes.spikes), np.NaN) for _ in range(4)]
  3149. # calculate rate-adjusted coherence per cell
  3150. for u in range(len(spikes.spikes)):
  3151. _, nl_low_theta_coh_units[u] = lf.multitaper_filtered_spike_coherence(spikes_nonlocal_binned[u][:, np.newaxis], lfp_nonlocal.lfp, \
  3152. spikes_nonlocal_fr[u], spikes_local_fr[u], \
  3153. low=lf.LOW_THETA[0], high=lf.LOW_THETA[1], window=1)
  3154. _, nl_high_theta_coh_units[u] = lf.multitaper_filtered_spike_coherence(spikes_nonlocal_binned[u][:, np.newaxis], lfp_nonlocal.lfp, \
  3155. spikes_nonlocal_fr[u], spikes_local_fr[u], \
  3156. low=lf.HIGH_THETA[0], high=lf.HIGH_THETA[1], window=1)
  3157. _, l_low_theta_coh_units[u] = lf.multitaper_filtered_spike_coherence(spikes_local_binned[u][:, np.newaxis], lfp_local.lfp, \
  3158. spikes_local_fr[u], spikes_nonlocal_fr[u], \
  3159. low=lf.LOW_THETA[0], high=lf.LOW_THETA[1], window=1)
  3160. _, l_high_theta_coh_units[u] = lf.multitaper_filtered_spike_coherence(spikes_local_binned[u][:, np.newaxis], lfp_local.lfp, \
  3161. spikes_local_fr[u], spikes_nonlocal_fr[u], \
  3162. low=lf.HIGH_THETA[0], high=lf.HIGH_THETA[1], window=1)
  3163. # average across the session
  3164. nl_low_theta_coh[animal_idx][sess_idx] = np.nanmean(nl_low_theta_coh_units)
  3165. nl_high_theta_coh[animal_idx][sess_idx] = np.nanmean(nl_high_theta_coh_units)
  3166. l_low_theta_coh[animal_idx][sess_idx] = np.nanmean(l_low_theta_coh_units)
  3167. l_high_theta_coh[animal_idx][sess_idx] = np.nanmean(l_high_theta_coh_units)
  3168. delta_low_theta_coh[animal_idx][sess_idx] = np.nanmean(nl_low_theta_coh_units-l_low_theta_coh_units)
  3169. delta_high_theta_coh[animal_idx][sess_idx] = np.nanmean(nl_high_theta_coh_units-l_high_theta_coh_units)
  3170. print(f"{row['File']} {time.time()-start_time}")
  3171. # %%
  3172. print(f"Type II theta coherence changes by {np.mean(np.nanmean(delta_low_theta_coh, axis=1)):.02f} +/- {stats.sem(np.nanmean(delta_low_theta_coh, axis=1)):.04f}")
  3173. print(f"Coherence: {np.mean(np.nanmean(l_low_theta_coh, axis=1)):.02f} +/- {stats.sem(np.nanmean(l_low_theta_coh, axis=1)):.04f}\n" + \
  3174. f"{np.mean(np.nanmean(nl_low_theta_coh, axis=1)):.02f} +/- {stats.sem(np.nanmean(nl_low_theta_coh, axis=1)):.04f}")
  3175. # %%
  3176. # analyze & plot
  3177. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3178. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3179. 'Feature_l': nl_low_theta_coh.flatten(),
  3180. 'Feature_nl': l_low_theta_coh.flatten()})
  3181. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3182. figure_path, 'low_theta_spike_field_coh_l_vs_nl', 'Coherence', [0,1])
  3183. data_by_session = pd.DataFrame({'Animal': np.repeat(np.arange(n_animals),n_sessions),
  3184. 'Session': np.tile(np.arange(n_sessions),n_animals),
  3185. 'Feature_l': nl_high_theta_coh.flatten(),
  3186. 'Feature_nl': l_high_theta_coh.flatten()})
  3187. plot_compare_paired_metrics('Feature_l', 'Feature_nl', data_by_session, \
  3188. figure_path, 'high_theta_spike_field_coh_l_vs_nl', 'Coherence', [0,1])
  3189. # %% [markdown]
  3190. # # Figure 4 &

Figures_published_edition.ipynb at commit dfbbde1, under GPL-3.0 · at the source

Overview

Authors: Emily A Aery Jones1,2, Isabel I C Low1,3, Frances S Cho1, Lisa M Giocomo1
  1. Department of Neurobiology, Stanford University School of Medicine, Stanford, CA USA
  2. Present Address: Department of Neurobiology, University of Maryland School of Medicine, Baltimore, MD USA
  3. Present Address: Zuckerman Mind Brain Behavior Institute, Columbia University, New York, NY USA
Institutions: University of Maryland, Baltimore (United States); Stanford Medicine (United States); Stanford University (United States); Mortimer B. Zuckerman Mind Brain Behavior Institute (United States); Columbia University (United States)
Journal: Nature neuroscience, volume 29, issue 5, pages 1181-1190
Dates: received 10 August 2024; accepted 28 January 2026; published online 1 April 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41593-026-02232-0 · PMID 41922514 · PMCID PMC13107481 · OpenAlex W7147527953
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), systems (subfield)
Methods: Spectral & time-frequency, Connectivity, Statistics, Machine learning, Preprocessing, Single-unit activity, calcium imaging
Keywords: Learning and memory, Neural circuits
MeSH: CA1 Region, Hippocampal*, Entorhinal Cortex*, Neurons*, Space Perception*, Action Potentials, Animals, Male, Mice, Mice, Inbred C57BL, Reward (* major topic)
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: NIMH NIH HHS (R01 MH106475); NINDS NIH HHS (U19 NS118284, F32 NS138225, K99 NS134734); Howard Hughes Medical Institute
Citations: cited by 1 paper (Europe PMC); 54 references in the paper
Research resources: RRID:IMSR_JAX:000664

Abstract

Neurons can collectively represent the current sensory experience during exploration or remote experiences during immobility. Remote representations can reflect learned associations and support learning. Neurons in medial entorhinal cortex (MEC) represent the animal’s current location during movement, but little is known about MEC representations during immobility. We recorded hundreds of neurons simultaneously in MEC and CA1 as mice learned to associate pairs of rewarded locations. During immobility, the MEC neural population frequently represented positions far from the animal’s location (‘nonlocal coding’). Cells with spatial firing fields at remote locations drove nonlocal coding, even as cells representing the current position remained active. While MEC nonlocal coding has been reported during sharp-wave ripples in CA1, we observed nonlocal coding more often outside of ripples and saw less CA1–MEC coordination during nonlocal coding. Further, nonlocal coding preferentially represented remote task-relevant locations at appropriate times. Together, this work suggests that MEC nonlocal coding could strengthen associations between locations independently from CA1.

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

Repositories

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

emilyasterjones/X_maze

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: b5692c46163cf889a074870ca44af2317f76132e, 24 October 2025
Languages: Python (2)
Size: 46 files, 2 scripts
Software Heritage: not archived
Found in: the text, “Electrophysiology”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (2 files), OpenCV (1 file), Pillow (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
4 files

petersaj/AP_histology

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 67d71af75657dbd14bd97604514337f4822b2a98, 23 September 2026
Languages: MATLAB (31)
Size: 35 files, 31 scripts
Software Heritage: not archived
Found in: the text, “Histology”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
33 files

emilyasterjones/ecephys_spike_sorting

License: BSD-2-Clause
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: b6ef3f0de4f16bf1bba2852edab4988d6345c521, 4 June 2024
Languages: Python (102), C++ (14), MATLAB (9), JavaScript (5), Shell (3), Jupyter (2), C/C++ (2)
Size: 237 files, 137 scripts
Software Heritage: not archived
Found in: the text, “Neural data preprocessing”
Holds: README, license file, environment (Pipfile, Pipfile.lock, setup.cfg, setup.py), tests, continuous integration, documentation, 2 notebooks
Not found: CITATION.cff
Tools: NumPy (51 files), pandas (17 files), Matplotlib (12 files), SciPy (11 files), Kilosort (4 files), scikit-learn (4 files), Phy (3 files), xarray (3 files), h5py (1 file), Pillow (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
139 files

jenniferColonell/ecephys_spike_sorting

License: BSD-2-Clause
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: d61fba8e4387780ea4277c78eb891aacf7a4ee6b, 22 April 2026
Languages: Python (94), C++ (14), JavaScript (5), MATLAB (4), Shell (2), C/C++ (2)
Size: 220 files, 121 scripts
Software Heritage: not archived
Found in: the text, “Neural data preprocessing”
Holds: README, license file, environment (Pipfile, Pipfile.lock, setup.cfg, setup.py), tests, continuous integration, documentation
Not found: CITATION.cff
Tools: NumPy (46 files), pandas (13 files), SciPy (12 files), Matplotlib (9 files), Kilosort (5 files), scikit-learn (4 files), xarray (3 files), h5py (1 file), Phy (1 file), Pillow (1 file), PyTorch (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
123 files

emilyasterjones/bombcell

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: b7127c41f35ec6e57973b3aa9b61ec79159e86f2, 11 March 2024
Languages: MATLAB (137), Python (8), C (3), R (1)
Size: 206 files, 149 scripts
Software Heritage: archived
Found in: the text, “Neural data preprocessing”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Statistics and Machine Learning Toolbox (23 files), Image Processing Toolbox (5 files), Signal Processing Toolbox (5 files), Phy (5 files), NumPy (4 files), SciPy (2 files), Optimization Toolbox (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
151 files

LorenFrankLab/track_linearization

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 424087881215e3e30bfbf00926571d9e5bef1239, 16 October 2025
Languages: Python (13), Jupyter (3)
Size: 30 files, 16 scripts
Software Heritage: not archived
Found in: the text, “Position tracking”
Holds: README, license file, environment (environment.yml, postBuild, pyproject.toml), tests, continuous integration, 3 notebooks
Not found: CITATION.cff, documentation
Tools: NumPy (14 files), NetworkX (9 files), Matplotlib (7 files), pandas (5 files), SciPy (4 files), Numba (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
18 files

Eden-Kramer-Lab/ripple_detection

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 68e1784b183f161a568d3154325cf770730bfb83, 26 September 2026
Languages: Python (38), Jupyter (4)
Size: 160 files, 42 scripts
Software Heritage: not archived
Found in: the text, “LFP analysis”
Holds: README, license file, CITATION.cff, environment (environment.yml, pyproject.toml, uv.lock), tests, continuous integration, documentation, 4 notebooks
Tools: NumPy (35 files), pandas (24 files), SciPy (12 files), Matplotlib (4 files)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
44 files

emilyasterjones/AeryJones_2025

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: dfbbde116929d420523421d7bfe5d441eb91568a, 11 June 2025
Languages: Python (18), Jupyter (6)
Size: 141 files, 24 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (setup.py), 6 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (19 files), pandas (16 files), SciPy (12 files), Matplotlib (10 files), seaborn (7 files), Neurodata Without Borders (PyNWB, MatNWB) (4 files), SpikeInterface (4 files), CircStat (2 files), DeepLabCut (2 files), OpenCV (2 files), Pingouin (2 files), NetworkX (1 file), Phy (1 file), Pillow (1 file), scikit-image (1 file), scikit-learn (1 file), statsmodels (1 file), xarray (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
26 files

Code availability

All original code can be found at https://github.com/emilyasterjones/AeryJones_2025.

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

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

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

The 0.1–300-Hz filtered LFP, isolated unit spike times, electrode site locations, trial data, mouse position and head direction, subject metadata and session metadata are available at https://dandiarchive.org/dandiset/001701/0.260120.0303. Source data are provided with this paper.

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

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 2 keywords, 10 MeSH terms, 3 funders, 52 references, 1 RRID.

Cite

This paper

Aery Jones, E. A., Low, I. I. C., Cho, F. S., & Giocomo, L. M. (2026). Entorhinal cortex represents task-relevant remote locations independently of CA1. Nature neuroscience, 29(5), 1181-1190. https://doi.org/10.1038/s41593-026-02232-0

BibTeX

@article{aeryjones2026entorhinal,
author = {Aery Jones, Emily A and Low, Isabel I C and Cho, Frances S and Giocomo, Lisa M},
title = {{Entorhinal cortex represents task-relevant remote locations independently of CA1}},
journal = {Nature neuroscience},
year = {2026},
month = apr,
volume = {29},
number = {5},
pages = {1181--1190},
publisher = {Nature Portfolio},
issn = {1097-6256},
doi = {10.1038/s41593-026-02232-0},
url = {https://doi.org/10.1038/s41593-026-02232-0},
pmid = {41922514},
pmcid = {PMC13107481}
}

RIS

TY - JOUR
AU - Aery Jones, Emily A
AU - Low, Isabel I C
AU - Cho, Frances S
AU - Giocomo, Lisa M
TI - Entorhinal cortex represents task-relevant remote locations independently of CA1
T2 - Nature neuroscience
J2 - Nat Neurosci
PY - 2026
DA - 2026/04/01
VL - 29
IS - 5
SP - 1181
EP - 1190
SN - 1097-6256
PB - Nature Portfolio
DO - 10.1038/s41593-026-02232-0
UR - https://doi.org/10.1038/s41593-026-02232-0
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41593-026-02232-0",
"type": "article-journal",
"title": "Entorhinal cortex represents task-relevant remote locations independently of CA1",
"container-title": "Nature neuroscience",
"author": [
{
"family": "Aery Jones",
"given": "Emily A"
},
{
"family": "Low",
"given": "Isabel I C"
},
{
"family": "Cho",
"given": "Frances S"
},
{
"family": "Giocomo",
"given": "Lisa M"
}
],
"container-title-short": "Nat Neurosci",
"volume": "29",
"issue": "5",
"page": "1181-1190",
"DOI": "10.1038/s41593-026-02232-0",
"PMID": "41922514",
"PMCID": "PMC13107481",
"ISSN": "1097-6256",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41593-026-02232-0",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
1
]
]
}
}

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.1016/j.patter.2026.101590 [code]
Density-based longitudinal neuron tracking in high-density electrophysiological recordings.
Journal: Patterns (New York, N.Y.)
In common: Phy, Kilosort, SpikeInterface, 14 other tools, 3 references
[2] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: Kilosort, SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), 12 other tools, mouse, 4 references
[3] doi:10.1038/s41593-026-02362-5 [code]
Replay of procedural memory is independent of the hippocampus.
Journal: Nature neuroscience
In common: Pingouin, h5py, Pillow, 9 other tools, mouse, 9 references
[4] doi:10.1038/s41467-026-75347-4 [code]
Sleep reveals dynamics integrating and segregating movement and stimulus representations in V1.
Journal: Nature communications
In common: Phy, Neurodata Without Borders (PyNWB, MatNWB), CircStat, 13 other tools, systems, mouse, 1 reference
[5] doi:10.1016/j.celrep.2026.117646 [code]
Medial entorhinal-hippocampal desynchronization parallels the emergence of memory impairment in a mouse model of Alzheimer's disease pathology.
Journal: Cell reports
In common: Neurodata Without Borders (PyNWB, MatNWB), CircStat, Optimization Toolbox, 9 other tools, systems, mouse, 5 references
[6] doi: [code]
Naturalistic behavior and self-generated neural activity predictive of self-correction
Journal: bioRxiv : the preprint server for biology
In common: SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), xarray, 11 other tools, 2 references
[7] doi:10.1038/s41467-026-76581-6 [code]
Thalamocortical bursts encode reward contingencies and drive associative learning.
Journal: Nature communications
In common: Kilosort, xarray, CircStat, 11 other tools, systems, mouse, 2 references
[8] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: Kilosort, Neurodata Without Borders (PyNWB, MatNWB), Numba, 12 other tools, systems, mouse, 1 reference
[9] 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, Numba, NetworkX, 12 other tools, systems, mouse, 2 references
[10] doi:10.1002/hipo.70131 [code]
Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences.
Journal: Hippocampus
In common: xarray, Numba, OpenCV, 6 other tools, systems, 6 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.