Reproducing Bennett, Ouellette, Ramirez et al. (2024) from dandiset 001051¶

Paper. Bennett C, Ouellette B, Ramirez TK, et al. (2024). SHIELD: Skull-shaped hemispheric implants enabling large-scale electrophysiology datasets in the mouse brain. Neuron 112, 2869-2885. doi:10.1016/j.neuron.2024.06.015. The claims file was written from the version of record (main text, STAR Methods and the supplemental figure legends) and the paper's code, https://github.com/AllenInstitute/SHIELD_Dynamic_Gating_Analysis at commit 3c09a0dc.

Dandiset. DANDI:001051, version 0.260922.1827. The paper cites the dandiset without a version (its data statement gives DANDI:001051 and the dandiset's page). There are two published versions, 0.260825.2232 and 0.260922.1827; both hold the same 666 assets with the same blobs and sizes, and the later one only updates metadata ("Updated metadata"). The most recent published version is pinned, as the protocol says when no version is cited.

What the dandiset holds. 99 sessions of 27 mice (one NWB file per session with spikes, unit metrics, CCF channel locations, stimulus tables, trials, licks, rewards, running and eye tracking) and 567 LFP files (not used here). It holds the behaving-mouse recordings of Figures 4, 7, 8 and S4; it does not hold the surgery records, ISI images, histology, MRI or training histories of Figure 3, the VGAT-ChR2 and Vglut2-Cre mice of Figure 6, the dual-hemisphere recordings of Figure 5, or which sessions had retracted or sharpened probes (Figure S4D to S4H).

Outside inputs. The paper's repository at commit 3c09a0dc2972381f06393ddf5c000e16462c7037: the list of the 87 sessions passing ephys QC (read from DynamicGating_ephys_metrics.ipynb), its units, probes and session tables (for the link claim), and the VBN units and sessions tables (VBN_units_table.csv, VBN_sessions.csv; the 5-mm window comparison from DANDI:000713), used by the with_inputs claims.

Runtime. About 20 minutes, almost all of it streaming the 99 session files (about 640 MB of spike times each) in four processes.

Claim Tested here
link-code-tables (added link) tested
fig3b-surgical-outcomes, fig3c-isi-through-implant, fig3d-histology-intact, tracks-rare-bleeding blocked, data missing
fig3fh-mri-deformation, fig3i-learning-similar blocked, data missing
fig4b-units-areas-per-session, figs4ab-insertions-per-mouse, scale-mice-sessions-insertions, penetration-success tested
fig4b-trajectory-consistency blocked, metadata insufficient
fig4c-drift-discontinuities-rare blocked, methods underspecified
fig4d-unit-drift, fig4d-qc-metrics-comparable, qc-pass-fraction-similar tested
fig4e-unit-yield-stable, fig4e-vbn-yield-drops, fig4e-firing-rate-stable, fig4ef-rf-fraction-stable tested
fig4f-rf-match-retinotopy blocked, data missing
fig4g-visual-responses-similar, behavior-stable-across-days, figs4i-insertion-suppression tested
figs4de-retraction-reduces-drift, figs4f-retraction-yield-similar, figs4gh-sharpened-probes blocked, metadata insufficient
figs3d-duragel-yield-stable, fig5-bilateral-recordings, fig6bc-cortical-silencing, fig6ef-optotagging-mrn blocked, data missing
fig7a to fig7f, fig7-hippocampus-not-engaged, figs6-replay (10 claims) tested
fig8bc, fig8d (two), fig8e, fig8f (5 claims) tested
In [1]:
import time
import warnings

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
from scipy.optimize import curve_fit
from statsmodels.stats.multitest import multipletests

import shield

pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 300)
warnings.filterwarnings("ignore")
started = time.time()
In [2]:
# Stream the session files: tables first, then one pass over each session's spikes.
assets = shield.list_assets()
session_assets = assets[~assets["path"].str.contains("_probe-")].reset_index(drop=True)
sessions, units = shield.scan_all(session_assets["asset_id"].tolist())
sessions["path"] = session_assets["path"].values
print(f"tables of {len(sessions)} sessions read in {time.time() - started:.0f} s")
SP = shield.spike_pass_all(sessions, units)
print(f"spike pass done after {time.time() - started:.0f} s")
tables of 99 sessions read in 63 s
spike pass done after 715 s
In [3]:
# The paper's code: the 87 sessions passing ephys QC, its unit and session tables (pinned commit)
PASS = set(shield.passing_session_ids())
sessions["passing"] = sessions["session_id"].isin(PASS)
sessions["day"] = sessions["session_type"].str.replace("EPHYS_", "").astype(int)
units = units.join(sessions.set_index("session_id")[["subject_id", "session_type", "passing", "genotype"]], on="session_id")
units["qc"] = shield.qc_pass(units)
units["good"] = units["quality"] == "good"
units["in_area"] = ~units["structure_acronym"].isin(shield.NOT_IN_AREA)
code_units = shield.read_code_csv("metadata_tables/units_table.csv")
vbn_units = shield.read_code_csv("metadata_tables/VBN_units_table.csv")
vbn_sessions = shield.read_code_csv("metadata_tables/VBN_sessions.csv")
code_sessions = shield.read_code_csv("metadata_tables/dynamic_gating_session_metadata.csv")
print("code units table:", code_units.shape, "; VBN units:", vbn_units.shape, "; VBN sessions:", vbn_sessions.shape)
code units table: (201520, 47) ; VBN units: (319013, 27) ; VBN sessions: (153, 22)

Data check¶

In [4]:
meta = shield.version_metadata()
print("Dandiset", shield.DANDISET_ID, "version", shield.DANDISET_VERSION, ":", meta["name"])
print("assets:", len(assets), "; session files:", len(session_assets), "; LFP probe files:", len(assets) - len(session_assets),
      f"; total {assets['size'].sum() / 1e12:.2f} TB")
print("subjects in the archive summary:", meta["assetsSummary"].get("numberOfSubjects"))
print("\nsessions per subject and genotype:")
print(sessions.groupby(["genotype"]).agg(mice=("subject_id", "nunique"), sessions=("session_id", "size")).to_string())
print("\nsex by mouse:", sessions.groupby("subject_id")["sex"].first().value_counts().to_dict())
print("session types:", sessions["session_type"].value_counts().sort_index().to_dict())
print("duplicate session identifiers:", int(sessions["session_id"].duplicated().sum()))
print("sessions in the code's 87-session list found in the dandiset:", int(sessions["passing"].sum()), "of", len(PASS))
print("\nsessions outside the 87:")
print(sessions.loc[~sessions["passing"], ["path", "session_id", "session_type", "subject_id", "n_units"]].to_string(index=False))
Dandiset 001051 version 0.260922.1827 : Large-scale Neuropixels recordings through SHIELD implant during visual change detection task with dynamic gating of engagement
assets: 666 ; session files: 99 ; LFP probe files: 567 ; total 1.81 TB
subjects in the archive summary: 27

sessions per subject and genotype:
                                               mice  sessions
genotype                                                     
Ai32(RCL-ChR2(H134R)_EYFP)/wt                     1         3
Sst-IRES-Cre/wt;Ai32(RCL-ChR2(H134R)_EYFP)/wt    16        58
Vip-IRES-Cre/wt;Ai32(RCL-ChR2(H134R)_EYFP)/wt     4        15
wt/wt                                             6        23

sex by mouse: {'M': 18, 'F': 9}
session types: {'EPHYS_1': 24, 'EPHYS_2': 26, 'EPHYS_3': 25, 'EPHYS_4': 24}
duplicate session identifiers: 0
sessions in the code's 87-session list found in the dandiset: 87 of 87

sessions outside the 87:
                                         path  session_id session_type subject_id  n_units
sub-604914/sub-604914_ses-20220427T041046.nwb  1173189336      EPHYS_2     604914     1758
sub-604914/sub-604914_ses-20220429T060155.nwb  1173741216      EPHYS_4     604914     2262
sub-614608/sub-614608_ses-20220629T220553.nwb  1187940755      EPHYS_3     614608     2356
sub-623322/sub-623322_ses-20220825T212029.nwb  1200878248      EPHYS_4     623322     1854
sub-623786/sub-623786_ses-20220915T202517.nwb  1211232400      EPHYS_4     623786     2351
sub-633232/sub-633232_ses-20221108T000611.nwb  1224385827      EPHYS_1     633232     1088
sub-633232/sub-633232_ses-20221108T230110.nwb  1224635971      EPHYS_2     633232     1933
sub-633232/sub-633232_ses-20221109T225948.nwb  1224928532      EPHYS_3     633232     1451
sub-633232/sub-633232_ses-20221110T225606.nwb  1225178442      EPHYS_4     633232     1461
sub-636740/sub-636740_ses-20221117T230548.nwb  1226780656      EPHYS_4     636740     2402
sub-637483/sub-637483_ses-20221123T172329.nwb  1228018071      EPHYS_3     637483     1817
sub-637488/sub-637488_ses-20221209T060640.nwb  1231476077      EPHYS_4     637488     2500
In [5]:
print("units:", len(units), "; quality values:", units["quality"].value_counts().to_dict())
print("isi_label values:", units["isi_label"].value_counts().to_dict())
cols = ["presence_ratio", "amplitude_cutoff", "isi_violations", "snr", "isolation_distance", "max_drift",
        "cumulative_drift", "amplitude", "firing_rate", "waveform_duration", "x", "y", "z"]
print(units[cols].describe(percentiles=[0.01, 0.5, 0.99]).T.round(3).to_string())
print("\nCCF coordinates (x anterior-posterior, y dorsal-ventral, z left-right, micrometres); midline z = 5700.")
inb = units[units["in_area"]]
print("annotated units with z > 5700:", int((inb["z"] > 5700).sum()), "; largest z:", inb["z"].max(),
      "; structures:", inb.loc[inb["z"] > 5700, "structure_acronym"].value_counts().head(8).to_dict())
print("units with coordinate -25 (no position):", int((units["x"] == -25).sum()),
      "; of those annotated in an area:", int(((units["x"] == -25) & units["in_area"]).sum()))
units: 201192 ; quality values: {'good': 163145, 'noise': 38047}
isi_label values: {'No label': 180372, 'VISp': 7834, 'VIS': 4715, 'VISlm': 2806, 'VISrl': 2139, 'VISam': 854, 'VISpm': 803, 'VISal': 731, 'VISl': 337, 'VISrll': 327, 'VISmma': 274}
                       count          mean           std     min      1%       50%           99%           max
presence_ratio      201192.0  8.800000e-01  2.220000e-01   0.010   0.080     0.990  9.900000e-01  9.900000e-01
amplitude_cutoff    201192.0  1.790000e-01  2.000000e-01   0.000   0.000     0.069  5.000000e-01  5.000000e-01
isi_violations      201192.0  1.228800e+01  6.511460e+02   0.000   0.000     0.072  6.280700e+01  1.473621e+05
snr                 201192.0  2.684000e+00  1.328000e+00   0.000   0.571     2.426  7.137000e+00  3.534900e+01
isolation_distance  201192.0  8.862258e+20  3.532465e+23   0.000   0.000    55.163  2.745429e+13  1.570126e+26
max_drift           201192.0  2.926000e+01  2.454000e+01   0.000   0.000    23.555  1.162310e+02  2.743800e+02
cumulative_drift    201192.0  1.408630e+02  1.421400e+02   0.000   0.000   108.800  6.986770e+02  2.316120e+03
amplitude           201192.0  1.518130e+02  1.091950e+02   0.847  29.944   122.620  5.773860e+02  2.551869e+03
firing_rate         201192.0  5.320000e+00  7.510000e+00   0.000   0.003     2.795  3.536300e+01  1.190590e+02
waveform_duration   201192.0  5.720000e-01  2.960000e-01   0.014   0.151     0.563  2.019000e+00  2.692000e+00
x                   201192.0  5.713993e+03  2.786315e+03 -25.000  -1.000  6400.000  9.300000e+03  9.800000e+03
y                   201192.0  2.124297e+03  1.272906e+03 -25.000  -1.000  2125.000  4.575000e+03  5.400000e+03
z                   201192.0  3.438140e+03  1.612368e+03 -25.000  -1.000  3825.000  5.525000e+03  6.250000e+03

CCF coordinates (x anterior-posterior, y dorsal-ventral, z left-right, micrometres); midline z = 5700.
annotated units with z > 5700: 464 ; largest z: 6250.0 ; structures: {'LSr': 142, 'ACB': 70, 'STR': 65, 'TTd': 64, 'SFO': 51, 'ILA': 34, 'SPA': 18, 'IMD': 12}
units with coordinate -25 (no position): 145 ; of those annotated in an area: 0

All recordings are in the left hemisphere: no annotated unit lies more than 550 micrometres right of the midline, and those that do are in midline structures. Every unit is labelled good or noise, as the Methods' non-noise rule needs. The subjects' genotypes are Sst-IRES-Cre;Ai32, Vip-IRES-Cre;Ai32, Ai32 and wild type: the VGAT-ChR2 and Vglut2-IRES-Cre mice of Figure 6 are not in the dandiset.

In [6]:
spt = pd.DataFrame([{"session_id": k, "first_stim": v["first_stim"], "behavior_start": v["beh_start"],
                     "behavior_end": v["beh_end"], "last_stim_end": v["last_stim_end"],
                     "images": ",".join(v["image_names"]), "im115_presentations": v["im115_n"]} for k, v in SP.items()])
sessions = sessions.merge(spt, on="session_id")
sessions["behavior_min"] = (sessions["behavior_end"] - sessions["behavior_start"]) / 60
print(sessions[["behavior_min", "first_stim", "last_stim_end", "im115_presentations"]].describe().round(1).to_string())
print("\nimage sets:", sessions["images"].value_counts().to_dict())
print("sessions without eye tracking:", sessions.loc[~sessions["has_eye"], "session_id"].tolist())
print("rewards per session (all / auto / earned):")
print(sessions[["n_rewards", "n_rewards_auto", "n_rewards_earned", "n_hit", "n_trials"]].describe().round(1).to_string())
print("earned rewards equal hit trials in", int((sessions["n_rewards_earned"] == sessions["n_hit"]).sum()), "of", len(sessions), "sessions")
       behavior_min  first_stim  last_stim_end  im115_presentations
count          99.0        99.0           99.0                 99.0
mean           65.1       101.0         5547.5               1219.6
std             0.0       101.6          103.2                 97.7
min            65.0        25.5         5459.5               1030.0
25%            65.0        35.1         5479.8               1153.5
50%            65.0        47.4         5499.2               1219.0
75%            65.1       137.0         5587.3               1279.0
max            65.2       635.7         6077.1               1506.0

image sets: {'im047_r-1.0,im114_r-1.0,im115_r-0.7,im115_r-1.0,omitted': 26, 'im005_r-1.0,im047_r-1.0,im115_r-0.7,im115_r-1.0,omitted': 25, 'im047_r-1.0,im087_r-1.0,im115_r-0.7,im115_r-1.0,omitted': 24, 'im047_r-1.0,im104_r-1.0,im115_r-0.7,im115_r-1.0,omitted': 24}
sessions without eye tracking: [1187475832, 1202438738]
rewards per session (all / auto / earned):
       n_rewards  n_rewards_auto  n_rewards_earned  n_hit  n_trials
count       99.0            99.0              99.0   99.0      99.0
mean        62.7             5.6              57.1   83.4     668.0
std         32.4             0.7              32.1   35.0     144.6
min          6.0             3.0               0.0    0.0     439.0
25%         38.5             6.0              32.5   63.5     566.0
50%         63.0             6.0              57.0   88.0     638.0
75%         82.5             6.0              76.5  106.0     730.0
max        138.0             6.0             132.0  162.0    1182.0
earned rewards equal hit trials in 3 of 99 sessions

The task has four image stimuli per session (three natural images and im115_r at 0.7 contrast) in place of the 8 images the Methods describe; im115_r at full contrast is in every session. The active block lasts about an hour and is followed by the receptive-field mapping (Gabors), flashes and spontaneous activity in most sessions.

Clocks. The spikes, the stimulus tables, licks and rewards come from one file per session and are on one clock as the file states; a check that they share a clock is that visual responses are locked to the image onsets: the Figure 4G PSTHs below (VISp units, im115_r) rise within tens of ms after onset and not before it, in every recording day, and the receptive-field maps of the Gabor stimulus are significant for a large fraction of VISp units, which they would not be with a shifted clock. For eye tracking and running the next cell contrasts conditions: running speed around the change time of hit trials against miss trials (mice slow down to lick after a detected change), and pupil area around full-field white against black flashes (the pupil constricts after a white flash). On a shared clock each contrast is flat before time 0 and turns after it, at a latency that fits the physiology (a lick response follows the change by about 0.4 s; the mouse pupil light reflex starts some 0.2 to 0.3 s after a flash).

In [7]:
tt_run = np.arange(500) * 0.01 - 2; tt_pup = np.arange(200) * 0.01 - 0.5
rows = []
for k, v in SP.items():
    c = v["clock"]
    h, m = c[("running", "hit")], c[("running", "miss")]
    d = (h - np.nanmean(h[(tt_run >= -1) & (tt_run < 0)])) - (m - np.nanmean(m[(tt_run >= -1) & (tt_run < 0)]))
    w, b = c[("pupil", "white")], c[("pupil", "black")]
    dp = (w - np.nanmean(w[tt_pup < 0])) - (b - np.nanmean(b[tt_pup < 0]))
    row = {"session_id": k, "hits": c[("n", "hit")], "misses": c[("n", "miss")], "response_latency_s": c["response_latency"]}
    if np.isfinite(d).any():
        post = (tt_run >= 0) & (tt_run < 1.5); pre = (tt_run >= -1) & (tt_run < 0)
        thr = 3 * np.nanmax(np.abs(d[pre]))
        cross = np.where(post & (d < -thr))[0]
        row.update(run_pre_max=np.nanmax(np.abs(d[pre])), run_post_min=np.nanmin(d[post]),
                   run_min_time_s=tt_run[post][np.nanargmin(d[post])], run_onset_s=tt_run[cross[0]] if len(cross) else np.nan)
    if np.isfinite(dp).any():
        post = tt_pup >= 0; pre = tt_pup < 0
        thr = 3 * np.nanmax(np.abs(dp[pre]))
        cross = np.where(post & (dp < -thr))[0]
        row.update(pupil_pre_max=np.nanmax(np.abs(dp[pre])), pupil_post_min=np.nanmin(dp[post]),
                   pupil_min_time_s=tt_pup[post][np.nanargmin(dp[post])], pupil_onset_s=tt_pup[cross[0]] if len(cross) else np.nan)
    rows.append(row)
ck = pd.DataFrame(rows).set_index("session_id")
print("running (cm/s; hit minus miss, each minus its -1 to 0 s mean) and pupil (area, pixels^2; white minus black flashes):")
print(ck.describe().round(3).to_string())
print("sessions without a running contrast:", ck.index[ck["run_post_min"].isna()].tolist(),
      "; without a pupil contrast:", ck.index[ck["pupil_post_min"].isna()].tolist())
print("sessions where the contrast after 0 is not more than 3 times the largest before 0 -- running:",
      ck.index[ck["run_post_min"].notna() & ck["run_onset_s"].isna()].tolist(),
      "; pupil:", ck.index[ck["pupil_post_min"].notna() & ck["pupil_onset_s"].isna()].tolist())
not_shown = sorted(set(ck.index[ck["run_post_min"].isna() | ck["run_onset_s"].isna()])
                   | set(ck.index[ck["pupil_post_min"].isna() | ck["pupil_onset_s"].isna()]))
print("sessions where alignment is not shown for at least one stream (no contrast, no eye tracking or a weak contrast):",
      len(not_shown), "of", len(ck), not_shown)
print("sessions where the minimum falls in the first 0.1 s -- running:", ck.index[ck["run_min_time_s"] < 0.1].tolist(),
      "; pupil:", ck.index[ck["pupil_min_time_s"] < 0.1].tolist())
fig, ax = plt.subplots(1, 2, figsize=(9, 3))
R_ = np.array([v["clock"][("running", "hit")] - v["clock"][("running", "miss")] for v in SP.values()])
P_ = np.array([v["clock"][("pupil", "white")] - v["clock"][("pupil", "black")] for v in SP.values()])
ax[0].plot(tt_run, np.nanmean(R_, 0)); ax[0].axvline(0, color="k", lw=0.5); ax[0].set_xlabel("s from change"); ax[0].set_ylabel("hit minus miss running (cm/s)")
ax[1].plot(tt_pup, np.nanmean(P_, 0)); ax[1].axvline(0, color="k", lw=0.5); ax[1].set_xlabel("s from flash"); ax[1].set_ylabel("white minus black pupil area")
plt.show()
print(f"plotted ranges: running {np.nanmin(np.nanmean(R_, 0)):.2f} to {np.nanmax(np.nanmean(R_, 0)):.2f}, pupil {np.nanmin(np.nanmean(P_, 0)):.1f} to {np.nanmax(np.nanmean(P_, 0)):.1f}")
running (cm/s; hit minus miss, each minus its -1 to 0 s mean) and pupil (area, pixels^2; white minus black flashes):
          hits   misses  response_latency_s  pupil_pre_max  pupil_post_min  pupil_min_time_s  pupil_onset_s  run_pre_max  run_post_min  run_min_time_s  run_onset_s
count   99.000   99.000              98.000         97.000          97.000            97.000         94.000       97.000        97.000          97.000       92.000
mean    83.444  122.667               0.456         66.518        -827.689             0.602          0.366        2.751       -30.818           0.686        0.246
std     35.046   54.738               0.080        113.154         318.538             0.146          0.121        1.848        16.388           0.307        0.086
min      0.000    0.000               0.178         14.200       -1974.375             0.000          0.050        0.196       -59.045           0.370        0.130
25%     63.500   84.000               0.380         30.640        -963.825             0.570          0.320        1.550       -43.276           0.510        0.200
50%     88.000  113.000               0.479         38.703        -759.445             0.590          0.350        2.488       -35.702           0.570        0.225
75%    106.000  152.000               0.519         60.252        -624.976             0.610          0.388        3.644       -20.067           0.740        0.260
max    162.000  268.000               0.580       1062.558           7.917             1.490          1.330        9.176        -0.527           1.490        0.640
sessions without a running contrast: [1173189336, 1230732154] ; without a pupil contrast: [1187475832, 1202438738]
sessions where the contrast after 0 is not more than 3 times the largest before 0 -- running: [1182427414, 1182628226, 1178693650, 1191375883, 1226010097] ; pupil: [1173741216, 1231476077, 1244070169]
sessions where alignment is not shown for at least one stream (no contrast, no eye tracking or a weak contrast): 12 of 99 [1173189336, 1173741216, 1178693650, 1182427414, 1182628226, 1187475832, 1191375883, 1202438738, 1226010097, 1230732154, 1231476077, 1244070169]
sessions where the minimum falls in the first 0.1 s -- running: [] ; pupil: [1231476077]
No description has been provided for this image
plotted ranges: running -23.29 to 7.38, pupil -755.2 to 7.0

The running and pupil streams share the stimulus and trial clock, as far as these contrasts show. Running slows after hits against misses from a median of 0.225 s after the change (minimum at a median 0.57 s; the median response latency of hits is 0.479 s), and the pupil constricts after white against black flashes from a median of 0.35 s (minimum at a median 0.59 s); before time 0 the contrasts are small (median largest deviation 2.5 cm/s against a median minimum of -35.7 cm/s after; 38.7 against -759 pixels squared). Sessions 1173189336 and 1230732154 have no running contrast (no hit or no miss trials), 1187475832 and 1202438738 no eye tracking. In 5 sessions for running (1182427414, 1182628226, 1178693650, 1191375883, 1226010097) and 3 for the pupil (1173741216, 1231476077, 1244070169) the contrast after 0 does not exceed three times the largest before it, so alignment is not shown there; in 1231476077 the pupil minimum falls in the first 0.1 s. Taken together, alignment of at least one stream is not shown in 12 of the 99 sessions (listed above). This is consistent with a shared clock, not proof of it at the 10 ms scale.

link-code-tables: the paper's session list and unit table against the dandiset¶

The 87 sessions the paper's code lists as passing ephys QC are sessions of the dandiset, and the code's units table (unit ids, counts, metrics, receptive-field p values) agrees with the dandiset's units for those sessions.

This link claim is added by the author (protocol, Author, step 1.4): the session list and the per-unit tables are outside inputs from the paper's repository, matched to the dandiset by session and unit identifiers.

In [8]:
p_units = units[units["passing"]]
cu = code_units[code_units["session"].isin(PASS)]
print("code sessions in the list found as dandiset identifiers:", len(PASS & set(sessions["session_id"])), "of", len(PASS))
cs = code_sessions.set_index("ecephys_session_id")
mm = sessions.set_index("session_id").join(cs[["session_type", "mouse_id", "genotype"]], rsuffix="_code")
print("session type agrees:", int((mm["session_type"] == mm["session_type_code"]).sum()), "of", len(mm),
      "; mouse agrees:", int((mm["subject_id"].astype(int) == mm["mouse_id"]).sum()),
      "; genotype agrees:", int((mm["genotype"] == mm["genotype_code"]).sum()))
n_nwb = p_units.groupby("session_id").size()
n_code = cu.groupby("session").size()
cmp = pd.concat([n_nwb, n_code], axis=1, keys=["dandiset", "code"]).fillna(0).astype(int)
print("\nunits in the 87 sessions: dandiset", int(n_nwb.sum()), "; code", int(n_code.sum()))
print("sessions with the same unit count:", int((cmp["dandiset"] == cmp["code"]).sum()), "of", len(cmp))
print(cmp[cmp["dandiset"] != cmp["code"]].to_string())
ids_missing = cu[~cu["unit_id"].isin(units.index)]
print("code units not in the dandiset:", len(ids_missing), "; by structure:", ids_missing["structure_acronym"].value_counts().head(6).to_dict())
# which probes the code units without a dandiset unit sit on, and whether the session file has units on that probe
if "probe_id" in units.columns:
    d_probes = units.groupby("session_id")["probe_id"].apply(lambda x: set(x.dropna().astype(int)))
    pm = ids_missing.groupby(["session", "probe_id"]).size().rename("code_units").reset_index()
    pm["dandiset_units_on_probe"] = [int(((units["session_id"] == s_) & (units["probe_id"] == p_)).sum()) for s_, p_ in zip(pm["session"], pm["probe_id"])]
    pm["code_units_on_probe"] = [int(((cu["session"] == s_) & (cu["probe_id"] == p_)).sum()) for s_, p_ in zip(pm["session"], pm["probe_id"])]
    print("code units not in the dandiset, by session and the code's probe_id:")
    print(pm.to_string(index=False))
    print("dandiset probe_ids in those sessions:", {int(s_): sorted(d_probes.get(s_, set())) for s_ in pm["session"].unique()})
else:
    print("the session files' electrodes table has no probe_id column")
print("their peak channels among the dandiset's unit peak channels:", int(ids_missing["peak_channel_id"].isin(units["peak_channel_id"]).sum()), "of", len(ids_missing))
print("dandiset units (87 sessions) not in the code table:", int((~p_units.index.isin(cu["unit_id"])).sum()))
j = p_units.join(cu.set_index("unit_id")[["max_drift", "firing_rate", "quality", "presence_ratio", "snr"]], rsuffix="_code", how="inner")
rows = []
for sid, g in j.groupby("session_id"):
    rows.append({"session_id": sid, "units": len(g),
                 "max_drift_differs": float((np.abs(g["max_drift"] - g["max_drift_code"]) > 0.01).mean()),
                 "firing_rate_differs": float((np.abs(g["firing_rate"] - g["firing_rate_code"]) > 0.01).mean()),
                 "quality_differs": float((g["quality"] != g["quality_code"]).mean())})
agree = pd.DataFrame(rows)
bad = agree[(agree[["max_drift_differs", "firing_rate_differs", "quality_differs"]] > 0.01).any(axis=1)]
print("\nsessions where more than 1% of matched units differ in a metric:", len(bad), "of", len(agree))
print(bad.round(3).to_string(index=False))
print("in the other", len(agree) - len(bad), "sessions the largest fraction differing is",
      round(float(agree.drop(bad.index)[["max_drift_differs", "firing_rate_differs", "quality_differs"]].max().max()), 4))
# implied recording duration n_spikes / firing_rate per unit: constant within a session when the rate
# describes the same spikes; with the code's firing_rate it scatters if the code's units are others
jj = j.assign(dur_dandiset=j["n_spikes"] / j["firing_rate"], dur_code=j["n_spikes"] / j["firing_rate_code"])
dd = jj.groupby("session_id").agg(units=("dur_dandiset", "size"), dur_dandiset_median=("dur_dandiset", "median"),
                                  dandiset_cv=("dur_dandiset", lambda x: x.std() / x.mean()),
                                  code_cv=("dur_code", lambda x: x.std() / x.mean()),
                                  code_ratio_iqr=("dur_code", lambda x: float(np.subtract(*np.percentile(x / x.median(), [75, 25])))))
dd["differs"] = dd.index.isin(bad["session_id"])
print("\nspike counts against firing rates (implied duration n_spikes / firing_rate, s):")
print(dd.groupby("differs")[["dandiset_cv", "code_cv", "code_ratio_iqr"]].median().round(4).to_string())
print(dd[dd["differs"]].round(4).to_string())
print("largest across sessions:", dd.groupby("differs")[["dandiset_cv", "code_cv"]].max().round(4).to_dict("index"))
code sessions in the list found as dandiset identifiers: 87 of 87
session type agrees: 99 of 99 ; mouse agrees: 99 ; genotype agrees: 99

units in the 87 sessions: dandiset 177959 ; code 178287
sessions with the same unit count: 82 of 87
            dandiset  code
1176989662      1927  1988
1178693650      1278  1392
1179911454      1296  1353
1180266229      1867  1925
1181096406      2257  2295
code units not in the dandiset: 328 ; by structure: {'CP': 133, 'Track not annotated': 119, 'ILA': 16, 'VL': 12, 'MOp': 10, 'PL': 10}
code units not in the dandiset, by session and the code's probe_id:
   session  probe_id  code_units  dandiset_units_on_probe  code_units_on_probe
1176989662        88          61                        0                  491
1178693650        46         114                        0                  131
1179911454       121          57                        0                  413
1180266229       133          58                        0                  490
1181096406       139          38                        0                  649
dandiset probe_ids in those sessions: {1176989662: [83, 84, 85, 86, 87], 1178693650: [41, 42, 43, 44], 1179911454: [116, 117, 118, 119, 120], 1180266229: [128, 129, 130, 131, 132], 1181096406: [134, 135, 136, 137, 138]}
their peak channels among the dandiset's unit peak channels: 0 of 328
dandiset units (87 sessions) not in the code table: 0

sessions where more than 1% of matched units differ in a metric: 6 of 87
 session_id  units  max_drift_differs  firing_rate_differs  quality_differs
 1176989662   1927              0.993                0.994            0.280
 1178460518   1969              0.993                0.993            0.349
 1178693650   1278              0.996                0.995            0.350
 1179911454   1296              0.990                0.985            0.382
 1180266229   1867              0.998                0.994            0.284
 1181096406   2257              0.553                0.553            0.144
in the other 81 sessions the largest fraction differing is 0.0

spike counts against firing rates (implied duration n_spikes / firing_rate, s):
         dandiset_cv  code_cv  code_ratio_iqr
differs                                      
False            0.0   0.0000          0.0000
True             0.0   9.1413          4.9251
            units  dur_dandiset_median  dandiset_cv  code_cv  code_ratio_iqr  differs
session_id                                                                           
1176989662   1927            5247.0036       0.0759   9.3586          4.8571     True
1178460518   1969            6165.1432       0.0000   7.4913          4.9931     True
1178693650   1278            6297.1002       0.0000   5.3501          4.6123     True
1179911454   1296            6196.4103       0.0000  11.8224          6.5466     True
1180266229   1867            6318.4278       0.0000   8.9241          5.7766     True
1181096406   2257            6128.5493       0.0000  10.2842          0.6353     True
largest across sessions: {False: {'dandiset_cv': 0.0003, 'code_cv': 0.0003}, True: {'dandiset_cv': 0.0759, 'code_cv': 11.8224}}
In [9]:
# receptive-field p values computed here (chi-square bootstrap) against the code's p_value_rf
rf = pd.concat([v["rf"].assign(session_id=k) for k, v in SP.items()])
rfj = rf.join(code_units.set_index("unit_id")[["p_value_rf"]], how="inner").dropna()
ok_s = ~rfj["session_id"].isin(bad["session_id"])
for lab, g in (("sessions whose metrics agree", rfj[ok_s]), ("the sessions that differ", rfj[~ok_s])):
    print(f"{lab}: {len(g)} units; Pearson r of p values {np.corrcoef(g['p_rf'], g['p_value_rf'])[0, 1]:.4f}; "
          f"same significance at 0.01 for {np.mean((g['p_rf'] < 0.01) == (g['p_value_rf'] < 0.01)):.3f}; "
          f"significant here {np.mean(g['p_rf'] < 0.01):.3f}, in the code {np.mean(g['p_value_rf'] < 0.01):.3f}")
# per session, in the sessions whose metrics agree: fraction of units whose significance at 0.01 differs
ps_ = rfj[ok_s].assign(diff=(rfj["p_rf"] < 0.01) != (rfj["p_value_rf"] < 0.01)).groupby("session_id")["diff"].agg(["size", "mean"])
print(f"per session (sessions whose metrics agree, {len(ps_)} sessions with RF units): fraction differing median {ps_['mean'].median():.3f}, "
      f"max {ps_['mean'].max():.3f}; sessions with more than 1% differing: {int((ps_['mean'] > 0.01).sum())}")
print("those sessions (units, fraction differing):", ps_[ps_["mean"] > 0.01].round(3).to_dict("index"))
near = rfj[ok_s]
dif = near[(near["p_rf"] < 0.01) != (near["p_value_rf"] < 0.01)]
print(f"units whose significance differs: {len(dif)}; code p of these from {dif['p_value_rf'].min():.3f} to {dif['p_value_rf'].max():.3f}, "
      f"p here from {dif['p_rf'].min():.3f} to {dif['p_rf'].max():.3f}; with both p values within 0.005 of 0.01: "
      f"{int(((dif['p_rf'] - 0.01).abs() <= 0.005).sum() if len(dif) else 0)} of those with p here near, "
      f"{int((((dif['p_rf'] - 0.01).abs() <= 0.005) & ((dif['p_value_rf'] - 0.01).abs() <= 0.005)).sum())} both")
sessions whose metrics agree: 7095 units; Pearson r of p values 0.9990; same significance at 0.01 for 0.988; significant here 0.551, in the code 0.551
the sessions that differ: 251 units; Pearson r of p values 0.0343; same significance at 0.01 for 0.450; significant here 0.701, in the code 0.271
per session (sessions whose metrics agree, 79 sessions with RF units): fraction differing median 0.008, max 0.038; sessions with more than 1% differing: 33
those sessions (units, fraction differing): {1174553025: {'size': 49, 'mean': 0.02}, 1175253205: {'size': 57, 'mean': 0.035}, 1181731440: {'size': 60, 'mean': 0.033}, 1182871514: {'size': 84, 'mean': 0.012}, 1187668018: {'size': 137, 'mean': 0.015}, 1187940755: {'size': 126, 'mean': 0.016}, 1190892228: {'size': 28, 'mean': 0.036}, 1194850630: {'size': 84, 'mean': 0.012}, 1197967151: {'size': 173, 'mean': 0.017}, 1198130743: {'size': 95, 'mean': 0.021}, 1198995158: {'size': 37, 'mean': 0.027}, 1199249150: {'size': 81, 'mean': 0.012}, 1200471383: {'size': 72, 'mean': 0.014}, 1202438738: {'size': 79, 'mean': 0.038}, 1202644967: {'size': 144, 'mean': 0.028}, 1203574877: {'size': 42, 'mean': 0.024}, 1208053773: {'size': 29, 'mean': 0.034}, 1210343162: {'size': 132, 'mean': 0.03}, 1210863396: {'size': 117, 'mean': 0.026}, 1218577463: {'size': 136, 'mean': 0.015}, 1218795542: {'size': 176, 'mean': 0.011}, 1219127153: {'size': 39, 'mean': 0.026}, 1219401111: {'size': 135, 'mean': 0.015}, 1224635971: {'size': 99, 'mean': 0.03}, 1224928532: {'size': 131, 'mean': 0.031}, 1226251663: {'size': 198, 'mean': 0.025}, 1227855488: {'size': 225, 'mean': 0.013}, 1229429400: {'size': 111, 'mean': 0.018}, 1232430325: {'size': 190, 'mean': 0.011}, 1232954220: {'size': 176, 'mean': 0.011}, 1234203203: {'size': 141, 'mean': 0.014}, 1243523806: {'size': 104, 'mean': 0.029}, 1243799530: {'size': 104, 'mean': 0.019}}
units whose significance differs: 83; code p of these from 0.003 to 0.019, p here from 0.003 to 0.020; with both p values within 0.005 of 0.01: 78 of those with p here near, 66 both

Result: partially reproduced. All 87 listed sessions are dandiset sessions, and session type, mouse and genotype agree for all 99. In 81 of the 87 sessions the code's unit table and the dandiset agree unit by unit (no metric differs). In 6 sessions (1176989662, 1178460518, 1178693650, 1179911454, 1180266229, 1181096406) the same unit ids carry other values for 55 to 99.8% of units. There the files' spike counts fit the files' firing rates (the implied duration n_spikes / firing_rate has a coefficient of variation of 0 in five sessions and 0.076 in 1176989662) but not the code's (5.4 to 11.8, against at most 0.0003 in the other 81 sessions), and the receptive-field p values computed here do not correlate with the code's (r = 0.03 over 251 units, against r = 0.9990 over 7095 units elsewhere): the code's table describes other units under those ids than the files hold. In 5 of those sessions the code's table also has 328 units that are not in the dandiset; each session's are on one probe_id of the code's (38 to 114 units) that is not among the session file's probe_ids and has no unit in the file, and none of their peak channels is a peak channel of a dandiset unit. Where the units agree, RF significance at 0.01 agrees for 98.8% of units pooled; per session the fraction differing has a median of 0.008 and a maximum of 0.038, above 1% in 33 of 79 sessions (listed above). All 83 differing units have p values from 0.003 to 0.020 in both computations, at the threshold, as two bootstraps of 1000 resamplings with different random draws give.

scale-mice-sessions-insertions¶

Stable recordings were made from 25 mice in 87 experiments with 467 probe insertions.

An insertion is a probe of a session with at least one unit, as the code counts distinct probe ids in its units table (DynamicGating_ephys_metrics.ipynb, the cell printing number_of_insertions).

In [10]:
ins = units.groupby(["session_id", "probe"]).size().rename("n").reset_index()
ins = ins.merge(sessions[["session_id", "subject_id", "passing", "session_type"]], on="session_id")
code_probes = shield.read_code_csv("metadata_tables/probes_table.csv")
cpk = code_units[["session", "probe_id"]].drop_duplicates().merge(code_probes[["id", "name"]], left_on="probe_id", right_on="id")
code_keys = set(zip(cpk["session"], cpk["name"]))
ins["in_code"] = [k in code_keys for k in zip(ins["session_id"], ins["probe"])]
for lab, g in (("87 QC-passing sessions", ins[ins["passing"]]), ("all 99 sessions", ins)):
    print(f"{lab}: mice {g['subject_id'].nunique()}, experiments {g['session_id'].nunique()}, insertions {len(g)}; "
          f"of these also in the code's units table {int(g['in_code'].sum())}")
pa_ = ins[ins["passing"] & ~ins["in_code"]]
print(f"insertions of the 87 sessions not in the code's table: {len(pa_)}, with {int(pa_['n'].sum())} units, labelled",
      units.set_index(["session_id", "probe"]).loc[list(zip(pa_["session_id"], pa_["probe"])), "structure_acronym"].value_counts().to_dict())
print("probes in the code's probes table for the 87 sessions:",
      int(shield.read_code_csv("metadata_tables/probes_table.csv").query("session in @PASS").shape[0]))
87 QC-passing sessions: mice 25, experiments 87, insertions 507; of these also in the code's units table 467
all 99 sessions: mice 27, experiments 99, insertions 578; of these also in the code's units table 533
insertions of the 87 sessions not in the code's table: 40, with 2004 units, labelled {'Track not annotated': 1873, 'CP': 92, 'VL': 22, 'V3': 14, 'No Area': 2, 'MOp': 1}
probes in the code's probes table for the 87 sessions: 467

Result: reproduced. Over the 87 sessions: 25 mice, 87 experiments and 507 insertions with units, within the tolerance of 467 (46.7). Of the 507, 467 are in the code's units table, the paper's number exactly; the other 40 are probes whose 2004 units are almost all on unannotated tracks ('Track not annotated' 1873), which the paper's tables leave out.

penetration-success¶

On average 5.46 of the 6 intended probes per session were successfully inserted.

In [11]:
for lab, g in (("(a) 87 QC-passing sessions", ins[ins["passing"]]), ("(b) all 99 sessions", ins),
               ("(a), insertions in the code's table only", ins[ins["passing"] & ins["in_code"]])):
    per = g.groupby("session_id").size()
    print(f"{lab}: {len(g)} insertions / {per.size} sessions = {len(g) / per.size:.3f} probes per session; "
          f"sessions with 6, 5, 4 or fewer probes: {(per == 6).sum()}, {(per == 5).sum()}, {(per <= 4).sum()}")
for lab, q in (("87 sessions", code_probes[code_probes["session"].isin(PASS)]), ("99 sessions", code_probes)):
    print(f"code's probes table, {lab}: {len(q)} probes in {q['session'].nunique()} sessions, inserted {q['inserted'].value_counts().to_dict()}, "
          f"{len(q) / q['session'].nunique():.3f} per session")
(a) 87 QC-passing sessions: 507 insertions / 87 sessions = 5.828 probes per session; sessions with 6, 5, 4 or fewer probes: 77, 8, 2
(b) all 99 sessions: 578 insertions / 99 sessions = 5.838 probes per session; sessions with 6, 5, 4 or fewer probes: 88, 9, 2
(a), insertions in the code's table only: 467 insertions / 87 sessions = 5.368 probes per session; sessions with 6, 5, 4 or fewer probes: 54, 24, 9
code's probes table, 87 sessions: 467 probes in 87 sessions, inserted {True: 467}, 5.368 per session
code's probes table, 99 sessions: 533 probes in 99 sessions, inserted {True: 533}, 5.384 per session

Result: reproduced. 5.83 probes per session over the 87 sessions and 5.84 over all 99, within 5.46 +- 0.546; counting only the insertions in the code's table, 5.37. No reading gives 5.46 exactly. The code's probes table lists 467 probes for the 87 sessions (5.368 per session) and 533 for the 99 (5.384), every one marked inserted, so it records no failed attempt and gives no direct penetration count either.

fig4b-units-areas-per-session¶

A typical 6-probe session records 1,821 non-noise units (SD 341.9) from 28.1 areas (SD 5.1).

In [12]:
ia = units[units["passing"] & units["in_area"]]
nprobe = ia.groupby("session_id")["probe"].nunique()
six = nprobe[nprobe == 6].index
d6 = ia[ia["session_id"].isin(six)]
areas = d6.groupby("session_id")["structure_acronym"].nunique()
nunits = d6[d6["good"]].groupby("session_id").size()
print(f"6-probe sessions: {len(six)} of {nprobe.size}")
print(f"areas per session: mean {areas.mean():.2f}, SD {areas.std():.2f} (range {areas.min()} to {areas.max()})")
print(f"non-noise units per session: mean {nunits.mean():.1f}, SD {nunits.std():.1f} (range {nunits.min()} to {nunits.max()})")
by_mouse = d6[d6["good"]].groupby(["subject_id", "session_id"]).size().groupby("subject_id").mean()
print("mean non-noise units per 6-probe session, by mouse:", by_mouse.round(0).astype(int).to_dict())
d6c = d6[~d6["session_id"].isin(bad["session_id"])]
print(f"leaving out the 6 sessions whose units differ from the code's: {d6c['session_id'].nunique()} sessions, units mean "
      f"{d6c[d6c['good']].groupby('session_id').size().mean():.1f}, areas mean {d6c.groupby('session_id')['structure_acronym'].nunique().mean():.2f}")
6-probe sessions: 44 of 77
areas per session: mean 28.11, SD 5.12 (range 19 to 41)
non-noise units per session: mean 1821.0, SD 341.9 (range 1100 to 2553)
mean non-noise units per 6-probe session, by mouse: {'607660': 2007, '608672': 1844, '612090': 2553, '614547': 2036, '614608': 2298, '615047': 1456, '615048': 2072, '615563': 1856, '615564': 1640, '623322': 1598, '623784': 1463, '623786': 1693, '626279': 1607, '632295': 1935, '636740': 1723, '637483': 1916, '640887': 1734, '640890': 2064}
leaving out the 6 sessions whose units differ from the code's: 44 sessions, units mean 1821.0, areas mean 28.11

Result: reproduced. Over the 44 six-probe sessions, 1821.0 non-noise units (SD 341.9) from 28.11 areas (SD 5.12), the paper's values.

figs4ab-insertions-per-mouse¶

Across recording days each mouse received about 20 probe insertions (SD 4.1) into about 10 implant holes (10.4, SD 2.3).

The implant hole of an insertion is not in the dandiset, so only the insertions part is tested.

In [13]:
for lab, g in (("(a) 87 QC-passing sessions", ins[ins["passing"]]), ("(b) all sessions", ins),
               ("(a), insertions in the code's table only", ins[ins["passing"] & ins["in_code"]])):
    per = g.groupby("subject_id").size()
    print(f"{lab}: {per.size} mice, insertions per mouse mean {per.mean():.2f}, SD {per.std():.2f}, "
          f"range {per.min()} to {per.max()}")
print("sessions per mouse (all sessions):", sessions.groupby("subject_id").size().value_counts().sort_index().to_dict())
(a) 87 QC-passing sessions: 25 mice, insertions per mouse mean 20.28, SD 3.34, range 12 to 24
(b) all sessions: 27 mice, insertions per mouse mean 21.41, SD 3.33, range 12 to 24
(a), insertions in the code's table only: 25 mice, insertions per mouse mean 18.68, SD 4.18, range 8 to 24
sessions per mouse (all sessions): {2: 1, 3: 7, 4: 19}

Result: partially reproduced. Insertions per mouse are 20.28 (SD 3.34) over the 87 sessions and 21.41 over all sessions, within 20 +- 4.1 (18.68 with only the code's insertions). The implant holes per mouse could not be tested: no file records the hole of an insertion.

fig4d-unit-drift¶

Unit drift with SHIELD is only slightly higher than with the 5-mm window (median 25.1 against 22.82 micrometres).

The 5-mm window units are the VBN units table of the paper's repository (from DANDI:000713).

In [14]:
dg_good = units[units["passing"] & units["good"]]
vbn = vbn_units.merge(vbn_sessions[["ecephys_session_id", "session_number"]], on="ecephys_session_id")
vbn_good = vbn[vbn["quality"] == "good"]
print("non-noise units: SHIELD", len(dg_good), "(paper 143,828); VBN", len(vbn_good), "(paper 262,177)")
def boot_sem_median(x, rng, n=1000):
    return np.std([np.median(rng.choice(x, len(x))) for _ in range(n)])
rng = np.random.default_rng(4)
x, y = dg_good["max_drift"].dropna().values, vbn_good["max_drift"].dropna().values
print(f"median max_drift: SHIELD {np.median(x):.2f} (bootstrap SE {boot_sem_median(x, rng, 200):.3f}), "
      f"VBN {np.median(y):.2f} (SE {boot_sem_median(y, rng, 200):.3f}); difference {np.median(x) - np.median(y):.2f} um")
print(f"two-sided Mann-Whitney p = {stats.mannwhitneyu(x, y).pvalue:.2e}")
xc = dg_good.loc[~dg_good["session_id"].isin(bad["session_id"]), "max_drift"]
in_code_keys = set(zip(ins.loc[ins["in_code"], "session_id"], ins.loc[ins["in_code"], "probe"]))
dg_in = np.array([k in in_code_keys for k in zip(dg_good["session_id"], dg_good["probe"])])
print("non-noise units in the code's units table for the 87 sessions:", int((cu["quality"] == "good").sum()),
      "; SHIELD non-noise units on insertions in the code's table:", int(dg_in.sum()), ", on the 40 others:", int((~dg_in).sum()))
print("non-noise units outside the 6 sessions that differ: dandiset", int((~dg_good["session_id"].isin(bad["session_id"])).sum()),
      ", code", int(((cu["quality"] == "good") & ~cu["session"].isin(bad["session_id"])).sum()))
print(f"SHIELD median without the 6 sessions that differ from the code: {xc.median():.2f}")
print("SHIELD median by mouse:", dg_good.groupby("subject_id")["max_drift"].median().round(1).to_dict())
non-noise units: SHIELD 144030 (paper 143,828); VBN 262177 (paper 262,177)
median max_drift: SHIELD 25.05 (bootstrap SE 0.063), VBN 22.82 (SE 0.043); difference 2.23 um
two-sided Mann-Whitney p = 3.92e-292
non-noise units in the code's units table for the 87 sessions: 144068 ; SHIELD non-noise units on insertions in the code's table: 143828 , on the 40 others: 202
non-noise units outside the 6 sessions that differ: dandiset 135334 , code 135334
SHIELD median without the 6 sessions that differ from the code: 25.22
SHIELD median by mouse: {'607660': 27.4, '608671': 23.7, '608672': 29.2, '611160': 29.7, '611166': 26.4, '612090': 27.4, '614547': 21.8, '614608': 20.2, '615047': 25.8, '615048': 29.2, '615563': 26.6, '615564': 24.5, '623319': 30.6, '623322': 24.4, '623784': 27.7, '623786': 32.3, '626279': 25.7, '632295': 21.8, '636740': 21.9, '637483': 20.5, '637488': 24.4, '638387': 27.2, '640887': 20.8, '640890': 19.6, '642504': 20.5}

Result: reproduced. Median max_drift is 25.05 um for SHIELD (144,030 non-noise units) and 22.82 for the VBN units (262,177), within 25.1 +- 0.1 and 22.82 +- 0.05. Of the 144,030, 143,828 (the paper's count exactly) are on the 467 insertions in the code's table and 202 on the 40 insertions it leaves out. The code's own table has 144,068 non-noise units for the 87 sessions, which differ from the files' in the 6 sessions where the table describes other units (other quality labels, and 328 units not in the files); outside those 6 sessions both give 135,334.

fig4d-qc-metrics-comparable¶

The distributions of unit quality metrics (presence ratio, amplitude cutoff, ISI violations, SNR, isolation distance, drift, amplitude) are comparable for SHIELD and the 5-mm window.

In [15]:
metrics = ["presence_ratio", "amplitude_cutoff", "isi_violations", "snr", "isolation_distance", "max_drift", "amplitude"]
rows = []
for m in metrics:
    a, b = dg_good[m].dropna().values, vbn_good[m].dropna().values
    rows.append({"metric": m, "SHIELD_median": np.median(a), "VBN_median": np.median(b),
                 "ratio": np.median(a) / np.median(b), "within_10pct": abs(np.median(a) / np.median(b) - 1) <= 0.10,
                 "mannwhitney_p": stats.mannwhitneyu(a, b).pvalue,
                 "rank_biserial": 1 - 2 * stats.mannwhitneyu(a, b).statistic / (len(a) * len(b))})
qm = pd.DataFrame(rows)
print(qm.to_string(index=False, float_format=lambda v: f"{v:.4g}"))
print("(a) metrics not significant at 0.05:", int((qm["mannwhitney_p"] >= 0.05).sum()), "of 7;",
      " (b) metrics with medians within 10%:", int(qm["within_10pct"].sum()), "of 7")
fig, ax = plt.subplots(1, 7, figsize=(16, 3))
for i, m in enumerate(metrics):
    ax[i].boxplot([dg_good[m].dropna(), vbn_good[m].dropna()], showfliers=False)
    ax[i].set_xticklabels(["SHIELD", "VBN"]); ax[i].set_title(m, fontsize=8)
plt.tight_layout(); plt.show()
print("Boxes without outliers; whiskers at 1.5 IQR.")
            metric  SHIELD_median  VBN_median  ratio  within_10pct  mannwhitney_p  rank_biserial
    presence_ratio           0.99        0.99      1          True      2.09e-157        0.04061
  amplitude_cutoff        0.04408     0.04947  0.891         False      3.233e-16        0.01543
    isi_violations        0.06981     0.08875 0.7866         False     4.134e-232        0.06143
               snr          2.471       2.497 0.9898          True      3.861e-06       0.008746
isolation_distance          56.11       58.22 0.9638          True     2.991e-111         0.0426
         max_drift          25.05       22.82  1.098          True     3.918e-292       -0.06948
         amplitude          125.2       130.2 0.9616          True     3.739e-161        0.05122
(a) metrics not significant at 0.05: 0 of 7;  (b) metrics with medians within 10%: 5 of 7
No description has been provided for this image
Boxes without outliers; whiskers at 1.5 IQR.

Result: not reproduced. Under reading (a) all seven metrics differ (Mann-Whitney p from 3.9e-292 to 3.9e-6), as in the code's own output; under reading (b) 5 of 7 medians are within 10%, with isi_violations (ratio 0.79) and amplitude_cutoff (0.89) lower for SHIELD. The differences are small (rank-biserial |r| at most 0.07) and the two outside 10% favour SHIELD.

qc-pass-fraction-similar¶

The fraction of units passing the standard quality filter is very similar for SHIELD (41.1%) and the 5-mm window (40.7%).

In [16]:
vq = (vbn_good["isi_violations"] < 0.5) & (vbn_good["amplitude_cutoff"] < 0.1) & (vbn_good["presence_ratio"] > 0.95)
print(f"SHIELD: {dg_good['qc'].sum()} of {len(dg_good)} non-noise units pass = {100 * dg_good['qc'].mean():.2f}%")
print(f"VBN: {vq.sum()} of {len(vbn_good)} = {100 * vq.mean():.2f}%")
print("SHIELD per mouse (%):", (100 * dg_good.groupby("subject_id")["qc"].mean()).round(1).to_dict())
print(f"SHIELD without the 6 differing sessions: {100 * dg_good.loc[~dg_good['session_id'].isin(bad['session_id']), 'qc'].mean():.2f}%")
SHIELD: 59188 of 144030 non-noise units pass = 41.09%
VBN: 106680 of 262177 = 40.69%
SHIELD per mouse (%): {'607660': 38.3, '608671': 39.8, '608672': 39.1, '611160': 42.2, '611166': 41.8, '612090': 39.8, '614547': 44.9, '614608': 43.8, '615047': 40.9, '615048': 40.7, '615563': 41.8, '615564': 38.6, '623319': 37.9, '623322': 40.0, '623784': 41.3, '623786': 37.6, '626279': 42.6, '632295': 42.1, '636740': 42.4, '637483': 43.7, '637488': 39.4, '638387': 39.7, '640887': 41.7, '640890': 46.5, '642504': 43.7}
SHIELD without the 6 differing sessions: 40.93%

Result: reproduced. 41.09% of SHIELD non-noise units and 40.69% of VBN units pass the filter, against 41.1% and 40.7%.

fig4e-unit-yield-stable¶

About 300 units are recorded per probe on each day, and unit yield does not differ across the four recording days with SHIELD (p = 0.48).

In [17]:
yc = dg_good.groupby(["subject_id", "probe", "session_type"]).size().rename("units").reset_index()
days = ["EPHYS_1", "EPHYS_2", "EPHYS_3", "EPHYS_4"]
groups = [yc.loc[yc["session_type"] == d, "units"].values for d in days]
for d, g in zip(days, groups):
    print(f"{d}: {len(g)} insertions, median {np.median(g):.1f} non-noise units (IQR {np.percentile(g, 25):.0f} to {np.percentile(g, 75):.0f})")
kw = stats.kruskal(*groups)
print(f"Kruskal-Wallis across the four days: H = {kw.statistic:.3f}, p = {kw.pvalue:.3f}")
dg_code = dg_good[[k in code_keys for k in zip(dg_good["session_id"], dg_good["probe"])]]
yca = dg_code.groupby(["subject_id", "probe", "session_type"]).size().rename("units").reset_index()
ga = [yca.loc[yca["session_type"] == d, "units"].values for d in days]
print("insertions in the code's table only: insertions", [len(g) for g in ga], ", medians", [float(np.median(g)) for g in ga],
      f", Kruskal-Wallis p = {stats.kruskal(*ga).pvalue:.3f}")
print("(paper: 129, 126, 121, 91 insertions; p = 0.48; the code printed medians 314, 305.5, 295, 303)")
ycc = dg_good[~dg_good["session_id"].isin(bad["session_id"])].groupby(["subject_id", "probe", "session_type"]).size().rename("units").reset_index()
kw2 = stats.kruskal(*[ycc.loc[ycc["session_type"] == d, "units"].values for d in days])
print(f"without the 6 differing sessions: p = {kw2.pvalue:.3f}, medians",
      [float(np.median(ycc.loc[ycc['session_type'] == d, 'units'])) for d in days])
EPHYS_1: 136 insertions, median 308.0 non-noise units (IQR 224 to 378)
EPHYS_2: 135 insertions, median 302.0 non-noise units (IQR 213 to 374)
EPHYS_3: 128 insertions, median 287.5 non-noise units (IQR 222 to 358)
EPHYS_4: 101 insertions, median 277.0 non-noise units (IQR 193 to 365)
Kruskal-Wallis across the four days: H = 3.409, p = 0.333
insertions in the code's table only: insertions [129, 126, 121, 91] , medians [314.0, 305.5, 295.0, 303.0] , Kruskal-Wallis p = 0.477
(paper: 129, 126, 121, 91 insertions; p = 0.48; the code printed medians 314, 305.5, 295, 303)
without the 6 differing sessions: p = 0.282, medians [304.0, 304.5, 287.5, 275.0]

Result: reproduced. Kruskal-Wallis across days p = 0.333 over all 500 insertions with non-noise units (medians 308, 302, 287.5, 277), and p = 0.477 over the 467 insertions of the code's table (129, 126, 121 and 91 insertions, medians 314, 305.5, 295, 303, the paper's counts); every median is within 300 +- 30.

fig4e-vbn-yield-drops¶

With the 5-mm window, unit yield decreased on the second recording day (p = 6e-22).

In [18]:
vy = vbn_good.groupby(["session_number", "ecephys_session_id", "ecephys_probe_id"]).size().rename("units").reset_index()
v1, v2 = [vy.loc[vy["session_number"] == k, "units"].values for k in (1, 2)]
print(f"VBN day 1: {len(v1)} insertions, median {np.median(v1):.1f}; day 2: {len(v2)} insertions, median {np.median(v2):.1f}")
ka = stats.kruskal(v1, v2)
kb = stats.kruskal(v1, v2, *groups)
print(f"(a) Kruskal-Wallis VBN day 1 against day 2: H = {ka.statistic:.2f}, p = {ka.pvalue:.2e}")
print(f"(b) Kruskal-Wallis over the code's six groups (two VBN days, four SHIELD days): H = {kb.statistic:.2f}, p = {kb.pvalue:.2e}")
VBN day 1: 459 insertions, median 321.0; day 2: 446 insertions, median 261.0
(a) Kruskal-Wallis VBN day 1 against day 2: H = 105.07, p = 1.18e-24
(b) Kruskal-Wallis over the code's six groups (two VBN days, four SHIELD days): H = 98.30, p = 1.20e-19

Result: reproduced. VBN yield falls from a median of 321 to 261 units per insertion; Kruskal-Wallis p = 1.2e-24 for day 1 against day 2 (a) and p = 1.2e-19 over the code's six groups (b).

fig4e-firing-rate-stable¶

The average firing rate of recorded units is similar across the four recording days (p = 0.21).

In [19]:
fr = dg_good.groupby(["session_type", "subject_id", "session_id"])["firing_rate"].median().rename("fr").reset_index()
fg = [fr.loc[fr["session_type"] == d, "fr"].values for d in days]
for d, g in zip(days, fg):
    print(f"{d}: {len(g)} sessions, median of session medians {np.median(g):.3f} Hz (range {g.min():.2f} to {g.max():.2f})")
k = stats.kruskal(*fg)
print(f"Kruskal-Wallis: H = {k.statistic:.3f}, p = {k.pvalue:.3f} (text 0.21; code and figure 0.26)")
EPHYS_1: 23 sessions, median of session medians 3.154 Hz (range 2.11 to 4.39)
EPHYS_2: 24 sessions, median of session medians 3.176 Hz (range 2.45 to 4.36)
EPHYS_3: 22 sessions, median of session medians 3.468 Hz (range 2.40 to 6.32)
EPHYS_4: 18 sessions, median of session medians 3.107 Hz (range 2.34 to 4.41)
Kruskal-Wallis: H = 4.385, p = 0.223 (text 0.21; code and figure 0.26)

Result: reproduced. Kruskal-Wallis p = 0.223 across days over 23, 24, 22 and 18 sessions (text 0.21, code 0.26).

fig4ef-rf-fraction-stable¶

A consistent fraction of VISp units has significant spatial receptive fields on each of the four recording days (p = 0.38).

Receptive fields are computed here from the Gabor presentations (chi-square statistic on the 9 x 9 map with the bootstrap p value of the AllenSDK, which the code's p_value_rf uses; Methods, Receptive Field Mapping). VISp is the ISI label, units pass QC.

In [20]:
v1u = units[units["passing"] & units["qc"] & (units["isi_label"] == "VISp")].join(rf[["p_rf"]], how="left")
print("QC VISp units:", len(v1u), "; with an RF p value:", int(v1u["p_rf"].notna().sum()))
v1u = v1u.join(code_units.set_index("unit_id")[["p_value_rf"]], how="left")
res = {}
for thr in (0.01, 0.05):
    for src in ("p_rf", "p_value_rf"):
        frac = v1u.assign(sig=v1u[src] < thr).groupby(["session_type", "subject_id", "session_id"])["sig"].mean().reset_index()
        g = [frac.loc[frac["session_type"] == d, "sig"].values for d in days]
        k = stats.kruskal(*g)
        res[(thr, src)] = k.pvalue
        print(f"threshold {thr}, p values {'computed here' if src == 'p_rf' else 'of the code table'}: sessions per day "
              f"{[len(x) for x in g]}, median fraction {[round(float(np.median(x)), 3) for x in g]}, Kruskal-Wallis p = {k.pvalue:.3f}")
QC VISp units: 2469 ; with an RF p value: 2469
threshold 0.01, p values computed here: sessions per day [12, 11, 12, 11], median fraction [0.9, 0.877, 0.913, 0.9], Kruskal-Wallis p = 0.671
threshold 0.01, p values of the code table: sessions per day [12, 11, 12, 11], median fraction [0.9, 0.877, 0.904, 0.865], Kruskal-Wallis p = 0.864
threshold 0.05, p values computed here: sessions per day [12, 11, 12, 11], median fraction [0.907, 0.896, 0.931, 0.964], Kruskal-Wallis p = 0.368
threshold 0.05, p values of the code table: sessions per day [12, 11, 12, 11], median fraction [0.915, 0.896, 0.923, 0.944], Kruskal-Wallis p = 0.813

Result: reproduced. With the Methods' threshold (p < 0.01) the fraction of QC VISp units with a significant RF does not differ across days (Kruskal-Wallis p = 0.671; median fractions 0.88 to 0.91 over 12, 11, 12 and 11 sessions); at 0.05, p = 0.368; with the code's own p values, 0.864 and 0.813.

fig4g-visual-responses-similar¶

The population response of VISp units to an image is highly similar across the four recording days.

In [21]:
ps = pd.concat([v["im115_psth"] for v in SP.values()])
sel = units[units["passing"] & units["qc"] & (units["isi_label"] == "VISp")].join(rf[["p_rf"]], how="inner")
sel = sel[sel["p_rf"] < 0.01]
P = ps.loc[sel.index].values
t_ms = np.arange(2500) - 1000
base = P[:, 990:1000].mean(1, keepdims=True)
resp = (P[:, 1000:1250] - base).mean(1)
peak = np.argmax(P[:, 1000:1250], axis=1)
summ = []
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))
for d in days:
    m = (sel["session_type"] == d).values
    mean = P[m].mean(0); mb = mean - mean[990:1000].mean(); mn = mb / mb.max()
    ax[0].plot(t_ms, mb, label=d); ax[1].plot(t_ms, mn, label=d)
    summ.append({"day": d, "units": int(m.sum()), "sessions": sel.loc[m, "session_id"].nunique(), "mice": sel.loc[m, "subject_id"].nunique(),
                 "median_response_0_250ms_Hz": np.median(resp[m]), "median_peak_ms": np.median(peak[m]),
                 "population_peak_ms": int(np.argmax(mn[1000:1250]))})
for a in ax: a.set_xlim(-100, 400); a.legend(fontsize=7); a.set_xlabel("ms from im115_r onset")
ax[0].set_ylabel("baseline-subtracted rate (Hz)"); ax[1].set_ylabel("normalised")
plt.show()
summ = pd.DataFrame(summ); print(summ.round(2).to_string(index=False))
ka = stats.kruskal(*[resp[(sel["session_type"] == d).values] for d in days])
kb = stats.kruskal(*[peak[(sel["session_type"] == d).values] for d in days])
print(f"(a) mean baseline-subtracted response 0-250 ms: Kruskal-Wallis p = {ka.pvalue:.3g}")
print(f"(b) time of peak 0-250 ms: Kruskal-Wallis p = {kb.pvalue:.3g}")
day_means = np.array([P[(sel["session_type"] == d).values].mean(0)[1000:1250] for d in days])
print("Pearson r between day population responses (0-250 ms):", np.round(np.corrcoef(day_means), 3).tolist())
pre, post = P[:, 900:1000].mean(1), P[:, 1020:1120].mean(1)
print(f"clock check: VISp rate -100 to 0 ms {np.median(pre):.2f} Hz, 20 to 120 ms {np.median(post):.2f} Hz "
      f"(Wilcoxon p = {stats.wilcoxon(post, pre).pvalue:.1e}); sessions where the post-onset rate is higher:",
      int((sel.assign(d=post - pre).groupby('session_id')['d'].median() > 0).sum()), "of", sel["session_id"].nunique())
No description has been provided for this image
    day  units  sessions  mice  median_response_0_250ms_Hz  median_peak_ms  population_peak_ms
EPHYS_1    595        12    12                        0.82            53.0                  42
EPHYS_2    482        11    11                        1.51            48.0                  44
EPHYS_3    534        12    12                        1.19            48.0                  44
EPHYS_4    446        11    11                        0.83            49.0                  42
(a) mean baseline-subtracted response 0-250 ms: Kruskal-Wallis p = 0.343
(b) time of peak 0-250 ms: Kruskal-Wallis p = 0.7
Pearson r between day population responses (0-250 ms): [[1.0, 0.991, 0.983, 0.98], [0.991, 1.0, 0.982, 0.977], [0.983, 0.982, 1.0, 0.983], [0.98, 0.977, 0.983, 1.0]]
clock check: VISp rate -100 to 0 ms 4.05 Hz, 20 to 120 ms 5.66 Hz (Wilcoxon p = 1.2e-41); sessions where the post-onset rate is higher: 39 of 46

Result: reproduced. 595, 482, 534 and 446 VISp units with significant RFs (expected 595, 482, 536, 446); the day population responses correlate at r = 0.977 to 0.991 over 0 to 250 ms; neither the per-unit response (Kruskal-Wallis p = 0.343) nor the time of peak (p = 0.70) differs across days.

behavior-stable-across-days¶

Task performance is stable across the four recording days despite repeated insertions (median rewards 59.5, 63.5, 56.5, 60.5; p = 0.96).

In [22]:
for lab, sset in (("87 QC-passing sessions", sessions[sessions["passing"]]), ("all sessions (variant)", sessions)):
    for col, name in (("n_rewards", "(a) all rewards"), ("n_rewards_earned", "(b) earned rewards")):
        g = [sset.loc[sset["session_type"] == d, col].values for d in days]
        k = stats.kruskal(*g)
        print(f"{lab}, {name}: sessions {[len(x) for x in g]}, medians {[float(np.median(x)) for x in g]}, Kruskal-Wallis p = {k.pvalue:.3f}")
87 QC-passing sessions, (a) all rewards: sessions [23, 24, 22, 18], medians [73.0, 65.0, 61.0, 53.5], Kruskal-Wallis p = 0.731
87 QC-passing sessions, (b) earned rewards: sessions [23, 24, 22, 18], medians [67.0, 59.0, 55.0, 48.0], Kruskal-Wallis p = 0.736
all sessions (variant), (a) all rewards: sessions [24, 26, 25, 24], medians [68.0, 63.5, 60.0, 56.5], Kruskal-Wallis p = 0.817
all sessions (variant), (b) earned rewards: sessions [24, 26, 25, 24], medians [62.5, 57.5, 54.0, 50.5], Kruskal-Wallis p = 0.816

Result: partially reproduced. Rewards do not differ across days under any reading (Kruskal-Wallis p from 0.731 to 0.817). The medians do not all match: over the 87 sessions all rewards give 73, 65, 61 and 53.5 (days 2 and 3 within tolerance), earned rewards 67, 59, 55 and 48 (days 2 and 3); over all sessions all rewards give 68, 63.5, 60 and 56.5 (days 2 to 4) and earned rewards 62.5, 57.5, 54 and 50.5 (days 1 to 3). Medians fall from day 1 to day 4 in every reading.

figs4i-insertion-suppression¶

Activity is transiently suppressed after probe insertion and recovers with a time constant of 25 to 30 minutes.

The fit window is not given by the paper (the S4I legend plots the mean change "over time from experiment start"). The main fit uses the longest window all 87 sessions share, 77.3 min from the first stimulus; this is an assumption, and it runs past the active block (about 65 min) into the Gabor and flash epochs. The fit is also shown over windows of 30 to 64 min, which keep to the active block, and per session over each session's own active block.

In [23]:
def sat(t, a, b, tau):
    return a - b * np.exp(-t / tau)
curves = {k: v["s4i_counts"] for k, v in SP.items() if k in PASS and len(v["s4i_counts"]) > 600}
L = min(len(c) for c in curves.values())
C = np.array([c[:L] for c in curves.values()], float)
tmin = np.arange(L) / 60
print(f"sessions: {len(C)}; common length {L / 60:.1f} min from the first stimulus")
fits = {}
fig, ax = plt.subplots(figsize=(6, 3))
for lab, norm in (("(a) divided by the session mean", C.mean(1, keepdims=True)),
                  ("(b) divided by the mean of the last 10 min", C[:, -600:].mean(1, keepdims=True))):
    y = (C / norm).mean(0)
    ym = y.reshape(-1, 60)[: L // 60].mean(1) if L % 60 == 0 else y[: (L // 60) * 60].reshape(-1, 60).mean(1)
    tm = np.arange(len(ym)) + 0.5
    popt, pcov = curve_fit(sat, tm, ym, p0=[1, 0.3, 20], maxfev=20000)
    fits[lab] = popt
    ax.plot(tm, ym, label=lab[:3])
    print(f"{lab}: tau = {popt[2]:.1f} min (SE {np.sqrt(pcov[2, 2]):.1f}), a = {popt[0]:.3f}, b = {popt[1]:.3f}; "
          f"rate at minute 1 {ym[0]:.3f}, at minute 30 {ym[29]:.3f}, last minute {ym[-1]:.3f}")
ax.set_xlabel("minutes from first stimulus"); ax.set_ylabel("normalised rate"); ax.legend(); plt.show()
# the same per session, to show the spread
taus = []
for c in C:
    ym = (c / c.mean())[: (L // 60) * 60].reshape(-1, 60).mean(1)
    try:
        taus.append(curve_fit(sat, np.arange(len(ym)) + 0.5, ym, p0=[1, 0.3, 20], maxfev=20000)[0][2])
    except Exception:
        taus.append(np.nan)
taus = np.array(taus)
print(f"per-session fits (a): median tau {np.nanmedian(taus):.1f} min, IQR {np.nanpercentile(taus, 25):.1f} to {np.nanpercentile(taus, 75):.1f}, failed {int(np.isnan(taus).sum())}")
first = sessions.set_index("session_id").loc[list(curves), "first_stim"]
print(f"first stimulus at {first.min():.0f} to {first.max():.0f} s of the recording (median {first.median():.0f})")

# the fit window: the fits above use the common length (77.3 min, which runs past the active block
# into the Gabor and flash epochs); the same fit over shorter windows from the first stimulus,
# including the shortest active block, and each window normalised by its own mean
be = np.array([(SP[k]["beh_end"] - SP[k]["first_stim"]) / 60 for k in curves])
bs = np.array([(SP[k]["beh_start"] - SP[k]["first_stim"]) / 60 for k in curves])
print(f"active block from {bs.min():.1f} to {bs.max():.1f} min after the first stimulus (median {np.median(bs):.1f}), "
      f"ending at {be.min():.1f} to {be.max():.1f} min (median {np.median(be):.1f})")
win_rows = []
for W in (30, 40, 45, 50, 60, int(be.min()), L // 60):
    Cw = C[:, : W * 60]
    for lab, normw in (("a", Cw.mean(1, keepdims=True)), ("b", Cw[:, -600:].mean(1, keepdims=True))):
        ym = (Cw / normw).mean(0).reshape(-1, 60).mean(1)
        tm = np.arange(len(ym)) + 0.5
        try:
            popt, pcov = curve_fit(sat, tm, ym, p0=[1, 0.3, 20], maxfev=20000)
            tau, se = popt[2], np.sqrt(pcov[2, 2])
        except Exception:
            tau, se = np.nan, np.nan
        win_rows.append({"window_min": W, "reading": lab, "tau_min": tau, "SE": se, "within_22.5_33": 22.5 <= tau <= 33})
print(pd.DataFrame(win_rows).to_string(index=False, float_format=lambda v: f"{v:.3g}"))
# per-session fits over the active block of each session (from its first stimulus to its end, at most
# the common length)
pts = []
for k, c in zip(curves, C):
    W = min(int((SP[k]["beh_end"] - SP[k]["first_stim"]) // 60), L // 60)
    ym = (c[: W * 60] / c[: W * 60].mean()).reshape(-1, 60).mean(1)
    try:
        pts.append(curve_fit(sat, np.arange(len(ym)) + 0.5, ym, p0=[1, 0.3, 20], maxfev=20000)[0][2])
    except Exception:
        pts.append(np.nan)
pts = np.array(pts)
print(f"per-session fits over each session's active block (a): median tau {np.nanmedian(pts):.1f} min, "
      f"IQR {np.nanpercentile(pts, 25):.1f} to {np.nanpercentile(pts, 75):.1f}, sessions within 22.5 to 33: "
      f"{int(((pts >= 22.5) & (pts <= 33)).sum())} of {len(pts)}, failed {int(np.isnan(pts).sum())}")
sessions: 87; common length 77.3 min from the first stimulus
(a) divided by the session mean: tau = 36.0 min (SE 2.3), a = 1.147, b = 0.358; rate at minute 1 0.826, at minute 30 0.999, last minute 1.077
(b) divided by the mean of the last 10 min: tau = 35.4 min (SE 2.3), a = 1.053, b = 0.322; rate at minute 1 0.763, at minute 30 0.921, last minute 0.990
No description has been provided for this image
per-session fits (a): median tau 34.8 min, IQR 23.1 to 52.5, failed 0
first stimulus at 25 to 636 s of the recording (median 64)
active block from 0.0 to 25.6 min after the first stimulus (median 0.0), ending at 65.0 to 90.7 min (median 65.1)
 window_min reading  tau_min       SE  within_22.5_33
         30       a 2.32e+05  4.3e+07           False
         30       b 9.23e+04 2.01e+07           False
         40       a     79.1       27           False
         40       b     66.9     19.8           False
         45       a       84     22.7           False
         45       b     76.2     19.4           False
         50       a     69.6     12.2           False
         50       b     65.2       11           False
         60       a     47.1      4.3           False
         60       b     45.9      4.2           False
         64       a     42.2     3.34           False
         64       b     41.2     3.26           False
         77       a       36      2.3           False
         77       b     35.4     2.27           False
per-session fits over each session's active block (a): median tau 39.8 min, IQR 25.7 to 66.8, sessions within 22.5 to 33: 26 of 87, failed 0

Result: not reproduced. Activity is lower early in the session and recovers (normalised rate 0.826 in minute 1 against 0.999 at minute 30 and 1.077 at the end, reading a), but the fitted time constant is 36.0 min (SE 2.3) under reading (a) and 35.4 min (SE 2.3) under (b) over the common 77.3 min, above the range 22.5 to 33 min; per-session fits give a median of 34.8 min (IQR 23.1 to 52.5). The window is an assumption, but no window brings tau into the range: it grows as the window shortens, 41.2 to 42.2 min over the shortest active block (64 min), 45.9 to 47.1 at 60 min, 65.2 to 84.0 at 40 to 50 min, and at 30 min the fit finds no recovery (tau unbounded). Per-session fits over each session's active block give a median of 39.8 min (IQR 25.7 to 66.8), with 26 of 87 sessions inside 22.5 to 33 min.

Alpha events: detection and the shared method¶

Events are detected in every session as the claims file's header describes, under each open reading: area membership L1 (the Methods: ISI label for cortex, else the CCF label; areas with at least 20 QC units) and L2 (the code: CCF labels, an area takes every unit whose label contains its acronym, areas with more than 20 units), and the event rule E1 (the Methods: four successive maxima) and E2 (the code: three successive maxima in a window with more than three). Random-time coherence is R1 (both areas at the random time) or R2 (the reference at the event, the other area at the random time). Session sets are (a) the 87 QC-passing sessions and (b) all 99. Every alpha claim below is shown under all 16 combinations.

Choices stated here. (1) Events are detected on the smoothed population rate (the header's exponential filter, inside shield.find_events), while the autocorrelogram threshold, the CCGs and the coherence use the unsmoothed 10 ms rate. The claims file defines the population rate as smoothed without saying which steps use it; the detail is taken from the code's printed counts that the header quotes (1653 VISp and 113 MOp qualifying events, code cell 12), which the unsmoothed autocorrelogram matches (1643 to 1704 and 119 to 122 under E2): the table below also shows the counts with the threshold on the smoothed autocorrelogram (qualifying_smoothed_acg, 3035 to 3426 and 152 to 170 under E2), far above the code's. (2) The Benjamini-Hochberg family of the pair test holds the brain areas only; the CP-RF and CP-noRF groups of Figure 8F, the unannotated labels and the ISI label "VIS" are left out of it (shown against the correction over all pairs below). (3) A claim part whose area has fewer than 25 events is reported as untested (absent), not as not significant. (4) VISl is taken with its ISI label VISlm (the L1 maps label the lateral area VISlm; the CCF label is VISl); both are shown. The area labels of each reading are listed below: SSp has no subdivisions in these files, RSP has RSPagl, RSPd and RSPv, ACA has ACAd and ACAv.

In [24]:
COMBOS = [(L, E, R, S) for L in ("L1", "L2") for E in ("E1", "E2") for R in ("R1", "R2") for S in ("a", "b")]
SETS = {"a": PASS, "b": set(sessions["session_id"])}

def events_table(ref, L, E, S):
    rows = []
    for sid, v in SP.items():
        if sid not in SETS[S]:
            continue
        e = v["alpha"].get((L, ref, E))
        if e is None or not len(e["events"]):
            continue
        ev = e["events"].copy()
        ev["session_id"] = sid
        rows.append(ev)
    return pd.concat(rows, ignore_index=True) if rows else pd.DataFrame()

det = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            for ref in shield.REFERENCE_AREAS:
                ev = events_table(ref, L, E, S)
                elig = sum(1 for sid, v in SP.items() if sid in SETS[S] and ref in v["areas"][L])
                q = ev[ev["qual"]] if len(ev) else ev
                det.append({"L": L, "E": E, "set": S, "area": ref, "eligible_sessions": elig,
                            "sessions_with_events": ev["session_id"].nunique() if len(ev) else 0,
                            "events": len(ev), "qualifying": len(q),
                            "qualifying_smoothed_acg": int((ev["auto_power_smooth"] > shield.AUTO_THRESHOLD).sum()) if len(ev) else 0,
                            "sessions_over_5_qualifying": int((q.groupby("session_id").size() > 5).sum()) if len(q) else 0,
                            "qualifying_in_those": int(q.groupby("session_id").size().loc[lambda s: s > 5].sum()) if len(q) else 0})
det = pd.DataFrame(det)
print(det.to_string(index=False))
print("(the code printed 50 sessions with 1653 qualifying VISp events and 14 sessions with 113 MOp events; not values of the paper)")

# area labels under L1 and L2 (87 sessions): sessions with at least the minimum of QC units, median units
from collections import Counter
for L in ("L1", "L2"):
    cnt, nu = Counter(), {}
    for sid, v in SP.items():
        if sid in PASS:
            for a, n in v["areas"][L].items():
                cnt[a] += 1; nu.setdefault(a, []).append(n)
    print(f"\n{L} areas (sessions, median units):", {a: (cnt[a], int(np.median(nu[a]))) for a in sorted(cnt)})
 L  E set area  eligible_sessions  sessions_with_events  events  qualifying  qualifying_smoothed_acg  sessions_over_5_qualifying  qualifying_in_those
L1 E1   a VISp                 47                    38    2965        1518                     2840                          11                 1483
L1 E1   a  MOp                 38                     8     106          80                       98                           4                   68
L1 E1   b VISp                 51                    42    3011        1522                     2872                          11                 1483
L1 E1   b  MOp                 44                    10     119          82                      110                           4                   68
L1 E2   a VISp                 47                    41    3581        1694                     3364                          11                 1651
L1 E2   a  MOp                 38                    12     162         119                      152                           5                  108
L1 E2   b VISp                 51                    45    3665        1704                     3426                          11                 1651
L1 E2   b  MOp                 44                    14     182         122                      170                           5                  108
L2 E1   a VISp                 51                    46    2673        1469                     2554                          11                 1428
L2 E1   a  MOp                 38                     8     106          80                       98                           4                   68
L2 E1   b VISp                 56                    51    2737        1478                     2603                          11                 1428
L2 E1   b  MOp                 44                    10     119          82                      110                           4                   68
L2 E2   a VISp                 51                    47    3244        1643                     3035                          13                 1604
L2 E2   a  MOp                 38                    12     162         119                      152                           5                  108
L2 E2   b VISp                 56                    52    3355        1660                     3120                          14                 1611
L2 E2   b  MOp                 44                    14     182         122                      170                           5                  108
(the code printed 50 sessions with 1653 qualifying VISp events and 14 sessions with 113 MOp events; not values of the paper)

L1 areas (sessions, median units): {'ACAd': (25, 27), 'ACAv': (19, 55), 'ACB': (3, 32), 'APN': (17, 34), 'AV': (1, 20), 'BST': (1, 28), 'CA1': (56, 35), 'CA3': (24, 31), 'CL': (5, 32), 'CP': (42, 52), 'DG': (31, 33), 'DP': (8, 31), 'ENTm': (1, 38), 'Eth': (1, 21), 'FRP': (1, 25), 'ILA': (8, 38), 'LD': (14, 39), 'LGd': (20, 38), 'LGv': (5, 27), 'LP': (26, 52), 'LSc': (17, 29), 'LSr': (30, 56), 'LSv': (4, 35), 'MB': (14, 31), 'MD': (5, 28), 'MGd': (6, 35), 'MGm': (6, 35), 'MGv': (14, 42), 'MOp': (38, 64), 'MOs': (46, 65), 'MRN': (5, 32), 'MS': (1, 28), 'NOT': (1, 21), 'OLF': (4, 31), 'ORBm': (2, 49), 'ORBvl': (3, 119), 'PAR': (1, 38), 'PF': (2, 24), 'PIL': (1, 36), 'PL': (12, 45), 'PO': (17, 51), 'POL': (3, 44), 'POST': (5, 30), 'PPT': (3, 22), 'PVT': (1, 48), 'ProS': (8, 37), 'RSPagl': (26, 42), 'RSPd': (28, 37), 'RSPv': (24, 53), 'RT': (10, 24), 'SCig': (7, 29), 'SCiw': (6, 28), 'SCsg': (1, 28), 'SF': (4, 26), 'SFO': (1, 25), 'SGN': (8, 24), 'SSp': (61, 62), 'SSs': (2, 99), 'STR': (7, 27), 'SUB': (20, 35), 'TH': (4, 32), 'TRS': (3, 30), 'TTd': (3, 41), 'Track not annotated': (21, 267), 'VAL': (2, 22), 'VIS': (27, 50), 'VISa': (3, 29), 'VISal': (5, 54), 'VISam': (6, 44), 'VISl': (2, 43), 'VISlm': (15, 52), 'VISmma': (2, 41), 'VISp': (47, 50), 'VISpm': (5, 55), 'VISrl': (11, 48), 'VISrll': (1, 60), 'VL': (1, 26), 'VPL': (6, 27), 'VPM': (11, 41), 'ZI': (1, 29)}

L2 areas (sessions, median units): {'ACAd': (21, 28), 'ACAv': (17, 57), 'ACB': (3, 32), 'APN': (17, 34), 'BST': (1, 28), 'CA1': (56, 35), 'CA3': (22, 31), 'CL': (4, 36), 'CP': (41, 55), 'DG': (30, 33), 'DP': (7, 31), 'ENTm': (1, 38), 'Eth': (1, 21), 'FRP': (1, 25), 'ILA': (8, 38), 'LD': (13, 39), 'LGd': (20, 38), 'LGv': (5, 27), 'LP': (24, 56), 'LSc': (15, 32), 'LSr': (29, 58), 'LSv': (4, 35), 'MB': (14, 31), 'MD': (4, 39), 'MGd': (6, 35), 'MGm': (5, 36), 'MGv': (14, 42), 'MOp': (38, 64), 'MOs': (46, 65), 'MRN': (4, 48), 'MS': (1, 28), 'NOT': (1, 21), 'OLF': (4, 31), 'ORBm': (2, 49), 'ORBvl': (3, 119), 'PAR': (1, 38), 'PF': (2, 24), 'PIL': (1, 36), 'PL': (11, 47), 'PO': (17, 63), 'POL': (3, 44), 'POST': (5, 30), 'PPT': (3, 22), 'PVT': (1, 48), 'ProS': (8, 37), 'RSPagl': (24, 46), 'RSPd': (28, 39), 'RSPv': (26, 52), 'RT': (6, 34), 'SCig': (7, 29), 'SCiw': (6, 28), 'SCsg': (1, 28), 'SF': (4, 26), 'SFO': (1, 25), 'SGN': (8, 24), 'SSp': (62, 64), 'SSs': (2, 99), 'STR': (6, 30), 'SUB': (20, 35), 'TEa': (1, 69), 'TH': (4, 32), 'TRS': (3, 30), 'TTd': (3, 41), 'Track not annotated': (21, 267), 'VAL': (2, 22), 'VISa': (5, 37), 'VISal': (14, 48), 'VISam': (8, 40), 'VISl': (18, 46), 'VISli': (4, 41), 'VISp': (51, 55), 'VISpm': (9, 51), 'VISpor': (1, 28), 'VISrl': (11, 47), 'VL': (1, 26), 'VPL': (6, 27), 'VPM': (11, 41), 'ZI': (1, 29)}
In [25]:
NOT_FAMILY = ["CP-RF", "CP-noRF", "VIS", *shield.NOT_IN_AREA]

def pair_table(ref, L, E, R, S, min_events=25):
    """The Figure 7F pair test: per pair the per-event band coherence minus random, pooled over
    sessions with more than 5 qualifying events; pairs with at least 25 events; Wilcoxon against
    zero; Benjamini-Hochberg across the reference area's regions. The family holds brain areas
    only: CP-RF and CP-noRF (Figure 8F groups), the unannotated labels and the ISI label "VIS"
    (visual cortex without an area) are tested but left out of it; `p_bh_all` is the correction
    over every pair, as in the first version of this notebook."""
    vals, ccg_sum, ccg_n = {}, {}, {}
    for sid, v in SP.items():
        if sid not in SETS[S]:
            continue
        e = v["alpha"].get((L, ref, E))
        if e is None or "coh" not in e or len(e["coh"]["event"]) <= 5:
            continue
        d = e["coh"]["event"] - e["coh"][R]
        for j, a in enumerate(e["coh_names"]):
            if a == ref:
                continue
            vals.setdefault(a, []).append(d[:, j])
            ccg_sum[a] = ccg_sum.get(a, 0) + e["ccg_mean"][a] * e["ccg_n"]
            ccg_n[a] = ccg_n.get(a, 0) + e["ccg_n"]
    rows = []
    lag_ms = (np.arange(200) - 100) * 10
    for a, lst in vals.items():
        x = np.concatenate(lst)
        x = x[np.isfinite(x)]
        if len(x) < min_events:
            continue
        c = ccg_sum[a] / ccg_n[a]
        rows.append({"area": a, "events": len(x), "sessions": len(lst), "mean_diff": x.mean(),
                     "p": stats.wilcoxon(x).pvalue, "ccg_peak": c.max(), "ccg_peak_lag_ms": lag_ms[np.argmax(c)]})
    t = pd.DataFrame(rows)
    if len(t):
        fam = ~t["area"].isin(NOT_FAMILY)
        t["p_bh_all"] = multipletests(t["p"], method="fdr_bh")[1]
        t["p_bh"] = np.nan
        t.loc[fam, "p_bh"] = multipletests(t.loc[fam, "p"], method="fdr_bh")[1]
        t.loc[~fam, "p_bh"] = t.loc[~fam, "p_bh_all"]
        t["sig_pos"] = (t["p_bh"] < 0.05) & (t["mean_diff"] > 0)
        t = t.sort_values("area").reset_index(drop=True)
    return t

PAIRS = {(ref, L, E, R, S): pair_table(ref, L, E, R, S) for ref in shield.REFERENCE_AREAS for (L, E, R, S) in COMBOS}
print("pairs tested per combination (VISp, MOp):",
      {f"{L}{E}{R}{S}": (len(PAIRS[('VISp', L, E, R, S)]), len(PAIRS[('MOp', L, E, R, S)])) for (L, E, R, S) in COMBOS})
changed = [(k, x.area) for k, t in PAIRS.items() if len(t) for x in t.itertuples()
           if x.area not in NOT_FAMILY and ((x.p_bh < 0.05) != (x.p_bh_all < 0.05))]
print("pairs whose significance at q < 0.05 changes with the family:", len(changed), "of",
      sum(int((~t["area"].isin(NOT_FAMILY)).sum()) for t in PAIRS.values() if len(t)), changed)
pairs tested per combination (VISp, MOp): {'L1E1R1a': (37, 12), 'L1E1R1b': (37, 12), 'L1E1R2a': (37, 12), 'L1E1R2b': (37, 12), 'L1E2R1a': (38, 22), 'L1E2R1b': (38, 22), 'L1E2R2a': (38, 22), 'L1E2R2b': (38, 22), 'L2E1R1a': (33, 11), 'L2E1R1b': (33, 11), 'L2E1R2a': (33, 11), 'L2E1R2b': (33, 11), 'L2E2R1a': (33, 21), 'L2E2R1b': (34, 21), 'L2E2R2a': (33, 21), 'L2E2R2b': (34, 21)}
pairs whose significance at q < 0.05 changes with the family: 4 of 734 [(('VISp', 'L2', 'E2', 'R2', 'a'), 'PO'), (('VISp', 'L2', 'E2', 'R2', 'b'), 'PO'), (('MOp', 'L1', 'E2', 'R2', 'a'), 'CA1'), (('MOp', 'L1', 'E2', 'R2', 'b'), 'CA1')]

fig7a-mop-higher-frequency¶

VISp alpha events are rhythmic at 3 to 5 Hz, and MOp events occur in a slightly higher band, 5 to 7 Hz.

Frequency readings: (a) the inverse of the median interval between the maxima detected in the event's window; (b) the peak of the autocorrelogram's spectrum between 1 and 20 Hz (the limits are a standard choice that excludes the DC term).

In [26]:
rows = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            ev_v = events_table("VISp", L, E, S); ev_m = events_table("MOp", L, E, S)
            ev_v, ev_m = ev_v[ev_v["qual"]], ev_m[ev_m["qual"]]
            for fcol, lab in (("freq_peaks", "a"), ("freq_auto", "b"), ("freq_auto_pad", "b, zero-padded")):
                xv, xm = ev_v[fcol].dropna(), ev_m[fcol].dropna()
                p = stats.ranksums(xm, xv, alternative="greater").pvalue if len(xm) and len(xv) else np.nan
                rows.append({"L": L, "E": E, "set": S, "reading": lab, "VISp_n": len(xv), "VISp_median_Hz": xv.median(),
                             "MOp_n": len(xm), "MOp_median_Hz": xm.median(), "p_MOp_higher": p,
                             "holds": (2.7 <= xv.median() <= 5.5) and (4.5 <= xm.median() <= 7.7) and p < 0.05})
fq = pd.DataFrame(rows)
print(fq.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
for lab, g in fq.groupby("reading"):
    print(f"reading ({lab}): holds in {int(g['holds'].sum())} of {len(g)}; MOp median {g['MOp_median_Hz'].min():.3g} to {g['MOp_median_Hz'].max():.3g} Hz, "
          f"VISp median {g['VISp_median_Hz'].min():.3g} to {g['VISp_median_Hz'].max():.3g} Hz, p {g['p_MOp_higher'].min():.2g} to {g['p_MOp_higher'].max():.2g}")
ev_m = events_table("MOp", "L2", "E2", "a"); ev_m = ev_m[ev_m["qual"]]
print("L2 E2 a, MOp events: share of freq_auto values at exactly 4.5 Hz", round(float((ev_m["freq_auto"] == 4.5).mean()), 3),
      "; values on the 0.5 Hz grid:", ev_m["freq_auto"].value_counts().sort_index().to_dict())
# the detector's band: synthetic sinusoidal rates of 2 to 8 Hz, 20 s each, from 0 to 20 spikes/s (the
# minima must reach the height rule, -1 on the inverted rate)
syn = []
for fz in np.arange(2, 8.01, 0.5):
    tt_ = np.arange(2000) * shield.BIN
    for E in ("E1", "E2"):
        syn.append({"Hz": fz, "rule": E, "events": len(shield.find_events((10 * (1 + np.sin(2 * np.pi * fz * tt_))).astype(np.float32), E)[0])})
syn = pd.DataFrame(syn).pivot(index="Hz", columns="rule", values="events")
print("events found in 20 s of a pure sinusoidal rate, by frequency:", syn.T.to_dict("index"))
 L  E set        reading  VISp_n  VISp_median_Hz  MOp_n  MOp_median_Hz  p_MOp_higher  holds
L1 E1   a              a    1518            4.17     80           4.55        0.0784  False
L1 E1   a              b    1518               4     80            4.5      0.000298   True
L1 E1   a b, zero-padded    1518            4.05     80           4.64      0.000668   True
L1 E1   b              a    1522            4.17     82           4.55        0.0972  False
L1 E1   b              b    1522               4     82            4.5      0.000235   True
L1 E1   b b, zero-padded    1522            4.05     82           4.64      0.000377   True
L1 E2   a              a    1694            4.17    119           4.35         0.568  False
L1 E2   a              b    1694               4    119            4.5      9.62e-07   True
L1 E2   a b, zero-padded    1694            4.05    119           4.74      1.69e-06   True
L1 E2   b              a    1704            4.17    122           4.35          0.67  False
L1 E2   b              b    1704               4    122            4.5      5.28e-07   True
L1 E2   b b, zero-padded    1704            4.05    122           4.74      7.06e-07   True
L2 E1   a              a    1469            4.17     80           4.55         0.116  False
L2 E1   a              b    1469               4     80            4.5       0.00158   True
L2 E1   a b, zero-padded    1469            4.05     80           4.64       0.00385   True
L2 E1   b              a    1478            4.17     82           4.55         0.138  False
L2 E1   b              b    1478               4     82            4.5        0.0012   True
L2 E1   b b, zero-padded    1478            4.05     82           4.64       0.00222   True
L2 E2   a              a    1643            4.17    119           4.35         0.684  False
L2 E2   a              b    1643               4    119            4.5      1.44e-05   True
L2 E2   a b, zero-padded    1643             4.1    119           4.74      4.13e-05   True
L2 E2   b              a    1660            4.21    122           4.35         0.765  False
L2 E2   b              b    1660               4    122            4.5      7.27e-06   True
L2 E2   b b, zero-padded    1660             4.1    122           4.74      1.67e-05   True
reading (a): holds in 0 of 8; MOp median 4.35 to 4.55 Hz, VISp median 4.17 to 4.21 Hz, p 0.078 to 0.76
reading (b): holds in 8 of 8; MOp median 4.5 to 4.5 Hz, VISp median 4 to 4 Hz, p 5.3e-07 to 0.0016
reading (b, zero-padded): holds in 8 of 8; MOp median 4.64 to 4.74 Hz, VISp median 4.05 to 4.1 Hz, p 7.1e-07 to 0.0038
L2 E2 a, MOp events: share of freq_auto values at exactly 4.5 Hz 0.227 ; values on the 0.5 Hz grid: {2.0: 2, 2.5: 1, 3.0: 3, 3.5: 8, 4.0: 21, 4.5: 27, 5.0: 27, 5.5: 14, 6.0: 9, 6.5: 5, 7.0: 2}
events found in 20 s of a pure sinusoidal rate, by frequency: {'E1': {2.0: 0, 2.5: 0, 3.0: 7, 3.5: 8, 4.0: 8, 4.5: 9, 5.0: 9, 5.5: 0, 6.0: 0, 6.5: 0, 7.0: 0, 7.5: 0, 8.0: 0}, 'E2': {2.0: 0, 2.5: 0, 3.0: 7, 3.5: 8, 4.0: 8, 4.5: 9, 5.0: 9, 5.5: 0, 6.0: 0, 6.5: 0, 7.0: 0, 7.5: 0, 8.0: 0}}

Result: reproduced. Under frequency reading (b), the autocorrelogram peak, VISp events are at a median 4.0 Hz and MOp events at 4.5 Hz, higher by the one-sided rank-sum test (p from 5.3e-7 to 0.0016) in all 8 combinations of L, E and session set. On the 0.5 Hz grid of the 2 s autocorrelogram the MOp median sits on the lower edge of the widened range (4.5 to 7.7 Hz) and 22.7% of MOp values (L2 E2 a) are exactly 4.5 Hz; with the spectrum zero-padded to 0.049 Hz steps the medians are 4.05 to 4.10 Hz (VISp) and 4.64 to 4.74 Hz (MOp), inside the ranges, with MOp higher at p from 7.1e-7 to 0.0038 in all 8. Under reading (a), the inverse of the median interval between maxima, VISp is at 4.17 to 4.21 Hz and MOp at 4.35 to 4.55 Hz, and MOp is not significantly higher (p 0.078 to 0.76). The criterion accepts a match under a reading. The detector itself finds sinusoidal rates of 3 to 5 Hz only (none at 2.5 Hz or below, none at 5.5 Hz or above), so the MOp frequencies are bounded by the event rule and a 5 to 7 Hz MOp band could not appear under either reading.

fig7b-low-arousal¶

VISp and MOp alpha events occur when the mouse is immobile and the pupil constricted, with pupil diameter about 25% smaller than at random session times.

Pupil is the pupil-fit area with blinks removed; the diameter reading takes the square root of the mean area in each window (an approximation stated here: the square root is taken of window means, not of samples).

In [27]:
rows = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            per = {}
            for ref in shield.REFERENCE_AREAS:
                ev = events_table(ref, L, E, S)
                ev = ev[ev["qual"]].copy()
                ref_mean = ev.groupby("session_id")["pupil_win_mean_rand"].transform("mean")
                ev["chg_area"] = (ev["pupil"] - ref_mean) / ref_mean
                ev["chg_diam"] = (np.sqrt(ev["pupil"]) - np.sqrt(ref_mean)) / np.sqrt(ref_mean)
                per[ref] = ev
                run_e, run_r = ev["running"].dropna(), ev["running_rand"].dropna()
                for meas in ("area", "diam"):
                    c = ev["chg_" + meas].dropna()
                    rows.append({"L": L, "E": E, "set": S, "events": ref, "measure": meas, "n": len(c),
                                 "mean_pct": 100 * c.mean(), "median_pct": 100 * c.median(),
                                 "p_pupil": stats.wilcoxon(c).pvalue if len(c) > 5 else np.nan,
                                 "running_event": run_e.mean(), "running_random": run_r.mean(),
                                 "p_running_lower": stats.ranksums(run_e, run_r, alternative="less").pvalue if len(run_e) > 5 else np.nan})
            both = pd.concat(per.values())
            for meas in ("area", "diam"):
                c = both["chg_" + meas].dropna()
                rows.append({"L": L, "E": E, "set": S, "events": "pooled", "measure": meas, "n": len(c),
                             "mean_pct": 100 * c.mean(), "median_pct": 100 * c.median(),
                             "p_pupil": stats.wilcoxon(c).pvalue if len(c) > 5 else np.nan})
ar = pd.DataFrame(rows)
ar["within_tol_mean"] = (ar["mean_pct"] >= -27.5) & (ar["mean_pct"] <= -22.5)
ar["within_tol_median"] = (ar["median_pct"] >= -27.5) & (ar["median_pct"] <= -22.5)
print(ar.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
 L  E set events measure    n  mean_pct  median_pct   p_pupil  running_event  running_random  p_running_lower  within_tol_mean  within_tol_median
L1 E1   a   VISp    area 1516     -12.9       -14.8  6.67e-97          0.235            4.19         8.45e-76            False              False
L1 E1   a   VISp    diam 1516     -7.48       -7.69  2.2e-104          0.235            4.19         8.45e-76            False              False
L1 E1   a    MOp    area   80     -36.5       -38.9  8.15e-15         0.0323            7.11         7.77e-10            False              False
L1 E1   a    MOp    diam   80     -20.8       -21.9  8.15e-15         0.0323            7.11         7.77e-10            False              False
L1 E1   a pooled    area 1596     -14.1       -16.1 9.62e-112            NaN             NaN              NaN            False              False
L1 E1   a pooled    diam 1596     -8.15       -8.38 1.16e-119            NaN             NaN              NaN            False              False
L1 E1   b   VISp    area 1520     -12.9       -14.8   3.9e-97          0.235            4.27         1.14e-76            False              False
L1 E1   b   VISp    diam 1520     -7.46       -7.68 1.33e-104          0.235            4.27         1.14e-76            False              False
L1 E1   b    MOp    area   82     -35.7       -37.9   3.8e-15           1.16            7.66         2.67e-09            False              False
L1 E1   b    MOp    diam   82     -20.4       -21.2   3.8e-15           1.16            7.66         2.67e-09            False              False
L1 E1   b pooled    area 1602       -14       -15.9 4.12e-112            NaN             NaN              NaN            False              False
L1 E1   b pooled    diam 1602     -8.12       -8.28 5.28e-120            NaN             NaN              NaN            False              False
L1 E2   a   VISp    area 1692     -13.2       -15.1 8.23e-115          0.271            4.44         4.61e-79            False              False
L1 E2   a   VISp    diam 1692      -7.6       -7.85 4.29e-123          0.271            4.44         4.61e-79            False              False
L1 E2   a    MOp    area  119     -35.6       -36.9  3.19e-21           0.04            5.67         5.35e-11            False              False
L1 E2   a    MOp    diam  119     -20.5       -20.6  3.19e-21           0.04            5.67         5.35e-11            False              False
L1 E2   a pooled    area 1811     -14.7       -16.3 1.83e-136            NaN             NaN              NaN            False              False
L1 E2   a pooled    diam 1811     -8.44       -8.51  2.6e-145            NaN             NaN              NaN            False              False
L1 E2   b   VISp    area 1702     -13.1       -14.9 3.18e-115          0.281            4.65         5.46e-81            False              False
L1 E2   b   VISp    diam 1702     -7.56       -7.78 1.69e-123          0.281            4.65         5.46e-81            False              False
L1 E2   b    MOp    area  122     -34.7       -35.8  1.21e-21           1.11            6.58          2.3e-10            False              False
L1 E2   b    MOp    diam  122       -20       -19.9  1.21e-21           1.11            6.58          2.3e-10            False              False
L1 E2   b pooled    area 1824     -14.6       -16.2 5.84e-137            NaN             NaN              NaN            False              False
L1 E2   b pooled    diam 1824     -8.39       -8.45 8.84e-146            NaN             NaN              NaN            False              False
L2 E1   a   VISp    area 1467     -15.4       -17.5 9.26e-126          0.212            4.15         2.15e-80            False              False
L2 E1   a   VISp    diam 1467     -8.81       -9.19 1.64e-132          0.212            4.15         2.15e-80            False              False
L2 E1   a    MOp    area   80     -31.7       -31.6  9.13e-15         0.0323            5.16         3.96e-06            False              False
L2 E1   a    MOp    diam   80     -17.8       -17.3  9.13e-15         0.0323            5.16         3.96e-06            False              False
L2 E1   a pooled    area 1547     -16.3       -18.5 5.97e-140            NaN             NaN              NaN            False              False
L2 E1   a pooled    diam 1547     -9.27       -9.73 5.06e-147            NaN             NaN              NaN            False              False
L2 E1   b   VISp    area 1476     -15.4       -17.5 1.52e-126          0.248            4.38         6.48e-81            False              False
L2 E1   b   VISp    diam 1476     -8.78       -9.17 2.71e-133          0.248            4.38         6.48e-81            False              False
L2 E1   b    MOp    area   82     -30.8       -31.2  6.84e-15           1.16            6.23         7.98e-06            False              False
L2 E1   b    MOp    diam   82     -17.3         -17   6.6e-15           1.16            6.23         7.98e-06            False              False
L2 E1   b pooled    area 1558     -16.2       -18.3 1.12e-140            NaN             NaN              NaN            False              False
L2 E1   b pooled    diam 1558     -9.23        -9.6 9.87e-148            NaN             NaN              NaN            False              False
L2 E2   a   VISp    area 1641     -14.5       -16.1 7.89e-134          0.297            4.18         1.16e-82            False              False
L2 E2   a   VISp    diam 1641     -8.26       -8.39 4.75e-141          0.297            4.18         1.16e-82            False              False
L2 E2   a    MOp    area  119     -36.4       -38.6  3.19e-21           0.04            5.25         3.32e-12            False              False
L2 E2   a    MOp    diam  119     -20.9       -21.7  3.19e-21           0.04            5.25         3.32e-12            False              False
L2 E2   a pooled    area 1760       -16       -17.3 3.38e-156            NaN             NaN              NaN            False              False
L2 E2   a pooled    diam 1760     -9.11       -9.07 8.01e-164            NaN             NaN              NaN            False              False
L2 E2   b   VISp    area 1658     -14.4       -15.9 7.85e-135          0.359            4.55         4.99e-84            False              False
L2 E2   b   VISp    diam 1658      -8.2       -8.29 5.01e-142          0.359            4.55         4.99e-84            False              False
L2 E2   b    MOp    area  122     -35.5       -38.2  1.44e-21           1.11            5.83         1.73e-11            False              False
L2 E2   b    MOp    diam  122     -20.4       -21.4  1.44e-21           1.11            5.83         1.73e-11            False              False
L2 E2   b pooled    area 1780     -15.8       -17.1 3.07e-157            NaN             NaN              NaN            False              False
L2 E2   b pooled    diam 1780     -9.03       -8.96 8.05e-165            NaN             NaN              NaN            False              False

Result: partially reproduced. At qualifying VISp and MOp events the pupil is smaller (Wilcoxon p at most 9.1e-15 in every combination) and running is lower (0.03 to 1.16 cm/s at events against 4.1 to 7.7 at random times, one-sided rank-sum p at most 8.0e-6). The size of the change does not match 25%: VISp events give -12.9 to -15.4% (area, mean) and -7.5 to -8.8% (diameter); MOp events -30.8 to -36.5% (area) and -17.3 to -20.9% (diameter); pooled -14.0 to -16.3% (area). No reading is within -22.5 to -27.5%. The pupil and running clocks are checked against the stimulus and trial clock in the data check (alignment of at least one stream is not shown in 12 of 99 sessions: 2 without a running contrast, 2 without eye tracking, 5 with a weak running and 3 with a weak pupil contrast).

fig7b-visp-offset-locked¶

VISp alpha events are often triggered by the offset of visual stimuli, while the timing of MOp events is not related to visual stimuli.

The stimulus before an event is the last active-behavior image presentation starting before it, as the code's stimulus-trials table holds the active presentations (its columns join each presentation to a trial).

In [28]:
rows = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            for ref in shield.REFERENCE_AREAS:
                ev = events_table(ref, L, E, S)
                x = ev.loc[ev["qual"], "since_offset"].dropna()
                x = x[(x >= 0) & (x < 0.5)].values
                h = np.histogram(x, np.arange(0, 0.5001, 0.05))[0]
                rows.append({"L": L, "E": E, "set": S, "area": ref, "n_in_0_500ms": len(x), "bins": h.tolist(),
                             "largest_bin_ms": int(np.argmax(h) * 50) if len(x) else -1,
                             "p_chi2": stats.chisquare(h).pvalue if len(x) else np.nan,
                             "p_ks": stats.kstest(x, stats.uniform(0, 0.5).cdf).pvalue if len(x) else np.nan})
off = pd.DataFrame(rows)
print(off.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
 L  E set area  n_in_0_500ms                                       bins  largest_bin_ms  p_chi2      p_ks
L1 E1   a VISp          1034   [8, 41, 634, 243, 23, 6, 22, 22, 15, 20]             100       0 1.44e-248
L1 E1   a  MOp            53            [10, 5, 5, 6, 2, 3, 7, 6, 4, 5]               0   0.502     0.375
L1 E1   b VISp          1035   [8, 41, 635, 243, 23, 6, 22, 22, 15, 20]             100       0 6.09e-249
L1 E1   b  MOp            55            [10, 6, 5, 6, 2, 3, 7, 6, 4, 6]               0   0.525     0.283
L1 E2   a VISp          1079   [9, 45, 644, 244, 24, 8, 25, 27, 23, 30]             100       0 9.15e-233
L1 E2   a  MOp            73           [11, 7, 6, 9, 3, 6, 9, 8, 4, 10]               0   0.511     0.623
L1 E2   b VISp          1084   [9, 45, 649, 244, 24, 8, 25, 27, 23, 30]             100       0  1.4e-234
L1 E2   b  MOp            76          [11, 8, 6, 10, 3, 6, 9, 8, 4, 11]               0   0.413     0.549
L2 E1   a VISp          1054  [8, 48, 627, 261, 14, 12, 24, 30, 14, 16]             100       0  8.1e-248
L2 E1   a  MOp            53            [10, 5, 5, 6, 2, 3, 7, 6, 4, 5]               0   0.502     0.375
L2 E1   b VISp          1059  [8, 48, 629, 264, 14, 12, 24, 30, 14, 16]             100       0  1.3e-249
L2 E1   b  MOp            55            [10, 6, 5, 6, 2, 3, 7, 6, 4, 6]               0   0.525     0.283
L2 E2   a VISp          1110 [10, 52, 651, 261, 15, 11, 27, 37, 21, 25]             100       0 1.71e-239
L2 E2   a  MOp            73           [11, 7, 6, 9, 3, 6, 9, 8, 4, 10]               0   0.511     0.623
L2 E2   b VISp          1119 [10, 52, 657, 264, 15, 11, 27, 37, 21, 25]             100       0 1.16e-242
L2 E2   b  MOp            76          [11, 8, 6, 10, 3, 6, 9, 8, 4, 11]               0   0.413     0.549

Result: reproduced. In every combination VISp events cluster 100 to 150 ms after stimulus offset (the largest bin; KS p 9.2e-233 or less, chi-square p printed as 0), while MOp events do not differ from uniform (chi-square p 0.41 to 0.53, KS p 0.28 to 0.62).

figs6-replay¶

Activity during post-stimulus VISp alpha events is weakly but significantly correlated with activity during the preceding stimulus (r = 0.77, p = 1.8e-6), as if events replay recent stimulation.

Per VISp unit the mean rate evoked by each of the session's four image stimuli and the mean rate during the qualifying events that follow each one; each unit's four values divided by their maximum (a standard choice for "rows normalised"); per session the average over units; Pearson correlation over sessions, four points per session. Sessions enter when each of the four images precedes at least one qualifying event. The criterion assigns each event to the last presentation before it (code cell 12), which also takes events that start during an image (time since offset below 0). The S6 legend and the Results speak of post-stimulus events ("alpha burst events following stimulus presentation", "post-stimulus alpha events"), so a variant keeps only events 0 to 0.5 s after the offset (post_offset_only). Events that follow an omitted flash are assigned to omitted, which is not one of the four images, and drop out.

In [29]:
rows = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            for ew in (0.25, 0.5):
                for vw, post in ((1.0, False), (1.25, False), (1.0, True), (1.25, True)):
                    xs, ys, ux, uy, nsess, nev = [], [], [], [], 0, 0
                    for sid, v in SP.items():
                        if sid not in SETS[S]:
                            continue
                        e = v["alpha"].get((L, "VISp", E))
                        if e is None or "replay" not in e:
                            continue
                        rp = e["replay"]
                        prev = np.array(rp["event_prev_image"], dtype=object)
                        so = rp["event_since_offset"]
                        keep = (so >= 0) & (so < 0.5) if post else np.ones(len(prev), bool)
                        imgs = rp["images"]
                        if not all(((prev == im) & keep).any() for im in imgs):
                            continue
                        evk = rp["evoked"][ew]
                        evr = np.stack([rp["event_rates"][vw][:, (prev == im) & keep].mean(1) for im in imgs], 1)
                        with np.errstate(all="ignore"):
                            a = evk / evk.max(1, keepdims=True)
                            b = evr / evr.max(1, keepdims=True)
                        ok = np.isfinite(a).all(1) & np.isfinite(b).all(1)
                        if ok.sum() == 0:
                            continue
                        xs += list(a[ok].mean(0)); ys += list(b[ok].mean(0)); ux += list(a[ok].ravel()); uy += list(b[ok].ravel())
                        nsess += 1; nev += int(sum(((prev == im) & keep).sum() for im in imgs))
                    r, p = stats.pearsonr(xs, ys) if len(xs) > 3 else (np.nan, np.nan)
                    ru, pu = stats.pearsonr(ux, uy) if len(ux) > 3 else (np.nan, np.nan)
                    rows.append({"L": L, "E": E, "set": S, "evoked_s": ew, "event_s": vw, "post_offset_only": post, "sessions": nsess, "events": nev, "points": len(xs),
                                 "r": r, "p": p, "per_unit_r": ru, "per_unit_points": len(ux), "per_unit_p": pu})
rep = pd.DataFrame(rows)
print(rep.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
rep["holds"] = (rep["r"] >= 0.693) & (rep["r"] <= 0.847) & (rep["p"] < 0.05)
for post, g in rep.groupby("post_offset_only"):
    print(f"{'events 0 to 0.5 s after the offset only' if post else 'all events'}: r {g['r'].min():.3f} to {g['r'].max():.3f}; "
          f"p < 0.05 in {int((g['p'] < 0.05).sum())} of {len(g)}; within 0.77 +- 0.077 with p < 0.05 in {int(g['holds'].sum())}")
so_all = np.concatenate([v["alpha"][("L2", "VISp", "E2")]["replay"]["event_since_offset"] for v in SP.values()
                         if "replay" in v["alpha"].get(("L2", "VISp", "E2"), {})])
print("L2 E2 qualifying VISp events by time since the last active presentation's offset (s):",
      dict(zip(["< -0.25", "-0.25 to 0 (during the image)", "0 to 0.5", "0.5 to 5", "over 5 (after the active block)"],
               np.histogram(so_all, [-np.inf, -0.25, 0, 0.5, 5, np.inf])[0].tolist())))
 L  E set  evoked_s  event_s  post_offset_only  sessions  events  points     r        p  per_unit_r  per_unit_points  per_unit_p
L1 E1   a      0.25        1             False         7    1429      28  0.39   0.0402       0.546             1464   2.05e-114
L1 E1   a      0.25     1.25             False         7    1429      28 0.633 0.000302         0.6             1464   4.22e-144
L1 E1   a      0.25        1              True         7     990      28 0.335   0.0812       0.547             1452   3.18e-114
L1 E1   a      0.25     1.25              True         7     990      28 0.527  0.00396       0.608             1464   1.56e-148
L1 E1   a       0.5        1             False         7    1429      28 0.366   0.0552       0.627             1464   8.74e-161
L1 E1   a       0.5     1.25             False         7    1429      28 0.599 0.000762       0.674             1464   1.92e-194
L1 E1   a       0.5        1              True         7     990      28 0.326   0.0905       0.611             1452   4.91e-149
L1 E1   a       0.5     1.25              True         7     990      28 0.498  0.00695       0.655             1464   6.46e-180
L1 E1   b      0.25        1             False         7    1429      28  0.39   0.0402       0.546             1464   2.05e-114
L1 E1   b      0.25     1.25             False         7    1429      28 0.633 0.000302         0.6             1464   4.22e-144
L1 E1   b      0.25        1              True         7     990      28 0.335   0.0812       0.547             1452   3.18e-114
L1 E1   b      0.25     1.25              True         7     990      28 0.527  0.00396       0.608             1464   1.56e-148
L1 E1   b       0.5        1             False         7    1429      28 0.366   0.0552       0.627             1464   8.74e-161
L1 E1   b       0.5     1.25             False         7    1429      28 0.599 0.000762       0.674             1464   1.92e-194
L1 E1   b       0.5        1              True         7     990      28 0.326   0.0905       0.611             1452   4.91e-149
L1 E1   b       0.5     1.25              True         7     990      28 0.498  0.00695       0.655             1464   6.46e-180
L1 E2   a      0.25        1             False         7    1578      28 0.274    0.159       0.543             1464   3.81e-113
L1 E2   a      0.25     1.25             False         7    1578      28 0.526  0.00403        0.59             1464   2.76e-138
L1 E2   a      0.25        1              True         7    1027      28 0.254    0.193       0.537             1460    5.6e-110
L1 E2   a      0.25     1.25              True         7    1027      28 0.449   0.0165       0.595             1464   5.28e-141
L1 E2   a       0.5        1             False         7    1578      28 0.275    0.156       0.624             1464    1.6e-158
L1 E2   a       0.5     1.25             False         7    1578      28  0.51  0.00555       0.667             1464   7.41e-189
L1 E2   a       0.5        1              True         7    1027      28 0.274    0.159         0.6             1460   1.32e-143
L1 E2   a       0.5     1.25              True         7    1027      28 0.442   0.0185       0.643             1464   1.46e-171
L1 E2   b      0.25        1             False         7    1578      28 0.274    0.159       0.543             1464   3.81e-113
L1 E2   b      0.25     1.25             False         7    1578      28 0.526  0.00403        0.59             1464   2.76e-138
L1 E2   b      0.25        1              True         7    1027      28 0.254    0.193       0.537             1460    5.6e-110
L1 E2   b      0.25     1.25              True         7    1027      28 0.449   0.0165       0.595             1464   5.28e-141
L1 E2   b       0.5        1             False         7    1578      28 0.275    0.156       0.624             1464    1.6e-158
L1 E2   b       0.5     1.25             False         7    1578      28  0.51  0.00555       0.667             1464   7.41e-189
L1 E2   b       0.5        1              True         7    1027      28 0.274    0.159         0.6             1460   1.32e-143
L1 E2   b       0.5     1.25              True         7    1027      28 0.442   0.0185       0.643             1464   1.46e-171
L2 E1   a      0.25        1             False         8    1396      32 0.308   0.0861       0.463             1960   1.72e-104
L2 E1   a      0.25     1.25             False         8    1396      32 0.473  0.00626       0.508             1964   2.65e-129
L2 E1   a      0.25        1              True         7    1023      28 0.292    0.131       0.533             1716   2.75e-126
L2 E1   a      0.25     1.25              True         7    1023      28 0.461   0.0137       0.588             1720   2.26e-160
L2 E1   a       0.5        1             False         8    1396      32 0.275    0.128       0.537             1960   1.29e-146
L2 E1   a       0.5     1.25             False         8    1396      32 0.431   0.0139       0.578             1964   8.98e-176
L2 E1   a       0.5        1              True         7    1023      28 0.302    0.118       0.593             1716   3.44e-163
L2 E1   a       0.5     1.25              True         7    1023      28 0.465   0.0126       0.634             1720   7.08e-194
L2 E1   b      0.25        1             False         8    1396      32 0.308   0.0861       0.463             1960   1.72e-104
L2 E1   b      0.25     1.25             False         8    1396      32 0.473  0.00626       0.508             1964   2.65e-129
L2 E1   b      0.25        1              True         7    1023      28 0.292    0.131       0.533             1716   2.75e-126
L2 E1   b      0.25     1.25              True         7    1023      28 0.461   0.0137       0.588             1720   2.26e-160
L2 E1   b       0.5        1             False         8    1396      32 0.275    0.128       0.537             1960   1.29e-146
L2 E1   b       0.5     1.25             False         8    1396      32 0.431   0.0139       0.578             1964   8.98e-176
L2 E1   b       0.5        1              True         7    1023      28 0.302    0.118       0.593             1716   3.44e-163
L2 E1   b       0.5     1.25              True         7    1023      28 0.465   0.0126       0.634             1720   7.08e-194
L2 E2   a      0.25        1             False         8    1549      32 0.233      0.2       0.493             1964   7.49e-121
L2 E2   a      0.25     1.25             False         8    1549      32 0.389   0.0277       0.532             1964   4.82e-144
L2 E2   a      0.25        1              True         7    1077      28 0.193    0.326       0.524             1716   1.26e-121
L2 E2   a      0.25     1.25              True         7    1077      28 0.368   0.0539       0.582             1720   1.54e-156
L2 E2   a       0.5        1             False         8    1549      32 0.208    0.253       0.566             1964   8.42e-167
L2 E2   a       0.5     1.25             False         8    1549      32 0.358   0.0442       0.604             1964   1.78e-195
L2 E2   a       0.5        1              True         7    1077      28 0.215    0.272       0.582             1716   7.89e-156
L2 E2   a       0.5     1.25              True         7    1077      28 0.384   0.0434       0.625             1720   9.79e-187
L2 E2   b      0.25        1             False         8    1549      32 0.233      0.2       0.493             1964   7.49e-121
L2 E2   b      0.25     1.25             False         8    1549      32 0.389   0.0277       0.532             1964   4.82e-144
L2 E2   b      0.25        1              True         7    1077      28 0.193    0.326       0.524             1716   1.26e-121
L2 E2   b      0.25     1.25              True         7    1077      28 0.368   0.0539       0.582             1720   1.54e-156
L2 E2   b       0.5        1             False         8    1549      32 0.208    0.253       0.566             1964   8.42e-167
L2 E2   b       0.5     1.25             False         8    1549      32 0.358   0.0442       0.604             1964   1.78e-195
L2 E2   b       0.5        1              True         7    1077      28 0.215    0.272       0.582             1716   7.89e-156
L2 E2   b       0.5     1.25              True         7    1077      28 0.384   0.0434       0.625             1720   9.79e-187
all events: r 0.208 to 0.633; p < 0.05 in 18 of 32; within 0.77 +- 0.077 with p < 0.05 in 0
events 0 to 0.5 s after the offset only: r 0.193 to 0.527; p < 0.05 in 14 of 32; within 0.77 +- 0.077 with p < 0.05 in 0
L2 E2 qualifying VISp events by time since the last active presentation's offset (s): {'< -0.25': 0, '-0.25 to 0 (during the image)': 473, '0 to 0.5': 1119, '0.5 to 5': 0, 'over 5 (after the active block)': 64}

Result: not reproduced. The correlation across sessions of evoked and event responses is r = 0.21 to 0.63 over the 32 combinations (7 or 8 sessions, 28 or 32 points; 7 sessions, as in the paper, under L1), below 0.693 in all of them; it is significant (p < 0.05) in 18 of 32. Keeping only events 0 to 0.5 s after the offset (the post-stimulus events of the S6 legend; 1119 of the 1656 L2 E2 events over all 99 sessions, 473 starting during an image and 64 after the active block) gives r = 0.19 to 0.53, significant in 14 of 32, none within tolerance. The per-unit correlation is r = 0.46 to 0.67 (0.52 to 0.66 with post-offset events only).

In [30]:
def lookup(t, names):
    """Rows of a pair table for an area given as one or more labels (subdivisions)."""
    return t[t["area"].isin(names)] if len(t) else t

def area_state(t, names, mode="any"):
    r = lookup(t, names)
    if not len(r):
        return "absent"
    s = r["sig_pos"].all() if mode == "all" else r["sig_pos"].any()
    return "sig" if s else "ns"

def show_pairs(ref, areas):
    out = []
    for (L, E, R, S) in COMBOS:
        t = PAIRS[(ref, L, E, R, S)]
        row = {"combo": f"{L} {E} {R} {S}"}
        for a, names in areas.items():
            r = lookup(t, names)
            row[a] = ("; ".join(f"{x.area} {x.mean_diff:+.3f} q={x.p_bh:.2g}{'*' if x.sig_pos else ''}" for x in r.itertuples())
                      if len(r) else "absent")
        out.append(row)
    return pd.DataFrame(out)

fig7cd-visp-coupling¶

During VISp events, VISp is tightly coupled to LP and VISl (sharp CCG peaks near zero lag and a 3 to 7 Hz coherence peak), but not to PO or SSp.

Each cell: the region, the mean of event minus random band coherence, the Benjamini-Hochberg q value, and * when significant and positive. Under L2, PO also takes POST (the code's substring match).

In [31]:
A = {"LP": ["LP"], "VISl": ["VISl", "VISlm"], "PO": ["PO"], "SSp": ["SSp"]}
print(show_pairs("VISp", A).to_string(index=False))
rows = []
for (L, E, R, S) in COMBOS:
    t = PAIRS[("VISp", L, E, R, S)]
    st = {a: area_state(t, n) for a, n in A.items()}
    coh_ok = st["LP"] == "sig" and st["VISl"] == "sig" and st["PO"] != "sig" and st["SSp"] != "sig"
    untested = ", ".join(a for a in A if st[a] == "absent") or "none"
    c = {a: lookup(t, n) for a, n in A.items()}
    pk = {a: (c[a]["ccg_peak"].max() if len(c[a]) else np.nan) for a in A}
    lg = {a: (c[a].sort_values("ccg_peak")["ccg_peak_lag_ms"].iloc[-1] if len(c[a]) else np.nan) for a in A}
    for w in (20, 50):
        near = all(abs(lg[a]) <= w for a in ("LP", "VISl") if np.isfinite(lg[a]))
        higher = min(pk["LP"], pk["VISl"]) > np.nanmax([pk["PO"], pk["SSp"]]) if np.isfinite([pk["PO"], pk["SSp"]]).any() else np.nan
        rows.append({"combo": f"{L} {E} {R} {S}", "ccg_window_ms": w, "coherence_part": coh_ok, "untested_parts": untested,
                     **{f"peak_{a}": round(pk[a], 3) for a in A}, **{f"lag_{a}": lg[a] for a in A},
                     "ccg_near_zero": near, "ccg_higher": higher})
v7 = pd.DataFrame(rows)
print(v7.to_string(index=False))
print("combinations where the coherence part holds:", v7.loc[v7["coherence_part"], "combo"].unique().tolist())
     combo                    LP                                            VISl                PO                   SSp
L1 E1 R1 a    LP +0.068 q=1e-20* VISl +0.158 q=3.6e-10*; VISlm +0.130 q=1.8e-08*  PO +0.005 q=0.33    SSp +0.005 q=0.097
L1 E1 R1 b    LP +0.068 q=1e-20* VISl +0.158 q=3.6e-10*; VISlm +0.130 q=1.8e-08*  PO +0.005 q=0.33    SSp +0.005 q=0.097
L1 E1 R2 a LP +0.266 q=3.6e-122*   VISl +0.375 q=4e-17*; VISlm +0.306 q=9.4e-17* PO +0.007 q=0.098 SSp +0.011 q=2.5e-05*
L1 E1 R2 b LP +0.266 q=3.6e-122*   VISl +0.375 q=4e-17*; VISlm +0.306 q=9.4e-17* PO +0.007 q=0.098 SSp +0.011 q=2.5e-05*
L1 E2 R1 a  LP +0.064 q=5.8e-19* VISl +0.151 q=4.5e-11*; VISlm +0.123 q=1.6e-08*  PO +0.005 q=0.29     SSp +0.004 q=0.23
L1 E2 R1 b  LP +0.064 q=5.8e-19* VISl +0.151 q=4.5e-11*; VISlm +0.123 q=1.6e-08*  PO +0.005 q=0.29     SSp +0.004 q=0.23
L1 E2 R2 a LP +0.264 q=2.1e-134* VISl +0.346 q=9.6e-19*; VISlm +0.294 q=1.1e-20*  PO +0.000 q=0.96  SSp +0.008 q=0.0032*
L1 E2 R2 b LP +0.264 q=2.1e-134* VISl +0.346 q=9.6e-19*; VISlm +0.294 q=1.1e-20*  PO +0.000 q=0.96  SSp +0.008 q=0.0032*
L2 E1 R1 a  LP +0.091 q=3.6e-27*                            VISl +0.124 q=2e-10*  PO -0.004 q=0.68     SSp +0.003 q=0.68
L2 E1 R1 b  LP +0.091 q=3.6e-27*                            VISl +0.124 q=2e-10*  PO -0.004 q=0.68     SSp +0.003 q=0.68
L2 E1 R2 a LP +0.305 q=1.1e-110*                          VISl +0.343 q=2.1e-24*  PO +0.005 q=0.27    SSp +0.006 q=0.075
L2 E1 R2 b LP +0.305 q=1.1e-110*                          VISl +0.343 q=2.1e-24*  PO +0.005 q=0.27    SSp +0.006 q=0.075
L2 E2 R1 a  LP +0.086 q=5.2e-29*                          VISl +0.137 q=3.7e-13*  PO +0.003 q=0.47      SSp +0.004 q=0.3
L2 E2 R1 b  LP +0.086 q=5.4e-29*                          VISl +0.137 q=3.9e-13*  PO +0.003 q=0.49     SSp +0.004 q=0.31
L2 E2 R2 a LP +0.302 q=5.5e-121*                          VISl +0.347 q=6.5e-29* PO +0.011 q=0.051   SSp +0.007 q=0.048*
L2 E2 R2 b LP +0.302 q=5.6e-121*                          VISl +0.347 q=6.7e-29* PO +0.011 q=0.051   SSp +0.007 q=0.036*
     combo  ccg_window_ms  coherence_part untested_parts  peak_LP  peak_VISl  peak_PO  peak_SSp  lag_LP  lag_VISl  lag_PO  lag_SSp  ccg_near_zero  ccg_higher
L1 E1 R1 a             20            True           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R1 a             50            True           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R1 b             20            True           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R1 b             50            True           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R2 a             20           False           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R2 a             50           False           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R2 b             20           False           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E1 R2 b             50           False           none    0.340      0.484    0.058     0.055       0         0      30        0           True        True
L1 E2 R1 a             20            True           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R1 a             50            True           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R1 b             20            True           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R1 b             50            True           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R2 a             20           False           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R2 a             50           False           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R2 b             20           False           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L1 E2 R2 b             50           False           none    0.335      0.477    0.056     0.055       0         0      20       20           True        True
L2 E1 R1 a             20            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R1 a             50            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R1 b             20            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R1 b             50            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R2 a             20            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R2 a             50            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R2 b             20            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E1 R2 b             50            True           none    0.384      0.508    0.092     0.062       0         0      20        0           True        True
L2 E2 R1 a             20            True           none    0.378      0.498    0.092     0.061       0         0      20        0           True        True
L2 E2 R1 a             50            True           none    0.378      0.498    0.092     0.061       0         0      20        0           True        True
L2 E2 R1 b             20            True           none    0.378      0.498    0.092     0.060       0         0      20        0           True        True
L2 E2 R1 b             50            True           none    0.378      0.498    0.092     0.060       0         0      20        0           True        True
L2 E2 R2 a             20           False           none    0.378      0.498    0.092     0.061       0         0      20        0           True        True
L2 E2 R2 a             50           False           none    0.378      0.498    0.092     0.061       0         0      20        0           True        True
L2 E2 R2 b             20           False           none    0.378      0.498    0.092     0.060       0         0      20        0           True        True
L2 E2 R2 b             50           False           none    0.378      0.498    0.092     0.060       0         0      20        0           True        True
combinations where the coherence part holds: ['L1 E1 R1 a', 'L1 E1 R1 b', 'L1 E2 R1 a', 'L1 E2 R1 b', 'L2 E1 R1 a', 'L2 E1 R1 b', 'L2 E1 R2 a', 'L2 E1 R2 b', 'L2 E2 R1 a', 'L2 E2 R1 b']

Result: blocked (methods underspecified). The CCG part holds in every combination (LP and VISl peak at 0 to 20 ms lag, peaks 0.335 to 0.508, above PO and SSp at 0.055 to 0.092). The coherence part holds under R1 in all 8 combinations (LP and VISl significant and positive; PO and SSp not) but under R2 only with L2 E1: with L1, SSp is also significant (mean +0.008 to +0.011, q 0.0032 and 2.5e-5), and with L2 E2 SSp is (+0.007, q 0.048 and 0.036; PO +0.011, q 0.051, is just above 0.05). Every part is tested in every combination. The readings disagree, so by the claims file's rule the claim is methods underspecified.

fig7cd-mop-coupling¶

During MOp events, MOp is coupled to PO and SSp but not to LP or VISl.

In [32]:
A2 = {"PO": ["PO"], "SSp": ["SSp"], "LP": ["LP"], "VISl": ["VISl", "VISlm"]}
print(show_pairs("MOp", A2).to_string(index=False))
rows = []
for (L, E, R, S) in COMBOS:
    t = PAIRS[("MOp", L, E, R, S)]
    st = {a: area_state(t, n) for a, n in A2.items()}
    c = {a: lookup(t, n) for a, n in A2.items()}
    pk = {a: (c[a]["ccg_peak"].max() if len(c[a]) else np.nan) for a in A2}
    lg = {a: (c[a].sort_values("ccg_peak")["ccg_peak_lag_ms"].iloc[-1] if len(c[a]) else np.nan) for a in A2}
    rows.append({"combo": f"{L} {E} {R} {S}", **st,
                 "coherence_part": st["PO"] == "sig" and st["SSp"] == "sig" and st["LP"] != "sig" and st["VISl"] != "sig",
                 "untested_parts": ", ".join(a for a in A2 if st[a] == "absent") or "none",
                 "contradicted": st["PO"] == "ns" or st["SSp"] == "ns" or st["LP"] == "sig" or st["VISl"] == "sig",
                 **{f"peak_{a}": round(pk[a], 3) for a in A2}, **{f"lag_{a}": lg[a] for a in A2}})
print(pd.DataFrame(rows).to_string(index=False))
m7 = pd.DataFrame(rows)
print("combinations with every part tested and holding:", m7.loc[m7["coherence_part"] & (m7["untested_parts"] == "none"), "combo"].tolist())
print("combinations where a tested part contradicts the claim:", m7.loc[m7["contradicted"], "combo"].tolist())
     combo                   PO                   SSp               LP               VISl
L1 E1 R1 a               absent SSp +0.181 q=8.8e-07* LP +0.000 q=0.79             absent
L1 E1 R1 b               absent SSp +0.181 q=8.8e-07* LP +0.000 q=0.79             absent
L1 E1 R2 a               absent   SSp +0.259 q=8e-14* LP +0.025 q=0.14             absent
L1 E1 R2 b               absent   SSp +0.259 q=8e-14* LP +0.025 q=0.14             absent
L1 E2 R1 a PO +0.174 q=1.1e-05* SSp +0.190 q=4.3e-09* LP -0.002 q=0.86             absent
L1 E2 R1 b PO +0.174 q=1.1e-05* SSp +0.190 q=4.3e-09* LP -0.002 q=0.86             absent
L1 E2 R2 a PO +0.195 q=5.3e-06*   SSp +0.250 q=1e-11* LP -0.006 q=0.77             absent
L1 E2 R2 b PO +0.195 q=5.3e-06*   SSp +0.250 q=1e-11* LP -0.006 q=0.77             absent
L2 E1 R1 a               absent SSp +0.173 q=2.7e-10*  LP +0.026 q=0.2             absent
L2 E1 R1 b               absent SSp +0.173 q=2.7e-10*  LP +0.026 q=0.2             absent
L2 E1 R2 a               absent SSp +0.246 q=8.6e-12* LP +0.018 q=0.49             absent
L2 E1 R2 b               absent SSp +0.246 q=8.6e-12* LP +0.018 q=0.49             absent
L2 E2 R1 a  PO +0.147 q=0.0015* SSp +0.187 q=5.4e-11* LP +0.001 q=0.89 VISl +0.005 q=0.89
L2 E2 R1 b  PO +0.147 q=0.0015* SSp +0.187 q=5.4e-11* LP +0.001 q=0.89 VISl +0.005 q=0.89
L2 E2 R2 a PO +0.169 q=1.6e-05* SSp +0.247 q=2.2e-11* LP -0.009 q=0.56 VISl +0.027 q=0.34
L2 E2 R2 b PO +0.169 q=1.6e-05* SSp +0.247 q=2.2e-11* LP -0.009 q=0.56 VISl +0.027 q=0.34
     combo     PO SSp LP   VISl  coherence_part untested_parts  contradicted  peak_PO  peak_SSp  peak_LP  peak_VISl  lag_PO  lag_SSp  lag_LP  lag_VISl
L1 E1 R1 a absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.035        NaN     NaN        0     180       NaN
L1 E1 R1 b absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.035        NaN     NaN        0     180       NaN
L1 E1 R2 a absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.035        NaN     NaN        0     180       NaN
L1 E1 R2 b absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.035        NaN     NaN        0     180       NaN
L1 E2 R1 a    sig sig ns absent            True           VISl         False    0.250     0.329    0.029        NaN    20.0        0     210       NaN
L1 E2 R1 b    sig sig ns absent            True           VISl         False    0.250     0.329    0.029        NaN    20.0        0     210       NaN
L1 E2 R2 a    sig sig ns absent            True           VISl         False    0.250     0.329    0.029        NaN    20.0        0     210       NaN
L1 E2 R2 b    sig sig ns absent            True           VISl         False    0.250     0.329    0.029        NaN    20.0        0     210       NaN
L2 E1 R1 a absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.033        NaN     NaN        0    -400       NaN
L2 E1 R1 b absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.033        NaN     NaN        0    -400       NaN
L2 E1 R2 a absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.033        NaN     NaN        0    -400       NaN
L2 E1 R2 b absent sig ns absent           False       PO, VISl         False      NaN     0.325    0.033        NaN     NaN        0    -400       NaN
L2 E2 R1 a    sig sig ns     ns            True           none         False    0.199     0.329    0.037      0.062    20.0        0    -400     320.0
L2 E2 R1 b    sig sig ns     ns            True           none         False    0.199     0.329    0.037      0.062    20.0        0    -400     320.0
L2 E2 R2 a    sig sig ns     ns            True           none         False    0.199     0.329    0.037      0.062    20.0        0    -400     320.0
L2 E2 R2 b    sig sig ns     ns            True           none         False    0.199     0.329    0.037      0.062    20.0        0    -400     320.0
combinations with every part tested and holding: ['L2 E2 R1 a', 'L2 E2 R1 b', 'L2 E2 R2 a', 'L2 E2 R2 b']
combinations where a tested part contradicts the claim: []

Result: blocked, methods underspecified. The criterion's "reproduced" needs MOp-PO to be significantly coherent. Under E1 (8 of the 16 combinations) too few MOp events fall in sessions with PO (fewer than 25), so PO is untested (absent) and coherence_part is False there. Under E2, in all 8 combinations, MOp is significantly coherent with PO (+0.15 to +0.20) and SSp (+0.19 to +0.25) and not with LP, and the CCGs of PO and SSp peak at 0 to 20 ms with heights 0.20 to 0.33 against 0.03 to 0.06 for LP and VISl. VISl is also untested under L1 and under L2 E1, and the table's coherence_part counts an untested VISl as passing, so every part is tested and holds only under L2 E2 (4 combinations; VISl +0.005 and +0.027, q 0.89 and 0.34). The readings therefore disagree, which the claims file's header makes methods underspecified. That no tested part contradicts the claim is not the criterion's test. This outcome was proposed after verification round 2 and agreed in round 3.

fig7e-lags-straddle-zero¶

In the 3 to 7 Hz band, time lags between VISp or MOp and their associated regions straddle zero; neither consistently leads or follows.

In [33]:
rows = []
for (L, E, R, S) in COMBOS:
    for ref in shield.REFERENCE_AREAS:
        t = PAIRS[(ref, L, E, R, S)]
        lagv = {}
        for sid, v in SP.items():
            if sid not in SETS[S]:
                continue
            e = v["alpha"].get((L, ref, E))
            if e is None or "lags" not in e or len(e["coh"]["event"]) <= 5:
                continue
            for a in e["lags"].columns:
                lagv.setdefault(a, []).append(e["lags"][a].values)
        res = []
        for a in shield.LAG_PAIRS[ref]:
            if a not in lagv or not len(lookup(t, [a])) or not lookup(t, [a])["sig_pos"].any():
                continue
            x = np.concatenate(lagv[a]); x = x[np.isfinite(x)]
            res.append({"pair": f"{ref}-{a}", "n": len(x), "median_lag_ms": 1000 * np.median(x),
                        "fraction_positive": np.mean(x > 0), "p": stats.wilcoxon(x).pvalue})
        if res:
            r = pd.DataFrame(res); r["p_bh"] = multipletests(r["p"], method="fdr_bh")[1]
            for x in r.itertuples():
                rows.append({"combo": f"{L} {E} {R} {S}", **x._asdict()})
lagt = pd.DataFrame(rows).drop(columns="Index")
lagt["not_significant"] = lagt["p_bh"] >= 0.05
print(lagt.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
print("pairs not significant (straddle zero under the test):", int(lagt["not_significant"].sum()), "of", len(lagt))
     combo       pair    n  median_lag_ms  fraction_positive        p     p_bh  not_significant
L1 E1 R1 a  VISp-VISl   97          -2.12              0.402    0.102    0.102             True
L1 E1 R1 a VISp-VISal  159           13.6              0.962 1.77e-22 4.43e-22            False
L1 E1 R1 a   VISp-LGd 1054          -5.13              0.267 1.52e-65 7.58e-65            False
L1 E1 R1 a    VISp-LP  760         -0.687               0.48 4.22e-06 5.28e-06            False
L1 E1 R1 a    VISp-CP  438           10.5              0.742  2.8e-21 4.66e-21            False
L1 E1 R1 a    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L1 E1 R1 b  VISp-VISl   97          -2.12              0.402    0.102    0.102             True
L1 E1 R1 b VISp-VISal  159           13.6              0.962 1.77e-22 4.43e-22            False
L1 E1 R1 b   VISp-LGd 1054          -5.13              0.267 1.52e-65 7.58e-65            False
L1 E1 R1 b    VISp-LP  760         -0.687               0.48 4.22e-06 5.28e-06            False
L1 E1 R1 b    VISp-CP  438           10.5              0.742  2.8e-21 4.66e-21            False
L1 E1 R1 b    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L1 E1 R2 a  VISp-VISl   97          -2.12              0.402    0.102    0.102             True
L1 E1 R2 a VISp-VISal  159           13.6              0.962 1.77e-22 4.43e-22            False
L1 E1 R2 a   VISp-LGd 1054          -5.13              0.267 1.52e-65 7.58e-65            False
L1 E1 R2 a    VISp-LP  760         -0.687               0.48 4.22e-06 5.28e-06            False
L1 E1 R2 a    VISp-CP  438           10.5              0.742  2.8e-21 4.66e-21            False
L1 E1 R2 a    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L1 E1 R2 b  VISp-VISl   97          -2.12              0.402    0.102    0.102             True
L1 E1 R2 b VISp-VISal  159           13.6              0.962 1.77e-22 4.43e-22            False
L1 E1 R2 b   VISp-LGd 1054          -5.13              0.267 1.52e-65 7.58e-65            False
L1 E1 R2 b    VISp-LP  760         -0.687               0.48 4.22e-06 5.28e-06            False
L1 E1 R2 b    VISp-CP  438           10.5              0.742  2.8e-21 4.66e-21            False
L1 E1 R2 b    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L1 E2 R1 a  VISp-VISl  107          -2.12              0.411   0.0618   0.0618             True
L1 E2 R1 a VISp-VISal  199           13.5              0.965 5.49e-29  1.1e-28            False
L1 E2 R1 a   VISp-LGd 1143          -5.16               0.27 2.16e-69 8.63e-69            False
L1 E2 R1 a    VISp-LP  834          -0.66              0.483 2.83e-06 3.78e-06            False
L1 E2 R1 a    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L1 E2 R1 a     MOp-PO   25          -23.3               0.12 0.000103 0.000206            False
L1 E2 R1 b  VISp-VISl  107          -2.12              0.411   0.0618   0.0618             True
L1 E2 R1 b VISp-VISal  199           13.5              0.965 5.49e-29  1.1e-28            False
L1 E2 R1 b   VISp-LGd 1143          -5.16               0.27 2.16e-69 8.63e-69            False
L1 E2 R1 b    VISp-LP  834          -0.66              0.483 2.83e-06 3.78e-06            False
L1 E2 R1 b    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L1 E2 R1 b     MOp-PO   25          -23.3               0.12 0.000103 0.000206            False
L1 E2 R2 a  VISp-VISl  107          -2.12              0.411   0.0618   0.0618             True
L1 E2 R2 a VISp-VISal  199           13.5              0.965 5.49e-29 1.37e-28            False
L1 E2 R2 a   VISp-LGd 1143          -5.16               0.27 2.16e-69 1.08e-68            False
L1 E2 R2 a    VISp-LP  834          -0.66              0.483 2.83e-06 3.54e-06            False
L1 E2 R2 a    VISp-CP  493           10.3              0.736 1.52e-23 2.53e-23            False
L1 E2 R2 a    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L1 E2 R2 a     MOp-PO   25          -23.3               0.12 0.000103 0.000206            False
L1 E2 R2 b  VISp-VISl  107          -2.12              0.411   0.0618   0.0618             True
L1 E2 R2 b VISp-VISal  199           13.5              0.965 5.49e-29 1.37e-28            False
L1 E2 R2 b   VISp-LGd 1143          -5.16               0.27 2.16e-69 1.08e-68            False
L1 E2 R2 b    VISp-LP  834          -0.66              0.483 2.83e-06 3.54e-06            False
L1 E2 R2 b    VISp-CP  493           10.3              0.736 1.52e-23 2.53e-23            False
L1 E2 R2 b    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L1 E2 R2 b     MOp-PO   25          -23.3               0.12 0.000103 0.000206            False
L2 E1 R1 a  VISp-VISl  141           1.79              0.596 0.000218 0.000272            False
L2 E1 R1 a VISp-VISal  388           2.11              0.595 7.99e-11 1.33e-10            False
L2 E1 R1 a   VISp-LGd 1025           -4.2              0.314 3.47e-48 1.73e-47            False
L2 E1 R1 a    VISp-LP  678         -0.865              0.485  0.00107  0.00107            False
L2 E1 R1 a    VISp-CP  387           10.6              0.762  5.8e-22 1.45e-21            False
L2 E1 R1 a    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L2 E1 R1 b  VISp-VISl  141           1.79              0.596 0.000218 0.000272            False
L2 E1 R1 b VISp-VISal  388           2.11              0.595 7.99e-11 1.33e-10            False
L2 E1 R1 b   VISp-LGd 1025           -4.2              0.314 3.47e-48 1.73e-47            False
L2 E1 R1 b    VISp-LP  678         -0.865              0.485  0.00107  0.00107            False
L2 E1 R1 b    VISp-CP  387           10.6              0.762  5.8e-22 1.45e-21            False
L2 E1 R1 b    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L2 E1 R2 a  VISp-VISl  141           1.79              0.596 0.000218 0.000272            False
L2 E1 R2 a VISp-VISal  388           2.11              0.595 7.99e-11 1.33e-10            False
L2 E1 R2 a   VISp-LGd 1025           -4.2              0.314 3.47e-48 1.73e-47            False
L2 E1 R2 a    VISp-LP  678         -0.865              0.485  0.00107  0.00107            False
L2 E1 R2 a    VISp-CP  387           10.6              0.762  5.8e-22 1.45e-21            False
L2 E1 R2 a    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L2 E1 R2 b  VISp-VISl  141           1.79              0.596 0.000218 0.000272            False
L2 E1 R2 b VISp-VISal  388           2.11              0.595 7.99e-11 1.33e-10            False
L2 E1 R2 b   VISp-LGd 1025           -4.2              0.314 3.47e-48 1.73e-47            False
L2 E1 R2 b    VISp-LP  678         -0.865              0.485  0.00107  0.00107            False
L2 E1 R2 b    VISp-CP  387           10.6              0.762  5.8e-22 1.45e-21            False
L2 E1 R2 b    MOp-SSp   50          -6.23               0.34  0.00833  0.00833            False
L2 E2 R1 a  VISp-VISl  169            1.8              0.592 0.000128  0.00016            False
L2 E2 R1 a VISp-VISal  457           2.92              0.621 7.28e-16 1.21e-15            False
L2 E2 R1 a   VISp-LGd 1114          -4.08               0.32 2.42e-49 1.21e-48            False
L2 E2 R1 a    VISp-LP  740         -0.168              0.497   0.0103   0.0103            False
L2 E2 R1 a    VISp-CP  439           10.5              0.754 3.01e-24 7.52e-24            False
L2 E2 R1 a    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L2 E2 R1 a     MOp-PO   25          -19.1               0.12 0.000217 0.000434            False
L2 E2 R1 b  VISp-VISl  169            1.8              0.592 0.000128  0.00016            False
L2 E2 R1 b VISp-VISal  464           2.98              0.625 1.59e-16 2.65e-16            False
L2 E2 R1 b   VISp-LGd 1114          -4.08               0.32 2.42e-49 1.21e-48            False
L2 E2 R1 b    VISp-LP  740         -0.168              0.497   0.0103   0.0103            False
L2 E2 R1 b    VISp-CP  439           10.5              0.754 3.01e-24 7.52e-24            False
L2 E2 R1 b    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L2 E2 R1 b     MOp-PO   25          -19.1               0.12 0.000217 0.000434            False
L2 E2 R2 a  VISp-VISl  169            1.8              0.592 0.000128  0.00016            False
L2 E2 R2 a VISp-VISal  457           2.92              0.621 7.28e-16 1.21e-15            False
L2 E2 R2 a   VISp-LGd 1114          -4.08               0.32 2.42e-49 1.21e-48            False
L2 E2 R2 a    VISp-LP  740         -0.168              0.497   0.0103   0.0103            False
L2 E2 R2 a    VISp-CP  439           10.5              0.754 3.01e-24 7.52e-24            False
L2 E2 R2 a    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L2 E2 R2 a     MOp-PO   25          -19.1               0.12 0.000217 0.000434            False
L2 E2 R2 b  VISp-VISl  169            1.8              0.592 0.000128  0.00016            False
L2 E2 R2 b VISp-VISal  464           2.98              0.625 1.59e-16 2.65e-16            False
L2 E2 R2 b   VISp-LGd 1114          -4.08               0.32 2.42e-49 1.21e-48            False
L2 E2 R2 b    VISp-LP  740         -0.168              0.497   0.0103   0.0103            False
L2 E2 R2 b    VISp-CP  439           10.5              0.754 3.01e-24 7.52e-24            False
L2 E2 R2 b    MOp-SSp   69          -5.86              0.319  0.00236  0.00236            False
L2 E2 R2 b     MOp-PO   25          -19.1               0.12 0.000217 0.000434            False
pairs not significant (straddle zero under the test): 8 of 102

Result: not reproduced. On the code's test (Wilcoxon against zero, Benjamini-Hochberg) the lags differ from zero for most pairs in every combination: 8 of 102 pair results are not significant (VISp to VISl under L1 only). Median lags are small (-23 to +14 ms), and the fraction of positive lags ranges from 0.12 (MOp to PO) to 0.97 (VISp to VISal, L1). Whether the claim means that the median lag does not differ from zero (the criterion) or that the distribution of lags spans zero is asked of a reader (questions/001051.yaml).

fig7f-visp-network¶

Beyond visual cortex and thalamus, VISp alpha events enhance coherence of VISp with ACA, RSP and CP.

In [34]:
A3 = {"ACA": ["ACA", "ACAd", "ACAv"], "RSP": ["RSP", "RSPd", "RSPv", "RSPagl"], "CP": ["CP"], "LGd": ["LGd"],
      "LP": ["LP"], "VISl": ["VISl", "VISlm"]}
print(show_pairs("VISp", A3).to_string(index=False))
rows = []
for (L, E, R, S) in COMBOS:
    t = PAIRS[("VISp", L, E, R, S)]
    for mode in ("any", "all"):
        st = {a: area_state(t, n, mode) for a, n in A3.items()}
        rows.append({"combo": f"{L} {E} {R} {S}", "subdivisions": mode, **st, "holds": all(s == "sig" for s in st.values())})
f7v = pd.DataFrame(rows)
print(f7v.to_string(index=False))
print("combinations where all six are significant and positive:", int(f7v["holds"].sum()), "of", len(f7v))
print("\nall VISp pairs, L1 E2 R2 a:")
print(PAIRS[("VISp", "L1", "E2", "R2", "a")].round(4).to_string(index=False))
     combo                                            ACA                                                                      RSP                   CP                    LGd                    LP                                            VISl
L1 E1 R1 a    ACAd +0.028 q=0.00032*; ACAv +0.013 q=0.091                           RSPagl +0.040 q=0.002*; RSPv +0.062 q=8.8e-10*  CP +0.021 q=0.0097*  LGd +0.104 q=2.2e-55*    LP +0.068 q=1e-20* VISl +0.158 q=3.6e-10*; VISlm +0.130 q=1.8e-08*
L1 E1 R1 b    ACAd +0.028 q=0.00032*; ACAv +0.013 q=0.091                           RSPagl +0.040 q=0.002*; RSPv +0.062 q=8.8e-10*  CP +0.021 q=0.0097*  LGd +0.104 q=2.2e-55*    LP +0.068 q=1e-20* VISl +0.158 q=3.6e-10*; VISlm +0.130 q=1.8e-08*
L1 E1 R2 a ACAd +0.071 q=4.8e-22*; ACAv +0.036 q=4.7e-12*                         RSPagl +0.044 q=0.00023*; RSPv +0.137 q=1.5e-32* CP +0.107 q=4.5e-40* LGd +0.347 q=5.5e-172* LP +0.266 q=3.6e-122*   VISl +0.375 q=4e-17*; VISlm +0.306 q=9.4e-17*
L1 E1 R2 b ACAd +0.071 q=4.8e-22*; ACAv +0.036 q=4.7e-12*                         RSPagl +0.044 q=0.00023*; RSPv +0.137 q=1.5e-32* CP +0.107 q=4.5e-40* LGd +0.347 q=5.5e-172* LP +0.266 q=3.6e-122*   VISl +0.375 q=4e-17*; VISlm +0.306 q=9.4e-17*
L1 E2 R1 a   ACAd +0.021 q=0.0027*; ACAv +0.017 q=0.0013*     RSPagl +0.045 q=0.0004*; RSPd +0.074 q=0.057; RSPv +0.065 q=2.3e-11*      CP +0.014 q=0.1  LGd +0.104 q=3.4e-63*  LP +0.064 q=5.8e-19* VISl +0.151 q=4.5e-11*; VISlm +0.123 q=1.6e-08*
L1 E2 R1 b   ACAd +0.021 q=0.0027*; ACAv +0.017 q=0.0013*     RSPagl +0.045 q=0.0004*; RSPd +0.074 q=0.057; RSPv +0.065 q=2.3e-11*      CP +0.014 q=0.1  LGd +0.104 q=3.4e-63*  LP +0.064 q=5.8e-19* VISl +0.151 q=4.5e-11*; VISlm +0.123 q=1.6e-08*
L1 E2 R2 a ACAd +0.054 q=6.5e-16*; ACAv +0.034 q=6.8e-13* RSPagl +0.061 q=2.6e-06*; RSPd +0.113 q=0.00071*; RSPv +0.133 q=3.2e-34* CP +0.103 q=2.3e-42* LGd +0.343 q=1.2e-186* LP +0.264 q=2.1e-134* VISl +0.346 q=9.6e-19*; VISlm +0.294 q=1.1e-20*
L1 E2 R2 b ACAd +0.054 q=6.5e-16*; ACAv +0.034 q=6.8e-13* RSPagl +0.061 q=2.6e-06*; RSPd +0.113 q=0.00071*; RSPv +0.133 q=3.2e-34* CP +0.103 q=2.3e-42* LGd +0.343 q=1.2e-186* LP +0.264 q=2.1e-134* VISl +0.346 q=9.6e-19*; VISlm +0.294 q=1.1e-20*
L2 E1 R1 a  ACAd +0.047 q=2.7e-06*; ACAv +0.017 q=0.0049*                             RSPagl +0.025 q=0.12; RSPv +0.079 q=4.8e-13*  CP +0.028 q=0.0049*  LGd +0.109 q=3.8e-58*  LP +0.091 q=3.6e-27*                            VISl +0.124 q=2e-10*
L2 E1 R1 b  ACAd +0.047 q=2.7e-06*; ACAv +0.017 q=0.0049*                             RSPagl +0.025 q=0.12; RSPv +0.079 q=4.8e-13*  CP +0.028 q=0.0049*  LGd +0.109 q=3.8e-58*  LP +0.091 q=3.6e-27*                            VISl +0.124 q=2e-10*
L2 E1 R2 a ACAd +0.108 q=1.6e-24*; ACAv +0.037 q=1.2e-13*                         RSPagl +0.057 q=1.2e-05*; RSPv +0.150 q=1.9e-34* CP +0.109 q=3.1e-37* LGd +0.353 q=1.7e-167* LP +0.305 q=1.1e-110*                          VISl +0.343 q=2.1e-24*
L2 E1 R2 b ACAd +0.108 q=1.6e-24*; ACAv +0.037 q=1.2e-13*                         RSPagl +0.057 q=1.2e-05*; RSPv +0.150 q=1.9e-34* CP +0.109 q=3.1e-37* LGd +0.353 q=1.7e-167* LP +0.305 q=1.1e-110*                          VISl +0.343 q=2.1e-24*
L2 E2 R1 a ACAd +0.045 q=9.5e-07*; ACAv +0.018 q=2.4e-05*                            RSPagl +0.025 q=0.093; RSPv +0.073 q=1.1e-13* CP +0.030 q=0.00066*  LGd +0.107 q=2.6e-66*  LP +0.086 q=5.2e-29*                          VISl +0.137 q=3.7e-13*
L2 E2 R1 b ACAd +0.045 q=9.9e-07*; ACAv +0.018 q=2.4e-05*       RSPagl +0.032 q=0.022*; RSPd +0.038 q=0.34; RSPv +0.073 q=1.1e-13* CP +0.030 q=0.00068*  LGd +0.107 q=2.6e-66*  LP +0.086 q=5.4e-29*                          VISl +0.137 q=3.9e-13*
L2 E2 R2 a ACAd +0.107 q=2.4e-25*; ACAv +0.039 q=3.3e-16*                           RSPagl +0.060 q=2e-07*; RSPv +0.143 q=9.6e-39* CP +0.107 q=1.2e-37* LGd +0.352 q=3.6e-182* LP +0.302 q=5.5e-121*                          VISl +0.347 q=6.5e-29*
L2 E2 R2 b ACAd +0.107 q=2.4e-25*; ACAv +0.039 q=3.4e-16* RSPagl +0.063 q=5.9e-08*; RSPd +0.092 q=0.00051*; RSPv +0.143 q=9.9e-39* CP +0.107 q=1.3e-37* LGd +0.352 q=3.7e-182* LP +0.302 q=5.6e-121*                          VISl +0.347 q=6.7e-29*
     combo subdivisions ACA RSP  CP LGd  LP VISl  holds
L1 E1 R1 a          any sig sig sig sig sig  sig   True
L1 E1 R1 a          all  ns sig sig sig sig  sig  False
L1 E1 R1 b          any sig sig sig sig sig  sig   True
L1 E1 R1 b          all  ns sig sig sig sig  sig  False
L1 E1 R2 a          any sig sig sig sig sig  sig   True
L1 E1 R2 a          all sig sig sig sig sig  sig   True
L1 E1 R2 b          any sig sig sig sig sig  sig   True
L1 E1 R2 b          all sig sig sig sig sig  sig   True
L1 E2 R1 a          any sig sig  ns sig sig  sig  False
L1 E2 R1 a          all sig  ns  ns sig sig  sig  False
L1 E2 R1 b          any sig sig  ns sig sig  sig  False
L1 E2 R1 b          all sig  ns  ns sig sig  sig  False
L1 E2 R2 a          any sig sig sig sig sig  sig   True
L1 E2 R2 a          all sig sig sig sig sig  sig   True
L1 E2 R2 b          any sig sig sig sig sig  sig   True
L1 E2 R2 b          all sig sig sig sig sig  sig   True
L2 E1 R1 a          any sig sig sig sig sig  sig   True
L2 E1 R1 a          all sig  ns sig sig sig  sig  False
L2 E1 R1 b          any sig sig sig sig sig  sig   True
L2 E1 R1 b          all sig  ns sig sig sig  sig  False
L2 E1 R2 a          any sig sig sig sig sig  sig   True
L2 E1 R2 a          all sig sig sig sig sig  sig   True
L2 E1 R2 b          any sig sig sig sig sig  sig   True
L2 E1 R2 b          all sig sig sig sig sig  sig   True
L2 E2 R1 a          any sig sig sig sig sig  sig   True
L2 E2 R1 a          all sig  ns sig sig sig  sig  False
L2 E2 R1 b          any sig sig sig sig sig  sig   True
L2 E2 R1 b          all sig  ns sig sig sig  sig  False
L2 E2 R2 a          any sig sig sig sig sig  sig   True
L2 E2 R2 a          all sig sig sig sig sig  sig   True
L2 E2 R2 b          any sig sig sig sig sig  sig   True
L2 E2 R2 b          all sig sig sig sig sig  sig   True
combinations where all six are significant and positive: 22 of 32

all VISp pairs, L1 E2 R2 a:
               area  events  sessions  mean_diff      p  ccg_peak  ccg_peak_lag_ms  p_bh_all   p_bh  sig_pos
               ACAd     450         3     0.0536 0.0000    0.1398                0    0.0000 0.0000     True
               ACAv     652         4     0.0337 0.0000    0.0699               10    0.0000 0.0000     True
                CA1    1156         7     0.0053 0.0868    0.0230              -60    0.1100 0.1136    False
                CA3     610         5     0.0065 0.0640    0.0139              -50    0.0838 0.0870    False
                 CP     493         4     0.1030 0.0000    0.1960              -10    0.0000 0.0000     True
              CP-RF    1021         5     0.1270 0.0000    0.2188              -20    0.0000 0.0000     True
            CP-noRF    1067         6     0.1066 0.0000    0.1623              -20    0.0000 0.0000     True
                 DG     690         6     0.0285 0.0000    0.0452              -40    0.0000 0.0000     True
                 DP     476         4     0.0099 0.0316    0.0651              -20    0.0444 0.0467     True
                LGd    1143         5     0.3433 0.0000    0.4256                0    0.0000 0.0000     True
                 LP     834         3     0.2642 0.0000    0.3354                0    0.0000 0.0000     True
                LSc     258         1     0.0078 0.1170    0.0199              190    0.1435 0.1474    False
                LSr     696         6     0.0134 0.0017    0.0629              -40    0.0027 0.0030     True
                 MB     283         3     0.0026 0.7827    0.0443              -10    0.8262 0.8316    False
                MOp     751         4     0.0023 0.3437    0.0274               80    0.3958 0.4030    False
                MOs     696         6     0.0008 0.8110    0.0364               10    0.8329 0.8356    False
                 MS     202         1     0.0060 0.4586    0.0376              -50    0.4979 0.5030    False
                OLF     265         2     0.0116 0.0543    0.0450              -40    0.0737 0.0769    False
               ORBm      26         1    -0.0133 0.3666    0.0832                0    0.4097 0.4155    False
                 PL     670         5     0.0104 0.0078    0.0517              -20    0.0113 0.0120     True
                 PO     413         3     0.0002 0.9584    0.0564               20    0.9584 0.9584    False
               POST     125         2     0.0259 0.0068    0.1087                0    0.0103 0.0110     True
                PPT     258         1     0.2460 0.0000    0.2964                0    0.0000 0.0000     True
               ProS     265         2     0.0234 0.0002    0.0433              -50    0.0003 0.0003     True
             RSPagl     102         1     0.0609 0.0000    0.0863               20    0.0000 0.0000     True
               RSPd      25         2     0.1133 0.0004    0.2271                0    0.0007 0.0007     True
               RSPv     309         4     0.1333 0.0000    0.2538              -10    0.0000 0.0000     True
               SCig      26         1     0.0186 0.1499    0.0522             -130    0.1780 0.1820    False
                SGN     834         3     0.2347 0.0000    0.2709                0    0.0000 0.0000     True
                SSp    1651        11     0.0080 0.0019    0.0552               20    0.0030 0.0032     True
                STR     461         3     0.0198 0.0000    0.0259              -10    0.0000 0.0000     True
                SUB     746         3     0.0147 0.0000    0.0904              -90    0.0000 0.0000     True
Track not annotated     209         2     0.4065 0.0000    0.5114              -10    0.0000 0.0000     True
                VIS     360         2     0.4085 0.0000    0.5532                0    0.0000 0.0000     True
              VISal     199         2     0.3941 0.0000    0.4645              -10    0.0000 0.0000     True
              VISam     406         2     0.3451 0.0000    0.4347                0    0.0000 0.0000     True
               VISl     107         1     0.3460 0.0000    0.4770                0    0.0000 0.0000     True
              VISlm     120         2     0.2935 0.0000    0.4546                0    0.0000 0.0000     True

Result: blocked (methods underspecified). ACA, RSP, CP, LGd, LP and VISl are all significant and positive in 14 of 16 combinations when any subdivision of ACA or RSP counts and in 8 of 16 when all must (22 of the 32 rows of the table, which lists each combination under both modes). Under L1 E2 R1 CP is not (q = 0.1), and with all subdivisions required RSPagl or RSPd or ACAv fail under R1. The readings disagree.

fig7f-mop-network¶

MOp alpha events enhance coherence of MOp with SSp, PO, MRN and the deep layers of the superior colliculus (SCiw).

In [35]:
A4 = {"SSp": ["SSp"], "PO": ["PO"], "MRN": ["MRN"], "SCiw": ["SCiw"]}
print(show_pairs("MOp", A4).to_string(index=False))
rows = []
for (L, E, R, S) in COMBOS:
    t = PAIRS[("MOp", L, E, R, S)]
    st = {a: area_state(t, n) for a, n in A4.items()}
    rows.append({"combo": f"{L} {E} {R} {S}", **st, "holds": all(s == "sig" for s in st.values())})
f7m = pd.DataFrame(rows)
print(f7m.to_string(index=False))
print("\nall MOp pairs, L1 E2 R2 a:")
print(PAIRS[("MOp", "L1", "E2", "R2", "a")].round(4).to_string(index=False))
     combo                   SSp                   PO                   MRN                   SCiw
L1 E1 R1 a SSp +0.181 q=8.8e-07*               absent                absent                 absent
L1 E1 R1 b SSp +0.181 q=8.8e-07*               absent                absent                 absent
L1 E1 R2 a   SSp +0.259 q=8e-14*               absent                absent                 absent
L1 E1 R2 b   SSp +0.259 q=8e-14*               absent                absent                 absent
L1 E2 R1 a SSp +0.190 q=4.3e-09* PO +0.174 q=1.1e-05*   MRN +0.112 q=0.035*  SCiw +0.114 q=0.0038*
L1 E2 R1 b SSp +0.190 q=4.3e-09* PO +0.174 q=1.1e-05*   MRN +0.112 q=0.035*  SCiw +0.114 q=0.0038*
L1 E2 R2 a   SSp +0.250 q=1e-11* PO +0.195 q=5.3e-06* MRN +0.171 q=2.5e-05* SCiw +0.179 q=2.4e-07*
L1 E2 R2 b   SSp +0.250 q=1e-11* PO +0.195 q=5.3e-06* MRN +0.171 q=2.5e-05* SCiw +0.179 q=2.4e-07*
L2 E1 R1 a SSp +0.173 q=2.7e-10*               absent                absent                 absent
L2 E1 R1 b SSp +0.173 q=2.7e-10*               absent                absent                 absent
L2 E1 R2 a SSp +0.246 q=8.6e-12*               absent                absent                 absent
L2 E1 R2 b SSp +0.246 q=8.6e-12*               absent                absent                 absent
L2 E2 R1 a SSp +0.187 q=5.4e-11*  PO +0.147 q=0.0015*     MRN +0.082 q=0.11   SCiw +0.121 q=0.003*
L2 E2 R1 b SSp +0.187 q=5.4e-11*  PO +0.147 q=0.0015*     MRN +0.082 q=0.11   SCiw +0.121 q=0.003*
L2 E2 R2 a SSp +0.247 q=2.2e-11* PO +0.169 q=1.6e-05* MRN +0.190 q=1.7e-05* SCiw +0.201 q=4.4e-09*
L2 E2 R2 b SSp +0.247 q=2.2e-11* PO +0.169 q=1.6e-05* MRN +0.190 q=1.7e-05* SCiw +0.201 q=4.4e-09*
     combo SSp     PO    MRN   SCiw  holds
L1 E1 R1 a sig absent absent absent  False
L1 E1 R1 b sig absent absent absent  False
L1 E1 R2 a sig absent absent absent  False
L1 E1 R2 b sig absent absent absent  False
L1 E2 R1 a sig    sig    sig    sig   True
L1 E2 R1 b sig    sig    sig    sig   True
L1 E2 R2 a sig    sig    sig    sig   True
L1 E2 R2 b sig    sig    sig    sig   True
L2 E1 R1 a sig absent absent absent  False
L2 E1 R1 b sig absent absent absent  False
L2 E1 R2 a sig absent absent absent  False
L2 E1 R2 b sig absent absent absent  False
L2 E2 R1 a sig    sig     ns    sig  False
L2 E2 R1 b sig    sig     ns    sig  False
L2 E2 R2 a sig    sig    sig    sig   True
L2 E2 R2 b sig    sig    sig    sig   True

all MOp pairs, L1 E2 R2 a:
   area  events  sessions  mean_diff      p  ccg_peak  ccg_peak_lag_ms  p_bh_all   p_bh  sig_pos
    CA1     108         5     0.0250 0.0116    0.0300              170    0.0513 0.0443     True
    CA3      25         2     0.0408 0.0551    0.0427              990    0.1514 0.1472    False
     CP      25         2     0.0098 0.9578    0.0546              -20    0.9578 0.9578    False
  CP-RF      69         3     0.0082 0.2700    0.0307               10    0.4569 0.4569    False
CP-noRF      76         4     0.0040 0.8684    0.0349             -230    0.9417 0.9417    False
     DG      64         4     0.0236 0.0501    0.0195              940    0.1514 0.1472    False
    LGd      76         4     0.0054 0.7719    0.0229             -160    0.8937 0.8627    False
     LP      60         2    -0.0058 0.6219    0.0293              210    0.7958 0.7732    False
    LSc      39         2     0.0155 0.4185    0.0332             -180    0.6138 0.6117    False
    LSr      32         1     0.0025 0.5608    0.0492              -10    0.7710 0.7610    False
    MRN      32         1     0.1713 0.0000    0.2314                0    0.0000 0.0000     True
     PO      25         2     0.1952 0.0000    0.2503               20    0.0000 0.0000     True
   POST      41         2     0.0188 0.0779    0.0353             -650    0.1558 0.1480    False
 RSPagl      48         2     0.0140 0.8989    0.0429              170    0.9417 0.9489    False
   RSPd      39         2     0.0285 0.0620    0.0603                0    0.1515 0.1472    False
   RSPv      32         1     0.0111 0.6511    0.0732               10    0.7958 0.7732    False
   SCiw      32         1     0.1795 0.0000    0.1620                0    0.0000 0.0000     True
    SGN      60         2     0.0164 0.0760    0.0310             -170    0.1558 0.1480    False
    SSp      69         3     0.2503 0.0000    0.3293                0    0.0000 0.0000     True
    SUB      44         1     0.0200 0.1378    0.0577             -270    0.2527 0.2381    False
    VIS      55         3     0.0250 0.0435    0.0449              150    0.1514 0.1514    False
   VISp     108         5     0.0023 0.3147    0.0211              680    0.4946 0.4983    False

Result: blocked (methods underspecified). Under E2 SSp, PO, MRN and SCiw are significant and positive in 3 of 4 L and R combinations; under L2 E2 R1 MRN is not (q = 0.11); under E1 PO, MRN and SCiw have too few events to be tested. MRN and SCiw each come from one session (32 events under L1 E2). The readings disagree.

fig7-hippocampus-not-engaged¶

Neither VISp nor MOp alpha events engage the hippocampus.

Hippocampal formation regions are those under "Hippocampal formation" in the code's CCF structure tree (analysis_code/ccf_structure_tree_2017.csv).

In [36]:
tree = shield.read_code_csv("analysis_code/ccf_structure_tree_2017.csv")
hpf_id = tree.loc[tree["acronym"] == "HPF", "id"].iloc[0]
hpf = set(tree.loc[tree["structure_id_path"].astype(str).str.contains(f"/{hpf_id}/"), "acronym"])
rows = []
for (L, E, R, S) in COMBOS:
    for ref in shield.REFERENCE_AREAS:
        t = PAIRS[(ref, L, E, R, S)]
        h = t[t["area"].isin(hpf)] if len(t) else t
        rows.append({"combo": f"{L} {E} {R} {S}", "ref": ref, "hippocampal_pairs": len(h),
                     "significant_positive": ", ".join(h.loc[h["sig_pos"], "area"]) if len(h) else "",
                     "tested": ", ".join(f"{x.area} {x.mean_diff:+.3f} q={x.p_bh:.2g}" for x in h.itertuples())})
hp = pd.DataFrame(rows)
print(hp.to_string(index=False))
print("combinations and references with a significant positive hippocampal pair:", int((hp["significant_positive"] != "").sum()), "of", len(hp))
     combo  ref  hippocampal_pairs     significant_positive                                                                                                                        tested
L1 E1 R1 a VISp                  6                       DG          CA1 +0.003 q=0.48, CA3 +0.004 q=0.49, DG +0.019 q=8.3e-05, POST +0.006 q=0.71, ProS +0.009 q=0.29, SUB +0.003 q=0.64
L1 E1 R1 a  MOp                  3                                                                                                 CA1 +0.015 q=0.52, DG +0.036 q=0.27, SUB -0.004 q=0.95
L1 E1 R1 b VISp                  6                       DG          CA1 +0.003 q=0.48, CA3 +0.004 q=0.49, DG +0.019 q=8.3e-05, POST +0.006 q=0.71, ProS +0.009 q=0.29, SUB +0.003 q=0.64
L1 E1 R1 b  MOp                  3                                                                                                 CA1 +0.015 q=0.52, DG +0.036 q=0.27, SUB -0.004 q=0.95
L1 E1 R2 a VISp                  6       CA1, DG, ProS, SUB   CA1 +0.007 q=0.016, CA3 +0.008 q=0.073, DG +0.031 q=6.2e-14, POST +0.023 q=0.063, ProS +0.022 q=8e-05, SUB +0.018 q=3.9e-07
L1 E1 R2 a  MOp                  3                                                                                                 CA1 +0.024 q=0.14, DG +0.019 q=0.62, SUB +0.017 q=0.15
L1 E1 R2 b VISp                  6       CA1, DG, ProS, SUB   CA1 +0.007 q=0.016, CA3 +0.008 q=0.073, DG +0.031 q=6.2e-14, POST +0.023 q=0.063, ProS +0.022 q=8e-05, SUB +0.018 q=3.9e-07
L1 E1 R2 b  MOp                  3                                                                                                 CA1 +0.024 q=0.14, DG +0.019 q=0.62, SUB +0.017 q=0.15
L1 E2 R1 a VISp                  6                 DG, ProS          CA1 -0.001 q=0.65, CA3 +0.006 q=0.15, DG +0.015 q=0.0013, POST +0.014 q=0.42, ProS +0.011 q=0.027, SUB +0.002 q=0.65
L1 E2 R1 a  MOp                  5                                                           CA1 +0.019 q=0.13, CA3 +0.030 q=0.23, DG +0.021 q=0.2, POST -0.003 q=0.86, SUB +0.007 q=0.77
L1 E2 R1 b VISp                  6                 DG, ProS          CA1 -0.001 q=0.65, CA3 +0.006 q=0.15, DG +0.015 q=0.0013, POST +0.014 q=0.42, ProS +0.011 q=0.027, SUB +0.002 q=0.65
L1 E2 R1 b  MOp                  5                                                           CA1 +0.019 q=0.13, CA3 +0.030 q=0.23, DG +0.021 q=0.2, POST -0.003 q=0.86, SUB +0.007 q=0.77
L1 E2 R2 a VISp                  6      DG, POST, ProS, SUB   CA1 +0.005 q=0.11, CA3 +0.006 q=0.087, DG +0.029 q=6.5e-13, POST +0.026 q=0.011, ProS +0.023 q=0.0003, SUB +0.015 q=4.9e-05
L1 E2 R2 a  MOp                  5                      CA1                                CA1 +0.025 q=0.044, CA3 +0.041 q=0.15, DG +0.024 q=0.15, POST +0.019 q=0.15, SUB +0.020 q=0.24
L1 E2 R2 b VISp                  6      DG, POST, ProS, SUB   CA1 +0.005 q=0.11, CA3 +0.006 q=0.087, DG +0.029 q=6.5e-13, POST +0.026 q=0.011, ProS +0.023 q=0.0003, SUB +0.015 q=4.9e-05
L1 E2 R2 b  MOp                  5                      CA1                                CA1 +0.025 q=0.044, CA3 +0.041 q=0.15, DG +0.024 q=0.15, POST +0.019 q=0.15, SUB +0.020 q=0.24
L2 E1 R1 a VISp                  6                       DG           CA1 -0.001 q=0.68, CA3 -0.001 q=0.81, DG +0.012 q=0.041, POST +0.005 q=0.81, ProS +0.011 q=0.089, SUB +0.008 q=0.16
L2 E1 R1 a  MOp                  3                                                                                                  CA1 +0.005 q=0.58, DG +0.037 q=0.2, SUB +0.007 q=0.74
L2 E1 R1 b VISp                  6                       DG           CA1 -0.001 q=0.68, CA3 -0.001 q=0.81, DG +0.012 q=0.041, POST +0.005 q=0.81, ProS +0.011 q=0.089, SUB +0.008 q=0.16
L2 E1 R1 b  MOp                  3                                                                                                  CA1 +0.005 q=0.58, DG +0.037 q=0.2, SUB +0.007 q=0.74
L2 E1 R2 a VISp                  6 CA3, DG, POST, ProS, SUB  CA1 +0.005 q=0.075, CA3 +0.011 q=0.0064, DG +0.032 q=1e-12, POST +0.046 q=0.008, ProS +0.023 q=0.00021, SUB +0.022 q=8.8e-08
L2 E1 R2 a  MOp                  3                  CA1, DG                                                                     CA1 +0.038 q=0.0097, DG +0.041 q=0.014, SUB +0.020 q=0.49
L2 E1 R2 b VISp                  6 CA3, DG, POST, ProS, SUB  CA1 +0.005 q=0.075, CA3 +0.011 q=0.0064, DG +0.032 q=1e-12, POST +0.046 q=0.008, ProS +0.023 q=0.00021, SUB +0.022 q=8.8e-08
L2 E1 R2 b  MOp                  3                  CA1, DG                                                                     CA1 +0.038 q=0.0097, DG +0.041 q=0.014, SUB +0.020 q=0.49
L2 E2 R1 a VISp                  6                  DG, SUB           CA1 +0.001 q=0.94, CA3 +0.002 q=0.75, DG +0.013 q=0.002, POST -0.000 q=0.94, ProS +0.011 q=0.07, SUB +0.008 q=0.026
L2 E2 R1 a  MOp                  5                                                         CA1 +0.012 q=0.25, CA3 +0.039 q=0.096, DG +0.028 q=0.11, POST -0.009 q=0.63, SUB +0.011 q=0.48
L2 E2 R1 b VISp                  6                  DG, SUB          CA1 +0.001 q=0.94, CA3 +0.002 q=0.7, DG +0.013 q=0.0021, POST -0.000 q=0.94, ProS +0.011 q=0.068, SUB +0.008 q=0.022
L2 E2 R1 b  MOp                  5                                                         CA1 +0.012 q=0.25, CA3 +0.039 q=0.096, DG +0.028 q=0.11, POST -0.009 q=0.63, SUB +0.011 q=0.48
L2 E2 R2 a VISp                  6  CA1, CA3, DG, ProS, SUB CA1 +0.009 q=0.0084, CA3 +0.010 q=0.014, DG +0.029 q=1.6e-13, POST +0.021 q=0.064, ProS +0.017 q=0.0041, SUB +0.022 q=5.5e-09
L2 E2 R2 a  MOp                  5                                                          CA1 +0.013 q=0.48, CA3 +0.026 q=0.48, DG +0.014 q=0.53, POST +0.012 q=0.77, SUB +0.011 q=0.61
L2 E2 R2 b VISp                  6  CA1, CA3, DG, ProS, SUB  CA1 +0.009 q=0.0082, CA3 +0.010 q=0.012, DG +0.029 q=1.7e-13, POST +0.021 q=0.063, ProS +0.017 q=0.004, SUB +0.022 q=5.4e-09
L2 E2 R2 b  MOp                  5                                                          CA1 +0.013 q=0.48, CA3 +0.026 q=0.48, DG +0.014 q=0.53, POST +0.012 q=0.77, SUB +0.011 q=0.61
combinations and references with a significant positive hippocampal pair: 20 of 32

Result: not reproduced. For VISp events at least one hippocampal region is significantly and positively coherent in every combination (DG in all, +0.012 to +0.032; also CA1, CA3, ProS, SUB or POST under R2, and ProS under L1 E2 R1 and SUB under L2 E2 R1); for MOp events in 4 of 16 (CA1 and DG under L2 E1 R2; CA1, +0.025 at q = 0.044, under L1 E2 R2). The hippocampal effects are small against those of the visual areas (+0.06 to +0.39).

fig8bc-cp-rf-location¶

CP units with significant spatial receptive fields lie mostly in the medial dorsal striatum, midway along the anterior-posterior extent of CP.

QC CP units of the 87 sessions, split by RF significance (p < 0.01, computed here). In the left hemisphere a larger CCF z is more medial and a smaller y more dorsal.

In [37]:
cpu = units[units["passing"] & units["qc"] & (units["structure_acronym"] == "CP")].join(rf[["p_rf"]], how="inner")
cpu["rf_sig"] = cpu["p_rf"] < 0.01
r_, n_ = cpu[cpu["rf_sig"]], cpu[~cpu["rf_sig"]]
print("orientation check, median z (um): VISp units", units.loc[units["structure_acronym"] == "VISp", "z"].median(),
      "; SSp", units.loc[units["structure_acronym"] == "SSp", "z"].median(), "; midline structures (LSr, SFO)",
      units.loc[units["structure_acronym"].isin(["LSr", "SFO"]), "z"].median(), "(the midline is at 5700)")
print(f"QC CP units with an RF p value: {len(cpu)} ({cpu['session_id'].nunique()} sessions, {cpu['subject_id'].nunique()} mice); "
      f"CP-RF {len(r_)}, CP-noRF {len(n_)}")
for ax_, lab, alt in (("z", "medial-lateral (z)", "greater"), ("y", "dorsal-ventral (y)", "less")):
    p = stats.ranksums(r_[ax_], n_[ax_], alternative=alt).pvalue
    print(f"{lab}: CP-RF median {r_[ax_].median():.0f}, CP-noRF median {n_[ax_].median():.0f} um; one-sided rank-sum p = {p:.2e}"
          f" (two-sided {stats.ranksums(r_[ax_], n_[ax_]).pvalue:.2e})")
print(f"anterior-posterior (x): CP-RF median {r_['x'].median():.0f} um; CP units sampled from {cpu['x'].min():.0f} to {cpu['x'].max():.0f} "
      f"(median {cpu['x'].median():.0f}); CP-RF fraction by AP tercile:",
      cpu.groupby(pd.qcut(cpu["x"], 3))["rf_sig"].mean().round(3).tolist())
print("CP-RF units by mouse:", r_.groupby("subject_id").size().to_dict())
orientation check, median z (um): VISp units 3050.0 ; SSp 3150.0 ; midline structures (LSr, SFO) 5300.0 (the midline is at 5700)
QC CP units with an RF p value: 2376 (52 sessions, 21 mice); CP-RF 688, CP-noRF 1688
medial-lateral (z): CP-RF median 4075, CP-noRF median 4350 um; one-sided rank-sum p = 1.00e+00 (two-sided 1.82e-33)
dorsal-ventral (y): CP-RF median 3150, CP-noRF median 3500 um; one-sided rank-sum p = 1.89e-58 (two-sided 3.78e-58)
anterior-posterior (x): CP-RF median 5388 um; CP units sampled from 3700 to 6500 (median 5038); CP-RF fraction by AP tercile: [0.102, 0.325, 0.447]
CP-RF units by mouse: {'607660': 57, '608671': 6, '608672': 19, '612090': 28, '614608': 57, '615047': 59, '615048': 49, '615563': 25, '615564': 55, '623319': 8, '623784': 63, '623786': 49, '626279': 8, '632295': 33, '636740': 23, '637483': 20, '638387': 14, '640887': 58, '640890': 8, '642504': 49}

Result: partially reproduced. CP-RF units (688 of 2376 QC CP units) are more dorsal than CP-noRF units (median y 3150 against 3500 um, p = 1.9e-58) but more lateral, not more medial (median z 4075 against 4350 um; one-sided p = 1.0, two-sided 1.8e-33). The CP-RF median AP coordinate is 5388 um, in the posterior half of the CP sampled (3700 to 6500 um), where the CP-RF fraction rises from 0.10 to 0.45.

fig8d-cp-rf-larger-response¶

CP units with receptive fields respond more strongly to the task's full-field images than CP units without receptive fields.

In [38]:
f8 = pd.concat([v["fig8d"] for v in SP.values() if "fig8d" in v])
u8 = units.loc[f8.index].join(rf[["p_rf"]], how="left")
# every CP unit of the presence-0.9 set, noise ones included, has an RF p value (shield.py), so the
# variant without a noise filter splits noise CP units by RF as the code does
print("CP units in the five sessions without an RF p value:", int((u8["structure_acronym"].eq("CP") & u8["p_rf"].isna()).sum()))
res8 = {}
for filt in ("qc", "presence 0.9 (code variant)"):
    keep = u8["qc"] if filt == "qc" else pd.Series(True, index=u8.index)
    cp_rf = u8.index[keep & (u8["structure_acronym"] == "CP") & (u8["p_rf"] < 0.01)]
    cp_no = u8.index[keep & (u8["structure_acronym"] == "CP") & ~(u8["p_rf"] < 0.01)]
    v1i = u8.index[keep & (u8["isi_label"] == "VISp")]
    def prep(ix):
        X = f8.loc[ix].values.astype(float)
        X = X - X[:, 200:250].mean()
        return shield.exponential_convolve(X, tau=2, axis=1)
    Xr, Xn, Xv = prep(cp_rf), prep(cp_no), prep(v1i)
    p = np.array([stats.ranksums(a, b).pvalue for a, b in zip(Xr.T, Xn.T)])
    sig = multipletests(p, method="fdr_bh")[0]
    t = np.arange(750) - 250
    win = (t >= 0) & (t < 250)
    higher = Xr.mean(0) > Xn.mean(0)
    res8[filt] = dict(Xr=Xr, Xn=Xn, Xv=Xv, sig=sig, t=t)
    print(f"{filt}: CP-RF {len(cp_rf)}, CP-noRF {len(cp_no)}, VISp {len(v1i)} units; significant bins 0-250 ms: {int((sig & win).sum())}, "
          f"of which CP-RF higher: {int((sig & win & higher).sum())}; significant bins before onset: {int((sig & (t < 0)).sum())}; "
          f"first significant bin {t[sig][0] if sig.any() else None} ms")
    print("   units by session (CP-RF, CP-noRF, VISp):",
          {int(s): (int((u8.loc[cp_rf, 'session_id'] == s).sum()), int((u8.loc[cp_no, 'session_id'] == s).sum()), int((u8.loc[v1i, 'session_id'] == s).sum())) for s in shield.CP_RF_SESSIONS})
R8 = res8["qc"]
fig, ax = plt.subplots(figsize=(6, 3)); ax2 = ax.twinx()
ax.plot(R8["t"], R8["Xr"].mean(0), "r", label="CP-RF"); ax.plot(R8["t"], R8["Xn"].mean(0), "b", label="CP-noRF")
ax2.plot(R8["t"], R8["Xv"].mean(0), "k", label="VISp"); ax.set_xlim(-50, 400); ax.legend(loc="upper left"); ax2.legend(loc="upper right")
ax.set_xlabel("ms from image onset"); ax.set_ylabel("CP rate (Hz)"); ax2.set_ylabel("VISp rate (Hz)"); plt.show()
CP units in the five sessions without an RF p value: 0
qc: CP-RF 70, CP-noRF 162, VISp 218 units; significant bins 0-250 ms: 53, of which CP-RF higher: 53; significant bins before onset: 0; first significant bin 55 ms
   units by session (CP-RF, CP-noRF, VISp): {1182628226: (16, 48, 57), 1182871514: (22, 13, 42), 1183071525: (19, 23, 56), 1194850630: (12, 23, 45), 1195036753: (1, 55, 18)}
presence 0.9 (code variant): CP-RF 79, CP-noRF 206, VISp 239 units; significant bins 0-250 ms: 78, of which CP-RF higher: 78; significant bins before onset: 0; first significant bin 51 ms
   units by session (CP-RF, CP-noRF, VISp): {1182628226: (18, 57, 67), 1182871514: (23, 17, 46), 1183071525: (23, 36, 60), 1194850630: (14, 24, 48), 1195036753: (1, 72, 18)}
No description has been provided for this image

Result: partially reproduced. In the five sessions CP-RF units respond more than CP-noRF units in 53 bins between 0 and 250 ms (first at 55 ms), CP-RF higher in all, none before onset, with the Methods' QC (the criterion's units). The unit counts there (70, 162 and 218) miss the paper's 80 and 205 for CP-RF and CP-noRF (tolerance 8 and 20.5) and match its 239 for VISp only within tolerance. With the code's filter (presence ratio above 0.9, noise units kept and split by their RF p values) the counts are 79, 206 and 239, within one unit of the paper's, with 78 significant bins, all CP-RF higher: the paper's counts come from the code's filter. The effect holds under both, the expected counts only under the variant. Whether the criterion should take the code's filter is asked of a reader (questions/001051.yaml).

fig8d-response-latencies¶

CP-RF responses lag VISp by about 20 ms, and CP-noRF responses lag CP-RF by about another 20 ms.

In [39]:
def latencies(X, t):
    m = X.mean(0); w = (t >= 0) & (t < 250)
    mw, tw = m[w], t[w]
    pk = tw[np.argmax(mw)]
    half = tw[np.argmax(mw >= mw.max() / 2)]
    return pk, half
for filt, R8_ in res8.items():
    lv, lr, ln = (latencies(R8_[k], R8_["t"]) for k in ("Xv", "Xr", "Xn"))
    print(f"{filt}: (a) peak  VISp {lv[0]} ms, CP-RF {lr[0]} ms, CP-noRF {ln[0]} ms -> CP-RF minus VISp {lr[0] - lv[0]}, CP-noRF minus CP-RF {ln[0] - lr[0]}")
    print(f"{' ' * len(filt)}  (b) half  VISp {lv[1]} ms, CP-RF {lr[1]} ms, CP-noRF {ln[1]} ms -> CP-RF minus VISp {lr[1] - lv[1]}, CP-noRF minus CP-RF {ln[1] - lr[1]}")
qc: (a) peak  VISp 48 ms, CP-RF 82 ms, CP-noRF 91 ms -> CP-RF minus VISp 34, CP-noRF minus CP-RF 9
    (b) half  VISp 38 ms, CP-RF 54 ms, CP-noRF 74 ms -> CP-RF minus VISp 16, CP-noRF minus CP-RF 20
presence 0.9 (code variant): (a) peak  VISp 48 ms, CP-RF 82 ms, CP-noRF 91 ms -> CP-RF minus VISp 34, CP-noRF minus CP-RF 9
                             (b) half  VISp 38 ms, CP-RF 54 ms, CP-noRF 74 ms -> CP-RF minus VISp 16, CP-noRF minus CP-RF 20

Result: blocked, methods underspecified. With latency as the time the population mean first reaches half its peak (b), CP-RF lags VISp by 16 ms and CP-noRF lags CP-RF by 20 ms, both within 20 +- 5. With the time of peak (a) the lags are 34 and 9 ms, both outside. The two filters (the Methods' QC and the code's) give the same values. The paper says only that "the response time course of these two populations was different: CP-RF lagged behind VISp responses by ... 20 ms, whereas CP-noRF had slower responses" (Results, Fine-scale alpha rhythm coupling between visual cortex and striatum, paragraph 3), and it gives no latency definition. That wording does not pick an onset measure over a peak measure, and the claims file lists both readings without preferring either. The readings give different outcomes and no basis in the paper picks one, so the claim is methods underspecified. Half-peak latency had been adopted as an assumption after it was seen to match; this outcome replaces it, as proposed after verification round 2 and agreed in round 3.

fig8e-cp-rf-more-modulated¶

Triggered on VISp alpha events, CP-RF firing rates are more modulated than CP-noRF rates, particularly in the troughs of the oscillation.

Sessions with qualifying VISp events (more than 5) in which CP is an area of the session.

In [40]:
from scipy.signal import argrelextrema
rows = []
for L in ("L1", "L2"):
    for E in ("E1", "E2"):
        for S in ("a", "b"):
            tr = []
            for sid, v in SP.items():
                e = v["alpha"].get((L, "VISp", E))
                if sid not in SETS[S] or e is None or "trig_cp" not in e or len(e["coh"]["event"]) <= 5 or "CP" not in v["areas"][L]:
                    continue
                if len(e["trig_cp"]):
                    tr.append(e["trig_cp"])
            if not tr:
                continue
            T = shield.exponential_convolve(np.concatenate(tr), tau=1, axis=2)
            tt = np.arange(250) * 0.01 - 0.5
            vis, crf, cno = T[:, 0], T[:, 1], T[:, 2]
            p = np.array([stats.ranksums(a, b).pvalue for a, b in zip(crf.T, cno.T)])
            sig = multipletests(p, method="fdr_bh")[0]
            pw = np.array([stats.wilcoxon(a, b).pvalue for a, b in zip(crf.T, cno.T)])
            sigw = multipletests(pw, method="fdr_bh")[0]
            vm = vis.mean(0)
            w01 = (tt >= 0) & (tt <= 1)
            troughs = [i for i in argrelextrema(vm, np.less, order=5)[0] if w01[i]]
            near_trough = np.zeros(250, bool)
            for i in troughs:
                near_trough[max(i - 2, 0): i + 3] = True
            base_r, base_n = crf[:, tt < 0].mean(), cno[:, tt < 0].mean()
            rng_r = np.ptp(crf.mean(0)[w01]); rng_n = np.ptp(cno.mean(0)[w01])
            rows.append({"L": L, "E": E, "set": S, "events": len(T), "sessions": len(tr), "VISp_troughs_s": [round(tt[i], 2) for i in troughs],
                         "sig_bins_ranksum": int(sig.sum()), "sig_bins_at_troughs": int((sig & near_trough).sum()),
                         "sig_bins_signedrank": int(sigw.sum()), "sig_at_troughs_signedrank": int((sigw & near_trough).sum()),
                         "range_CP_RF": rng_r, "range_CP_noRF": rng_n, "range_norm_CP_RF": rng_r / base_r, "range_norm_CP_noRF": rng_n / base_n})
f8e = pd.DataFrame(rows)
f8e["holds_a"] = (f8e["sig_bins_at_troughs"] > 0) & (f8e["range_CP_RF"] > f8e["range_CP_noRF"])
f8e["holds_b"] = (f8e["sig_bins_at_troughs"] > 0) & (f8e["range_norm_CP_RF"] > f8e["range_norm_CP_noRF"])
print(f8e.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
 L  E set  events  sessions                VISp_troughs_s  sig_bins_ranksum  sig_bins_at_troughs  sig_bins_signedrank  sig_at_troughs_signedrank  range_CP_RF  range_CP_noRF  range_norm_CP_RF  range_norm_CP_noRF  holds_a  holds_b
L1 E1   a     438         4 [0.0, 0.23, 0.49, 0.74, 0.98]               112                   24                   98                         23         1.98           1.29               0.5               0.323     True     True
L1 E1   b     438         4 [0.0, 0.23, 0.49, 0.74, 0.98]               112                   24                   98                         23         1.98           1.29               0.5               0.323     True     True
L1 E2   a     493         4 [0.0, 0.23, 0.49, 0.73, 0.97]               117                   25                  102                         25         1.96           1.18               0.5               0.297     True     True
L1 E2   b     493         4 [0.0, 0.23, 0.49, 0.73, 0.97]               117                   25                  102                         25         1.96           1.18               0.5               0.297     True     True
L2 E1   a     387         5  [0.0, 0.23, 0.5, 0.74, 0.98]               210                   25                  198                         25         2.04           1.34              0.53               0.323     True     True
L2 E1   b     387         5  [0.0, 0.23, 0.5, 0.74, 0.98]               210                   25                  198                         25         2.04           1.34              0.53               0.323     True     True
L2 E2   a     439         5 [0.0, 0.23, 0.49, 0.74, 0.98]               222                   25                  216                         25         2.11           1.29             0.555               0.314     True     True
L2 E2   b     439         5 [0.0, 0.23, 0.49, 0.74, 0.98]               222                   25                  216                         25         2.11           1.29             0.555               0.314     True     True

Result: reproduced. In every combination (387 to 493 events in 4 or 5 sessions) CP-RF and CP-noRF rates differ in 112 to 222 bins, 24 or 25 of them at the troughs of the VISp rate, and the CP-RF range over 0 to 1 s is larger (1.96 to 2.11 against 1.18 to 1.34 Hz; normalised 0.50 to 0.56 against 0.30 to 0.32).

fig8f-cp-rf-higher-coherence¶

During VISp alpha events, CP-RF units are more coherent with VISp in the 3 to 7 Hz band than CP-noRF units (p = 3.8e-14, 439 events).

In [41]:
rows = []
for (L, E, R, S) in COMBOS:
    dr, dn = [], []
    for sid, v in SP.items():
        e = v["alpha"].get((L, "VISp", E))
        if sid not in SETS[S] or e is None or "coh" not in e or len(e["coh"]["event"]) <= 5 or "CP" not in v["areas"][L]:
            continue
        nm = e["coh_names"]
        if "CP-RF" not in nm or "CP-noRF" not in nm:
            continue
        d = e["coh"]["event"] - e["coh"][R]
        dr.append(d[:, nm.index("CP-RF")]); dn.append(d[:, nm.index("CP-noRF")])
    a, b = np.concatenate(dr), np.concatenate(dn)
    ok = np.isfinite(a) & np.isfinite(b)
    w = stats.wilcoxon(a[ok], b[ok])
    rows.append({"combo": f"{L} {E} {R} {S}", "events": int(ok.sum()), "sessions": len(dr), "CP_RF_mean": a[ok].mean(),
                 "CP_noRF_mean": b[ok].mean(), "p": w.pvalue, "holds": (a[ok].mean() > b[ok].mean()) and w.pvalue < 0.05})
f8f = pd.DataFrame(rows)
print(f8f.to_string(index=False, float_format=lambda v: f"{v:.3g}"))
     combo  events  sessions  CP_RF_mean  CP_noRF_mean        p  holds
L1 E1 R1 a     438         4      0.0116        0.0177    0.441  False
L1 E1 R1 b     438         4      0.0116        0.0177    0.441  False
L1 E1 R2 a     438         4      0.0998        0.0507 6.47e-11   True
L1 E1 R2 b     438         4      0.0998        0.0507 6.47e-11   True
L1 E2 R1 a     493         4     0.00679       0.00214    0.364  False
L1 E2 R1 b     493         4     0.00679       0.00214    0.364  False
L1 E2 R2 a     493         4      0.0956        0.0416 6.32e-14   True
L1 E2 R2 b     493         4      0.0956        0.0416 6.32e-14   True
L2 E1 R1 a     387         5      0.0171        0.0189    0.693  False
L2 E1 R1 b     387         5      0.0171        0.0189    0.693  False
L2 E1 R2 a     387         5        0.11        0.0454 1.22e-14   True
L2 E1 R2 b     387         5        0.11        0.0454 1.22e-14   True
L2 E2 R1 a     439         5      0.0221        0.0175    0.754  False
L2 E2 R1 b     439         5      0.0221        0.0175    0.754  False
L2 E2 R2 a     439         5       0.101        0.0517 1.94e-10   True
L2 E2 R2 b     439         5       0.101        0.0517 1.94e-10   True

Result: blocked (methods underspecified). Under R2 (the code), CP-RF coherence with VISp exceeds CP-noRF (mean 0.096 to 0.110 against 0.042 to 0.052, paired Wilcoxon p from 6.5e-11 to 1.2e-14); under R1 (the Methods read plainly) it does not (p 0.36 to 0.75). The readings disagree. The code's readings (L2, E2) give 439 events, the paper's count.

Blocked claims: what the files hold¶

The groups of one session file, and the attributes of its probes, show where the data a blocked claim needs would be.

In [42]:
f = shield.open_nwb(sessions["asset_id"].iloc[0])
groups = []
f.visititems(lambda n, o: groups.append(n) if o.attrs.get("neurodata_type") and n.count("/") <= 2 else None)
print("groups with a neurodata type:", [g for g in groups if not g.startswith("specifications")])
print("probe device attributes:", {k: str(v) for k, v in f["general/devices/probeA"].attrs.items()})
print("electrode group attributes:", {k: str(v) for k, v in f["general/extracellular_ephys/probeA"].attrs.items()})
print("session metadata:", {k: str(v) for k, v in f["general/metadata"].attrs.items()})
f.close()
print("\ngenotypes:", sorted(sessions["genotype"].unique()))
print("code session table columns:", list(code_sessions.columns))
print("code probes table columns:", list(code_probes.columns))
groups with a neurodata type: ['acquisition/EyeTracking', 'acquisition/EyeTracking/corneal_reflection_tracking', 'acquisition/EyeTracking/eye_tracking', 'acquisition/EyeTracking/likely_blink', 'acquisition/EyeTracking/pupil_tracking', 'acquisition/v_in', 'acquisition/v_sig', 'general/devices/NP.1', 'general/devices/probeA', 'general/devices/probeB', 'general/devices/probeC', 'general/devices/probeD', 'general/devices/probeE', 'general/devices/probeF', 'general/extracellular_ephys/electrodes', 'general/extracellular_ephys/probeA', 'general/extracellular_ephys/probeB', 'general/extracellular_ephys/probeC', 'general/extracellular_ephys/probeD', 'general/extracellular_ephys/probeE', 'general/extracellular_ephys/probeF', 'general/metadata', 'general/subject', 'general/task_parameters', 'intervals/dynamic_routing_image_set_presentations', 'intervals/dynamic_routing_image_set_presentations/active', 'intervals/dynamic_routing_image_set_presentations/duration', 'intervals/dynamic_routing_image_set_presentations/end_frame', 'intervals/dynamic_routing_image_set_presentations/flashes_since_change', 'intervals/dynamic_routing_image_set_presentations/id', 'intervals/dynamic_routing_image_set_presentations/image_name', 'intervals/dynamic_routing_image_set_presentations/is_change', 'intervals/dynamic_routing_image_set_presentations/is_image_novel', 'intervals/dynamic_routing_image_set_presentations/omitted', 'intervals/dynamic_routing_image_set_presentations/rewarded', 'intervals/dynamic_routing_image_set_presentations/start_frame', 'intervals/dynamic_routing_image_set_presentations/start_time', 'intervals/dynamic_routing_image_set_presentations/stimulus_block', 'intervals/dynamic_routing_image_set_presentations/stimulus_name', 'intervals/dynamic_routing_image_set_presentations/stop_time', 'intervals/dynamic_routing_image_set_presentations/tags', 'intervals/dynamic_routing_image_set_presentations/tags_index', 'intervals/dynamic_routing_image_set_presentations/timeseries', 'intervals/dynamic_routing_image_set_presentations/timeseries_index', 'intervals/flash_250ms_presentations', 'intervals/flash_250ms_presentations/active', 'intervals/flash_250ms_presentations/color', 'intervals/flash_250ms_presentations/contrast', 'intervals/flash_250ms_presentations/duration', 'intervals/flash_250ms_presentations/end_frame', 'intervals/flash_250ms_presentations/id', 'intervals/flash_250ms_presentations/is_change', 'intervals/flash_250ms_presentations/is_image_novel', 'intervals/flash_250ms_presentations/omitted', 'intervals/flash_250ms_presentations/rewarded', 'intervals/flash_250ms_presentations/start_frame', 'intervals/flash_250ms_presentations/start_time', 'intervals/flash_250ms_presentations/stimulus_block', 'intervals/flash_250ms_presentations/stimulus_index', 'intervals/flash_250ms_presentations/stimulus_name', 'intervals/flash_250ms_presentations/stop_time', 'intervals/flash_250ms_presentations/tags', 'intervals/flash_250ms_presentations/tags_index', 'intervals/flash_250ms_presentations/timeseries', 'intervals/flash_250ms_presentations/timeseries_index', 'intervals/gabor_20_deg_250ms_presentations', 'intervals/gabor_20_deg_250ms_presentations/active', 'intervals/gabor_20_deg_250ms_presentations/contrast', 'intervals/gabor_20_deg_250ms_presentations/duration', 'intervals/gabor_20_deg_250ms_presentations/end_frame', 'intervals/gabor_20_deg_250ms_presentations/id', 'intervals/gabor_20_deg_250ms_presentations/is_change', 'intervals/gabor_20_deg_250ms_presentations/is_image_novel', 'intervals/gabor_20_deg_250ms_presentations/omitted', 'intervals/gabor_20_deg_250ms_presentations/orientation', 'intervals/gabor_20_deg_250ms_presentations/position_x', 'intervals/gabor_20_deg_250ms_presentations/position_y', 'intervals/gabor_20_deg_250ms_presentations/rewarded', 'intervals/gabor_20_deg_250ms_presentations/spatial_frequency', 'intervals/gabor_20_deg_250ms_presentations/start_frame', 'intervals/gabor_20_deg_250ms_presentations/start_time', 'intervals/gabor_20_deg_250ms_presentations/stimulus_block', 'intervals/gabor_20_deg_250ms_presentations/stimulus_index', 'intervals/gabor_20_deg_250ms_presentations/stimulus_name', 'intervals/gabor_20_deg_250ms_presentations/stop_time', 'intervals/gabor_20_deg_250ms_presentations/tags', 'intervals/gabor_20_deg_250ms_presentations/tags_index', 'intervals/gabor_20_deg_250ms_presentations/temporal_frequency', 'intervals/gabor_20_deg_250ms_presentations/timeseries', 'intervals/gabor_20_deg_250ms_presentations/timeseries_index', 'intervals/spontaneous_presentations', 'intervals/spontaneous_presentations/active', 'intervals/spontaneous_presentations/duration', 'intervals/spontaneous_presentations/end_frame', 'intervals/spontaneous_presentations/id', 'intervals/spontaneous_presentations/is_change', 'intervals/spontaneous_presentations/is_image_novel', 'intervals/spontaneous_presentations/omitted', 'intervals/spontaneous_presentations/rewarded', 'intervals/spontaneous_presentations/start_frame', 'intervals/spontaneous_presentations/start_time', 'intervals/spontaneous_presentations/stimulus_block', 'intervals/spontaneous_presentations/stimulus_name', 'intervals/spontaneous_presentations/stop_time', 'intervals/spontaneous_presentations/tags', 'intervals/spontaneous_presentations/tags_index', 'intervals/spontaneous_presentations/timeseries', 'intervals/spontaneous_presentations/timeseries_index', 'intervals/trials', 'intervals/trials/aborted', 'intervals/trials/auto_rewarded', 'intervals/trials/catch', 'intervals/trials/change_frame', 'intervals/trials/change_image_name', 'intervals/trials/change_time', 'intervals/trials/correct_reject', 'intervals/trials/false_alarm', 'intervals/trials/go', 'intervals/trials/hit', 'intervals/trials/id', 'intervals/trials/initial_image_name', 'intervals/trials/is_change', 'intervals/trials/is_sham_change', 'intervals/trials/lick_times', 'intervals/trials/lick_times_index', 'intervals/trials/miss', 'intervals/trials/no_reward_epoch', 'intervals/trials/omitted_reward', 'intervals/trials/response_latency', 'intervals/trials/response_time', 'intervals/trials/reward_time', 'intervals/trials/reward_volume', 'intervals/trials/start_time', 'intervals/trials/stop_time', 'intervals/trials/trial_length', 'processing/eye_tracking_rig_metadata', 'processing/eye_tracking_rig_metadata/eye_tracking_rig_metadata', 'processing/licking', 'processing/licking/licks', 'processing/optotagging', 'processing/optotagging/optogenetic_stimulation', 'processing/optotagging/optotagging', 'processing/rewards', 'processing/rewards/autorewarded', 'processing/rewards/volume', 'processing/running', 'processing/running/dx', 'processing/running/speed', 'processing/running/speed_unfiltered', 'processing/stimulus', 'processing/stimulus/timestamps', 'stimulus/templates/dynamic_routing_image_set', 'units', 'units/PT_ratio', 'units/amplitude', 'units/amplitude_cutoff', 'units/cluster_id', 'units/cumulative_drift', 'units/d_prime', 'units/firing_rate', 'units/id', 'units/isi_label', 'units/isi_violations', 'units/isolation_distance', 'units/l_ratio', 'units/local_index', 'units/max_drift', 'units/nn_hit_rate', 'units/nn_miss_rate', 'units/peak_channel_id', 'units/presence_ratio', 'units/quality', 'units/recovery_slope', 'units/repolarization_slope', 'units/silhouette_score', 'units/snr', 'units/spike_amplitudes', 'units/spike_amplitudes_index', 'units/spike_times', 'units/spike_times_index', 'units/spread', 'units/structure_layer', 'units/velocity_above', 'units/velocity_below', 'units/waveform_duration', 'units/waveform_mean', 'units/waveform_mean_index']
probe device attributes: {'description': 'Neuropixels 1.0 Probe', 'manufacturer': 'imec', 'namespace': 'ndx-aibs-ecephys', 'neurodata_type': 'EcephysProbe', 'object_id': 'ade98fd2-9111-470c-a0c1-2001a1f27d87', 'probe_id': '627', 'sampling_rate': '30000.1328473262'}
electrode group attributes: {'description': 'Ecephys Electrode Group', 'has_lfp_data': 'False', 'lfp_sampling_rate': 'nan', 'location': 'See electrode locations', 'namespace': 'ndx-aibs-ecephys', 'neurodata_type': 'EcephysElectrodeGroup', 'object_id': 'b8453190-8a0c-4959-a9b0-1d2f44a42516', 'probe_id': '627'}
session metadata: {'behavior_session_id': '1173237009', 'behavior_session_uuid': 'afc5511f-a84f-424b-a32b-0c76ffbad82f', 'equipment_name': 'NP.1', 'namespace': 'ndx-aibs-behavior-ophys', 'neurodata_type': 'BehaviorMetadata', 'object_id': '493f1382-563b-44c4-9011-8d1e7e2603f7', 'project_code': 'Not Available', 'session_type': 'EPHYS_2', 'stimulus_frame_rate': '60.0'}

genotypes: ['Ai32(RCL-ChR2(H134R)_EYFP)/wt', 'Sst-IRES-Cre/wt;Ai32(RCL-ChR2(H134R)_EYFP)/wt', 'Vip-IRES-Cre/wt;Ai32(RCL-ChR2(H134R)_EYFP)/wt', 'wt/wt']
code session table columns: ['ecephys_session_id', 'behavior_session_id', 'date_of_acquisition', 'equipment_name', 'session_type', 'mouse_id', 'genotype', 'sex', 'project_code', 'age_in_days', 'probes_annotated_in_ccf', 'unit_count', 'channel_count', 'structure_acronyms', 'session_number', 'novel_image_id', 'has_lfp']
code probes table columns: ['Unnamed: 0.1', 'Unnamed: 0', 'id', 'name', 'location', 'sampling_rate', 'lfp_sampling_rate', 'has_lfp_data', 'session', 'inserted']

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 Outcome
link-code-tables partially reproduced (6 of 87 sessions hold other units than the code's table)
fig3b-surgical-outcomes blocked, data missing
fig3c-isi-through-implant blocked, data missing
fig3d-histology-intact blocked, data missing
tracks-rare-bleeding blocked, data missing
fig3fh-mri-deformation blocked, data missing
fig3i-learning-similar blocked, data missing
fig4b-units-areas-per-session reproduced
figs4ab-insertions-per-mouse partially reproduced (holes not testable)
scale-mice-sessions-insertions reproduced
penetration-success reproduced
fig4b-trajectory-consistency blocked, metadata insufficient
fig4c-drift-discontinuities-rare blocked, methods underspecified
fig4d-unit-drift reproduced
fig4d-qc-metrics-comparable not reproduced
qc-pass-fraction-similar reproduced
fig4e-unit-yield-stable reproduced
fig4e-vbn-yield-drops reproduced
fig4e-firing-rate-stable reproduced
fig4ef-rf-fraction-stable reproduced
fig4f-rf-match-retinotopy blocked, data missing
fig4g-visual-responses-similar reproduced
behavior-stable-across-days partially reproduced
figs4i-insertion-suppression not reproduced
figs4de-retraction-reduces-drift blocked, metadata insufficient
figs4f-retraction-yield-similar blocked, metadata insufficient
figs4gh-sharpened-probes blocked, metadata insufficient
figs3d-duragel-yield-stable blocked, data missing
fig5-bilateral-recordings blocked, data missing
fig6bc-cortical-silencing blocked, data missing
fig6ef-optotagging-mrn blocked, data missing
fig7a-mop-higher-frequency reproduced
fig7b-low-arousal partially reproduced
fig7b-visp-offset-locked reproduced
figs6-replay not reproduced
fig7cd-visp-coupling blocked, methods underspecified
fig7cd-mop-coupling blocked, methods underspecified
fig7e-lags-straddle-zero not reproduced
fig7f-visp-network blocked, methods underspecified
fig7f-mop-network blocked, methods underspecified
fig7-hippocampus-not-engaged not reproduced
fig8bc-cp-rf-location partially reproduced
fig8d-cp-rf-larger-response partially reproduced (effect holds; unit counts match only with the code's filter)
fig8d-response-latencies blocked, methods underspecified
fig8e-cp-rf-more-modulated reproduced
fig8f-cp-rf-higher-coherence blocked, methods underspecified

How the analysis differs from the paper's. Receptive fields are recomputed from the Gabor presentations with the AllenSDK chi-square bootstrap (1000 resamplings, own seed). Population rates for windows are cut from one 10 ms grid per session (window starts rounded to the grid). Alpha-event claims are run under the claims file's readings L1/L2, E1/E2, R1/R2 and two session sets; the code's batch outputs are not used. Events are detected on the smoothed population rate, and the autocorrelogram threshold, CCGs and coherence use the unsmoothed rate (taken from the code's printed event counts, which only the unsmoothed autocorrelogram matches). The Benjamini-Hochberg family of the pair test holds brain areas only. Pupil diameter is taken as the square root of window-mean area. The S4I fit uses 1-minute means and an exponential recovery a - b exp(-t / tau), fitted by least squares over 77.3 min (an assumption; shorter windows give longer time constants). The Figure S6 events are assigned to the last presentation before them, as the criterion says; a variant keeps post-offset events only.

What was found about the dataset. (1) In 6 of the 87 QC-passing sessions the units under each id differ from the paper's units table (metrics differ for 55 to 99.8% of units; RF p values uncorrelated; the files' spike counts fit the files' firing rates but not the table's), and in 5 of them 328 units of the paper's table sit on one probe per session that the files hold no units for. (2) 40 probe recordings in the 87 sessions (2004 units, nearly all on unannotated tracks) are not in the paper's tables; nothing in the files marks them. (3) No file records which probes were sharpened or retracted, the implant hole or trajectory of an insertion, or failed insertion attempts. (4) The dandiset has none of the Figure 3 material, the Figure 5 dual-hemisphere sessions or the Figure 6 VGAT-ChR2 and Vglut2-Cre mice. (5) Two sessions (1187475832, 1202438738) have no eye tracking. (6) The task uses four image stimuli per session, where the Methods describe 8 natural images.