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)
In [1]:
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.

In [2]:
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.

In [3]:
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.

In [4]:
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
In [5]:
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.

In [6]:
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.

In [7]:
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).

In [8]:
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
In [9]:
# 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.

In [10]:
# 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
In [11]:
# 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.

In [12]:
# 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).

Claims about the dataset¶

cohort-birthdates¶

Chronic recordings were made from 17 adult mice (12 males, 5 females) electroporated at E13.5 (n = 4), E14.5 (n = 3), E15.5 (n = 6) and E16.5 (n = 4).

In [13]:
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.

In [14]:
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.

In [15]:
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.

In [16]:
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.

In [17]:
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.

In [18]:
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).

In [19]:
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).

In [20]:
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)
In [21]:
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
No description has been provided for this image

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.

In [22]:
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
In [23]:
# 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).

In [24]:
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
No description has been provided for this image

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).

In [25]:
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).

In [26]:
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).

In [27]:
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)
In [28]:
# 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).

In [29]:
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_maze is 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_id values 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.