Reproducing Schiavo et al. (2020) from dandiset 000249 and its figshare recordings¶
Paper. Schiavo JK, Valtcheva S, Bair-Marshall CJ, Song SC, Martin KA, Froemke RC (2020). Innate and plastic mechanisms for maternal behaviour in auditory cortex. Nature 587, 426-431. doi:10.1038/s41586-020-2807-6. The claims file was written from the NIH author manuscript in PubMed Central (PMC7677212), with its Methods and the Extended Data legends; this attempt read the same text.
Dandiset. DANDI:000249, version 0.230423.1416 (doi:10.48324/dandi.000249/0.230423.1416). The paper cites no DANDI version (its data statement points to a figshare DOI). This is the only published version, so it is pinned. The draft lists the same 777 assets and size; it was modified later (2024-05-18), and the analysis uses nothing that a draft could change.
What the dandiset holds. 777 NWB files of 54 mice, each holding one raw, unaligned two-photon
movie (TwoPhotonSeries), the subject id, a session description that names the stimulus set
(for example base_r1_pc1_71b_j6a_61f_002), and an experiment description that names the group.
There are no ROIs, fluorescence traces, stimulus times, stimulus order or trial tables. The paper's
behavioural assays, whole-cell recordings and vocalization recordings are not in the dandiset.
Data from elsewhere. The dandiset's metadata links the figshare collection
doi:10.6084/m9.figshare.c.5043830.v6 (the paper's own figshare DOI, 10.35092/yhjc.c.5037737, does
not resolve). The claims file marks the whole-cell, slice and operant claims data: elsewhere, at
the figshare articles it names, which are read here at their versions: in vivo whole-cell
recordings of experienced (12611657 v3) and naive females (12613496 v2), slice recordings with
repeated stimulation (12611522 v1), oxytocin slice recordings (12613811 v1), operant touch times
(13056584 v1) and the operant task code (13054076 v1). The imaging items of the collection hold the
same unaligned movies as TIFF files, with the same descriptions, and add nothing that the
dandiset lacks.
Claims. Tested here: subject-group-link, fig1g-ipsc-depress-faster,
fig1i-operant-prototypes, ed5b-press-count-unchanged, ed6ef-epsc-tuning,
fig2h-epsc-enhanced-ev, fig2i-ipsc-weaker-nv, fig2j-ie-ratio-tracks-isi,
fig2k-ipsc-exceeds-epsc-575 and ed9b-oxytocin-enhances-spiking. Blocked:
| Claims | Reason |
|---|---|
| fig1a-isi-distribution, fig1c-warm-vs-cold, fig1c-salient-isis-retrieved, fig1c-slow-and-single-not-retrieved, ed2a-dams-evs-similar, ed2b-latency-slow-longer, ed2d-ymaze-approach, fig4c-ch575-retrieve-slow, fig4d-actx-inhibition-blocks, ed8hi-single-syllables-not-learned, fig4e-ot-inhibition-deficits | data missing: the vocalization recordings and the retrieval and y-maze behaviour are neither in the dandiset nor in the figshare collection |
| ed4d-response-tracks-isi-probability | data missing: it needs the ISI distribution of fig1a (and the imaging responses, below) |
| ed3e, fig1f, ed6c, ed3k, ed3m, ed4c, ed4e, ed4g, fig2d, ed6d, fig2e, fig2f, ed7bc, fig3e, fig3h, ed8cd, fig4b, fig4g, fig4h, ed3bc (all imaging claims) | metadata insufficient: the movies carry no stimulus times and no order of the calls within a recording, and no ROIs |
| fig3-retrieval-onset-range | metadata insufficient: the start of cohousing is not recorded |
| ed6h-dams-evs-epsc-similar | metadata insufficient: the whole-cell spreadsheets do not say which experienced cells come from dams |
Two tested claims end blocked as methods_underspecified because the readings the claims file
names give different outcomes (fig1i-operant-prototypes, ed5b-press-count-unchanged). The five
in vivo whole-cell claims (ed6ef-epsc-tuning, fig2h-epsc-enhanced-ev, fig2i-ipsc-weaker-nv,
fig2j-ie-ratio-tracks-isi, fig2k-ipsc-exceeds-epsc-575) end blocked as other: in the
deposited recordings the current almost never follows the calls, so the Methods' inclusion rule
leaves too few cells; their sections show what the measure gives on every listed cell.
Revision after the first verification. The call onset is now the start of the sound (the first, quiet syllable was missed before); the data check adds trial-averaged currents aligned to it and three more tests of whether the currents follow the calls; the in vivo claims add the expected-direction reading of the largest trials; the subject link reads the figshare item titles; the slice section checks the pulse times against the artefacts; the oxytocin section prints the spreadsheet labels and a second reading of the control window; the operant interval is taken where the times are printed at 0.1 ms; the movies are folded at trial periods.
Revision after the second verification. The data check lists and draws the files with a sweep-locked deflection, with the latency and sign of each, and folds one three-call movie of each of 38 subjects at multiples of the trial period. The wording on the in vivo currents and on the imaging call order follows these numbers.
import time
import warnings
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
from statsmodels.stats.anova import AnovaRM
import schiavo as S
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 300)
pd.set_option("display.max_colwidth", 120)
warnings.filterwarnings("ignore", category=RuntimeWarning)
started = time.time()
print("dandiset", S.DANDISET_ID, "version", S.DANDISET_VERSION)
print("figshare articles (id, version):", S.FIGSHARE)
dandiset 000249 version 0.230423.1416
figshare articles (id, version): {'invivo_ev': (12611657, 3), 'invivo_nv': (12613496, 2), 'slice': (12611522, 1), 'oxytocin': (12613811, 1), 'operant': (13056584, 1), 'operant_code': (13054076, 1)}
meta = S.dandi_version_metadata()
summary = meta["assetsSummary"]
print("Archive summary: files", summary["numberOfFiles"], "| subjects", summary["numberOfSubjects"],
"| bytes", summary["numberOfBytes"], "| variables", summary.get("variableMeasured"))
print("Related resources:", [(r.get("relation"), r.get("identifier")) for r in meta.get("relatedResource", [])])
assets = S.asset_table()
nwb = S.nwb_summaries(assets)
files = assets.merge(nwb, on="path")
print(f"\nFiles read: {len(files)}; subjects: {files.subject_id.nunique()}")
for col in ["acquisition", "processing", "stimulus", "intervals", "analysis"]:
print(f"{col:>12}:", files[col].astype(str).value_counts().to_dict())
print("\nMovie shapes, types and rates:")
print(files.groupby(["height", "width", "dtype", "rate", "imaging_rate"]).size().to_string())
print("\nIndicator and location fields:", files.indicator.unique().tolist(), files.location.unique().tolist())
print("Frames per movie: min", files.n_frames.min(), "median", int(files.n_frames.median()), "max", files.n_frames.max())
print("Session description in the file equals the archive's:",
bool((files.session_description_file == files.session_description).all()))
print("Subject id in the file equals the archive's:", bool((files.subject_id == files.subject).all()))
Archive summary: files 777 | subjects 54 | bytes 97968237237 | variables ['OpticalChannel', 'ImagingPlane', 'TwoPhotonSeries']
Related resources: [('dcite:IsDescribedBy', 'https://doi.org/10.1038/s41586-020-2807-6'), ('dcite:IsOriginalFormOf', 'https://doi.org/10.6084/m9.figshare.c.5043830.v6')]
Files read: 777; subjects: 54
acquisition: {"['TwoPhotonSeries']": 777}
processing: {'[]': 777}
stimulus: {'[]': 777}
intervals: {'[]': 777}
analysis: {'[]': 777}
Movie shapes, types and rates:
height width dtype rate imaging_rate
256 256 float32 3.90625 3.90625 2
uint16 3.90625 3.90625 775
Indicator and location fields: ['unknown'] ['unknown']
Frames per movie: min 253 median 1220 max 2900
Session description in the file equals the archive's: True
Subject id in the file equals the archive's: True
Each file holds one movie and nothing else: no processing module (no ROIs or traces), no
stimulus series and no intervals (trials). The movies are 256 x 256 pixels at 3.90625 Hz, which
matches the Methods' 256 x 256 pixels at "4 Hz (0.26 s/frames)". Indicator and location are
"unknown" in every file, although the experiment descriptions name the indicator class and the
hemisphere.
groups = (files.groupby("subject_id").experiment_description.first()
.apply(S.group_from_description).apply(pd.Series))
groups["n_files"] = files.groupby("subject_id").size()
groups["description_names"] = files.groupby("subject_id").experiment_description.first().str.extract(
r"videos for\s*\n?\s*([A-Za-z0-9-]+)", expand=False)
groups["tone_sequences"] = files.groupby("subject_id").session_description.apply(
lambda s: bool(s.str.contains("kmtf", case=False).any()))
groups["tones_only"] = files.groupby("subject_id").session_description.apply(
lambda s: bool(s.str.contains("tone|kmtf", case=False).all()))
groups["first_retrieval"] = files.groupby("subject_id").session_description.apply(
lambda s: bool(s.str.lower().str.replace(" ", "").str.contains("firstretrieval").any()))
groups["ch575"] = files.groupby("subject_id").experiment_description.first().str.contains("CH\\+575")
groups["not_cohoused_control"] = files.groupby("subject_id").experiment_description.first().str.contains("not cohoused")
print(groups.to_string())
mismatch = groups[groups.description_names.str.upper() != groups.index.str.upper()]
print("\nSubjects whose experiment description names another id:")
print(mismatch[["description_names"]].to_string())
group cells hemisphere cohoused n_files description_names tone_sequences tones_only first_retrieval ch575 not_cohoused_control
subject_id
EV06 EV excitatory left True 6 V06 False False False False False
EV104 EV inhibitory left True 8 EV104 False False False False False
EV105 EV inhibitory left True 6 EV105 False False False False False
EV122 EV excitatory right True 11 EV122 False False False False False
EV123 EV excitatory right True 5 EV123 False False False False False
EV17 EV excitatory left True 7 EV17 False False False False False
EV23 EV excitatory left True 8 EV23 False False False False False
EV27 EV excitatory left True 13 EV27 False False False False False
EV30 EV excitatory left True 10 EV30 False False False False False
EV31 EV inhibitory left True 14 EV31 False False False False False
EV35 EV excitatory left True 5 EV35 False False False False False
EV38 EV excitatory left True 15 EV38 False False False False False
EV40 EV inhibitory left True 8 EV40 False False False False False
EV42 EV excitatory left True 9 EV42 False False False False False
EV44 EV inhibitory left True 13 EV44 False False False False False
EV52 EV excitatory left True 7 EV52 False False False False False
EV56 unknown excitatory not stated False 2 NV56 True True False False False
EV62 unknown excitatory not stated False 3 NV62 True True False False False
EV84 EV excitatory right True 11 EV84R False False False False False
EV86 EV excitatory right True 12 EV86R False False False False False
M001 dam excitatory left False 10 M001 True False False False False
NV103 NV excitatory not stated True 17 NV103 True False False False True
NV106 NV inhibitory not stated True 16 NV106 False False False False False
NV108 NV excitatory not stated True 16 NV108 False False False False True
NV115 NV excitatory not stated True 22 NV115 False False True False False
NV119 NV excitatory not stated True 14 NV119 True False False False True
NV120 NV excitatory not stated True 12 NV120 False False False False True
NV140 unknown excitatory not stated False 2 NV140 True True False False False
NV21 NV excitatory not stated True 15 NV21 False False True False False
NV25 NV inhibitory not stated True 40 NV25 False False True False False
NV28 NV inhibitory not stated True 41 NV28 False False True False False
NV32 NV excitatory not stated True 33 NV32 False False True False False
NV33 NV inhibitory not stated True 39 NV33 False False True False False
NV36 NV excitatory not stated True 33 NV36 False False True False False
NV37 NV excitatory not stated True 26 NV37 False False True False False
NV39 NV excitatory not stated True 18 NV39 False False True False False
NV41 NV inhibitory not stated True 27 NV41 False False True False False
NV43 NV excitatory not stated True 30 NV43 False False True False False
NV47 NV inhibitory not stated True 24 NV47 False False True False False
NV50 NV excitatory not stated True 19 NV50 False False True False False
NV54 NV excitatory not stated True 23 NV54 False False True False False
NV57 NV excitatory not stated True 14 NV57 False False False True False
NV58 NV excitatory not stated True 13 NV58 False False False True False
NV60 NV excitatory not stated True 14 NV60 False False False True False
NV63 NV excitatory not stated True 13 NV63 False False False True False
NV64 unknown excitatory not stated False 3 NV64 True True False False False
NV65 NV excitatory not stated True 9 NV65 False False False True False
NV66 unknown excitatory not stated False 3 NV66 True True False False False
NV77 NV excitatory not stated True 10 NV77 False False False True False
NV84 NV excitatory not stated True 16 NV84 False False False False True
OXTHALO-10 NV pan-neuronal (opto-OT) not stated True 11 OXTHALO-10 False False False False False
OXTHALO-15 NV pan-neuronal (opto-OT) not stated True 4 OXTHALO-15 False False False False False
OXTHALO-17 NV pan-neuronal (opto-OT) not stated True 9 OXTHALO-17 False False False False False
OXTHALO-19 NV pan-neuronal (opto-OT) not stated True 8 OXTHALO-19 False False False False False
Subjects whose experiment description names another id:
description_names
subject_id
EV06 V06
EV56 NV56
EV62 NV62
EV84 EV84R
EV86 EV86R
Recordings deposited more than once¶
Exact copies would share a checksum. Copies under a new encoding would share their frame count and the sums of their first and last frames.
print("Files sharing a SHA-256 digest:", int(files.sha256.duplicated(keep=False).sum()))
key = files.groupby(["n_frames", "frame0_sum", "frame_last_sum"]).path.apply(list)
dups = key[key.map(len) > 1]
for paths in dups:
sub = files[files.path.isin(paths)][["path", "session_start", "session_description", "n_frames", "size"]]
print(sub.to_string(index=False), "\n")
print("Recordings deposited twice:", len(dups))
Files sharing a SHA-256 digest: 0
path session_start session_description n_frames size
sub-NV58/sub-NV58_ses-20180308T122553_ophys.nwb 2018-03-08T12:25:53.416000-05:00 plus24hrs_postexp1_r1_pc2_j1a_j8a_j5a_003 1220 121806862
sub-NV63/sub-NV63_ses-20180308T122553_ophys.nwb 2018-03-08T12:25:53.416000-05:00 plus24hrs_postexp1_r1_pc2_j1a_j8a_j5a_003 1220 121806862
path session_start session_description n_frames size
sub-NV58/sub-NV58_ses-20180308T121743_ophys.nwb 2018-03-08T12:17:43.400000-05:00 plus24hrs_postexp1_r1_pc1_71b_j6a_61f_002 1220 122226309
sub-NV63/sub-NV63_ses-20180308T121743_ophys.nwb 2018-03-08T12:17:43.400000-05:00 plus24hrs_postexp1_r1_pc1_71b_j6a_61f_002 1220 122226309
path session_start session_description n_frames size
sub-NV58/sub-NV58_ses-20180308T125439_ophys.nwb 2018-03-08T12:54:39.800000-05:00 plus24hrs_postexp1_r2_pc1_71b_j6a_61f_004 1220 125252672
sub-NV63/sub-NV63_ses-20180308T125439_ophys.nwb 2018-03-08T12:54:39.800000-05:00 plus24hrs_postexp1_r2_pc1_71b_j6a_61f_004 1220 125252672
Recordings deposited twice: 3
Three recordings (the plus24hrs_postexp1 sessions of 2018-03-08) are deposited under both
sub-NV58 and sub-NV63, with the same start times and the same frames. Nothing in the files says to
which mouse they belong. They are counted once below.
Is the timing of the calls recoverable from the movies?¶
The calls were "played in a pseudorandom order (repetition rate: 0.1 Hz)" (Methods). With no stimulus times, a test of any imaging claim would need the onset and the identity of each call from the movie itself. The spectrum of the movie (8 x 8 pixel blocks, each as dF/F, high-passed with a 20 s moving average) is shown for a prototype file, a morph file and a pure tone file.
from scipy.ndimage import uniform_filter1d
def block_spectrum(s3):
import h5py, remfile
with h5py.File(remfile.File(s3), "r") as f:
x = f["acquisition/TwoPhotonSeries/data"][:].astype(np.float32)
fs = float(f["acquisition/TwoPhotonSeries/starting_time"].attrs["rate"])
b = x.reshape(len(x), 32, 8, 32, 8).mean(axis=(2, 4)).reshape(len(x), -1)
b = (b - b.mean(0)) / b.mean(0)
b = b - uniform_filter1d(b, int(20 * fs), axis=0)
P = (np.abs(np.fft.rfft(b, axis=0)) ** 2).mean(axis=1)
return np.fft.rfftfreq(len(b), 1 / fs), P
examples = ["sub-NV43/sub-NV43_ses-20171014T183731_ophys.nwb", # pc1: three prototypes
"sub-EV27/sub-EV27_ses-20170425T192357_ophys.nwb", # morph set of 61f,
"sub-NV33/sub-NV33_ses-20170711T155914_ophys.nwb"] # pure tones
examples = [p for p in examples if p in set(files.path)] or files.path.iloc[:3].tolist()
fig, ax = plt.subplots(figsize=(8, 3))
for p in examples:
row = files[files.path == p].iloc[0]
fr, P = block_spectrum(row.s3)
sel = (fr > 0.05) & (fr < 1.0)
rel = P[sel] / np.median(P[sel])
near = (fr[sel] > 0.09) & (fr[sel] < 0.11)
top = np.argsort(rel)[::-1][:3]
print(f"{row.session_description}: power at 0.09-0.11 Hz {rel[near].max():.1f} x median; "
f"largest peaks at {np.round(fr[sel][top], 3).tolist()} Hz ({np.round(rel[top], 1).tolist()} x median)")
ax.semilogy(fr[sel], rel, label=row.session_description[:40])
ax.axvline(0.1, color="grey", lw=0.5)
ax.set_xlabel("frequency (Hz)"); ax.set_ylabel("power / median"); ax.legend(fontsize=7)
plt.show()
FirstRetrieval_r3_pc1_71b_j6a_61f_002: power at 0.09-0.11 Hz 3.8 x median; largest peaks at [0.064, 0.058, 0.074] Hz ([8.899999618530273, 6.199999809265137, 5.800000190734863] x median)
s3_r1_61f_5set_1150_850_1300_1600_700_006: power at 0.09-0.11 Hz 6.2 x median; largest peaks at [0.066, 0.058, 0.089] Hz ([10.399999618530273, 10.300000190734863, 9.199999809265137] x median)
base_r1_puretones_half_001: power at 0.09-0.11 Hz 2.7 x median; largest peaks at [0.391, 0.197, 0.194] Hz ([76.0, 57.70000076293945, 49.5] x median)
# A sharper test of the call timing: fold each pixel (2 x 2 binned, dF/F, high-passed with a 30 s
# moving average) at a trial period and measure how much of its variance the folded mean (20 phase
# bins per 10.24 s) explains; the mean of the 200 strongest pixels, over a grid of periods. A period
# at which stimuli recur stands above its neighbours. Folding at k periods tests whether the k calls
# of a file recur in a fixed cycle (as in the whole-cell playback), which would make each phase one
# call.
def pixel_movie(s3):
import h5py, remfile
with h5py.File(remfile.File(s3), "r") as f:
x = f["acquisition/TwoPhotonSeries/data"][:].astype(np.float32)
fs = float(f["acquisition/TwoPhotonSeries/starting_time"].attrs["rate"])
n = len(x)
b = x.reshape(n, 128, 2, 128, 2).mean(axis=(2, 4)).reshape(n, -1)
del x
b = (b - b.mean(0)) / b.mean(0)
return b - uniform_filter1d(b, int(30 * fs), axis=0), fs
def fold_strengths(b, fs, periods, bins_per_10s=20, top=200):
n, tvar, out = len(b), b.var(0), []
for P in periods:
nb = max(4, int(round(bins_per_10s * P / 10.24)))
k = ((np.arange(n) / fs % P) / P * nb).astype(int)
m = np.stack([b[k == i].mean(0) for i in range(nb)])
out.append(np.sort(m.var(0) / tvar)[-top:].mean())
return np.array(out)
grid = np.round(np.arange(4.5, 12.001, 0.02), 2)
for p in examples:
row = files[files.path == p].iloc[0]
b, fs = pixel_movie(row.s3)
s = fold_strengths(b, fs, grid)
top3 = np.argsort(s)[::-1][:3]
print(f"{row.session_description}: median over 4.5-12 s {np.median(s):.4f}; strongest periods "
f"{[(float(grid[i]), round(float(s[i]), 4)) for i in top3]}; at 10.00 s {s[np.argmin(abs(grid - 10))]:.4f}; "
f"40 frames = {40 / fs:.2f} s, 20 frames = {20 / fs:.2f} s")
if "tone" not in row.session_description:
cyc = []
for k in range(1, 7):
P = 40 / fs * k
near = fold_strengths(b, fs, [P + d for d in (-0.6, -0.4, -0.3, 0.3, 0.4, 0.6)]).mean()
cyc.append(f"{k}x: {fold_strengths(b, fs, [P])[0]:.4f} (nearby {near:.4f})")
print(" folding at k x 40 frames:", "; ".join(cyc))
del b
FirstRetrieval_r3_pc1_71b_j6a_61f_002: median over 4.5-12 s 0.0298; strongest periods [(10.24, 0.0674), (10.22, 0.0665), (10.26, 0.062)]; at 10.00 s 0.0339; 40 frames = 10.24 s, 20 frames = 5.12 s
folding at k x 40 frames: 1x: 0.0674 (nearby 0.0347); 2x: 0.0943 (nearby 0.0705); 3x: 0.1487 (nearby 0.0990); 4x: 0.1507 (nearby 0.1292); 5x: 0.1570 (nearby 0.1469); 6x: 0.2188 (nearby 0.1931)
s3_r1_61f_5set_1150_850_1300_1600_700_006: median over 4.5-12 s 0.0178; strongest periods [(10.24, 0.0351), (10.22, 0.0344), (10.26, 0.0331)]; at 10.00 s 0.0214; 40 frames = 10.24 s, 20 frames = 5.12 s
folding at k x 40 frames: 1x: 0.0351 (nearby 0.0218); 2x: 0.0596 (nearby 0.0452); 3x: 0.0735 (nearby 0.0618); 4x: 0.0845 (nearby 0.0743); 5x: 0.1025 (nearby 0.0917); 6x: 0.1251 (nearby 0.1184)
base_r1_puretones_half_001: median over 4.5-12 s 0.0244; strongest periods [(10.24, 0.1686), (5.12, 0.1625), (10.26, 0.1597)]; at 10.00 s 0.0281; 40 frames = 10.24 s, 20 frames = 5.12 s
# Is a three-call cycle present in other movies? One file of each subject with the three-call set
# 71b, j6a, 61f (1220 frames), the first by path: the fold strength at k x 40 frames minus the mean
# at the nearby periods, for k = 1 to 6. A fixed cycle of three calls would make the excess at 3 x
# stand above those at 2 x and 4 x in most files.
three = files[files.session_description.str.contains(r"_pc1_71b_?j6a_?61f_", case=False) & (files.n_frames == 1220)]
three = three.sort_values("path").groupby("subject_id").head(1)
def cycle_excess(row):
b, fs = pixel_movie(row.s3)
ex = []
for k in range(1, 7):
P = 40 / fs * k
ex.append(fold_strengths(b, fs, [P])[0] - fold_strengths(b, fs, [P + d for d in (-0.6, -0.4, -0.3, 0.3, 0.4, 0.6)]).mean())
return {"subject": row.subject_id, "session": row.session_description[:40], **{f"{k}x": e for k, e in zip(range(1, 7), ex)}}
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(4) as ex_pool:
ex_tab = pd.DataFrame(list(ex_pool.map(cycle_excess, [r for _, r in three.iterrows()])))
print(ex_tab.round(4).to_string(index=False))
kx = ["1x", "2x", "3x", "4x", "5x", "6x"]
print(f"\nFiles: {len(ex_tab)} of {three.subject_id.nunique()} subjects; excess at 1x (the trial period) above 0.005 in "
f"{int((ex_tab['1x'] > 0.005).sum())}")
print("Median excess at each multiple:", ex_tab[kx].median().round(4).to_dict())
print("Files whose largest excess is at each multiple:", ex_tab[kx].idxmax(axis=1).value_counts().reindex(kx, fill_value=0).to_dict())
print("Files with the excess at 3x above both 2x and 4x:", int(((ex_tab["3x"] > ex_tab["2x"]) & (ex_tab["3x"] > ex_tab["4x"])).sum()),
"of", len(ex_tab), "| at 2x above 1x and 3x:", int(((ex_tab["2x"] > ex_tab["1x"]) & (ex_tab["2x"] > ex_tab["3x"])).sum()),
"| at 4x above 3x and 5x:", int(((ex_tab["4x"] > ex_tab["3x"]) & (ex_tab["4x"] > ex_tab["5x"])).sum()))
subject session 1x 2x 3x 4x 5x 6x
EV122 s1_r1_pc1_71b_j6a_61f_002 0.0028 0.0023 0.0086 0.0052 0.0024 0.0049
EV123 s1_r1_pc1_71b_j6a_61f_002 0.0079 0.0052 0.0062 0.0025 0.0055 0.0028
EV30 s1cohoused_r1_pc1_71b_j6a_61f_002 0.0070 0.0051 0.0045 0.0035 0.0032 0.0016
EV31 s1_r1_pc1_71b_j6a_61f_002 0.0701 0.0579 0.0811 0.0386 0.0294 0.0504
EV35 base_r1_pc1_71b_j6a_61f_002 0.0187 0.0063 0.0287 0.0019 0.0039 0.0121
EV38 s1_r1_pc1_71b_j6a_61f_011 0.0207 0.0044 0.0262 0.0046 0.0094 0.0209
EV40 s1_r1_pc1_71b_j6a_61f_002 -0.0001 0.0020 0.0025 0.0016 0.0006 0.0044
EV44 s1ch_r1_pc1_71b_j6a_61f_002 0.0437 0.0361 0.0342 0.0219 0.0181 0.0209
EV52 base_r1_pc1_71b_j6a_61f_002 0.0826 0.0572 0.0924 0.0314 0.0254 0.0448
EV84 s1_r1_pc1_71bj6a61f_002 -0.0002 -0.0007 -0.0025 0.0015 -0.0016 0.0038
EV86 s1_r1_pc1_71bj6a61f_002 0.0071 0.0048 0.0063 0.0043 0.0087 0.0037
M001 s2_72hrsch_r1_pc1_71b_j6a_61f_002 0.0009 0.0025 0.0062 0.0059 0.0016 0.0069
NV106 plus24hrs_r2_pc1_71b_j6a_61f_007 0.0095 0.0072 0.0079 0.0058 0.0076 0.0108
NV108 s1_r1_pc1_71b_j6a_61f_002 0.0181 0.0132 0.0310 0.0082 0.0062 0.0204
NV119 s1_r2_pc1_71b_j6a_61f_005 0.0028 0.0032 0.0053 0.0025 0.0027 0.0061
NV120 s1_r2_pc1_71b_j6a_61f_015 0.0191 0.0141 0.0142 0.0096 0.0085 0.0077
NV28 base_r3_pc1_71b_j6a_61f_011 0.0006 0.0007 0.0068 0.0025 0.0025 0.0030
NV32 base_r2_pc1_71b_j6a_61f_010 -0.0001 -0.0005 0.0022 -0.0002 0.0005 0.0001
NV33 base_r1_pc1_71b_j6a_61f_002 0.0978 0.0838 0.1125 0.0556 0.0445 0.0676
NV36 base_r1_pc1_71b_j6a_61f_002 0.0059 0.0056 0.0146 0.0019 0.0012 0.0153
NV37 base_r1_PC1_71b_j6a_61f_002 0.0016 -0.0026 0.0015 -0.0004 -0.0008 0.0022
NV39 base_r2_pc1_71b_j6a_61f_008 0.0189 0.0221 0.0185 0.0130 0.0084 0.0125
NV41 base_r1_pc1_71b_j6a_61f_002 0.0003 -0.0001 0.0049 0.0023 0.0047 0.0080
NV43 base_r3_pc1_71b_j6a_61f_005 0.0071 0.0059 0.0338 0.0079 0.0063 0.0214
NV47 base_r1_pc1_71b_j6a_61f_002 0.0597 0.0521 0.0688 0.0355 0.0236 0.0413
NV50 24hrsPRE_r1_pc1_71b_j6a_61f_002 0.0557 0.0393 0.0850 0.0208 0.0204 0.0417
NV54 base_r1_pc1_71b_j6a_61f_002 0.1195 0.0984 0.0867 0.0522 0.0376 0.0448
NV57 base_r2_pc1_71b_j6a_61f_005 0.1038 0.0842 0.1274 0.0624 0.0392 0.0761
NV58 r1_base_pc1_71b_j6a_61f_002 0.0105 0.0132 0.0121 0.0070 0.0034 0.0086
NV60 base_r1_pc1_71b_j6a_61f_002 0.0962 0.0822 0.0800 0.0512 0.0444 0.0431
NV63 plus24hrs_postexp1_r1_pc1_71b_j6a_61f_00 0.0010 0.0007 0.0055 0.0045 0.0044 0.0026
NV65 plus24hrs_s2_r1_pc1_71b_j6a_61f_002 0.0259 0.0212 0.0288 0.0164 0.0123 0.0161
NV77 base_r1_pc1_71bj6a61f_002 0.0830 0.0597 0.1922 0.0449 0.0200 0.1112
NV84 s1_r2_pc1_71bj6a61f_004 0.0226 0.0186 0.0243 0.0126 0.0078 0.0166
OXTHALO-10 plus24hrs_r2_pc1_71b_j6a_61f_002 0.0231 0.0139 0.0179 0.0103 0.0057 0.0120
OXTHALO-15 plus24hrs_r2_pc1_71b_j6a_61f_002 0.0046 0.0003 0.0031 0.0017 0.0082 0.0053
OXTHALO-17 plus24hrs_r1_pc1_71b_j6a_61f_002 0.0062 0.0049 0.0075 0.0054 0.0046 0.0065
OXTHALO-19 plus24hrs_r1_pc1_71b_j6a_61f_002 0.0342 0.0329 0.0341 0.0224 0.0154 0.0207
Files: 38 of 38 subjects; excess at 1x (the trial period) above 0.005 in 27
Median excess at each multiple: {'1x': 0.014299999922513962, '2x': 0.006800000090152025, '3x': 0.016300000250339508, '4x': 0.006399999838322401, '5x': 0.007000000216066837, '6x': 0.01209999993443489}
Files whose largest excess is at each multiple: {'1x': 8, '2x': 2, '3x': 18, '4x': 0, '5x': 2, '6x': 8}
Files with the excess at 3x above both 2x and 4x: 31 of 38 | at 2x above 1x and 3x: 2 | at 4x above 3x and 5x: 1
In the call files the power near 0.1 Hz (3.8 and 6.2 times the median) does not stand above the
slower fluctuations (8.9 and 10.4 times the median at 0.06 to 0.07 Hz). Folding single pixels at a
trial period finds the stimulus period: in both call files the strongest period between 4.5 and
12 s is 10.24 s, 40 frames (fold strength 0.067 and 0.035 against a median of 0.030 and 0.018 over
the grid; 0.034 and 0.021 at 10.00 s), and in the tone file 10.24 s and 5.12 s (20 frames), the
Methods' 0.2 Hz tone rate tied to the frame clock. So the phase of the trials could be recovered
from a movie; 40 frames is 0.098 Hz, the Methods' "0.1 Hz" rounded. In the first three-call file,
3 x 40 frames exceeds the nearby periods by 0.050, against 0.033 at one period and 0.024 at two; in
the five-stimulus morph file 5 x 40 frames exceeds them by 0.011, no more than other multiples.
Over one three-call movie of each of 38 subjects (the table above), the excess at 3 x 40 frames is
above those at 2 x and 4 x in 31 of 38 files (median excess 0.016 at 3 x against 0.007 and 0.006),
and the largest of the six multiples in 18; at 2 x it stands above its neighbours in 2 files and at
4 x in 1. The trial period itself stands out (excess above 0.005) in 27 of 38. So in most three-call
movies the trials appear to recur in a fixed cycle of three, although the Methods say the calls were
played "in a pseudorandom order". The cycle gives each trial a position, but nothing in the files
says which call is played at which position or on the first trial: the session descriptions name
the calls of a file, and whether their listed order is the playback order cannot be checked.
Every imaging claim needs responses assigned to named calls (and ROIs, which are not deposited),
so all are blocked as metadata_insufficient.
The figshare recordings¶
The in vivo whole-cell articles list each cell, file, stimulus set, holding potential and notes in
a spreadsheet. Each ABF file has the membrane current and a microphone channel (MIC) that
records the played call, from which the call's onset, offset and ISI are measured per sweep.
iv_list, iv_sweeps, iv_info = S.load_invivo()
print("Files listed:", len(iv_list), "| on figshare:", int(iv_list.on_figshare.sum()))
print("Listed but not on figshare:", iv_list[~iv_list.on_figshare][["group", "cell", "file", "set", "holding"]].to_dict("records"))
print("\nCells per group:", iv_list.groupby("group").cell.nunique().to_dict(),
"(paper: 12 experienced, 14 naive EPSC cells; 6 and 6 IPSC cells)")
print("Recording settings:", iv_info.groupby(["rate", "sweep_s", "protocol"]).size().to_dict())
print("Channels:", iv_info.adc.astype(str).unique().tolist())
sets = iv_list[~iv_list.set.str.contains("5xcalls") & iv_list.on_figshare].file
sw = iv_sweeps[iv_sweeps.file.isin(sets)]
print("\nMorph-set files:", sw.file.nunique(), "| sweeps:", len(sw), "| sweeps with no call found:", int(sw.isi.isna().sum()))
print("Files whose ISI comes from call-duration ranks:", int(sw.groupby("file").duration_rank_used.first().sum()),
"| from the measured gap only:", sw.groupby("file").duration_rank_used.first().eq(False).sum())
print("\nPer ISI bin, measured silent gap (ms) and call duration (s):")
print(sw.dropna(subset=["isi"]).groupby("isi").agg(sweeps=("gap_ms", "size"), gap_min=("gap_ms", "min"),
gap_median=("gap_ms", "median"), gap_max=("gap_ms", "max"), dur_min=("duration", "min"),
dur_max=("duration", "max")).round(3).to_string())
agree = (sw.isi == sw.isi_gap).mean()
print(f"Sweeps where the duration rank and the nearest gap bin agree: {agree:.1%}")
order = sw.dropna(subset=["isi"]).groupby("file").isi.apply(lambda v: "-".join(str(int(i)) for i in v.iloc[:4]))
print("Order of the first four sweeps, by file:", order.value_counts().to_dict())
print("\nCall onset (start of the first syllable, quiet or loud) within the 5 s sweep (s): min %.3f, median %.3f, max %.3f"
% (sw.onset.min(), sw.onset.median(), sw.onset.max()))
spread = sw.groupby("file").onset_raw.agg(lambda v: v.max() - v.min())
print("Spread of the detected onset within a file (ms): median %.1f, max %.1f; sweeps given the file median "
"(more than %d ms from it): %d of %d" % (1000 * spread.median(), 1000 * spread.max(), 1000 * S.MIC_ONSET_TOL_S,
int(sw.onset_replaced.sum()), int(sw.onset.notna().sum())))
late = (sw.onset_loud - sw.onset)
print("Onset of the first loud syllable (the earlier rule) minus the call onset (ms): median %.0f, max %.0f; "
"files where it is over 50 ms late: %d of %d" % (1000 * late.median(), 1000 * late.max(),
int((sw.groupby("file").apply(lambda d: (d.onset_loud - d.onset).median()) > 0.05).sum()),
sw.file.nunique()))
Files listed: 112 | on figshare: 111
Listed but not on figshare: [{'group': 'naive', 'cell': '190326_e1', 'file': '193260007', 'set': '71B set', 'holding': 60.0}]
Cells per group: {'experienced': 12, 'naive': 14} (paper: 12 experienced, 14 naive EPSC cells; 6 and 6 IPSC cells)
Recording settings: {(20000, 5.0, 'VC_pupcall_OFFICIAL_JS'): 111}
Channels: ["[('Vm_prime', 'pA'), ('MIC', 'V')]"]
Morph-set files: 83 | sweeps: 3204 | sweeps with no call found: 74
Files whose ISI comes from call-duration ranks: 82 | from the measured gap only: 1
Per ISI bin, measured silent gap (ms) and call duration (s):
sweeps gap_min gap_median gap_max dur_min dur_max
isi
125.0 783 123.400 142.100 164.85 0.611 0.788
175.0 786 159.450 192.050 214.90 0.709 0.984
275.0 782 273.225 301.325 314.50 1.031 1.380
575.0 779 570.475 604.050 614.85 1.959 2.559
Sweeps where the duration rank and the nearest gap bin agree: 87.5%
Order of the first four sweeps, by file: {'175-125-275-575': 82, '175-175': 1}
Call onset (start of the first syllable, quiet or loud) within the 5 s sweep (s): min 1.519, median 1.528, max 1.577
Spread of the detected onset within a file (ms): median 0.4, max 197.2; sweeps given the file median (more than 20 ms from it): 5 of 3130
Onset of the first loud syllable (the earlier rule) minus the call onset (ms): median 193, max 644; files where it is over 50 ms late: 51 of 83
hc = iv_sweeps.groupby("file").holding_current.median().rename("median_holding_current_pA")
chk = iv_list.merge(hc, left_on="file", right_index=True)
chk["listed"] = np.where(chk.holding < -30, "-70 mV", "0 to +60 mV")
print("Median holding current (pA) by listed holding potential:")
print(chk.groupby("listed").median_holding_current_pA.describe().round(1).to_string())
odd = chk[((chk.holding < -30) & (chk.median_holding_current_pA > 100)) |
((chk.holding > -30) & (chk.median_holding_current_pA < -100))]
print("\nFiles whose holding current contradicts the listed potential:")
print(odd[["group", "cell", "file", "set", "holding", "median_holding_current_pA", "notes"]].to_string(index=False))
per_sweep = iv_sweeps.merge(iv_list[["file", "holding"]], on="file")
bad = per_sweep[((per_sweep.holding < -30) & (per_sweep.holding_current > 100)) |
((per_sweep.holding > -30) & (per_sweep.holding_current < -100))]
print("Sweeps dropped for this, by file:", bad.groupby("file").size().to_dict())
Median holding current (pA) by listed holding potential:
count mean std min 25% 50% 75% max
listed
-70 mV 83.0 -128.2 155.4 -714.1 -196.8 -88.8 -53.1 564.0
0 to +60 mV 28.0 288.5 225.7 -206.0 126.2 273.7 455.0 686.6
Files whose holding current contradicts the listed potential:
group cell file set holding median_holding_current_pA notes
experienced 190424_e2 19424013 J6A set -70.0 563.964844
experienced 190411_e1 19411001 j5b set -70.0 316.772461 some sweeps are at positive potentials
naive 190315_e3 19315014 J5B set 0.0 -205.993652 inhibition
Sweeps dropped for this, by file: {'19314006': 2, '19315014': 2, '19411001': 26, '19424013': 41}
# Do the currents follow the calls? Four checks, per cell and current (EPSC at -70 mV, IPSC at a
# depolarized potential), on the morph-set sweeps used below (main selection).
rng_check = np.random.default_rng(13)
# (1) The Methods' evoked measure (call window minus the 1 s before onset), signed in the expected
# direction (inward for EPSCs, outward for IPSCs), against zero over trials; the same for the
# earlier onset rule (first loud syllable) and for the sham measure (0.7 s before onset minus the
# 0.7 s before that), the same test with no call in the window.
rows = []
for name, meas in (("call window", "evoked"), ("call window, loud-syllable onset", "evoked_loud"),
("sham, before the call", "sham")):
_, raw = S.psc_table(iv_list, iv_sweeps, top_n=5, measure=meas)
for (grp, cell, kind), d in raw.groupby(["group", "cell", "kind"]):
tt = stats.ttest_1samp(d.directed, 0)
rows.append({"window": name, "group": grp, "cell": cell, "kind": kind, "trials": len(d),
"mean directed pA": d.directed.mean(), "share expected sign": (d.directed > 0).mean(),
"p": tt.pvalue, "responds": bool(tt.statistic > 0 and tt.pvalue < 0.05)})
resp = pd.DataFrame(rows)
print("(1) Cells whose mean evoked current over trials is in the expected direction at p < 0.05 (one test per cell;"
" about 2.5% expected by chance):")
print(resp.groupby(["window", "kind", "group"]).responds.agg(["sum", "size"]).to_string())
print("Share of trials with the expected sign:", resp.groupby("window").apply(
lambda d: round(np.average(d["share expected sign"], weights=d.trials), 3)).to_dict())
print("\nCall window, per cell:")
print(resp[resp.window == "call window"].drop(columns="window").round(3).to_string(index=False))
# (2) The response in the first 300 ms after onset, from the 5 ms binned traces, against the 300 ms
# before onset, in the expected direction.
_, raw_main = S.psc_table(iv_list, iv_sweeps, top_n=5)
tr = raw_main.merge(iv_sweeps[["file", "sweep", "trace"]], on=["file", "sweep"])
nb_pre = int(-S.TRACE_WIN_S[0] / S.TRACE_BIN_S)
b300 = int(0.3 / S.TRACE_BIN_S)
cell_traces, early = {}, []
for (grp, cell, kind), d in tr.groupby(["group", "cell", "kind"]):
X = np.stack(d.trace.values).astype(float)
X = (X - np.nanmean(X[:, nb_pre - b300:nb_pre], axis=1, keepdims=True)) * (-1 if kind == "EPSC" else 1)
cell_traces[(grp, cell, kind)] = (X, d.isi.values)
e = np.nanmean(X[:, nb_pre:nb_pre + b300], axis=1)
tt = stats.ttest_1samp(e, 0)
early.append({"group": grp, "cell": cell, "kind": kind, "trials": len(e), "0-300 ms, pA": e.mean(),
"p": tt.pvalue, "responds": bool(tt.statistic > 0 and tt.pvalue < 0.05)})
early = pd.DataFrame(early)
print("\n(2) Cells with a mean response in the expected direction 0-300 ms after onset at p < 0.05:")
print(early.groupby(["kind", "group"]).responds.agg(["sum", "size"]).to_string())
print("0-300 ms response (pA, expected direction), median and range over cells:",
early.groupby("kind")["0-300 ms, pA"].agg(["median", "min", "max"]).round(2).to_dict("index"))
# (3) Any deflection locked to the sweep at any time from onset to 3 s after: per file, the largest
# |t| of the trial mean over 20 ms bins, against 200 random sign flips of the trials.
def max_t(Y):
mu = np.nanmean(Y, 0); se = np.nanstd(Y, 0) / np.sqrt(len(Y))
return np.nanmax(np.abs(mu / se))
scan, scan_curves = [], {}
sets_sw = iv_sweeps.merge(iv_list[["file", "group", "cell", "holding", "set"]], on="file").dropna(subset=["onset"])
for (grp, cell, f, hold, st), d in sets_sw.groupby(["group", "cell", "file", "holding", "set"]):
if len(d) < 5:
continue
X = np.stack(d.trace.values).astype(float)
X = X.reshape(len(X), -1, 4).mean(axis=2) # 20 ms bins
pre = nb_pre // 4
X = X[:, pre:] - np.nanmean(X[:, pre - 10:pre], axis=1, keepdims=True)
obs = max_t(X)
null = np.array([max_t(X * rng_check.choice([-1, 1], size=(len(X), 1))) for _ in range(200)])
mu = np.nanmean(X, 0); tc = mu / (np.nanstd(X, 0) / np.sqrt(len(X)))
j, sign = int(np.nanargmax(np.abs(tc))), (-1 if hold < -30 else 1)
above = np.flatnonzero(np.abs(tc) > np.quantile(null, 0.95))
scan.append({"group": grp, "cell": cell, "file": f, "kind": "EPSC" if hold < -30 else "IPSC",
"set": "5 prototypes" if "5xcalls" in st else "morph set", "sweeps": len(X),
"max |t|": obs, "p": float(np.mean(null >= obs)),
"peak s": (j + 0.5) * 0.02, "peak pA, expected direction": sign * mu[j],
"first bin above null s": (above[0] + 0.5) * 0.02 if len(above) else np.nan,
"bins above null": len(above)})
scan_curves[f] = (sign * X, sign * mu, tc)
scan = pd.DataFrame(scan)
print("\n(3) Files with a sweep-locked deflection anywhere 0-3 s after onset at p < 0.05 (5% expected by chance):")
print(scan.groupby(["set", "kind"]).p.agg(n_files="size", significant=lambda p: int((p < 0.05).sum())).to_string())
# (4) The Methods' inclusion rule: at least five significant prototype-evoked PSCs, a significant PSC
# being a trial whose current in the expected direction exceeds k s.d. of the cell's sham measure.
# The same rule applied to the sham trials gives its false-positive rate.
incl_tabs = {}
for k in S.SIG_K:
_, tab = S.significant_cells(iv_list, iv_sweeps, k)
incl_tabs[k] = tab
_, sh = S.psc_table(iv_list, iv_sweeps, measure="sham")
sd_ = sh.groupby(["group", "cell", "kind"]).directed.transform("std")
print(f"\n(4) k = {k}: share of call trials significant {tab.sig_share.mean():.3f} (mean over cells); "
f"share of sham trials passing the same rule {(sh.directed > k * sd_).mean():.3f}")
print("Cells with at least 5 significant prototype trials:")
print(tab.groupby(["kind", "group"]).agg(cells=("included", "size"), included=("included", "sum"),
max_sig_proto=("proto_sig", "max")).to_string())
print("Included:", tab[tab.included].reset_index()[["group", "cell", "kind", "proto_sig"]].to_dict("records"))
sd = iv_sweeps.merge(iv_list[["file", "group", "holding"]], on="file").dropna(subset=["baseline_sd"])
sd["kind"] = np.where(sd.holding < -30, "EPSC", "IPSC")
print("\nBaseline current s.d. within a sweep (pA), median by group and current:")
print(sd.groupby(["kind", "group"]).baseline_sd.median().round(2).to_string())
(1) Cells whose mean evoked current over trials is in the expected direction at p < 0.05 (one test per cell; about 2.5% expected by chance):
sum size
window kind group
call window EPSC experienced 0 12
naive 1 14
IPSC experienced 1 7
naive 0 6
call window, loud-syllable onset EPSC experienced 1 12
naive 2 14
IPSC experienced 1 7
naive 0 6
sham, before the call EPSC experienced 2 12
naive 1 14
IPSC experienced 0 7
naive 0 6
Share of trials with the expected sign: {'call window': 0.482, 'call window, loud-syllable onset': 0.466, 'sham, before the call': 0.465}
Call window, per cell:
group cell kind trials mean directed pA share expected sign p responds
experienced 190311_e1 EPSC 80 -0.262 0.500 0.834 False
experienced 190313_e2 EPSC 70 5.489 0.643 0.050 False
experienced 190313_e3 EPSC 80 -0.752 0.475 0.811 False
experienced 190403_e1 EPSC 80 -1.293 0.488 0.448 False
experienced 190403_e1 IPSC 80 0.269 0.388 0.879 False
experienced 190410_e1 EPSC 96 2.585 0.479 0.228 False
experienced 190410_e1 IPSC 19 9.161 0.526 0.031 True
experienced 190410_e2 EPSC 120 -0.023 0.408 0.985 False
experienced 190411_e1 EPSC 55 -2.114 0.382 0.519 False
experienced 190411_e1 IPSC 100 2.795 0.560 0.342 False
experienced 190411_e2 EPSC 80 2.302 0.400 0.239 False
experienced 190419_e3 EPSC 80 2.961 0.512 0.220 False
experienced 190419_e3 IPSC 80 -0.329 0.388 0.931 False
experienced 190424_e1 EPSC 80 0.475 0.400 0.824 False
experienced 190424_e1 IPSC 75 2.206 0.587 0.324 False
experienced 190424_e2 EPSC 80 -3.193 0.412 0.241 False
experienced 190424_e2 IPSC 55 2.509 0.618 0.617 False
experienced 190424_e3 EPSC 80 0.638 0.512 0.628 False
experienced 190424_e3 IPSC 80 1.761 0.575 0.346 False
naive 060519_e2 EPSC 80 -0.350 0.488 0.872 False
naive 060519_e2 IPSC 80 -0.083 0.450 0.931 False
naive 190314_e1 EPSC 40 0.571 0.475 0.450 False
naive 190314_e1 IPSC 79 -0.462 0.418 0.125 False
naive 190314_e5 EPSC 80 0.475 0.625 0.081 False
naive 190314_e5 IPSC 80 0.112 0.462 0.761 False
naive 190315_e2 EPSC 40 -0.108 0.425 0.758 False
naive 190315_e3 EPSC 40 0.019 0.525 0.947 False
naive 190315_e3 IPSC 80 -0.364 0.488 0.134 False
naive 190320_e1 EPSC 80 0.535 0.400 0.723 False
naive 190320_e2 EPSC 40 -1.550 0.375 0.180 False
naive 190320_e5 EPSC 80 -2.619 0.450 0.220 False
naive 190326_e1 EPSC 40 26.387 1.000 0.000 True
naive 190326_e1 IPSC 40 -4.639 0.475 0.051 False
naive 190326_e2 EPSC 80 2.755 0.512 0.238 False
naive 190326_e2 IPSC 133 0.036 0.534 0.979 False
naive 190326_e4 EPSC 40 -0.012 0.525 0.996 False
naive 190326_e5 EPSC 80 -0.102 0.438 0.962 False
naive 190329_e2 EPSC 80 0.137 0.412 0.775 False
naive 190329_e3 EPSC 80 0.120 0.350 0.914 False
(2) Cells with a mean response in the expected direction 0-300 ms after onset at p < 0.05:
sum size
kind group
EPSC experienced 0 12
naive 0 14
IPSC experienced 0 7
naive 0 6
0-300 ms response (pA, expected direction), median and range over cells: {'EPSC': {'median': 0.34, 'min': -4.18, 'max': 4.05}, 'IPSC': {'median': 0.31, 'min': -2.06, 'max': 2.54}}
(3) Files with a sweep-locked deflection anywhere 0-3 s after onset at p < 0.05 (5% expected by chance):
n_files significant
set kind
5 prototypes EPSC 28 1
morph set EPSC 55 9
IPSC 27 3
(4) k = 2: share of call trials significant 0.039 (mean over cells); share of sham trials passing the same rule 0.037
Cells with at least 5 significant prototype trials:
cells included max_sig_proto
kind group
EPSC experienced 12 0 4.0
naive 14 1 5.0
IPSC experienced 7 0 1.0
naive 6 0 1.0
Included: [{'group': 'naive', 'cell': '190326_e1', 'kind': 'EPSC', 'proto_sig': 5.0}]
(4) k = 3: share of call trials significant 0.011 (mean over cells); share of sham trials passing the same rule 0.008
Cells with at least 5 significant prototype trials:
cells included max_sig_proto
kind group
EPSC experienced 12 0 2.0
naive 14 0 3.0
IPSC experienced 7 0 0.0
naive 6 0 1.0
Included: []
Baseline current s.d. within a sweep (pA), median by group and current:
kind group
EPSC experienced 11.41
naive 5.33
IPSC experienced 21.89
naive 6.17
# Trial-averaged current of every cell, aligned to call onset, by ISI (expected direction up:
# inward for EPSCs, outward for IPSCs), 25 ms bins, baseline the 300 ms before onset.
keys = sorted(cell_traces, key=lambda k: (k[2], k[0], k[1]))
ncol = 7
fig, axs = plt.subplots(int(np.ceil(len(keys) / ncol)), ncol, figsize=(16, 2.0 * np.ceil(len(keys) / ncol)), sharex=True)
t_ax = (np.arange(int((S.TRACE_WIN_S[1] - S.TRACE_WIN_S[0]) / S.TRACE_BIN_S)) * S.TRACE_BIN_S + S.TRACE_WIN_S[0]).reshape(-1, 5).mean(1)
lo_all, hi_all, out = np.inf, -np.inf, 0
for ax, key in zip(axs.flat, keys):
X, isi = cell_traces[key]
for c, col in zip((125, 175, 275, 575), ("tab:blue", "k", "tab:orange", "tab:red")):
m = np.nanmean(X[isi == c], axis=0).reshape(-1, 5).mean(1)
ax.plot(t_ax, m, color=col, lw=0.8, label=str(c))
lo_all, hi_all = min(lo_all, np.nanmin(m)), max(hi_all, np.nanmax(m))
out += int(((m < -30) | (m > 30)).sum())
ax.axvline(0, color="grey", lw=0.5); ax.axhline(0, color="grey", lw=0.5)
ax.set_ylim(-30, 30)
ax.set_title(f"{key[0][:3]} {key[1]} {key[2]}", fontsize=7)
for ax in list(axs.flat)[len(keys):]:
ax.axis("off")
axs.flat[0].legend(fontsize=6, title="ISI", title_fontsize=6)
fig.supxlabel("s from call onset"); fig.supylabel("pA, expected direction up")
plt.tight_layout(); plt.show()
print(f"Trial means range from {lo_all:.1f} to {hi_all:.1f} pA; {out} 25 ms points fall outside the +-30 pA axes.")
grand = {k: np.nanmean([np.nanmean(cell_traces[c][0], 0) for c in keys if c[2] == k], axis=0) for k in ("EPSC", "IPSC")}
for k, m in grand.items():
print(f"Mean over {sum(c[2] == k for c in keys)} {k} cells, 0-300 ms after onset: {np.nanmean(m[nb_pre:nb_pre + b300]):.2f} pA; "
f"largest 25 ms bin 0-1 s after onset: {np.nanmax(m[nb_pre:nb_pre + 200].reshape(-1, 5).mean(1)):.2f} pA")
Trial means range from -87.5 to 62.3 pA; 166 25 ms points fall outside the +-30 pA axes. Mean over 26 EPSC cells, 0-300 ms after onset: 0.25 pA; largest 25 ms bin 0-1 s after onset: 2.19 pA Mean over 13 IPSC cells, 0-300 ms after onset: 0.21 pA; largest 25 ms bin 0-1 s after onset: 2.45 pA
# The files of check (3) with a sweep-locked deflection at p < 0.05: when the deflection peaks, its
# sign (positive is the expected direction, inward for EPSCs and outward for IPSCs), and the first
# 20 ms bin whose |t| exceeds the 95th percentile of the file's sign-flip null. A current that follows
# the calls would have a consistent latency after onset and the expected sign; chance deflections
# fall anywhere in the 3 s and in either direction.
print("Binomial p of the counts of check (3) against a rate of 0.05 (one-sided): all files %d of %d, p = %.4f; morph-set EPSC files %d of %d, p = %.4f"
% ((scan.p < 0.05).sum(), len(scan), stats.binomtest(int((scan.p < 0.05).sum()), len(scan), 0.05, alternative="greater").pvalue,
((scan.p < 0.05) & (scan.set == "morph set") & (scan.kind == "EPSC")).sum(), ((scan.set == "morph set") & (scan.kind == "EPSC")).sum(),
stats.binomtest(int(((scan.p < 0.05) & (scan.set == "morph set") & (scan.kind == "EPSC")).sum()),
int(((scan.set == "morph set") & (scan.kind == "EPSC")).sum()), 0.05, alternative="greater").pvalue))
sig13 = scan[scan.p < 0.05].sort_values(["group", "cell", "file"])
print(sig13.round(3).to_string(index=False))
print("Of these, peaking in the expected direction:", int((sig13["peak pA, expected direction"] > 0).sum()),
"| peaking within 1 s of onset in the expected direction:",
int(((sig13["peak pA, expected direction"] > 0) & (sig13["peak s"] < 1)).sum()))
print("Peak time over all", len(scan), "files (s): quartiles", scan["peak s"].quantile([0.25, 0.5, 0.75]).round(2).tolist())
fig, axs = plt.subplots(2, 7, figsize=(16, 4.4), sharex=True)
t20 = (np.arange(150) + 0.5) * 0.02
lo13, hi13 = np.inf, -np.inf
for ax, (_, r) in zip(axs.flat, sig13.iterrows()):
Xs, mu, tc = scan_curves[r.file]
se = np.nanstd(Xs, 0) / np.sqrt(len(Xs))
ax.fill_between(t20, mu - se, mu + se, color="0.8"); ax.plot(t20, mu, color="k", lw=0.8)
ax.axhline(0, color="grey", lw=0.5); ax.axvline(r["peak s"], color="tab:red", lw=0.5)
ax.set_title(f"{r.group[:3]} {r.cell} {r.file}\n{r.kind}, {r.set}", fontsize=7)
lo13, hi13 = min(lo13, np.nanmin(mu - se)), max(hi13, np.nanmax(mu + se))
for ax in list(axs.flat)[len(sig13):]:
ax.axis("off")
fig.supxlabel("s from call onset"); fig.supylabel("pA, expected direction up (mean +- s.e.)")
plt.tight_layout(); plt.show()
print(f"Plotted means +- s.e. range from {lo13:.1f} to {hi13:.1f} pA (axes fit each file; red line: peak).")
# The files whose deflection is in the expected direction within the call: the current 0.4 to 1.2 s
# after onset (baseline the 300 ms before onset), per trial and by ISI, with the call's duration.
for f in sig13[(sig13["peak pA, expected direction"] > 0)].file:
d = iv_sweeps[iv_sweeps.file == f].dropna(subset=["onset"])
k = iv_list.loc[iv_list.file == f, "holding"].iloc[0]
X = np.stack(d.trace.values).astype(float) * (-1 if k < -30 else 1)
X = X - np.nanmean(X[:, nb_pre - 60:nb_pre], 1, keepdims=True)
w = np.nanmean(X[:, nb_pre + 80:nb_pre + 240], 1)
print(f"{f}: 0.4-1.2 s after onset {w.mean():.1f} pA, expected sign in {(w > 0).sum()} of {len(w)} trials; by ISI "
f"{pd.Series(w).groupby(d.isi.values).mean().round(1).to_dict()}; call lasts {float((d.offset - d.onset).median()):.2f} s (median)")
Binomial p of the counts of check (3) against a rate of 0.05 (one-sided): all files 13 of 110, p = 0.0034; morph-set EPSC files 9 of 55, p = 0.0015
group cell file kind set sweeps max |t| p peak s peak pA, expected direction first bin above null s bins above null
experienced 190313_e2 19313021 EPSC morph set 40 3.813 0.020 2.25 -15.002 2.25 1
experienced 190313_e2 19313023 EPSC morph set 31 5.859 0.000 0.77 37.101 0.47 21
experienced 190403_e1 19403002 EPSC morph set 40 3.188 0.030 0.35 -7.995 0.35 2
experienced 190410_e1 19410002 EPSC morph set 40 3.674 0.010 1.25 -13.038 1.23 2
experienced 190410_e1 19410006 EPSC morph set 40 3.633 0.000 2.75 30.584 2.71 5
naive 190315_e3 19315013 IPSC morph set 40 3.607 0.030 1.79 -2.702 1.79 1
naive 190315_e3 19315017 IPSC morph set 40 3.399 0.045 2.91 -1.460 2.91 1
naive 190320_e1 19320002 EPSC morph set 40 3.330 0.010 0.11 -5.689 0.09 2
naive 190320_e2 19320018 EPSC morph set 40 4.160 0.000 2.47 -6.356 0.15 11
naive 190320_e5 19320028 EPSC morph set 40 3.640 0.025 2.09 -15.700 2.01 2
naive 190326_e1 19326001 EPSC 5 prototypes 15 4.204 0.015 0.79 48.166 0.51 8
naive 190326_e1 19326003 EPSC morph set 40 8.623 0.000 0.73 41.851 0.39 37
naive 190326_e1 19326005 IPSC morph set 40 4.036 0.010 1.21 -16.696 1.13 3
Of these, peaking in the expected direction: 4 | peaking within 1 s of onset in the expected direction: 3
Peak time over all 110 files (s): quartiles [0.57, 1.47, 2.28]
Plotted means +- s.e. range from -20.8 to 65.0 pA (axes fit each file; red line: peak).
19313023: 0.4-1.2 s after onset 22.0 pA, expected sign in 25 of 31 trials; by ISI {125.0: 20.1, 175.0: 28.2, 275.0: 10.5, 575.0: 30.0}; call lasts 0.81 s (median)
19410006: 0.4-1.2 s after onset 6.7 pA, expected sign in 22 of 40 trials; by ISI {125.0: 12.1, 175.0: 8.6, 275.0: -9.6, 575.0: 15.7}; call lasts 1.13 s (median)
19326001: 0.4-1.2 s after onset 37.9 pA, expected sign in 13 of 15 trials; by ISI {175.0: 37.9}; call lasts 0.91 s (median)
19326003: 0.4-1.2 s after onset 31.2 pA, expected sign in 34 of 40 trials; by ISI {125.0: 38.5, 175.0: 31.2, 275.0: 44.1, 575.0: 10.9}; call lasts 1.14 s (median)
The microphone channel gives every sweep of the 82 complete morph-set files a call, and in each of them the four call durations fall in four separate groups that repeat in a fixed order (prototype, 125, 275, 575 ms); the median measured silent gaps of the four groups are 142, 192, 301 and 604 ms. The stimulus therefore follows the legends' ISIs (125, 175, 275, 575 ms), not the Methods' "125, 225, 575". The order is fixed, not pseudorandom. Playback starts at the same time in every sweep (1.519 to 1.577 s; within a file the onsets spread by 0.4 ms at the median). The first syllable of many calls is much quieter than the rest, and the onset of the first loud syllable, the rule used before revision, comes a median 193 ms (up to 644 ms) after the start of the call, more than 50 ms late in 51 of 83 files; the call onset is now the start of the sound, and the earlier rule is a variant. File 19424013 is listed at -70 mV but carries about +560 pA throughout, as recorded at a depolarized potential; 19411001 is listed at -70 mV and its note says "some sweeps are at positive potentials". Sweeps whose current contradicts the listed potential are dropped (the counts are printed above); keeping them is a variant below.
The recorded currents almost never follow the calls. (1) The Methods' evoked measure is in the expected direction at p < 0.05 in 2 of the 39 cell recordings (one naive EPSC cell, 190326_e1, and one experienced IPSC cell with 19 trials), against 3 of 39 for the same test before the call; 48% of trials have the expected sign, against 47% before the call. (2) No cell has a mean response in the first 300 ms after onset at p < 0.05 (cell means -4.2 to 4.1 pA for EPSCs), and the mean over cells is 0.25 pA for EPSCs and 0.21 pA for IPSCs. (3) 13 of 110 files have a sweep-locked deflection anywhere in the 3 s after onset at p < 0.05 against sign-flipped trials, more than the 5.5 expected by chance (binomial p = 0.0034; 9 of the 55 morph-set EPSC files, p = 0.0015), and only 1 of the 28 files of five prototypes, which were played to find each cell's best call. Listed and drawn, 3 of the 13 carry a current in the expected direction during the call: both EPSC files of naive cell 190326_e1 (peaks of 48 and 42 pA at 0.79 and 0.73 s; expected sign 0.4 to 1.2 s after onset in 13 of 15 and 34 of 40 trials) and one of the two EPSC files of experienced cell 190313_e2 (37 pA at 0.77 s, 25 of 31 trials). In all three the current first exceeds the file's null 0.39 to 0.51 s after the start of the sound, while the call lasts 0.8 to 1.1 s. This is a call-locked inward current with a consistent latency, which the 0-300 ms test of check (2) misses; because playback starts at the same time in every sweep, a current locked to the sweep cannot be told apart from one locked to the call. The other 10 files peak at scattered times (0.11 to 2.91 s after onset), 9 of them against the expected direction, and the tenth (19410006) peaks at 2.75 s, after the call has ended. (4) With a significant PSC defined as a trial beyond 2 s.d. of the cell's sham measure in the expected direction, 3.9% of call trials pass against 3.7% of sham trials, and 1 of 39 cell recordings has the five significant prototype trials the Methods require (none at 3 s.d.); that recording is 190326_e1, a naive cell. The figure draws every cell's trial means by ISI. The baseline current varies about twice as much in experienced as in naive recordings (s.d. 11.4 against 5.3 pA for EPSCs, 21.9 against 6.2 pA for IPSCs).
sl_files, _ = S.figshare_files("slice")
sl_list = S.slice_list(pd.read_excel(__import__("io").BytesIO(S.fetch(sl_files["Recording List.xlsx"]["download_url"])), header=None))
sl_list["on_figshare"] = sl_list.file.map(lambda f: f + ".abf" in sl_files)
print("Slice recordings listed:", len(sl_list), "| on figshare:", int(sl_list.on_figshare.sum()))
print("Missing:", sl_list[~sl_list.on_figshare].to_dict("records"))
from concurrent.futures import ThreadPoolExecutor
def _slice(f):
return S.slice_amplitudes(S.fetch(sl_files[f + ".abf"]["download_url"]), f + ".abf")
with ThreadPoolExecutor(8) as ex:
sl_amps = pd.concat(list(ex.map(_slice, sl_list[sl_list.on_figshare].file)))
sl_amps = sl_amps.merge(sl_list, on="file")
print("Cells per rate:", sl_list[sl_list.on_figshare].groupby("rate").cell.nunique().to_dict(), "(paper: 75 ms 10, 175 ms 14, 575 ms 13)")
print("Pulse periods (ms) found in the files, by rate bin:")
print(sl_amps.groupby("rate").period_ms.unique().apply(lambda v: sorted(np.round(v, 1))).to_string())
print("Holding levels of the two trains (mV):", sorted(sl_amps.level.unique()))
print("Pulses per train:", sorted(sl_amps.pulse.unique()))
per_trial = sl_amps[sl_amps.sweep >= 0]
print("Trials (sweeps) per cell and rate: median", int(per_trial.groupby(["cell", "rate", "file"]).sweep.nunique().groupby(["cell", "rate"]).sum().median()))
print("First-pulse amplitudes (pA) by current:")
print(per_trial[per_trial.pulse == 1].groupby("kind").amp.describe().round(1).to_string())
Slice recordings listed: 75 | on figshare: 74
Missing: [{'cell': 'Cell 20', 'rate': 575, 'file': '19o11005', 'on_figshare': False}]
Cells per rate: {75: 10, 175: 14, 575: 12} (paper: 75 ms 10, 175 ms 14, 575 ms 13)
Pulse periods (ms) found in the files, by rate bin:
rate
75 [50.0, 87.5]
175 [150.0, 175.4]
575 [550.1, 574.7]
Holding levels of the two trains (mV): [np.float64(-70.0), np.float64(-30.0), np.float64(-20.0), np.float64(-10.0), np.float64(0.0)]
Pulses per train: [np.int64(1), np.int64(2), np.int64(3), np.int64(4), np.int64(5)]
Trials (sweeps) per cell and rate: median 10
First-pulse amplitudes (pA) by current:
count mean std min 25% 50% 75% max
kind
EPSC 537.0 417.1 475.3 13.3 107.6 205.3 538.5 2279.1
IPSC 537.0 349.9 404.3 -97.3 61.4 241.4 476.3 1979.2
Each slice sweep holds one train of five pulses at -70 mV (EPSCs) and one at a depolarized level (IPSCs; 0 mV in most files, -10 to -30 mV in some, within the Methods' "-40 to 0 mV"). The pulse periods (50 and 87.5 ms; 150 and 175 ms; 550 and 575 ms) fall in the Methods' 75, 175 and 575 ms bins of plus or minus 25 ms, except 50 ms, at the edge of the 75 ms bin. One 575 ms cell (Cell 20, file 19o11005) is listed but not on figshare, so 12 of 13 cells are tested at 575 ms.
ox_files, _ = S.figshare_files("oxytocin")
ox_sheet = [n for n in ox_files if n.endswith(".xlsx")][0]
ox_list = S.oxytocin_list(pd.read_excel(__import__("io").BytesIO(S.fetch(ox_files[ox_sheet]["download_url"])), header=None))
ox_list["on_figshare"] = ox_list.file.map(lambda f: f + ".abf" in ox_files)
print("Oxytocin files listed:", len(ox_list), "| on figshare:", int(ox_list.on_figshare.sum()))
print("Cells:", ox_list.groupby("group").cell.nunique().to_dict(), "(paper: 9 oxytocin, 7 ACSF)")
print("Roles from the spreadsheet labels:", ox_list.groupby(["group", "role"]).size().to_dict())
def _ox(f):
df, start = S.spikes_per_pulse(S.fetch(ox_files[f + ".abf"]["download_url"]), f + ".abf")
df["file_start"] = start
return df
with ThreadPoolExecutor(8) as ex:
ox_sp = pd.concat(list(ex.map(_ox, ox_list[ox_list.on_figshare & (ox_list.role != "iv")].file)))
ox_sp = ox_sp.merge(ox_list[["file", "group", "cell", "role", "label"]], on="file")
ox_sp["abs_t"] = pd.to_datetime(ox_sp.file_start) + pd.to_timedelta(ox_sp.t, unit="s")
print("Pulse period (ms):", sorted(ox_sp.period_ms.round(2).unique()), "| pulses per sweep:",
sum(c.startswith("stim") for c in ox_sp.columns), "| sweep interval (s):", sorted(ox_sp.groupby("file").t.diff().dropna().round(1).unique()))
print("Current steps (pA):", sorted(ox_sp.step_pA.unique()))
Oxytocin files listed: 94 | on figshare: 94
Cells: {'control': 7, 'oxytocin': 9} (paper: 9 oxytocin, 7 ACSF)
Roles from the spreadsheet labels: {('control', 'baseline'): 6, ('control', 'iv'): 5, ('control', 'other'): 19, ('oxytocin', 'baseline'): 11, ('oxytocin', 'iv'): 17, ('oxytocin', 'other'): 16, ('oxytocin', 'wash in'): 12, ('oxytocin', 'wash out'): 8}
Pulse period (ms): [np.float64(588.25)] | pulses per sweep: 5 | sweep interval (s): [np.float64(12.0)] Current steps (pA): [np.float64(50.0), np.float64(80.0), np.float64(90.0), np.float64(100.0), np.float64(110.0), np.float64(120.0), np.float64(150.0), np.float64(160.0), np.float64(170.0), np.float64(180.0), np.float64(190.0), np.float64(200.0), np.float64(210.0), np.float64(220.0), np.float64(225.0), np.float64(230.0), np.float64(240.0), np.float64(250.0), np.float64(255.0), np.float64(270.0), np.float64(280.0), np.float64(310.0), np.float64(315.0), np.float64(320.0)]
op = S.operant_files()
with ThreadPoolExecutor(16) as ex:
op["touches"] = list(ex.map(S.read_touches, op.url))
op["n_samples"] = op.touches.map(len)
op["first_s"] = op.touches.map(lambda v: v.min() if len(v) else np.nan)
op["last_s"] = op.touches.map(lambda v: v.max() if len(v) else np.nan)
op["interval_ms"] = op.touches.map(S.sampling_interval) * 1000
print("Operant files:", len(op), "| mice per group:", op.groupby("group").mouse.nunique().to_dict(), "(paper: 7 per group)")
print("Sessions per mouse:", op.groupby("mouse").session.nunique().value_counts().to_dict(),
"| missing sessions:", {m: sorted(set(range(1, 9)) - set(g.session)) for m, g in op.groupby("mouse") if set(range(1, 9)) - set(g.session)})
print("Part b files joined to their session:", op[op.part_b].name.tolist())
print("Touch times (s): first %.2f to %.2f, last %.1f to %.1f (sessions run 1800 s after a start offset)"
% (op.first_s.min(), op.first_s.max(), op.last_s.min(), op.last_s.max()))
print("Median interval between touch samples per session (ms, from times below 10 s, printed at 0.1 ms): median %.2f, range %.2f-%.2f, over %d sessions with such times"
% (op.interval_ms.median(), op.interval_ms.min(), op.interval_ms.max(), op.interval_ms.notna().sum()))
allt = np.concatenate(op.touches.values)
for lo, hi in [(0, 10), (10, 100), (100, 1000), (1000, 3000)]:
v = allt[(allt >= lo) & (allt < hi)]
d = np.diff(np.unique(v))
print(f"times {lo}-{hi} s: smallest step between distinct values {d.min():.4f} s; "
f"share of samples repeating the previous value {np.mean(np.diff(v) == 0):.2f}")
Operant files: 170 | mice per group: {'fast': 7, 'proto': 7, 'slow': 7} (paper: 7 per group)
Sessions per mouse: {8: 19, 7: 2} | missing sessions: {'NV24': [5], 'NV38': [3]}
Part b files joined to their session: ['nv38_slow_v4b.csv', 'nv30_proto_v6b.csv', 'nv31_proto_v4b.csv', 'nv31_proto_v6b.csv']
Touch times (s): first 2.36 to 1262.80, last 113.8 to 1866.0 (sessions run 1800 s after a start offset)
Median interval between touch samples per session (ms, from times below 10 s, printed at 0.1 ms): median 0.70, range 0.50-0.80, over 31 sessions with such times
times 0-10 s: smallest step between distinct values 0.0001 s; share of samples repeating the previous value 0.00
times 10-100 s: smallest step between distinct values 0.0010 s; share of samples repeating the previous value 0.36
times 100-1000 s: smallest step between distinct values 0.0100 s; share of samples repeating the previous value 0.94 times 1000-3000 s: smallest step between distinct values 0.1000 s; share of samples repeating the previous value 0.99
The touch times are printed with five significant digits (MATLAB dlmwrite's default), so they
step by 0.1 ms below 10 s, 1 ms to 100 s, 10 ms to 1000 s and 100 ms after that, and most samples
after 100 s repeat the value before them. A press can only be resolved to that step: a gap must
exceed one and a half local steps to end a press (the helper's presses, which takes 1 ms as the
step below 100 s). Where the times are printed at 0.1 ms, the task code polls the sensor every
0.70 ms at the median (0.50 to 0.80 ms over the 31 sessions with touches before 10 s).
subject-group-link¶
The dandiset's subjects can be assigned to the paper's imaging groups (experienced or naive virgin, dam, right cortex, cohousing, CH+575, opto-OT; excitatory, inhibitory or pan-neuronal indicator), with stimulus identity (prototype or morph ISI) for each presentation.
titles, coll = S.figshare_collection_titles()
print(f"figshare collection {coll['id']} version {coll['version']} (pinned {S.FIGSHARE_COLLECTION[1]}): "
f"{coll['articles_count']} articles; listed now: {len(titles)}")
tt = titles.dropna(subset=["mouse"]).set_index("mouse")
g = groups.join(tt[["title", "title_group", "title_cells", "title_hemisphere"]])
print("Subjects with a figshare item of the same mouse id:", int(g.title.notna().sum()), "of", len(g))
known = g.group != "unknown"
print("Experience group, description against title, where the description gives one: agree",
int((g.group[known] == g.title_group[known]).sum()), "of", int(known.sum()))
print("Cell class agree:", int((g.cells == g.title_cells).sum()), "of", len(g),
"| disagreements:", g[g.cells != g.title_cells][["cells", "title"]].to_dict("index"))
print("Right hemisphere agree:", int(((g.hemisphere == "right") == (g.title_hemisphere == "right")).sum()), "of", len(g))
print("Group taken from the title where the description gives none:",
g[~known][["title", "title_group"]].to_dict("index"))
g["group_source"] = np.where(known, "description", "figshare title")
g.loc[~known, "group"] = g.loc[~known, "title_group"]
g["paper_group"] = "other"
g.loc[(g.group == "EV") & (g.cells == "excitatory") & (g.hemisphere == "left"), "paper_group"] = "EV excitatory, left"
g.loc[(g.group == "EV") & (g.cells == "inhibitory"), "paper_group"] = "EV interneurons"
g.loc[(g.group == "EV") & (g.hemisphere == "right"), "paper_group"] = "EV right cortex"
g.loc[g.group == "dam", "paper_group"] = "dam"
g.loc[g.cells.str.startswith("pan"), "paper_group"] = "CH+575 opto-OT"
g.loc[g.ch575 & ~g.cells.str.startswith("pan"), "paper_group"] = "CH+575"
g.loc[(g.group == "NV") & (g.cells == "inhibitory") & g.cohoused, "paper_group"] = "NV interneurons, chronic cohousing"
g.loc[(g.group == "NV") & (g.cells == "excitatory") & g.cohoused & ~g.ch575 & ~g.not_cohoused_control, "paper_group"] = "NV excitatory, chronic cohousing"
g.loc[g.not_cohoused_control, "paper_group"] = "NV excitatory, not cohoused (control)"
tone_only = files.groupby("subject_id").session_description.apply(lambda s: bool(s.str.contains("tone|kmtf", case=False).all()))
g.loc[tone_only.reindex(g.index).fillna(False).astype(bool), "paper_group"] = "tones only (no pup calls)"
counts = g.groupby("paper_group").apply(lambda d: pd.Series({"n": len(d), "subjects": ", ".join(sorted(d.index))}))
print()
print(counts.to_string())
print("\nTones-only subjects with their group (description or title):",
g[g.paper_group.str.startswith("tones")][["group", "cells", "group_source"]].to_dict("index"))
paper = {"EV excitatory, left": 9, "EV interneurons": 5, "EV right cortex": 4, "dam": 1, "CH+575": 6,
"CH+575 opto-OT": 4, "NV interneurons, chronic cohousing": 6}
print("\nAgainst the paper's counts:")
for k, v in paper.items():
print(f" {k}: dandiset {int((g.paper_group == k).sum())}, paper {v}")
chronic = g[g.first_retrieval]
print(f" chronic cohousing (subjects with a First Retrieval session): dandiset {len(chronic)} "
f"({', '.join(chronic.index)}), paper 13")
nv_exc = g[(g.group == "NV") & (g.cells == "excitatory")]
print(f" naive excitatory mice of any kind: {len(nv_exc)}; paper's Fig. 1f NV excitatory N=12 (which ones is not said)")
print("\nSubjects without a paper group:", g[g.paper_group == "other"].index.tolist())
print("Subjects whose experience group could not be read from description or title:", g[g.group == "unknown"].index.tolist())
print("\nStimulus identity per presentation: stimulus series in any file:",
int((files.stimulus.map(len) > 0).sum()), "| interval tables:", int((files.intervals.map(len) > 0).sum()))
figshare collection 5043830 version 6 (pinned 6): 60 articles; listed now: 60
Subjects with a figshare item of the same mouse id: 54 of 54
Experience group, description against title, where the description gives one: agree 49 of 49
Cell class agree: 54 of 54 | disagreements: {}
Right hemisphere agree: 54 of 54
Group taken from the title where the description gives none: {'EV56': {'title': 'EV56_Experienced_Exc', 'title_group': 'EV'}, 'EV62': {'title': 'EV62_Experienced_Exc', 'title_group': 'EV'}, 'NV140': {'title': 'NV140_Naive_Exc', 'title_group': 'NV'}, 'NV64': {'title': 'NV64_Naive_Exc', 'title_group': 'NV'}, 'NV66': {'title': 'NV66_Naive_Exc', 'title_group': 'NV'}}
n subjects
paper_group
CH+575 6 NV57, NV58, NV60, NV63, NV65, NV77
CH+575 opto-OT 4 OXTHALO-10, OXTHALO-15, OXTHALO-17, OXTHALO-19
EV excitatory, left 9 EV06, EV17, EV23, EV27, EV30, EV35, EV38, EV42, EV52
EV interneurons 5 EV104, EV105, EV31, EV40, EV44
EV right cortex 4 EV122, EV123, EV84, EV86
NV excitatory, chronic cohousing 9 NV115, NV21, NV32, NV36, NV37, NV39, NV43, NV50, NV54
NV excitatory, not cohoused (control) 5 NV103, NV108, NV119, NV120, NV84
NV interneurons, chronic cohousing 6 NV106, NV25, NV28, NV33, NV41, NV47
dam 1 M001
tones only (no pup calls) 5 EV56, EV62, NV140, NV64, NV66
Tones-only subjects with their group (description or title): {'EV56': {'group': 'EV', 'cells': 'excitatory', 'group_source': 'figshare title'}, 'EV62': {'group': 'EV', 'cells': 'excitatory', 'group_source': 'figshare title'}, 'NV140': {'group': 'NV', 'cells': 'excitatory', 'group_source': 'figshare title'}, 'NV64': {'group': 'NV', 'cells': 'excitatory', 'group_source': 'figshare title'}, 'NV66': {'group': 'NV', 'cells': 'excitatory', 'group_source': 'figshare title'}}
Against the paper's counts:
EV excitatory, left: dandiset 9, paper 9
EV interneurons: dandiset 5, paper 5
EV right cortex: dandiset 4, paper 4
dam: dandiset 1, paper 1
CH+575: dandiset 6, paper 6
CH+575 opto-OT: dandiset 4, paper 4
NV interneurons, chronic cohousing: dandiset 6, paper 6
chronic cohousing (subjects with a First Retrieval session): dandiset 14 (NV115, NV21, NV25, NV28, NV32, NV33, NV36, NV37, NV39, NV41, NV43, NV47, NV50, NV54), paper 13
naive excitatory mice of any kind: 23; paper's Fig. 1f NV excitatory N=12 (which ones is not said)
Subjects without a paper group: []
Subjects whose experience group could not be read from description or title: []
Stimulus identity per presentation: stimulus series in any file: 0 | interval tables: 0
Result: partially reproduced. Every subject has a figshare item titled with its mouse id, and the titles agree with the experiment descriptions on experience group (49 of 49 where the description gives one), cell class (54 of 54) and right hemisphere (54 of 54). The five mice imaged only with tones take their group from the titles: EV56 and EV62 experienced (their descriptions call them NV56 and NV62), NV64, NV66 and NV140 naive. The counts match the paper for EV excitatory in left cortex (9), EV interneurons (5), EV right cortex (4), the dam (1), CH+575 (6), CH+575 opto-OT (4) and naive interneurons (6). 14 subjects have a first retrieval session, against the paper's 13 chronically imaged virgins, and 23 naive excitatory mice of several kinds stand against the 12 of Fig. 1f, which the paper does not name. No file holds a stimulus series or a trial table, so no presentation can be assigned its call or ISI, which the criterion makes a partial result. Three recordings are deposited under both NV58 and NV63.
fig1g-ipsc-depress-faster¶
In pup-naive auditory cortical slices, IPSCs depress faster than EPSCs at the prototypical stimulation rate (175 ms), while EPSCs and IPSCs depress similarly at 75 or 575 ms.
Method from the Methods ("The peak amplitude of evoked EPSCs and IPSCs were measured and normalized to the event evoked by the first stimulation on each trial (Sn/S1)") and the claims file: per cell the mean Sn/S1 over trials; IPSC against EPSC by one-way ANOVA (with two groups the same test as an unpaired t-test) under reading (a), per pulse with Bonferroni over pulses 2 to 5, and (b), the mean over pulses 2 to 5. The peak is the largest inward (EPSC) or outward (IPSC) current 2 to 30 ms after each pulse against the 5 ms before it, a standard choice stated in the helper. A trial whose first IPSC or EPSC is not above baseline gives no ratio and is dropped; the mean trace of each file is a variant.
def stp_tests(amps, label):
a = amps.pivot_table(index=["rate", "cell", "kind", "file", "sweep"], columns="pulse", values="amp")
dropped = int((a[1] <= 0).sum())
a = a[a[1] > 0]
c = a.div(a[1], axis=0).groupby(["rate", "cell", "kind"]).mean()
rows = []
for rate in (75, 175, 575):
e = c.xs((rate, "EPSC"), level=("rate", "kind"))
i = c.xs((rate, "IPSC"), level=("rate", "kind"))
p = np.array([stats.ttest_ind(i[k], e[k]).pvalue for k in (2, 3, 4, 5)])
mi, me = i[[2, 3, 4, 5]].mean(axis=1), e[[2, 3, 4, 5]].mean(axis=1)
rows.append({"variant": label, "rate": rate, "n_E": len(e), "n_I": len(i),
"E_S2..S5": e[[2, 3, 4, 5]].mean().round(2).tolist(), "I_S2..S5": i[[2, 3, 4, 5]].mean().round(2).tolist(),
"(a) p per pulse, Bonferroni": np.round(np.minimum(p * 4, 1), 3).tolist(),
"(a) I below E at p<0.05": bool(((p * 4) < 0.05).any() and (i[[2, 3, 4, 5]].mean() < e[[2, 3, 4, 5]].mean())[(p * 4) < 0.05].all()),
"(b) mean S2-5 E": round(me.mean(), 2), "(b) mean S2-5 I": round(mi.mean(), 2),
"(b) p": round(stats.ttest_ind(mi, me).pvalue, 4),
"(b) paired p": round(stats.ttest_rel(mi.loc[mi.index.intersection(me.index)], me.loc[mi.index.intersection(me.index)]).pvalue, 4)})
return pd.DataFrame(rows), dropped
t1, d1 = stp_tests(sl_amps[sl_amps.sweep >= 0], "per trial")
t2, d2 = stp_tests(sl_amps[sl_amps.sweep < 0], "mean trace per file")
print(f"Trials dropped for a first response not above baseline: {d1} (per trial); files dropped: {d2} (mean trace)")
print(pd.concat([t1, t2]).to_string(index=False))
Trials dropped for a first response not above baseline: 35 (per trial); files dropped: 7 (mean trace)
variant rate n_E n_I E_S2..S5 I_S2..S5 (a) p per pulse, Bonferroni (a) I below E at p<0.05 (b) mean S2-5 E (b) mean S2-5 I (b) p (b) paired p
per trial 75 10 10 [0.8, 0.61, 0.53, 0.49] [0.79, 0.79, 0.74, 0.58] [1.0, 1.0, 1.0, 1.0] False 0.61 0.72 0.7241 0.7292
per trial 175 14 13 [0.78, 0.67, 0.65, 0.62] [1.01, 0.95, 0.87, 0.78] [1.0, 1.0, 1.0, 1.0] False 0.68 0.90 0.3312 0.3742
per trial 575 12 11 [0.81, 0.74, 0.76, 0.61] [0.74, 0.89, 0.69, 0.73] [1.0, 1.0, 1.0, 1.0] False 0.73 0.76 0.8175 0.7739
mean trace per file 75 10 9 [0.79, 0.6, 0.52, 0.47] [0.84, 0.77, 0.72, 0.71] [1.0, 1.0, 1.0, 1.0] False 0.60 0.76 0.5158 0.5583
mean trace per file 175 14 13 [0.75, 0.65, 0.62, 0.59] [0.58, 0.45, 0.62, 0.6] [0.153, 0.06, 1.0, 1.0] False 0.65 0.57 0.3906 0.3992
mean trace per file 575 12 11 [0.75, 0.69, 0.68, 0.53] [0.61, 0.58, 0.53, 0.49] [0.15, 0.132, 0.134, 1.0] False 0.66 0.55 0.0795 0.0219
a = sl_amps[sl_amps.sweep >= 0].pivot_table(index=["rate", "cell", "kind", "file", "sweep"], columns="pulse", values="amp")
a = a[a[1] > 0]
c = a.div(a[1], axis=0).groupby(["rate", "cell", "kind"]).mean()
fig, axs = plt.subplots(1, 3, figsize=(10, 3), sharey=True)
for ax, rate in zip(axs, (75, 175, 575)):
for kind, col in (("EPSC", "k"), ("IPSC", "r")):
x = c.xs((rate, kind), level=("rate", "kind"))
ax.errorbar(range(1, 6), x.mean(), x.sem(), color=col, label=kind)
ax.set_title(f"{rate} ms"); ax.set_xlabel("pulse")
axs[0].set_ylabel("Sn/S1"); axs[0].legend()
axs[0].set_ylim(0, 2)
outside = int(((c < 0) | (c > 2)).sum().sum())
print("Cell means of Sn/S1 range from %.2f to %.2f; %d values fall outside the axes (0 to 2)" % (c.min().min(), c.max().max(), outside))
plt.show()
Cell means of Sn/S1 range from -1.29 to 4.34; 17 values fall outside the axes (0 to 2)
# Do the pulse times from the protocol fall on the stimulus artefacts? For the first sweep of every
# slice file, the time of the steepest current change within 5 ms of each computed pulse time,
# relative to it; and one sweep drawn with the pulse times, baseline and peak windows.
def artefact_offsets(f):
abf = S.abf_from_bytes(S.fetch(sl_files[f + ".abf"]["download_url"]), f + ".abf")
fs = abf.dataRate
abf.setSweep(0)
y = abf.sweepY.copy()
out = []
for start, per, n, level in S.pulse_trains(abf):
for k in range(n):
tp = start + k * per
a, b = tp - int(0.005 * fs), tp + int(0.005 * fs)
out.append({"file": f, "level": level, "pulse": k + 1,
"offset_ms": (a + np.argmax(np.abs(np.diff(y[a:b + 1]))) - tp) / fs * 1000})
return out
with ThreadPoolExecutor(8) as ex:
art = pd.DataFrame(sum(ex.map(artefact_offsets, sl_list[sl_list.on_figshare].file), []))
print("Steepest change relative to the computed pulse time (ms), over", art.file.nunique(), "files and", len(art), "pulses:")
print(art.offset_ms.describe().round(3).to_string())
print("Pulses whose steepest change is within 1 ms of the computed time: %.1f%%" % (100 * (art.offset_ms.abs() <= 1).mean()))
f = sl_list[sl_list.on_figshare & (sl_list.rate == 175)].file.iloc[0]
abf = S.abf_from_bytes(S.fetch(sl_files[f + ".abf"]["download_url"]), f + ".abf")
abf.setSweep(0)
fs, y = abf.dataRate, abf.sweepY
trains = S.pulse_trains(abf)
fig, axs = plt.subplots(1, len(trains), figsize=(10, 3))
for ax, (start, per, n, level) in zip(np.atleast_1d(axs), trains):
i0, i1 = start - int(0.05 * fs), start + n * per + int(0.05 * fs)
tt = np.arange(i0, i1) / fs * 1000
ax.plot(tt, y[i0:i1], color="k", lw=0.5)
for k in range(n):
tp = start + k * per
ax.axvline(tp / fs * 1000, color="r", lw=0.5)
ax.axvspan((tp - S.SLICE_BASE_S * fs) / fs * 1000, tp / fs * 1000, color="b", alpha=0.2)
ax.axvspan((tp + S.SLICE_PEAK_WIN_S[0] * fs) / fs * 1000,
(tp + min(S.SLICE_PEAK_WIN_S[1], per / fs - 0.002) * fs) / fs * 1000, color="g", alpha=0.2)
lo, hi = np.percentile(y[i0:i1], [0.5, 99.5])
ax.set_ylim(lo - 0.2 * (hi - lo), hi + 0.2 * (hi - lo))
ax.set_title(f"{f}, held at {level:g} mV", fontsize=9); ax.set_xlabel("ms from sweep start")
print(f"{f} train at {level:g} mV: current {y[i0:i1].min():.0f} to {y[i0:i1].max():.0f} pA; "
f"axis clipped to {lo - 0.2 * (hi - lo):.0f} to {hi + 0.2 * (hi - lo):.0f} pA (artefact peaks fall outside)")
np.atleast_1d(axs)[0].set_ylabel("pA")
plt.show()
Steepest change relative to the computed pulse time (ms), over 74 files and 740 pulses: count 740.000 mean 0.584 std 0.698 min -4.100 25% 0.250 50% 0.500 75% 0.600 max 4.900 Pulses whose steepest change is within 1 ms of the computed time: 91.8%
19207013 train at -70 mV: current -3903 to 402 pA; axis clipped to -1656 to 218 pA (artefact peaks fall outside) 19207013 train at 0 mV: current 227 to 1246 pA; axis clipped to 298 to 1339 pA (artefact peaks fall outside)
Result: not reproduced. At 175 ms IPSCs do not depress more than EPSCs. Per trial, the mean Sn/S1 over pulses 2 to 5 is 0.90 for IPSCs against 0.68 for EPSCs (p = 0.33), and no pulse differs after Bonferroni correction (all adjusted p = 1.0). On the mean trace of each file the IPSC ratio is lower (0.57 against 0.65, p = 0.39), with pulses 2 and 3 at adjusted p 0.15 and 0.06. At 75 ms neither reading differs (p 0.52 and 0.72 for the mean over pulses). At 575 ms the unpaired tests do not differ (p 0.08 and 0.82), but on mean traces the paired test finds IPSCs lower than EPSCs (0.55 against 0.66, p = 0.022), where the claim has them similar. 35 trials and 7 file means with no first response above baseline were dropped, and 17 cell means of Sn/S1 fall outside 0 to 2, so the per-trial IPSC ratios are noisy. The outcome rests on the peak measure (2 to 30 ms after each pulse against the 5 ms before it), which the paper does not specify. The pulse times read from the protocol fall on the stimulus artefacts: the steepest change of the current is a median 0.5 ms after the computed time, within 1 ms for 91.8% of 740 pulses, and the drawn sweep shows the windows. Per trial and on mean traces the IPSC ratio at 175 ms moves in opposite directions (0.90 and 0.57 against 0.68 and 0.65 for EPSCs); the single-trial ratios are noisy (cell means of Sn/S1 range from -1.29 to 4.34), and neither direction is significant.
fig1i-operant-prototypes¶
Of naive virgins able to silence playback with a lever press, only those hearing prototypes significantly increased their press duration by session 8.
The claims file's readings: a press ends at a gap above (a) twice the median inter-sample interval
or (b) 100 ms; the comparison is (a) session 1 against session 8, or (b) the mean of sessions 1-2
against 7-8; paired two-tailed t-test per group of 7 mice. Files marked b are joined to their
session. The median inter-sample interval is the polling period of the task code, so it is taken
once, pooled over sessions, from the touch times printed at 0.1 ms (below 10 s; data check): 0.70
ms. Above 10 s the printed times step by 1 ms or more, and the median of the printed intervals
there (1.0 ms) would measure the printing, not the polling. Because a gap must also exceed one and
a half printed steps, the gap that ends a press grows with time in the session (printed below):
under the median-interval rule 1.5 ms before 100 s, 15 ms to 1000 s and 150 ms after, so a 50 ms
pause splits a press early in a session but not after 1000 s.
glob_interval = float(np.median(np.concatenate(op.touches.map(S.interval_samples).values)))
print("Median interval between touch samples, pooled over sessions (from times below 10 s, printed at 0.1 ms): %.2f ms"
% (1000 * glob_interval))
print("Smallest gap that ends a press (ms), by time in the session (the printed step sets a floor):")
for lo, mid in ((0, 5.0), (10, 50.0), (100, 500.0), (1000, 1500.0)):
print(f" from {lo} s: median2 rule {1000 * max(2 * glob_interval, 1.5 * float(S.resolution(mid))):.1f}, "
f"100 ms rule {1000 * max(0.1, 1.5 * float(S.resolution(mid))):.0f}")
rows = []
for (mouse, grp, ses), d in op.groupby(["mouse", "group", "session"]):
t = np.concatenate(d.touches.values)
iv = glob_interval
for rule in ("median2", "100ms"):
p = S.presses(t, rule, iv)
rows.append({"mouse": mouse, "group": grp, "session": ses, "rule": rule, "presses": len(p),
"mean_duration_s": p[:, 2].mean() if len(p) else np.nan, "max_duration_s": p[:, 2].max() if len(p) else np.nan})
press = pd.DataFrame(rows)
print("Longest single press (s) under each rule:", press.groupby("rule").max_duration_s.max().round(1).to_dict())
print("\nMean press duration (s) per mouse and session, gap rule (b) 100 ms:")
print(press[press.rule == "100ms"].pivot_table(index=["group", "mouse"], columns="session", values="mean_duration_s").round(2).to_string())
def operant_tests(measure):
out = []
for rule in ("median2", "100ms"):
for comp in ("1 vs 8", "1-2 vs 7-8"):
for grp in ("proto", "fast", "slow"):
x = press[(press.rule == rule) & (press.group == grp)].pivot(index="mouse", columns="session", values=measure)
a, b = (x[1], x[8]) if comp == "1 vs 8" else (x[[1, 2]].mean(axis=1), x[[7, 8]].mean(axis=1))
ok = a.notna() & b.notna()
tt = stats.ttest_rel(b[ok], a[ok])
out.append({"gap rule": rule, "comparison": comp, "group": grp, "n": int(ok.sum()),
"early": round(a[ok].mean(), 3), "late": round(b[ok].mean(), 3),
"t": round(tt.statistic, 2), "p": round(tt.pvalue, 4),
"increase at p<0.05": bool(tt.pvalue < 0.05 and tt.statistic > 0)})
return pd.DataFrame(out)
dur = operant_tests("mean_duration_s")
print("\nMean press duration, session comparisons (paired two-tailed t-test):")
print(dur.to_string(index=False))
verdict = dur.groupby(["gap rule", "comparison"]).apply(
lambda d: bool(d.set_index("group").loc["proto", "increase at p<0.05"]
and not d.set_index("group").loc[["fast", "slow"], "p"].lt(0.05).any()))
print("\nReading reproduces the claim (prototypes increase, the others not significant):")
print(verdict.to_string())
Median interval between touch samples, pooled over sessions (from times below 10 s, printed at 0.1 ms): 0.70 ms Smallest gap that ends a press (ms), by time in the session (the printed step sets a floor): from 0 s: median2 rule 1.5, 100 ms rule 100 from 10 s: median2 rule 1.5, 100 ms rule 100 from 100 s: median2 rule 15.0, 100 ms rule 100 from 1000 s: median2 rule 150.0, 100 ms rule 150
Longest single press (s) under each rule: {'100ms': 39.5, 'median2': 32.6}
Mean press duration (s) per mouse and session, gap rule (b) 100 ms:
session 1 2 3 4 5 6 7 8
group mouse
fast NV26 0.77 1.05 1.35 1.00 1.62 0.94 0.86 0.81
NV27 0.61 0.49 0.56 0.98 0.65 0.80 0.66 0.47
NV28 0.43 0.31 0.53 0.89 1.55 0.93 0.66 0.93
NV29 0.06 1.27 0.71 0.88 0.55 0.95 0.97 1.25
NV32 0.60 1.13 2.61 1.05 1.20 0.96 1.06 0.95
NV33 0.58 0.87 0.97 0.84 0.52 1.23 0.98 1.04
NV35 0.65 1.70 1.51 2.82 1.76 1.21 1.02 0.99
proto NV18 0.42 0.60 1.96 1.52 2.17 1.89 1.95 1.88
NV19 0.15 0.47 0.60 0.55 0.32 0.71 0.85 0.69
NV22 0.20 0.17 0.18 0.02 0.22 0.46 0.29 1.88
NV23 0.32 0.58 1.57 0.88 1.13 1.02 0.88 1.47
NV30 0.79 0.86 2.18 1.55 0.75 0.99 0.76 1.44
NV31 1.00 0.87 0.85 1.09 0.99 0.58 0.62 0.85
NV34 0.23 0.25 0.44 0.82 0.99 1.59 1.97 1.62
slow NV20 1.19 1.40 1.56 0.94 1.27 0.97 1.04 0.79
NV21 1.33 1.23 1.34 1.79 1.94 2.99 0.90 2.23
NV24 0.37 0.72 0.73 1.16 NaN 1.16 1.24 1.06
NV25 0.57 0.74 1.14 0.81 0.86 0.52 0.82 0.84
NV36 0.58 1.49 1.04 1.01 0.63 1.10 0.95 0.81
NV37 0.54 1.04 0.50 1.00 0.95 0.53 1.15 1.22
NV38 0.42 0.84 NaN 1.39 0.92 0.87 0.76 1.09
Mean press duration, session comparisons (paired two-tailed t-test):
gap rule comparison group n early late t p increase at p<0.05
median2 1 vs 8 proto 7 0.329 1.003 2.96 0.0253 True
median2 1 vs 8 fast 7 0.397 0.685 2.10 0.0810 False
median2 1 vs 8 slow 7 0.537 0.726 1.55 0.1724 False
median2 1-2 vs 7-8 proto 7 0.375 0.896 2.78 0.0318 True
median2 1-2 vs 7-8 fast 7 0.608 0.658 0.59 0.5745 False
median2 1-2 vs 7-8 slow 7 0.682 0.720 0.37 0.7275 False
100ms 1 vs 8 proto 7 0.444 1.404 3.95 0.0076 True
100ms 1 vs 8 fast 7 0.530 0.919 2.45 0.0500 True
100ms 1 vs 8 slow 7 0.715 1.146 2.61 0.0403 True
100ms 1-2 vs 7-8 proto 7 0.493 1.224 3.12 0.0206 True
100ms 1-2 vs 7-8 fast 7 0.753 0.903 1.65 0.1502 False
100ms 1-2 vs 7-8 slow 7 0.890 1.063 1.36 0.2227 False
Reading reproduces the claim (prototypes increase, the others not significant):
gap rule comparison
100ms 1 vs 8 False
1-2 vs 7-8 True
median2 1 vs 8 True
1-2 vs 7-8 True
Result: blocked (methods underspecified). Prototypes increase the mean press duration under all four readings (session 1 against 8: p = 0.025 with the median-interval gap, 0.0076 with the 100 ms gap; sessions 1-2 against 7-8: p = 0.032 and 0.021). The fast and slow groups are not significant under three readings (p 0.081 to 0.73), but with the 100 ms gap and session 1 against 8 the slow group also increases (p = 0.040) and the fast group reaches p = 0.050. The criterion blocks the claim when the readings disagree. The reading closest to the paper's p-values (0.03, 0.24, 0.13) is the median-interval gap with session 1 against 8 (0.025, 0.081, 0.17). The press-ending gap of the median-interval rule depends on when in the session a press falls (1.5 to 150 ms), a consequence of the printed resolution that no reading avoids.
ed5b-press-count-unchanged¶
The number of lever presses per session did not increase by session 8 in any of the three groups.
Presses counted as above, under both gap rules and both comparisons; paired two-tailed t-test per group of 7.
cnt = operant_tests("presses")
print(cnt.to_string(index=False))
holds = cnt.groupby(["gap rule", "comparison"])["increase at p<0.05"].apply(lambda v: not v.any())
print("\nReading finds no increase in any group:")
print(holds.to_string())
gap rule comparison group n early late t p increase at p<0.05
median2 1 vs 8 proto 7 100.571 145.000 1.18 0.2821 False
median2 1 vs 8 fast 7 76.857 94.571 0.55 0.6023 False
median2 1 vs 8 slow 7 147.857 116.429 -1.25 0.2584 False
median2 1-2 vs 7-8 proto 7 92.643 128.643 1.41 0.2093 False
median2 1-2 vs 7-8 fast 7 68.500 107.429 2.46 0.0492 True
median2 1-2 vs 7-8 slow 7 127.357 113.643 -0.54 0.6105 False
100ms 1 vs 8 proto 7 76.857 99.714 0.86 0.4203 False
100ms 1 vs 8 fast 7 57.857 71.429 0.55 0.6019 False
100ms 1 vs 8 slow 7 110.571 76.143 -1.98 0.0949 False
100ms 1-2 vs 7-8 proto 7 71.357 92.786 1.36 0.2216 False
100ms 1-2 vs 7-8 fast 7 52.929 75.643 1.49 0.1858 False
100ms 1-2 vs 7-8 slow 7 91.786 77.643 -0.88 0.4152 False
Reading finds no increase in any group:
gap rule comparison
100ms 1 vs 8 True
1-2 vs 7-8 True
median2 1 vs 8 True
1-2 vs 7-8 False
Result: blocked (methods underspecified). No group's number of presses increases under three readings (all p >= 0.09). With the median-interval gap and sessions 1-2 against 7-8, the fast group increases from 68.5 to 107.4 presses (p = 0.049). The criterion blocks the claim when the readings disagree.
ed6ef-epsc-tuning¶
In experienced females, prototype- and morph-evoked EPSCs are similar and negatively correlated with ISI; in naive cortex morph-evoked EPSCs are weaker than prototype-evoked ones and uncorrelated with ISI.
Method (Methods, "In vivo whole-cell recordings", quoted in the helper): per sweep the mean current from call onset to 250 ms after call offset minus the mean of the 1 s before onset; per cell and ISI the mean absolute value of the largest 5 or 10 trials (both run). EPSCs are the files held at -70 mV, IPSCs those at 0 to +60 mV. Experienced cells pool dams and EVs (the spreadsheet does not separate them). Repeated-measures one-way ANOVA over the four ISIs; post hoc paired t-tests with Bonferroni correction, over the three comparisons with the prototype that the legend reports, and over all six pairs (a reading of the family, which the legend does not give). Pearson correlation over all cell and ISI points. The main analysis follows the spreadsheet notes and the best call where one is named, and drops sweeps whose holding current contradicts the listed potential. Variants: every sweep of every set file; the largest trials in the expected direction (inward for EPSCs, outward for IPSCs) instead of the largest absolute values, a second reading of "absolute magnitudes (pA) from the largest 5-10 evoked trials"; and the call onset taken at the first loud syllable, the rule used before revision. The Methods' inclusion rule ("Only neurons that exhibited at least five significant prototype-evoked E/IPSCs") leaves too few cells to run (data check), so every listed cell is used, and the numbers below measure the recordings, not call-evoked currents.
VARIANTS = {"main": {},
"all sweeps": dict(apply_notes=False, best_call_only=False, check_holding=False),
"expected direction": dict(select="expected"),
"loud-syllable onset": dict(measure="evoked_loud")}
def psc(top_n, variant):
t, raw = S.psc_table(iv_list, iv_sweeps, top_n=top_n, **VARIANTS[variant])
return t.pivot_table(index=["group", "cell", "kind"], columns="isi", values="value"), t, raw
tables = {(n, v): psc(n, v) for n in S.TOP_N for v in VARIANTS}
w, t, raw = tables[(5, "main")]
print("Trials per cell and ISI (main, EPSC): min", int(t[t.kind == "EPSC"].n_trials.min()),
"median", int(t[t.kind == "EPSC"].n_trials.median()))
print("Share of trials whose evoked current has the expected sign (EPSC inward, IPSC outward):",
raw.assign(ok=lambda d: np.where(d.kind == "EPSC", d.evoked < 0, d.evoked > 0)).groupby("kind").ok.mean().round(2).to_dict())
print("Cells kept by the Methods' inclusion rule (data check):",
{k: incl_tabs[k].groupby(["kind", "group"]).included.sum().to_dict() for k in S.SIG_K},
"so the claims cannot be run on included cells; every listed cell is used below.")
rows = []
for (n, v), (w, _, _) in tables.items():
for grp in ("experienced", "naive"):
e = w.xs((grp, "EPSC"), level=("group", "kind")).dropna()
long = e.reset_index().melt(id_vars="cell", var_name="isi", value_name="v")
an = AnovaRM(long, "v", "cell", within=["isi"]).fit().anova_table
p3 = {c: min(1, stats.ttest_rel(e[175], e[c]).pvalue * 3) for c in (125, 275, 575)}
p6 = {(a, b): min(1, stats.ttest_rel(e[a], e[b]).pvalue * 6) for i, a in enumerate(e.columns) for b in e.columns[i + 1:]}
r = stats.pearsonr(long.isi.astype(float), long.v)
rows.append({"top": n, "variant": v, "group": grp, "cells": len(e),
"mean 125/175/275/575": e.mean().round(1).tolist(),
"RM ANOVA F": round(an["F Value"].iloc[0], 2), "RM p": round(an["Pr > F"].iloc[0], 4),
"175 vs 125/275/575, Bonf x3": [round(v_, 3) for v_ in p3.values()],
"smallest pair p, Bonf x6": round(min(p6.values()), 3),
"Pearson r": round(r.statistic, 3), "r p": round(r.pvalue, 4), "points": len(long)})
ed6ef = pd.DataFrame(rows)
print(ed6ef.to_string(index=False))
Trials per cell and ISI (main, EPSC): min 10 median 20
Share of trials whose evoked current has the expected sign (EPSC inward, IPSC outward): {'EPSC': 0.48, 'IPSC': 0.5}
Cells kept by the Methods' inclusion rule (data check): {2: {('EPSC', 'experienced'): 0, ('EPSC', 'naive'): 1, ('IPSC', 'experienced'): 0, ('IPSC', 'naive'): 0}, 3: {('EPSC', 'experienced'): 0, ('EPSC', 'naive'): 0, ('IPSC', 'experienced'): 0, ('IPSC', 'naive'): 0}} so the claims cannot be run on included cells; every listed cell is used below.
top variant group cells mean 125/175/275/575 RM ANOVA F RM p 175 vs 125/275/575, Bonf x3 smallest pair p, Bonf x6 Pearson r r p points 5 main experienced 12 [30.9, 33.5, 32.4, 28.4] 0.87 0.4648 [0.992, 1, 0.8] 1.000 -0.137 0.3538 48 5 main naive 14 [17.4, 20.0, 18.3, 16.2] 1.73 0.1760 [0.122, 0.944, 0.201] 0.245 -0.070 0.6073 56 5 all sweeps experienced 12 [35.5, 36.0, 36.6, 31.2] 0.96 0.4252 [1, 1, 0.949] 0.454 -0.126 0.3936 48 5 all sweeps naive 14 [17.4, 20.2, 18.4, 16.2] 1.96 0.1354 [0.078, 0.851, 0.175] 0.157 -0.073 0.5944 56 5 expected direction experienced 12 [25.1, 26.2, 24.4, 20.5] 1.00 0.4058 [1, 1, 0.678] 0.909 -0.201 0.1697 48 5 expected direction naive 14 [13.6, 16.5, 14.5, 10.0] 5.26 0.0038 [0.205, 0.384, 0.034] 0.067 -0.166 0.2226 56 5 loud-syllable onset experienced 12 [32.5, 32.5, 34.3, 28.7] 1.43 0.2510 [1, 1, 0.843] 0.297 -0.159 0.2797 48 5 loud-syllable onset naive 14 [17.4, 20.9, 17.2, 18.3] 1.57 0.2116 [0.057, 0.196, 0.832] 0.114 -0.013 0.9246 56 10 main experienced 12 [22.8, 24.8, 23.0, 21.8] 0.55 0.6507 [1, 1, 1] 1.000 -0.092 0.5323 48 10 main naive 14 [12.5, 14.1, 13.0, 11.5] 1.67 0.1894 [0.218, 1, 0.185] 0.371 -0.067 0.6227 56 10 all sweeps experienced 12 [26.8, 27.7, 27.9, 24.6] 0.64 0.5921 [1, 1, 1] 0.900 -0.089 0.5490 48 10 all sweeps naive 14 [12.6, 14.5, 13.3, 11.7] 2.09 0.1174 [0.097, 0.997, 0.139] 0.195 -0.072 0.5997 56 10 expected direction experienced 12 [14.6, 15.7, 14.1, 11.7] 1.16 0.3380 [1, 1, 0.499] 0.832 -0.205 0.1625 48 10 expected direction naive 14 [7.0, 9.0, 7.7, 5.3] 3.39 0.0273 [0.233, 0.429, 0.109] 0.217 -0.119 0.3826 56 10 loud-syllable onset experienced 12 [23.4, 24.3, 24.1, 21.0] 1.04 0.3864 [1, 1, 0.774] 0.521 -0.146 0.3219 48 10 loud-syllable onset naive 14 [12.5, 14.8, 12.1, 13.0] 1.97 0.1347 [0.128, 0.066, 0.7] 0.132 -0.017 0.9021 56
w, _, _ = tables[(5, "main")]
print("Per-cell EPSC (pA, largest 5 trials, main):")
print(w.xs("EPSC", level="kind").round(1).to_string())
fig, axs = plt.subplots(1, 2, figsize=(8, 3), sharey=True)
for ax, grp in zip(axs, ("experienced", "naive")):
e = w.xs((grp, "EPSC"), level=("group", "kind"))
for _, r in e.iterrows():
ax.plot(e.columns, r.values, color="0.7", lw=0.8)
ax.errorbar(e.columns, e.mean(), e.sem(), color="k")
ax.set_title(f"{grp} (n={len(e)})"); ax.set_xlabel("ISI (ms)")
axs[0].set_ylabel("|EPSC| (pA)")
print("Plotted range: %.1f to %.1f pA, all points shown" % (w.xs("EPSC", level="kind").min().min(), w.xs("EPSC", level="kind").max().max()))
plt.show()
Per-cell EPSC (pA, largest 5 trials, main):
isi 125 175 275 575
group cell
experienced 190311_e1 21.8 15.5 20.1 13.0
190313_e2 40.3 57.3 20.7 22.7
190313_e3 44.4 48.8 44.7 37.7
190403_e1 24.3 11.0 20.6 37.7
190410_e1 37.2 41.5 30.8 37.4
190410_e2 32.3 24.1 25.2 23.5
190411_e1 27.7 39.8 40.1 36.8
190411_e2 30.1 41.2 30.8 16.9
190419_e3 37.0 39.9 42.3 28.5
190424_e1 28.6 27.4 39.8 30.5
190424_e2 33.6 34.4 54.8 40.0
190424_e3 13.6 21.6 18.4 16.0
naive 060519_e2 26.0 39.2 27.6 32.6
190314_e1 4.5 6.6 8.6 2.8
190314_e5 2.2 7.1 3.0 2.7
190315_e2 2.8 2.1 1.7 3.8
190315_e3 3.0 1.0 1.8 1.9
190320_e1 22.0 23.6 27.0 20.0
190320_e2 7.6 8.5 6.3 11.4
190320_e5 30.9 33.8 24.8 36.8
190326_e1 39.3 46.1 36.7 22.3
190326_e2 33.3 35.4 34.1 31.3
190326_e4 19.6 20.3 16.5 13.3
190326_e5 25.9 32.1 44.7 22.6
190329_e2 7.1 9.2 6.3 7.0
190329_e3 19.2 14.6 16.5 18.3
Plotted range: 1.0 to 57.3 pA, all points shown
Result: blocked (other). The current almost never follows the calls (data check): 2 of 39 cell recordings respond at p < 0.05 to the calls, against 3 of 39 before the calls; two cells (naive 190326_e1 and experienced 190313_e2, in one of its two files) carry an inward current during the call from about 0.4 s after onset; and the Methods' inclusion rule keeps 1 of 39 (2 s.d.) or none (3 s.d.), so the claim cannot be tested on the cells it is about. As computed on every listed cell it would not be reproduced: with the largest 5 trials, naive prototype-evoked EPSCs (20.0 pA) are not larger than morph-evoked ones (17.4, 18.3, 16.2 pA; adjusted p 0.12, 0.94, 0.20; repeated-measures ANOVA p = 0.18), and no variant gives p < 0.05 for all three comparisons (the expected-direction reading gives 0.034 at 575 ms only). Experienced EPSCs are not correlated with ISI (r = -0.14, p = 0.35; -0.09 to -0.21, p >= 0.16, under every variant). No experienced pair differs (smallest adjusted p 0.30 to 1.0) and the naive correlation is not significant (r = -0.07, p = 0.61), as in the paper.
fig2h-epsc-enhanced-ev¶
Prototype-evoked EPSCs are similar between groups, but EPSCs evoked by non-prototypical ISIs are larger in experienced than in naive females.
EPSC magnitudes as in ed6ef-epsc-tuning; experienced against naive per ISI by unpaired one-tailed t-test (experienced larger), as the legend gives; the two-tailed p is shown as well.
rows = []
for (n, v), (w, _, _) in tables.items():
E = w.xs("EPSC", level="kind")
for c in (125, 175, 275, 575):
a, b = E.xs("experienced", level="group")[c].dropna(), E.xs("naive", level="group")[c].dropna()
rows.append({"top": n, "variant": v, "isi": c, "EV mean": round(a.mean(), 1), "n EV": len(a),
"NV mean": round(b.mean(), 1), "n NV": len(b), "ratio": round(a.mean() / b.mean(), 2),
"one-tailed p": round(stats.ttest_ind(a, b, alternative="greater").pvalue, 4),
"two-tailed p": round(stats.ttest_ind(a, b).pvalue, 4)})
print(pd.DataFrame(rows).to_string(index=False))
for select in ("abs", "expected"):
print(f"\nNoise control: the same measure taken before the call (sham), largest 5 trials, main sweeps, ranking {select}:")
sham = S.psc_table(iv_list, iv_sweeps, top_n=5, measure="sham", select=select)[0].pivot_table(index=["group", "cell", "kind"], columns="isi", values="value")
for kind in ("EPSC", "IPSC"):
Z = sham.xs(kind, level="kind")
for c in (125, 175, 275, 575):
a, b = Z.xs("experienced", level="group")[c].dropna(), Z.xs("naive", level="group")[c].dropna()
print(f" {kind} {c} ms: EV {a.mean():.1f} (n {len(a)}), NV {b.mean():.1f} (n {len(b)}), "
f"one-tailed p {stats.ttest_ind(a, b, alternative='greater').pvalue:.4f}, two-tailed p {stats.ttest_ind(a, b).pvalue:.4f}")
top variant isi EV mean n EV NV mean n NV ratio one-tailed p two-tailed p 5 main 125 30.9 12 17.4 14 1.78 0.0023 0.0046 5 main 175 33.5 12 20.0 14 1.68 0.0129 0.0257 5 main 275 32.4 12 18.3 14 1.77 0.0062 0.0123 5 main 575 28.4 12 16.2 14 1.75 0.0047 0.0094 5 all sweeps 125 35.5 12 17.4 14 2.04 0.0010 0.0019 5 all sweeps 175 36.0 12 20.2 14 1.78 0.0082 0.0165 5 all sweeps 275 36.6 12 18.4 14 1.99 0.0033 0.0066 5 all sweeps 575 31.2 12 16.2 14 1.93 0.0031 0.0062 5 expected direction 125 25.1 12 13.6 14 1.84 0.0062 0.0124 5 expected direction 175 26.2 12 16.5 14 1.59 0.0412 0.0825 5 expected direction 275 24.4 12 14.5 14 1.69 0.0196 0.0392 5 expected direction 575 20.5 12 10.0 14 2.05 0.0035 0.0071 5 loud-syllable onset 125 32.5 12 17.4 14 1.87 0.0013 0.0026 5 loud-syllable onset 175 32.5 12 20.9 14 1.55 0.0201 0.0402 5 loud-syllable onset 275 34.3 12 17.2 14 2.00 0.0008 0.0017 5 loud-syllable onset 575 28.7 12 18.3 14 1.57 0.0214 0.0429 10 main 125 22.8 12 12.5 14 1.82 0.0035 0.0069 10 main 175 24.8 12 14.1 14 1.76 0.0088 0.0175 10 main 275 23.0 12 13.0 14 1.77 0.0065 0.0130 10 main 575 21.8 12 11.5 14 1.89 0.0022 0.0044 10 all sweeps 125 26.8 12 12.6 14 2.13 0.0015 0.0031 10 all sweeps 175 27.7 12 14.5 14 1.91 0.0051 0.0103 10 all sweeps 275 27.9 12 13.3 14 2.09 0.0030 0.0059 10 all sweeps 575 24.6 12 11.7 14 2.11 0.0016 0.0032 10 expected direction 125 14.6 12 7.0 14 2.09 0.0085 0.0169 10 expected direction 175 15.7 12 9.0 14 1.75 0.0394 0.0788 10 expected direction 275 14.1 12 7.7 14 1.83 0.0253 0.0506 10 expected direction 575 11.7 12 5.3 14 2.20 0.0134 0.0268 10 loud-syllable onset 125 23.4 12 12.5 14 1.87 0.0024 0.0048 10 loud-syllable onset 175 24.3 12 14.8 14 1.63 0.0168 0.0335 10 loud-syllable onset 275 24.1 12 12.1 14 2.00 0.0012 0.0024 10 loud-syllable onset 575 21.0 12 13.0 14 1.62 0.0182 0.0364 Noise control: the same measure taken before the call (sham), largest 5 trials, main sweeps, ranking abs:
EPSC 125 ms: EV 33.6 (n 12), NV 14.8 (n 14), one-tailed p 0.0002, two-tailed p 0.0003 EPSC 175 ms: EV 34.1 (n 12), NV 18.4 (n 14), one-tailed p 0.0047, two-tailed p 0.0094 EPSC 275 ms: EV 32.0 (n 12), NV 17.3 (n 14), one-tailed p 0.0032, two-tailed p 0.0064 EPSC 575 ms: EV 35.7 (n 12), NV 19.6 (n 14), one-tailed p 0.0037, two-tailed p 0.0074 IPSC 125 ms: EV 42.2 (n 7), NV 12.9 (n 6), one-tailed p 0.0190, two-tailed p 0.0380 IPSC 175 ms: EV 45.9 (n 7), NV 13.8 (n 6), one-tailed p 0.0049, two-tailed p 0.0098 IPSC 275 ms: EV 31.9 (n 7), NV 13.8 (n 6), one-tailed p 0.0161, two-tailed p 0.0322 IPSC 575 ms: EV 43.6 (n 7), NV 12.8 (n 6), one-tailed p 0.0009, two-tailed p 0.0018 Noise control: the same measure taken before the call (sham), largest 5 trials, main sweeps, ranking expected:
EPSC 125 ms: EV 26.2 (n 12), NV 9.1 (n 14), one-tailed p 0.0000, two-tailed p 0.0000 EPSC 175 ms: EV 24.6 (n 12), NV 12.9 (n 14), one-tailed p 0.0075, two-tailed p 0.0150 EPSC 275 ms: EV 20.3 (n 12), NV 12.1 (n 14), one-tailed p 0.0226, two-tailed p 0.0453 EPSC 575 ms: EV 22.7 (n 12), NV 13.0 (n 14), one-tailed p 0.0164, two-tailed p 0.0328 IPSC 125 ms: EV 32.8 (n 7), NV 9.1 (n 6), one-tailed p 0.0252, two-tailed p 0.0504 IPSC 175 ms: EV 29.8 (n 7), NV 8.8 (n 6), one-tailed p 0.0294, two-tailed p 0.0589 IPSC 275 ms: EV 21.0 (n 7), NV 9.1 (n 6), one-tailed p 0.0278, two-tailed p 0.0556 IPSC 575 ms: EV 22.8 (n 7), NV 10.2 (n 6), one-tailed p 0.0211, two-tailed p 0.0423
Result: blocked (other), as ed6ef-epsc-tuning: the current almost never follows the calls. As computed it would not be reproduced. Experienced EPSCs exceed naive ones at 125, 275 and 575 ms (one-tailed p 0.0023, 0.0062 and 0.0047 with the largest 5 trials) but also at the prototype (33.5 against 20.0 pA, p = 0.013; 0.0051 to 0.041 under every variant), where the claim has them similar. The same measure taken in the 0.7 s before the call gives the same group difference at every ISI (EV 32.0 to 35.7 against NV 14.8 to 19.6 pA, one-tailed p 0.0002 to 0.0047; ranking trials in the expected direction, p below 0.0001 to 0.023), as the baseline s.d. is about twice as large in experienced recordings. The difference is present before any call.
fig2i-ipsc-weaker-nv¶
IPSCs are broadly tuned in both groups but weaker in naive virgins at every ISI.
IPSC magnitudes as in ed6ef-epsc-tuning (cells with a set file held at 0 to +60 mV); experienced against naive per ISI by unpaired two-tailed t-test; Pearson correlation of IPSC with ISI over cell and ISI points per group.
rows, cors = [], []
for (n, v), (w, _, _) in tables.items():
I = w.xs("IPSC", level="kind")
for c in (125, 175, 275, 575):
a, b = I.xs("experienced", level="group")[c].dropna(), I.xs("naive", level="group")[c].dropna()
rows.append({"top": n, "variant": v, "isi": c, "EV mean": round(a.mean(), 1), "n EV": len(a),
"NV mean": round(b.mean(), 1), "n NV": len(b), "two-tailed p": round(stats.ttest_ind(a, b).pvalue, 4)})
for grp in ("experienced", "naive"):
x = I.xs(grp, level="group").dropna().reset_index().melt(id_vars="cell", var_name="isi", value_name="v")
r = stats.pearsonr(x.isi.astype(float), x.v)
cors.append({"top": n, "variant": v, "group": grp, "cells": x.cell.nunique(),
"r": round(r.statistic, 3), "p": round(r.pvalue, 4)})
print(pd.DataFrame(rows).to_string(index=False))
print()
print(pd.DataFrame(cors).to_string(index=False))
w, _, _ = tables[(5, "main")]
print("\nIPSC cells (main):", {g: sorted(w.xs((g, "IPSC"), level=("group", "kind")).index) for g in ("experienced", "naive")})
_, t5, _ = tables[(5, "main")]
print("\nIPSC trials per cell and ISI (main):")
print(t5[t5.kind == "IPSC"].pivot_table(index=["group", "cell"], columns="isi", values="n_trials").to_string())
print("Their files, notes and holding potentials:")
ip = iv_list[(iv_list.holding > -30) & ~iv_list.set.str.contains("5xcalls")]
print(ip[["group", "cell", "file", "set", "holding", "notes", "best_call", "on_figshare"]].to_string(index=False))
top variant isi EV mean n EV NV mean n NV two-tailed p
5 main 125 39.4 7 13.1 6 0.0232
5 main 175 35.9 7 15.1 6 0.0552
5 main 275 38.0 7 11.2 6 0.0125
5 main 575 38.5 7 11.5 6 0.0066
5 all sweeps 125 39.7 7 13.1 6 0.0221
5 all sweeps 175 35.9 7 15.1 6 0.0552
5 all sweeps 275 38.7 7 11.2 6 0.0139
5 all sweeps 575 38.7 7 11.5 6 0.0068
5 expected direction 125 31.2 7 9.1 6 0.0124
5 expected direction 175 26.0 7 9.1 6 0.0209
5 expected direction 275 29.4 7 6.6 6 0.0075
5 expected direction 575 25.1 7 6.7 6 0.0121
5 loud-syllable onset 125 38.6 7 13.3 6 0.0125
5 loud-syllable onset 175 37.5 7 15.4 6 0.0611
5 loud-syllable onset 275 37.9 7 13.2 6 0.0242
5 loud-syllable onset 575 41.0 7 11.5 6 0.0038
10 main 125 28.0 7 9.3 6 0.0159
10 main 175 26.0 7 10.5 6 0.0330
10 main 275 27.7 7 8.0 6 0.0086
10 main 575 28.3 7 8.1 6 0.0044
10 all sweeps 125 28.5 7 9.3 6 0.0138
10 all sweeps 175 26.4 7 10.5 6 0.0321
10 all sweeps 275 29.0 7 8.0 6 0.0130
10 all sweeps 575 28.7 7 8.1 6 0.0051
10 expected direction 125 18.9 7 4.3 6 0.0051
10 expected direction 175 16.4 7 4.6 6 0.0115
10 expected direction 275 16.1 7 3.0 6 0.0070
10 expected direction 575 14.7 7 2.8 6 0.0126
10 loud-syllable onset 125 27.8 7 9.1 6 0.0052
10 loud-syllable onset 175 27.7 7 10.8 6 0.0402
10 loud-syllable onset 275 27.8 7 9.6 6 0.0212
10 loud-syllable onset 575 31.4 7 8.3 6 0.0018
top variant group cells r p
5 main experienced 7 0.012 0.9509
5 main naive 6 -0.087 0.6871
5 all sweeps experienced 7 0.011 0.9562
5 all sweeps naive 6 -0.087 0.6871
5 expected direction experienced 7 -0.120 0.5424
5 expected direction naive 6 -0.120 0.5780
5 loud-syllable onset experienced 7 0.068 0.7324
5 loud-syllable onset naive 6 -0.090 0.6757
10 main experienced 7 0.036 0.8565
10 main naive 6 -0.089 0.6806
10 all sweeps experienced 7 0.034 0.8632
10 all sweeps naive 6 -0.089 0.6806
10 expected direction experienced 7 -0.185 0.3472
10 expected direction naive 6 -0.094 0.6606
10 loud-syllable onset experienced 7 0.129 0.5124
10 loud-syllable onset naive 6 -0.069 0.7486
IPSC cells (main): {'experienced': ['190403_e1', '190410_e1', '190411_e1', '190419_e3', '190424_e1', '190424_e2', '190424_e3'], 'naive': ['060519_e2', '190314_e1', '190314_e5', '190315_e3', '190326_e1', '190326_e2']}
IPSC trials per cell and ISI (main):
isi 125 175 275 575
group cell
experienced 190403_e1 20.0 20.0 20.0 20.0
190410_e1 5.0 5.0 5.0 4.0
190411_e1 25.0 25.0 25.0 25.0
190419_e3 20.0 20.0 20.0 20.0
190424_e1 19.0 18.0 19.0 19.0
190424_e2 14.0 13.0 14.0 14.0
190424_e3 20.0 20.0 20.0 20.0
naive 060519_e2 20.0 20.0 20.0 20.0
190314_e1 20.0 20.0 20.0 19.0
190314_e5 20.0 20.0 20.0 20.0
190315_e3 20.0 20.0 20.0 20.0
190326_e1 10.0 10.0 10.0 10.0
190326_e2 33.0 34.0 33.0 33.0
Their files, notes and holding potentials:
group cell file set holding notes best_call on_figshare
experienced 190424_e3 19424019 61F set 40.0 True
experienced 190424_e3 19424020 61F set 40.0 True
experienced 190424_e2 19424011 J6A set 60.0 from sweep 25 True
experienced 190424_e2 19424012 J6A set 60.0 True
experienced 190424_e1 19424004 71b set 40.0 from sweep 5 True
experienced 190424_e1 19424006 71b set 40.0 True
experienced 190419_e3 19419009 J6A set 60.0 True
experienced 190419_e3 19419010 J6A set 60.0 True
experienced 190403_e1 19403003 61F set 60.0 True
experienced 190403_e1 19403005 61F set 60.0 True
experienced 190410_e1 19410004 J5B set 60.0 j5b True
experienced 190411_e1 19411005 J5B set 60.0 stop @ sweep 20 True
experienced 190411_e1 19411015 J5B set 60.0 True
experienced 190411_e1 19411016 J5B set 60.0 True
naive 060519_e2 19605010 J6B set 50.0 True
naive 060519_e2 19605011 J6B set 50.0 True
naive 190314_e1 19314004 61F 0.0 True
naive 190314_e1 19314006 61F 0.0 inhibition but rare True
naive 190314_e5 19314029 61F set 0.0 occasionnal oscillations; rare inhibition True
naive 190314_e5 19314031 61F set 0.0 True
naive 190326_e1 19326005 71B set 60.0 True
naive 190326_e1 193260007 71B set 60.0 False
naive 190326_e2 19326011 61F set 60.0 True
naive 190326_e2 19326012 61F set 60.0 True
naive 190326_e2 19326014 61F set 60.0 True
naive 190326_e2 19326015 61F set 60.0 True
naive 190315_e3 19315013 J5B set 0.0 inhibition True
naive 190315_e3 19315014 J5B set 0.0 inhibition True
naive 190315_e3 19315017 J5B set 0.0 inhibition True
Result: blocked (other), as ed6ef-epsc-tuning: the current almost never follows the calls. As computed, the readings disagree. Experienced IPSCs exceed naive ones at 125, 275 and 575 ms under every variant (two-tailed p 0.0018 to 0.024). At 175 ms they do with the largest 10 trials (p 0.012 to 0.040) and with the expected-direction ranking (p = 0.021) but not with the largest 5 absolute values (p = 0.055, and 0.061 with the loud-syllable onset). Neither group's IPSC is correlated with ISI (r -0.19 to 0.13, p >= 0.347), as the claim says. The same measure taken before the call shows the same group difference (EV 31.9 to 45.9 against NV 12.8 to 13.8 pA, two-tailed p 0.0018 to 0.038; printed under fig2h). Seven experienced cells have IPSC recordings, where the paper has six; one of them (190410_e1) has only 4 or 5 trials per ISI, from one file of 19 sweeps, which may be the cell the paper left out, but the spreadsheet does not say so.
fig2j-ie-ratio-tracks-isi¶
In experienced females the IPSC-EPSC ratio is positively correlated with ISI, so that the balance shifts toward inhibition for slower ISIs; not so in naive females.
(IPSC - EPSC)/(IPSC + EPSC) per cell and ISI (Methods), from cells with both currents, correlated with ISI over all cell and ISI points.
rows = []
for (n, v), (w, _, _) in tables.items():
for grp in ("experienced", "naive"):
both = w.xs(grp, level="group").unstack("kind").dropna()
E, I = both.xs("EPSC", axis=1, level="kind"), both.xs("IPSC", axis=1, level="kind")
ratio = (I - E) / (I + E)
x = ratio.reset_index().melt(id_vars="cell", var_name="isi", value_name="v")
r = stats.pearsonr(x.isi.astype(float), x.v)
rows.append({"top": n, "variant": v, "group": grp, "cells": len(ratio),
"mean ratio 125/175/275/575": ratio.mean().round(2).tolist(), "r": round(r.statistic, 3), "p": round(r.pvalue, 4)})
print(pd.DataFrame(rows).to_string(index=False))
top variant group cells mean ratio 125/175/275/575 r p 5 main experienced 7 [0.11, 0.05, 0.01, 0.05] -0.055 0.7798 5 main naive 6 [-0.07, -0.07, -0.14, 0.03] 0.131 0.5422 5 all sweeps experienced 7 [0.05, 0.02, -0.05, 0.01] -0.040 0.8401 5 all sweeps naive 6 [-0.07, -0.07, -0.14, 0.03] 0.131 0.5422 5 expected direction experienced 7 [0.11, 0.09, 0.05, 0.0] -0.124 0.5303 5 expected direction naive 6 [-0.05, -0.22, -0.29, -0.06] 0.055 0.7975 5 loud-syllable onset experienced 7 [0.1, 0.05, -0.03, 0.11] 0.063 0.7494 5 loud-syllable onset naive 6 [-0.1, -0.14, -0.08, -0.05] 0.077 0.7221 10 main experienced 7 [0.1, 0.07, 0.03, 0.05] -0.076 0.7022 10 main naive 6 [-0.06, -0.07, -0.12, 0.03] 0.113 0.5996 10 all sweeps experienced 7 [0.04, 0.02, -0.05, -0.01] -0.054 0.7855 10 all sweeps naive 6 [-0.06, -0.07, -0.12, 0.03] 0.113 0.5997 10 expected direction experienced 7 [0.15, 0.15, 0.07, 0.05] -0.119 0.5461 10 expected direction naive 6 [0.14, -0.5, -0.37, -0.91] -0.266 0.2098 10 loud-syllable onset experienced 7 [0.11, 0.08, 0.01, 0.14] 0.087 0.6596 10 loud-syllable onset naive 6 [-0.1, -0.12, -0.07, -0.04] 0.071 0.7422
Result: blocked (other), as ed6ef-epsc-tuning: the current almost never follows the calls. As computed it would not be reproduced: in the 7 experienced cells with both currents the IPSC-EPSC ratio is not correlated with ISI (r = -0.055, p = 0.78; r -0.12 to 0.087 under every variant), against the paper's r = 0.47 (tolerance 0.42 to 0.52). The naive correlation is not significant (r = 0.13, p = 0.54; p >= 0.21 under every variant), as in the paper.
fig2k-ipsc-exceeds-epsc-575¶
Within cells of experienced cortex, 575 ms-evoked IPSCs are stronger than EPSCs, while the two do not differ at 125, 175 or 275 ms.
Experienced cells with both currents; paired two-tailed t-test of IPSC against EPSC per ISI.
rows = []
for (n, v), (w, _, _) in tables.items():
both = w.xs("experienced", level="group").unstack("kind").dropna()
E, I = both.xs("EPSC", axis=1, level="kind"), both.xs("IPSC", axis=1, level="kind")
for c in (125, 175, 275, 575):
rows.append({"top": n, "variant": v, "isi": c, "cells": len(both),
"IPSC": round(I[c].mean(), 1), "EPSC": round(E[c].mean(), 1),
"cells with I > E": int((I[c] > E[c]).sum()), "paired p": round(stats.ttest_rel(I[c], E[c]).pvalue, 4)})
print(pd.DataFrame(rows).to_string(index=False))
print("Cells:", sorted(both.index))
top variant isi cells IPSC EPSC cells with I > E paired p 5 main 125 7 39.4 28.9 6 0.2579 5 main 175 7 35.9 30.8 5 0.4229 5 main 275 7 38.0 35.2 5 0.5746 5 main 575 7 38.5 32.4 4 0.3990 5 all sweeps 125 7 39.7 34.5 5 0.6254 5 all sweeps 175 7 35.9 34.3 5 0.8443 5 all sweeps 275 7 38.7 42.0 3 0.6541 5 all sweeps 575 7 38.7 36.7 4 0.8193 5 expected direction 125 7 31.2 23.8 5 0.3392 5 expected direction 175 7 26.0 20.8 6 0.3287 5 expected direction 275 7 29.4 25.1 5 0.4729 5 expected direction 575 7 25.1 23.9 3 0.8661 5 loud-syllable onset 125 7 38.6 30.4 5 0.2551 5 loud-syllable onset 175 7 37.5 30.2 5 0.4110 5 loud-syllable onset 275 7 37.9 37.7 4 0.9610 5 loud-syllable onset 575 7 41.0 31.7 4 0.2869 10 main 125 7 28.0 21.4 4 0.3106 10 main 175 7 26.0 21.8 6 0.3178 10 main 275 7 27.7 24.9 5 0.4028 10 main 575 7 28.3 24.5 4 0.4622 10 all sweeps 125 7 28.5 26.4 4 0.7921 10 all sweeps 175 7 26.4 25.8 6 0.9250 10 all sweeps 275 7 29.0 32.2 4 0.5901 10 all sweeps 575 7 28.7 28.8 4 0.9869 10 expected direction 125 7 18.9 13.9 4 0.2970 10 expected direction 175 7 16.4 11.9 4 0.1693 10 expected direction 275 7 16.1 14.3 5 0.6283 10 expected direction 575 7 14.7 13.3 3 0.7740 10 loud-syllable onset 125 7 27.8 21.7 4 0.2315 10 loud-syllable onset 175 7 27.7 21.4 4 0.3381 10 loud-syllable onset 275 7 27.8 26.2 4 0.6118 10 loud-syllable onset 575 7 31.4 23.1 4 0.1653 Cells: ['190403_e1', '190410_e1', '190411_e1', '190419_e3', '190424_e1', '190424_e2', '190424_e3']
Result: blocked (other), as ed6ef-epsc-tuning: the current almost never follows the calls. As computed it would not be reproduced: at 575 ms IPSCs are larger than EPSCs on average (38.5 against 32.4 pA with the largest 5 trials, larger in 4 of 7 cells) but not significantly (paired p = 0.40; 0.17 to 0.99 under every variant). At 125, 175 and 275 ms they do not differ (p 0.17 to 0.96), as in the paper.
ed9b-oxytocin-enhances-spiking¶
Oxytocin enhances spiking to repeated 575 ms pulses in auditory cortical slices from stimulus 2 on, while stimulation without oxytocin does not change spike probability.
Spike probability per pulse (a crossing of 0 mV before the next pulse, a standard choice) at baseline (the files labelled baseline or PRE) and over the sweeps 15 to 30 minutes after wash onset (Methods), the onset being the first sweep of the first "wash in" file; for ACSF controls, "following baseline acquisition" (Methods) is read as from the first sweep after the baseline files. Friedman test over the ten columns (baseline and post for each stimulus) with Dunn's comparisons of baseline against post for each stimulus, Bonferroni over the five (the legend gives one p per stimulus); all 45 pairs and Wilcoxon signed-rank tests are variants.
stims = [f"stim{k}" for k in range(1, 6)]
def ox_table(control_anchor):
"""Baseline and 15-30 min post spike probability per cell. For controls the window counts from
the first sweep after the baseline files ("after baseline", main) or from the first baseline
sweep ("baseline start", a second reading of "following baseline acquisition")."""
rows = []
for (grp, cell), d in ox_sp.groupby(["group", "cell"]):
d = d.sort_values("abs_t")
base = d[d.role == "baseline"]
if not len(base):
base = d[d.file == d.file.iloc[0]]
if grp == "oxytocin":
onset = d[d.role == "wash in"].abs_t.min()
elif control_anchor == "after baseline":
onset = d[~d.file.isin(base.file)].abs_t.min()
else:
onset = base.abs_t.min()
mins = (d.abs_t - onset).dt.total_seconds() / 60
post = d[(mins >= S.OXY_WINDOW_MIN[0]) & (mins <= S.OXY_WINDOW_MIN[1]) & ~d.file.isin(base.file)]
r = {"group": grp, "cell": cell, "baseline sweeps": len(base), "post sweeps": len(post),
"post files": ",".join(sorted(post.file.unique()))}
for s in stims:
r["base " + s] = base[s].mean()
r["post " + s] = post[s].mean() if len(post) else np.nan
rows.append(r)
return pd.DataFrame(rows)
def ox_tests(ox, grp):
x = ox[ox.group == grp].dropna(subset=["post stim1"])
data = np.c_[x[["base " + s for s in stims]].values, x[["post " + s for s in stims]].values]
chi, dunn = S.dunn_friedman(data, [(k, k + 5) for k in range(5)])
_, dunn45 = S.dunn_friedman(data, [(i, j) for i in range(10) for j in range(i + 1, 10)])
sel45 = dunn45.set_index("pair").loc[[(k, k + 5) for k in range(5)]]
print(f"\n{grp}: {len(x)} cells with sweeps in the window (of {ox[ox.group == grp].shape[0]}); "
f"Friedman chi2 = {chi.statistic:.2f}, p = {chi.pvalue:.2g}")
out = pd.DataFrame({"stim": stims, "baseline": x[["base " + s for s in stims]].mean().values.round(3),
"post": x[["post " + s for s in stims]].mean().values.round(3),
"Dunn p, Bonf x5": dunn.p_bonf.round(4).values,
"Dunn p, Bonf x45": (sel45.p * 45).clip(upper=1).round(4).values,
"Wilcoxon p": [round(stats.wilcoxon(x["post " + s], x["base " + s]).pvalue, 4)
if (x["post " + s] != x["base " + s]).any() else 1.0 for s in stims]})
print(out.to_string(index=False))
ox = ox_table("after baseline")
print(ox.round(2).to_string(index=False))
for grp in ("oxytocin", "control"):
ox_tests(ox, grp)
print("\nVariant: control window counted from the first baseline sweep")
ox_b = ox_table("baseline start")
print(ox_b[ox_b.group == "control"][["cell", "baseline sweeps", "post sweeps", "post files"]].to_string(index=False))
ox_tests(ox_b, "control")
group cell baseline sweeps post sweeps post files base stim1 post stim1 base stim2 post stim2 base stim3 post stim3 base stim4 post stim4 base stim5 post stim5 control 191122B 8 68 19n22005,19n22006 0.38 0.65 0.12 0.59 0.38 0.59 0.12 0.54 0.38 0.26 control 191122C 6 73 19n22012,19n22013 0.17 0.21 0.33 0.27 0.17 0.22 0.17 0.18 0.00 0.05 control 191126D 28 70 19n26013,19n26014 0.00 0.10 0.00 0.06 0.00 0.10 0.00 0.07 0.00 0.00 control 191202A 7 44 19d02003 0.14 0.02 0.00 0.07 0.00 0.07 0.00 0.11 0.14 0.11 control 191202B 60 0 0.00 NaN 0.00 NaN 0.00 NaN 0.00 NaN 0.00 NaN control 191202C 60 0 0.00 NaN 0.00 NaN 0.00 NaN 0.00 NaN 0.00 NaN control 191202D 14 52 19d02015 0.14 0.00 0.14 0.00 0.07 0.00 0.14 0.00 0.07 0.00 oxytocin 191023B 33 60 19o23015,19o23016 0.00 1.00 0.06 0.98 0.03 0.98 0.09 1.00 0.03 0.78 oxytocin 191023C 19 69 19o23023,19o23025,19o23026 0.00 0.01 0.00 0.04 0.00 0.12 0.00 0.06 0.00 0.12 oxytocin 191023D 20 38 19o23031,19o23033 0.00 0.00 0.00 0.00 0.10 0.03 0.00 0.03 0.10 0.05 oxytocin 191023E 21 75 19o23040,19o23041 0.00 0.67 0.00 0.73 0.00 0.76 0.00 0.76 0.00 0.75 oxytocin 191028A 27 56 19o28008 0.00 0.05 0.19 0.45 0.11 0.45 0.11 0.57 0.11 0.43 oxytocin 191028C 21 46 19o28037,19o28038 0.00 0.24 0.00 0.15 0.00 0.09 0.00 0.02 0.00 0.09 oxytocin 191031A 35 57 19o31011,19o31012,19o31013 0.00 0.51 0.00 0.54 0.00 0.72 0.00 0.60 0.00 0.35 oxytocin 191031B 14 70 19o31019,19o31020 0.36 1.00 0.14 1.00 0.21 0.99 0.14 1.00 0.07 1.00 oxytocin 191106A 25 45 19n06012 0.04 0.80 0.04 0.91 0.00 0.87 0.00 0.89 0.00 0.87
oxytocin: 9 cells with sweeps in the window (of 9); Friedman chi2 = 51.35, p = 6e-08 stim baseline post Dunn p, Bonf x5 Dunn p, Bonf x45 Wilcoxon p stim1 0.044 0.476 0.0710 0.6388 0.0078 stim2 0.048 0.535 0.0224 0.2020 0.0078 stim3 0.051 0.555 0.0136 0.1226 0.0078 stim4 0.038 0.547 0.0011 0.0098 0.0039 stim5 0.035 0.492 0.0199 0.1787 0.0078 control: 5 cells with sweeps in the window (of 7); Friedman chi2 = 7.72, p = 0.56 stim baseline post Dunn p, Bonf x5 Dunn p, Bonf x45 Wilcoxon p stim1 0.165 0.195 1.0000 1.0 1.0000 stim2 0.120 0.198 1.0000 1.0 0.8125 stim3 0.123 0.195 0.7927 1.0 0.3125 stim4 0.087 0.181 1.0000 1.0 0.4375 stim5 0.118 0.087 1.0000 1.0 0.3750 Variant: control window counted from the first baseline sweep cell baseline sweeps post sweeps post files 191122B 8 68 19n22005,19n22006 191122C 6 73 19n22012,19n22013 191126D 28 57 19n26012,19n26013,19n26014 191202A 7 52 19d02003 191202B 60 22 19d02006 191202C 60 50 19d02010 191202D 14 60 19d02015 control: 7 cells with sweeps in the window (of 7); Friedman chi2 = 9.49, p = 0.39
stim baseline post Dunn p, Bonf x5 Dunn p, Bonf x45 Wilcoxon p stim1 0.118 0.163 1.0000 1.0 0.5625 stim2 0.086 0.159 1.0000 1.0 0.4375 stim3 0.088 0.168 0.4675 1.0 0.0938 stim4 0.062 0.141 0.7892 1.0 0.3125 stim5 0.084 0.086 1.0000 1.0 1.0000
# The spreadsheet label behind each file's role, and each file's sweeps and timing (minutes from the
# cell's first recorded sweep), so that the baseline and post windows can be checked by cell.
lab = ox_list.copy()
per_file = ox_sp.groupby("file").agg(sweeps=("sweep", "size"), start=("abs_t", "min"), end=("abs_t", "max"))
lab = lab.merge(per_file, left_on="file", right_index=True, how="left")
t0 = lab.groupby("cell").start.transform("min")
lab["start min"] = ((lab.start - t0).dt.total_seconds() / 60).round(1)
lab["end min"] = ((lab.end - t0).dt.total_seconds() / 60).round(1)
print(lab[["group", "cell", "file", "label", "role", "on_figshare", "sweeps", "start min", "end min"]].to_string(index=False))
group cell file label role on_figshare sweeps start min end min oxytocin 191023B 19o23010 IV pre iv True NaN NaN NaN oxytocin 191023B 19o23011 baseline, 240pA step baseline True 6.0 0.0 1.0 oxytocin 191023B 19o23012 baseline, 230pA step baseline True 27.0 1.4 6.6 oxytocin 191023B 19o23013 wash in wash in True 16.0 7.1 10.1 oxytocin 191023B 19o23014 OT other True 40.0 10.3 18.1 oxytocin 191023B 19o23015 wash out begins wash out True 39.0 18.5 26.1 oxytocin 191023B 19o23016 other True 39.0 28.6 36.2 oxytocin 191023B 19o23017 IV post iv True NaN NaN NaN oxytocin 191023C 19o23018 IV pre iv True NaN NaN NaN oxytocin 191023C 19o23021 baseline, 90pA step baseline True 19.0 0.0 3.6 oxytocin 191023C 19o23022 wash in wash in True 40.0 3.8 11.6 oxytocin 191023C 19o23023 OT other True 60.0 12.0 23.8 oxytocin 191023C 19o23024 IV w OT iv True NaN NaN NaN oxytocin 191023C 19o23025 wash out begins wash out True 21.0 25.1 29.1 oxytocin 191023C 19o23026 other True 31.0 29.3 35.3 oxytocin 191023E 19o23036 IV pre iv True NaN NaN NaN oxytocin 191023E 19o23037 baseline, 270pA baseline True 21.0 0.0 4.0 oxytocin 191023E 19o23038 wash in wash in True 45.0 5.4 14.2 oxytocin 191023E 19o23039 IV with OT iv True NaN NaN NaN oxytocin 191023E 19o23040 OT: spikes by S30 other True 60.0 16.0 27.8 oxytocin 191023E 19o23041 wash out - POST: S40-S60 wash out True 52.0 28.1 38.3 oxytocin 191023E 19o23043 IV post iv True NaN NaN NaN oxytocin 191023D 19o23027 IV pre iv True NaN NaN NaN oxytocin 191023D 19o23028 baseline, 170pA baseline True 20.0 0.0 3.8 oxytocin 191023D 19o23029 wash in, spikes picking up wash in True 40.0 4.1 11.9 oxytocin 191023D 19o23030 IV w OT iv True NaN NaN NaN oxytocin 191023D 19o23031 OT other True 40.0 12.8 20.6 oxytocin 191023D 19o23032 IV w OT iv True NaN NaN NaN oxytocin 191023D 19o23033 wash out wash out True 30.0 21.7 27.5 oxytocin 191028A 19o28000 IV pre iv True NaN NaN NaN oxytocin 191028A 19o28004 PRE: step at 250pA baseline True 27.0 0.0 5.2 oxytocin 191028A 19o28005 Recording wash in wash in True 33.0 5.9 12.3 oxytocin 191028A 19o28006 wash in, spiking wash in True 19.0 13.4 17.0 oxytocin 191028A 19o28007 OT recording other True 12.0 17.4 19.6 oxytocin 191028A 19o28008 OT other True 60.0 20.1 31.9 oxytocin 191028A 19o28009 IV OT iv True NaN NaN NaN oxytocin 191028C 19o28030 IV pre iv True NaN NaN NaN oxytocin 191028C 19o28031 PRE: baseline 320pA baseline True 21.0 0.0 4.0 oxytocin 191028C 19o28032 wash in, 315pA, spikes begin wash in True 3.0 6.3 6.7 oxytocin 191028C 19o28033 wash in, 310pA wash in True 25.0 7.1 11.9 oxytocin 191028C 19o28034 IV OT iv True NaN NaN NaN oxytocin 191028C 19o28035 OT recording other True 34.0 13.1 19.7 oxytocin 191028C 19o28036 recording OT, 315pA other True 3.0 20.1 20.5 oxytocin 191028C 19o28037 wash out wash out True 40.0 21.1 28.9 oxytocin 191028C 19o28038 POST wash out True 7.0 29.3 30.5 oxytocin 191031A 19o31004 IV PRE iv True NaN NaN NaN oxytocin 191031A 19o31005 baseline, 150pA baseline True 15.0 0.0 2.8 oxytocin 191031A 19o31006 baseline, 160pA baseline True 20.0 3.6 7.4 oxytocin 191031A 19o31007 wash in wash in True 30.0 7.6 13.4 oxytocin 191031A 19o31008 wash in wash in True 2.0 16.8 17.0 oxytocin 191031A 19o31009 OT with 160pA other True 3.0 17.5 17.9 oxytocin 191031A 19o31010 OT w 150pA other True 1.0 21.5 21.5 oxytocin 191031A 19o31011 Ot with 150pA again other True 4.0 22.6 23.2 oxytocin 191031A 19o31012 OT with 160pA step other True 21.0 23.7 27.7 oxytocin 191031A 19o31013 wash out wash out True 32.0 28.7 34.9 oxytocin 191031B 19o31016 IV PRE iv True NaN NaN NaN oxytocin 191031B 19o31017 baseline, 110pA baseline True 14.0 0.0 2.6 oxytocin 191031B 19o31018 wash in wash in True 32.0 2.9 9.1 oxytocin 191031B 19o31019 Ot in bath, 110pA other True 60.0 9.5 21.3 oxytocin 191031B 19o31020 wash out wash out True 60.0 22.5 34.3 oxytocin 191106A 19n06008 IV pre iv True NaN NaN NaN oxytocin 191106A 19n06010 baseline, test with 320pA step baseline True 25.0 0.0 4.8 oxytocin 191106A 19n06011 Wash in, watched bubble, OT ~S10 wash in True 60.0 5.1 16.9 oxytocin 191106A 19n06012 OT recording other True 60.0 17.2 29.0 control 191122B 19n22003 baseline w step to 250pA baseline True 8.0 0.0 1.4 control 191122B 19n22004 step to 240pA other True 28.0 2.2 7.6 control 191122B 19n22005 step to 230pA other True 60.0 8.1 19.9 control 191122B 19n22006 step to 230pA other True 60.0 21.5 33.3 control 191122C 19n22007 IV pre iv True NaN NaN NaN control 191122C 19n22008 baseline, step 220pA baseline True 6.0 0.0 1.0 control 191122C 19n22009 step 210 pA other True 10.0 1.4 3.2 control 191122C 19n22010 step 210pA other True 13.0 4.0 6.4 control 191122C 19n22011 step 200pA other True 23.0 6.9 11.3 control 191122C 19n22012 step 190pA other True 60.0 11.7 23.5 control 191122C 19n22013 step 180pA other True 44.0 24.1 32.7 control 191126D 19n26010 IV pre, spikes at 220pA iv True NaN NaN NaN control 191126D 19n26011 Baseline, step to 190pA baseline True 28.0 0.0 5.4 control 191126D 19n26012 step 190pA other True 60.0 5.9 17.7 control 191126D 19n26013 step 190pA other True 10.0 21.3 23.1 control 191126D 19n26014 step 190pA other True 60.0 23.6 35.4 control 191126D 19n26015 step 180pA other True NaN NaN NaN control 191202A 19d02000 IV pre, spikes at 240pA iv True NaN NaN NaN control 191202A 19d02001 baseline, step 210pA, cell spiking baseline True 7.0 0.0 1.2 control 191202A 19d02002 step, 200pA other True 60.0 1.6 13.4 control 191202A 19d02003 rec 200pa - end other True 55.0 14.5 25.3 control 191202B 19d02004 IV pre, spikes at 120pA iv True NaN NaN NaN control 191202B 19d02005 baseline, 50pA step, spiking at 60pA baseline True 60.0 0.0 11.8 control 191202B 19d02006 step to 40pA other True 27.0 14.1 19.3 control 191202C 19d02008 IV pre, spikes at 250pA iv True NaN NaN NaN control 191202C 19d02009 baseline, step t 100pA baseline True 60.0 0.0 11.8 control 191202C 19d02010 100pA other True 60.0 13.0 24.8 control 191202D 19d02013 130pA other True 14.0 0.0 2.6 control 191202D 19d02014 120pA other True 60.0 3.1 14.9 control 191202D 19d02015 100pA other True 60.0 16.6 28.4
Result: reproduced, resting on the reading of the Dunn comparisons. In the 9 oxytocin cells spike probability rises from 0.035 to 0.051 at baseline to 0.48 to 0.56 in the 15 to 30 minutes after wash onset (Friedman chi2 = 51.35, p = 6e-08). With Dunn's comparisons of baseline against post for each stimulus, Bonferroni over five, stimuli 2 to 5 increase (adjusted p 0.022, 0.014, 0.0011 and 0.020) and stimulus 1 does not (p = 0.071), the pattern of the paper (0.34; 0.009, 0.008, 0.001, 0.04), although stimulus 1 also rises (0.044 to 0.48). The 5 ACSF controls with sweeps in the window show no change (Friedman p = 0.56, all adjusted p >= 0.79). The outcome depends on the comparison family: over all 45 pairs only stimulus 4 is significant (p = 0.0098), and Wilcoxon tests find stimulus 1 significant too (p = 0.0078). The label table shows each role: two controls (191202B and C) have one baseline file of 60 sweeps (0 to 11.8 min) and one later file (14.1 to 19.3 and 13.0 to 24.8 min), so the window 15 to 30 minutes after their first sweep after baseline holds no sweep. Counting the control window from the first baseline sweep instead brings in all 7 controls, which again show no change (Friedman p = 0.39, all adjusted p >= 0.47).
Blocked claims: what the data show¶
The start of cohousing is not recorded, so retrieval onset (fig3-retrieval-onset-range) cannot be measured. The hours from the last session imaged before the first retrieval session (a base, 24 or 48 hours PRE session) are printed with those from the last session named base. Either is an upper bound only if cohousing began after that session, which nothing in the files says; they are printed for the record and decide nothing. NV50 has no session named base, only a 24hrsPRE one.
x = files.assign(t=pd.to_datetime(files.session_start.str[:19]), d=files.session_description.str.lower().str.replace(" ", ""))
rows = []
for s, d in x.groupby("subject_id"):
fr = d[d.d.str.contains("firstretrieval")]
if not len(fr):
continue
b = d[d.d.str.startswith("base")]
before = d[d.t < fr.t.min()] # any session before the first retrieval (base, 24hrsPRE, 48HrsPRE ...)
rows.append({"subject": s, "last base session": b.t.max(), "last session before": before.t.max(),
"its name": before.sort_values("t").session_description.iloc[-1][:24] if len(before) else "",
"first retrieval": fr.t.min(),
"hours from last base": round((fr.t.min() - b.t.max()).total_seconds() / 3600, 1) if len(b) else np.nan,
"hours from last session before": round((fr.t.min() - before.t.max()).total_seconds() / 3600, 1) if len(before) else np.nan})
print(pd.DataFrame(rows).to_string(index=False))
print("Subjects with a first retrieval session:", len(rows), "(paper: N=13 with onset 12 to 109 h)")
print(f"\nRun time: {(time.time() - started) / 60:.1f} min")
subject last base session last session before its name first retrieval hours from last base hours from last session before NV115 2017-01-07 16:14:31 2017-01-10 16:18:24 48HrsPRE_r1_71b_1300_sta 2017-01-12 18:17:53 122.1 50.0 NV21 2017-03-19 18:26:53 2017-03-19 18:26:53 base1_r1_p26_1600_600_00 2017-03-21 22:30:53 52.1 52.1 NV25 2017-04-24 22:33:26 2017-04-27 15:44:31 24hrsPRE_r2_pc2_j1a_j8a_ 2017-04-29 12:05:19 109.5 44.3 NV28 2017-05-23 22:27:02 2017-05-23 22:27:02 base_r4_71b_2600_p26_280 2017-05-24 22:18:19 23.9 23.9 NV32 2017-06-12 17:13:31 2017-06-12 17:13:31 base_r2_pc2_j1a_j8a_j3a_ 2017-06-13 22:26:30 29.2 29.2 NV33 2017-07-11 18:50:12 2017-07-11 18:50:12 base_r2_j1a4480_61f3400_ 2017-07-13 14:25:14 43.6 43.6 NV36 2017-08-07 22:41:13 2017-08-07 22:41:13 base_r2_pc2_j1a_j8a_j3a_ 2017-08-08 22:33:01 23.9 23.9 NV37 2017-08-28 17:48:27 2017-08-28 17:48:27 base_r2_71b4200_007 2017-08-30 08:23:46 38.6 38.6 NV39 2017-09-04 16:44:03 2017-09-04 16:44:03 base_r2_61f3400REAL_j1a4 2017-09-06 10:54:11 42.2 42.2 NV41 2017-09-18 22:01:37 2017-09-18 22:01:37 base_r2_pc2_j1a_j8a_j3a_ 2017-09-20 17:08:21 43.1 43.1 NV43 2017-10-09 17:08:25 2017-10-13 15:39:11 24hoursPRE_r3_fmsweep64k 2017-10-14 18:24:47 121.3 26.8 NV47 2017-11-20 13:06:40 2017-11-24 15:49:20 24hrsPRE_r1_71b2600_4200 2017-11-26 14:00:39 144.9 46.2 NV50 NaT 2018-01-03 23:44:24 24hrsPRE_r1_71b_2600_420 2018-01-05 01:03:20 NaN 25.3 NV54 2018-01-29 19:01:18 2018-01-29 19:01:18 base_r1_71b_2600_4200_47 2018-01-30 22:47:59 27.8 27.8
Subjects with a first retrieval session: 14 (paper: N=13 with onset 12 to 109 h) Run time: 5.5 min
Summary¶
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.
| Claim | Data | Outcome |
|---|---|---|
| subject-group-link | with labels | partially reproduced |
| fig1g-ipsc-depress-faster | elsewhere (figshare) | not reproduced (assumed) |
| fig1i-operant-prototypes | elsewhere (figshare) | blocked: methods underspecified |
| ed5b-press-count-unchanged | elsewhere (figshare) | blocked: methods underspecified |
| ed6ef-epsc-tuning | elsewhere (figshare) | blocked: other (call-evoked currents in too few recorded cells) |
| fig2h-epsc-enhanced-ev | elsewhere (figshare) | blocked: other (as ed6ef) |
| fig2i-ipsc-weaker-nv | elsewhere (figshare) | blocked: other (as ed6ef) |
| fig2j-ie-ratio-tracks-isi | elsewhere (figshare) | blocked: other (as ed6ef) |
| fig2k-ipsc-exceeds-epsc-575 | elsewhere (figshare) | blocked: other (as ed6ef) |
| ed6h-dams-evs-epsc-similar | elsewhere (figshare) | blocked: metadata insufficient |
| ed9b-oxytocin-enhances-spiking | elsewhere (figshare) | reproduced (assumed) |
| 20 imaging claims (ed3e, fig1f, ed6c, ed3k, ed3m, ed4c, ed4e, ed4g, fig2d, ed6d, fig2e, fig2f, ed7bc, fig3e, fig3h, ed8cd, fig4b, fig4g, fig4h, ed3bc) | dandiset | blocked: metadata insufficient |
| fig3-retrieval-onset-range | dandiset | blocked: metadata insufficient |
| fig1a, fig1c (three claims), ed2a, ed2b, ed2d, fig4c, fig4d, ed8hi, fig4e, ed4d | dandiset | blocked: data missing |
How the analysis differs from the paper's. No claim on the dandiset's own data could be tested beyond the link: the movies carry no ROIs, stimulus order or stimulus times (the call period, 40 frames, can be found in the movies, and in 31 of 38 three-call movies a cycle of three trials, but not which call each trial played). The tested claims use the figshare recordings the claims file names. For the in vivo recordings the call's onset, timing and ISI come from the recorded microphone channel, and the stimulus sets hold the legends' ISIs (125, 175, 275, 575 ms). The recorded currents almost never follow the calls: 2 of 39 cell recordings respond to the calls on the Methods' measure, against 3 of 39 before the call; 13 of 110 files have a sweep-locked deflection, more than the 5.5 chance gives, but only two cells (190326_e1 and 190313_e2) carry a current in the expected direction during the call, from about 0.4 s after onset; and the Methods' inclusion rule, with a significant PSC defined here, keeps at most 1 of 39 cell recordings, so the five in vivo claims are blocked. Dams and EVs cannot be separated. Slice peaks are measured 2 to 30 ms after each pulse, a choice made here. Operant presses are reconstructed from touch times printed at a resolution of 0.1 to 100 ms. For the oxytocin slices the Dunn comparison family is read as five planned comparisons.
What was found about the dataset.
- The dandiset holds only raw, unaligned two-photon movies. There are no ROIs or traces (the paper drew ROIs by hand), no stimulus times and no order of the calls within a recording, so none of the 20 imaging findings can be tested. The figshare imaging items hold the same movies as TIFF. The stimuli recur every 40 frames (10.24 s, the Methods' 0.1 Hz rounded; tones every 20 frames, 5.12 s). In 31 of 38 three-call movies the trials appear to recur in a fixed cycle of three, where the Methods say the order was pseudorandom; nothing says which call is at which position.
- The behavioural data (retrieval, y-maze) and the vocalization recordings behind Fig. 1a are in neither the dandiset nor the figshare collection.
- Three recordings are deposited under both NV58 and NV63, with the same start times and frames.
- EV56 and EV62 are described as NV56 and NV62 in their files, and the five tones-only mice carry no experience group in their descriptions; the figshare item titles give it (EV56 and EV62 experienced, NV64, NV66 and NV140 naive). Indicator and location are "unknown" in every file, though the descriptions state both.
- The paper's figshare DOI does not resolve; the dandiset links the figshare collection.
- In the in vivo whole-cell data the current almost never follows the calls: two cells carry an inward current during the call from about 0.4 s after onset, and the rest show about as many responses as chance gives. The experienced recordings are about twice as noisy as the naive ones, and file 19424013 is listed at -70 mV but was recorded at a depolarized potential. The first syllable of many calls is much quieter than the rest. One listed file of each of the in vivo naive and slice articles is not on figshare. The spreadsheets do not mark dams.
- The operant touch times are printed with five significant digits, so after 1000 s of a session they step by 0.1 s.