Reproducing the behavior of Kauvar, Richman, Liu et al. (2025) from dandiset 001347¶

Paper. Kauvar IV, Richman EB, Liu TX, et al. (2025). Conserved brain-wide emergence of emotional response from sensory experience in humans and mice. Science 388, eadt3971. doi:10.1126/science.adt3971. The claims file (claims/001326/10.1126-science.adt3971.yaml) was written from the full text with its supplement (table S3).

Dataset. DANDI:001347, version 0.250528.0702. The paper's data statement points to this dandiset for the human and mouse behavior without naming a version; 0.250528.0702 is its only published version, so it is pinned. The mouse Neuropixels sessions are in dandiset 001326 and are tested in the entry's other notebook; this notebook reads 001326 (version 0.250528.1957) only for the stimulus tables, to link the recorded mice's eye closure files to those sessions.

What 001347 holds that is used here. The eye closure traces of the behavior-rig mice (sub-mouse, one file per experiment id), the eye closure of the ten Neuropixels-recorded mice (sub-m<mouse>-<rec>), and the per-trial eye closure of the four non-implanted participants (sub-h009 to sub-h013, P50 study). The iEEG files (sub-SD*, sub-H) are not analysed here; the data check lists the datasets every one of them holds, to look for the data of the blocked claims, and the derived sub-H files are analysed in reproduce_kauvar_2025_ieeg.ipynb.

Outside input. The sub-mouse files carry an experiment id and nothing else: no drug, dose, pressure or injection label. The session-to-condition lists (and the trial selections) come from the paper's behavior code, Zenodo doi:10.5281/zenodo.14920570 (mouse_eyepuff/scripts/fig1_*.py). These only name the experiments the dataset already holds (a with_labels input). The recorded-mouse files are matched to the 001326 sessions by the date and mouse in convert_npx_mouse_airpuff_to_nwb.py of the same deposit, and the match is tested below on the puff series timing. The human files hold pre_trials and post_trials; the same code (human_ieeg/scripts/analyze_airpuff_p50.py) labels them saline and ketamine.

No step draws random numbers; the curve fits are deterministic.

Claim Section Status
fig4b-recorded-eye-closure-ketamine Recorded mice tested (10 of the 13 sessions are in 001347)
fig5b-recorded-accumulation Recorded mice tested (of the 11 sessions, 10, 8 or 9 are in 001347, by reading)
figs34c-tone-no-eye-closure Recorded mice tested (6 of the 7 tone sessions are in 001347)
fig1k-mouse-ketamine-late-closure Behavior-rig mice tested
fig1l-dissociatives-preinfusion Behavior-rig mice tested
figs2bc-mouse-early-late Behavior-rig mice tested
fig1m-accumulation-persistence Behavior-rig mice tested
figs2g-scaling-puff-number Behavior-rig mice tested
figs2h-dose-response Behavior-rig mice tested
fig1p-anesthesia-abolishes-both Behavior-rig mice tested
figs5-other-drugs Behavior-rig mice tested
figs6-vpm-muscimol Behavior-rig mice tested
fig5hi-behavior-model-doses Behavior-rig mice tested (depends on fig5g-tau-persist-model, other notebook)
figs4-reward-licking blocked: no lick data in 001347
fig1e-human-ketamine-late-closure Humans tested
figs1b-human-early-closure-unchanged Humans tested
fig1f-human-reports blocked: no participant reports in 001347
fig2d-cadss-ketamine blocked: no CADSS scores in 001347
figs14g-cadss-correlation blocked: no CADSS scores in 001347
link-001347-condition-labels Data check tested
link-001347-recorded-sessions Data check tested
In [1]:
import time
import warnings

import numpy as np
import pandas as pd
import scipy.stats

import behavior as B

warnings.filterwarnings("ignore", category=RuntimeWarning)
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 30)
pd.set_option("display.max_rows", 200)
np.set_printoptions(precision=3, suppress=True, linewidth=180)

started = time.time()
urls = B.asset_urls()
ids = sorted(set(sum(B.EXPERIMENTS.values(), [])))
mouse = B.load_mouse(ids, urls)
npx = B.load_npx(urls)
p50 = B.load_p50(urls)
stim = B.stim_001326(list(B.NPX_SESSIONS.values()))
print(f"dandiset {B.DANDISET} version {B.VERSION}: {len(urls)} files; loaded {len(mouse)} behavior-rig "
      f"experiments, {len(npx)} recorded-mouse files, {len(p50)} human files, and {len(stim)} stimulus "
      f"tables of dandiset {B.DANDISET_NPX} version {B.VERSION_NPX}, in {time.time() - started:.0f} s")


def ci(x):
    # mean and 95% interval (1.96 s.e.m., population s.d. as analysis.py; sem with ddof=1 also shown)
    x = np.asarray(x, float)
    m, s0, s1 = x.mean(), x.std() / np.sqrt(len(x)), scipy.stats.sem(x)
    return f"{m:.3f} [{m - 1.96 * s0:.3f}, {m + 1.96 * s0:.3f}] (ddof=1: [{m - 1.96 * s1:.3f}, {m + 1.96 * s1:.3f}]) n={len(x)}"
dandiset 001347 version 0.250528.0702: 207 files; loaded 99 behavior-rig experiments, 10 recorded-mouse files, 4 human files, and 10 stimulus tables of dandiset 001326 version 0.250528.1957, in 112 s

Data check¶

The checks below compare the files with what the code and the paper say about them: which experiment ids exist, trial counts, sampling rates, value ranges, whether the puffs fall where the trial structure puts them (against a shifted control), recordings deposited twice, the link of the recorded-mouse files to dandiset 001326, and (revised after verification) the datasets of every file other than the behavior-rig mice, searched for the lick, report and CADSS data of the blocked claims.

In [2]:
import collections, hashlib
kinds = collections.Counter(p.split("/")[0] if not p.startswith("sub-mouse") else "sub-mouse" for p in urls)
print("files by folder:", dict(sorted(kinds.items())))

# ---- behavior-rig mice
present = {int(p.split("ses-")[1].split("_")[0]) for p in urls if p.startswith("sub-mouse/")}
named = set(ids)
other = set(sum(B.EXPERIMENTS_OTHER.values(), []))
print(f"\nsub-mouse experiment ids: {len(present)} in 001347, {len(named)} in the code lists the claims use, {len(other - named)} more in "
      f"code lists of other panels ({', '.join(B.EXPERIMENTS_OTHER)}); named but absent {sorted((named | other) - present)}, "
      f"present but in no code list {sorted(present - named - other)}")
meta = pd.DataFrame({i: m for i, (_, m) in mouse.items()}).T
print("subject descriptions:", meta.description.unique(), "| units:", meta.unit.unique(), "| rate attribute (Hz):", meta.rate.unique())
print("subject_id equals the experiment id in every file:", all(str(i) == meta.loc[i, "subject_id"] for i in mouse))
rows = []
for c, lst in B.EXPERIMENTS.items():
    nf = [mouse[i][1]["n_frames"] for i in lst]
    rows.append({"condition": c, "n_mice": len(lst), "ids": lst, "trials in file": sorted({n // B.FRAMES_PER_TRIAL for n in nf}),
                 "trials used by code": B.NTRIALS[c], "pre trials": f"{B.WHICH_PRE[c][0]}-{B.WHICH_PRE[c][-1]}",
                 "infusion trials": f"{B.WHICH_POST[c][0]}-{B.WHICH_POST[c][-1]}"})
print(pd.DataFrame(rows).to_string(index=False))
allx = np.concatenate([x for x, _ in mouse.values()])
print(f"eye closure values: min {np.nanmin(allx):.3f}, 1st pct {np.nanpercentile(allx, 1):.3f}, median {np.nanmedian(allx):.3f}, "
      f"99th pct {np.nanpercentile(allx, 99):.3f}, max {np.nanmax(allx):.3f}, NaN {int(np.isnan(allx).sum())} of {allx.size} "
      "(the code calls it a correlation with an image of the closed eye; no unit beyond the rate)")
# puff timing: closure 0.1-0.2 s after each nominal puff minus the 0.5 s before it, against the grid shifted by 1.5 s
def jump(shift):
    v = []
    for i, (x, m) in mouse.items():
        nt = m["n_frames"] // B.FRAMES_PER_TRIAL
        tr = x[:nt * B.FRAMES_PER_TRIAL].reshape(nt, -1)
        for k in range(8):
            p = int(B.FPS * (B.T_FIRST + 3 * k + shift))
            v.append(np.nanmean(tr[:, p + 20:p + 40]) - np.nanmean(tr[:, p - 100:p]))
    return np.array(v)
for s in (0.0, 1.5):
    j = jump(s)
    print(f"puff grid shifted by {s} s: closure jump 0.1-0.2 s after minus 0.5 s before, mean {j.mean():.3f}, "
          f"min {j.min():.3f}, fraction > 0.05: {np.mean(j > 0.05):.2f} (n = {len(j)} experiment x puff)")
# recordings deposited twice
hashes = collections.defaultdict(list)
for i, (x, _) in mouse.items():
    hashes[hashlib.md5(np.nan_to_num(x[:B.FRAMES_PER_TRIAL * 20]).tobytes()).hexdigest()].append(i)
dups = [v for v in hashes.values() if len(v) > 1]
print("identical first-20-trial traces across files:", dups if dups else "none")
both = sorted(set(B.EXPERIMENTS["ket35"]) & set(B.EXPERIMENTS["28 PSI"]))
print(f"the code uses the same files for 'ket35' and '28 PSI' (the 28 PSI preinfusion trials of the ket35 experiments): {both}")
# every dataset in one sub-mouse file (metadata only), computed from the file (revised after verification)
first_mouse = sorted(p for p in urls if p.startswith("sub-mouse/"))[0]
mobj = {n: sh for n, sh in B.file_objects(urls[first_mouse]).items() if not n.startswith(("specifications", "general/"))}
print(f"datasets in {first_mouse} (outside general/):", mobj)

# ---- recorded mice
print("\nrecorded-mouse files (sub-m<mouse>-<rec>):")
rows = []
for name, d in npx.items():
    x = d["eyeclosure"]
    rows.append({"file subject": d["subject_id"], "001326 session (code)": B.NPX_SESSIONS[name], "fps": d["fps"], "rate attr": d["rate"],
                 "samples": len(x), "duration_s": round(len(x) / d["fps"]), "series events": len(d["events_on"]),
                 "by_trial": d["by_trial_shape"], "by_puff": d["by_puff_shape"], "ket_trial": d["ket_trial"],
                 "preket": f"{d['trial_range_preket'][0]}-{d['trial_range_preket'][-1]}",
                 "ket": f"{d['trial_range_ket'][0]}-{d['trial_range_ket'][-1]}",
                 "min": np.nanmin(x), "max": np.nanmax(x), "NaN": int(np.isnan(x).sum())})
print(pd.DataFrame(rows).to_string(index=False))
print("by_puff rows / 8 against the series events:", {n: (d["by_puff_shape"][0] // 8, len(d["events_on"])) for n, d in npx.items()})
for shift in (5.0, 3.5):
    j = []
    for d in npx.values():
        x, f = d["eyeclosure"], d["fps"]
        for e in d["events_on"]:
            for k in range(8):
                p = int(e + (shift + 3 * k) * f)
                if p + 20 < len(x):
                    j.append(np.nanmean(x[p + 10:p + 20]) - np.nanmean(x[p - 50:p]))
    j = np.array(j)
    print(f"puff k at events-on + {shift} + 3k s: closure 0.1-0.2 s after minus 0.5 s before, mean {np.nanmean(j):.3f}, "
          f"fraction > 0.05: {np.nanmean(j > 0.05):.2f} (n = {len(j)})")
same_day = [("m11208-959", "m11210-960")]
for a, b in same_day:
    n = min(len(npx[a]["eyeclosure"]), len(npx[b]["eyeclosure"]))
    xa, xb = npx[a]["eyeclosure"][:n], npx[b]["eyeclosure"][:n]
    ok = ~np.isnan(xa) & ~np.isnan(xb)
    print(f"{a} and {b} (same date): correlation of the traces {np.corrcoef(xa[ok], xb[ok])[0, 1]:.3f}, identical: {np.array_equal(xa, xb)}")

# ---- link to 001326
print("\nlink to dandiset 001326 (series onsets: 001326 time = a * video time + b over the first n series):")
links = {}
rows = []
for name, d in npx.items():
    s = B.NPX_SESSIONS[name]
    links[name] = L = B.link_clock(d, stim[s])
    rows.append({"file": name, "001326 session": s, "video series": L["n_video"], "001326 series": L["n_001326"],
                 "matched": L["n_matched"], "slope a": round(L["a"], 6), "max |residual| s": round(L["max_abs_resid_s"], 4),
                 "tones in 001326": len(stim[s].get("tone", []))})
print(pd.DataFrame(rows).to_string(index=False))
all1326 = ["sub-0_ses-20230901", "sub-1_ses-20230218", "sub-1_ses-20230226", "sub-2_ses-20230902", "sub-3_ses-20230218",
           "sub-3_ses-20230225", "sub-4_ses-20230907", "sub-5_ses-20230706", "sub-5_ses-20230822", "sub-6_ses-20230823",
           "sub-7_ses-20220831", "sub-8_ses-20220228", "sub-9_ses-20230908"]
print("001326 sessions with no file in 001347:", sorted(set(all1326) - set(B.NPX_SESSIONS.values())))

# ---- humans
print("\nhuman P50 files:")
for s, (pre, post, tt) in p50.items():
    dt = np.diff(tt)
    pj = np.nanmean(pre[:, (tt > 0.1) & (tt < 0.2)]) - np.nanmean(pre[:, (tt > -0.5) & (tt < 0)])
    print(f"{s}: pre_trials {pre.shape}, post_trials {post.shape}, tt {tt[0]:.3f} to {tt[-1]:.3f} s step {dt.mean():.4f} s "
          f"({1 / dt.mean():.0f} Hz), values {np.nanmin([pre.min(), post.min()]):.3f} to {np.nanmax([pre.max(), post.max()]):.3f}, "
          f"NaN {int(np.isnan(pre).sum() + np.isnan(post).sum())}, closure jump at t = 0 (0.1-0.2 s minus -0.5-0 s) {pj:.3f}")
print("labels in the human files: only 'pre_trials_data' and 'post_trials_data'; the code takes them as saline and ketamine")

# ---- what is not in 001347: every file except the behavior-rig mice (iEEG ecephys and behavior, the
# derived human files, the human P50 files, the recorded mice) and one behavior-rig file, searched by
# dataset name (metadata only)
search = [p for p in sorted(urls) if not p.startswith("sub-mouse/")] + [first_mouse]
objs = dict(zip(search, B._pmap(lambda p: B.file_objects(urls[p]), search)))
words = ("cadss", "lick", "report", "rating", "questionnaire", "survey", "score", "reward", "blink")
hits = {p: [n for n in o if any(w in n.lower() for w in words)] for p, o in objs.items()}
hits = {p: v for p, v in hits.items() if v}
print(f"\n{len(search)} files searched for datasets named like {words}:", hits if hits else "none found")
kinds = collections.defaultdict(set)
for p, o in objs.items():
    kind = ("sub-mouse" if p.startswith("sub-mouse/") else "sub-H" if p.startswith("sub-H/") else "sub-SD ecephys" if p.endswith("_ecephys.nwb") else "sub-SD behavior" if p.startswith("sub-SD")
            else "sub-h P50" if p.startswith("sub-h") else "sub-m recorded mice")
    kinds[kind] |= {"/".join(n.split("/")[:2]) for n in o if not n.startswith(("specifications", "general/"))}
for kind, groups in sorted(kinds.items()):
    print(f"  {kind}: top-level groups {sorted(groups)[:40]}{' ...' if len(groups) > 40 else ''}")
files by folder: {'sub-H': 7, 'sub-SD163': 1, 'sub-SD168': 4, 'sub-SD170': 1, 'sub-SD172': 4, 'sub-SD178': 1, 'sub-SD181': 5, 'sub-SD184': 1, 'sub-SD185': 6, 'sub-SD186': 5, 'sub-SD207': 1, 'sub-SD211': 7, 'sub-SD215': 1, 'sub-SD242': 1, 'sub-SD245': 6, 'sub-h009': 1, 'sub-h011': 1, 'sub-h012': 1, 'sub-h013': 1, 'sub-m11208-959': 1, 'sub-m11208-962': 1, 'sub-m11210-960': 1, 'sub-m11219-1108': 1, 'sub-m11221-1119': 1, 'sub-m11223-1125': 1, 'sub-m11225-1131': 1, 'sub-m11227-1132': 1, 'sub-m304-876': 1, 'sub-m94-549': 1, 'sub-mouse': 142}

sub-mouse experiment ids: 142 in 001347, 99 in the code lists the claims use, 43 more in code lists of other panels (days, male, skip, 5 PSI); named but absent [], present but in no code list []
subject descriptions: ['mouse_airpuff'] | units: ['Eye closure, sampled at 200.0Hz'] | rate attribute (Hz): [200.0]
subject_id equals the experiment id in every file: True
condition  n_mice                                              ids trials in file  trials used by code pre trials infusion trials
      sal       5                        [282, 283, 284, 285, 286]           [40]                   40       5-19           22-36
    ket50       5                        [275, 276, 277, 280, 281]           [40]                   40       5-19           22-36
    pcp20       5                        [287, 288, 289, 290, 291]           [40]                   40       5-19           22-36
       kx       5                        [316, 338, 339, 343, 344]           [40]                   40       5-19           22-36
      dzp       5                        [325, 326, 327, 333, 334]           [40]                   40       5-19           22-36
      bup       5                        [340, 341, 342, 347, 348]           [40]                   40       5-19           22-36
    ket35       8 [1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027]           [40]                   40       5-19           22-36
    ket25       5                        [355, 356, 357, 362, 363]           [40]                   40       5-19           22-36
    ket13       5                        [401, 400, 399, 398, 397]           [40]                   40       5-19           22-36
     ket6       5                        [403, 405, 404, 406, 426]           [40]                   40       5-19           22-36
    pcp10       5                        [358, 359, 360, 361, 364]           [40]                   40       5-19           22-36
     pcp5       5                        [370, 368, 366, 365, 434]           [40]                   40       5-19           22-36
     pcp1       5                        [430, 432, 431, 435, 436]           [40]                   40       5-19           22-36
   18 PSI       8 [1004, 1001, 1002, 1003, 1030, 1031, 1029, 1028]           [20]                   20       2-16            2-16
    8 PSI       8 [1034, 1033, 1032, 1035, 1008, 1005, 1007, 1006]           [20]                   20       2-16            2-16
   28 PSI       8 [1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027]           [40]                   20       2-16            2-16
   intact       5                        [975, 971, 972, 973, 974]           [20]                   20       2-16            2-16
 musc_sal       5                        [984, 985, 980, 981, 982]           [20]                   20       2-16            2-16
 vpm_musc       5                        [989, 990, 986, 987, 988]           [20]                   20       2-16            2-16
eye closure values: min -0.625, 1st pct -0.330, median 0.238, 99th pct 0.874, max 0.997, NaN 0 of 54108000 (the code calls it a correlation with an image of the closed eye; no unit beyond the rate)
puff grid shifted by 0.0 s: closure jump 0.1-0.2 s after minus 0.5 s before, mean 0.429, min 0.049, fraction > 0.05: 1.00 (n = 792 experiment x puff)
puff grid shifted by 1.5 s: closure jump 0.1-0.2 s after minus 0.5 s before, mean -0.010, min -0.043, fraction > 0.05: 0.00 (n = 792 experiment x puff)
identical first-20-trial traces across files: none
the code uses the same files for 'ket35' and '28 PSI' (the 28 PSI preinfusion trials of the ket35 experiments): [1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027]
datasets in sub-mouse/sub-mouse_ses-1001_behavior.nwb (outside general/): {'acquisition/eye_closure/data': (324000,), 'acquisition/eye_closure/starting_time': (), 'file_create_date': (1,), 'identifier': (), 'session_description': (), 'session_start_time': (), 'timestamps_reference_time': ()}

recorded-mouse files (sub-m<mouse>-<rec>):
file subject 001326 session (code)  fps  rate attr  samples  duration_s  series events   by_trial    by_puff  ket_trial preket   ket  min  max  NaN
     m94-549    sub-8_ses-20220228  100      100.0   906075        9061             79 (7300, 79) (320, 400)         20   0-19 22-29  0.0  1.0  200
    m304-876    sub-7_ses-20220831  100      100.0   610201        6102             50 (7300, 50) (400, 400)         20   0-19 22-29  0.0  1.0   21
  m11208-959    sub-1_ses-20230218  100      100.0   558508        5585             48 (7300, 48) (384, 400)         20   0-19 22-29  0.0  1.0 1348
  m11208-962    sub-1_ses-20230226  100      100.0   783644        7836             59 (7300, 59) (472, 400)         20   0-19 22-29  0.0  1.0 7018
  m11210-960    sub-3_ses-20230218  100      100.0   562955        5630             48 (7300, 48) (384, 400)         20   0-19 22-29  0.0  1.0   14
 m11219-1108    sub-6_ses-20230823  100      100.0   755262        7553             58 (7300, 58) (464, 400)         20   0-19 22-29  0.0  1.0 8332
 m11221-1119    sub-0_ses-20230901  100      100.0   750304        7503             58 (7300, 58) (464, 400)         20   0-19 22-29  0.0  1.0 6680
 m11223-1125    sub-2_ses-20230902  100      100.0   751004        7510             58 (7300, 58) (464, 400)         20   0-19 22-29  0.0  1.0 8424
 m11225-1131    sub-4_ses-20230907  100      100.0   779228        7792             58 (7300, 58) (464, 400)         20   0-19 22-29  0.0  1.0 6461
 m11227-1132    sub-9_ses-20230908  100      100.0   809342        8093             58 (7300, 58) (464, 400)         20   0-19 22-29  0.0  1.0 8938
by_puff rows / 8 against the series events: {'m94-549': (40, 79), 'm304-876': (50, 50), 'm11208-959': (48, 48), 'm11208-962': (59, 59), 'm11210-960': (48, 48), 'm11219-1108': (58, 58), 'm11221-1119': (58, 58), 'm11223-1125': (58, 58), 'm11225-1131': (58, 58), 'm11227-1132': (58, 58)}
puff k at events-on + 5.0 + 3k s: closure 0.1-0.2 s after minus 0.5 s before, mean 0.249, fraction > 0.05: 0.86 (n = 4592)
puff k at events-on + 3.5 + 3k s: closure 0.1-0.2 s after minus 0.5 s before, mean -0.011, fraction > 0.05: 0.02 (n = 4592)
m11208-959 and m11210-960 (same date): correlation of the traces 0.713, identical: False

link to dandiset 001326 (series onsets: 001326 time = a * video time + b over the first n series):
       file     001326 session  video series  001326 series  matched  slope a  max |residual| s  tones in 001326
    m94-549 sub-8_ses-20220228            79             40       40 1.000031            0.0136                0
   m304-876 sub-7_ses-20220831            50             52       50 1.000030            0.0116                0
 m11208-959 sub-1_ses-20230218            48             50       48 1.000030            0.0054                0
 m11208-962 sub-1_ses-20230226            59             61       59 1.000030            0.0050              240
 m11210-960 sub-3_ses-20230218            48             50       48 1.000030            0.0061                0
m11219-1108 sub-6_ses-20230823            58             60       58 1.000029            0.0051              232
m11221-1119 sub-0_ses-20230901            58             60       58 1.000030            0.0055              232
m11223-1125 sub-2_ses-20230902            58             60       58 1.000030            0.0055              232
m11225-1131 sub-4_ses-20230907            58             60       58 1.000030            0.0047              240
m11227-1132 sub-9_ses-20230908            58             60       58 1.000030            0.0047              240
001326 sessions with no file in 001347: ['sub-3_ses-20230225', 'sub-5_ses-20230706', 'sub-5_ses-20230822']

human P50 files:
h009: pre_trials (40, 800), post_trials (40, 800), tt -1.530 to 2.465 s step 0.0050 s (200 Hz), values -0.259 to 2.739, NaN 0, closure jump at t = 0 (0.1-0.2 s minus -0.5-0 s) 0.159
h011: pre_trials (40, 800), post_trials (40, 800), tt -1.530 to 2.465 s step 0.0050 s (200 Hz), values -0.170 to 0.880, NaN 0, closure jump at t = 0 (0.1-0.2 s minus -0.5-0 s) 0.762
h012: pre_trials (40, 800), post_trials (40, 800), tt -1.530 to 2.465 s step 0.0050 s (200 Hz), values -0.382 to 0.932, NaN 0, closure jump at t = 0 (0.1-0.2 s minus -0.5-0 s) 0.505
h013: pre_trials (40, 800), post_trials (40, 800), tt -1.530 to 2.465 s step 0.0050 s (200 Hz), values -0.159 to 0.941, NaN 0, closure jump at t = 0 (0.1-0.2 s minus -0.5-0 s) 0.502
labels in the human files: only 'pre_trials_data' and 'post_trials_data'; the code takes them as saline and ketamine
66 files searched for datasets named like ('cadss', 'lick', 'report', 'rating', 'questionnaire', 'survey', 'score', 'reward', 'blink'): none found
  sub-H: top-level groups ['acquisition/anatomy_data', 'acquisition/ff', 'acquisition/session_ch_data', 'acquisition/session_patient_data', 'acquisition/session_trial_data', 'acquisition/tt', 'file_create_date', 'identifier', 'scratch/ACC_change', 'scratch/ACC_contour', 'scratch/AG_change', 'scratch/AG_contour', 'scratch/AMY_change', 'scratch/AMY_contour', 'scratch/BG_change', 'scratch/BG_contour', 'scratch/CS_change', 'scratch/CS_contour', 'scratch/Default_change', 'scratch/Default_contour', 'scratch/Dorsal Attention_change', 'scratch/Dorsal Attention_contour', 'scratch/FG_change', 'scratch/FG_contour', 'scratch/FO_change', 'scratch/FO_contour', 'scratch/Frontoparietal_change', 'scratch/Frontoparietal_contour', 'scratch/HIPP_change', 'scratch/HIPP_contour', 'scratch/IFG_change', 'scratch/IFG_contour', 'scratch/INS_change', 'scratch/INS_contour', 'scratch/ITG_change', 'scratch/ITG_contour', 'scratch/Limbic_change', 'scratch/Limbic_contour', 'scratch/MCC_change', 'scratch/MCC_contour'] ...
  sub-SD behavior: top-level groups ['acquisition/aff_w', 'acquisition/affective-baseline_puffeye', 'acquisition/affective-infusion_puffeye', 'acquisition/affective-postinfusion_puffeye', 'acquisition/affective-preinfusion_puffeye', 'acquisition/affective_normed-baseline_puffeye', 'acquisition/affective_normed-infusion_puffeye', 'acquisition/affective_normed-postinfusion_puffeye', 'acquisition/affective_normed-preinfusion_puffeye', 'acquisition/by_trial_closure-baseline_puffeye', 'acquisition/by_trial_closure-infusion_puffeye', 'acquisition/by_trial_closure-postinfusion_puffeye', 'acquisition/by_trial_closure-preinfusion_puffeye', 'acquisition/events-baseline_puffeye-off', 'acquisition/events-baseline_puffeye-on', 'acquisition/events-infusion_puffeye-off', 'acquisition/events-infusion_puffeye-on', 'acquisition/events-postinfusion_puffeye-off', 'acquisition/events-postinfusion_puffeye-on', 'acquisition/events-preinfusion_puffeye-off', 'acquisition/events-preinfusion_puffeye-on', 'acquisition/eyeclosure-baseline_puffeye', 'acquisition/eyeclosure-infusion_puffeye', 'acquisition/eyeclosure-postinfusion_puffeye', 'acquisition/eyeclosure-preinfusion_puffeye', 'acquisition/fps', 'acquisition/ref_w', 'acquisition/reflexive-baseline_puffeye', 'acquisition/reflexive-infusion_puffeye', 'acquisition/reflexive-postinfusion_puffeye', 'acquisition/reflexive-preinfusion_puffeye', 'acquisition/which_trials', 'file_create_date', 'identifier', 'session_description', 'session_start_time', 'timestamps_reference_time']
  sub-SD ecephys: top-level groups ['acquisition/dc_channels', 'acquisition/dc_trace', 'file_create_date', 'identifier', 'processing/ecephys', 'session_description', 'session_start_time', 'timestamps_reference_time']
  sub-h P50: top-level groups ['acquisition/post_trials_data', 'acquisition/pre_trials_data', 'acquisition/time_reference', 'file_create_date', 'identifier', 'session_description', 'session_start_time', 'timestamps_reference_time']
  sub-m recorded mice: top-level groups ['acquisition/aff_w', 'acquisition/events-off', 'acquisition/events-on', 'acquisition/eyeclosure', 'acquisition/eyeclosure_by_puff', 'acquisition/eyeclosure_by_trial', 'acquisition/eyesize', 'acquisition/eyesize_by_puff', 'acquisition/eyesize_by_trial', 'acquisition/fps', 'acquisition/ket_puff', 'acquisition/ket_trial', 'acquisition/led', 'acquisition/led_delay', 'acquisition/npuffs_per_trial', 'acquisition/p_post', 'acquisition/p_pre', 'acquisition/puff_aff', 'acquisition/puff_ref', 'acquisition/ref_w', 'acquisition/t_eyeclosure', 'acquisition/t_trial_eyeclosure', 'acquisition/trial_range_ket', 'acquisition/trial_range_preket', 'acquisition/w_post', 'acquisition/w_pre', 'file_create_date', 'identifier', 'session_description', 'session_start_time', 'timestamps_reference_time']
  sub-mouse: top-level groups ['acquisition/eye_closure', 'file_create_date', 'identifier', 'session_description', 'session_start_time', 'timestamps_reference_time']

Recorded mice¶

The files sub-m<mouse>-<rec> hold the eye closure of the Neuropixels-recorded mice (100 Hz). The code names each as <date>_m<mouse>_<rec>, and the date and mouse give one 001326 session. The data check shows the link holds for the ten files: the onset of every puff series in the video falls on the matching 001326 series onset within 14 ms under one linear clock map. 001326 has two more series at the end than the video in nine sessions (the video stops first), and m94-549 has 79 series events against 40 series in 001326 (the first 40 match). Three 001326 sessions have no file: sub-3_ses-20230225 and both sub-5 sessions, so the paper's 13 sessions are 10 here, and its 11 (Fig. 5) are at most 10.

Method (analysis.py make_puffseries_behavior_plot, lines 38-90, Zenodo doi:10.5281/zenodo.14876694): puff 1 at events-on + 5 s, puffs 3 s apart, each puff cut from 1 s before to 2 s after (5 s more after puff 8), each series minus the mean of the 1 s before its first puff, Pre series 1 to 20, Ketamine series 23 to 32, mean over series per session, each condition's mean trace divided by its maximum (the code). Late closure is the mean over 0.3 to 0.8 s after a puff.

fig4b-recorded-eye-closure-ketamine¶

The recorded mice showed affective eye closure after the first puff of each series, and ketamine reduced it.

Criterion (claims file): eye closure of the recorded sessions, baseline-subtracted with the 1 s before the puff (table S3), first puff of each series, Pre series 1 to 20 against Ketamine series 23 to 32 (code; Methods-literal 22 to 32 as a variant), with and without the code's normalization by each trace's maximum (each condition's mean trace divided by its own maximum; dividing both by the Pre maximum is a further variant); late closure 0.3 to 0.8 s; reproduced when the across-session mean of Pre minus Ketamine has a 95% interval (1.96 s.e.m.) above zero. The file's own trial_range_ket (series 23 to 30, 8 series, matching the 64 Ketamine puffs of table S3) is shown as a further variant.

In [3]:
npx_first = {n: B.npx_series(d, first_only=True) for n, d in npx.items()}
npx_all = {n: B.npx_series(d) for n, d in npx.items()}
readings = {"code (Pre 1-20, Ket 23-32)": (np.arange(20), np.arange(22, 32)),
            "Methods (Pre 1-20, Ket 22-32)": (np.arange(20), np.arange(21, 32))}
rows = []
for n, d in npx.items():
    S = npx_first[n]
    rd = dict(readings)
    rd["file ranges (Pre 1-20, Ket 23-30)"] = (d["trial_range_preket"], d["trial_range_ket"])
    for lab, (pre, ket) in rd.items():
        mp, mk = np.nanmean(S[pre], 0), np.nanmean(S[ket], 0)
        for norm in ("raw", "max-normalized", "both divided by the Pre maximum"):
            a, b = {"raw": (mp, mk), "max-normalized": (mp / np.nanmax(mp), mk / np.nanmax(mk)),
                    "both divided by the Pre maximum": (mp / np.nanmax(mp), mk / np.nanmax(mp))}[norm]
            rows.append({"session": B.NPX_SESSIONS[n], "reading": lab, "norm": norm,
                         "pre_late": B.npx_late(a, 0), "ket_late": B.npx_late(b, 0)})
df4 = pd.DataFrame(rows)
df4["pre_minus_ket"] = df4.pre_late - df4.ket_late
print("series per session:", {B.NPX_SESSIONS[n]: len(s) for n, s in npx_first.items()})
print(df4[(df4.norm == "raw") & (df4.reading.str.startswith("code"))].round(3).to_string(index=False))
print(df4[(df4.norm != "raw") & (df4.reading.str.startswith("code"))].round(3).to_string(index=False))
for (lab, norm), g in df4.groupby(["reading", "norm"], sort=False):
    t, p = scipy.stats.ttest_rel(g.pre_late, g.ket_late)
    print(f"\n{lab}, {norm}: Pre late {ci(g.pre_late)}\n   Ketamine late {ci(g.ket_late)}\n   Pre - Ketamine {ci(g.pre_minus_ket)}"
          f"  (paired t = {t:.2f}, p = {p:.4f}, not a test the paper reports)")
series per session: {'sub-8_ses-20220228': 79, 'sub-7_ses-20220831': 50, 'sub-1_ses-20230218': 48, 'sub-1_ses-20230226': 59, 'sub-3_ses-20230218': 48, 'sub-6_ses-20230823': 58, 'sub-0_ses-20230901': 58, 'sub-2_ses-20230902': 58, 'sub-4_ses-20230907': 58, 'sub-9_ses-20230908': 58}
           session                    reading norm  pre_late  ket_late  pre_minus_ket
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32)  raw     0.175     0.193         -0.017
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32)  raw     0.430     0.232          0.198
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32)  raw     0.117     0.177         -0.060
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32)  raw     0.074     0.182         -0.107
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32)  raw     0.142     0.051          0.091
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32)  raw     0.206     0.163          0.044
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32)  raw     0.119     0.195         -0.076
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32)  raw     0.146     0.082          0.064
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32)  raw     0.127     0.120          0.007
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32)  raw     0.204     0.089          0.115
           session                    reading                            norm  pre_late  ket_late  pre_minus_ket
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32)                  max-normalized     0.543     0.308          0.234
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.543     0.596         -0.054
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32)                  max-normalized     0.742     0.373          0.369
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.742     0.401          0.341
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32)                  max-normalized     0.530     0.362          0.168
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.530     0.804         -0.275
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32)                  max-normalized     0.643     0.306          0.337
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.643     1.574         -0.931
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32)                  max-normalized     0.424     0.264          0.160
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.424     0.152          0.272
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32)                  max-normalized     0.758     0.316          0.441
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.758     0.598          0.160
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32)                  max-normalized     0.823     0.531          0.292
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.823     1.350         -0.526
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32)                  max-normalized     0.550     0.286          0.264
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.550     0.310          0.240
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32)                  max-normalized     0.551     0.243          0.308
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.551     0.523          0.029
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32)                  max-normalized     0.734     0.229          0.505
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum     0.734     0.321          0.413

code (Pre 1-20, Ket 23-32), raw: Pre late 0.174 [0.116, 0.232] (ddof=1: [0.113, 0.235]) n=10
   Ketamine late 0.148 [0.114, 0.183] (ddof=1: [0.112, 0.185]) n=10
   Pre - Ketamine 0.026 [-0.030, 0.082] (ddof=1: [-0.033, 0.085]) n=10  (paired t = 0.85, p = 0.4154, not a test the paper reports)

code (Pre 1-20, Ket 23-32), max-normalized: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.322 [0.271, 0.373] (ddof=1: [0.268, 0.376]) n=10
   Pre - Ketamine 0.308 [0.243, 0.373] (ddof=1: [0.239, 0.376]) n=10  (paired t = 8.80, p = 0.0000, not a test the paper reports)

code (Pre 1-20, Ket 23-32), both divided by the Pre maximum: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.663 [0.391, 0.934] (ddof=1: [0.376, 0.949]) n=10
   Pre - Ketamine -0.033 [-0.285, 0.219] (ddof=1: [-0.299, 0.233]) n=10  (paired t = -0.24, p = 0.8130, not a test the paper reports)

Methods (Pre 1-20, Ket 22-32), raw: Pre late 0.174 [0.116, 0.232] (ddof=1: [0.113, 0.235]) n=10
   Ketamine late 0.150 [0.115, 0.185] (ddof=1: [0.113, 0.187]) n=10
   Pre - Ketamine 0.024 [-0.031, 0.079] (ddof=1: [-0.034, 0.082]) n=10  (paired t = 0.80, p = 0.4434, not a test the paper reports)

Methods (Pre 1-20, Ket 22-32), max-normalized: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.319 [0.264, 0.375] (ddof=1: [0.261, 0.378]) n=10
   Pre - Ketamine 0.311 [0.242, 0.379] (ddof=1: [0.239, 0.382]) n=10  (paired t = 8.48, p = 0.0000, not a test the paper reports)

Methods (Pre 1-20, Ket 22-32), both divided by the Pre maximum: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.666 [0.401, 0.930] (ddof=1: [0.387, 0.944]) n=10
   Pre - Ketamine -0.036 [-0.282, 0.211] (ddof=1: [-0.296, 0.224]) n=10  (paired t = -0.27, p = 0.7932, not a test the paper reports)

file ranges (Pre 1-20, Ket 23-30), raw: Pre late 0.174 [0.116, 0.232] (ddof=1: [0.113, 0.235]) n=10
   Ketamine late 0.147 [0.114, 0.179] (ddof=1: [0.113, 0.181]) n=10
   Pre - Ketamine 0.027 [-0.027, 0.082] (ddof=1: [-0.030, 0.085]) n=10  (paired t = 0.93, p = 0.3780, not a test the paper reports)

file ranges (Pre 1-20, Ket 23-30), max-normalized: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.318 [0.259, 0.376] (ddof=1: [0.256, 0.379]) n=10
   Pre - Ketamine 0.312 [0.243, 0.381] (ddof=1: [0.240, 0.385]) n=10  (paired t = 8.44, p = 0.0000, not a test the paper reports)

file ranges (Pre 1-20, Ket 23-30), both divided by the Pre maximum: Pre late 0.630 [0.554, 0.706] (ddof=1: [0.550, 0.710]) n=10
   Ketamine late 0.649 [0.397, 0.901] (ddof=1: [0.384, 0.915]) n=10
   Pre - Ketamine -0.020 [-0.253, 0.214] (ddof=1: [-0.265, 0.226]) n=10  (paired t = -0.16, p = 0.8798, not a test the paper reports)

Result: partially reproduced. With each condition's mean trace divided by its own maximum, as analysis.py make_puffseries_behavior_plot does, Pre minus Ketamine late closure is 0.308 [0.243, 0.373] across the 10 sessions that have a file (code epochs; 0.311 and 0.312 for the other epoch readings). Without normalization it is 0.026 [-0.030, 0.082] (Pre 0.174, Ketamine 0.148), and with both conditions divided by the Pre maximum -0.033 [-0.285, 0.219]: those intervals include zero. The criterion shows the trace with and without the code's normalization and does not choose; the reduction holds under the code's normalization only, so it is present in part. Three of the 13 sessions have no file in DANDI:001347.

fig5b-recorded-accumulation¶

In the recorded mice, eye closure accumulated across each puff series, and ketamine abolished the accumulation.

Criterion (claims file): 11 sessions from 9 mice, the excluded animal open; eye closure baseline-subtracted with the 1 s before each series; accumulation is late closure (0.3 to 0.8 s) after puff 8 minus after puff 1; reproduced when the 95% interval (1.96 s.e.m. across sessions) is above zero for Pre and includes zero or lies below the Pre mean for Ketamine (series 23 to 32).

Readings of the excluded animal: in 001326 removing sub-1, sub-3 or sub-5 leaves 11 sessions from 9 mice. In 001347 sub-5 has no file, so excluding sub-5 leaves 10 sessions (sub-3_ses-20230225 is also absent), excluding sub-1 leaves 8, excluding sub-3 leaves 9. Each is shown, raw and with the code's normalization by the maximum of each condition's mean trace; the Methods-literal Ketamine series 22 to 32 and the file's 23 to 30 are variants.

In [4]:
rows = []
for n, d in npx.items():
    S = npx_all[n]
    rd = dict(readings)
    rd["file ranges (Pre 1-20, Ket 23-30)"] = (d["trial_range_preket"], d["trial_range_ket"])
    for lab, (pre, ket) in rd.items():
        mp, mk = np.nanmean(S[pre], 0), np.nanmean(S[ket], 0)
        for norm in ("raw", "max-normalized", "both divided by the Pre maximum"):
            a, b = {"raw": (mp, mk), "max-normalized": (mp / np.nanmax(mp), mk / np.nanmax(mk)),
                    "both divided by the Pre maximum": (mp / np.nanmax(mp), mk / np.nanmax(mp))}[norm]
            rows.append({"session": B.NPX_SESSIONS[n], "reading": lab, "norm": norm,
                         "pre_p1": B.npx_late(a, 0), "pre_p8": B.npx_late(a, 7),
                         "ket_p1": B.npx_late(b, 0), "ket_p8": B.npx_late(b, 7)})
df5 = pd.DataFrame(rows)
df5["pre_acc"] = df5.pre_p8 - df5.pre_p1
df5["ket_acc"] = df5.ket_p8 - df5.ket_p1
print(df5[df5.reading.str.startswith("code")].round(3).to_string(index=False))
exclusions = {"exclude sub-5 (no file; 10 sessions)": "sub-5_", "exclude sub-1 (8 sessions)": "sub-1_", "exclude sub-3 (9 sessions)": "sub-3_"}
for (lab, norm), g in df5.groupby(["reading", "norm"], sort=False):
    for elab, pref in exclusions.items():
        h = g[~g.session.str.startswith(pref)]
        pm = h.pre_acc.mean()
        s = h.ket_acc.std(ddof=0) / np.sqrt(len(h))
        lo, hi = h.ket_acc.mean() - 1.96 * s, h.ket_acc.mean() + 1.96 * s
        print(f"\n{lab}, {norm}, {elab}:\n   Pre accumulation {ci(h.pre_acc)}\n   Ketamine accumulation {ci(h.ket_acc)}"
              f"\n   Ketamine interval includes 0: {lo <= 0 <= hi}; lies below the Pre mean ({pm:.3f}): {hi < pm}")
           session                    reading                            norm  pre_p1  pre_p8  ket_p1  ket_p8  pre_acc  ket_acc
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32)                             raw   0.175   0.307   0.193   0.321    0.132    0.129
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32)                  max-normalized   0.511   0.896   0.308   0.514    0.386    0.206
sub-8_ses-20220228 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.511   0.896   0.561   0.936    0.386    0.375
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32)                             raw   0.430   0.550   0.232   0.348    0.121    0.116
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32)                  max-normalized   0.732   0.937   0.356   0.534    0.205    0.178
sub-7_ses-20220831 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.732   0.937   0.395   0.593    0.205    0.198
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32)                             raw   0.117   0.193   0.177   0.179    0.076    0.002
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32)                  max-normalized   0.495   0.819   0.362   0.367    0.323    0.005
sub-1_ses-20230218 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.495   0.819   0.752   0.762    0.323    0.010
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32)                             raw   0.074   0.118   0.182   0.217    0.044    0.035
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32)                  max-normalized   0.579   0.920   0.296   0.354    0.341    0.057
sub-1_ses-20230226 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.579   0.920   1.418   1.690    0.341    0.273
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32)                             raw   0.142   0.207   0.051   0.061    0.065    0.010
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32)                  max-normalized   0.401   0.583   0.264   0.315    0.182    0.050
sub-3_ses-20230218 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.401   0.583   0.144   0.171    0.182    0.027
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32)                             raw   0.206   0.286   0.163   0.212    0.080    0.049
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32)                  max-normalized   0.700   0.973   0.265   0.346    0.273    0.080
sub-6_ses-20230823 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.700   0.973   0.552   0.719    0.273    0.167
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32)                             raw   0.119   0.149   0.195   0.230    0.031    0.036
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32)                  max-normalized   0.695   0.875   0.460   0.545    0.180    0.085
sub-0_ses-20230901 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.695   0.875   1.140   1.349    0.180    0.209
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32)                             raw   0.146   0.175   0.082   0.105    0.029    0.023
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32)                  max-normalized   0.550   0.658   0.285   0.363    0.108    0.079
sub-2_ses-20230902 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.550   0.658   0.310   0.396    0.108    0.086
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32)                             raw   0.127   0.127   0.120   0.115    0.001   -0.006
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32)                  max-normalized   0.536   0.540   0.243   0.232    0.003   -0.011
sub-4_ses-20230907 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.536   0.540   0.509   0.485    0.003   -0.024
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32)                             raw   0.204   0.205   0.089   0.093    0.001    0.004
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32)                  max-normalized   0.734   0.736   0.229   0.239    0.003    0.010
sub-9_ses-20230908 code (Pre 1-20, Ket 23-32) both divided by the Pre maximum   0.734   0.736   0.321   0.335    0.003    0.014

code (Pre 1-20, Ket 23-32), raw, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.058 [0.031, 0.085] (ddof=1: [0.030, 0.086]) n=10
   Ketamine accumulation 0.040 [0.012, 0.067] (ddof=1: [0.011, 0.069]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.058): False

code (Pre 1-20, Ket 23-32), raw, exclude sub-1 (8 sessions):
   Pre accumulation 0.057 [0.024, 0.090] (ddof=1: [0.022, 0.093]) n=8
   Ketamine accumulation 0.045 [0.012, 0.078] (ddof=1: [0.010, 0.080]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

code (Pre 1-20, Ket 23-32), raw, exclude sub-3 (9 sessions):
   Pre accumulation 0.057 [0.027, 0.087] (ddof=1: [0.026, 0.089]) n=9
   Ketamine accumulation 0.043 [0.013, 0.073] (ddof=1: [0.011, 0.075]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

code (Pre 1-20, Ket 23-32), max-normalized, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.074 [0.032, 0.116] (ddof=1: [0.030, 0.118]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): True

code (Pre 1-20, Ket 23-32), max-normalized, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.085 [0.036, 0.133] (ddof=1: [0.033, 0.137]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): True

code (Pre 1-20, Ket 23-32), max-normalized, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.076 [0.030, 0.123] (ddof=1: [0.028, 0.125]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): True

code (Pre 1-20, Ket 23-32), both divided by the Pre maximum, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.134 [0.056, 0.211] (ddof=1: [0.052, 0.215]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): False

code (Pre 1-20, Ket 23-32), both divided by the Pre maximum, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.132 [0.046, 0.217] (ddof=1: [0.040, 0.223]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): False

code (Pre 1-20, Ket 23-32), both divided by the Pre maximum, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.145 [0.063, 0.228] (ddof=1: [0.058, 0.233]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): False

Methods (Pre 1-20, Ket 22-32), raw, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.058 [0.031, 0.085] (ddof=1: [0.030, 0.086]) n=10
   Ketamine accumulation 0.037 [0.009, 0.066] (ddof=1: [0.007, 0.068]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.058): False

Methods (Pre 1-20, Ket 22-32), raw, exclude sub-1 (8 sessions):
   Pre accumulation 0.057 [0.024, 0.090] (ddof=1: [0.022, 0.093]) n=8
   Ketamine accumulation 0.044 [0.011, 0.078] (ddof=1: [0.009, 0.080]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

Methods (Pre 1-20, Ket 22-32), raw, exclude sub-3 (9 sessions):
   Pre accumulation 0.057 [0.027, 0.087] (ddof=1: [0.026, 0.089]) n=9
   Ketamine accumulation 0.041 [0.010, 0.072] (ddof=1: [0.008, 0.074]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

Methods (Pre 1-20, Ket 22-32), max-normalized, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.066 [0.021, 0.110] (ddof=1: [0.019, 0.113]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): True

Methods (Pre 1-20, Ket 22-32), max-normalized, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.079 [0.028, 0.129] (ddof=1: [0.025, 0.132]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): True

Methods (Pre 1-20, Ket 22-32), max-normalized, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.072 [0.024, 0.120] (ddof=1: [0.021, 0.123]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): True

Methods (Pre 1-20, Ket 22-32), both divided by the Pre maximum, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.123 [0.042, 0.204] (ddof=1: [0.037, 0.208]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): False

Methods (Pre 1-20, Ket 22-32), both divided by the Pre maximum, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.129 [0.043, 0.215] (ddof=1: [0.037, 0.221]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): False

Methods (Pre 1-20, Ket 22-32), both divided by the Pre maximum, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.136 [0.050, 0.222] (ddof=1: [0.044, 0.227]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): False

file ranges (Pre 1-20, Ket 23-30), raw, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.058 [0.031, 0.085] (ddof=1: [0.030, 0.086]) n=10
   Ketamine accumulation 0.046 [0.017, 0.076] (ddof=1: [0.015, 0.077]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.058): False

file ranges (Pre 1-20, Ket 23-30), raw, exclude sub-1 (8 sessions):
   Pre accumulation 0.057 [0.024, 0.090] (ddof=1: [0.022, 0.093]) n=8
   Ketamine accumulation 0.056 [0.023, 0.090] (ddof=1: [0.020, 0.092]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

file ranges (Pre 1-20, Ket 23-30), raw, exclude sub-3 (9 sessions):
   Pre accumulation 0.057 [0.027, 0.087] (ddof=1: [0.026, 0.089]) n=9
   Ketamine accumulation 0.050 [0.017, 0.082] (ddof=1: [0.016, 0.084]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.057): False

file ranges (Pre 1-20, Ket 23-30), max-normalized, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.087 [0.045, 0.130] (ddof=1: [0.042, 0.132]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): True

file ranges (Pre 1-20, Ket 23-30), max-normalized, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.106 [0.061, 0.150] (ddof=1: [0.058, 0.153]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): True

file ranges (Pre 1-20, Ket 23-30), max-normalized, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.087 [0.040, 0.134] (ddof=1: [0.037, 0.137]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): True

file ranges (Pre 1-20, Ket 23-30), both divided by the Pre maximum, exclude sub-5 (no file; 10 sessions):
   Pre accumulation 0.200 [0.122, 0.279] (ddof=1: [0.117, 0.283]) n=10
   Ketamine accumulation 0.143 [0.067, 0.218] (ddof=1: [0.063, 0.222]) n=10
   Ketamine interval includes 0: False; lies below the Pre mean (0.200): False

file ranges (Pre 1-20, Ket 23-30), both divided by the Pre maximum, exclude sub-1 (8 sessions):
   Pre accumulation 0.167 [0.083, 0.252] (ddof=1: [0.077, 0.257]) n=8
   Ketamine accumulation 0.169 [0.084, 0.254] (ddof=1: [0.078, 0.259]) n=8
   Ketamine interval includes 0: False; lies below the Pre mean (0.167): False

file ranges (Pre 1-20, Ket 23-30), both divided by the Pre maximum, exclude sub-3 (9 sessions):
   Pre accumulation 0.202 [0.115, 0.290] (ddof=1: [0.110, 0.295]) n=9
   Ketamine accumulation 0.153 [0.072, 0.234] (ddof=1: [0.067, 0.239]) n=9
   Ketamine interval includes 0: False; lies below the Pre mean (0.202): False

Result: partially reproduced. Pre accumulation is above zero in every reading (code normalization, without sub-5: 0.200 [0.122, 0.279]; unnormalized 0.058 [0.031, 0.085]). With the code's per-condition maximum normalization the Ketamine interval lies below the Pre mean under every exclusion (0.074 [0.032, 0.116]; 0.085 and 0.076 without sub-1 and sub-3). Without normalization, which is what the criterion describes (baseline-subtracted closure), the Ketamine interval (0.040 [0.012, 0.067]) neither includes zero nor lies below the Pre mean, and with both conditions divided by the Pre maximum it does not either (0.134 [0.056, 0.211]). Accumulation holds; its loss under ketamine holds only under the code's normalization. At most 10 sessions are available (sub-5 has no file).

figs34c-tone-no-eye-closure¶

Mice did not close their eyes appreciably to a series of auditory tones, before or during ketamine.

Criterion (claims file): eye closure across the tone series, 7 sessions from 7 mice, Pre 10 series, Ketamine 5 (table S3); reproduced when the 95% interval across sessions (1.96 s.e.m.) of the mean closure from 0.3 to 0.8 s after each tone, averaged over the eight tones, lies below the eyepuff value of the same sessions and below 0.1 of full closure.

Method. The recorded-mouse files hold no tone events. The tone onsets are taken from the 001326 stimulus table and placed on the video by the clock map of the data check. Tone series are cut as puff series (1 s before to 2 s after each tone, the series minus the 1 s before its first tone). Pre is the first 10 tone series before ketamine onset; two splits are shown, the code's ketamine onset (30 s after the last puff of series 20, analysis.py get_windowed_tone_psth) and the infusion time of the 001326 file. Ketamine is the first 5 series after the split (table S3), 10 as a variant (the code's tone analyses). The eyepuff value is the same measure over the 8 puffs of Pre series 1 to 20 and Ketamine series 23 to 32. The eye closure values run from 0 to 1 (data check), read as the fraction of full closure; values divided by the maximum of the session's Pre puff trace (the code's normalization for Fig. 4B) are shown as a variant. The 7 sessions of table S3 are the 9 tone sessions of 001326 without sub-5; 6 of them have a file here (sub-3_ses-20230225 is absent).

In [5]:
def mean8(tr):
    return np.mean([B.npx_late(tr, k) for k in range(8)])

tone_rows = []
for n, d in npx.items():
    s = B.NPX_SESSIONS[n]
    st = stim[s]
    if "tone" not in st:
        continue
    t0, T = B.tone_series(d, st, links[n])
    _, groups = B.series_starts(st["puff"])
    ket_start = groups[19][-1] + 30
    S = npx_all[n]
    pp, pk = np.nanmean(S[:20], 0), np.nanmean(S[22:32], 0)
    mx = np.nanmax(pp)
    for split_lab, split in (("code ket onset", ket_start), ("file infusion time", st["ket_infusion"])):
        pre = np.where(t0 < split)[0][:10]
        post = np.where(t0 > split)[0]
        for kn in (5, 10):
            tp, tk = np.nanmean(T[pre], 0), np.nanmean(T[post[:kn]], 0)
            tone_rows.append({"session": s, "split": split_lab, "ket series": kn, "n tone series in video": len(t0),
                              "n pre": len(pre), "n ket": len(post[:kn]),
                              "tone_pre": mean8(tp), "tone_ket": mean8(tk), "puff_pre": mean8(pp), "puff_ket": mean8(pk), "puff_pre_max": mx})
dft = pd.DataFrame(tone_rows)
print(dft[(dft.split == "code ket onset") & (dft["ket series"] == 5)].round(3).to_string(index=False))
for (split_lab, kn), g in dft.groupby(["split", "ket series"], sort=False):
    print(f"\nsplit at {split_lab}, Ketamine = first {kn} tone series (sessions {len(g)}; Pre series per session {g['n pre'].tolist()})")
    for norm in ("raw", "divided by Pre puff maximum"):
        f = 1 if norm == "raw" else g.puff_pre_max
        for ph in ("pre", "ket"):
            tone = g[f"tone_{ph}"] / f
            puff = g[f"puff_{ph}"] / f
            x = np.asarray(tone, float)
            hi = x.mean() + 1.96 * x.std() / np.sqrt(len(x))
            print(f"   {norm}, {ph}: tone {ci(tone)} | puff {ci(puff)} | tone upper bound {hi:.3f} below puff mean "
                  f"{np.mean(puff):.3f}: {hi < np.mean(puff)}; below 0.1: {hi < 0.1}")
           session          split  ket series  n tone series in video  n pre  n ket  tone_pre  tone_ket  puff_pre  puff_ket  puff_pre_max
sub-1_ses-20230226 code ket onset           5                      30      9      5    -0.064     0.006     0.087     0.222         0.128
sub-6_ses-20230823 code ket onset           5                      29      9      5    -0.049    -0.002     0.258     0.181         0.294
sub-0_ses-20230901 code ket onset           5                      29      9      5    -0.164    -0.041     0.133     0.207         0.171
sub-2_ses-20230902 code ket onset           5                      29      9      5    -0.089    -0.016     0.116     0.100         0.265
sub-4_ses-20230907 code ket onset           5                      30      9      5    -0.038     0.012     0.134     0.115         0.236
sub-9_ses-20230908 code ket onset           5                      30      9      5    -0.022    -0.018     0.213     0.087         0.278

split at code ket onset, Ketamine = first 5 tone series (sessions 6; Pre series per session [9, 9, 9, 9, 9, 9])
   raw, pre: tone -0.071 [-0.108, -0.034] (ddof=1: [-0.112, -0.030]) n=6 | puff 0.157 [0.109, 0.204] (ddof=1: [0.105, 0.208]) n=6 | tone upper bound -0.034 below puff mean 0.157: True; below 0.1: True
   raw, ket: tone -0.010 [-0.024, 0.004] (ddof=1: [-0.025, 0.005]) n=6 | puff 0.152 [0.109, 0.195] (ddof=1: [0.105, 0.199]) n=6 | tone upper bound 0.004 below puff mean 0.152: True; below 0.1: True
   divided by Pre puff maximum, pre: tone -0.367 [-0.607, -0.127] (ddof=1: [-0.630, -0.104]) n=6 | puff 0.683 [0.566, 0.800] (ddof=1: [0.555, 0.811]) n=6 | tone upper bound -0.127 below puff mean 0.683: True; below 0.1: True
   divided by Pre puff maximum, ket: tone -0.046 [-0.124, 0.032] (ddof=1: [-0.131, 0.040]) n=6 | puff 0.789 [0.378, 1.201] (ddof=1: [0.338, 1.240]) n=6 | tone upper bound 0.032 below puff mean 0.789: True; below 0.1: True

split at code ket onset, Ketamine = first 10 tone series (sessions 6; Pre series per session [9, 9, 9, 9, 9, 9])
   raw, pre: tone -0.071 [-0.108, -0.034] (ddof=1: [-0.112, -0.030]) n=6 | puff 0.157 [0.109, 0.204] (ddof=1: [0.105, 0.208]) n=6 | tone upper bound -0.034 below puff mean 0.157: True; below 0.1: True
   raw, ket: tone -0.008 [-0.019, 0.003] (ddof=1: [-0.020, 0.004]) n=6 | puff 0.152 [0.109, 0.195] (ddof=1: [0.105, 0.199]) n=6 | tone upper bound 0.003 below puff mean 0.152: True; below 0.1: True
   divided by Pre puff maximum, pre: tone -0.367 [-0.607, -0.127] (ddof=1: [-0.630, -0.104]) n=6 | puff 0.683 [0.566, 0.800] (ddof=1: [0.555, 0.811]) n=6 | tone upper bound -0.127 below puff mean 0.683: True; below 0.1: True
   divided by Pre puff maximum, ket: tone -0.045 [-0.107, 0.018] (ddof=1: [-0.113, 0.024]) n=6 | puff 0.789 [0.378, 1.201] (ddof=1: [0.338, 1.240]) n=6 | tone upper bound 0.018 below puff mean 0.789: True; below 0.1: True

split at file infusion time, Ketamine = first 5 tone series (sessions 6; Pre series per session [10, 10, 10, 10, 10, 10])
   raw, pre: tone -0.066 [-0.102, -0.029] (ddof=1: [-0.106, -0.026]) n=6 | puff 0.157 [0.109, 0.204] (ddof=1: [0.105, 0.208]) n=6 | tone upper bound -0.029 below puff mean 0.157: True; below 0.1: True
   raw, ket: tone -0.005 [-0.015, 0.006] (ddof=1: [-0.016, 0.007]) n=6 | puff 0.152 [0.109, 0.195] (ddof=1: [0.105, 0.199]) n=6 | tone upper bound 0.006 below puff mean 0.152: True; below 0.1: True
   divided by Pre puff maximum, pre: tone -0.343 [-0.576, -0.109] (ddof=1: [-0.598, -0.087]) n=6 | puff 0.683 [0.566, 0.800] (ddof=1: [0.555, 0.811]) n=6 | tone upper bound -0.109 below puff mean 0.683: True; below 0.1: True
   divided by Pre puff maximum, ket: tone -0.016 [-0.070, 0.039] (ddof=1: [-0.076, 0.044]) n=6 | puff 0.789 [0.378, 1.201] (ddof=1: [0.338, 1.240]) n=6 | tone upper bound 0.039 below puff mean 0.789: True; below 0.1: True

split at file infusion time, Ketamine = first 10 tone series (sessions 6; Pre series per session [10, 10, 10, 10, 10, 10])
   raw, pre: tone -0.066 [-0.102, -0.029] (ddof=1: [-0.106, -0.026]) n=6 | puff 0.157 [0.109, 0.204] (ddof=1: [0.105, 0.208]) n=6 | tone upper bound -0.029 below puff mean 0.157: True; below 0.1: True
   raw, ket: tone -0.010 [-0.019, -0.002] (ddof=1: [-0.020, -0.001]) n=6 | puff 0.152 [0.109, 0.195] (ddof=1: [0.105, 0.199]) n=6 | tone upper bound -0.002 below puff mean 0.152: True; below 0.1: True
   divided by Pre puff maximum, pre: tone -0.343 [-0.576, -0.109] (ddof=1: [-0.598, -0.087]) n=6 | puff 0.683 [0.566, 0.800] (ddof=1: [0.555, 0.811]) n=6 | tone upper bound -0.109 below puff mean 0.683: True; below 0.1: True
   divided by Pre puff maximum, ket: tone -0.053 [-0.103, -0.004] (ddof=1: [-0.107, 0.001]) n=6 | puff 0.789 [0.378, 1.201] (ddof=1: [0.338, 1.240]) n=6 | tone upper bound -0.004 below puff mean 0.789: True; below 0.1: True

Result: reproduced. In the 6 tone sessions that have a file, mean closure 0.3 to 0.8 s after the tones is below zero before ketamine (raw, code split: -0.071 [-0.108, -0.034]) and near zero during it (-0.010 [-0.024, 0.004]), below the eyepuff values of the same sessions (0.157 and 0.152) and below 0.1 under every split, Ketamine count and normalization.

Behavior-rig mice¶

Method (Zenodo doi:10.5281/zenodo.14920570, mouse_eyepuff/scripts and ketmouse/eye_trace_utils.py; details and lines in the docstring of behavior.py). Each trial (81 s, puff 1 at 6 s, eight puffs 3 s apart) minus the mean of its first 2 s (prox_norm). Early (reflexive) closure is the mean over 0.1 to 0.2 s after a puff, late (affective) closure over 0.3 to 0.8 s (Fig. 1 legend). Normalized late closure is the per-trial late closure divided by the mean early closure over the trials of that condition, averaged over trials (mean_ref=True). Drug and dose experiments use Pre trials 6 to 20 and infusion trials 23 to 37 (15 each; which_pre = np.arange(5, 20), which_post = np.arange(22, 37)); PSI and muscimol experiments use trials 3 to 17. Conditions are paired by position in the code's lists (the code's pairing of mice). Tests are paired t-tests, Benjamini-Hochberg corrected over the family the code uses.

In [6]:
first = B.condition_table(mouse, ["sal", "ket50", "pcp20"], 0)
eighth = B.condition_table(mouse, ["sal", "ket50", "pcp20", "bup", "dzp", "kx"], 7)
print("rows (mouse x phase):", len(first), len(eighth))
rows (mouse x phase): 30 60

fig1k-mouse-ketamine-late-closure¶

In mice, ketamine (50 mg/kg) reduced late eye closure normalized by early closure on the first puff of each series (0.26 under saline, -0.07 under ketamine).

Expected: saline 0.26 (tolerance 0.12), ketamine -0.07 (0.12); paired t-test t = 4.737, p = 0.009, 5 mice, 15 trials each (table S3). Method: first puff, infusion trials of the saline and ketamine experiments, paired by position (fig1_drugs_first_puff.py, ttest_rel(saline, ket) on per-mouse means).

In [7]:
t, p, a, b = B.paired(first, "sal", "post", "ket50", "post", "norm")
print("per mouse, saline:", a.round(3), "ketamine:", b.round(3))
print(f"saline {a.mean():.3f} +- {scipy.stats.sem(a):.3f} (s.e.m.); ketamine {b.mean():.3f} +- {scipy.stats.sem(b):.3f}")
print(f"paired t-test saline vs ketamine: t = {t:.3f}, p = {p:.4f}")
print(f"within tolerance: saline {abs(a.mean() - 0.26) <= 0.12}, ketamine {abs(b.mean() + 0.07) <= 0.12}")
per mouse, saline: [0.225 0.217 0.144 0.208 0.515] ketamine: [ 0.122 -0.161 -0.191 -0.088 -0.019]
saline 0.262 +- 0.065 (s.e.m.); ketamine -0.068 +- 0.056
paired t-test saline vs ketamine: t = 4.737, p = 0.0091
within tolerance: saline True, ketamine True

Result: reproduced. Normalized late closure is 0.262 under saline and -0.068 under ketamine, both within tolerance, paired t = 4.737, p = 0.0091, as table S3.

fig1l-dissociatives-preinfusion¶

Compared with preinfusion, normalized late closure was unchanged by saline but reduced by ketamine (50 mg/kg) and PCP (20 mg/kg).

Expected (table S3): pre-saline 0.29 (0.16), saline 0.26 (0.12), pre-ketamine 0.37 (0.12), ketamine -0.07 (0.12), pre-PCP 0.35 (0.2), PCP -0.03 (0.1); corrected p 0.504, 0.002, 0.041. Method: first puff, FDR over the three comparisons (fig1_drugs.py:193-232, first_puff_ordering = ['sal', 'ket50', 'pcp20'] with annotate_pvals_pre_vs_post).

In [8]:
exp = {"sal": (0.29, 0.16, 0.26, 0.12), "ket50": (0.37, 0.12, -0.07, 0.12), "pcp20": (0.35, 0.2, -0.03, 0.1)}
res = []
for c in ["sal", "ket50", "pcp20"]:
    t, p, a, b = B.paired(first, c, "pre", c, "post", "norm")
    res.append((c, t, p, a, b))
pc = B.fdr([r[2] for r in res])
for (c, t, p, a, b), q in zip(res, pc):
    e = exp[c]
    print(f"{c}: pre per mouse {a.round(3)}, infusion {b.round(3)}")
    print(f"   pre {a.mean():.3f} (within tol {abs(a.mean() - e[0]) <= e[1]}), infusion {b.mean():.3f} (within tol {abs(b.mean() - e[2]) <= e[3]}); "
          f"t = {t:.3f}, p = {p:.4f}, FDR p = {q:.4f}")
sal: pre per mouse [0.342 0.165 0.094 0.27  0.56 ], infusion [0.225 0.217 0.144 0.208 0.515]
   pre 0.286 (within tol True), infusion 0.262 (within tol True); t = 0.733, p = 0.5041, FDR p = 0.5041
ket50: pre per mouse [0.609 0.344 0.35  0.219 0.332], infusion [ 0.122 -0.161 -0.191 -0.088 -0.019]
   pre 0.371 (within tol True), infusion -0.068 (within tol True); t = 9.525, p = 0.0007, FDR p = 0.0020
pcp20: pre per mouse [0.512 0.224 0.193 0.174 0.642], infusion [ 0.138 -0.106 -0.073  0.036 -0.158]
   pre 0.349 (within tol True), infusion -0.033 (within tol True); t = 3.409, p = 0.0271, FDR p = 0.0406

Result: reproduced. All six means are within tolerance (saline 0.286 to 0.262, ketamine 0.371 to -0.068, PCP 0.349 to -0.033), and the FDR-corrected p values are 0.504 (saline), 0.002 (ketamine) and 0.041 (PCP), as table S3.

figs2bc-mouse-early-late¶

Under ketamine the early (reflexive) closure of mice was reduced but present (0.78 to 0.45), while late (affective) closure was abolished (0.34 to -0.02).

Expected (table S3, fig. S2 B and C, "first puff of each trial", saline against ketamine): early 0.78 (0.1) and 0.45 (0.12), t = 6.854, p = 0.002; late 0.34 (0.14) and -0.02 (0.04), t = 4.951, p = 0.008. Method: as fig1k, metrics early and late (fig1_drugs_first_puff.py loops over 'Affective' and 'Reflexive').

In [9]:
exp = {"early": (0.78, 0.1, 0.45, 0.12), "late": (0.34, 0.14, -0.02, 0.04)}
for m in ("early", "late"):
    t, p, a, b = B.paired(first, "sal", "post", "ket50", "post", m)
    e = exp[m]
    print(f"{m}: saline per mouse {a.round(3)}, ketamine {b.round(3)}")
    print(f"   saline {a.mean():.3f} +- {scipy.stats.sem(a):.3f} (within tol {abs(a.mean() - e[0]) <= e[1]}), "
          f"ketamine {b.mean():.3f} +- {scipy.stats.sem(b):.3f} (within tol {abs(b.mean() - e[2]) <= e[3]}); t = {t:.3f}, p = {p:.4f}")
    print(f"   ketamine early closure above zero (one-sample t): {scipy.stats.ttest_1samp(b, 0)}" if m == "early" else "")
early: saline per mouse [0.849 0.784 0.617 0.913 0.725], ketamine [0.447 0.467 0.234 0.529 0.58 ]
   saline 0.778 +- 0.051 (within tol True), ketamine 0.451 +- 0.059 (within tol True); t = 6.854, p = 0.0024
   ketamine early closure above zero (one-sample t): TtestResult(statistic=np.float64(7.623479765246672), pvalue=np.float64(0.001589618059813142), df=np.int64(4))
late: saline per mouse [0.191 0.17  0.089 0.19  0.374], ketamine [ 0.054 -0.075 -0.045 -0.046 -0.011]
   saline 0.203 +- 0.047 (within tol True), ketamine -0.025 +- 0.022 (within tol True); t = 4.951, p = 0.0078

Result: reproduced. Early closure falls from 0.778 to 0.451 (t = 6.854, p = 0.0024) and stays above zero, and late closure falls from 0.203 to -0.025 (t = 4.951, p = 0.0078); all four means are within tolerance and the t values are those of table S3. The saline late mean (0.203) is near the lower edge of 0.34 +- 0.14.

fig1m-accumulation-persistence¶

Late eye closure accumulated across the eight puffs of a series and decayed slowly after the last puff, with a Kaplan-Meier median persistence of 15.16 s.

Criterion (claims file): preinfusion trials, 20 per mouse in 5 mice (table S3, fig. S2 E); reproduced when the KM median is within 1.5 s of 15.16 s and the mean late closure (0.3 to 0.8 s) after the eighth puff exceeds that after the first with non-overlapping mean +- 1.96 s.e.m. across mice. The event ending survival is not stated in the paper; it is taken from fig1_persistence.py (see behavior.py): closure falls to 1.2 times the trial's first-5-s baseline, scanned from 30.3 s into the trial, censored at 50 s. That script uses the saline experiments and all 40 trials (200 trials), while the legend and table say 100 trials, 20 per mouse; both are shown, with the saline-infusion trials 21 to 40 as a further reading. Accumulation uses the saline experiments' preinfusion trials 1 to 20 (and the code's Pre trials 6 to 20 as a variant).

In [10]:
for lab, trials in (("table S3: saline experiments, preinfusion trials 1-20", np.arange(20)),
                    ("code: saline experiments, all 40 trials", np.arange(40)),
                    ("saline experiments, infusion trials 21-40", np.arange(20, 40))):
    T, E, med = [], [], []
    for i in B.EXPERIMENTS["sal"]:
        t, e = B.survival_times(mouse[i][0], 40, trials)
        T.append(t); E.append(e); med.append(B.km_median(t, e))
    T, E = np.concatenate(T), np.concatenate(E)
    m = B.km_median(T, E)
    print(f"{lab}: {len(T)} trials, {int(E.sum())} events, {int(len(E) - E.sum())} censored; KM median {m:.2f} s from 30.3 s "
          f"({m + 3.3:.2f} s from the eighth puff onset); within 1.5 s of 15.16: {abs(m - 15.16) <= 1.5}; per-mouse medians {np.round(med, 2)}")
for lab, trials in (("preinfusion trials 1-20", np.arange(20)), ("code Pre trials 6-20", np.arange(5, 20))):
    p1, p8 = [], []
    for i in B.EXPERIMENTS["sal"]:
        m = B.by_trial(mouse[i][0], 40)[trials]
        p1.append(B.puff_measures(m, 0)["late"]); p8.append(B.puff_measures(m, 7)["late"])
    p1, p8 = np.array(p1), np.array(p8)
    s1, s8 = p1.std(ddof=1) / np.sqrt(5), p8.std(ddof=1) / np.sqrt(5)
    print(f"\naccumulation, saline experiments, {lab}: per mouse puff 1 {p1.round(3)}, puff 8 {p8.round(3)}")
    print(f"   puff 1 {ci(p1)}; puff 8 {ci(p8)}; non-overlapping (ddof=1): {p1.mean() + 1.96 * s1 < p8.mean() - 1.96 * s8}")
table S3: saline experiments, preinfusion trials 1-20: 100 trials, 86 events, 14 censored; KM median 13.05 s from 30.3 s (16.35 s from the eighth puff onset); within 1.5 s of 15.16: False; per-mouse medians [18.73  4.22  5.01 20.72 25.74]
code: saline experiments, all 40 trials: 200 trials, 166 events, 34 censored; KM median 14.68 s from 30.3 s (17.98 s from the eighth puff onset); within 1.5 s of 15.16: True; per-mouse medians [22.54  5.44  5.33 24.24 33.37]
saline experiments, infusion trials 21-40: 100 trials, 80 events, 20 censored; KM median 20.16 s from 30.3 s (23.46 s from the eighth puff onset); within 1.5 s of 15.16: False; per-mouse medians [26.24 10.84  6.53 30.44 39.88]

accumulation, saline experiments, preinfusion trials 1-20: per mouse puff 1 [0.321 0.208 0.072 0.249 0.369], puff 8 [0.449 0.463 0.379 0.578 0.716]
   puff 1 0.244 [0.154, 0.333] (ddof=1: [0.143, 0.344]) n=5; puff 8 0.517 [0.413, 0.621] (ddof=1: [0.401, 0.633]) n=5; non-overlapping (ddof=1): True

accumulation, saline experiments, code Pre trials 6-20: per mouse puff 1 [0.305 0.131 0.063 0.272 0.43 ], puff 8 [0.447 0.425 0.412 0.578 0.695]
   puff 1 0.240 [0.126, 0.354] (ddof=1: [0.113, 0.368]) n=5; puff 8 0.511 [0.415, 0.607] (ddof=1: [0.404, 0.618]) n=5; non-overlapping (ddof=1): True

Result: partially reproduced. Late closure accumulates: 0.244 [0.154, 0.333] after puff 1 against 0.517 [0.413, 0.621] after puff 8, non-overlapping. With the criterion's trials (preinfusion, 20 per mouse) and the code's survival event, the Kaplan-Meier median is 13.05 s, outside 15.16 +- 1.5 s; the code's own trial set (all 40 trials) gives 14.68 s, within it.

figs2g-scaling-puff-number¶

In mice, normalized late eye closure was larger after the eighth puff of a series than after the first at each of three puff pressures (8, 18 and 28 PSI), while it did not differ between pressures at the eighth puff.

Criterion (claims file): paired t-tests across 8 mice with FDR over the six comparisons, per-mouse means of late / early, preinfusion; reproduced when the three puff 1 against puff 8 tests give corrected p < 0.05 with puff 8 higher and the three pressure comparisons give corrected p >= 0.05. Method: fig1_psi.py (20 trials, trials 3 to 17, plot_puff_comparison_bar_trials with mean_ref=True).

In [11]:
exp = {("8 PSI", 0): (0.256, 0.13), ("8 PSI", 7): (0.517, 0.11), ("18 PSI", 0): (0.239, 0.063),
       ("18 PSI", 7): (0.515, 0.073), ("28 PSI", 0): (0.286, 0.13), ("28 PSI", 7): (0.447, 0.12)}
vals = {}
for c in ("8 PSI", "18 PSI", "28 PSI"):
    for k in (0, 7):
        vals[(c, k)] = np.array([B.puff_measures(B.by_trial(mouse[i][0], 20)[B.WHICH_PRE[c]], k)["norm"] for i in B.EXPERIMENTS[c]])
        m, s = B.mean_sem(vals[(c, k)])
        e = exp[(c, k)]
        print(f"{c} puff {k + 1}: per mouse {vals[(c, k)].round(3)}; {m:.3f} +- {s:.3f} (within tol {abs(m - e[0]) <= e[1]})")
labels, ts, ps = [], [], []
for c in ("8 PSI", "18 PSI", "28 PSI"):
    t, p = scipy.stats.ttest_rel(vals[(c, 0)], vals[(c, 7)]); labels.append(f"{c}: puff 1 vs puff 8"); ts.append(t); ps.append(p)
for a, b in (("8 PSI", "18 PSI"), ("8 PSI", "28 PSI"), ("18 PSI", "28 PSI")):
    t, p = scipy.stats.ttest_rel(vals[(a, 7)], vals[(b, 7)]); labels.append(f"puff 8: {a} vs {b}"); ts.append(t); ps.append(p)
for l, t, p, q in zip(labels, ts, ps, B.fdr(ps)):
    print(f"{l}: t = {t:.3f}, p = {p:.5f}, FDR p = {q:.4f}")
8 PSI puff 1: per mouse [0.091 0.056 0.439 0.103 0.645 0.174 0.245 0.294]; 0.256 +- 0.071 (within tol True)
8 PSI puff 8: per mouse [0.32  0.344 0.649 0.516 0.7   0.379 0.785 0.442]; 0.517 +- 0.062 (within tol True)
18 PSI puff 1: per mouse [0.129 0.182 0.376 0.354 0.17  0.189 0.184 0.326]; 0.239 +- 0.034 (within tol True)
18 PSI puff 8: per mouse [0.433 0.482 0.575 0.607 0.299 0.6   0.496 0.628]; 0.515 +- 0.039 (within tol True)
28 PSI puff 1: per mouse [0.451 0.1   0.499 0.174 0.622 0.064 0.205 0.176]; 0.286 +- 0.073 (within tol True)
28 PSI puff 8: per mouse [0.576 0.23  0.641 0.361 0.732 0.242 0.42  0.378]; 0.447 +- 0.065 (within tol True)
8 PSI: puff 1 vs puff 8: t = -4.834, p = 0.00189, FDR p = 0.0038
18 PSI: puff 1 vs puff 8: t = -9.243, p = 0.00004, FDR p = 0.0001
28 PSI: puff 1 vs puff 8: t = -11.633, p = 0.00001, FDR p = 0.0000
puff 8: 8 PSI vs 18 PSI: t = 0.024, p = 0.98155, FDR p = 0.9815
puff 8: 8 PSI vs 28 PSI: t = 1.106, p = 0.30522, FDR p = 0.4578
puff 8: 18 PSI vs 28 PSI: t = 0.718, p = 0.49628, FDR p = 0.5955

Result: reproduced. Puff 8 exceeds puff 1 at 8, 18 and 28 PSI (corrected p = 0.0038, 0.0001 and below 0.0001), the three pressure comparisons at puff 8 give corrected p = 0.98, 0.46 and 0.60, and all six means are within tolerance.

figs2h-dose-response¶

The dissociative reduction of normalized late eye closure began around 35 mg/kg ketamine and 10 mg/kg PCP; lower doses did not significantly reduce it.

Criterion (claims file): paired t-tests of preinfusion against infusion of normalized late closure after the eighth puff at each dose, FDR-corrected under (a) all ten comparisons of the table together (saline included) and (b) each drug apart; reproduced when, under both, ketamine 35 and 50 and PCP 10 and 20 give corrected p < 0.05 with a decrease and the lower doses corrected p >= 0.05. Expected infusion values: ketamine 35 mg/kg 0.17 (0.14), PCP 10 mg/kg 0.2 (0.25). Method: fig1_dose.py (ordering of ten conditions; ket35 has 8 mice). Under (b) saline is its own family of one.

In [12]:
order = ["sal", "ket50", "ket35", "ket25", "ket13", "ket6", "pcp20", "pcp10", "pcp5", "pcp1"]
dose = B.condition_table(mouse, order, 7)
res = {}
for c in order:
    t, p, a, b = B.paired(dose, c, "pre", c, "post", "norm")
    res[c] = (t, p, a, b)
qa = dict(zip(order, B.fdr([res[c][1] for c in order])))
qb = {}
for fam in (["sal"], ["ket50", "ket35", "ket25", "ket13", "ket6"], ["pcp20", "pcp10", "pcp5", "pcp1"]):
    qb.update(dict(zip(fam, B.fdr([res[c][1] for c in fam]))))
for c in order:
    t, p, a, b = res[c]
    print(f"{c}: n = {len(a)}, pre {a.mean():.3f}, infusion {b.mean():.3f}, t = {t:.3f}, p = {p:.4f}, FDR (a) {qa[c]:.4f}, FDR (b) {qb[c]:.4f}")
print(f"ketamine 35 infusion {res['ket35'][3].mean():.3f} (within 0.14 of 0.17: {abs(res['ket35'][3].mean() - 0.17) <= 0.14}); "
      f"PCP 10 infusion {res['pcp10'][3].mean():.3f} (within 0.25 of 0.2: {abs(res['pcp10'][3].mean() - 0.2) <= 0.25})")
print("ketamine 13 mg/kg per mouse, pre:", res["ket13"][2].round(3), "infusion:", res["ket13"][3].round(3))
sal: n = 5, pre 0.657, infusion 0.705, t = -0.980, p = 0.3827, FDR (a) 0.4252, FDR (b) 0.3827
ket50: n = 5, pre 0.659, infusion -0.036, t = 8.488, p = 0.0011, FDR (a) 0.0106, FDR (b) 0.0053
ket35: n = 8, pre 0.447, infusion 0.169, t = 3.843, p = 0.0064, FDR (a) 0.0159, FDR (b) 0.0159
ket25: n = 5, pre 0.614, infusion 0.439, t = 1.232, p = 0.2853, FDR (a) 0.3566, FDR (b) 0.3566
ket13: n = 5, pre 0.573, infusion 1.000, t = -1.270, p = 0.2729, FDR (a) 0.3566, FDR (b) 0.3566
ket6: n = 5, pre 0.702, infusion 0.678, t = 0.329, p = 0.7585, FDR (a) 0.7585, FDR (b) 0.7585
pcp20: n = 5, pre 0.709, infusion -0.006, t = 6.080, p = 0.0037, FDR (a) 0.0159, FDR (b) 0.0114
pcp10: n = 5, pre 0.719, infusion 0.205, t = 5.404, p = 0.0057, FDR (a) 0.0159, FDR (b) 0.0114
pcp5: n = 5, pre 0.644, infusion 0.505, t = 2.682, p = 0.0551, FDR (a) 0.1103, FDR (b) 0.0735
pcp1: n = 5, pre 0.610, infusion 0.395, t = 2.064, p = 0.1080, FDR (a) 0.1799, FDR (b) 0.1080
ketamine 35 infusion 0.169 (within 0.14 of 0.17: True); PCP 10 infusion 0.205 (within 0.25 of 0.2: True)
ketamine 13 mg/kg per mouse, pre: [0.524 0.643 0.627 0.493 0.58 ] infusion: [0.331 0.866 0.711 0.783 2.309]

Result: reproduced. Under both FDR families, ketamine 50 and 35 mg/kg and PCP 20 and 10 mg/kg reduce late closure (corrected p 0.005 to 0.016) and the lower doses do not (ketamine 25, 13 and 6 mg/kg: 0.357, 0.357, 0.759; PCP 5 and 1 mg/kg: 0.110 and 0.180, or 0.074 and 0.108 per drug). Ketamine 35 (0.169) and PCP 10 (0.205) are within tolerance.

fig1p-anesthesia-abolishes-both¶

A general anesthetic dose of ketamine with xylazine abolished both reflexive and affective eye closure, while ketamine or PCP alone kept the reflexive blink.

Criterion (claims file): paired t-tests with FDR of reflexive (0.1 to 0.2 s) and affective (0.3 to 0.8 s) closure after the eighth puff, 5 mice, 15 trials each; reproduced when both K/X tests give corrected p < 0.05 with K/X lower, the K/X values are within tolerance (reflexive 0.03 +- 0.04, affective 0.02 +- 0.02; pre-K/X 0.77 +- 0.18 and 0.47 +- 0.06), and the reflexive closure under ketamine (0.44 +- 0.12) and PCP (0.43 +- 0.02) is within tolerance. Method: fig1_drugs.py:340-392, FDR over the six conditions of ordering per metric.

In [13]:
order6 = ["sal", "ket50", "pcp20", "bup", "dzp", "kx"]
for m, lab in (("early", "reflexive"), ("late", "affective")):
    r = [B.paired(eighth, c, "pre", c, "post", m) for c in order6]
    q = B.fdr([x[1] for x in r])
    print(f"{lab} closure, eighth puff (FDR over six conditions):")
    for c, (t, p, a, b), qq in zip(order6, r, q):
        print(f"   {c}: pre {a.mean():.3f}, infusion {b.mean():.3f} (per mouse {b.round(3)}), t = {t:.4f}, p = {p:.5f}, FDR p = {qq:.4f}")
kx = eighth[eighth.cond == "kx"]
pre_r, kx_r = kx[kx.phase == "pre"].early.mean(), kx[kx.phase == "post"].early.mean()
pre_a, kx_a = kx[kx.phase == "pre"].late.mean(), kx[kx.phase == "post"].late.mean()
ket_r = eighth[(eighth.cond == "ket50") & (eighth.phase == "post")].early.mean()
pcp_r = eighth[(eighth.cond == "pcp20") & (eighth.phase == "post")].early.mean()
print(f"within tolerance: pre-K/X reflexive {pre_r:.3f} {abs(pre_r - 0.77) <= 0.18}; K/X reflexive {kx_r:.3f} {abs(kx_r - 0.03) <= 0.04}; "
      f"pre-K/X affective {pre_a:.3f} {abs(pre_a - 0.47) <= 0.06}; K/X affective {kx_a:.3f} {abs(kx_a - 0.02) <= 0.02}; "
      f"ketamine reflexive {ket_r:.3f} {abs(ket_r - 0.44) <= 0.12}; PCP reflexive {pcp_r:.3f} {abs(pcp_r - 0.43) <= 0.02}")
reflexive closure, eighth puff (FDR over six conditions):
   sal: pre 0.781, infusion 0.779 (per mouse [0.795 0.796 0.624 0.918 0.761]), t = 0.1026, p = 0.92325, FDR p = 0.9232
   ket50: pre 0.763, infusion 0.436 (per mouse [0.398 0.474 0.25  0.467 0.593]), t = 3.0416, p = 0.03834, FDR p = 0.0575
   pcp20: pre 0.633, infusion 0.428 (per mouse [0.466 0.393 0.41  0.446 0.427]), t = 4.8422, p = 0.00839, FDR p = 0.0168
   bup: pre 0.540, infusion 0.470 (per mouse [0.348 0.743 0.463 0.379 0.42 ]), t = 1.2745, p = 0.27151, FDR p = 0.3258
   dzp: pre 0.779, infusion 0.479 (per mouse [0.52  0.592 0.407 0.473 0.406]), t = 6.2356, p = 0.00337, FDR p = 0.0101
   kx: pre 0.773, infusion 0.025 (per mouse [ 0.041  0.062 -0.02   0.046 -0.001]), t = 8.6017, p = 0.00100, FDR p = 0.0060
affective closure, eighth puff (FDR over six conditions):
   sal: pre 0.511, infusion 0.553 (per mouse [0.376 0.503 0.388 0.78  0.72 ]), t = -0.8982, p = 0.41987, FDR p = 0.4199
   ket50: pre 0.495, infusion -0.007 (per mouse [ 0.084 -0.062 -0.067 -0.017  0.023]), t = 7.9465, p = 0.00136, FDR p = 0.0041
   pcp20: pre 0.451, infusion 0.000 (per mouse [ 0.114 -0.037 -0.021  0.027 -0.081]), t = 5.7504, p = 0.00453, FDR p = 0.0091
   bup: pre 0.373, infusion 0.292 (per mouse [0.294 0.381 0.363 0.163 0.261]), t = 1.5276, p = 0.20133, FDR p = 0.2416
   dzp: pre 0.583, infusion 0.299 (per mouse [0.313 0.412 0.179 0.336 0.256]), t = 3.9535, p = 0.01677, FDR p = 0.0252
   kx: pre 0.470, infusion 0.024 (per mouse [0.009 0.051 0.034 0.014 0.013]), t = 12.0602, p = 0.00027, FDR p = 0.0016
within tolerance: pre-K/X reflexive 0.773 True; K/X reflexive 0.025 True; pre-K/X affective 0.470 True; K/X affective 0.024 True; ketamine reflexive 0.436 True; PCP reflexive 0.428 True

Result: reproduced. K/X lowers reflexive closure from 0.773 to 0.025 (t = 8.6017, corrected p = 0.006) and affective closure from 0.470 to 0.024 (t = 12.0602, corrected p = 0.0016), all within tolerance, while reflexive closure stays at 0.436 under ketamine and 0.428 under PCP, within tolerance.

figs5-other-drugs¶

Buprenorphine and diazepam did not reduce normalized late closure, unlike ketamine and PCP.

Criterion (claims file): paired t-tests with FDR on normalized late closure of the eighth puff; reproduced when ketamine and PCP give corrected p < 0.05 with a decrease and buprenorphine and diazepam corrected p >= 0.05. Method: fig1_drugs.py:394-407 corrects over ordering_nokx (saline, ketamine, PCP, buprenorphine, diazepam: five comparisons); the four drugs alone and the six with K/X are shown as variants.

In [14]:
r = {c: B.paired(eighth, c, "pre", c, "post", "norm") for c in order6}
for lab, fam in (("code: five conditions (K/X left out)", ["sal", "ket50", "pcp20", "bup", "dzp"]),
                 ("four drugs", ["ket50", "pcp20", "bup", "dzp"]), ("six conditions", order6)):
    q = B.fdr([r[c][1] for c in fam])
    print(lab)
    for c, qq in zip(fam, q):
        t, p, a, b = r[c]
        print(f"   {c}: pre {a.mean():.3f}, infusion {b.mean():.3f}, t = {t:.4f}, p = {p:.4f}, FDR p = {qq:.4f}")
print("K/X infusion per mouse (normalized late closure divides by an early closure near zero):", r["kx"][3].round(2))
code: five conditions (K/X left out)
   sal: pre 0.657, infusion 0.705, t = -0.9797, p = 0.3827, FDR p = 0.4784
   ket50: pre 0.659, infusion -0.036, t = 8.4882, p = 0.0011, FDR p = 0.0053
   pcp20: pre 0.709, infusion -0.006, t = 6.0804, p = 0.0037, FDR p = 0.0092
   bup: pre 0.647, infusion 0.639, t = 0.0878, p = 0.9342, FDR p = 0.9342
   dzp: pre 0.748, infusion 0.616, t = 1.6120, p = 0.1823, FDR p = 0.3038
four drugs
   ket50: pre 0.659, infusion -0.036, t = 8.4882, p = 0.0011, FDR p = 0.0042
   pcp20: pre 0.709, infusion -0.006, t = 6.0804, p = 0.0037, FDR p = 0.0074
   bup: pre 0.647, infusion 0.639, t = 0.0878, p = 0.9342, FDR p = 0.9342
   dzp: pre 0.748, infusion 0.616, t = 1.6120, p = 0.1823, FDR p = 0.2430
six conditions
   sal: pre 0.657, infusion 0.705, t = -0.9797, p = 0.3827, FDR p = 0.4593
   ket50: pre 0.659, infusion -0.036, t = 8.4882, p = 0.0011, FDR p = 0.0063
   pcp20: pre 0.709, infusion -0.006, t = 6.0804, p = 0.0037, FDR p = 0.0111
   bup: pre 0.647, infusion 0.639, t = 0.0878, p = 0.9342, FDR p = 0.9342
   dzp: pre 0.748, infusion 0.616, t = 1.6120, p = 0.1823, FDR p = 0.3645
   kx: pre 0.627, infusion -2.262, t = 1.2832, p = 0.2687, FDR p = 0.4031
K/X infusion per mouse (normalized late closure divides by an early closure near zero): [  0.22   0.82  -1.66   0.31 -11.01]

Result: reproduced. With the code's family of five comparisons, ketamine (corrected p = 0.0053) and PCP (0.0092) reduce normalized late closure and buprenorphine (0.934) and diazepam (0.304) do not; the four- and six-comparison families give the same pattern.

figs6-vpm-muscimol¶

Inhibiting VPM thalamus with muscimol reduced normalized affective closure (0.60 after saline injection, 0.11 after muscimol) while the fast early closure remained.

Expected (table S3, fig. S6 C): saline injection 0.60 (0.1), muscimol 0.11 (0.24); saline against muscimol t = 3.85, corrected p = 0.0289; no injection against muscimol t = 3.79, corrected p = 0.0289. Method: fig1_muscimol.py (20 trials, trials 3 to 17, eighth puff; get_drug_comparison_pvals corrects over three comparisons, no injection vs saline, saline vs muscimol, no injection vs muscimol; the script then also corrects the two muscimol comparisons alone, shown). The fast early closure is shown as the early closure (0.1 to 0.2 s) and the 0 to 40 ms window of the script.

In [15]:
mus = B.condition_table(mouse, ["intact", "musc_sal", "vpm_musc"], 7)
for c in ("intact", "musc_sal", "vpm_musc"):
    g = mus[(mus.cond == c) & (mus.phase == "pre")]
    print(f"{c}: normalized late per mouse {g.norm.to_numpy().round(3)}, mean {g.norm.mean():.3f} +- {scipy.stats.sem(g.norm):.3f}; "
          f"early {g.early.mean():.3f} +- {scipy.stats.sem(g.early):.3f}")
pairs = [("intact", "musc_sal"), ("musc_sal", "vpm_musc"), ("intact", "vpm_musc")]
r = [B.paired(mus, a, "pre", b, "pre", "norm") for a, b in pairs]
q3, q2 = B.fdr([x[1] for x in r]), B.fdr([x[1] for x in r[1:]])
for (a, b), x, qq in zip(pairs, r, q3):
    print(f"{a} vs {b}: t = {x[0]:.3f}, p = {x[1]:.4f}, FDR (three) p = {qq:.4f}")
print("FDR over the two muscimol comparisons:", q2.round(4))
sal_m = mus[(mus.cond == "musc_sal") & (mus.phase == "pre")].norm.mean()
mus_m = mus[(mus.cond == "vpm_musc") & (mus.phase == "pre")].norm.mean()
print(f"within tolerance: saline {abs(sal_m - 0.60) <= 0.1}, muscimol {abs(mus_m - 0.11) <= 0.24}")
fast = []
for c in ("intact", "musc_sal", "vpm_musc"):
    v = []
    for i in B.EXPERIMENTS[c]:
        m = B.by_trial(mouse[i][0], 20)[B.WHICH_PRE[c]]
        a0, b0 = B.win(0, (0.0, 0.04))
        v.append(np.nanmean(m[:, a0:b0]))
    fast.append(np.array(v))
    print(f"{c}: fast early closure 0-40 ms after puff 1, per mouse {np.round(v, 3)}, {ci(v)}")
intact: normalized late per mouse [0.613 0.749 0.542 0.657 0.603], mean 0.633 +- 0.034; early 0.453 +- 0.089
musc_sal: normalized late per mouse [0.724 0.586 0.618 0.434 0.649], mean 0.602 +- 0.048; early 0.411 +- 0.066
vpm_musc: normalized late per mouse [ 0.339 -0.284 -0.078  0.303  0.247], mean 0.105 +- 0.122; early 0.198 +- 0.055
intact vs musc_sal: t = 0.455, p = 0.6727, FDR (three) p = 0.6727
musc_sal vs vpm_musc: t = 3.845, p = 0.0184, FDR (three) p = 0.0289
intact vs vpm_musc: t = 3.789, p = 0.0193, FDR (three) p = 0.0289
FDR over the two muscimol comparisons: [0.019 0.019]
within tolerance: saline True, muscimol True
intact: fast early closure 0-40 ms after puff 1, per mouse [0.091 0.077 0.054 0.104 0.074], 0.080 [0.065, 0.095] (ddof=1: [0.064, 0.097]) n=5
musc_sal: fast early closure 0-40 ms after puff 1, per mouse [ 0.041  0.164  0.125  0.059 -0.011], 0.075 [0.021, 0.130] (ddof=1: [0.015, 0.136]) n=5
vpm_musc: fast early closure 0-40 ms after puff 1, per mouse [0.066 0.132 0.031 0.122 0.093], 0.089 [0.056, 0.121] (ddof=1: [0.053, 0.125]) n=5

Result: reproduced. Normalized affective closure is 0.602 after saline injection and 0.105 after muscimol, both within tolerance (t = 3.845 and 3.789, corrected p = 0.0289 for both comparisons), and the fast early closure stays (0.089 [0.056, 0.121] under muscimol against 0.075 after saline).

fig5hi-behavior-model-doses¶

Varying only the persistence timescale of the two-phase model explained much of the variation in affective eye closure across doses of ketamine and of PCP.

Criterion (claims file): fit as for fig5g-tau-persist-model on the dose series (ketamine 6, 13, 25, 35 mg/kg; PCP 1, 5, 10 mg/kg), 750 ms after each puff masked (Methods, analysis.py filter_reflex); variance explained is 1 - MSE_model/MSE_null against the fit with no parameter varying; reproduced when the variance explained is positive for each drug and the fitted persistence timescale falls with dose. Method: analysis.py theoretical_behavior (model at 10 ms over 44 s from 1 s before puff 1, masked by filter_reflex), bounds and starts of compare_models. The data the code fed to it are not in the released code; they are taken here (assumption) as the across-mouse mean of the per-mouse mean of the infusion trials (23 to 37), baseline-subtracted as above, 1 s before to 43 s after puff 1, averaged to 100 Hz (Methods: "eye closure data was averaged across participants and concatenated"). Adding the 50 mg/kg ketamine and 20 mg/kg PCP experiments is a variant. This claim depends on fig5g-tau-persist-model, tested in the entry's other notebook.

In [16]:
for lab, fams in (("criterion doses", (("ketamine", ["ket6", "ket13", "ket25", "ket35"]), ("PCP", ["pcp1", "pcp5", "pcp10"]))),
                  ("with ketamine 50 and PCP 20", (("ketamine", ["ket6", "ket13", "ket25", "ket35", "ket50"]), ("PCP", ["pcp1", "pcp5", "pcp10", "pcp20"])))):
    for drug, conds in fams:
        data = np.hstack([B.filter_reflex(B.model_trace(mouse, c)) for c in conds])
        r = B.fit_behavior_model(data, len(conds))
        falls = bool(np.all(np.diff(r["tauP"]) < 0))
        print(f"{lab}, {drug} {conds}: variance explained {r['varexp']:.3f}; S = {r['S']:.3f}, tauB = {r['tauB']:.3f} s; "
              f"tauP by dose {np.round(r['tauP'], 3)} s (falls with every dose step: {falls}); null tauP {np.round(r['null_tauP'], 3)}")
criterion doses, ketamine ['ket6', 'ket13', 'ket25', 'ket35']: variance explained 0.917; S = 22.165, tauB = 100.000 s; tauP by dose [24.475  9.692  5.136  0.613] s (falls with every dose step: True); null tauP [15.003 15.003 15.003 15.003]
criterion doses, PCP ['pcp1', 'pcp5', 'pcp10']: variance explained 0.886; S = 0.589, tauB = 2.852 s; tauP by dose [18.447 10.361  2.238] s (falls with every dose step: True); null tauP [13.782 13.782 13.782]
with ketamine 50 and PCP 20, ketamine ['ket6', 'ket13', 'ket25', 'ket35', 'ket50']: variance explained 0.934; S = 22.165, tauB = 100.000 s; tauP by dose [24.475  9.692  5.136  0.613  0.   ] s (falls with every dose step: True); null tauP [15.431 15.431 15.431 15.431 15.431]
with ketamine 50 and PCP 20, PCP ['pcp1', 'pcp5', 'pcp10', 'pcp20']: variance explained 0.931; S = 0.589, tauB = 2.852 s; tauP by dose [18.447 10.361  2.238  0.156] s (falls with every dose step: True); null tauP [14.023 14.023 14.023 14.023]

Result: reproduced. Varying only tauP explains 0.917 of the variance across ketamine doses and 0.886 across PCP doses, and the fitted tauP falls with every dose step (ketamine 6 to 35 mg/kg: 24.5, 9.7, 5.1, 0.6 s; PCP 1 to 10 mg/kg: 18.4, 10.4, 2.2 s). The ketamine fit sits at the upper bound of tauB (100 s).

Humans (four non-implanted participants)¶

Method: human_ieeg/scripts/analyze_airpuff_p50.py. Trials 3 to 40 (trial_range = np.arange(2, 40), "Skip 1st and 2nd trial, which sometimes didn't have a puff"), samples with 0.3 < t < 0.8 s (late) and 0.1 < t < 0.2 s (early), per participant the mean over trials of late divided by the mean early closure (mean_ref=True); pre_trials taken as saline and post_trials as ketamine (the script's labels). No baseline is subtracted in the script.

fig1e-human-ketamine-late-closure¶

In four non-implanted participants, late eye closure after the eyepuff (0.3 to 0.8 s), normalized by early closure (0.1 to 0.2 s), was lower during ketamine (0.43) than during saline (0.71).

Criterion (claims file): per participant, the mean over trials (the first two excluded) of late divided by early closure; reproduced when both group means are within tolerance (saline 0.71 +- 0.16, ketamine 0.43 +- 0.24) and a paired t-test gives p < 0.05 with ketamine lower (table S3: t = 3.703, p = 0.0342).

In [17]:
H = {s: (B.human_measures(pre, tt), B.human_measures(post, tt)) for s, (pre, post, tt) in p50.items()}
a = np.array([H[s][0]["norm"] for s in H]); b = np.array([H[s][1]["norm"] for s in H])
print("participants:", list(H), "trials used per condition:", len(np.arange(2, 40)))
print("saline per participant:", a.round(3), "ketamine:", b.round(3))
t, p = scipy.stats.ttest_rel(a, b)
print(f"saline {a.mean():.3f} +- {scipy.stats.sem(a):.3f} (within tol {abs(a.mean() - 0.71) <= 0.16}); "
      f"ketamine {b.mean():.3f} +- {scipy.stats.sem(b):.3f} (within tol {abs(b.mean() - 0.43) <= 0.24}); paired t = {t:.3f}, p = {p:.4f}")
la = np.array([H[s][0]["late"] for s in H]); lb = np.array([H[s][1]["late"] for s in H])
print(f"late closure without normalization (fig. S1C): saline {la.mean():.3f}, ketamine {lb.mean():.3f}, {scipy.stats.ttest_rel(la, lb)}")
participants: ['h009', 'h011', 'h012', 'h013'] trials used per condition: 38
saline per participant: [0.908 0.497 0.697 0.724] ketamine: [0.519 0.065 0.527 0.591]
saline 0.706 +- 0.084 (within tol True); ketamine 0.426 +- 0.121 (within tol True); paired t = 3.703, p = 0.0342
late closure without normalization (fig. S1C): saline 0.591, ketamine 0.343, TtestResult(statistic=np.float64(3.6531566324131175), pvalue=np.float64(0.03541404501863728), df=np.int64(3))

Result: reproduced. Normalized late closure is 0.706 under saline and 0.426 under ketamine, within tolerance, paired t = 3.703, p = 0.0342, as table S3.

figs1b-human-early-closure-unchanged¶

Ketamine did not change the early reflexive eye closure of the same participants (0.84 against 0.78).

Criterion (claims file): a paired t-test of per-participant mean early closure (table S3 calls it the sum over 0.1 to 0.2 s) under saline against ketamine; reproduced when p >= 0.05, the difference reported either way. Expected 0.84 (0.02) and 0.78 (0.08); table S3 t = 1.622, p = 0.2032. The script takes the mean over the samples, which gives values on the table's scale; the sum is shown too.

In [18]:
a = np.array([H[s][0]["early"] for s in H]); b = np.array([H[s][1]["early"] for s in H])
t, p = scipy.stats.ttest_rel(a, b)
print("saline per participant:", a.round(3), "ketamine:", b.round(3))
print(f"mean over 0.1-0.2 s: saline {a.mean():.3f} +- {scipy.stats.sem(a):.3f} (within tol {abs(a.mean() - 0.84) <= 0.02}), "
      f"ketamine {b.mean():.3f} +- {scipy.stats.sem(b):.3f} (within tol {abs(b.mean() - 0.78) <= 0.08}); "
      f"difference {a.mean() - b.mean():.3f}; paired t = {t:.3f}, p = {p:.4f}")
sa = np.array([H[s][0]["early_sum"] for s in H]); sb = np.array([H[s][1]["early_sum"] for s in H])
print(f"sum over the {H[list(H)[0]][0]['n_early_samples']} samples: saline {sa.mean():.3f}, ketamine {sb.mean():.3f}, {scipy.stats.ttest_rel(sa, sb)}")
print(f"total runtime {time.time() - started:.0f} s")
saline per participant: [0.808 0.834 0.876 0.838] ketamine: [0.735 0.685 0.812 0.876]
mean over 0.1-0.2 s: saline 0.839 +- 0.014 (within tol True), ketamine 0.777 +- 0.042 (within tol True); difference 0.062; paired t = 1.622, p = 0.2032
sum over the 20 samples: saline 16.781, ketamine 15.534, TtestResult(statistic=np.float64(1.622318486587699), pvalue=np.float64(0.20318914592221227), df=np.int64(3))
total runtime 186 s

Result: reproduced. Early closure is 0.839 under saline and 0.777 under ketamine (difference 0.062), within tolerance, and the paper's paired t-test does not find a difference (t = 1.622, p = 0.2032).

Summary¶

Claim Outcome
fig4b-recorded-eye-closure-ketamine partially reproduced
fig5b-recorded-accumulation partially reproduced
figs34c-tone-no-eye-closure reproduced
fig1k-mouse-ketamine-late-closure reproduced
fig1l-dissociatives-preinfusion reproduced
figs2bc-mouse-early-late reproduced
fig1m-accumulation-persistence partially reproduced
figs2g-scaling-puff-number reproduced
figs2h-dose-response reproduced
fig1p-anesthesia-abolishes-both reproduced
figs5-other-drugs reproduced
figs6-vpm-muscimol reproduced
fig5hi-behavior-model-doses reproduced
fig1e-human-ketamine-late-closure reproduced
figs1b-human-early-closure-unchanged reproduced
link-001347-condition-labels reproduced
link-001347-recorded-sessions partially reproduced

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: the conditions, doses and pressures of the behavior files come from the session lists of the paper's code (Zenodo doi:10.5281/zenodo.14920570), which also gives the trial selections and windows; the recorded-mouse eye closure covers 10 of the 13 sessions; the tone onsets are placed on the video clock through the fit to the 001326 puff series.

What was found about DANDI:001347: the behavior-rig files hold only an eye closure trace and an experiment id, with no drug, dose, pressure or injection label; the human P50 files label trials only as pre and post; there are no lick data, participant reports or CADSS scores in any of its files (searched by dataset name in every file other than the behavior-rig mice and in one behavior-rig file, 66 files in all); three recorded sessions of DANDI:001326 have no eye-closure file, and the recorded-mouse files carry no tone events.