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.

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

Data check¶

The dandiset¶

Every NWB file of the pinned version is opened (streamed); the survey reads each file's groups, the movie's shape, type and rate, the descriptions, and the first and last frame (to compare recordings). Nothing else of the movies is read here.

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

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

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

In [5]:
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)
No description has been provided for this image
In [6]:
# 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
In [7]:
# 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.

In [8]:
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
In [9]:
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}
In [10]:
# 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
In [11]:
# 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")
No description has been provided for this image
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
In [12]:
# 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]
No description has been provided for this image
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).

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

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

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

In [17]:
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
In [18]:
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)
No description has been provided for this image
In [19]:
# 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)
No description has been provided for this image

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.

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

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

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

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.

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

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

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

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

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

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