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