Kauvar, Richman, Liu et al. (2025): human eyepuff behaviour and dissociation, from DANDI:001347¶
Paper: Kauvar, Richman, Liu et al., "Conserved brain-wide emergence of emotional response from sensory experience in humans and mice", Science 388, eadt3971 (2025), doi:10.1126/science.adt3971.
Dataset: DANDI:001347, version 0.250528.0702. It is the only published version (28 May 2025, the day before the paper appeared); the paper cites the dandiset without a version, so the latest published version is pinned. Its content (207 assets, 16.0 GB) is the same size and asset count as the current draft.
Claims file: claims/001347/10.1126-science.adt3971.yaml, written from the version of record with
its Materials and Methods, figure legends and the Supplementary Materials (Table S3).
This notebook tests the claims on human eye closure and dissociation (CADSS). Other notebooks of the entry test the mouse behaviour (02), the iEEG spectrograms (03), the iEEG coding dimension (04) and the mouse Neuropixels data of DANDI:001326 (05).
| Claim | Tested here |
|---|---|
| p50-condition-labels-link (link) | tested |
| cadss-scores-link (link) | tested |
| fig1e-human-late-eye-closure-ketamine | tested (with the code's condition labels) |
| figs1b-human-early-eye-closure-unchanged | tested (with the code's condition labels) |
| figs1c-human-late-eye-closure-decrease | tested (with the code's condition labels) |
| figs1d-human-cadss-saline-ketamine | tested (CADSS from the code record) |
| fig1f-human-subjective-reports | blocked: the verbal reports are not in the dataset |
| figs8a-ieeg-inclusion-per-participant | tested |
| figs8b-ieeg-excluded-participants | tested |
| fig2c-ieeg-cohort-selective-affective-decrease | tested |
| fig2d-ieeg-cadss-reversible | tested (CADSS from the code record) |
| figs14g-cadss-correlates-with-affective-decrease | tested (CADSS from the code record) |
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import scipy.stats as st
import matplotlib.pyplot as plt
from statsmodels.stats.multitest import multipletests
import dandi_io
import human_eye as he
print("dandiset", dandi_io.DANDISET, "version", dandi_io.VERSION)
assets = dandi_io.list_assets()
print(len(assets), "assets in the pinned version")
dandiset 001347 version 0.250528.0702
207 assets in the pinned version
Data check¶
The non-iEEG files (sub-h*) hold two series of 40 eye-closure trials each, pre_trials_data and
post_trials_data, with a time axis time_reference/tt (seconds from the puff). The iEEG behaviour
files (sub-SD*_ses-puff_behavior.nwb) hold, per session, the continuous eye closure, the video frame
of each puff (events-*-on), the trial windows (by_trial_closure, 800 samples by trials), the
stored per-trial early, late and late/early values, the early and late windows, the frame rate, and
which_trials.
p50 = {}
rows = []
for sub in he.P50_SUBJECTS:
tt, pre, post = he.load_p50(sub)
p50[sub] = (tt, pre, post)
for name, x in [("pre", pre), ("post", post)]:
rows.append({"subject": sub, "series": name, "trials": x.shape[0], "samples": x.shape[1],
"t first (s)": tt[0], "t last (s)": round(tt[-1], 3),
"rate (Hz)": round(1 / np.median(np.diff(tt)), 3),
"min": round(np.nanmin(x), 3), "max": round(np.nanmax(x), 3),
"NaN": int(np.isnan(x).sum())})
print("Non-iEEG cohort (eye closure, fraction of the open eye):")
print(pd.DataFrame(rows).to_string(index=False))
Non-iEEG cohort (eye closure, fraction of the open eye): subject series trials samples t first (s) t last (s) rate (Hz) min max NaN h009 pre 40 800 -1.53 2.465 200.0 -0.055 0.976 0 h009 post 40 800 -1.53 2.465 200.0 -0.259 2.739 0 h011 pre 40 800 -1.53 2.465 200.0 -0.170 0.880 0 h011 post 40 800 -1.53 2.465 200.0 -0.089 0.844 0 h012 pre 40 800 -1.53 2.465 200.0 -0.139 0.932 0 h012 post 40 800 -1.53 2.465 200.0 -0.382 0.913 0 h013 pre 40 800 -1.53 2.465 200.0 -0.159 0.910 0 h013 post 40 800 -1.53 2.465 200.0 -0.086 0.941 0
beh = {sub: he.load_ieeg_behavior(sub) for sub in he.IEEG_SUBJECTS}
rows = []
for sub, b in beh.items():
for sess in he.SESSIONS:
s = b[sess]
rows.append({"subject": sub, "session": sess, "file session": s["file_session"],
"trials": s["trials"].shape[0], "samples": s["trials"].shape[1],
"puffs": len(s["events_on"]), "which_trials": len(b["which_trials"]),
"continuous (min)": round(len(s["eyeclosure"]) / b["fps"] / 60, 1),
"trace min": round(np.nanmin(s["trials"]), 2),
"trace max": round(np.nanmax(s["trials"]), 2)})
info = pd.DataFrame(rows)
print("iEEG cohort behaviour files:")
print(info.to_string(index=False))
print("windows stored in every file: early", {tuple(b["ref_w"]) for b in beh.values()},
"late", {tuple(b["aff_w"]) for b in beh.values()}, "fps", {b["fps"] for b in beh.values()})
iEEG cohort behaviour files:
subject session file session trials samples puffs which_trials continuous (min) trace min trace max
SD163 preinfusion preinfusion 40 800 40 39 6.2 -0.16 0.92
SD163 infusion infusion 40 800 40 39 6.1 -0.06 0.92
SD163 postinfusion postinfusion 40 800 40 39 6.4 -0.17 0.93
SD168 preinfusion baseline 40 800 40 24 13.3 -0.04 0.97
SD168 infusion infusion 40 800 40 24 6.9 -0.12 0.99
SD168 postinfusion postinfusion 40 800 40 24 6.0 -0.11 1.01
SD170 preinfusion preinfusion 40 800 40 39 6.5 -0.25 1.00
SD170 infusion infusion 40 800 40 39 6.4 -0.39 0.90
SD170 postinfusion postinfusion 40 800 40 39 6.3 -0.32 0.96
SD172 preinfusion baseline 40 800 40 39 6.1 -0.04 1.00
SD172 infusion infusion 40 800 40 39 6.1 -0.25 0.96
SD172 postinfusion postinfusion 40 800 40 39 6.4 -0.09 0.99
SD178 preinfusion preinfusion 32 800 32 27 5.6 -0.25 1.00
SD178 infusion infusion 32 800 32 27 5.1 -0.12 0.98
SD178 postinfusion postinfusion 32 800 32 27 5.8 -0.16 1.00
SD181 preinfusion preinfusion 40 800 40 30 7.2 -0.17 0.98
SD181 infusion infusion 40 800 40 30 7.2 -0.37 0.93
SD181 postinfusion postinfusion 40 800 40 30 6.6 -0.24 0.99
SD184 preinfusion preinfusion 40 800 40 30 6.9 -0.19 0.99
SD184 infusion infusion 40 800 40 30 6.0 -0.22 0.95
SD184 postinfusion postinfusion 40 800 40 30 6.7 -0.02 0.99
SD185 preinfusion preinfusion 40 800 40 31 6.2 -0.20 1.00
SD185 infusion infusion 32 800 32 31 5.6 -0.27 1.00
SD185 postinfusion postinfusion 40 800 40 31 7.4 -0.07 0.99
SD186 preinfusion preinfusion 40 800 40 12 6.0 -0.12 1.02
SD186 infusion infusion 32 800 32 12 11.7 -0.04 2.58
SD186 postinfusion postinfusion 32 800 32 12 5.3 -0.08 1.02
SD207 preinfusion preinfusion 40 800 40 11 11.4 -0.15 0.95
SD207 infusion infusion 14 800 14 11 1.4 -0.08 0.96
SD207 postinfusion postinfusion 40 800 40 11 6.8 -0.49 0.91
SD211 preinfusion preinfusion 40 800 40 39 6.4 -0.26 1.00
SD211 infusion infusion 40 800 40 39 6.0 -0.12 0.93
SD211 postinfusion postinfusion 40 800 40 39 6.6 -0.08 0.96
SD215 preinfusion preinfusion 40 800 40 39 6.0 -0.37 1.01
SD215 infusion infusion 40 800 40 39 6.8 -0.35 0.71
SD215 postinfusion postinfusion 40 800 40 39 6.0 -0.06 0.94
SD242 preinfusion baseline 40 800 40 39 6.1 -0.06 0.97
SD242 infusion infusion 40 800 40 39 8.4 -0.06 0.97
SD242 postinfusion postinfusion 40 800 40 39 6.7 -0.08 0.93
SD245 preinfusion baseline 40 800 40 39 6.8 -0.14 1.01
SD245 infusion infusion 40 800 40 39 6.1 -0.19 0.92
SD245 postinfusion postinfusion 40 800 40 39 6.0 -0.18 0.91
windows stored in every file: early {(np.float64(0.1), np.float64(0.2))} late {(np.float64(0.3), np.float64(0.8))} fps {200.0}
Checks of the stored values against the traces they come from. (1) Each trial window of
by_trial_closure should be the continuous trace from 400 samples before to 400 after the puff
frame. (2) The stored early and late values should be the window means of the trial windows, and the
stored late/early their ratio. (3) The puff frames should be spaced as the Methods describe: blocks of
8 puffs 3 to 8 s apart, 35 s between blocks.
rows = []
for sub, b in beh.items():
for sess in he.SESSIONS:
s = b[sess]
ec, ev, tr = s["eyeclosure"], s["events_on"], s["trials"]
ok = [i for i, e in enumerate(ev) if e - 400 >= 0 and e + 400 <= len(ec)]
d_win = max(np.nanmax(np.abs(ec[ev[i] - 400:ev[i] + 400] - tr[i])) for i in ok)
early = he.window_mean(tr, he.EARLY_W)
late = he.window_mean(tr, he.LATE_W)
gaps = np.diff(ev) / b["fps"]
rows.append({"subject": sub, "session": sess, "windows checked": len(ok),
"max |window - trace|": d_win,
"max |early - stored|": np.nanmax(np.abs(early - s["reflexive"])),
"max |late - stored|": np.nanmax(np.abs(late - s["affective"])),
"max |ratio - stored|": np.nanmax(np.abs(late / early - s["affective_normed"])),
"puff gap min (s)": round(gaps.min(), 2), "median": round(np.median(gaps), 2),
"max": round(gaps.max(), 2), "gaps > 20 s": int((gaps > 20).sum())})
chk = pd.DataFrame(rows)
print(chk.to_string(index=False))
subject session windows checked max |window - trace| max |early - stored| max |late - stored| max |ratio - stored| puff gap min (s) median max gaps > 20 s SD163 preinfusion 40 0.0 2.220446e-16 4.440892e-16 7.771561e-16 3.52 5.54 36.53 4 SD163 infusion 40 0.0 2.220446e-16 3.330669e-16 6.661338e-16 3.51 5.53 36.54 4 SD163 postinfusion 40 0.0 2.220446e-16 4.440892e-16 8.881784e-16 3.52 5.54 36.56 4 SD168 preinfusion 40 0.0 3.330669e-16 7.771561e-16 1.332268e-15 3.50 5.52 36.52 4 SD168 infusion 40 0.0 2.220446e-16 8.881784e-16 8.881784e-16 3.50 5.52 36.52 4 SD168 postinfusion 40 0.0 2.220446e-16 7.771561e-16 7.771561e-16 3.50 5.52 36.52 4 SD170 preinfusion 40 0.0 2.220446e-16 3.330669e-16 3.330669e-16 3.50 5.52 36.52 4 SD170 infusion 40 0.0 2.220446e-16 3.330669e-16 4.440892e-16 3.50 5.52 36.52 4 SD170 postinfusion 40 0.0 2.220446e-16 1.665335e-16 1.665335e-16 3.50 5.52 36.53 4 SD172 preinfusion 40 0.0 2.220446e-16 3.885781e-16 5.551115e-16 3.50 5.52 36.52 4 SD172 infusion 40 0.0 2.220446e-16 2.775558e-16 3.885781e-16 3.33 5.52 36.52 4 SD172 postinfusion 40 0.0 3.330669e-16 5.551115e-16 6.661338e-16 3.50 5.52 36.52 4 SD178 preinfusion 32 0.0 2.220446e-16 6.661338e-16 8.881784e-16 3.50 5.52 36.52 3 SD178 infusion 32 0.0 3.330669e-16 9.992007e-16 1.332268e-15 3.50 5.51 36.52 3 SD178 postinfusion 32 0.0 3.330669e-16 6.661338e-16 6.661338e-16 3.50 5.52 36.51 3 SD181 preinfusion 40 0.0 2.220446e-16 6.661338e-16 1.421085e-14 3.50 5.52 36.53 4 SD181 infusion 40 0.0 4.440892e-16 4.440892e-16 6.661338e-16 3.50 5.51 36.52 4 SD181 postinfusion 40 0.0 2.220446e-16 4.440892e-16 5.551115e-16 3.50 5.52 36.52 4 SD184 preinfusion 40 0.0 2.220446e-16 3.330669e-16 4.440892e-16 3.50 5.52 36.52 4 SD184 infusion 40 0.0 3.330669e-16 4.440892e-16 3.885781e-16 3.51 5.52 36.52 4 SD184 postinfusion 40 0.0 2.220446e-16 4.440892e-16 5.551115e-16 3.50 5.51 36.52 4 SD185 preinfusion 40 0.0 4.440892e-16 5.551115e-16 4.440892e-16 3.51 5.52 36.52 4 SD185 infusion 32 0.0 3.330669e-16 3.885781e-16 4.440892e-16 3.50 5.51 36.52 3 SD185 postinfusion 40 0.0 2.220446e-16 3.330669e-16 4.440892e-16 3.50 5.52 36.52 4 SD186 preinfusion 40 0.0 3.330669e-16 3.330669e-16 5.551115e-16 3.50 5.52 36.52 4 SD186 infusion 32 0.0 3.330669e-16 4.440892e-16 7.771561e-16 3.50 5.52 36.52 3 SD186 postinfusion 32 0.0 3.330669e-16 4.440892e-16 8.881784e-16 3.50 5.52 36.52 3 SD207 preinfusion 40 0.0 3.330669e-16 2.775558e-16 3.330669e-16 3.51 5.52 36.52 4 SD207 infusion 14 0.0 2.220446e-16 1.387779e-16 1.665335e-16 1.40 4.54 9.31 0 SD207 postinfusion 40 0.0 2.220446e-16 2.220446e-16 4.973799e-14 3.51 5.51 36.52 4 SD211 preinfusion 40 0.0 2.220446e-16 2.220446e-16 3.552714e-15 3.50 5.52 36.52 4 SD211 infusion 40 0.0 2.220446e-16 1.110223e-16 1.421085e-14 3.51 5.52 36.52 4 SD211 postinfusion 40 0.0 3.330669e-16 1.665335e-16 2.220446e-16 3.51 5.52 36.52 4 SD215 preinfusion 40 0.0 2.220446e-16 6.661338e-16 8.881784e-16 3.50 5.52 36.52 4 SD215 infusion 40 0.0 1.110223e-16 2.220446e-16 5.551115e-16 3.51 5.52 36.52 4 SD215 postinfusion 40 0.0 2.220446e-16 6.661338e-16 1.065814e-14 3.50 5.52 36.52 4 SD242 preinfusion 40 0.0 2.220446e-16 8.881784e-16 2.842171e-14 3.51 5.52 36.52 4 SD242 infusion 40 0.0 2.220446e-16 5.551115e-16 7.771561e-16 3.51 5.52 36.52 4 SD242 postinfusion 40 0.0 2.220446e-16 4.440892e-16 4.440892e-16 3.50 5.52 36.52 4 SD245 preinfusion 40 0.0 3.330669e-16 6.661338e-16 9.992007e-16 3.50 5.52 36.52 4 SD245 infusion 40 0.0 3.330669e-16 3.330669e-16 4.996004e-16 3.50 5.52 36.52 4 SD245 postinfusion 40 0.0 2.220446e-16 4.440892e-16 4.440892e-16 3.51 5.52 36.52 4
The trial windows equal the continuous trace around each puff frame, and the stored per-trial values equal the window means and their ratio to rounding, in every participant and session, so the stored values and the traces agree (the table above). The shortest gap between puffs is 3.3 to 3.5 s and the median about 5.5 s, with pauses of about 36.5 s between blocks (4 pauses in 40-puff sessions, 3 in 32-puff sessions), consistent with the Methods (3 to 8 s between puffs, 35 s between blocks). Some sessions hold fewer than 40 puffs (SD178, SD185 and SD186 have 32, SD207's infusion session 14). Some trace values exceed 1, which a fraction of eye closure cannot (h009 ketamine series up to 2.74, SD186 infusion up to 2.58), and are tracking artefacts; they are kept, as in the paper's code. Whether the puff frames are the true puff times cannot be shown from the eye alone; the iEEG coding-dimension notebook (04) shows the puff times share the LFP clock.
which_trials holds 0-based trial indices; in most files it starts at 1, so the first trial is
left out, as the Methods say ("the first trial was excluded").
print({sub: (len(b["which_trials"]), int(b["which_trials"].min()), int(b["which_trials"].max()))
for sub, b in beh.items()})
{'SD163': (39, 1, 39), 'SD168': (24, 1, 39), 'SD170': (39, 1, 39), 'SD172': (39, 1, 39), 'SD178': (27, 2, 31), 'SD181': (30, 1, 31), 'SD184': (30, 1, 31), 'SD185': (31, 1, 31), 'SD186': (12, 1, 13), 'SD207': (11, 1, 13), 'SD211': (39, 1, 39), 'SD215': (39, 1, 39), 'SD242': (39, 1, 39), 'SD245': (39, 1, 39)}
p50-condition-labels-link: which non-iEEG series is saline¶
The non-iEEG files do not say which of the two series is the saline visit. The paper's code
(Zenodo 14920570, human_ieeg/scripts/analyze_airpuff_p50.py) reads pre as saline and post as
ketamine (treatment saline for pre_trials, highdose for post_trials), and lists the eight
sessions by participant with their treatment and CADSS. The link holds when the code's participants
are the dataset's, each with both treatments, and the series have the trial counts the paper
reports (40, of which 38 are used). The meaning of pre and post itself cannot be checked against
anything in the data; the visit order was crossed over (the code's session names put saline first for
h009, h012 and h013 and ketamine first for h011), so pre does not mean the first visit.
subjects, cadss_p50 = he.p50_code_labels()
code_tab = pd.DataFrame([{"session": s, "participant": s.split("_")[0], "treatment": t,
"visit": int(s.split("_")[1]), "CADSS": cadss_p50[s]} for s, t in subjects])
print(code_tab.to_string(index=False))
print("participants in the code:", sorted(code_tab.participant.unique()))
print("participants in the dataset:", sorted(p50))
print("each with one saline and one ketamine session:",
all(sorted(g.treatment) == ["highdose", "saline"] for _, g in code_tab.groupby("participant")))
first = code_tab.sort_values("visit").groupby("participant").first().treatment
print("treatment of the first visit:", first.to_dict())
print("trials per series in the dataset:", {s: (v[1].shape[0], v[2].shape[0]) for s, v in p50.items()})
session participant treatment visit CADSS
h009_1 h009 saline 1 0
h009_3 h009 highdose 3 13
h011_1_112836 h011 highdose 1 7
h011_2_111511 h011 saline 2 0
h012_1_105053 h012 saline 1 1
h012_2_111359trim h012 highdose 2 9
h013_1_110713 h013 saline 1 1
h013_3_115544 h013 highdose 3 10
participants in the code: ['h009', 'h011', 'h012', 'h013']
participants in the dataset: ['h009', 'h011', 'h012', 'h013']
each with one saline and one ketamine session: True
treatment of the first visit: {'h009': 'saline', 'h011': 'highdose', 'h012': 'saline', 'h013': 'saline'}
trials per series in the dataset: {'h009': (40, 40), 'h011': (40, 40), 'h012': (40, 40), 'h013': (40, 40)}
Result: the link holds for identity and counts. The four participants of the code's list (h009, h011, h012, h013) are the four in the dataset, each with one saline and one ketamine session, and every series has 40 trials. Which series is saline rests on the code alone: the files do not label it, and the first visit was saline for three participants and ketamine for one, so the series names do not encode the visit order either.
cadss-scores-link: CADSS scores of the code record¶
The CADSS scores are not in the dataset. The code record holds the iEEG cohort's item scores per
participant and time point (data/cadss_across_pts.csv). The link holds when every participant with
behaviour in the dataset is in the table, with a total before ketamine, during infusion and at 110 min.
cad = he.load_cadss()
print(cad.drop(columns="columns").to_string())
missing = [s for s in he.IEEG_SUBJECTS if s not in cad.index]
print("dataset participants missing from the table:", missing)
print("table participants without behaviour in the dataset:", [p for p in cad.index if p not in he.IEEG_SUBJECTS])
have = cad.loc[[s for s in he.IEEG_SUBJECTS if s in cad.index], ["pre", "infusion", "post"]]
print("dataset participants with all three totals:", int(have.notna().all(axis=1).sum()), "of", len(he.IEEG_SUBJECTS))
print("SD185 columns:", cad.loc["SD185", "columns"])
pre infusion post retro pt SD140 0 13.0 0.0 NaN SD152 3 22.0 7.0 NaN SD157 2 22.0 3.0 NaN SD160 0 21.0 0.0 NaN SD163 0 1.0 0.0 NaN SD168 0 9.0 3.0 NaN SD169 0 32.0 6.0 NaN SD170 2 6.0 3.0 NaN SD172 0 30.0 0.0 NaN SD178 0 2.0 0.0 15.0 SD181 0 10.0 0.0 NaN SD184 0 38.0 5.0 54.0 SD185 0 0.0 0.0 10.0 SD186 1 33.0 2.0 20.0 SD191 0 6.0 NaN 14.0 SD195 0 4.0 0.0 14.0 SD207 0 4.0 0.0 4.0 SD211 3 26.0 0.0 31.0 SD215 1 23.0 0.0 27.0 SD242 0 1.0 0.0 1.0 SD245 2 39.0 1.0 54.0 dataset participants missing from the table: [] table participants without behaviour in the dataset: ['SD140', 'SD152', 'SD157', 'SD160', 'SD169', 'SD191', 'SD195'] dataset participants with all three totals: 14 of 14 SD185 columns: Before Ket, 12 mins, 33 mins, (retro) 50 mins, 110 mins, Next Day
Result: the link holds. All 14 iEEG participants with behaviour in the dataset are in the code record's CADSS table, each with a total before ketamine, during infusion and at 110 min (the printed count); the table also holds 7 participants without behaviour in the dataset. The match is by the participant identifier, which both use. Nothing in the dataset can be compared with the scores.
fig1e-human-late-eye-closure-ketamine¶
In the non-iEEG human cohort, late eye closure normalised by early eye closure is lower during ketamine than during saline (0.71 against 0.43, 4 participants).
Method H-EYE (claims file): per participant and series, trials 3 to 40, late (0.3 to 0.8 s) and early
(0.1 to 0.2 s) means; the late/early value is the mean late over the mean early (Open B, reading a).
Paired t-test across the 4 participants. The readings of Open A (first two trials or the first one
excluded) and Open B (ratio of means or mean of ratios) are all shown. Saline is the pre series, by
the code's label (link above). The eye-closure traces in the files are already normalised by the
open-eye size; the normalisation (median eye size over the first 2 s of the first trial window,
claims file H-EYE) cannot be repeated, as the eye sizes are not in these files.
readings = {"A(a) trials 3-40": np.arange(2, 40), "A(b) trials 2-40": np.arange(1, 40)}
p50_rows = []
for sub, (tt, pre, post) in p50.items():
for rname, idx in readings.items():
for ratio in ["ratio_of_means", "mean_of_ratios"]:
for cond, x in [("saline", pre), ("ketamine", post)]:
e, l, r = he.p50_values(tt, x, idx, ratio)
p50_rows.append({"subject": sub, "trials": rname, "ratio": ratio, "condition": cond,
"early": e, "late": l, "late/early": r, "n trials": len(idx)})
p50_df = pd.DataFrame(p50_rows)
def paired(df, value, trials, ratio="ratio_of_means"):
d = df[(df.trials == trials) & (df.ratio == ratio)].pivot(index="subject", columns="condition", values=value)
t = st.ttest_rel(d.saline, d.ketamine)
return d, t
out = []
for trials in readings:
for ratio in ["ratio_of_means", "mean_of_ratios"]:
d, t = paired(p50_df, "late/early", trials, ratio)
out.append({"trials": trials, "ratio": ratio, "saline mean": d.saline.mean(), "saline sem": st.sem(d.saline),
"ketamine mean": d.ketamine.mean(), "ketamine sem": st.sem(d.ketamine),
"t": t.statistic, "p": t.pvalue, "participants lower on ketamine": int((d.ketamine < d.saline).sum())})
print(pd.DataFrame(out).round(4).to_string(index=False))
d, t = paired(p50_df, "late/early", "A(a) trials 3-40")
print("\nPer participant, main reading (A(a), ratio of means):")
print(d.round(3).to_string())
print("expected: saline 0.71 +/- 0.08, ketamine 0.43 +/- 0.12; paper t = 3.703, p = 0.0342")
trials ratio saline mean saline sem ketamine mean ketamine sem t p participants lower on ketamine A(a) trials 3-40 ratio_of_means 0.7064 0.0843 0.4255 0.1213 3.7034 0.0342 4 A(a) trials 3-40 mean_of_ratios 0.7049 0.0843 0.4263 0.1211 3.6810 0.0347 4 A(b) trials 2-40 ratio_of_means 0.7043 0.0850 0.4240 0.1170 3.9121 0.0297 4 A(b) trials 2-40 mean_of_ratios 0.7029 0.0851 0.4244 0.1173 3.8777 0.0304 4 Per participant, main reading (A(a), ratio of means): condition ketamine saline subject h009 0.519 0.908 h011 0.065 0.497 h012 0.527 0.697 h013 0.591 0.724 expected: saline 0.71 +/- 0.08, ketamine 0.43 +/- 0.12; paper t = 3.703, p = 0.0342
fig, ax = plt.subplots(1, 4, figsize=(12, 2.6), sharey=True)
for a, (sub, (tt, pre, post)) in zip(ax, p50.items()):
a.plot(tt, pre[2:40].mean(0), label="saline (pre)")
a.plot(tt, post[2:40].mean(0), label="ketamine (post)")
a.axvspan(*he.EARLY_W, alpha=0.1); a.axvspan(*he.LATE_W, alpha=0.1, color="r")
a.set_title(sub); a.set_xlabel("time from puff (s)")
ax[0].set_ylabel("eye closure"); ax[0].legend(fontsize=7)
plt.tight_layout(); plt.show()
print("plotted range of the mean traces:", round(min(min(v[1][2:40].mean(0).min(), v[2][2:40].mean(0).min()) for v in p50.values()), 3),
"to", round(max(max(v[1][2:40].mean(0).max(), v[2][2:40].mean(0).max()) for v in p50.values()), 3))
plotted range of the mean traces: -0.008 to 0.906
Result: reproduced. With trials 3 to 40 and the ratio of trial means, late/early is 0.706 on saline
and 0.426 on ketamine (both within tolerance), lower on ketamine in all 4 participants, paired t = 3.703,
p = 0.0342, the values of Table S3. The other three readings also give ketamine lower with p < 0.05
(table above). The result rests on the code's label of the pre series as saline.
figs1b-human-early-eye-closure-unchanged¶
In the non-iEEG cohort, early (reflexive) eye closure does not differ between saline and ketamine (0.84 against 0.78).
A claim of absence, judged on the paper's test: paired t-test across the 4 participants of the mean early eye closure, trials 3 to 40.
d, t = paired(p50_df, "early", "A(a) trials 3-40")
print(d.round(3).to_string())
print(f"saline {d.saline.mean():.3f} (sem {st.sem(d.saline):.3f}), ketamine {d.ketamine.mean():.3f} (sem {st.sem(d.ketamine):.3f})")
print(f"paired t = {t.statistic:.3f}, p = {t.pvalue:.4f}; difference {d.saline.mean() - d.ketamine.mean():.3f}, "
f"Cohen dz = {(d.saline - d.ketamine).mean() / (d.saline - d.ketamine).std(ddof=1):.2f}")
d2, t2 = paired(p50_df, "early", "A(b) trials 2-40")
print(f"variant, trials 2-40: saline {d2.saline.mean():.3f}, ketamine {d2.ketamine.mean():.3f}, t = {t2.statistic:.3f}, p = {t2.pvalue:.4f}")
print("expected: saline 0.84 +/- 0.01, ketamine 0.78 +/- 0.04; paper t = 1.622, p = 0.2032")
condition ketamine saline subject h009 0.735 0.808 h011 0.685 0.834 h012 0.812 0.876 h013 0.876 0.838 saline 0.839 (sem 0.014), ketamine 0.777 (sem 0.042) paired t = 1.622, p = 0.2032; difference 0.062, Cohen dz = 0.81 variant, trials 2-40: saline 0.839, ketamine 0.778, t = 1.617, p = 0.2042 expected: saline 0.84 +/- 0.01, ketamine 0.78 +/- 0.04; paper t = 1.622, p = 0.2032
Result: reproduced. Early eye closure is 0.839 on saline and 0.777 on ketamine, paired t = 1.622, p = 0.2032, not significant, as in Table S3; with trials 2 to 40 the test is also not significant.
figs1c-human-late-eye-closure-decrease¶
In the non-iEEG cohort, late (affective) eye closure alone is lower during ketamine than during saline (0.59 against 0.34).
for trials in readings:
d, t = paired(p50_df, "late", trials)
print(f"{trials}: saline {d.saline.mean():.3f} (sem {st.sem(d.saline):.3f}), ketamine {d.ketamine.mean():.3f} "
f"(sem {st.sem(d.ketamine):.3f}), t = {t.statistic:.3f}, p = {t.pvalue:.4f}, lower in {(d.ketamine < d.saline).sum()} of 4")
print("expected: saline 0.59 +/- 0.07, ketamine 0.34 +/- 0.10; paper t = 3.653, p = 0.0354")
A(a) trials 3-40: saline 0.591 (sem 0.066), ketamine 0.343 (sem 0.103), t = 3.653, p = 0.0354, lower in 4 of 4 A(b) trials 2-40: saline 0.590 (sem 0.067), ketamine 0.341 (sem 0.100), t = 3.813, p = 0.0317, lower in 4 of 4 expected: saline 0.59 +/- 0.07, ketamine 0.34 +/- 0.10; paper t = 3.653, p = 0.0354
Result: reproduced. Late eye closure is 0.591 on saline and 0.343 on ketamine with trials 3 to 40, paired t = 3.653, p = 0.0354, lower in all 4 participants; trials 2 to 40 give the same conclusion.
figs1d-human-cadss-saline-ketamine¶
In the non-iEEG cohort, dissociation (CADSS) is higher with ketamine than with saline (9.75 against 0.50).
The scores are not in the dataset; the criterion allows the code's constants
(analyze_airpuff_p50.py, the cadss dictionary), read here from the code record.
cd = code_tab.pivot(index="participant", columns="treatment", values="CADSS")
t = st.ttest_rel(cd.saline, cd.highdose)
print(cd.to_string())
print(f"saline {cd.saline.mean():.2f} (sem {st.sem(cd.saline):.2f}), ketamine {cd.highdose.mean():.2f} (sem {st.sem(cd.highdose):.2f})")
print(f"paired t = {t.statistic:.3f}, p = {t.pvalue:.4f}")
print("expected: saline 0.50 +/- 0.29, ketamine 9.75 +/- 1.25; paper t = 7.034, p = 0.006")
treatment highdose saline participant h009 13 0 h011 7 0 h012 9 1 h013 10 1 saline 0.50 (sem 0.29), ketamine 9.75 (sem 1.25) paired t = -7.034, p = 0.0059 expected: saline 0.50 +/- 0.29, ketamine 9.75 +/- 1.25; paper t = 7.034, p = 0.006
Result: reproduced, on the code record's scores. CADSS is 0.50 on saline and 9.75 on ketamine, paired t = -7.034 (saline minus ketamine), p = 0.0059. These are the code's own numbers; nothing in the dataset bears on them.
fig1f-human-subjective-reports¶
Blocked. The participants' verbal reports are not in the dataset: the four sub-h* files hold only
the eye-closure trials and their time axis (listed in the data check), and no other asset holds text
from the non-iEEG cohort.
figs8a-ieeg-inclusion-per-participant¶
Seven iEEG participants met the behavioural inclusion criteria, each showing a significant decrease of late/early eye closure from preinfusion to ketamine and a significant recovery from ketamine to postinfusion.
Per participant, the per-trial late/early of the trials in which_trials, recomputed from the trial
windows (they equal the stored values, data check). Trials are paired across sessions by position.
Paired t-tests preinfusion against ketamine and ketamine against postinfusion, Benjamini-Hochberg over
the two (claims file; code airpuff_utils, Annotator with t-test_paired and fdr_bh). NaN trials (where
the eye was not tracked) are dropped pairwise.
def inclusion_tests(sub):
b = beh[sub]
v = {s: he.per_trial(b, s)["late_over_early"].to_numpy() for s in he.SESSIONS}
res = {"subject": sub, "n which_trials": len(b["which_trials"])}
ps = []
for a, c, lab in [("preinfusion", "infusion", "pre"), ("postinfusion", "infusion", "post")]:
ok = ~np.isnan(v[a]) & ~np.isnan(v[c])
t = st.ttest_rel(v[a][ok], v[c][ok])
res[f"t {lab}-ket"] = t.statistic
res[f"n {lab}-ket"] = int(ok.sum())
ps.append(t.pvalue)
pc = multipletests(ps, method="fdr_bh")[1]
res["p pre-ket (BH)"], res["p post-ket (BH)"] = pc
for s in he.SESSIONS:
res[f"mean {s[:4]}"] = np.nanmean(v[s])
res[f"sem {s[:4]}"] = st.sem(v[s], nan_policy="omit")
res["decrease"] = (res["t pre-ket"] > 0) and (pc[0] < 0.05)
res["recovery"] = (res["t post-ket"] > 0) and (pc[1] < 0.05)
res["meets both"] = res["decrease"] and res["recovery"]
return res
inc = pd.DataFrame([inclusion_tests(s) for s in he.IEEG_SUBJECTS]).set_index("subject")
pd.set_option("display.width", 250)
print(inc.loc[he.INCLUDED].round(4).to_string())
n which_trials t pre-ket n pre-ket t post-ket n post-ket p pre-ket (BH) p post-ket (BH) mean prei sem prei mean infu sem infu mean post sem post decrease recovery meets both subject SD168 24 5.8087 24 3.2763 24 0.0000 0.0033 0.9397 0.0133 0.8321 0.0186 0.8968 0.0102 True True True SD172 39 5.6281 39 11.7475 39 0.0000 0.0000 0.5323 0.0298 0.3498 0.0177 0.7526 0.0280 True True True SD181 30 10.5245 30 5.8804 30 0.0000 0.0000 0.8481 0.0422 0.2112 0.0413 0.5332 0.0381 True True True SD185 31 4.7462 31 6.8624 31 0.0000 0.0000 0.6035 0.0311 0.4520 0.0275 0.6684 0.0205 True True True SD186 12 2.7912 12 2.6265 12 0.0236 0.0236 0.5417 0.0474 0.3532 0.0360 0.5538 0.0573 True True True SD211 39 7.1858 39 10.4531 39 0.0000 0.0000 0.3119 0.0193 0.1534 0.0151 0.3587 0.0135 True True True SD245 39 5.2881 39 2.9258 39 0.0000 0.0058 0.5474 0.0155 0.4022 0.0246 0.5046 0.0222 True True True
import yaml, pathlib
claims = yaml.safe_load(pathlib.Path("../../claims/001347/10.1126-science.adt3971.yaml").read_text())["claims"]
exp8a = {e["quantity"]: (e["value"], e["tolerance"]) for e in next(c for c in claims if c["id"] == "figs8a-ieeg-inclusion-per-participant")["expected"]}
rows = []
for q, (val, tol) in exp8a.items():
sub, _, sess = q.split(" ")
col = {"preinfusion": "mean prei", "ketamine": "mean infu", "postinfusion": "mean post"}[sess]
m = inc.loc[sub, col]
rows.append({"quantity": q, "expected": val, "tolerance": tol, "measured": round(m, 3), "within": abs(m - val) <= tol + 1e-9})
cmp8a = pd.DataFrame(rows)
print(cmp8a.to_string(index=False))
print("means within tolerance:", int(cmp8a.within.sum()), "of", len(cmp8a))
print("participants meeting both tests:", int(inc.loc[he.INCLUDED, "meets both"].sum()), "of 7")
print("trial counts (pairs) for the 7:", inc.loc[he.INCLUDED, "n pre-ket"].to_dict())
quantity expected tolerance measured within
SD168 late/early preinfusion 0.94 0.01 0.940 True
SD168 late/early ketamine 0.83 0.02 0.832 True
SD168 late/early postinfusion 0.90 0.01 0.897 True
SD172 late/early preinfusion 0.53 0.03 0.532 True
SD172 late/early ketamine 0.35 0.02 0.350 True
SD172 late/early postinfusion 0.75 0.03 0.753 True
SD181 late/early preinfusion 0.85 0.04 0.848 True
SD181 late/early ketamine 0.21 0.04 0.211 True
SD181 late/early postinfusion 0.53 0.04 0.533 True
SD185 late/early preinfusion 0.60 0.03 0.604 True
SD185 late/early ketamine 0.45 0.03 0.452 True
SD185 late/early postinfusion 0.67 0.02 0.668 True
SD186 late/early preinfusion 0.54 0.05 0.542 True
SD186 late/early ketamine 0.35 0.04 0.353 True
SD186 late/early postinfusion 0.55 0.06 0.554 True
SD211 late/early preinfusion 0.31 0.02 0.312 True
SD211 late/early ketamine 0.15 0.02 0.153 True
SD211 late/early postinfusion 0.36 0.01 0.359 True
SD245 late/early preinfusion 0.56 0.02 0.547 True
SD245 late/early ketamine 0.40 0.02 0.402 True
SD245 late/early postinfusion 0.50 0.02 0.505 True
means within tolerance: 21 of 21
participants meeting both tests: 7 of 7
trial counts (pairs) for the 7: {'SD168': 24, 'SD172': 39, 'SD181': 30, 'SD185': 31, 'SD186': 12, 'SD211': 39, 'SD245': 39}
Result: reproduced. All 7 participants show a significant decrease from preinfusion to ketamine and a significant recovery after it (14 of 14 corrected tests p < 0.05 in the stated direction), with the trial counts of Table S3 (24, 39, 30, 31, 12, 39 and 39), and 21 of 21 session means within tolerance (tables above). Thirteen of the 14 t values equal Table S3's to the printed digits; SD245's preinfusion against ketamine test gives t = 5.288 where the table has 5.2553.
figs8b-ieeg-excluded-participants¶
The other seven iEEG eyepuff participants did not meet the inclusion criteria, each lacking a significant ketamine decrease or a significant recovery.
excl = [s for s in he.IEEG_SUBJECTS if s not in he.INCLUDED]
print(inc.loc[excl, ["n which_trials", "t pre-ket", "p pre-ket (BH)", "t post-ket", "p post-ket (BH)",
"mean prei", "mean infu", "mean post", "decrease", "recovery", "meets both"]].round(4).to_string())
print("excluded participants meeting both:", int(inc.loc[excl, "meets both"].sum()), "of", len(excl))
print("participants meeting both, over all 14:", list(inc.index[inc["meets both"]]))
n which_trials t pre-ket p pre-ket (BH) t post-ket p post-ket (BH) mean prei mean infu mean post decrease recovery meets both subject SD163 39 -0.3794 0.7065 1.7575 0.1738 0.6664 0.6786 0.7761 False False False SD170 39 0.1429 0.8871 -1.1008 0.5558 0.1266 0.1207 0.0489 False False False SD178 27 -1.7376 0.0941 -3.6936 0.0021 0.9330 0.9758 0.8988 False False False SD184 30 -0.9389 0.3555 2.4481 0.0413 0.4019 0.4277 0.5015 False True False SD207 11 4.8338 0.0014 -2.1669 0.0555 0.5165 0.2715 0.1720 True False False SD215 39 1.2251 0.2281 2.3802 0.0448 0.3305 0.2075 0.4546 False True False SD242 39 4.8557 0.0000 -0.2170 0.8294 0.7469 0.6285 0.6236 True False False excluded participants meeting both: 0 of 7 participants meeting both, over all 14: ['SD168', 'SD172', 'SD181', 'SD185', 'SD186', 'SD211', 'SD245']
Result: reproduced. None of SD163, SD170, SD178, SD184, SD207, SD215 and SD242 meets both criteria, and the participants that do, over all 14, are the 7 of figs8a (printed list).
fig2c-ieeg-cohort-selective-affective-decrease¶
In the iEEG cohort, ketamine reduces the late (affective) phase of the eyepuff eye closure while the early reflexive phase remains.
Fig. 2C is the mean over the 7 participants of each participant's median trace over the trials of
which_trials (code, overlay_sessions_eye_closure_traces_by_pt). Early and late means are taken on
these median traces. The code plots them on a time axis shifted by 0.05 s (origin_shift_s); the
early and late means are shown with the windows unshifted (the stored windows) and shifted 0.05 s
later. The paired t-test of early eye closure, ketamine against preinfusion, is the criterion's.
rows = []
traces = {}
for sub in he.INCLUDED:
b = beh[sub]
for sess in he.SESSIONS:
med = np.nanmedian(b[sess]["trials"][b["which_trials"]], axis=0)[None, :]
traces[(sub, sess)] = med[0]
for shift in [0.0, 0.05]:
rows.append({"subject": sub, "session": sess, "shift": shift,
"early": he.window_mean(med, he.EARLY_W, shift)[0],
"late": he.window_mean(med, he.LATE_W, shift)[0]})
c2 = pd.DataFrame(rows)
for shift in [0.0, 0.05]:
d = c2[c2["shift"] == shift]
m = d.groupby("session")[["early", "late"]].agg(["mean", "sem"]).loc[he.SESSIONS]
print(f"window shift {shift} s, mean (sem) over 7 participants:")
print(m.round(3).to_string())
e = d.pivot(index="subject", columns="session", values="early")
l = d.pivot(index="subject", columns="session", values="late")
te = st.ttest_rel(e.preinfusion, e.infusion)
print(f" early, pre against ketamine: paired t = {te.statistic:.3f}, p = {te.pvalue:.4f} (positive t = ketamine lower)")
for other in ["preinfusion", "postinfusion"]:
tl = st.ttest_rel(l[other], l.infusion)
print(f" late, {other} against ketamine: t = {tl.statistic:.3f}, p = {tl.pvalue:.4f}; ketamine lower in {(l.infusion < l[other]).sum()} of 7")
print()
print("per participant (no shift):")
print(c2[c2["shift"] == 0].pivot(index="subject", columns="session", values=["early", "late"]).round(3).to_string())
window shift 0.0 s, mean (sem) over 7 participants:
early late
mean sem mean sem
session
preinfusion 0.877 0.010 0.541 0.082
infusion 0.850 0.024 0.306 0.087
postinfusion 0.875 0.015 0.518 0.082
early, pre against ketamine: paired t = 1.318, p = 0.2356 (positive t = ketamine lower)
late, preinfusion against ketamine: t = 2.908, p = 0.0271; ketamine lower in 7 of 7
late, postinfusion against ketamine: t = 3.447, p = 0.0137; ketamine lower in 7 of 7
window shift 0.05 s, mean (sem) over 7 participants:
early late
mean sem mean sem
session
preinfusion 0.862 0.015 0.523 0.083
infusion 0.828 0.031 0.284 0.086
postinfusion 0.849 0.015 0.503 0.079
early, pre against ketamine: paired t = 1.405, p = 0.2096 (positive t = ketamine lower)
late, preinfusion against ketamine: t = 2.802, p = 0.0311; ketamine lower in 7 of 7
late, postinfusion against ketamine: t = 3.712, p = 0.0099; ketamine lower in 7 of 7
per participant (no shift):
early late
session infusion postinfusion preinfusion infusion postinfusion preinfusion
subject
SD168 0.923 0.910 0.858 0.784 0.821 0.830
SD172 0.830 0.910 0.875 0.249 0.768 0.377
SD181 0.864 0.861 0.869 0.102 0.393 0.795
SD185 0.933 0.858 0.934 0.367 0.557 0.607
SD186 0.769 0.923 0.868 0.249 0.499 0.474
SD211 0.782 0.822 0.855 0.122 0.227 0.239
SD245 0.852 0.838 0.880 0.268 0.362 0.461
tt = (np.arange(800) - 400) / he.FPS
fig, ax = plt.subplots(figsize=(5, 3))
allv = []
for sess in he.SESSIONS:
m = np.mean([traces[(s, sess)] for s in he.INCLUDED], axis=0)
allv.append(m)
ax.plot(tt, m, label=sess)
ax.set_xlim(-0.5, 1.5); ax.set_xlabel("time from puff (s)"); ax.set_ylabel("eye closure"); ax.legend()
plt.show()
sel = (tt >= -0.5) & (tt <= 1.5)
print("plotted range:", round(min(a[sel].min() for a in allv), 3), "to", round(max(a[sel].max() for a in allv), 3))
plotted range: 0.035 to 0.929
Result: reproduced. Across the 7 participants, mean late eye closure of the median traces is lower on ketamine than at preinfusion and at postinfusion, and early eye closure on ketamine is not significantly lower than at preinfusion by the paired t-test, with the windows unshifted and shifted by 0.05 s (numbers above).
fig2d-ieeg-cadss-reversible¶
In the 7 included iEEG participants, dissociation (CADSS) rises during ketamine and returns to baseline after infusion (0.86, 22.43, 0.86).
Scores from the code record (link above); infusion is the maximum over the time points up to 50 min,
with SD185's retrospective rating in place of its infusion rating (claims file; code
mean_cadss_by_sess). Paired t-tests with Benjamini-Hochberg over the two.
c7 = cad.loc[he.INCLUDED, ["pre", "infusion", "post", "retro"]].copy()
c7.loc["SD185", "infusion"] = c7.loc["SD185", "retro"]
print(c7.to_string())
t1 = st.ttest_rel(c7.pre, c7.infusion); t2 = st.ttest_rel(c7.post, c7.infusion)
pc = multipletests([t1.pvalue, t2.pvalue], method="fdr_bh")[1]
for col in ["pre", "infusion", "post"]:
print(f"{col}: mean {c7[col].mean():.2f}, sem {st.sem(c7[col]):.2f}")
print(f"pre against ketamine t = {t1.statistic:.3f}, corrected p = {pc[0]:.4f}; post against ketamine t = {t2.statistic:.3f}, corrected p = {pc[1]:.4f}")
c7b = cad.loc[he.INCLUDED, ["pre", "infusion", "post"]]
print(f"variant without the retrospective rating for SD185: infusion mean {c7b.infusion.mean():.2f}")
print("expected: 0.86 +/- 0.46, 22.43 +/- 4.75, 0.86 +/- 0.46; paper t = 4.807 and 4.512, p = 0.004 and 0.004")
pre infusion post retro pt SD168 0 9.0 3.0 NaN SD172 0 30.0 0.0 NaN SD181 0 10.0 0.0 NaN SD185 0 10.0 0.0 10.0 SD186 1 33.0 2.0 20.0 SD211 3 26.0 0.0 31.0 SD245 2 39.0 1.0 54.0 pre: mean 0.86, sem 0.46 infusion: mean 22.43, sem 4.75 post: mean 0.86, sem 0.46 pre against ketamine t = -4.807, corrected p = 0.0040; post against ketamine t = -4.513, corrected p = 0.0040 variant without the retrospective rating for SD185: infusion mean 21.00 expected: 0.86 +/- 0.46, 22.43 +/- 4.75, 0.86 +/- 0.46; paper t = 4.807 and 4.512, p = 0.004 and 0.004
Result: reproduced, on the code record's scores. CADSS is 0.86 before, 22.43 during and 0.86 after ketamine, with both corrected p = 0.004; these are the code record's numbers and nothing in the dataset bears on them.
figs14g-cadss-correlates-with-affective-decrease¶
Across participants, the ketamine increase in CADSS correlates with the ketamine reduction of normalised affective eye closure (Spearman 0.744, 10 participants).
Participants: the iEEG eyepuff participants with behaviour and CADSS (the dataset has no bolus
participant), excluding those whose median per-trial late/early at preinfusion is below 0.5 (claims file;
code load_cadss_and_behavior). CADSS change: infusion minus preinfusion total, SD185's retrospective
rating as its infusion value. Eye-closure change, reading (a), the code's: mean of the preinfusion and
postinfusion medians minus the infusion median; reading (b): preinfusion median minus infusion median.
rows = []
for sub in he.IEEG_SUBJECTS:
b = beh[sub]
med = {s: np.nanmedian(he.per_trial(b, s)["late_over_early"]) for s in he.SESSIONS}
inf = cad.loc[sub, "retro"] if sub == "SD185" else cad.loc[sub, "infusion"]
rows.append({"subject": sub, "median pre": med["preinfusion"], "median ket": med["infusion"],
"median post": med["postinfusion"],
"change (a)": (med["preinfusion"] + med["postinfusion"]) / 2 - med["infusion"],
"change (b)": med["preinfusion"] - med["infusion"],
"CADSS change": inf - cad.loc[sub, "pre"]})
g = pd.DataFrame(rows).set_index("subject")
g["kept"] = g["median pre"] >= 0.5
print(g.round(3).to_string())
k = g[g.kept]
print("participants kept:", len(k))
for r in ["change (a)", "change (b)"]:
rho = st.spearmanr(k["CADSS change"], k[r])
print(f"reading {r}: Spearman rho = {rho.statistic:.3f}, p = {rho.pvalue:.4f}")
rho_all = st.spearmanr(g["CADSS change"], g["change (a)"])
print(f"variant, all 14 participants, reading (a): rho = {rho_all.statistic:.3f}, p = {rho_all.pvalue:.4f}")
print("expected: rho 0.744 +/- 0.074, p = 0.014, 10 participants")
median pre median ket median post change (a) change (b) CADSS change kept subject SD163 0.646 0.646 0.767 0.061 0.000 1.0 True SD168 0.961 0.842 0.898 0.088 0.119 9.0 True SD170 0.103 -0.002 0.037 0.072 0.105 4.0 False SD172 0.509 0.348 0.781 0.297 0.161 30.0 True SD178 0.942 0.993 0.917 -0.064 -0.051 2.0 True SD181 0.961 0.193 0.530 0.552 0.768 10.0 True SD184 0.395 0.413 0.488 0.029 -0.018 38.0 False SD185 0.574 0.427 0.678 0.199 0.147 10.0 True SD186 0.508 0.334 0.494 0.167 0.174 32.0 True SD207 0.513 0.252 0.163 0.086 0.261 4.0 True SD211 0.316 0.160 0.382 0.189 0.156 23.0 False SD215 0.364 0.417 0.374 -0.048 -0.053 22.0 False SD242 0.748 0.626 0.628 0.062 0.122 1.0 True SD245 0.559 0.402 0.500 0.128 0.157 37.0 True participants kept: 10 reading change (a): Spearman rho = 0.744, p = 0.0136 reading change (b): Spearman rho = 0.561, p = 0.0916 variant, all 14 participants, reading (a): rho = 0.355, p = 0.2129 expected: rho 0.744 +/- 0.074, p = 0.014, 10 participants
Result: reproduced under reading (a). Ten participants remain after the exclusion (SD170, SD184, SD211 and SD215 have preinfusion medians below 0.5), and with the code's eye-closure change the Spearman correlation with the CADSS change is 0.744, p = 0.0136, the paper's value. Under reading (b), ketamine minus preinfusion, rho is 0.561 and p = 0.0916, not significant. The CADSS values are the code record's.
Summary¶
| Claim | Outcome |
|---|---|
| p50-condition-labels-link | reproduced (identity and counts; the saline label rests on the code) |
| cadss-scores-link | reproduced |
| fig1e-human-late-eye-closure-ketamine | reproduced |
| figs1b-human-early-eye-closure-unchanged | reproduced |
| figs1c-human-late-eye-closure-decrease | reproduced |
| figs1d-human-cadss-saline-ketamine | reproduced (code record's scores) |
| fig1f-human-subjective-reports | blocked, data missing |
| figs8a-ieeg-inclusion-per-participant | reproduced |
| figs8b-ieeg-excluded-participants | reproduced |
| fig2c-ieeg-cohort-selective-affective-decrease | reproduced |
| fig2d-ieeg-cadss-reversible | reproduced (code record's scores) |
| figs14g-cadss-correlates-with-affective-decrease | reproduced under reading (a), the code's (code record's scores) |
Differences from the paper's method: the eye-closure traces are taken as deposited (already normalised to the open eye); the eye sizes and the DeepLabCut tracking are not in these files. The saline label of the non-iEEG series and all CADSS scores come from the code record.
About the dataset: the non-iEEG files do not say which series is saline; the CADSS scores and the verbal reports are not deposited; the iEEG behaviour files are internally consistent (trial windows, stored per-trial values and their ratio agree with the continuous trace).