Reproducing Huszar et al. (2022) from dandiset 000552¶
Paper. Huszar R, Zhang Y, Blockus H, Buzsaki G (2022). Preconfigured dynamics in the hippocampus are guided by embryonic birthdate and rate of neurogenesis. Nature Neuroscience 25, 1201-1212. doi:10.1038/s41593-022-01138-x. This attempt used the full text of the author manuscript in PubMed Central (PMC10807234); the version of record is not open access. The Supplementary Tables were not available to it.
Dandiset. DANDI:000552, version
0.230630.2304. The paper cites no DANDI version (its data statement points to the Buzsaki lab
repository). This is the only published version and the most recent one, so it is pinned. The
draft differs from it only by three small BIDS files (README, dataset_description.json,
bids-validator-config.json) that the analysis does not use; the data check below shows this.
What the dandiset holds. 104 processed files (one per session, 17 mice) with sleep states and sharp-wave ripples, of which 65 (11 mice) also hold spike-sorted units, position and maze trials; and 13 raw files. The units carry no label of light-responsiveness (birthdate), no cell type and no waveforms, and no optogenetic pulse times are stored. The paper's central comparisons, same- against different-birthdate neurons and light-responsive neurons across birthdates, therefore cannot be made. What can be tested is the description of the dataset and the findings that use all pyramidal neurons on the familiar and novel mazes.
Code read. The paper's analysis code, https://github.com/rhuszar/optotagCA1_final at commit
51320d45c5ee41147048128df333f251379bf755 (MATLAB, with some result files). It sets the figure-eight
segments and the stem exclusion used here (see huszar.py). No data are taken from it.
No random numbers are drawn in the claim sections; the only random draw is the time shuffle of the
peri-ripple control in the data check, seeded in huszar.py.
| Claim | Status |
|---|---|
| cohort-birthdates | tested |
| sessions-durations | tested |
| spwr-duration | tested |
| unit-count | tested |
| maze-cohort | tested |
| maze-task | tested |
| novel-explorations | tested |
| fig5e-pv-decorrelation | tested |
| fig6c-theta-rate-preserved | tested |
| fig6c-novel-ratemap-corr-higher | tested |
| fig6d-info-lower-novel | tested |
| fig6d-rate-lower-novel | tested |
| ed2-light-responsive-count, fig1e, fig1f, fig1g, fig1h, fig2b, fig2c, fig2d, fig2f, fig2g, fig3c, fig3e, fig5b, fig5c, fig5f, fig6e, fig6f | blocked: no light-responsiveness labels or pulse times (data missing) |
| fig5d, fig7b, fig7ce | blocked: also need unit positions or cell types (data missing) |
| fig4d-model-fit | blocked: depends on fig3e |
| fig1c-radial-depth | blocked: histology not in the dandiset (data missing) |
| fig7fg-convergence-cofiring | blocked: connection detection does not match the paper's (methods underspecified) |
import time
import warnings
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from dandi.dandiapi import DandiAPIClient
from scipy import stats
import huszar as h
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 200)
warnings.filterwarnings("ignore", category=RuntimeWarning)
def familiar(s):
# the familiar figure-eight recording of a session: its first maze recording
return next(r for r in h.recordings(s) if r["familiar"])
started = time.time()
assets = h.list_assets()
sessions = h.load_all()
print(f"loaded {len(sessions)} processed files of dandiset {h.DANDISET_ID} version {h.DANDISET_VERSION} "
f"in {time.time() - started:.0f} s")
loaded 104 processed files of dandiset 000552 version 0.230630.2304 in 298 s
Data check¶
Each check of the metadata against the values is stated under its output.
proc, raw = assets[assets.kind == "processed"], assets[assets.kind == "raw"]
print(f"{len(assets)} files: {len(proc)} processed (behavior+ecephys), {len(raw)} raw_ecephys; "
f"{assets.size_gb.sum():.0f} GB in all, {proc.size_gb.sum():.1f} GB processed")
info = pd.DataFrame([{
"session": s["session_id"], "subject": s["subject"], "birthdate": s["birthdate"], "sex": s["sex"],
"age": s["age"], "date": s["start"][:10], "duration_min": round(s["end_s"] / 60, 1),
"states": len(s["states"]), "ripples": len(s["ripples"]),
"units": len(s["n_spikes"]) if s["has_units"] else 0, "position": s["has_position"],
"recordings": ",".join(map(str, sorted(s["trials"].recordings.unique()))) if s["has_position"] else "",
"trials": len(s["trials"]) if s["has_position"] else 0} for s in sessions])
subjects = info.groupby("subject").agg(
birthdate=("birthdate", "first"), sex=("sex", lambda x: x.value_counts().idxmax()),
sex_by_file=("sex", lambda x: ", ".join(f"{k} {v}" for k, v in x.value_counts().items())), sessions=("session", "size"),
with_units=("units", lambda u: int((u > 0).sum())), units=("units", "sum"),
with_position=("position", "sum")).reset_index()
subjects["raw_files"] = subjects.subject.map(raw.subject.str.replace("-", "_").value_counts()).fillna(0).astype(int)
print(subjects.to_string(index=False))
print("\nMice whose sex field differs between their files:", subjects.loc[subjects.sex_by_file.str.contains(","), ["subject", "sex_by_file"]].values.tolist())
print("Sex of each mouse is taken as the value in most of its files (column sex).")
print("\nAge field values:", info.age.unique().tolist())
print("Archive summary: numberOfSubjects 17, species Mus musculus, 117 files.")
117 files: 104 processed (behavior+ecephys), 13 raw_ecephys; 1545 GB in all, 12.6 GB processed subject birthdate sex sex_by_file sessions with_units units with_position raw_files e13_16f1 E13.5 F F 9 9 9 1213 9 0 e13_1f1 E13.5 F F 3, M 2 5 0 0 0 0 e13_1m1 E13.5 M M 9, F 3 12 0 0 0 0 e13_26m1 E13.5 M M 4 4 4 853 4 3 e14_1m1 E14.5 M M 12 12 0 0 0 0 e14_2m2 E14.5 M M 4 4 4 583 4 0 e14_2m3 E14.5 M M 8 8 8 452 8 0 e15_10m1 E15.5 M M 7 7 7 704 7 0 e15_13f1 E15.5 F F 4 4 4 1385 4 4 e15_4m1 E15.5 M M 3 3 0 0 0 0 e15_4m2 E15.5 M M 4 4 0 0 0 0 e15_5m1 E15.5 M M 3 3 0 0 0 0 e15_9f1 E15.5 F F 7 7 7 610 7 0 e16_1f1 E16.5 F F 9 9 9 604 9 0 e16_2m1 E16.5 M M 5 5 5 301 5 0 e16_3m1 E16.5 M M 4 4 4 697 4 2 e16_3m2 E16.5 M M 4 4 4 799 4 4 Mice whose sex field differs between their files: [['e13_1f1', 'F 3, M 2'], ['e13_1m1', 'M 9, F 3']] Sex of each mouse is taken as the value in most of its files (column sex). Age field values: ['P3M/P6M'] Archive summary: numberOfSubjects 17, species Mus musculus, 117 files.
The archive's summary (17 subjects, 117 files) matches the files. The birthdate (embryonic day of
electroporation) is stored nowhere in the files except as the prefix of the subject id (e13 for
E13.5); the age is a range (P3M/P6M) for every mouse. The sex field is not the same in all files of a mouse for
e13_1f1 and e13_1m1 (the counts by file are printed above); the sex of a mouse is taken here as the
value in most of its files. Six mice (e13_1f1 and e13_1m1 at E13.5, e14_1m1 at E14.5, and e15_4m1, e15_4m2 and e15_5m1 at
E15.5) have 39 files between them that hold sleep states and ripples but no units and no position.
draft = {a.path: a.size for a in DandiAPIClient().get_dandiset(h.DANDISET_ID, "draft").get_assets()}
pub = dict(zip(assets.path, assets["size"]))
print("Files only in the draft:", sorted(set(draft) - set(pub)))
print("Files only in the published version:", sorted(set(pub) - set(draft)))
print("Files in both with different sizes:", [p for p in pub if p in draft and draft[p] != pub[p]])
Files only in the draft: ['README', 'bids-validator-config.json', 'dataset_description.json'] Files only in the published version: [] Files in both with different sizes: []
The draft adds three small BIDS files and changes no data file, so the published version holds everything the draft holds that the analysis could use.
print(info.to_string(index=False))
session subject birthdate sex age date duration_min states ripples units position recordings trials e13_16f1_210302 e13_16f1 E13.5 F P3M/P6M 2021-03-02 350.1 30 5798 135 True 1 22 e13_16f1_210312 e13_16f1 E13.5 F P3M/P6M 2021-03-12 298.3 41 5402 124 True 1 21 e13_16f1_210314 e13_16f1 E13.5 F P3M/P6M 2021-03-14 269.9 38 4876 121 True 1 25 e13_16f1_210315 e13_16f1 E13.5 F P3M/P6M 2021-03-15 280.1 41 4508 114 True 1 20 e13_16f1_210317 e13_16f1 E13.5 F P3M/P6M 2021-03-17 307.6 46 5304 130 True 1 25 e13_16f1_210319 e13_16f1 E13.5 F P3M/P6M 2021-03-19 287.9 31 5404 120 True 1 26 e13_16f1_210322 e13_16f1 E13.5 F P3M/P6M 2021-03-22 302.3 46 5409 104 True 1 25 e13_16f1_210328 e13_16f1 E13.5 F P3M/P6M 2021-03-28 325.8 65 6250 211 True 1 21 e13_16f1_210331 e13_16f1 E13.5 F P3M/P6M 2021-03-31 303.1 30 5855 154 True 1 22 e13_1f1_200228 e13_1f1 E13.5 M P3M/P6M 2020-02-28 396.1 116 5827 0 False 0 e13_1f1_200229 e13_1f1 E13.5 M P3M/P6M 2020-02-29 248.3 90 3904 0 False 0 e13_1f1_200305 e13_1f1 E13.5 F P3M/P6M 2020-03-05 414.7 1 1942 0 False 0 e13_1f1_200306 e13_1f1 E13.5 F P3M/P6M 2020-03-06 367.2 5 2045 0 False 0 e13_1f1_200307 e13_1f1 E13.5 F P3M/P6M 2020-03-07 331.5 1 1247 0 False 0 e13_1m1_200220 e13_1m1 E13.5 M P3M/P6M 2020-02-20 113.7 41 1336 0 False 0 e13_1m1_200221 e13_1m1 E13.5 M P3M/P6M 2020-02-21 317.4 93 3878 0 False 0 e13_1m1_200222 e13_1m1 E13.5 M P3M/P6M 2020-02-22 309.4 57 3407 0 False 0 e13_1m1_200223 e13_1m1 E13.5 M P3M/P6M 2020-02-23 284.4 59 3308 0 False 0 e13_1m1_200224 e13_1m1 E13.5 M P3M/P6M 2020-02-24 455.2 99 5425 0 False 0 e13_1m1_200225 e13_1m1 E13.5 M P3M/P6M 2020-02-25 473.7 122 6629 0 False 0 e13_1m1_200226 e13_1m1 E13.5 M P3M/P6M 2020-02-26 208.6 50 2693 0 False 0 e13_1m1_200227 e13_1m1 E13.5 F P3M/P6M 2020-02-27 384.8 77 4535 0 False 0 e13_1m1_200228 e13_1m1 E13.5 F P3M/P6M 2020-02-28 395.4 83 5555 0 False 0 e13_1m1_200229 e13_1m1 E13.5 F P3M/P6M 2020-02-29 259.1 60 3496 0 False 0 e13_1m1_200305 e13_1m1 E13.5 M P3M/P6M 2020-03-05 414.2 13 3069 0 False 0 e13_1m1_200306 e13_1m1 E13.5 M P3M/P6M 2020-03-06 422.9 41 4147 0 False 0 e13_26m1_211019 e13_26m1 E13.5 M P3M/P6M 2021-10-19 404.9 60 7548 271 True 1,2 80 e13_26m1_211116 e13_26m1 E13.5 M P3M/P6M 2021-11-16 343.7 43 5770 209 True 1,2 33 e13_26m1_211119 e13_26m1 E13.5 M P3M/P6M 2021-11-19 427.4 67 6970 191 True 1,2 83 e13_26m1_211124 e13_26m1 E13.5 M P3M/P6M 2021-11-24 370.6 49 5976 182 True 1 29 e14_1m1_200130 e14_1m1 E14.5 M P3M/P6M 2020-01-30 159.0 22 1533 0 False 0 e14_1m1_200201 e14_1m1 E14.5 M P3M/P6M 2020-02-01 214.4 56 2889 0 False 0 e14_1m1_200203 e14_1m1 E14.5 M P3M/P6M 2020-02-03 344.4 55 3290 0 False 0 e14_1m1_200204 e14_1m1 E14.5 M P3M/P6M 2020-02-04 334.7 66 4230 0 False 0 e14_1m1_200205 e14_1m1 E14.5 M P3M/P6M 2020-02-05 468.9 93 5264 0 False 0 e14_1m1_200206 e14_1m1 E14.5 M P3M/P6M 2020-02-06 180.9 45 2485 0 False 0 e14_1m1_200207 e14_1m1 E14.5 M P3M/P6M 2020-02-07 233.8 44 2223 0 False 0 e14_1m1_200208 e14_1m1 E14.5 M P3M/P6M 2020-02-08 253.1 11 2106 0 False 0 e14_1m1_200210 e14_1m1 E14.5 M P3M/P6M 2020-02-10 242.9 45 2872 0 False 0 e14_1m1_200211 e14_1m1 E14.5 M P3M/P6M 2020-02-11 387.6 83 4227 0 False 0 e14_1m1_200212 e14_1m1 E14.5 M P3M/P6M 2020-02-12 377.5 76 4695 0 False 0 e14_1m1_200213 e14_1m1 E14.5 M P3M/P6M 2020-02-13 340.1 80 4310 0 False 0 e14_2m2_201013 e14_2m2 E14.5 M P3M/P6M 2020-10-13 423.6 11 6103 200 True 1 15 e14_2m2_201017 e14_2m2 E14.5 M P3M/P6M 2020-10-17 327.4 35 3906 86 True 1 21 e14_2m2_201019 e14_2m2 E14.5 M P3M/P6M 2020-10-19 309.2 27 4297 113 True 1 21 e14_2m2_201022 e14_2m2 E14.5 M P3M/P6M 2020-10-22 313.1 55 4159 184 True 1 19 e14_2m3_201017 e14_2m3 E14.5 M P3M/P6M 2020-10-17 329.3 70 5469 34 True 1 27 e14_2m3_201019 e14_2m3 E14.5 M P3M/P6M 2020-10-19 310.5 42 4870 36 True 1 23 e14_2m3_201118 e14_2m3 E14.5 M P3M/P6M 2020-11-18 288.9 64 2513 54 True 1 24 e14_2m3_201121 e14_2m3 E14.5 M P3M/P6M 2020-11-21 268.4 66 1483 56 True 1 22 e14_2m3_201125 e14_2m3 E14.5 M P3M/P6M 2020-11-25 391.1 110 5908 74 True 1 22 e14_2m3_201129 e14_2m3 E14.5 M P3M/P6M 2020-11-29 264.5 58 4384 72 True 1 25 e14_2m3_201206 e14_2m3 E14.5 M P3M/P6M 2020-12-06 267.6 80 4511 63 True 1 32 e14_2m3_201208 e14_2m3 E14.5 M P3M/P6M 2020-12-08 294.8 85 4744 63 True 1 39 e15_10m1_200716 e15_10m1 E15.5 M P3M/P6M 2020-07-16 290.2 45 4915 99 True 1 20 e15_10m1_200719 e15_10m1 E15.5 M P3M/P6M 2020-07-19 306.3 28 4706 83 True 1 20 e15_10m1_200723 e15_10m1 E15.5 M P3M/P6M 2020-07-23 307.4 54 4943 98 True 1 16 e15_10m1_200727 e15_10m1 E15.5 M P3M/P6M 2020-07-27 339.2 25 4952 97 True 1 27 e15_10m1_200803 e15_10m1 E15.5 M P3M/P6M 2020-08-03 361.6 46 5601 75 True 1 21 e15_10m1_200809 e15_10m1 E15.5 M P3M/P6M 2020-08-09 259.4 37 3965 127 True 1 21 e15_10m1_200812 e15_10m1 E15.5 M P3M/P6M 2020-08-12 311.9 45 5200 125 True 1 22 e15_13f1_220117 e15_13f1 E15.5 F P3M/P6M 2022-01-17 425.0 44 9135 376 True 1 35 e15_13f1_220118 e15_13f1 E15.5 F P3M/P6M 2022-01-18 494.2 42 9502 363 True 1,2 96 e15_13f1_220119 e15_13f1 E15.5 F P3M/P6M 2022-01-19 487.5 37 8682 343 True 1,3 63 e15_13f1_220120 e15_13f1 E15.5 F P3M/P6M 2022-01-20 454.0 79 8721 303 True 1,2 96 e15_4m1_191121 e15_4m1 E15.5 M P3M/P6M 2019-11-21 346.4 100 5396 0 False 0 e15_4m1_191125 e15_4m1 E15.5 M P3M/P6M 2019-11-25 281.5 66 2154 0 False 0 e15_4m1_191203 e15_4m1 E15.5 M P3M/P6M 2019-12-03 321.9 57 5087 0 False 0 e15_4m2_191212 e15_4m2 E15.5 M P3M/P6M 2019-12-12 165.6 29 2253 0 False 0 e15_4m2_191213 e15_4m2 E15.5 M P3M/P6M 2019-12-13 228.8 76 3525 0 False 0 e15_4m2_191215 e15_4m2 E15.5 M P3M/P6M 2019-12-15 256.8 55 3566 0 False 0 e15_4m2_191216 e15_4m2 E15.5 M P3M/P6M 2019-12-16 263.0 43 3173 0 False 0 e15_5m1_200313_1 e15_5m1 E15.5 M P3M/P6M 2020-03-13 243.8 60 4798 0 False 0 e15_5m1_200313_2 e15_5m1 E15.5 M P3M/P6M 2020-03-13 269.0 67 4234 0 False 0 e15_5m1_200314 e15_5m1 E15.5 M P3M/P6M 2020-03-14 333.1 125 6770 0 False 0 e15_9f1_200715 e15_9f1 E15.5 F P3M/P6M 2020-07-15 324.3 63 6275 69 True 1 21 e15_9f1_200717 e15_9f1 E15.5 F P3M/P6M 2020-07-17 275.4 52 5118 69 True 1 20 e15_9f1_200720 e15_9f1 E15.5 F P3M/P6M 2020-07-20 358.0 53 5937 70 True 1 30 e15_9f1_200723 e15_9f1 E15.5 F P3M/P6M 2020-07-23 306.0 55 5413 84 True 1 22 e15_9f1_200729 e15_9f1 E15.5 F P3M/P6M 2020-07-29 322.7 29 4637 118 True 1 28 e15_9f1_200802 e15_9f1 E15.5 F P3M/P6M 2020-08-02 295.2 25 4222 72 True 1 28 e15_9f1_200809 e15_9f1 E15.5 F P3M/P6M 2020-08-09 259.6 27 3617 128 True 1 20 e16_1f1_200823 e16_1f1 E16.5 F P3M/P6M 2020-08-23 433.6 47 7784 47 True 1 20 e16_1f1_200917 e16_1f1 E16.5 F P3M/P6M 2020-09-17 301.6 66 5899 62 True 1 20 e16_1f1_200918 e16_1f1 E16.5 F P3M/P6M 2020-09-18 326.8 80 6317 63 True 1 26 e16_1f1_200919 e16_1f1 E16.5 F P3M/P6M 2020-09-19 475.6 93 7332 65 True 1 29 e16_1f1_200920 e16_1f1 E16.5 F P3M/P6M 2020-09-20 404.1 115 6334 76 True 1 22 e16_1f1_200923 e16_1f1 E16.5 F P3M/P6M 2020-09-23 335.3 66 4464 65 True 1 22 e16_1f1_201001 e16_1f1 E16.5 F P3M/P6M 2020-10-01 325.1 58 5527 54 True 1 20 e16_1f1_201002 e16_1f1 E16.5 F P3M/P6M 2020-10-02 418.1 72 3948 91 True 1 20 e16_1f1_201004 e16_1f1 E16.5 F P3M/P6M 2020-10-04 358.7 48 3920 81 True 1 24 e16_2m1_201116 e16_2m1 E16.5 M P3M/P6M 2020-11-16 247.0 27 4247 50 True 1 12 e16_2m1_201119 e16_2m1 E16.5 M P3M/P6M 2020-11-19 308.9 56 3072 58 True 1 20 e16_2m1_201121 e16_2m1 E16.5 M P3M/P6M 2020-11-21 274.0 50 2559 60 True 1 22 e16_2m1_201129 e16_2m1 E16.5 M P3M/P6M 2020-11-29 264.1 29 2548 69 True 1 18 e16_2m1_201206 e16_2m1 E16.5 M P3M/P6M 2020-12-06 267.7 37 2971 64 True 1 20 e16_3m1_210111 e16_3m1 E16.5 M P3M/P6M 2021-01-11 246.6 39 3682 203 True 1 18 e16_3m1_210119 e16_3m1 E16.5 M P3M/P6M 2021-01-19 380.8 36 5794 145 True 1 26 e16_3m1_210201 e16_3m1 E16.5 M P3M/P6M 2021-02-01 292.5 52 5250 167 True 1 30 e16_3m1_210203 e16_3m1 E16.5 M P3M/P6M 2021-02-03 348.8 62 6420 182 True 1 22 e16_3m2_211210 e16_3m2 E16.5 M P3M/P6M 2021-12-10 484.5 100 10158 195 True 1 26 e16_3m2_211211 e16_3m2 E16.5 M P3M/P6M 2021-12-11 356.4 58 7506 197 True 1,3 77 e16_3m2_211212 e16_3m2 E16.5 M P3M/P6M 2021-12-12 372.4 38 6496 200 True 1,3 64 e16_3m2_211213 e16_3m2 E16.5 M P3M/P6M 2021-12-13 401.2 73 5869 207 True 1,2 56
units_files = [s for s in sessions if s["has_units"]]
print("Columns of the units tables:", sorted({tuple(s["units_columns"]) for s in units_files}))
for g in ("acquisition", "stimulus/presentation", "analysis", "intervals"):
print(f"Contents of {g} across files:", sorted({tuple(s["groups"].get(g, [])) for s in sessions}))
mods = pd.Series([str(s["processing"]) for s in sessions]).value_counts()
print("\nProcessing modules and their contents (number of files):")
for k, v in mods.items():
print(f" {v:3d} {k}")
import re
print("\nForms of unit_name over all units (digits as #):", sorted({re.sub(r"\d+", "#", n) for s in units_files for n in s["unit_names"]}))
print("Files where unit_name is the clu_id as text:", sum(all(n == str(c) for n, c in zip(s["unit_names"], s["clu_ids"])) for s in units_files), "of", len(units_files))
for s in units_files[:3]:
print(f" {s['session_id']}: unit_name {s['unit_names'][:5]}, clu_id {s['clu_ids'][:5].tolist()}, group_id {s['unit_groups'][:5]}")
same = sum(set(s["unit_groups"]) <= set(s["electrode_groups"]) for s in units_files)
print(f"\nFiles in which every unit group_id names an electrode group: {same} of {len(units_files)}")
print("Example (first file with units):", sorted(set(units_files[0]["unit_groups"])), "against",
units_files[0]["electrode_groups"])
Columns of the units tables: [('clu_id', 'group_id', 'id', 'spike_times', 'spike_times_index', 'unit_name')]
Contents of acquisition across files: [()]
Contents of stimulus/presentation across files: [()]
Contents of analysis across files: [()]
Contents of intervals across files: [(), ('epochs',), ('epochs', 'trials'), ('trials',)]
Processing modules and their contents (number of files):
64 {'behavior': ['LinearizedPosition', 'RewardEventsEightMazeTrack', 'SleepStates', 'SubjectPosition'], 'ecephys': ['Ripples']}
39 {'behavior': ['SleepStates'], 'ecephys': ['Ripples']}
1 {'behavior': ['LinearizedPosition', 'RewardEventsEightMazeTrack', 'SleepStates', 'SubjectPosition'], 'ecephys': ['LFP', 'Ripples']}
Forms of unit_name over all units (digits as #): ['#']
Files where unit_name is the clu_id as text: 0 of 65
e13_16f1_210302: unit_name ['1', '2', '3', '4', '5'], clu_id [119.0, 1162.0, 1167.0, 1181.0, 1196.0], group_id ['Group1', 'Group4', 'Group4', 'Group7', 'Group5']
e13_16f1_210312: unit_name ['1', '2', '3', '4', '5'], clu_id [1154.0, 1160.0, 1170.0, 1183.0, 1193.0], group_id ['Group3', 'Group5', 'Group4', 'Group4', 'Group2']
e13_16f1_210314: unit_name ['1', '2', '3', '4', '5'], clu_id [1156.0, 1163.0, 1173.0, 1180.0, 1186.0], group_id ['Group3', 'Group5', 'Group4', 'Group4', 'Group1']
Files in which every unit group_id names an electrode group: 1 of 65
Example (first file with units): ['Group1', 'Group2', 'Group3', 'Group4', 'Group5', 'Group6', 'Group7', 'Group8'] against ['Group 1', 'Group 2', 'Group 3', 'Group 4', 'Group 5', 'Group 6', 'Group 7', 'Group 8']
Nothing in the files marks a unit as light-responsive or gives its cell type; the units tables
hold only spike times, a unit name, a cluster id and a group id, and the forms of the unit names and
cluster ids printed above carry no cell type or light-response code. No optogenetic stimulation times
are stored (stimulus and acquisition are empty in every processed file; the raw files are checked
below). The units have no waveforms and no link to the electrodes table, and in some files their
group_id values ("Group3") are not the names of the electrode groups ("Group 3"), so a unit's
place on the probe cannot be recovered either.
rates = np.concatenate([h.mean_rates(s) for s in units_files])
print(f"Units: {len(rates)} in {len(units_files)} files")
print("Session mean firing rate (Hz), percentiles 0, 5, 25, 50, 75, 95, 100:",
np.percentile(rates, [0, 5, 25, 50, 75, 95, 100]).round(2).tolist())
print(f"Fraction at or below {h.PYR_MAX_RATE_HZ} Hz (this attempt's putative pyramidal rule): {np.mean(rates <= h.PYR_MAX_RATE_HZ):.3f}"
f"; at or below 10 Hz: {np.mean(rates <= 10):.3f}; paper's pyramidal fraction 7411/9471 = {7411 / 9471:.3f}")
print("Spike times sorted within every unit in every file:", all(s["spikes_sorted"] for s in units_files))
late = [(s["session_id"], round(s["last_spike"] - s["end_s"], 1)) for s in units_files if s["last_spike"] > s["end_s"] + 1]
print("Files whose last spike is more than 1 s after the last sleep-state or ripple interval:", late)
print("Earliest first spike over files (s):", round(min(s["first_spike"] for s in units_files), 3))
dur = np.concatenate([s["ripples"].duration.values for s in sessions])
print(f"\nRipples: {len(dur)} in {len(sessions)} files; durations {dur.min() * 1000:.1f} to {dur.max() * 1000:.1f} ms")
dd = np.concatenate([(s["ripples"].stop - s["ripples"].start - s["ripples"].duration).values for s in sessions])
print(f"stop - start - ripple_durations: largest absolute difference {np.abs(dd).max() * 1000:.3f} ms")
inside = np.concatenate([((s["ripples"].peak >= s["ripples"].start) & (s["ripples"].peak <= s["ripples"].stop)).values for s in sessions])
print(f"Ripple peaks inside their start-stop interval: {inside.mean():.4f}")
labels = pd.Series(np.concatenate([s["states"].label.values for s in sessions])).value_counts()
print("Sleep-state labels (intervals over all files):", labels.to_dict())
print("Files with at least one Non-REM interval:", sum((s["states"].label == "Non-REM").any() for s in sessions), "of", len(sessions))
Units: 8201 in 65 files
Session mean firing rate (Hz), percentiles 0, 5, 25, 50, 75, 95, 100: [0.0, 0.16, 0.51, 1.14, 2.32, 13.0, 45.97]
Fraction at or below 5.0 Hz (this attempt's putative pyramidal rule): 0.878; at or below 10 Hz: 0.927; paper's pyramidal fraction 7411/9471 = 0.782
Spike times sorted within every unit in every file: True
Files whose last spike is more than 1 s after the last sleep-state or ripple interval: [('e13_16f1_210302', 1.6), ('e13_16f1_210312', 2.4), ('e13_16f1_210314', 1.8), ('e13_16f1_210315', 2.5), ('e13_16f1_210319', 1.6), ('e13_16f1_210322', 2.3), ('e13_16f1_210328', 2.0), ('e13_16f1_210331', 1.6), ('e13_26m1_211019', 2.0), ('e13_26m1_211116', 1.9), ('e13_26m1_211119', 1.6), ('e13_26m1_211124', 1.8), ('e14_2m2_201013', 2.2), ('e14_2m2_201017', 1.8), ('e14_2m2_201019', 2.5), ('e14_2m2_201022', 1.5), ('e14_2m3_201019', 2.3), ('e14_2m3_201118', 1.8), ('e14_2m3_201125', 2.1), ('e14_2m3_201129', 1.8), ('e14_2m3_201206', 1.5), ('e14_2m3_201208', 2.2), ('e15_10m1_200716', 2.1), ('e15_10m1_200719', 2.2), ('e15_10m1_200723', 1.2), ('e15_10m1_200727', 1.5), ('e15_10m1_200803', 1.8), ('e15_10m1_200812', 1.7), ('e15_13f1_220117', 2.4), ('e15_13f1_220118', 2.1), ('e15_13f1_220119', 1.8), ('e15_13f1_220120', 2.4), ('e15_9f1_200717', 1.7), ('e15_9f1_200729', 5.2), ('e15_9f1_200802', 1.8), ('e15_9f1_200809', 1.7), ('e16_1f1_200823', 1.7), ('e16_1f1_200918', 1.5), ('e16_1f1_200919', 2.0), ('e16_1f1_200920', 1.6), ('e16_1f1_200923', 1.7), ('e16_1f1_201001', 2.5), ('e16_1f1_201002', 1.7), ('e16_1f1_201004', 1.9), ('e16_2m1_201116', 1.7), ('e16_2m1_201119', 2.3), ('e16_2m1_201121', 1.8), ('e16_2m1_201129', 1.5), ('e16_2m1_201206', 2.2), ('e16_3m1_210111', 2.2), ('e16_3m1_210119', 2.0), ('e16_3m1_210201', 2.4), ('e16_3m1_210203', 2.3), ('e16_3m2_211210', 1.7), ('e16_3m2_211211', 1.8), ('e16_3m2_211212', 2.1), ('e16_3m2_211213', 2.0)]
Earliest first spike over files (s): 0.003
Ripples: 494578 in 104 files; durations 20.0 to 199.2 ms
stop - start - ripple_durations: largest absolute difference 0.000 ms
Ripple peaks inside their start-stop interval: 1.0000
Sleep-state labels (intervals over all files): {'Awake': 2373, 'Non-REM': 2317, 'REM': 1050}
Files with at least one Non-REM interval: 102 of 104
Spike times are sorted. In 57 files the last spike comes 1.2 to 5.2 s after the end of the last sleep-state or ripple interval, so the session length used here (that end) is short by a few seconds. Ripple durations lie within the 20 to 200 ms limits of the Methods, and the stored durations equal stop minus start. Two files (e13_1f1_200305 and e13_1f1_200307) hold a single Awake interval for the whole session, so sleep was not scored in them. Units are in seconds and are consistent with the session lengths. The fraction of units at or below 5 Hz (the rule used here for putative pyramidal cells, as the files hold no cell type) is compared with the paper's fraction of pyramidal cells.
rows = []
for s in sessions:
if not s["has_position"]:
continue
for r in h.recordings(s):
p = r["pos"]
rows.append({"session": s["session_id"], "recording": r["recording"], "familiar": r["familiar"], "kind": r["kind"],
"trials": len(r["trials"]), "pos_unit": s["pos_unit"],
"dt_ms": round(1000 * np.median(np.diff(p.t)), 2),
"lin_max_in_trials": round(r["track_max_cm"], 1),
"x_range": f"{np.nanmin(p.x):.2f}-{np.nanmax(p.x):.2f}", "y_range": f"{np.nanmin(p.y):.2f}-{np.nanmax(p.y):.2f}",
"speed_p95": round(np.nanpercentile(p.speed, 95), 1)})
recs = pd.DataFrame(rows)
print(recs.to_string(index=False))
nan_trials = sum(int((s["trials"].start_time.isna() | s["trials"].stop_time.isna()).sum()) for s in sessions if s["has_position"])
print(f"\nTrials with no start or stop time: {nan_trials} of {sum(len(s['trials']) for s in sessions if s['has_position'])}")
fam_flag = []
for s in sessions:
if not s["has_position"]:
continue
first = min(s["trials"].recordings.unique(), key=lambda r: s["trials"][s["trials"].recordings == r].start_time.min())
for _, t in s["trials"].iterrows():
fam_flag.append({"first_recording": t.recordings == first, "is_familiar_maze": t.is_familiar_maze,
"single_recording_session": s["trials"].recordings.nunique() == 1})
ff = pd.DataFrame(fam_flag)
print("\nis_familiar_maze against whether the trial is in the session's first maze recording:")
print(pd.crosstab([ff.single_recording_session, ff.first_recording], ff.is_familiar_maze).to_string())
session recording familiar kind trials pos_unit dt_ms lin_max_in_trials x_range y_range speed_p95 e13_16f1_210302 1 True figure-eight 22 cm 33.33 204.9 1.90-87.45 2.16-89.49 10.5 e13_16f1_210312 1 True figure-eight 21 cm 33.33 204.7 2.99-88.24 1.77-89.08 17.4 e13_16f1_210314 1 True figure-eight 25 cm 33.33 204.9 2.95-90.44 1.58-89.07 15.8 e13_16f1_210315 1 True figure-eight 20 cm 33.33 205.0 3.03-91.05 2.94-88.91 16.5 e13_16f1_210317 1 True figure-eight 25 cm 33.33 204.8 2.34-89.45 1.94-89.14 14.8 e13_16f1_210319 1 True figure-eight 26 cm 33.33 204.9 2.17-88.92 2.04-88.80 13.8 e13_16f1_210322 1 True figure-eight 25 cm 33.33 204.9 2.52-87.14 2.86-89.15 12.6 e13_16f1_210328 1 True figure-eight 21 cm 33.33 204.8 2.11-91.94 2.00-88.99 14.6 e13_16f1_210331 1 True figure-eight 22 cm 33.33 204.9 2.14-89.03 1.70-89.30 14.1 e13_26m1_211019 1 True figure-eight 47 cm 33.33 204.6 2.17-88.08 3.65-89.32 23.5 e13_26m1_211019 2 False T-maze 33 cm 33.33 163.7 0.01-0.72 0.06-0.98 19.3 e13_26m1_211116 1 True figure-eight 21 cm 33.33 204.7 1.68-88.91 1.70-89.41 16.3 e13_26m1_211116 2 False linear track 9 cm 40.00 100.0 0.26-0.89 0.04-0.77 13.9 e13_26m1_211119 1 True figure-eight 25 cm 33.33 204.7 1.30-86.93 2.59-89.45 18.8 e13_26m1_211119 2 False linear track 58 cm 33.33 110.0 0.01-0.20 0.00-1.00 19.8 e13_26m1_211124 1 True figure-eight 26 cm 33.33 204.9 2.54-88.32 2.86-89.16 21.4 e14_2m2_201013 1 True figure-eight 15 cm 33.33 204.9 8.09-90.08 2.48-85.37 12.3 e14_2m2_201017 1 True figure-eight 21 cm 33.33 204.9 2.48-89.39 2.18-86.63 13.5 e14_2m2_201019 1 True figure-eight 21 cm 33.33 205.0 8.21-88.33 2.80-85.78 15.3 e14_2m2_201022 1 True figure-eight 19 cm 33.33 205.0 3.97-87.75 2.97-87.39 12.0 e14_2m3_201017 1 True figure-eight 27 cm 33.33 204.5 4.03-90.34 3.05-88.04 21.1 e14_2m3_201019 1 True figure-eight 23 cm 33.33 205.0 5.86-91.07 2.52-86.89 23.7 e14_2m3_201118 1 True figure-eight 24 cm 33.33 204.8 5.96-90.37 2.80-87.42 24.3 e14_2m3_201121 1 True figure-eight 22 cm 33.33 204.7 8.21-89.91 2.35-86.58 29.8 e14_2m3_201125 1 True figure-eight 22 cm 33.33 204.7 6.80-92.84 3.25-86.85 26.0 e14_2m3_201129 1 True figure-eight 25 cm 33.33 204.9 5.06-91.43 3.79-87.69 21.3 e14_2m3_201206 1 True figure-eight 32 cm 33.33 204.9 5.25-90.54 2.51-88.56 27.1 e14_2m3_201208 1 True figure-eight 39 cm 33.33 204.6 5.81-90.19 2.74-87.10 32.7 e15_10m1_200716 1 True figure-eight 20 cm 33.33 205.0 3.63-90.40 3.05-88.29 13.0 e15_10m1_200719 1 True figure-eight 20 cm 33.33 204.8 3.00-90.31 2.42-88.90 13.8 e15_10m1_200723 1 True figure-eight 16 cm 33.33 204.9 2.74-90.16 2.38-88.32 19.0 e15_10m1_200727 1 True figure-eight 27 cm 33.33 204.4 4.21-90.43 2.03-88.64 20.0 e15_10m1_200803 1 True figure-eight 21 cm 33.33 204.9 4.65-90.21 1.62-87.85 17.4 e15_10m1_200809 1 True figure-eight 21 cm 33.33 205.0 4.30-90.50 1.83-86.65 23.9 e15_10m1_200812 1 True figure-eight 22 cm 33.33 204.6 4.52-90.53 1.37-85.57 21.8 e15_13f1_220117 1 True figure-eight 32 cm 33.33 204.9 1.19-88.59 2.82-89.32 18.0 e15_13f1_220118 1 True figure-eight 25 cm 33.33 204.9 1.32-87.55 1.08-89.27 16.3 e15_13f1_220118 2 False linear track 68 cm 33.33 110.0 1.85-22.94 0.30-116.03 20.0 e15_13f1_220119 1 True figure-eight 29 cm 33.33 204.7 1.48-88.97 1.82-89.09 17.9 e15_13f1_220119 3 False T-maze 32 cm 33.33 163.8 0.29-65.28 0.52-89.40 21.5 e15_13f1_220120 1 True figure-eight 46 cm 33.33 204.8 1.72-88.35 2.91-89.32 19.1 e15_13f1_220120 2 False linear track 49 cm 40.00 100.0 0.24-0.90 0.03-0.75 16.1 e15_9f1_200715 1 True figure-eight 21 cm 33.33 204.7 4.19-88.99 3.58-86.67 17.2 e15_9f1_200717 1 True figure-eight 20 cm 33.33 204.9 3.70-90.11 3.24-86.69 18.4 e15_9f1_200720 1 True figure-eight 30 cm 33.33 204.9 2.49-89.96 1.34-87.67 20.6 e15_9f1_200723 1 True figure-eight 22 cm 33.33 204.9 2.56-91.80 2.58-88.99 20.9 e15_9f1_200729 1 True figure-eight 28 cm 33.33 204.9 2.48-89.91 3.01-88.39 20.4 e15_9f1_200802 1 True figure-eight 28 cm 33.33 205.0 2.59-92.67 1.86-88.04 18.7 e15_9f1_200809 1 True figure-eight 20 cm 33.33 204.9 2.50-90.51 2.25-88.37 18.6 e16_1f1_200823 1 True figure-eight 20 cm 33.33 205.0 4.51-89.52 4.56-84.57 14.7 e16_1f1_200917 1 True figure-eight 20 cm 33.33 204.7 3.40-91.50 3.37-88.83 19.8 e16_1f1_200918 1 True figure-eight 26 cm 33.33 205.0 3.57-93.93 2.93-88.59 18.6 e16_1f1_200919 1 True figure-eight 29 cm 33.33 204.9 4.68-90.24 3.14-88.28 19.5 e16_1f1_200920 1 True figure-eight 22 cm 33.33 204.9 3.96-90.97 2.80-89.11 15.1 e16_1f1_200923 1 True figure-eight 22 cm 33.33 204.9 1.44-91.81 1.96-88.94 17.5 e16_1f1_201001 1 True figure-eight 20 cm 33.33 204.9 4.38-90.68 2.49-87.03 19.4 e16_1f1_201002 1 True figure-eight 20 cm 33.33 204.9 7.02-90.49 2.71-88.65 15.0 e16_1f1_201004 1 True figure-eight 24 cm 33.33 204.8 5.57-92.72 1.98-88.93 21.4 e16_2m1_201116 1 True figure-eight 12 cm 33.33 204.7 5.04-90.01 6.10-82.54 12.4 e16_2m1_201119 1 True figure-eight 20 cm 33.33 204.6 2.36-93.19 1.98-88.66 12.5 e16_2m1_201121 1 True figure-eight 22 cm 33.33 204.5 2.39-89.49 1.68-88.62 14.4 e16_2m1_201129 1 True figure-eight 18 cm 33.33 204.6 3.88-91.88 2.41-87.43 12.1 e16_2m1_201206 1 True figure-eight 20 cm 33.33 204.7 3.52-90.78 1.83-87.67 12.4 e16_3m1_210111 1 True figure-eight 18 cm 33.33 204.5 10.27-94.00 7.15-88.07 12.8 e16_3m1_210119 1 True figure-eight 26 cm 33.33 205.0 2.56-89.96 1.56-89.11 17.4 e16_3m1_210201 1 True figure-eight 30 cm 33.33 204.6 2.39-90.28 1.80-89.18 16.6 e16_3m1_210203 1 True figure-eight 22 cm 33.33 205.0 2.34-89.86 2.40-88.99 15.3 e16_3m2_211210 1 True figure-eight 23 cm 33.33 204.8 1.30-88.75 1.36-88.05 16.3 e16_3m2_211211 1 True figure-eight 32 cm 33.33 204.7 7.16-86.80 4.20-88.46 22.2 e16_3m2_211211 3 False linear track 45 cm 33.33 110.0 1.41-22.94 0.28-115.96 17.7 e16_3m2_211212 1 True figure-eight 29 cm 33.33 204.9 1.66-88.17 1.76-87.88 18.0 e16_3m2_211212 3 False linear track 34 cm 40.00 100.0 28.93-110.22 5.49-93.53 16.0 e16_3m2_211213 1 True figure-eight 27 cm 33.33 204.9 1.22-91.21 2.35-89.21 16.4 e16_3m2_211213 2 False T-maze 29 cm 33.33 163.6 0.00-0.71 0.01-0.99 15.5 Trials with no start or stop time: 19 of 1942
is_familiar_maze against whether the trial is in the session's first maze recording:
is_familiar_maze False True
single_recording_session first_recording
False False 362 0
True 0 286
True True 1182 112
Position is stated in cm. The familiar figure-eight recordings span about 90 by 90 cm (the Methods
give a 79 by 79 cm maze, so the tracked head positions extend somewhat beyond it or the scale is
approximate), and the linearized position inside trials reaches about 205 cm, the trial length of
the Methods. In several novel recordings the x and y values lie between 0 and 1, so they are not in
cm as stated; their linearized position (0 to 100, 110 or 164 cm) looks like cm. Position is
sampled every 33.3 ms (30 Hz) in most recordings and every 40 ms (25 Hz) in some novel ones.
is_familiar_maze is True for only the first two trials of sessions with a single maze
recording, where every trial is on the familiar maze, so the column is wrong there; this attempt
takes the first maze recording of a session as the familiar figure-eight (Methods: the familiar
maze was always run first).
raw_info = pd.DataFrame([h.raw_file_info(a) for a in raw.asset])
raw_info["session_date_in_name"] = raw_info.path.str.extract(r"-(\d{6})-raw")[0]
print(raw_info.to_string(index=False))
big = [s for s in sessions if "lfp" in s]
for s in big:
print(f"\nProcessed file {s['session_id']} also holds an LFP series: {s['lfp']}")
rawa = raw[raw.path.str.contains(s["session_id"].replace("_", "-") + "-raw")].asset.iloc[0]
proca = proc[proc.path.str.contains(s["session_id"].replace("_", "-") + "_behavior")].asset.iloc[0]
a, b = h.lfp_samples(proca, 1_000_000, 2000, 5), h.lfp_samples(rawa, 1_000_000, 2000, 5)
print("Same 2000 samples (channel 5) as the raw file's 1250 Hz LFP:", bool(np.array_equal(a, b)))
path start raw_shape raw_rate lfp_shape lfp_rate session_date_in_name
sub-e13-26m1/sub-e13-26m1_ses-e13-26m1-211019-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (728941440, 128) 30000.0 (30372560, 128) 1250.0 211019
sub-e13-26m1/sub-e13-26m1_ses-e13-26m1-211116-raw_ecephys.nwb 2021-11-16T00:00:00-05:00 (618657540, 128) 30000.0 (25777397, 128) 1250.0 211116
sub-e13-26m1/sub-e13-26m1_ses-e13-26m1-211124-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (667103040, 128) 30000.0 (27795960, 128) 1250.0 211124
sub-e15-13f1/sub-e15-13f1_ses-e15-13f1-220117-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 NaN NaN (31878000, 128) 1250.0 220117
sub-e15-13f1/sub-e15-13f1_ses-e15-13f1-220118-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (889593360, 128) 30000.0 (37066390, 128) 1250.0 220118
sub-e15-13f1/sub-e15-13f1_ses-e15-13f1-220119-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (877493760, 128) 30000.0 (36562240, 128) 1250.0 220119
sub-e15-13f1/sub-e15-13f1_ses-e15-13f1-220120-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (817332480, 128) 30000.0 (34055520, 128) 1250.0 220120
sub-e16-3m1/sub-e16-3m1_ses-e16-3m1-210201-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (526542720, 64) 30000.0 (21939280, 64) 1250.0 210201
sub-e16-3m1/sub-e16-3m1_ses-e16-3m1-210203-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (627940800, 64) 30000.0 NaN NaN 210203
sub-e16-3m2/sub-e16-3m2_ses-e16-3m2-211210-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (872089920, 128) 30000.0 (36337080, 128) 1250.0 211210
sub-e16-3m2/sub-e16-3m2_ses-e16-3m2-211211-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 NaN NaN (26731000, 128) 1250.0 211211
sub-e16-3m2/sub-e16-3m2_ses-e16-3m2-211212-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (670353600, 128) 30000.0 (27931400, 128) 1250.0 211212
sub-e16-3m2/sub-e16-3m2_ses-e16-3m2-211213-raw_ecephys.nwb 2011-08-18T00:00:00-04:00 (722190720, 128) 30000.0 (30091280, 128) 1250.0 211213
Processed file e16_3m1_210201 also holds an LFP series: {'shape': (21939280, 64), 'rate': 30000.0, 'conversion': 3.0517578125e-13}
Same 2000 samples (channel 5) as the raw file's 1250 Hz LFP: True
# Everything each raw file holds, and whether the end of one session holds clipped (flat or zeroed) stretches
def walk(g, items, prefix=""):
for k in g.keys():
o = g[k]
if hasattr(o, "keys"):
if prefix + k != "specifications":
walk(o, items, prefix + k + "/")
else:
items.append(f"{prefix}{k}")
return items
listing = {}
for a in raw.itertuples():
f = h.open_asset(a.asset)
listing[a.path] = walk(f, [])
f.close()
paths = sorted({k for v in listing.values() for k in v})
print(f"Datasets in the {len(listing)} raw files (number of files holding each):")
for k in paths:
print(f" {k}: {sum(k in v for v in listing.values())}")
stim_like = [k for k in paths if any(w in k.lower() for w in ("stim", "ttl", "event", "pulse", "opto", "digital", "analog"))]
print("Datasets whose path names a stimulus, TTL, event, pulse, optogenetic, digital or analog series:", stim_like)
ra = raw.iloc[-1]
f = h.open_asset(ra.asset)
d = f["acquisition/ElectricalSeries/data"]
rate = float(f["acquisition/ElectricalSeries/starting_time"].attrs["rate"])
ses = next(s for s in sessions if s["session_id"].replace("_", "-") in ra.path)
print(f"Raw length {d.shape[0] / rate:.1f} s; processed session {ses['session_id']} ends at {ses['end_s']:.1f} s")
scan = []
for k in range(1, 11):
st = d.shape[0] - int(k * 180 * rate)
x = d[st:st + int(2 * rate), :8].astype(int)
flat = (np.diff(x, axis=0) == 0).all(axis=1)
ends = np.diff(np.r_[0, flat.astype(int), 0])
runs = np.where(ends == -1)[0] - np.where(ends == 1)[0]
scan.append({"start_s": round(st / rate), "flat_steps": int(flat.sum()), "longest_flat": int(runs.max()) if len(runs) else 0,
"zero_on_all_8": int((x == 0).all(axis=1).sum())})
f.close()
scan = pd.DataFrame(scan)
print(f"Last 30 min, a 2 s window every 3 min, channels 0-7: samples unchanged on all 8 channels {scan.flat_steps.sum()}, "
f"longest such run {scan.longest_flat.max()}, samples zero on all 8 {scan.zero_on_all_8.sum()}")
Datasets in the 13 raw files (number of files holding each): acquisition/ElectricalSeries/data: 11 acquisition/ElectricalSeries/electrodes: 11 acquisition/ElectricalSeries/starting_time: 11 file_create_date: 13 general/extracellular_ephys/electrodes/channel_name: 13 general/extracellular_ephys/electrodes/gain_to_uV: 13 general/extracellular_ephys/electrodes/group: 13 general/extracellular_ephys/electrodes/group_name: 13 general/extracellular_ephys/electrodes/id: 13 general/extracellular_ephys/electrodes/location: 13 general/extracellular_ephys/electrodes/rel_x: 13 general/extracellular_ephys/electrodes/rel_y: 13 general/session_id: 13 general/subject/age: 13 general/subject/genotype: 13 general/subject/sex: 13 general/subject/species: 13 general/subject/strain: 13 general/subject/subject_id: 13 identifier: 13 processing/ecephys/LFP/ElectricalSeriesLFP/data: 12 processing/ecephys/LFP/ElectricalSeriesLFP/electrodes: 12 processing/ecephys/LFP/ElectricalSeriesLFP/starting_time: 12 session_description: 13 session_start_time: 13 timestamps_reference_time: 13 Datasets whose path names a stimulus, TTL, event, pulse, optogenetic, digital or analog series: []
Raw length 24073.0 s; processed session e16_3m2_211213 ends at 24071.0 s
Last 30 min, a 2 s window every 3 min, channels 0-7: samples unchanged on all 8 channels 0, longest such run 0, samples zero on all 8 0
The raw files hold the wideband signal (30 kHz) for 11 sessions and an LFP (1250 Hz) for 12. The listing of all 13 raw files above shows no stimulation, TTL or event series, only the two voltage series and the electrodes table. The Methods say light pulses were given at the end of each session, in blocks of five pulses 200 ms apart, and that the raw data were clipped out from 0.15 ms before to 0.8 ms after each pulse. They do not give the interval between blocks or say what the clipped samples hold. In the file of e16_3m2_211213 the wideband signal ends 2 s after the processed session, and ten 2 s windows over its last 30 min hold no flat or zeroed stretch on the eight channels read. If clipped samples are flat or zero, this is consistent with the stimulation period not being in the deposited recording; only this one file was scanned. Twelve of the 13 raw files give a session start of 2011-08-18, which cannot be right for sessions recorded in 2021 and 2022 (the date in the file name and in the processed file). The processed file of e16_3m1_210201 holds a copy of the raw file's 1250 Hz LFP, but labelled 30,000 Hz and with a conversion factor of 3.05e-13 V; both attributes are wrong. No analysis here uses the LFP.
# 1. Ripples and spikes: population rate around ripple peaks, against the same peaks shifted at random by up to 60 s.
centre = (h.RIPPLE_PSTH_EDGES[:-1] > -0.02) & (h.RIPPLE_PSTH_EDGES[:-1] < 0.02)
base = np.abs(h.RIPPLE_PSTH_EDGES[:-1]) >= 0.4
ratio = np.array([s["ripple_psth"][centre].mean() / s["ripple_psth"][base].mean() for s in units_files])
ratio_sh = np.array([s["ripple_psth_shuffled"][centre].mean() / s["ripple_psth_shuffled"][base].mean() for s in units_files])
print(f"Population rate within 20 ms of ripple peaks over rate 0.4-0.5 s away: median {np.median(ratio):.2f} "
f"(range {ratio.min():.2f} to {ratio.max():.2f}); shifted peaks: median {np.median(ratio_sh):.2f} "
f"(range {ratio_sh.min():.2f} to {ratio_sh.max():.2f})")
# 2. Ripples and sleep states: ripple rate in each state.
rr = []
for s in sessions:
st = s["states"]
for lab in st.label.unique():
sel = st[st.label == lab]
n = sum(((s["ripples"].peak >= a) & (s["ripples"].peak < b)).sum() for a, b in zip(sel.start, sel.stop))
rr.append({"session": s["session_id"], "state": lab, "per_min": n / ((sel.stop - sel.start).sum() / 60)})
rr = pd.DataFrame(rr).pivot(index="session", columns="state", values="per_min")
print("\nRipples per minute by sleep state, median over files:", rr.median().round(2).to_dict())
print("Files where the REM rate is the lowest of the three:", int((rr["REM"] < rr[["Awake", "Non-REM"]].min(axis=1)).sum()), "of", len(rr))
# 3. Trials, position and sleep states.
starts, ends, awake = [], [], []
for s in sessions:
if not s["has_position"]:
continue
p = s["pos"]
for k, g in p[p.trial >= 0].groupby("trial"):
starts.append(g.lin.iloc[0]); ends.append(g.lin.iloc[-1])
tr = s["trials"].dropna(subset=["start_time", "stop_time"])
aw = s["states"][s["states"].label == "Awake"]
tot = (tr.stop_time - tr.start_time).sum()
cov = sum(max(0, min(b, d) - max(a, c)) for a, b in zip(tr.start_time, tr.stop_time) for c, d in zip(aw.start, aw.stop))
awake.append(cov / tot)
print(f"\nLinearized position at the first sample of a trial: median {np.median(starts):.1f} cm; at the last: {np.median(ends):.1f} cm")
print(f"Fraction of trial time labelled Awake: median {np.median(awake):.3f}, lowest {np.min(awake):.3f}")
Population rate within 20 ms of ripple peaks over rate 0.4-0.5 s away: median 4.73 (range 3.60 to 6.35); shifted peaks: median 1.00 (range 0.96 to 1.06)
Ripples per minute by sleep state, median over files: {'Awake': 6.68, 'Non-REM': 27.73, 'REM': 0.34}
Files where the REM rate is the lowest of the three: 101 of 104
Linearized position at the first sample of a trial: median 2.1 cm; at the last: 203.9 cm Fraction of trial time labelled Awake: median 1.000, lowest 0.987
# 4. Spikes and position: spatial information of all units on the first maze recording, with spikes shifted in time.
shifts = [-2.0, -0.5, 0.0, 0.5, 2.0]
shift_rows = []
for s in units_files:
if not s["has_position"]:
continue
rec = familiar(s)
row = {"session": s["session_id"]}
for sh in shifts:
m = h.rate_maps(s["maze_spikes"], rec, shift_s=sh)
row[sh] = np.nanmedian(h.skaggs_information(m["maps"].reshape(len(m["maps"]), -1), m["occupancy"].ravel()))
shift_rows.append(row)
shift_df = pd.DataFrame(shift_rows).set_index("session")
best = shift_df.idxmax(axis=1)
print("Median spatial information (bits/s) by time shift of spikes against position, median over sessions:",
shift_df.median().round(3).to_dict())
print("Sessions whose information peaks at zero shift:", int((best == 0.0).sum()), "of", len(best),
"; others:", best[best != 0.0].to_dict())
Median spatial information (bits/s) by time shift of spikes against position, median over sessions: {-2.0: 0.368, -0.5: 0.534, 0.0: 0.575, 0.5: 0.523, 2.0: 0.414}
Sessions whose information peaks at zero shift: 53 of 65 ; others: {'e13_16f1_210315': 0.5, 'e14_2m2_201017': -0.5, 'e14_2m2_201019': 0.5, 'e14_2m3_201121': 0.5, 'e15_10m1_200719': -0.5, 'e15_10m1_200727': 0.5, 'e15_9f1_200717': 0.5, 'e15_9f1_200723': -0.5, 'e16_1f1_200920': 0.5, 'e16_1f1_200923': 0.5, 'e16_2m1_201129': 0.5, 'e16_3m2_211211': 0.5}
The checks are consistent with a shared clock. Population firing rises about five-fold at ripple peaks and not at randomly shifted peaks, so the ripple tables share the spikes' clock. Ripples are rarest in REM sleep in 101 of 104 files, as expected if the state intervals share that clock. Trials begin near 0 cm and end near 205 cm of linearized position and fall in Awake intervals, so the trials share the position clock. Spatial information is highest with no shift between spikes and position in 53 of 65 sessions and at a shift of 0.5 s in the other 12, well above its value at 2 s, so spikes and position share a clock to within about half a second; a finer offset is not ruled out. The 39 files without units could only be checked for ripples against sleep states.
# Recordings deposited more than once: identical spike counts per unit, or identical ripple tables.
sig_units = {}
for s in units_files:
sig_units.setdefault(tuple(s["n_spikes"]), []).append(s["session_id"])
sig_rip = {}
for s in sessions:
sig_rip.setdefault((len(s["ripples"]), round(float(s["ripples"].peak.sum()), 3)), []).append(s["session_id"])
print("Files sharing identical per-unit spike counts:", [v for v in sig_units.values() if len(v) > 1])
print("Files sharing identical ripple tables:", [v for v in sig_rip.values() if len(v) > 1])
two = info[info.session.str.startswith("e15_5m1_200313")]
print("\nThe two files of e15_5m1 on 2020-03-13:")
print(two.to_string(index=False))
Files sharing identical per-unit spike counts: []
Files sharing identical ripple tables: []
The two files of e15_5m1 on 2020-03-13:
session subject birthdate sex age date duration_min states ripples units position recordings trials
e15_5m1_200313_1 e15_5m1 E15.5 M P3M/P6M 2020-03-13 243.8 60 4798 0 False 0
e15_5m1_200313_2 e15_5m1 E15.5 M P3M/P6M 2020-03-13 269.0 67 4234 0 False 0
No recording is deposited twice. The two files of mouse e15_5m1 dated 2020-03-13 differ in length and in their ripples, so they are two recordings of one day (the paper's code lists both).
print("Mice:", subjects.subject.nunique())
print("By sex (each mouse's value in most of its files):", subjects.sex.value_counts().to_dict())
first_file = info.groupby("subject").sex.first()
print("By sex, read from each mouse's first file:", first_file.value_counts().to_dict())
print("Sex by file for the mice whose files differ:", subjects.loc[subjects.sex_by_file.str.contains(","), ["subject", "sex_by_file"]].values.tolist())
print("By birthdate:", subjects.birthdate.value_counts().sort_index().to_dict())
print("Paper: 17 mice; 12 M, 5 F; E13.5 4, E14.5 3, E15.5 6, E16.5 4.")
print(pd.crosstab(subjects.birthdate, subjects.sex).to_string())
subjects["sex_from_id"] = subjects.subject.str.extract(r"_\d+([fm])\d+$")[0].str.upper()
print("\nSex implied by the subject id (the letter before the last digit, f or m):", subjects.sex_from_id.value_counts().to_dict())
print("Mice whose sex field disagrees with their id:", subjects.loc[subjects.sex != subjects.sex_from_id, ["subject", "sex", "sex_from_id"]].values.tolist())
Mice: 17
By sex (each mouse's value in most of its files): {'M': 12, 'F': 5}
By sex, read from each mouse's first file: {'M': 13, 'F': 4}
Sex by file for the mice whose files differ: [['e13_1f1', 'F 3, M 2'], ['e13_1m1', 'M 9, F 3']]
By birthdate: {'E13.5': 4, 'E14.5': 3, 'E15.5': 6, 'E16.5': 4}
Paper: 17 mice; 12 M, 5 F; E13.5 4, E14.5 3, E15.5 6, E16.5 4.
sex F M
birthdate
E13.5 2 2
E14.5 0 3
E15.5 2 4
E16.5 1 3
Sex implied by the subject id (the letter before the last digit, f or m): {'M': 12, 'F': 5}
Mice whose sex field disagrees with their id: []
Result: reproduced. The dandiset holds 17 mice with the stated birthdates (E13.5 4, E14.5 3, E15.5 6, E16.5 4). Taking each mouse's sex from most of its files gives 12 males and 5 females, as the paper states. The sex field differs between files of two mice (e13_1f1 is Male in 2 files and Female in 3; e13_1m1 is Male in 9 and Female in 3), so reading only the first file of each mouse gives 13 and 4. The ids, which carry f or m, also give 12 and 5.
sessions-durations¶
The recordings comprise 92 sessions lasting 113.7 to 475.6 min (median 309.1 min), each including a homecage period of sleep and wake.
d = info.duration_min
print(f"Processed files (sessions): {len(info)}; paper: 92 (Methods) or 96 (Figure 1e and Figure 7f).")
print(f"Duration: shortest {d.min():.1f} min ({info.session[d.idxmin()]}), longest {d.max():.1f} min ({info.session[d.idxmax()]}), median {d.median():.1f} min")
print(f"Durations within 102.3-523.2 min: {int(d.between(102.3, 523.2).sum())} of {len(d)}; within 113.7-475.6 min: {int(d.between(113.7, 475.6).sum())}")
print("Sessions longer than 475.6 min:", info.loc[d > 475.6, ["session", "duration_min"]].values.tolist())
print("Sessions with a duration equal (to 0.1 min) to the paper's bounds:", info.loc[d.round(1).isin([113.7, 475.6]), ["session", "duration_min"]].values.tolist())
excl = info[~info.subject.isin(["e15_13f1", "e16_3m2"])]
print(f"\nVariant: without e15_13f1 and e16_3m2, the two mice absent from the paper code's homecage results: "
f"{len(excl)} sessions, median {excl.duration_min.median():.1f} min, range {excl.duration_min.min():.1f}-{excl.duration_min.max():.1f}")
print("Sessions per birthdate (all files):", info.birthdate.value_counts().sort_index().to_dict(),
"; Figure 1e: E13.5 30, E14.5 24, E15.5 24, E16.5 18")
print("Sessions per birthdate (without e15_13f1 and e16_3m2):", excl.birthdate.value_counts().sort_index().to_dict())
no_sleep = [(s["session_id"], s["states"].label.value_counts().to_dict()) for s in sessions if "Non-REM" not in set(s["states"].label)]
print(f"Files without a Non-REM interval: {len(no_sleep)} of {len(sessions)}: {no_sleep}")
Processed files (sessions): 104; paper: 92 (Methods) or 96 (Figure 1e and Figure 7f).
Duration: shortest 113.7 min (e13_1m1_200220), longest 494.2 min (e15_13f1_220118), median 319.6 min
Durations within 102.3-523.2 min: 104 of 104; within 113.7-475.6 min: 101
Sessions longer than 475.6 min: [['e15_13f1_220118', 494.2], ['e15_13f1_220119', 487.5], ['e16_3m2_211210', 484.5]]
Sessions with a duration equal (to 0.1 min) to the paper's bounds: [['e13_1m1_200220', 113.7], ['e16_1f1_200919', 475.6]]
Variant: without e15_13f1 and e16_3m2, the two mice absent from the paper code's homecage results: 96 sessions, median 309.9 min, range 113.7-475.6
Sessions per birthdate (all files): {'E13.5': 30, 'E14.5': 24, 'E15.5': 28, 'E16.5': 22} ; Figure 1e: E13.5 30, E14.5 24, E15.5 24, E16.5 18
Sessions per birthdate (without e15_13f1 and e16_3m2): {'E13.5': 30, 'E14.5': 24, 'E15.5': 24, 'E16.5': 18}
Files without a Non-REM interval: 2 of 104: [('e13_1f1_200305', {'Awake': 1}), ('e13_1f1_200307', {'Awake': 1})]
Result: partially reproduced. The 104 files are within 10% of the paper's other session count (96, from Figure 1e and Figure 7f), though not of the 92 in the Methods; their median length is 319.6 min (paper 309.1) and all lie within the widened range, from 113.7 to 494.2 min. Without e15_13f1 and e16_3m2 there are 96 sessions whose per-birthdate counts equal those of Figure 1e (30, 24, 24, 18) and whose lengths run from 113.7 to 475.6 min, the paper's bounds, with a median of 309.9 min. Two files of e13_1f1 hold no scored sleep (one Awake interval for the whole session), so not every session holds the sleep the claim states; that part fails.
spwr-duration¶
SPW-Rs were detected with duration limits of 20 to 200 ms, and their median duration across all recordings was 36.4 ms.
per = pd.DataFrame([{"session": s["session_id"], "subject": s["subject"], "n": len(s["ripples"]),
"median_ms": 1000 * s["ripples"].duration.median()} for s in sessions])
print(f"(a) median over all {len(dur)} ripples pooled: {1000 * np.median(dur):.1f} ms")
print(f"(b) median of the {len(per)} per-session medians: {per.median_ms.median():.1f} ms")
print(f"Ripples within 20-200 ms: {np.mean((dur >= 0.02) & (dur <= 0.2)):.4f}; shortest {1000 * dur.min():.1f}, longest {1000 * dur.max():.1f} ms")
print("Paper: 36.4 ms (tolerance 32.8-40.0).")
print("\nBy mouse (median of session medians, ms, and ripples):")
print(per.groupby("subject").agg(sessions=("n", "size"), ripples=("n", "sum"), median_ms=("median_ms", "median")).round(1).to_string())
(a) median over all 494578 ripples pooled: 36.0 ms
(b) median of the 104 per-session medians: 36.0 ms
Ripples within 20-200 ms: 1.0000; shortest 20.0, longest 199.2 ms
Paper: 36.4 ms (tolerance 32.8-40.0).
By mouse (median of session medians, ms, and ripples):
sessions ripples median_ms
subject
e13_16f1 9 48806 38.4
e13_1f1 5 14965 40.0
e13_1m1 12 47478 39.2
e13_26m1 4 26264 34.4
e14_1m1 12 40124 36.8
e14_2m2 4 18465 35.2
e14_2m3 8 33882 35.6
e15_10m1 7 34282 37.6
e15_13f1 4 36040 35.6
e15_4m1 3 12637 29.6
e15_4m2 4 12517 31.6
e15_5m1 3 15802 32.0
e15_9f1 7 35219 36.0
e16_1f1 9 51525 35.2
e16_2m1 5 15397 32.0
e16_3m1 4 21146 39.6
e16_3m2 4 30029 33.2
Result: reproduced. The median ripple duration is 36.0 ms under both readings (paper 36.4 ms), and all 494,578 ripples last 20.0 to 199.2 ms.
unit-count¶
The dataset includes 9,471 well-isolated units, 7,411 putative pyramidal cells and 2,060 putative interneurons.
print(f"Units in the dandiset: {len(rates)} in {len(units_files)} of {len(sessions)} files; paper 9,471 (tolerance 8,524-10,418)")
print(f"Fraction at or below {h.PYR_MAX_RATE_HZ} Hz: {np.mean(rates <= h.PYR_MAX_RATE_HZ):.3f} (paper's pyramidal fraction 0.783)")
print("\nBy mouse:")
print(subjects[["subject", "birthdate", "sessions", "with_units", "units"]].to_string(index=False))
nou = subjects[subjects.with_units == 0]
print(f"\nMice without any units: {nou.subject.tolist()} ({int(nou.sessions.sum())} files)")
print(f"Units per file with units: median {np.median([len(s['n_spikes']) for s in units_files]):.0f}, "
f"range {min(len(s['n_spikes']) for s in units_files)}-{max(len(s['n_spikes']) for s in units_files)}")
Units in the dandiset: 8201 in 65 of 104 files; paper 9,471 (tolerance 8,524-10,418) Fraction at or below 5.0 Hz: 0.878 (paper's pyramidal fraction 0.783) By mouse: subject birthdate sessions with_units units e13_16f1 E13.5 9 9 1213 e13_1f1 E13.5 5 0 0 e13_1m1 E13.5 12 0 0 e13_26m1 E13.5 4 4 853 e14_1m1 E14.5 12 0 0 e14_2m2 E14.5 4 4 583 e14_2m3 E14.5 8 8 452 e15_10m1 E15.5 7 7 704 e15_13f1 E15.5 4 4 1385 e15_4m1 E15.5 3 0 0 e15_4m2 E15.5 4 0 0 e15_5m1 E15.5 3 0 0 e15_9f1 E15.5 7 7 610 e16_1f1 E16.5 9 9 604 e16_2m1 E16.5 5 5 301 e16_3m1 E16.5 4 4 697 e16_3m2 E16.5 4 4 799 Mice without any units: ['e13_1f1', 'e13_1m1', 'e14_1m1', 'e15_4m1', 'e15_4m2', 'e15_5m1'] (39 files) Units per file with units: median 99, range 34-376
Result: not reproduced. The dandiset holds 8,201 units, fewer than the paper's 9,471 (tolerance 8,524 to 10,418). Six of the 17 mice (39 files) have no units at all. The 5 Hz rule classes 87.8% of units as putative pyramidal cells, against the paper's 78.3% pyramidal by waveform and autocorrelogram.
maze-cohort¶
Nine mice (E13.5 n = 2, E14.5 n = 2, E15.5 n = 2, E16.5 n = 3) ran a spatial alternation task on a familiar figure-eight maze, and a cohort of three mice also explored novel environments.
maze_mice = subjects[subjects.with_position > 0]
novel_mice = sorted({s["subject"] for s in sessions if s["has_position"] and s["trials"].recordings.nunique() > 1})
print("Mice with figure-eight trials:", len(maze_mice), maze_mice.birthdate.value_counts().sort_index().to_dict())
print("Mice with a second maze recording (novel):", novel_mice)
code_nine = maze_mice[~maze_mice.subject.isin(["e15_13f1", "e16_3m2"])]
print("(a) disjoint cohorts would need 12 maze mice; found", len(maze_mice))
print("(b) maze mice without e15_13f1 and e16_3m2:", len(code_nine), code_nine.birthdate.value_counts().sort_index().to_dict(),
"; novel mice:", len(novel_mice))
print("Paper: nine mice, E13.5 2, E14.5 2, E15.5 2, E16.5 3; novel cohort 3.")
Mice with figure-eight trials: 11 {'E13.5': 2, 'E14.5': 2, 'E15.5': 3, 'E16.5': 4}
Mice with a second maze recording (novel): ['e13_26m1', 'e15_13f1', 'e16_3m2']
(a) disjoint cohorts would need 12 maze mice; found 11
(b) maze mice without e15_13f1 and e16_3m2: 9 {'E13.5': 2, 'E14.5': 2, 'E15.5': 2, 'E16.5': 3} ; novel mice: 3
Paper: nine mice, E13.5 2, E14.5 2, E15.5 2, E16.5 3; novel cohort 3.
Result: reproduced. Under reading (b), the code's, the maze mice without e15_13f1 and e16_3m2 are nine, at E13.5 2, E14.5 2, E15.5 2 and E16.5 3, and three mice (e13_26m1, e15_13f1, e16_3m2) have novel recordings. Reading (a) fails: 11 mice have maze data, not 12, because e13_26m1 belongs to both cohorts, which the paper calls separate.
maze-task¶
On the figure-eight maze each trial was 205 cm long, position was tracked at 30 Hz, and animals ran at least ten trials along each arm in every session.
lens, dts, arms = [], [], []
for s in sessions:
if not s["has_position"]:
continue
fam = familiar(s)
p = fam["pos"]
lens += p.groupby("trial").lin.max().tolist()
dts.append(np.median(np.diff(p.t)))
t = fam["trials"]
arms.append({"session": s["session_id"], "subject": s["subject"], "trials": len(s["trials"][s["trials"].recordings == fam["recording"]]),
"visited_left": int((t.visited_arm == "left").sum()), "visited_right": int((t.visited_arm == "right").sum()),
"expected_left": int((t.expected_arm == "left").sum()), "expected_right": int((t.expected_arm == "right").sum())})
arms = pd.DataFrame(arms)
print(f"Largest linearized position in a familiar trial: median {np.median(lens):.1f} cm (range {np.min(lens):.1f}-{np.max(lens):.1f}); paper 205 cm")
print(f"Tracking rate: median {1 / np.median(dts):.2f} Hz over {len(dts)} familiar recordings (range {1 / np.max(dts):.2f}-{1 / np.min(dts):.2f}); paper 30 Hz")
arms["min_visited"] = arms[["visited_left", "visited_right"]].min(axis=1)
arms["min_expected"] = arms[["expected_left", "expected_right"]].min(axis=1)
print(f"\nSessions with at least 10 trials on each arm: visited {int((arms.min_visited >= 10).sum())} of {len(arms)}; "
f"expected {int((arms.min_expected >= 10).sum())} of {len(arms)}")
print(f"Sessions with at least 20 trials (rows of the trials table) on the familiar maze: {int((arms.trials >= 20).sum())} of {len(arms)} (fewest {arms.trials.min()})")
print("\nSessions with fewer than 10 on an arm under either reading:")
print(arms[(arms.min_visited < 10) | (arms.min_expected < 10)].to_string(index=False))
Largest linearized position in a familiar trial: median 204.1 cm (range 0.8-205.0); paper 205 cm
Tracking rate: median 30.00 Hz over 65 familiar recordings (range 30.00-30.00); paper 30 Hz
Sessions with at least 10 trials on each arm: visited 38 of 65; expected 22 of 65
Sessions with at least 20 trials (rows of the trials table) on the familiar maze: 59 of 65 (fewest 12)
Sessions with fewer than 10 on an arm under either reading:
session subject trials visited_left visited_right expected_left expected_right min_visited min_expected
e13_16f1_210302 e13_16f1 22 7 15 14 7 7 7
e13_16f1_210312 e13_16f1 21 10 11 11 9 10 9
e13_16f1_210315 e13_16f1 20 9 11 11 8 9 8
e13_16f1_210319 e13_16f1 26 10 16 16 9 10 9
e13_16f1_210322 e13_16f1 25 10 15 15 9 10 9
e13_16f1_210328 e13_16f1 21 10 11 11 9 10 9
e13_16f1_210331 e13_16f1 22 10 12 12 9 10 9
e13_26m1_211116 e13_26m1 21 10 11 11 9 10 9
e14_2m2_201013 e14_2m2 15 8 7 7 7 7 7
e14_2m2_201017 e14_2m2 21 12 9 9 11 9 9
e14_2m2_201019 e14_2m2 21 11 10 9 11 10 9
e14_2m2_201022 e14_2m2 19 10 9 9 9 9 9
e14_2m3_201017 e14_2m3 27 8 19 18 8 8 8
e14_2m3_201019 e14_2m3 23 10 13 13 9 10 9
e14_2m3_201118 e14_2m3 24 10 14 14 9 10 9
e14_2m3_201125 e14_2m3 22 10 12 12 9 10 9
e14_2m3_201129 e14_2m3 25 10 15 15 9 10 9
e15_10m1_200716 e15_10m1 20 8 12 11 8 8 8
e15_10m1_200719 e15_10m1 20 8 12 11 8 8 8
e15_10m1_200723 e15_10m1 16 8 8 8 7 8 7
e15_10m1_200803 e15_10m1 21 10 11 11 9 10 9
e15_10m1_200809 e15_10m1 21 9 12 11 9 9 9
e15_10m1_200812 e15_10m1 22 9 13 12 9 9 9
e15_9f1_200715 e15_9f1 21 7 14 13 7 7 7
e15_9f1_200717 e15_9f1 20 9 11 11 8 9 8
e15_9f1_200809 e15_9f1 20 8 12 12 7 8 7
e16_1f1_200823 e16_1f1 20 13 7 7 12 7 7
e16_1f1_200917 e16_1f1 20 7 13 12 7 7 7
e16_1f1_200918 e16_1f1 26 9 17 16 9 9 9
e16_1f1_200919 e16_1f1 29 8 21 20 8 8 8
e16_1f1_200920 e16_1f1 22 9 13 12 9 9 9
e16_1f1_200923 e16_1f1 22 9 13 13 8 9 8
e16_1f1_201001 e16_1f1 20 8 12 12 7 8 7
e16_1f1_201002 e16_1f1 20 7 13 13 6 7 6
e16_1f1_201004 e16_1f1 24 9 15 15 8 9 8
e16_2m1_201116 e16_2m1 12 5 7 6 5 5 5
e16_2m1_201119 e16_2m1 20 10 10 10 9 10 9
e16_2m1_201121 e16_2m1 22 13 9 8 13 9 8
e16_2m1_201129 e16_2m1 18 8 10 10 7 8 7
e16_2m1_201206 e16_2m1 20 10 10 9 10 10 9
e16_3m1_210111 e16_3m1 18 10 8 7 10 8 7
e16_3m1_210203 e16_3m1 22 10 12 12 9 10 9
e16_3m2_211213 e16_3m2 27 17 10 9 17 10 9
Result: not reproduced. Trials span 204.1 cm (median of the largest linearized position; paper 205 cm) and position is sampled at 30.00 Hz, but only 38 of 65 familiar sessions have ten or more trials on each visited arm (22 of 65 by expected arm), and 6 sessions have fewer than 20 trials (the fewest has 12).
novel-explorations¶
The novel-environment cohort of three mice made 16 novel environment explorations in six distinct environments (two linear tracks, three open fields and one figure-eight maze).
nov = recs[~recs.familiar]
print("Novel recordings with trials and position:")
print(nov[["session", "recording", "kind", "trials", "lin_max_in_trials", "x_range", "y_range", "dt_ms"]].to_string(index=False))
print(f"\n{len(nov)} novel explorations in {nov.session.str[:8].nunique()} mice; paper 16 in 3 mice (tolerance 14.4-17.6)")
print("Kinds (track length):", nov.groupby(["kind", "lin_max_in_trials"]).size().to_dict())
print("\nEpochs of the novel-cohort sessions (minutes, position samples inside):")
unpos = []
for s in sessions:
if s["subject"] in novel_mice and "epochs" in s:
e = s["epochs"].copy()
e["minutes"] = ((e.stop - e.start) / 60).round(1)
e["position_samples"] = [int(((s["pos"].t >= a) & (s["pos"].t < b)).sum()) for a, b in zip(e.start, e.stop)]
print(s["session_id"], e[["minutes", "position_samples"]].values.tolist())
e["last"] = np.arange(len(e)) == len(e) - 1
unpos.append(e[(e.position_samples == 0) & e.minutes.between(10, 50) & ~e["last"]].assign(session=s["session_id"]))
unpos = pd.concat(unpos)
print(f"\nEpochs of 10 to 50 min without position that are not a session's last epoch: {len(unpos)}, in sessions {sorted(unpos.session.unique())}")
print("Behavior series in the novel-cohort files:", sorted({str(s["processing"].get("behavior")) for s in sessions if s["subject"] in novel_mice}))
print("Acquisition contents in the novel-cohort files:", sorted({str(s["groups"].get("acquisition")) for s in sessions if s["subject"] in novel_mice}))
print(f"Not counted (no position, so outside the criterion), novel recordings with position plus these epochs: {len(nov)} + {len(unpos)} = {len(nov) + len(unpos)}")
Novel recordings with trials and position:
session recording kind trials lin_max_in_trials x_range y_range dt_ms
e13_26m1_211019 2 T-maze 33 163.7 0.01-0.72 0.06-0.98 33.33
e13_26m1_211116 2 linear track 9 100.0 0.26-0.89 0.04-0.77 40.00
e13_26m1_211119 2 linear track 58 110.0 0.01-0.20 0.00-1.00 33.33
e15_13f1_220118 2 linear track 68 110.0 1.85-22.94 0.30-116.03 33.33
e15_13f1_220119 3 T-maze 32 163.8 0.29-65.28 0.52-89.40 33.33
e15_13f1_220120 2 linear track 49 100.0 0.24-0.90 0.03-0.75 40.00
e16_3m2_211211 3 linear track 45 110.0 1.41-22.94 0.28-115.96 33.33
e16_3m2_211212 3 linear track 34 100.0 28.93-110.22 5.49-93.53 40.00
e16_3m2_211213 2 T-maze 29 163.6 0.00-0.71 0.01-0.99 33.33
9 novel explorations in 3 mice; paper 16 in 3 mice (tolerance 14.4-17.6)
Kinds (track length): {('T-maze', 163.6): 1, ('T-maze', 163.7): 1, ('T-maze', 163.8): 1, ('linear track', 100.0): 3, ('linear track', 110.0): 3}
Epochs of the novel-cohort sessions (minutes, position samples inside):
e13_26m1_211019 [[126.5, 0.0], [29.0, 51501.0], [96.0, 0.0], [27.7, 48056.0], [90.8, 0.0], [35.0, 0.0]]
e13_26m1_211119 [[102.5, 0.0], [23.9, 41704.0], [121.1, 0.0], [26.8, 46166.0], [153.3, 0.0]]
e13_26m1_211124 [[143.5, 0.0], [23.4, 41348.0], [75.5, 0.0], [30.9, 0.0], [97.3, 0.0]]
e15_13f1_220117 [[109.5, 0.0], [31.2, 55242.0], [93.7, 0.0], [39.9, 0.0], [150.8, 0.0]]
e15_13f1_220118 [[93.6, 0.0], [35.0, 62112.0], [89.8, 0.0], [32.4, 57452.0], [67.8, 0.0], [41.8, 0.0], [104.6, 0.0], [29.2, 0.0]]
e15_13f1_220119 [[113.6, 0.0], [34.4, 61023.0], [62.9, 0.0], [40.6, 0.0], [101.7, 0.0], [25.2, 44732.0], [91.1, 0.0], [18.0, 0.0]]
e15_13f1_220120 [[159.9, 0.0], [39.6, 70582.0], [100.4, 0.0], [30.3, 44805.0], [105.1, 0.0], [18.8, 0.0]]
e16_3m2_211210 [[179.5, 0.0], [29.6, 52106.0], [67.8, 0.0], [43.7, 0.0], [163.8, 0.0]]
e16_3m2_211211 [[87.8, 0.0], [21.1, 36888.0], [50.4, 0.0], [40.7, 48818.0], [52.0, 0.0], [27.5, 0.0], [61.9, 0.0], [15.0, 0.0]]
e16_3m2_211212 [[101.5, 0.0], [29.5, 52223.0], [61.4, 0.0], [30.3, 0.0], [50.3, 0.0], [22.6, 33205.0], [76.7, 0.0]]
e16_3m2_211213 [[125.9, 0.0], [30.4, 54018.0], [64.8, 0.0], [34.3, 61076.0], [145.8, 0.0]]
Epochs of 10 to 50 min without position that are not a session's last epoch: 7, in sessions ['e13_26m1_211124', 'e15_13f1_220117', 'e15_13f1_220118', 'e15_13f1_220119', 'e16_3m2_211210', 'e16_3m2_211211', 'e16_3m2_211212']
Behavior series in the novel-cohort files: ["['LinearizedPosition', 'RewardEventsEightMazeTrack', 'SleepStates', 'SubjectPosition']"]
Acquisition contents in the novel-cohort files: ['[]']
Not counted (no position, so outside the criterion), novel recordings with position plus these epochs: 9 + 7 = 16
Result: partially reproduced. The three mice are there, but only 9 novel explorations have trials and position (3 T-maze runs of about 164 cm and 6 linear-track runs of 100 or 110 cm), against 16. The novel-cohort sessions also hold 7 epochs of 10 to 50 min without position that are not a session's last epoch; the files hold no other position or video series for them, so they are not counted. They would bring the count to 16, but they were picked after the data were seen, and nothing in the files says they are explorations. The kinds with position (a T-maze and two track lengths) can be read as the paper's novel figure-eight and two linear tracks, taking the 164 cm T-maze to be the novel figure-eight; no open field has position.
Claims about all pyramidal neurons¶
Putative pyramidal cells are units whose session mean rate is at most 5 Hz (the files hold no cell type). Rate maps follow the Methods (1 cm bins, speed above 1.5 cm/s, 9 cm FWHM smoothing, left and right trials apart).
fig5e-pv-decorrelation¶
Population firing-rate vectors at the same linearized position decorrelate between left and right trials once the animal leaves the common stem of the figure-eight maze (median correlations stem 0.93, turn 1 0.412, arm 0.372, turn 2 0.394).
def pv_correlations(s, max_rate=h.PYR_MAX_RATE_HZ, min_speed=h.MIN_SPEED_CM_S):
fam = familiar(s)
keep = h.mean_rates(s) <= max_rate
sp = [x for x, k in zip(s["maze_spikes"], keep) if k]
m = h.rate_maps(sp, fam, min_speed=min_speed)["maps"]
cc = np.full(h.FIG8_BINS, np.nan)
for b in range(h.FIG8_BINS):
l, r = m[:, 0, b], m[:, 1, b]
ok = np.isfinite(l) & np.isfinite(r)
if ok.sum() > 2 and l[ok].std() > 0 and r[ok].std() > 0:
cc[b] = np.corrcoef(l[ok], r[ok])[0, 1]
return cc, int(keep.sum())
pv_sessions = [s for s in units_files if s["has_position"] and s["subject"] not in ("e15_13f1", "e16_3m2")]
pv, ncells = {}, {}
for s in pv_sessions:
pv[s["session_id"]], ncells[s["session_id"]] = pv_correlations(s)
pvm = np.array(list(pv.values()))
def by_segment(mat):
return {k: mat[:, a - 1:b].ravel() for k, (a, b) in h.FIG8_SEGMENTS.items()}
seg = by_segment(pvm)
print(f"{len(pv)} sessions of {len({s['subject'] for s in pv_sessions})} mice; putative pyramidal cells per session: median {np.median(list(ncells.values())):.0f}, range {min(ncells.values())}-{max(ncells.values())}")
paper = {"stem": 0.93, "turn1": 0.412, "arm": 0.372, "turn2": 0.3944}
for k, v in seg.items():
print(f"{k:6s} bins {len(v):5d}, without a value {int(np.isnan(v).sum()):3d}, median {np.nanmedian(v):.3f} "
f"(paper {paper[k]}, tolerance {paper[k] * 0.9:.3f}-{paper[k] * 1.1:.3f}), range {np.nanmin(v):.2f} to {np.nanmax(v):.2f}")
kw = stats.kruskal(*[v[~np.isnan(v)] for v in seg.values()])
print(f"Kruskal-Wallis across segments: H = {kw.statistic:.1f}, p = {kw.pvalue:.2g} (paper H = 7278.3)")
57 sessions of 9 mice; putative pyramidal cells per session: median 72, range 28-242 stem bins 4218, without a value 37, median 0.937 (paper 0.93, tolerance 0.837-1.023), range 0.48 to 1.00 turn1 bins 2109, without a value 3, median 0.547 (paper 0.412, tolerance 0.371-0.453), range 0.03 to 0.97 arm bins 4218, without a value 8, median 0.492 (paper 0.372, tolerance 0.335-0.409), range -0.01 to 0.95 turn2 bins 1140, without a value 3, median 0.465 (paper 0.3944, tolerance 0.355-0.434), range 0.01 to 0.88 Kruskal-Wallis across segments: H = 7632.7, p = 0 (paper H = 7278.3)
sub_of = {s["session_id"]: s["subject"] for s in pv_sessions}
per_mouse = pd.DataFrame({k: pd.Series({m: np.nanmedian(pvm[[sub_of[x] == m for x in pv], a - 1:b])
for m in sorted(set(sub_of.values()))}) for k, (a, b) in h.FIG8_SEGMENTS.items()})
print("Median PV correlation by mouse:")
print(per_mouse.round(3).to_string())
thr_paper = float(np.quantile(rates, 7411 / 9471))
print(f"\nRate threshold that classes the paper's pyramidal fraction ({7411 / 9471:.3f}) of all units as pyramidal: {thr_paper:.2f} Hz")
print("Variants (medians stem, turn1, arm, turn2):")
for label, kw_ in [("all units", dict(max_rate=np.inf)), ("rate at most 10 Hz", dict(max_rate=10)),
("rate at most 2 Hz", dict(max_rate=2)), (f"rate at most {thr_paper:.2f} Hz", dict(max_rate=thr_paper)), ("no speed threshold", dict(min_speed=-1))]:
mat = np.array([pv_correlations(s, **kw_)[0] for s in pv_sessions])
sg = by_segment(mat)
print(f" {label:20s}", {k: round(float(np.nanmedian(v)), 3) for k, v in sg.items()})
fig, ax = plt.subplots(figsize=(7, 3))
x = np.arange(1, h.FIG8_BINS + 1)
mu, sd = np.nanmean(pvm, 0), np.nanstd(pvm, 0)
ax.fill_between(x, mu - sd, mu + sd, color="0.85")
ax.plot(x, mu, "k")
for a, b in h.FIG8_SEGMENTS.values():
ax.axvline(b + 0.5, color="0.5", lw=0.5)
ax.set_xlabel("linearized position (cm)"); ax.set_ylabel("PV correlation, left vs right")
ax.set_ylim(-0.2, 1.05)
print(f"Plotted: mean over sessions ({np.nanmin(mu):.2f} to {np.nanmax(mu):.2f}) with one s.d.; "
f"{int(((mu - sd) < -0.2).sum())} bins of the lower band fall below the axis")
plt.show()
Median PV correlation by mouse:
stem turn1 arm turn2
e13_16f1 0.944 0.606 0.584 0.457
e13_26m1 0.937 0.393 0.376 0.296
e14_2m2 0.849 0.511 0.561 0.440
e14_2m3 0.949 0.440 0.459 0.397
e15_10m1 0.934 0.391 0.355 0.463
e15_9f1 0.930 0.467 0.396 0.421
e16_1f1 0.928 0.695 0.654 0.678
e16_2m1 0.952 0.738 0.477 0.544
e16_3m1 0.948 0.486 0.518 0.590
Rate threshold that classes the paper's pyramidal fraction (0.782) of all units as pyramidal: 2.59 Hz
Variants (medians stem, turn1, arm, turn2):
all units {'stem': 0.988, 'turn1': 0.948, 'arm': 0.94, 'turn2': 0.921}
rate at most 10 Hz {'stem': 0.966, 'turn1': 0.787, 'arm': 0.771, 'turn2': 0.746}
rate at most 2 Hz {'stem': 0.925, 'turn1': 0.387, 'arm': 0.288, 'turn2': 0.306}
rate at most 2.59 Hz {'stem': 0.928, 'turn1': 0.436, 'arm': 0.343, 'turn2': 0.357}
no speed threshold {'stem': 0.924, 'turn1': 0.589, 'arm': 0.522, 'turn2': 0.492}
Plotted: mean over sessions (0.42 to 0.93) with one s.d.; 0 bins of the lower band fall below the axis
Result: partially reproduced. Over 57 sessions of nine mice the population vectors decorrelate after the stem (Kruskal-Wallis H = 7632.7, p = 0), and the stem median (0.937) matches the paper's 0.93, but turn 1 (0.547), arm (0.492) and turn 2 (0.465) stay more correlated than reported (0.412, 0.372, 0.394). The values depend on which units count as pyramidal: with a 2 Hz cut the three medians are 0.387, 0.288 and 0.306, and with all units 0.948, 0.94 and 0.921. The stem is the most correlated segment in every mouse (stem 0.849 to 0.952, other segments 0.296 to 0.738). With the threshold (2.59 Hz) that classes the paper's fraction of units as pyramidal, the four medians are 0.928, 0.436, 0.343 and 0.357, all within tolerance; the criterion fixed the 5 Hz rule, so this variant does not change the outcome.
Familiar and novel environments (Figure 6c, d)¶
The sessions are those of the three mice of the novel-environment cohort (12 sessions, as in the
paper's get_corrBeh_famnov.m); 9 of them hold a novel maze recording. The "theta-related" rate is
approximated by the rate while running at 5 cm/s or faster inside maze trials. Running stretches end
at the end of a trial, so the time between trials is not counted; the next cell checks this on
synthetic data and shows how often the animal was running across a trial boundary.
FAMNOV = ("e13_26m1", "e15_13f1", "e16_3m2")
def famnov_measures(max_rate=h.PYR_MAX_RATE_HZ, run_speed=h.RUN_SPEED_CM_S, include_stem=False):
cells, pairs, infos = [], [], []
for s in sessions:
if s["subject"] not in FAMNOV:
continue
keep = h.mean_rates(s) <= max_rate
sp = [x for x, k in zip(s["maze_spikes"], keep) if k]
rates = {}
for r in h.recordings(s):
m = h.rate_maps(sp, r)
bins = np.arange(1, r["n_bins"] + 1) if include_stem else h.correlation_bins(r)
c = h.pairwise_map_correlations(h.concatenated(m["maps"], bins))
pairs.append(pd.DataFrame({"session": s["session_id"], "subject": s["subject"], "familiar": r["familiar"],
"kind": r["kind"], "r": c}))
flat = m["maps"].reshape(len(sp), -1)
infos.append(pd.DataFrame({"session": s["session_id"], "subject": s["subject"], "familiar": r["familiar"],
"kind": r["kind"], "bits": h.skaggs_information(flat, m["occupancy"].ravel()),
"info_unweighted": h.uniform_information(flat)}))
rates["fam" if r["familiar"] else "nov"] = h.running_rate(sp, r, min_speed=run_speed)
cells.append(pd.DataFrame({"session": s["session_id"], "subject": s["subject"], "fam": rates["fam"],
"nov": rates.get("nov", np.full(len(sp), np.nan))}))
return pd.concat(cells, ignore_index=True), pd.concat(pairs, ignore_index=True), pd.concat(infos, ignore_index=True)
cells6, pairs6, info6 = famnov_measures()
print(f"{cells6.session.nunique()} sessions, {len(cells6)} putative pyramidal cells; "
f"{cells6.nov.notna().sum()} in the {cells6[cells6.nov.notna()].session.nunique()} sessions with a novel maze")
12 sessions, 2717 putative pyramidal cells; 2038 in the 9 sessions with a novel maze
# The running rate on synthetic data: two 10 s trials 10 s apart, running throughout, a unit firing at 1 Hz
# also between the trials. The time between trials must not count, so the rate is 1 Hz.
t_syn = np.r_[np.arange(0, 10, 1 / 30), np.arange(20, 30, 1 / 30)]
pos_syn = pd.DataFrame({"t": t_syn, "speed": 10.0, "trial": np.repeat([0, 1], 300)})
r_syn = h.running_rate([np.arange(0.5, 30, 1.0)], {"pos": pos_syn})[0]
print(f"Synthetic running rate: {r_syn:.3f} Hz (true 1 Hz)")
assert abs(r_syn - 1) < 1e-9
# Gaps between consecutive trials, and running at the edges of trials, in the sessions of the three mice
gap_rows = []
for s in sessions:
if s["subject"] not in FAMNOV:
continue
for r in h.recordings(s):
tr = r["trials"].sort_values("start_time")
p = r["pos"]
edge = p.groupby("trial").speed.agg(["first", "last"]).reindex(tr.index)
gap_rows.append({"familiar": r["familiar"], "gap_s": (tr.start_time.values[1:] - tr.stop_time.values[:-1]),
"run_across": (edge["last"].values[:-1] >= h.RUN_SPEED_CM_S) & (edge["first"].values[1:] >= h.RUN_SPEED_CM_S)})
for fam in (True, False):
g = np.concatenate([x["gap_s"] for x in gap_rows if x["familiar"] == fam])
ra = np.concatenate([x["run_across"] for x in gap_rows if x["familiar"] == fam])
print(f"{'Familiar' if fam else 'Novel'}: gaps between consecutive trials median {np.median(g):.2f} s (range {g.min():.2f} to {g.max():.1f}); "
f"pairs of trials running at the end of one and the start of the next: {ra.sum()} of {len(ra)}")
Synthetic running rate: 1.000 Hz (true 1 Hz) Familiar: gaps between consecutive trials median 1.75 s (range 0.43 to 148.1); pairs of trials running at the end of one and the start of the next: 194 of 350 Novel: gaps between consecutive trials median 0.04 s (range 0.03 to 141.1); pairs of trials running at the end of one and the start of the next: 177 of 348
fig6c-theta-rate-preserved¶
Individual pyramidal neurons preserve their theta-related firing rates between the familiar and novel environments (Spearman rho = 0.7378, n = 2,544 neurons).
both = cells6.dropna(subset=["fam", "nov"])
print(f"Cells with both rates: {len(both)}; dropped: {cells6.nov.isna().sum()} in sessions without a novel maze, "
f"{(cells6.fam.isna() & cells6.nov.notna()).sum()} without a familiar rate")
r = stats.spearmanr(both.fam, both.nov)
print(f"Spearman rho = {r.statistic:.3f}, p = {r.pvalue:.2g}, n = {len(both)} (paper 0.7378, n = 2,544; criterion rho >= 0.664)")
print("Cells silent in one environment (rate 0): familiar", int((both.fam == 0).sum()), ", novel", int((both.nov == 0).sum()))
print("\nBy mouse:")
for m, g in both.groupby("subject"):
rr_ = stats.spearmanr(g.fam, g.nov)
print(f" {m}: rho = {rr_.statistic:.3f}, p = {rr_.pvalue:.2g}, n = {len(g)}")
print("By session:", {k: round(stats.spearmanr(g.fam, g.nov).statistic, 3) for k, g in both.groupby("session")})
print("\nVariants:")
for label, kw_ in [("all units", dict(max_rate=np.inf)), ("rate at most 10 Hz", dict(max_rate=10)), ("running at 1.5 cm/s or faster", dict(run_speed=1.5))]:
cv = famnov_measures(**kw_)[0].dropna(subset=["fam", "nov"])
rv = stats.spearmanr(cv.fam, cv.nov)
print(f" {label:30s} rho = {rv.statistic:.3f}, n = {len(cv)}")
fig, ax = plt.subplots(figsize=(3.5, 3.5))
lo = 0.01
ax.loglog(np.clip(both.fam, lo, None), np.clip(both.nov, lo, None), ".", ms=2, color="0.3")
ax.set_xlabel("familiar rate (Hz)"); ax.set_ylabel("novel rate (Hz)")
print(f"Plotted rates range {both[['fam', 'nov']].min().min():.3f} to {both[['fam', 'nov']].max().max():.2f} Hz; "
f"{int(((both.fam < lo) | (both.nov < lo)).sum())} cells below {lo} Hz drawn at {lo}")
plt.show()
Cells with both rates: 2038; dropped: 679 in sessions without a novel maze, 0 without a familiar rate
Spearman rho = 0.726, p = 0, n = 2038 (paper 0.7378, n = 2,544; criterion rho >= 0.664)
Cells silent in one environment (rate 0): familiar 28 , novel 53
By mouse:
e13_26m1: rho = 0.734, p = 3.6e-102, n = 598
e15_13f1: rho = 0.711, p = 9e-140, n = 903
e16_3m2: rho = 0.745, p = 4.4e-96, n = 537
By session: {'e13_26m1_211019': np.float64(0.823), 'e13_26m1_211116': np.float64(0.636), 'e13_26m1_211119': np.float64(0.774), 'e15_13f1_220118': np.float64(0.726), 'e15_13f1_220119': np.float64(0.826), 'e15_13f1_220120': np.float64(0.586), 'e16_3m2_211211': np.float64(0.678), 'e16_3m2_211212': np.float64(0.746), 'e16_3m2_211213': np.float64(0.8)}
Variants:
all units rho = 0.802, n = 2284
rate at most 10 Hz rho = 0.756, n = 2130
running at 1.5 cm/s or faster rho = 0.754, n = 2038 Plotted rates range 0.000 to 17.84 Hz; 175 cells below 0.01 Hz drawn at 0.01
Result: reproduced. The running rates of 2,038 putative pyramidal cells on the familiar and novel mazes correlate with Spearman rho = 0.726 (paper 0.7378), and rho is 0.711 to 0.745 in each mouse; the variants give 0.754 to 0.802. The rate here is a running rate, not the paper's rate in theta cycles.
fig6c-novel-ratemap-corr-higher¶
Pairwise spatial ratemap correlations between pyramidal neurons are higher in novel environments (median 0.048) than in the familiar environment (median -0.045).
fc = pairs6[pairs6.familiar].r.dropna(); nc = pairs6[~pairs6.familiar].r.dropna()
dropped = pairs6.r.isna().sum()
t = stats.ranksums(nc, fc)
print(f"Familiar: median {fc.median():.4f}, n = {len(fc)} pairs (paper -0.045, n = 31,067; tolerance -0.075 to -0.015)")
print(f"Novel: median {nc.median():.4f}, n = {len(nc)} pairs (paper 0.048, n = 41,991; tolerance 0.018 to 0.078)")
print(f"Rank-sum (novel against familiar): z = {t.statistic:.2f}, p = {t.pvalue:.2g}; pairs without a value (a silent cell): {dropped}")
print("\nBy mouse and maze (median, pairs):")
print(pairs6.groupby(["subject", "familiar", "kind"]).r.agg(["median", "count"]).round(4).to_string())
print("\nBy session, familiar against novel median in sessions with both:")
ps = pairs6.groupby(["session", "familiar"]).r.median().unstack()
print(ps.dropna().round(4).to_string())
print("Sessions where the novel median is higher:", int((ps.dropna()[False] > ps.dropna()[True]).sum()), "of", len(ps.dropna()))
n_hi, n_ses = int((ps.dropna()[False] > ps.dropna()[True]).sum()), len(ps.dropna())
print(f"Variant, sign test over sessions (novel median higher in {n_hi} of {n_ses}): p = {stats.binomtest(n_hi, n_ses).pvalue:.2g}")
print("Variant, rank-sum p within each session (novel against familiar):",
{k: float(f"{stats.ranksums(g[~g.familiar].r.dropna(), g[g.familiar].r.dropna()).pvalue:.2g}") for k, g in pairs6[pairs6.session.isin(ps.dropna().index)].groupby("session")})
print("Pairs per session (novel, familiar):", pairs6.dropna(subset=["r"]).groupby(["session", "familiar"]).size().unstack().fillna(0).astype(int).values.tolist())
fam_same = pairs6[pairs6.familiar & pairs6.session.isin(ps.dropna().index)].r.dropna()
print(f"\nVariant, familiar pairs only from sessions with a novel maze: median {fam_same.median():.4f}, rank-sum p = {stats.ranksums(nc, fam_same).pvalue:.2g}")
for label, kw_ in [("all units", dict(max_rate=np.inf)), ("rate at most 10 Hz", dict(max_rate=10)), ("stem included", dict(include_stem=True))]:
pv_ = famnov_measures(**kw_)[1]
f_, n_ = pv_[pv_.familiar].r.dropna(), pv_[~pv_.familiar].r.dropna()
print(f"Variant, {label:20s} familiar {f_.median():.4f}, novel {n_.median():.4f}, rank-sum p = {stats.ranksums(n_, f_).pvalue:.2g}")
Familiar: median -0.0530, n = 327656 pairs (paper -0.045, n = 31,067; tolerance -0.075 to -0.015)
Novel: median -0.0364, n = 242655 pairs (paper 0.048, n = 41,991; tolerance 0.018 to 0.078)
Rank-sum (novel against familiar): z = -0.42, p = 0.67; pairs without a value (a silent cell): 6313
By mouse and maze (median, pairs):
median count
subject familiar kind
e13_26m1 False T-maze -0.0202 29161
linear track -0.0230 30486
True figure-eight -0.0486 73106
e15_13f1 False T-maze -0.0548 46360
linear track -0.0484 89201
True figure-eight -0.0569 192043
e16_3m2 False T-maze -0.0390 16471
linear track 0.0073 30976
True figure-eight -0.0454 62507
By session, familiar against novel median in sessions with both:
familiar False True
session
e13_26m1_211019 -0.0202 -0.0521
e13_26m1_211116 -0.0189 -0.0448
e13_26m1_211119 -0.0279 -0.0426
e15_13f1_220118 -0.0580 -0.0584
e15_13f1_220119 -0.0548 -0.0564
e15_13f1_220120 -0.0288 -0.0521
e16_3m2_211211 0.0204 -0.0455
e16_3m2_211212 -0.0101 -0.0404
e16_3m2_211213 -0.0390 -0.0473
Sessions where the novel median is higher: 9 of 9
Variant, sign test over sessions (novel median higher in 9 of 9): p = 0.0039
Variant, rank-sum p within each session (novel against familiar): {'e13_26m1_211019': 0.031, 'e13_26m1_211116': 0.0022, 'e13_26m1_211119': 0.0014, 'e15_13f1_220118': 2.6e-14, 'e15_13f1_220119': 0.054, 'e15_13f1_220120': 0.24, 'e16_3m2_211211': 1.3e-22, 'e16_3m2_211212': 7.3e-05, 'e16_3m2_211213': 0.028}
Pairs per session (novel, familiar): [[29161, 28680], [16290, 17020], [14196, 14365], [0, 13041], [0, 58311], [53956, 53301], [46360, 45451], [35245, 34980], [0, 14878], [15400, 15400], [15576, 15576], [16471, 16653]]
Variant, familiar pairs only from sessions with a novel maze: median -0.0520, rank-sum p = 0.1
Variant, all units familiar -0.0415, novel -0.0259, rank-sum p = 0.38
Variant, rate at most 10 Hz familiar -0.0493, novel -0.0332, rank-sum p = 0.66
Variant, stem included familiar -0.0410, novel -0.0348, rank-sum p = 3.5e-50
Result: not reproduced. Pooled over pairs, the median ratemap correlation is -0.0364 in novel mazes and -0.0530 on the familiar maze, the rank-sum test finds no difference (p = 0.67), and the novel median is outside its tolerance (0.018 to 0.078). Within each of the 9 sessions with a novel maze the novel median is the higher one (sign test p = 0.0039; the rank-sum test within a session gives p < 0.05 in 7 of 9), so the direction holds session by session, but the pooled test the criterion names and the reported size do not. The pooled test is weighted by the sessions with most pairs. The dandiset lacks the open-field explorations, and the paper's pairs each include a light-responsive neuron; either may account for the difference.
fig6d-info-lower-novel¶
Spatial information is lower in novel environments than in the familiar one (median 0.39 against 0.88 bits/s).
fi = info6[info6.familiar].bits.dropna(); ni = info6[~info6.familiar].bits.dropna()
t = stats.ranksums(ni, fi)
print(f"Familiar: median {fi.median():.3f} bits/s, n = {len(fi)} (paper 0.88, n = 2,544; tolerance 0.792-0.968)")
print(f"Novel: median {ni.median():.3f} bits/s, n = {len(ni)} (paper 0.39, n = 3,443; tolerance 0.351-0.429)")
print(f"Rank-sum: z = {t.statistic:.2f}, p = {t.pvalue:.2g}; cells without a value (silent on the maze): {int(info6.bits.isna().sum())}")
print(f"Ranges: familiar {fi.min():.3f} to {fi.max():.2f}, novel {ni.min():.3f} to {ni.max():.2f}")
print("\nBy mouse and maze (median bits/s, cells):")
print(info6.groupby(["subject", "familiar", "kind"]).bits.agg(["median", "count"]).round(3).to_string())
fu, nu = info6[info6.familiar].info_unweighted.dropna(), info6[~info6.familiar].info_unweighted.dropna()
print(f"\nVariant, the code's unweighted information: familiar {fu.median():.3f}, novel {nu.median():.3f}, rank-sum p = {stats.ranksums(nu, fu).pvalue:.2g}")
for label, kw_ in [("all units", dict(max_rate=np.inf)), ("rate at most 10 Hz", dict(max_rate=10))]:
iv = famnov_measures(**kw_)[2]
print(f"Variant, {label:20s} familiar {iv[iv.familiar].bits.median():.3f}, novel {iv[~iv.familiar].bits.median():.3f}")
Familiar: median 0.631 bits/s, n = 2709 (paper 0.88, n = 2,544; tolerance 0.792-0.968)
Novel: median 0.270 bits/s, n = 2028 (paper 0.39, n = 3,443; tolerance 0.351-0.429)
Rank-sum: z = -16.27, p = 1.5e-59; cells without a value (silent on the maze): 18
Ranges: familiar 0.003 to 7.80, novel 0.003 to 6.19
By mouse and maze (median bits/s, cells):
median count
subject familiar kind
e13_26m1 False T-maze 0.282 242
linear track 0.263 350
True figure-eight 0.645 760
e15_13f1 False T-maze 0.608 305
linear track 0.337 595
True figure-eight 0.617 1240
e16_3m2 False T-maze 0.367 183
linear track 0.083 353
True figure-eight 0.638 709
Variant, the code's unweighted information: familiar 0.646, novel 0.285, rank-sum p = 6.8e-60
Variant, all units familiar 0.611, novel 0.279
Variant, rate at most 10 Hz familiar 0.607, novel 0.261
Result: partially reproduced. Spatial information is lower on novel mazes (median 0.270 bits/s) than on the familiar maze (0.631 bits/s), rank-sum p = 1.5e-59, as the paper reports, but both medians are below the paper's 0.39 and 0.88. The code's unweighted information gives 0.285 and 0.646. Novel information is lowest on the linear tracks (0.083 to 0.337) and, for one mouse's T-maze, close to its familiar value (0.608 against 0.617).
fig6d-rate-lower-novel¶
Theta-related firing rates are slightly lower in novel environments than in the familiar one (median 0.69 against 0.77 Hz).
fr_, nr_ = cells6.fam.dropna(), cells6.nov.dropna()
t = stats.ranksums(nr_, fr_)
print(f"Familiar: median {fr_.median():.3f} Hz, n = {len(fr_)} (paper 0.77, n = 2,544; tolerance 0.693-0.847)")
print(f"Novel: median {nr_.median():.3f} Hz, n = {len(nr_)} (paper 0.69; tolerance 0.621-0.759)")
print(f"Rank-sum: z = {t.statistic:.2f}, p = {t.pvalue:.2g} (paper p = 3.3e-3)")
w = stats.wilcoxon(both.nov, both.fam)
print(f"Variant, paired signed-rank over {len(both)} cells with both: median ratio novel/familiar "
f"{np.median(both.nov[both.fam > 0] / both.fam[both.fam > 0]):.3f}, p = {w.pvalue:.2g}")
fam_same = cells6[cells6.nov.notna()].fam
print(f"Variant, familiar only in sessions with a novel maze: median {fam_same.median():.3f} Hz, rank-sum p = {stats.ranksums(nr_, fam_same).pvalue:.2g}")
print("\nBy mouse (median Hz):")
print(cells6.groupby("subject")[["fam", "nov"]].median().round(3).to_string())
for label, kw_ in [("all units", dict(max_rate=np.inf)), ("rate at most 10 Hz", dict(max_rate=10)), ("running at 1.5 cm/s or faster", dict(run_speed=1.5))]:
cv = famnov_measures(**kw_)[0]
print(f"Variant, {label:30s} familiar {cv.fam.median():.3f}, novel {cv.nov.median():.3f}, rank-sum p = {stats.ranksums(cv.nov.dropna(), cv.fam.dropna()).pvalue:.2g}")
Familiar: median 0.794 Hz, n = 2717 (paper 0.77, n = 2,544; tolerance 0.693-0.847)
Novel: median 0.714 Hz, n = 2038 (paper 0.69; tolerance 0.621-0.759)
Rank-sum: z = -2.04, p = 0.041 (paper p = 3.3e-3)
Variant, paired signed-rank over 2038 cells with both: median ratio novel/familiar 0.942, p = 0.19
Variant, familiar only in sessions with a novel maze: median 0.782 Hz, rank-sum p = 0.09
By mouse (median Hz):
fam nov
subject
e13_26m1 0.883 0.722
e15_13f1 0.729 0.734
e16_3m2 0.835 0.680
Variant, all units familiar 0.956, novel 0.923, rank-sum p = 0.11
Variant, rate at most 10 Hz familiar 0.848, novel 0.804, rank-sum p = 0.084
Variant, running at 1.5 cm/s or faster familiar 0.774, novel 0.730, rank-sum p = 0.075
Result: reproduced. Running rates are lower on novel mazes (median 0.714 Hz) than on the familiar maze (0.794 Hz), rank-sum p = 0.041, and both medians are within tolerance of the paper's 0.69 and 0.77 Hz. The difference is weak: the paired test over 2,038 cells gives a median novel-to-familiar ratio of 0.942 (p = 0.19), the variants give p = 0.075 to 0.11, and in mouse e15_13f1 the novel median is slightly higher (0.734 against 0.729 Hz).
Blocked claims¶
The data check shows what the files lack. Every claim that compares same-birthdate (SBD) with
different-birthdate (DBD) neurons, or light-responsive neurons across birthdates, needs to know
which units responded to light. The files hold no such label, no optogenetic pulse times from which
to derive one (no processed file and none of the 13 raw files has a stimulation, TTL or event series), and no
waveforms. The paper's labels come from CellExplorer's cell_metrics.optoTag and
putativeCellType fields and pulses.events.mat files on the lab's server, which are not public
at a pinned identifier. Claims fig5d, fig7b and fig7ce also need unit positions on the probe or cell
types, which the files lack. fig1c needs histology, which is not in the dandiset. fig7fg needs
putative interneurons and their monosynaptic inputs. The cell below applies the rate rule (units
above 5 Hz as interneurons) and two forms of the Methods' connection test to two sessions: both find
connections in a far larger fraction of pairs than the paper does over its dataset, so this
procedure is not the paper's, whose connections come from the lab's CellExplorer files
(mono_res), not public. The units of 39 of the paper's 96 sessions are also not in the dandiset.
| Claim | Reason |
|---|---|
| ed2-light-responsive-count, fig1e, fig1f, fig1g, fig1h | no light-responsiveness labels or pulse times |
| fig2b, fig2c, fig2d, fig2f, fig2g | no labels; SBD and DBD pairs cannot be formed |
| fig3c, fig3e | no labels; assemblies cannot be grouped by birthdate |
| fig4d-model-fit | depends on fig3e |
| fig5b, fig5c, fig5f, fig6e, fig6f | no labels |
| fig5d | no labels and no unit positions |
| fig7b, fig7ce | no labels and no cell types |
| fig1c-radial-depth | no histology |
| fig7fg-convergence-cofiring | connection detection differs from the paper's (methods underspecified) |
# What a test of Figure 7f would take: interneurons by the rate rule, and connections in two sessions.
FIG7F_OUT = ("e15_13f1", "e16_3m2") # mice without homecage results in the paper's code; the 96 sessions are the others
n_int = pd.DataFrame([{"session": s["session_id"], "units": len(s["n_spikes"]),
"above_5Hz": int((h.mean_rates(s) > h.PYR_MAX_RATE_HZ).sum())} for s in units_files])
print(f"Units above {h.PYR_MAX_RATE_HZ} Hz per file with units: median {n_int.above_5Hz.median():.0f}, range {n_int.above_5Hz.min()}-{n_int.above_5Hz.max()}, "
f"total {n_int.above_5Hz.sum()} (paper 2,060 interneurons)")
in96 = [s for s in sessions if s["subject"] not in FIG7F_OUT]
in96_units = [s for s in in96 if s["has_units"]]
print(f"Files with units: {len(units_files)} of 104; of the {len(in96)} files without e15_13f1 and e16_3m2 (the paper's 96 sessions): "
f"{len(in96_units)} with units, {len(in96) - len(in96_units)} without")
# The two connection tests on synthetic trains: a target firing 1.8 ms after 30% of the presynaptic spikes, and an unrelated one.
rng = np.random.default_rng(0)
pre = np.sort(rng.uniform(0, 1000, 5000))
post_conn = np.sort(np.r_[rng.uniform(0, 1000, 20000), pre[rng.random(5000) < 0.3] + 0.0018])
post_none = np.sort(rng.uniform(0, 1000, 20000))
for name, post in (("connected", post_conn), ("unrelated", post_none)):
c = h.ccg_counts(pre, post)
print(f"Synthetic {name}: fixed lags {h.is_monosynaptic(c)}, peak-centred {h.is_monosynaptic_peak(c)}")
assert h.is_monosynaptic(h.ccg_counts(pre, post_conn)) and h.is_monosynaptic_peak(h.ccg_counts(pre, post_conn))
assert not h.is_monosynaptic(h.ccg_counts(pre, post_none)) and not h.is_monosynaptic_peak(h.ccg_counts(pre, post_none))
def convergence(targets, pyr_i):
conv = []
for a in range(len(pyr_i)):
for b in range(a + 1, len(pyr_i)):
u = targets[pyr_i[a]] | targets[pyr_i[b]]
conv.append(len(targets[pyr_i[a]] & targets[pyr_i[b]]) / len(u) if u else np.nan)
return np.array(conv)
examples = [max(units_files, key=lambda s: len(s["n_spikes"])), max(in96_units, key=lambda s: len(s["n_spikes"]))]
for ex in examples:
trains = h.spike_trains(proc[proc.path == ex["path"]].asset.iloc[0])
mr = h.mean_rates(ex)
pyr_i, int_i = np.where(mr <= h.PYR_MAX_RATE_HZ)[0], np.where(mr > h.PYR_MAX_RATE_HZ)[0]
t0 = time.time()
ccgs = {(p, i): h.ccg_counts(trains[p], trains[i]) for p in pyr_i for i in int_i}
n_pairs = len(pyr_i) * len(int_i)
print(f"\nSession {ex['session_id']} ({'outside' if ex['subject'] in FIG7F_OUT else 'inside'} the 15 mice of Figure 7f): "
f"{len(pyr_i)} units at or below and {len(int_i)} above {h.PYR_MAX_RATE_HZ} Hz; {n_pairs} possible pairs")
for label, test in (("fixed lags 0.8-3.2 ms", h.is_monosynaptic), ("3 bins centred on the peak (variant)", h.is_monosynaptic_peak)):
targets = {p: {i for i in int_i if test(ccgs[(p, i)])} for p in pyr_i}
n_conn = sum(len(v) for v in targets.values())
conv = convergence(targets, pyr_i)
print(f" {label}: {n_conn} connections ({n_conn / n_pairs:.3f}; paper 31,932 of 184,230 = {31932 / 184230:.3f}); "
f"pyramidal pairs {len(conv)}, nonzero convergence {np.mean(conv > 0):.3f}, zero {np.mean(conv == 0):.3f}, "
f"no target in either {np.mean(np.isnan(conv)):.3f} (paper 0.6527, 0.3416); "
f"interneurons with an input {len(set().union(*targets.values()))} of {len(int_i)}")
print(f" in {time.time() - t0:.0f} s")
Units above 5.0 Hz per file with units: median 14, range 6-38, total 1001 (paper 2,060 interneurons) Files with units: 65 of 104; of the 96 files without e15_13f1 and e16_3m2 (the paper's 96 sessions): 57 with units, 39 without Synthetic connected: fixed lags True, peak-centred True Synthetic unrelated: fixed lags False, peak-centred False
Session e15_13f1_220117 (outside the 15 mice of Figure 7f): 343 units at or below and 33 above 5.0 Hz; 11319 possible pairs
fixed lags 0.8-3.2 ms: 5073 connections (0.448; paper 31,932 of 184,230 = 0.173); pyramidal pairs 58653, nonzero convergence 0.967, zero 0.033, no target in either 0.000 (paper 0.6527, 0.3416); interneurons with an input 32 of 33
3 bins centred on the peak (variant): 4585 connections (0.405; paper 31,932 of 184,230 = 0.173); pyramidal pairs 58653, nonzero convergence 0.957, zero 0.043, no target in either 0.000 (paper 0.6527, 0.3416); interneurons with an input 32 of 33 in 50 s
Session e13_26m1_211019 (inside the 15 mice of Figure 7f): 242 units at or below and 29 above 5.0 Hz; 7018 possible pairs
fixed lags 0.8-3.2 ms: 2859 connections (0.407; paper 31,932 of 184,230 = 0.173); pyramidal pairs 29161, nonzero convergence 0.967, zero 0.033, no target in either 0.000 (paper 0.6527, 0.3416); interneurons with an input 29 of 29
3 bins centred on the peak (variant): 2764 connections (0.394; paper 31,932 of 184,230 = 0.173); pyramidal pairs 29161, nonzero convergence 0.934, zero 0.066, no target in either 0.001 (paper 0.6527, 0.3416); interneurons with an input 29 of 29 in 32 s
Of the paper's 96 sessions (the files without e15_13f1 and e16_3m2), 57 have units and 39 do not.
Over all files the rate rule finds 1,001 units above 5 Hz, against the paper's 2,060 interneurons.
Both connection tests pass the synthetic check. In session e15_13f1_220117, whose mouse is not among
the 15 of Figure 7f, the test at fixed lags of 0.8 to 3.2 ms finds 5,073 connections in 11,319
possible pairs (0.448, against 0.173 in the paper's dataset), and 96.7% of pyramidal pairs share a
target (paper 65.27%). In session e13_26m1_211019, inside the Figure 7f cohort, it finds 0.407, and
96.7% of pairs share a target. The Methods test the three bins around the peak of the CCG, where
is_monosynaptic tests fixed lags; the peak-centred variant, with the peak sought at lags up to
4.0 ms (a range the Methods do not give), finds 0.405 and 0.394, with 95.7% and 93.4% of pairs
sharing a target. Neither form of the test gives the paper's rate of connections, so the claim stays
blocked. The difference may lie in this implementation as well as in what the Methods leave
unstated (the lag range, the baseline's hollow fraction and the CCG window).
print(f"Run time: {(time.time() - started) / 60:.1f} min")
Run time: 10.5 min
Summary¶
| Claim | Outcome |
|---|---|
| cohort-birthdates | reproduced |
| sessions-durations | partially reproduced |
| spwr-duration | reproduced |
| unit-count | not reproduced |
| maze-cohort | reproduced |
| maze-task | not reproduced |
| novel-explorations | partially reproduced |
| fig5e-pv-decorrelation | partially reproduced |
| fig6c-theta-rate-preserved | reproduced |
| fig6c-novel-ratemap-corr-higher | not reproduced |
| fig6d-info-lower-novel | partially reproduced |
| fig6d-rate-lower-novel | reproduced |
| ed2, fig1e, fig1f, fig1g, fig1h, fig2b, fig2c, fig2d, fig2f, fig2g, fig3c, fig3e, fig4d, fig5b, fig5c, fig5d, fig5f, fig6e, fig6f, fig7b, fig7ce, fig1c | blocked: data missing |
| fig7fg-convergence-cofiring | blocked: methods underspecified |
A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.
How the analysis differs from the paper's. Putative pyramidal cells are units with a session mean rate of at most 5 Hz, as the files hold no cell types or waveforms; the Figure 5e values move a long way with this choice, and a 2.59 Hz rule, which classes the paper's fraction as pyramidal, puts all four within tolerance. The "theta-related" rate is the rate while running at 5 cm/s or faster, not the rate in LFP theta cycles; its Figure 6d difference is weak (p = 0.041, paired p = 0.19). Speed comes from smoothed linearized position, not a Kalman filter. The Figure 6c pair correlations use all pairs of putative pyramidal cells, where the paper's pairs each include a light-responsive neuron, and the novel mazes are only those with position (no open fields). The familiar maze is taken as the first maze recording of each session.
What was found about the dandiset.
- No unit is labelled light-responsive, no cell type is given, no waveforms are stored and no optogenetic pulse times are stored, in the processed files or any of the 13 raw files. The paper's main findings, which compare same- and different-birthdate neurons, cannot be tested.
- Six of the 17 mice (39 files) have sleep states and ripples but no units and no position; the dandiset holds 8,201 units where the paper reports 9,471.
- No open field has position: only 9 of the 16 novel explorations have trials and position, and the novel-cohort sessions hold 7 further epochs without position that may be the others. Several novel recordings give x and y between 0 and 1 though stated in cm.
is_familiar_mazeis wrong in sessions with a single maze recording (True only for the first two trials). The sex field differs between files of e13_1f1 (Female 3, Male 2) and e13_1m1 (Male 9, Female 3). Two files of e13_1f1 have no scored sleep.- Twelve of 13 raw files give a session start of 2011-08-18. The processed file of e16_3m1_210201 holds a copy of the 1250 Hz LFP labelled 30,000 Hz with a wrong conversion factor.
- Units have no link to electrodes, and their
group_idvalues do not match the electrode group names in 64 of 65 files. The birthdate of each mouse is stored only in its subject id. - The 96 files of the 15 homecage mice match the paper's per-birthdate session counts and duration bounds exactly; the Methods' count of 92 sessions disagrees with the paper's own 96.