Reproducing Li et al. (2015) from dandiset 000010¶
Paper. Li N, Chen T-W, Guo ZV, Gerfen CR, Svoboda K (2015). A motor cortex circuit for motor
planning and movement. Nature 519, 51-56.
doi:10.1038/nature14178. The claims file
(claims/000010/10.1038-nature14178.yaml) was written from the version of record (main text,
Methods and Extended Data legends) and checked by a second reader.
Dandiset. DANDI:000010, version 0.220126.1905. The paper cites no DANDI version (it predates the deposit and names the CRCNS alm-1 data). This is the only published version, and so the most recent one; the draft lists the same 158 assets and total size (its later modification was not examined further). The analysis streams the published version.
What the dandiset holds. 99 extracellular sessions from 19 mice (left ALM, sorted units, trial tables, behavioural events, a lick trace and, in 75 sessions, the laser power of the optogenetic tagging light delivered during behavioural trials) and 59 two-photon imaging planes from 4 mice (raw and neuropil fluorescence, ROI masks, PT/IT labels, trial tables). It holds no data from the photoinhibition and photoactivation experiments and no histology.
Questions. Seven questions were asked of a reader (questions/000010.yaml) and answered. The
paper settles none of them; each answer wrote the readings the paper allows into the claims file,
and every claim they touch is run under those readings here: the rule for a reaction time, whether
no-lick trials count, three details of the population dynamics, the length of the imaging response
epoch, the layer 5 selection, the trials with tagging light (readings t1 and t2 for every
electrophysiology claim of Figs 2 and 3), and the rule for the antidromic spike.
| Claim | Tested | Why not |
|---|---|---|
| behaviour-performance | yes | |
| fig1d-delay-inhibition-ipsi-bias | no | data missing: no photoinhibition mice or photostimulation trials |
| fig1d-sample-inhibition-smaller | no | data missing (as above) |
| units-classification | yes | |
| fig2-selective-fraction | yes | |
| fig2d-preparatory-perimovement | yes | |
| fig2c-selectivity-time-course | yes | |
| fig2c-population-dynamics | yes | |
| ed2c-error-trials-predict-choice | yes | |
| fig2e-no-contra-bias-spikes | yes | |
| ed1-fast-spiking-properties | yes | |
| tagging-counts | yes, in part | the failed-collision classes carry no label |
| antidromic-latencies | yes, in part | the activated neurons that failed the collision test carry no label |
| fig3e-pt-it-both-activity-types | yes | |
| fig3f-pt-contra-preferring | yes | |
| fig3f-pt-contra-selectivity | yes | |
| imaging-selectivity-counts | yes | |
| fig4f-spatially-intermingled | yes | |
| fig4h-pt-contra-imaging | no | metadata insufficient: no plane depth, so no layer 5 selection; all labelled cells are shown as a variant |
| reaction-time | no | methods underspecified: the threshold the paper leaves open sets the value; the readings are run and shown |
| fig5b-pt-delay-activation-contra-bias | no | data missing: no photostimulation trials |
| fig5b-pt-sample-activation-contra-bias | no | data missing (as above) |
| ed6b-pt-dose-response | no | data missing (as above) |
| ed6d-it-activation-inconsistent | no | data missing (as above) |
| ed3e-high-power-contra-licking | no | data missing (as above) |
| ed7-pt-activation-persistent | no | data missing (as above) |
| ed3-anatomy-it-pt-separate | no | data missing: no histology |
import time
import warnings
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
import alm
warnings.filterwarnings("ignore", category=RuntimeWarning)
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 200)
started = time.time()
assets = alm.list_assets()
meta = alm.version_metadata()
E = alm.load_all(assets, "ephys")
O = alm.load_all(assets, "ophys")
print(f"dandiset {alm.DANDISET} version {alm.VERSION}: {len(assets)} assets; loaded {len(E)} extracellular "
f"sessions and {len(O)} imaging planes in {time.time() - started:.0f} s")
dandiset 000010 version 0.220126.1905: 158 assets; loaded 99 extracellular sessions and 59 imaging planes in 477 s
Data check¶
Each check of the metadata against the values is stated under its output.
print("Archive summary:", meta["assetsSummary"]["numberOfSubjects"], "subjects,",
meta["assetsSummary"]["numberOfFiles"], "files; approaches:",
[a["name"] for a in meta["assetsSummary"]["approach"]])
print("Related resource:", [r["identifier"] for r in meta.get("relatedResource", [])])
print(assets.groupby("kind").agg(files=("path", "size"), subjects=("subject", "nunique"),
GB=("size", lambda x: round(x.sum() / 1e9, 1))).to_string())
info = pd.DataFrame([dict(kind="ephys", subject=s["subject"], genotype=s["genotype"], region=",".join(s["region"]),
optogenetics=",".join(s["optogenetics"])) for s in E] +
[dict(kind="ophys", subject=s["subject"], genotype=s["genotype"], region=s["plane_location"],
optogenetics="") for s in O])
print("\nSessions per subject, with genotype, recording region and optogenetic sites named in the files:")
print(info.groupby(["kind", "subject", "genotype", "region", "optogenetics"]).size().rename("sessions").to_string())
print("\nData objects in the files (acquisition, stimulus, processing, analysis):")
print(pd.Series([g for s in E + O for g in s["groups"]]).value_counts().to_string())
print("\nTrial table columns:", sorted({tuple(s["trial_colnames"]) for s in E + O}))
print("Unit table columns:", sorted({tuple(s["unit_colnames"]) for s in E}))
print("ROI table columns:", sorted({tuple(s["roi_colnames"]) for s in O}))
print("Imaging plane descriptions:", sorted({s["plane_description"] for s in O}))
Archive summary: 23 subjects, 158 files; approaches: ['electrophysiological approach', 'behavioral approach', 'microscopy approach; cell population imaging']
Related resource: ['doi:10.1038/nature14178']
files subjects GB
kind
ephys 99 19 36.2
ophys 59 4 3.8
Sessions per subject, with genotype, recording region and optogenetic sites named in the files:
kind subject genotype region optogenetics
ephys 210861 sim1-cre left M2 left ALM,left PONS 3
210862 sim1-cre left M2 left ALM,left PONS 3
210863 sim1-cre left M2 left ALM,left PONS 3
214427 rbp4-cre left M2 bilateral ALM,left PONS 4
214429 rbp4-cre left M2 bilateral ALM,left PONS 4
214430 rbp4-cre left M2 bilateral ALM,left PONS 4
218453 chr2-eyfp x tlx-cre left M2 5
218457 chr2-eyfp x tlx-cre left M2 6
218693 chr2-eyfp x sim1-cre left M2 3
219030 sim1-cre left M2 left ALM,left PONS 5
219031 sim1-cre left M2 left ALM,left PONS 5
219033 sim1-cre left M2 left ALM,left PONS 7
219036 sim1-cre left M2 left ALM,left PONS 7
219037 sim1-cre left M2 left ALM,left PONS 7
219038 sim1-cre left M2 left ALM,left PONS 8
219247 tlx-cre left M2 bilateral ALM 5
219248 tlx-cre left M2 bilateral ALM 7
219253 tlx-cre left M2 bilateral ALM 10
221977 tlx-cre left M2 bilateral ALM 3
ophys 216219 Rosa-lsl-tdtomato ALM 21
217951 ALM 11
221541 ALM 13
226244 ALM 14
Data objects in the files (acquisition, stimulus, processing, analysis):
acquisition/BehavioralEvents 99
acquisition/BehavioralTimeSeries 99
acquisition/images 59
processing/behavior/BehavioralEvents 59
processing/ophys/Fluorescence 59
processing/ophys/ImageSegmentation 59
stimulus/presentation/left PONS_aom_input_trace 48
stimulus/presentation/left PONS_laser_power 48
stimulus/presentation/bilateral ALM_aom_input_trace 36
stimulus/presentation/bilateral ALM_laser_power 36
Trial table columns: [('start_time', 'stop_time', 'task', 'task_protocol', 'trial_instruction', 'early_lick', 'outcome')]
Unit table columns: [('sampling_rate', 'quality', 'posx', 'posy', 'cell_type', 'spike_times', 'obs_intervals', 'electrodes', 'electrode_group', 'waveform_mean', 'waveform_sd')]
ROI table columns: [('cell_type', 'neuropil_mask', 'included', 'image_mask')]
Imaging plane descriptions: ['Imaging session for PT and IT neurons during audio delay task']
The files hold 99 extracellular sessions from 19 mice and 59 imaging planes from 4 mice, the numbers of the paper (99 sessions and 19 mice; 59 fields of view and 4 mice). Every extracellular session is in left ALM ("left M2"). The only stimulus records are the laser power and AOM input traces of the tagging light in 75 sessions (next cells); the trial tables have no column for photostimulation. The VGAT-ChR2-EYFP and PV-ires-Cre x ReaChR mice of Figure 1 are not among the subjects, and the three Ai32 crosses (the photoactivation lines of Figure 5) have no stimulus record, so no claim on photoinhibition or photoactivation can be tested. The imaging mice are given the genotype "Rosa-lsl-tdtomato"; the Methods use C57Bl/6Crl mice. No imaging plane gives its depth.
rows = []
for kind, S in (("ephys", E), ("ophys", O)):
for i, s in enumerate(S):
tr = s["trials"].assign(kind=kind, session=i, subject=s["subject"])
rows.append(tr)
T = pd.concat(rows, ignore_index=True)
T["matched"] = T["sample"].notna()
print("Trials by kind, instruction, outcome and early-lick flag; `matched` counts trials with a sample event "
f"{alm.MATCH_WIN[0]} to {alm.MATCH_WIN[1]} s after the trial start:")
print(T.groupby(["kind", "instruction", "outcome", "early"]).matched.agg(trials="size", matched="sum").to_string())
off = (T["sample"] - T.start)[T.matched]
print("\nSample event minus trial start (s), by kind:")
print(off.groupby(T.kind[T.matched]).describe().round(4).to_string())
n_ev = {k: sum(s["n_events"]["sample"] for s in S) for k, S in (("ephys", E), ("ophys", O))}
print("\nSample events in the files:", n_ev, "; matched to a trial:", T.groupby("kind").matched.sum().to_dict())
# Performing trials without events: their spikes
unm = []
for i, s in enumerate(E):
tr = s["trials"]
m = tr["sample"].isna() & tr.instruction.isin(["left", "right"])
if m.any():
allsp = np.sort(np.concatenate(s["spikes"]))
n = alm.counts(allsp, tr.start[m].values, tr.stop[m].values)
unm.append(pd.DataFrame(dict(session=i, subject=s["subject"], instruction=tr.instruction[m],
outcome=tr.outcome[m], early=tr.early[m], spikes=n,
dur=(tr.stop - tr.start)[m].round(2))))
unm = pd.concat(unm)
print(f"\nExtracellular trials with a lick instruction but no events: {len(unm)} in {unm.session.nunique()} sessions")
print(unm.groupby(["instruction", "outcome", "early"]).agg(trials=("spikes", "size"), with_spikes=("spikes", lambda x: int((x > 0).sum())),
max_spikes=("spikes", "max"), lasting_5_5_s=("dur", lambda x: int((x == 5.5).sum()))).to_string())
print("Per session, fraction of lick-instruction trials without events (top 10):")
frac = unm.groupby("session").size() / pd.Series({i: s["trials"].instruction.isin(["left", "right"]).sum() for i, s in enumerate(E)})
print(frac.dropna().sort_values(ascending=False).head(10).round(3).to_string())
d = (T.go - T["sample"])
ne = T.early.isin(["no early", "False"]) & T.matched & T.instruction.isin(["left", "right"])
print("\nGo cue minus sample event (s) in trials without an early lick:")
print(d[ne].describe().round(4).to_string())
print("Trials without an early lick whose go cue is not 2.6 s after the sample event (dropped):",
int((ne & ((d - 2.6).abs() >= 0.01)).sum()), "of", int(ne.sum()))
bad = ne & ((d - 2.6).abs() >= 0.01)
print(" of these, by kind:", T.kind[bad].value_counts().to_dict(), "; go cue minus sample event (s) from",
round(float(d[bad].min()), 4), "to", round(float(d[bad].max()), 4), "; shorter than 2.6 s:", int((bad & (d < 2.6)).sum()),
", longer:", int((bad & (d > 2.6)).sum()))
print(" without an early lick and with no go cue:", T.kind[ne & d.isna()].value_counts().to_dict())
print("Go cue minus sample event (s) in early-lick trials:", d[T.early.eq("early") & T.matched].describe().round(2).to_dict())
print("Delay event minus sample event (s), median by kind:", (T.delay - T["sample"]).groupby(T.kind).median().round(3).to_dict())
print("\nEarly-lick flags in the imaging files:", T[T.kind == "ophys"].early.value_counts().to_dict())
# Trials with a lick instruction but no events, by mouse, and where they fall in the session
ph, pos = [], []
for i, s in enumerate(E):
tr = s["trials"].reset_index(drop=True)
m = tr["sample"].isna() & tr.instruction.isin(["left", "right"])
fill = m & tr.instruction.eq("right") & tr.outcome.eq("hit") & tr.early.eq("no early") & ((tr.stop - tr.start).round(2) == 5.5)
ph.append(dict(subject=s["subject"], genotype=s["genotype"], session=i, light_session=len(s["laser"]) > 0,
trials=int(tr.instruction.isin(["left", "right"]).sum()), no_events=int(m.sum()), right_hit_5_5_s=int(fill.sum())))
pos += list(np.where(m)[0] / len(tr))
PH = pd.DataFrame(ph)
print("\nTrials with a lick instruction but no events, by mouse:")
print(PH.groupby(["subject", "genotype"]).agg(sessions=("session", "size"), light_sessions=("light_session", "sum"), trials=("trials", "sum"),
no_events=("no_events", "sum"), right_hit_5_5_s=("right_hit_5_5_s", "sum"),
sessions_with_them=("no_events", lambda x: int((x > 0).sum()))).to_string())
print("In sessions with a light record:", int(PH.no_events[PH.light_session].sum()), "; without:", int(PH.no_events[~PH.light_session].sum()))
hist = np.histogram(pos, bins=10, range=(0, 1))[0]
print("Position within the session (trial index / trials), counts per tenth:", hist.tolist(),
f"; first or last tenth: {hist[0] + hist[-1]} of {hist.sum()} ({(hist[0] + hist[-1]) / hist.sum():.2f})")
Trials by kind, instruction, outcome and early-lick flag; `matched` counts trials with a sample event 0.5 to 0.7 s after the trial start:
trials matched
kind instruction outcome early
ephys left hit early 1512 1507
no early 9217 9177
ignore early 12 12
no early 212 203
miss early 393 393
no early 3038 3025
non-performing non-performing no early 1654 0
right hit early 1368 1364
no early 10769 9322
ignore early 9 9
no early 224 213
miss early 382 380
no early 2649 2640
ophys left hit False 1659 1659
ignore False 44 44
miss False 223 223
non-performing non-performing False 590 30
right hit False 1574 1574
ignore False 12 11
miss False 417 417
Sample event minus trial start (s), by kind:
count mean std min 25% 50% 75% max
kind
ephys 28245.0 0.5723 0.0005 0.5240 0.5722 0.5723 0.5725 0.5733
ophys 3958.0 0.5836 0.0075 0.5788 0.5828 0.5830 0.5832 0.6995
Sample events in the files: {'ephys': 28465, 'ophys': 4519} ; matched to a trial: {'ephys': 28245, 'ophys': 3958}
Extracellular trials with a lick instruction but no events: 1540 in 78 sessions
trials with_spikes max_spikes lasting_5_5_s
instruction outcome early
left hit early 5 5 368 0
no early 40 40 634 40
ignore no early 9 9 680 9
miss no early 13 13 434 13
right hit early 4 3 407 0
no early 1447 102 661 1447
ignore no early 11 11 632 11
miss early 2 2 203 0
no early 9 9 791 9
Per session, fraction of lick-instruction trials without events (top 10):
session
12 0.275
16 0.245
43 0.200
15 0.183
11 0.172
14 0.164
78 0.136
18 0.132
19 0.123
17 0.116
Go cue minus sample event (s) in trials without an early lick:
count 28431.0000
mean 2.5975
std 0.0735
min 0.0283
25% 2.6006
50% 2.6008
75% 2.6008
max 2.6778
Trials without an early lick whose go cue is not 2.6 s after the sample event (dropped): 66 of 28508
of these, by kind: {'ephys': 66} ; go cue minus sample event (s) from 0.0283 to 2.6778 ; shorter than 2.6 s: 65 , longer: 1
without an early lick and with no go cue: {'ephys': 75, 'ophys': 2}
Go cue minus sample event (s) in early-lick trials: {'count': 3635.0, 'mean': 3.62, 'std': 1.2, 'min': 0.04, '25%': 2.89, '50%': 3.36, '75%': 3.85, 'max': 17.34}
Delay event minus sample event (s), median by kind: {'ephys': 1.3, 'ophys': 1.2}
Early-lick flags in the imaging files: {'False': 4519}
Trials with a lick instruction but no events, by mouse:
sessions light_sessions trials no_events right_hit_5_5_s sessions_with_them
subject genotype
210861 sim1-cre 3 0 1239 0 0 0
210862 sim1-cre 3 0 870 18 15 2
210863 sim1-cre 3 0 946 0 0 0
214427 rbp4-cre 4 4 1411 217 209 4
214429 rbp4-cre 4 4 1947 297 289 4
214430 rbp4-cre 4 4 1166 139 137 4
218453 chr2-eyfp x tlx-cre 5 0 1419 1 0 1
218457 chr2-eyfp x tlx-cre 6 0 1770 10 0 5
218693 chr2-eyfp x sim1-cre 3 0 889 7 4 3
219030 sim1-cre 5 5 1573 74 70 5
219031 sim1-cre 5 5 1407 119 110 5
219033 sim1-cre 7 7 1992 42 36 5
219036 sim1-cre 7 7 1791 23 21 5
219037 sim1-cre 7 7 2159 40 39 5
219038 sim1-cre 8 8 2275 132 126 8
219247 tlx-cre 5 5 1634 151 145 5
219248 tlx-cre 7 7 2669 221 207 7
219253 tlx-cre 10 9 1729 14 5 7
221977 tlx-cre 3 3 899 35 34 3
In sessions with a light record: 1502 ; without: 38
Position within the session (trial index / trials), counts per tenth: [231, 18, 46, 97, 119, 219, 105, 100, 82, 523] ; first or last tenth: 754 of 1540 (0.49)
Trials are matched to their behavioural events by the sample event, which falls 0.572 s (extracellular) or 0.583 s (imaging) after the trial start in almost every trial. In the extracellular files, the trials that carry no events are the "non-performing" trials and 1,540 trials with a lick instruction. Of these, 1,447 are marked "right", "hit" and "no early", last exactly 5.5 s, and 1,345 of them hold no spike from any unit; in some sessions such trials are a quarter of the trials. They look like placeholders for trials that were removed, filled with default values. Almost all (1,502 of 1,540) are in the 75 sessions with a record of the tagging light; 754 of them (49%) fall in the first or last tenth of a session (231 and 523), and 7 are in the Ai32 mouse 218693 (4 of the placeholder kind), so they are not the missing photoactivation trials of Figure 5 and Extended Data Fig. 7. The other 93 hold spikes but no sample event in the matching window. All 1,540 are left out of every analysis here, the behaviour included; counted as given, the placeholders would raise performance. In trials without an early lick, the go cue follows the sample event by 2.6 s, the 1.3 s sample and 1.3 s delay epochs of the Methods. 66 trials break this, all extracellular (of 28,508 matched trials without an early lick, extracellular and imaging together): in 65 the go cue comes sooner (as little as 0.03 s after the sample event) and in one later (2.68 s). They are dropped, as are the 75 extracellular and 2 imaging trials without an early lick that have no go cue. In early-lick trials the go cue comes later, consistent with the delay being restarted after an early lick. The imaging files flag no trial as an early lick, so early licks cannot be excluded there.
rows = []
for i, s in enumerate(E):
tr = s["trials"]
for k, v in s["laser"].items():
on = np.sort(v["onsets"])
rows.append(dict(session=i, subject=s["subject"], genotype=s["genotype"], series=k, unit=v["unit"],
max=round(v["max"], 1), pulses=len(on), segments=v["n_segments"],
width_ms=np.round(np.median(v["width_ms"]), 2) if len(v["width_ms"]) else np.nan,
pair_gap_ms=round(1000 * np.median(np.diff(on)[np.diff(on) < 0.05]), 1) if len(on) > 1 else np.nan,
pair_period_s=round(np.median(np.diff(alm.pair_onsets(on))), 3) if len(on) > 2 else np.nan,
light_trials=int(tr.light.sum()), trials=len(tr), outside=s["pulses_outside_trials"]))
LZ = pd.DataFrame(rows)
print("Sessions with a laser power series:", LZ.session.nunique(), "of", len(E))
print(LZ.groupby(["genotype", "series", "unit", "max"]).agg(sessions=("session", "size"), pulses=("pulses", "sum"),
light_trials=("light_trials", "sum"), trials=("trials", "sum"), width_ms=("width_ms", "median"),
pair_gap_ms=("pair_gap_ms", "median"), pair_period_s=("pair_period_s", "median"), outside=("outside", "sum")).to_string())
print("Genotypes without any stimulus series:", sorted({s["genotype"] for s in E} - set(LZ.genotype)))
seg = sorted({int(x) for s in E for v in s["laser"].values() for x in v["segment_samples"]})
print("Samples per stored segment of the laser series:", seg, "; rate (Hz):", sorted({round(v["rate"]) for s in E for v in s["laser"].values()}))
TL = pd.concat([s["trials"].assign(session=i) for i, s in enumerate(E)], ignore_index=True)
print("\nTrials with light, by outcome (extracellular, trials with events):")
print(pd.crosstab([TL.outcome[TL["sample"].notna()]], TL.light[TL["sample"].notna()]).to_string())
pw = np.concatenate([v["power"] for s in E for v in s["laser"].values()])
print("Laser power at the pulses (file units):", np.unique(np.round(pw)).tolist())
Sessions with a laser power series: 75 of 99
sessions pulses light_trials trials width_ms pair_gap_ms pair_period_s outside
genotype series unit max
rbp4-cre bilateral ALM_laser_power watts 47.1 12 16368 1361 4781 1.0 11.0 0.5 460
left PONS_laser_power watts 82.0 9 11074 1087 3665 1.0 11.0 0.5 460
sim1-cre left PONS_laser_power watts 82.0 39 65880 3277 11701 1.0 11.0 0.5 486
tlx-cre bilateral ALM_laser_power watts 47.1 24 43937 2198 7022 1.0 11.0 0.5 0
Genotypes without any stimulus series: ['chr2-eyfp x sim1-cre', 'chr2-eyfp x tlx-cre']
Samples per stored segment of the laser series: [50000, 100000] ; rate (Hz): [10000]
Trials with light, by outcome (extracellular, trials with events):
light False True
outcome
hit 16587 4783
ignore 179 258
miss 4837 1601
Laser power at the pulses (file units): [47.0, 82.0]
In 75 sessions (the virus-injected Sim1, Rbp4 and Tlx mice; 84 series, as the Rbp4 sessions have
one for each site), stimulus/presentation holds a laser power series stored in 5 s segments at
10 kHz, one per trial with light (10 s where two such trials follow without a gap). The two Tlx x Ai32 mice, whose 8 IT units the files
label, have no light record. The pulses are
1 ms wide, in pairs 10 ms apart, every 0.5 s, at 82 (left pons) or 47.1 (bilateral ALM): the
antidromic tagging protocol of the Methods ("Pairs of laser pulses (1 ms duration, 47-82 mW peak
power, separated by 10 ms) were deployed every 500ms"). The series are labelled "watts", but the
values match the Methods' milliwatts. The light was given during behavioural trials, which the
trial tables do not flag; here a trial has light when a pulse falls in its window. The paper does
not say whether these trials belong in the task analyses; the claims file names two readings for
every electrophysiology claim of Figs 2 and 3, (t1) all trials, light or not, the literal reading
of the Methods, and (t2) only trials without the light, either of which may decide. The behaviour claim asks for trials
without photostimulation, so it leaves them out.
U = alm.unit_table(E)
print("Deposited units:", len(U), "; quality labels:", pd.Series(np.concatenate([s["quality"] for s in E])).value_counts().to_dict())
print("Sampling rate of the waveforms (Hz):", sorted(set(np.concatenate([s["unit_rate"] for s in E]))),
"; waveform length (samples):", sorted({s["waveform"].shape[1] for s in E}))
print("\nUnits by file label, width class (trough to peak of the mean waveform) and duplicate flag:")
print(U.groupby(["label", "width_class", "is_duplicate"]).size().to_string())
print("\nEvery PT or IT unit has an identical spike train in a unit labelled Pyr in the same file:",
int(U.is_duplicate.sum()), "of", int(U.label.isin(["PT", "IT"]).sum()))
print("Spike widths (ms) by label:")
print(U.groupby("label").width.describe().round(3).to_string())
print("Units whose waveform peak after the trough is the last sample:", int(U.peak_at_end.sum()),
f"(the waveform ends {(28 - 15) / 19531.25 * 1000:.3f} ms after the trough at sample 15)")
print("Trough sample index:", sorted({int(np.argmin(w)) for s in E for w in s["waveform"]}))
spans = np.concatenate([np.diff(s["obs_intervals"], axis=1).ravel() for s in E])
print("\nObservation intervals per unit: lengths (s)", np.unique(spans.round(3)))
inside, where = [], []
for i, s in enumerate(E):
tr = s["trials"]
allsp = np.sort(np.concatenate(s["spikes"]))
k = np.searchsorted(tr.start.values, allsp, side="right") - 1
ins = (k >= 0) & (allsp <= tr.stop.values[np.clip(k, 0, None)] + 1e-6)
inside.append(np.mean(ins))
where.append(dict(session=i, subject=s["subject"], spikes=len(allsp), outside=int((~ins).sum()),
before_first=int((allsp < tr.start.min()).sum()), after_last=int((allsp > tr.stop.max() + 1e-6).sum()),
gaps_between_trials=int((tr.start.values[1:] - tr.stop.values[:-1] > 0.01).sum())))
inside = np.array(inside)
WH = pd.DataFrame(where)
WH["between"] = WH.outside - WH.before_first - WH.after_last
print("Fraction of spikes inside a trial window [start, stop], per session: min", round(inside.min(), 4),
"median", round(float(np.median(inside)), 4), "; sessions with all spikes inside:", int((inside > 0.99999).sum()),
", with at least 1% outside:", int((inside < 0.99).sum()), "of", len(inside))
print("Spikes outside every trial window: before the first trial", int(WH.before_first.sum()), ", after the last",
int(WH.after_last.sum()), ", between trials", int(WH.between.sum()), "of", int(WH.outside.sum()))
print("Sessions with at least 1% of spikes outside the trial windows:")
print(WH[inside < 0.99].assign(inside=inside[inside < 0.99].round(4)).to_string(index=False))
Deposited units: 1441 ; quality labels: {'good': 1441}
Sampling rate of the waveforms (Hz): [np.float64(19531.25)] ; waveform length (samples): [29]
Units by file label, width class (trough to peak of the mean waveform) and duplicate flag:
label width_class is_duplicate
FS FS False 124
IT Pyr True 27
PT Pyr True 45
Pyr Pyr False 1245
Every PT or IT unit has an identical spike train in a unit labelled Pyr in the same file: 72 of 72
Spike widths (ms) by label:
count mean std min 25% 50% 75% max
label
FS 124.0 0.225 0.046 0.154 0.154 0.256 0.256 0.256
IT 27.0 0.647 0.025 0.614 0.614 0.666 0.666 0.666
PT 45.0 0.606 0.048 0.461 0.563 0.614 0.614 0.666
Pyr 1245.0 0.636 0.043 0.461 0.614 0.666 0.666 0.666
Units whose waveform peak after the trough is the last sample: 746 (the waveform ends 0.666 ms after the trough at sample 15)
Trough sample index: [15]
Observation intervals per unit: lengths (s) [5.5]
Fraction of spikes inside a trial window [start, stop], per session: min 0.7019 median 0.9997 ; sessions with all spikes inside: 11 , with at least 1% outside: 2 of 99
Spikes outside every trial window: before the first trial 0 , after the last 0 , between trials 18174 of 18174
Sessions with at least 1% of spikes outside the trial windows:
session subject spikes outside before_first after_last gaps_between_trials between inside
14 214429 36634 8851 0 0 573 8851 0.7584
43 219031 18711 5577 0 0 143 5577 0.7019
The deposited units are 1,245 labelled "Pyr", 124 "FS", 45 "PT" and 27 "IT". Every PT and IT unit is a second copy of a unit labelled Pyr in the same file (identical spike trains), so the recordings hold 1,369 distinct units; duplicates are counted once, and the PT/IT label is carried to its Pyr twin. The spike widths computed from the waveforms put every Pyr unit above 0.45 ms and every FS unit below 0.35 ms, so the labels agree with the width rule, and no unit has an intermediate width: the 39 intermediate units of the Methods are not in the dandiset. The waveforms are 29 samples with the trough at sample 15, so widths above 0.666 ms cannot be measured; this does not affect the classification at 0.35 and 0.45 ms. Spikes are stored almost only within the 5.5 s trial windows, which run from 0.57 s before the sample event to 2.33 s after the go cue in trials without an early lick and cover every epoch used here. In 97 of 99 sessions at least 99% of the spikes fall in a trial window (all of them in 11). In two sessions (mice 214429 and 219031) 24% and 30% fall outside, all of them between trials, where those sessions have gaps between the windows; no spike comes before the first trial or after the last.
# Clock check, extracellular: selectivity of each session's putative pyramidal units before the
# sample event (where no task information exists) and in the response epoch.
P = U[(U.width_class == "Pyr") & ~U.is_duplicate]
rows = []
for si in sorted(P.session.unique()):
Ps = P[P.session == si]
pre = alm.window_counts(E, Ps, [-0.5], width=0.5)
resp = alm.window_counts(E, Ps, [2.6], width=1.3)
p_pre = [alm.ttest_p(R[:, 0], L[:, 0]) for R, L in pre.values()]
p_resp = [alm.ttest_p(R[:, 0], L[:, 0]) for R, L in resp.values()]
rows.append(dict(session=si, subject=E[si]["subject"], units=len(Ps),
sel_pre=np.mean(np.array(p_pre) < 0.05), sel_resp=np.mean(np.array(p_resp) < 0.05)))
clock = pd.DataFrame(rows)
print("Fraction of units with lick right vs lick left t-test P < 0.05, correct trials, over all sessions:")
print(clock[["sel_pre", "sel_resp"]].describe().round(3).to_string())
print("Sessions where the response-epoch fraction does not exceed the pre-sample fraction:")
print(clock[clock.sel_resp <= clock.sel_pre].to_string(index=False))
Fraction of units with lick right vs lick left t-test P < 0.05, correct trials, over all sessions:
sel_pre sel_resp
count 99.000 99.000
mean 0.044 0.556
std 0.055 0.182
min 0.000 0.182
25% 0.000 0.429
50% 0.000 0.545
75% 0.089 0.692
max 0.200 1.000
Sessions where the response-epoch fraction does not exceed the pre-sample fraction:
Empty DataFrame
Columns: [session, subject, units, sel_pre, sel_resp]
Index: []
# Clock check, imaging: rank sum test between trial types before the sample event and in the
# response epoch, per plane, included ROIs, correct trials.
rows = []
for i, pl in enumerate(O):
X, rel, right, dropped = alm.plane_trials(pl, "correct")
inc = pl["included"]
pre = X[:, (rel >= -0.5) & (rel < 0), :][:, :, inc].mean(1)
resp = X[:, (rel >= 2.6) & (rel < 3.9), :][:, :, inc].mean(1)
rows.append(dict(plane=i, subject=pl["subject"], rois=int(inc.sum()), trials=len(right), dropped=dropped,
sel_pre=np.mean(stats.ranksums(pre[right], pre[~right], axis=0).pvalue < 0.05),
sel_resp=np.mean(stats.ranksums(resp[right], resp[~right], axis=0).pvalue < 0.05)))
iclock = pd.DataFrame(rows)
print("Fraction of included ROIs with rank sum P < 0.05:")
print(iclock[["sel_pre", "sel_resp"]].describe().round(3).to_string())
print("Planes where the response-epoch fraction does not exceed the pre-sample fraction:")
print(iclock[iclock.sel_resp <= iclock.sel_pre].to_string(index=False))
print("Trials dropped for lack of 0.5 s of frames before the trial start:", int(iclock.dropped.sum()))
Fraction of included ROIs with rank sum P < 0.05:
sel_pre sel_resp
count 59.000 59.000
mean 0.064 0.438
std 0.034 0.110
min 0.000 0.136
25% 0.040 0.367
50% 0.059 0.438
75% 0.086 0.500
max 0.145 0.692
Planes where the response-epoch fraction does not exceed the pre-sample fraction:
Empty DataFrame
Columns: [plane, subject, rois, trials, dropped, sel_pre, sel_resp]
Index: []
Trials dropped for lack of 0.5 s of frames before the trial start: 34
print("Imaging rate (Hz):", sorted({round(pl["rate"], 3) for pl in O}), "; first frame time (s):",
sorted({round(pl["t0"], 4) for pl in O}))
dur = [(pl["t0"] + len(pl["F"]) / pl["rate"], pl["trials"].stop.max()) for pl in O]
print("Last frame time minus last trial stop (s): min", round(min(a - b for a, b in dur), 2), "max", round(max(a - b for a, b in dur), 2))
roi = pd.DataFrame(dict(type=np.concatenate([pl["roi_type"] for pl in O]),
included=np.concatenate([pl["included"] for pl in O]),
subject=np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])))
print("ROIs:", len(roi), "; included:", int(roi.included.sum()))
print(pd.crosstab([roi.subject, roi.type], roi.included).to_string())
Fall = np.concatenate([(pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]].min(0) for pl in O])
print("Included ROIs whose neuropil-corrected F (r = 0.7) reaches 0 or below:", int((Fall <= 0).sum()))
print("Raw F range (a.u.):", round(min(pl["F"].min() for pl in O), 1), "to", round(max(pl["F"].max() for pl in O), 1))
ratio = np.concatenate([np.median(pl["F"] / np.maximum(pl["Fnp"], 1e-9), 0)[pl["included"]] for pl in O])
print("Included ROIs whose median F / F_neuropil is below 1.05 (Methods: cells at least 5% brighter than the neuropil):",
int((ratio < 1.05).sum()), "of", len(ratio))
f0bad = 0; f0all = 0
for pl in O:
X, rel, right, _ = alm.plane_trials(pl, "nonearly")
f0all += X.shape[0] * int(pl["included"].sum())
f0bad += int((~np.isfinite(X[:, :, pl["included"]]).all(1)).sum())
print("Included ROI-trials with a non-finite DF/F (F0 of 0):", f0bad, "of", f0all)
Imaging rate (Hz): [14.84] ; first frame time (s): [0.0674]
Last frame time minus last trial stop (s): min 2.57 max 20.22
ROIs: 5215 ; included: 4706
included False True
subject type
216219 IT 156 374
interneuron 0 2
unknown 83 1121
217951 IT 13 95
PT 0 34
unknown 31 646
221541 IT 60 233
PT 20 286
Pyr 0 23
interneuron 0 7
unknown 27 599
226244 IT 38 141
PT 10 210
unknown 71 935
Included ROIs whose neuropil-corrected F (r = 0.7) reaches 0 or below: 3403
Raw F range (a.u.): 0.0 to 2631.2
Included ROIs whose median F / F_neuropil is below 1.05 (Methods: cells at least 5% brighter than the neuropil): 424 of 4706
Included ROI-trials with a non-finite DF/F (F0 of 0): 0 of 305349
lick = alm.lick_trace_check(assets[assets.kind == "ephys"].asset_id.iloc[0])
print({k: v for k, v in lick.items() if k not in ("mean_trace", "t_ms")})
print("Mean lick trace around the go cue of correct trials (a.u.), 5 ms bins:")
print(pd.Series(lick["mean_trace"].round(4), index=lick["t_ms"]).iloc[30:70].to_string())
{'n_samples': 21100000, 'rate': np.float64(9999.999999668034), 'n_segments': 422, 'seg_len': array([50000]), 'seg_start_is_trial_start': np.float64(1.0), 'n_trials': 423, 'unit': 'a.u.', 'description': "Time-series of the animal's tongue movement when licking"}
Mean lick trace around the go cue of correct trials (a.u.), 5 ms bins:
-50.0 0.1225
-45.0 0.1225
-40.0 0.1223
-35.0 0.1223
-30.0 0.1222
-25.0 0.1221
-20.0 0.1220
-15.0 0.1219
-10.0 0.1219
-5.0 0.1216
0.0 0.1212
5.0 0.1218
10.0 0.1218
15.0 0.1218
20.0 0.1218
25.0 0.1218
30.0 0.1220
35.0 0.1225
40.0 0.1238
45.0 0.1256
50.0 0.1278
55.0 0.1303
60.0 0.1324
65.0 0.1343
70.0 0.1357
75.0 0.1370
80.0 0.1386
85.0 0.1404
90.0 0.1423
95.0 0.1432
100.0 0.1427
105.0 0.1441
110.0 0.1433
115.0 0.1427
120.0 0.1425
125.0 0.1420
130.0 0.1416
135.0 0.1405
140.0 0.1395
145.0 0.1387
The imaging rate is 14.84 Hz (Methods, 15 Hz), and the fluorescence spans every trial. There are 5,215 ROIs, of which 4,706 are marked included, the paper's cell count; the inclusion flag is taken to stand for the Methods' selection, which the files describe only as "whether to include this roi into later analyses" (fig4h section) (some included ROIs are not 5% brighter than their neuropil by the median ratio; counts above). The neuropil-corrected signal of most included ROIs touches 0 or below in some frame, which is noise on a small signal; F0 is averaged over 0.5 s, and the count of ROI-trials where it is exactly 0 is printed above; the imaging-selectivity-counts section shows how many are near 0 and what leaving them out changes. Besides PT, IT and unknown, the files label 23 ROIs "Pyr" and 9 "interneuron", labels the paper does not describe. Both clock checks show selectivity in the response epoch far above the pre-sample level in almost every session and plane (sessions and planes where it does not are listed above), so the spike times, the fluorescence and the trial events are consistent with one clock. The lick trace is stored in 5 s segments at 10 kHz, one per trial from its start, and rises within about 50 to 100 ms after the go cue, consistent with the mouth-movement signal of the Methods; it is the signal used for reaction-time.
behaviour-performance¶
Mice in the object location discrimination task performed at 78.4% correct on average, and fewer than 13% of responses came before the go cue.
Criterion: per cent correct excluding lick early trials, on trials without photostimulation; mean formed (a) over all trials pooled, (b) over sessions, (c) over mice; within 78.4 +/- 7.84%, and the fraction of lick early trials below 14.3% under the same reading. Trials with tagging light are left out (the data check finds them from the laser series); the result with them is a variant. Trials without events are left out (see the data check). Early licks are flagged only in the extracellular files, so those 99 sessions are the main test and the imaging sessions a variant. Trials without a lick in the response window (outcome "ignore") are, under the claims file's readings, (x) kept in the denominator as not correct, the literal reading of the Methods, or (y) left out; either may decide.
def behaviour(S, kind, light="none", timing=False):
rows = []
for i, s in enumerate(S):
tr = s["trials"]
has = tr["sample"].notna() & tr.instruction.isin(["left", "right"])
if light == "none" and "light" in tr:
has &= ~tr.light
early = tr.early.eq("early")
if timing: # drop the trials without an early lick whose go cue is not 2.6 s after the sample event
has &= early | ((tr.go - tr["sample"] - (alm.SAMPLE_LEN + alm.DELAY_LEN)).abs() < 0.01)
rows.append(dict(kind=kind, session=i, subject=s["subject"], trials=int(has.sum()),
hit=int((has & ~early & (tr.outcome == "hit")).sum()),
miss=int((has & ~early & (tr.outcome == "miss")).sum()),
ignore=int((has & ~early & (tr.outcome == "ignore")).sum()),
early=int((has & early).sum())))
return pd.DataFrame(rows)
out = []
early = {}
for light in ("none", "any"):
B = pd.concat([behaviour(E, "ephys", light), behaviour(O, "ophys", light)], ignore_index=True)
for kind in ("ephys", "ophys", "both"):
d = B if kind == "both" else B[B.kind == kind]
for ign in ("(x) not correct", "(y) left out"):
den = d.hit + d.miss + (d.ignore if ign.startswith("(x)") else 0)
m = d.assign(den=den).groupby("subject")[["hit", "den"]].sum()
out.append(dict(light_trials="left out" if light == "none" else "kept", sessions=kind, no_lick=ign,
n_sessions=len(d), n_mice=d.subject.nunique(), trials=int(d.trials.sum()),
a_pooled=100 * d.hit.sum() / den.sum(), b_sessions=100 * (d.hit / den).mean(),
c_mice=100 * (m.hit / m.den).mean()))
d = B[B.kind == "ephys"]
early[light] = dict(a_pooled=100 * d.early.sum() / d.trials.sum(), b_sessions=100 * (d.early / d.trials).mean(),
c_mice=100 * (d.groupby("subject").early.sum() / d.groupby("subject").trials.sum()).mean())
perf = pd.DataFrame(out)
print("Per cent correct (non-early trials with events), readings (a) pooled, (b) mean over sessions, (c) mean over mice:")
print(perf.round(1).to_string(index=False))
print("\nPer cent of trials with an early lick (extracellular sessions), light trials left out:",
{k: round(v, 1) for k, v in early["none"].items()}, "; kept:", {k: round(v, 1) for k, v in early["any"].items()})
B = pd.concat([behaviour(E, "ephys"), behaviour(O, "ophys")], ignore_index=True)
d = B[B.kind == "ephys"]
print("Paper: 78.4% correct (tolerance 70.56 to 86.24), early responses below 13% (limit 14.3%)")
print("\nPer mouse, extracellular sessions (no-lick trials left out, reading y):")
pm = d.groupby("subject")[["trials", "hit", "miss", "early"]].sum()
pm["pct_correct"] = 100 * pm.hit / (pm.hit + pm.miss)
pm["pct_early"] = 100 * pm.early / pm.trials
print(pm.round(1).to_string())
print("Range of per-session per cent correct:", round((100 * d.hit / (d.hit + d.miss)).min(), 1), "to",
round((100 * d.hit / (d.hit + d.miss)).max(), 1))
# Variant: the 2.6 s timing filter of the other claims' trial masks also applied here
Bt = behaviour(E, "ephys", timing=True)
den_x, den_y = Bt.hit + Bt.miss + Bt.ignore, Bt.hit + Bt.miss
print(f"\nWith the {int(d.trials.sum() - Bt.trials.sum())} trials without an early lick whose go cue is missing or not 2.6 s after "
f"the sample event also dropped (light trials left out): per cent correct over sessions {100 * (Bt.hit / den_x).mean():.1f} (x) and {100 * (Bt.hit / den_y).mean():.1f} (y); "
f"early licks {100 * Bt.early.sum() / Bt.trials.sum():.1f} pooled, {100 * (Bt.early / Bt.trials).mean():.1f} over sessions, "
f"{100 * (Bt.groupby('subject').early.sum() / Bt.groupby('subject').trials.sum()).mean():.1f} over mice")
Per cent correct (non-early trials with events), readings (a) pooled, (b) mean over sessions, (c) mean over mice:
light_trials sessions no_lick n_sessions n_mice trials a_pooled b_sessions c_mice
left out ephys (x) not correct 99 19 21603 76.5 75.8 75.2
left out ephys (y) left out 99 19 21603 77.2 76.5 75.9
left out ophys (x) not correct 59 4 3928 82.3 83.0 82.2
left out ophys (y) left out 59 4 3928 83.5 84.2 83.6
left out both (x) not correct 158 23 25531 77.5 78.5 76.4
left out both (y) left out 158 23 25531 78.3 79.4 77.2
kept ephys (x) not correct 99 19 28245 75.3 75.0 74.4
kept ephys (y) left out 99 19 28245 76.6 76.3 75.7
kept ophys (x) not correct 59 4 3928 82.3 83.0 82.2
kept ophys (y) left out 59 4 3928 83.5 84.2 83.6
kept both (x) not correct 158 23 32173 76.2 78.0 75.7
kept both (y) left out 158 23 32173 77.5 79.2 77.1
Per cent of trials with an early lick (extracellular sessions), light trials left out: {'a_pooled': np.float64(14.5), 'b_sessions': np.float64(11.6), 'c_mice': np.float64(14.7)} ; kept: {'a_pooled': np.float64(13.0), 'b_sessions': np.float64(11.7), 'c_mice': np.float64(14.7)}
Paper: 78.4% correct (tolerance 70.56 to 86.24), early responses below 13% (limit 14.3%)
Per mouse, extracellular sessions (no-lick trials left out, reading y):
trials hit miss early pct_correct pct_early
subject
210861 1239 249 84 904 74.8 73.0
210862 852 396 131 325 75.1 38.1
210863 946 576 189 181 75.3 19.1
214427 882 653 190 27 77.5 3.1
214429 1188 787 121 272 86.7 22.9
214430 497 270 144 80 65.2 16.1
218453 1418 778 210 420 78.7 29.6
218457 1760 1207 294 240 80.4 13.6
218693 882 706 119 22 85.6 2.5
219030 915 514 323 78 61.4 8.5
219031 944 683 226 35 75.1 3.7
219033 1436 1082 227 127 82.7 8.8
219036 1251 932 285 28 76.6 2.2
219037 1528 1208 238 75 83.5 4.9
219038 1481 943 491 47 65.8 3.2
219247 924 480 314 129 60.5 14.0
219248 1685 1416 214 42 86.9 2.5
219253 1165 882 214 27 80.5 2.3
221977 610 373 167 67 69.1 11.0
Range of per-session per cent correct: 50.0 to 92.5
With the 103 trials without an early lick whose go cue is missing or not 2.6 s after the sample event also dropped (light trials left out): per cent correct over sessions 75.8 (x) and 76.5 (y); early licks 14.5 pooled, 11.7 over sessions, 14.8 over mice
Result: reproduced. In the 99 extracellular sessions without the trials with light, per cent correct is 76.5% pooled, 75.8% over sessions and 75.2% over mice with no-lick trials counted as not correct (reading x), and 77.2%, 76.5% and 75.9% with them left out (reading y), all within 78.4 +/- 7.84%. Early licks are 11.6% of trials over sessions, below 14.3%, but 14.5% pooled and 14.7% over mice, above it, so the claim holds under reading (b), with either no-lick reading. Keeping the light trials gives 75.3%, 75.0% and 74.4% correct (x; 76.6%, 76.3% and 75.7% under y) and 13.0%, 11.7% and 14.7% early licks. The imaging sessions, where early licks are not flagged, give 82.3% (x) and 83.5% (y) pooled. The dandiset holds 23 of the paper's 42 mice. Dropping also the 103 trials without an early lick whose go cue is missing or not 2.6 s after the sample event, as the trial masks of the other claims do, gives 75.8% (x) and 76.5% (y) over sessions and 11.7% early licks over sessions (14.5% pooled, 14.8% over mice), the same outcome.
units-classification¶
1,408 single units were recorded from left ALM in 19 mice over 99 sessions; by spike width 124 were fast-spiking, 1,245 putative pyramidal and 39 intermediate.
Criterion: spike width is the trough-to-peak interval of the mean waveform; fast-spiking below 0.35 ms, putative pyramidal above 0.45 ms, intermediate between; each count within 10%.
D = U[~U.is_duplicate]
expected = dict(single_units=(1408, 140.8), sessions=(99, 9.9), mice=(19, 1.9), fast_spiking=(124, 12.4),
putative_pyramidal=(1245, 124.5), intermediate=(39, 3.9))
measured = dict(single_units=len(D), sessions=len(E), mice=len({s["subject"] for s in E}),
fast_spiking=int((D.width_class == "FS").sum()), putative_pyramidal=int((D.width_class == "Pyr").sum()),
intermediate=int((D.width_class == "intermediate").sum()))
tab = pd.DataFrame([dict(quantity=k, measured=measured[k], expected=v, tolerance=t,
within=abs(measured[k] - v) <= t) for k, (v, t) in expected.items()])
print(tab.to_string(index=False))
print("\nCounting the PT/IT duplicates as units: single units", len(U), ", putative pyramidal by width",
int((U.width_class == "Pyr").sum()))
print("Units per mouse (distinct):")
print(D.groupby("subject").size().to_string())
plt.figure(figsize=(5, 3))
plt.hist(D.width, bins=np.arange(0.1, 0.75, 1000 / 19531.25) - 0.0256, color="0.4") # one bin per sample interval
plt.axvline(0.35, color="r"); plt.axvline(0.45, color="r")
plt.xlabel("spike width (ms)"); plt.ylabel("units"); plt.title("trough to peak, distinct units")
plt.tight_layout(); plt.show()
print("Widths plotted:", round(D.width.min(), 3), "to", round(D.width.max(), 3), "ms; none outside the axis")
quantity measured expected tolerance within
single_units 1369 1408 140.8 True
sessions 99 99 9.9 True
mice 19 19 1.9 True
fast_spiking 124 124 12.4 True
putative_pyramidal 1245 1245 124.5 True
intermediate 0 39 3.9 False
Counting the PT/IT duplicates as units: single units 1441 , putative pyramidal by width 1317
Units per mouse (distinct):
subject
210861 54
210862 53
210863 87
214427 26
214429 47
214430 40
218453 53
218457 111
218693 57
219030 56
219031 92
219033 54
219036 97
219037 99
219038 118
219247 60
219248 142
219253 89
221977 34
Widths plotted: 0.154 to 0.666 ms; none outside the axis
Result: partially reproduced. The dandiset holds 1,369 distinct units (1,441 with the PT/IT duplicates) in 99 sessions from 19 mice; by spike width 124 are fast-spiking and 1,245 putative pyramidal, as in the paper. No unit has an intermediate width, against 39 in the paper: five of the six counts match and the intermediate units are not in the dandiset.
fig2-selective-fraction¶
73% (912 of 1,245) of putative pyramidal neurons distinguished the trial types in at least one trial epoch.
Criterion: per neuron, spike counts in the sample, delay and response epochs compared between lick right and lick left trials (two-tailed t-test, P < 0.05, uncorrected); selective when significant in any epoch. Readings: trials (a) correct only or (b) all non-early trials by instruction; response epoch (i) 1.3 s or (ii) 1.5 s after the go cue; trials with tagging light (t1) kept or (t2) left out, a reading the claims file sets for every electrophysiology claim of Figs 2 and 3, either of which may decide.
P = U[(U.width_class == "Pyr") & ~U.is_duplicate]
EP = {}
rows = []
for light in ("any", "none"):
for tset, tname in (("correct", "a_correct"), ("nonearly", "b_nonearly")):
for resp, rname in ((1.3, "i_1.3s"), (1.5, "ii_1.5s")):
r = alm.epoch_stats(E, P, tset, resp, light)
EP[(tset, resp, light)] = r
sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
sel = sig.any(axis=1)
prep = sig.p_sample | sig.p_delay
per = sig.p_response
rows.append(dict(light_trials="kept" if light == "any" else "left out", trials=tname, response=rname,
neurons=len(r), selective=int(sel.sum()),
fraction=round(sel.mean(), 3), untestable=int(r[["p_sample", "p_delay", "p_response"]].isna().all(axis=1).sum()),
min_trials_right=int(r.n_right.min()), min_trials_left=int(r.n_left.min()),
prep_only=int((prep & ~per).sum()), peri_only=int((per & ~prep).sum()), both=int((prep & per).sum())))
SEL = pd.DataFrame(rows)
print(SEL[["light_trials", "trials", "response", "neurons", "selective", "fraction", "untestable", "min_trials_right", "min_trials_left"]].to_string(index=False))
print("Paper: 912 (tolerance 820.8 to 1003.2), fraction 0.73 (0.657 to 0.803)")
SELSET = {light: (EP[("correct", 1.3, light)][["p_sample", "p_delay", "p_response"]] < 0.05).any(axis=1) for light in ("any", "none")}
sel_main = SELSET["any"]
print("\nPer mouse, reading (a)(i):")
print(pd.DataFrame(dict(subject=P.subject, sel=sel_main)).groupby("subject").sel.agg(["size", "sum", "mean"]).round(2).to_string())
print("Chance with three tests at P < 0.05, if independent: about", round(1 - 0.95 ** 3, 3))
light_trials trials response neurons selective fraction untestable min_trials_right min_trials_left
kept a_correct i_1.3s 1245 912 0.733 0 31 10
kept a_correct ii_1.5s 1245 910 0.731 0 31 10
kept b_nonearly i_1.3s 1245 846 0.680 0 35 21
kept b_nonearly ii_1.5s 1245 851 0.684 0 35 21
left out a_correct i_1.3s 1245 870 0.699 1 17 10
left out a_correct ii_1.5s 1245 868 0.697 1 17 10
left out b_nonearly i_1.3s 1245 795 0.639 1 19 21
left out b_nonearly ii_1.5s 1245 792 0.636 1 19 21
Paper: 912 (tolerance 820.8 to 1003.2), fraction 0.73 (0.657 to 0.803)
Per mouse, reading (a)(i):
size sum mean
subject
210861 48 25 0.52
210862 47 27 0.57
210863 78 50 0.64
214427 23 18 0.78
214429 44 29 0.66
214430 37 32 0.86
218453 45 29 0.64
218457 100 77 0.77
218693 51 46 0.90
219030 47 32 0.68
219031 81 61 0.75
219033 49 41 0.84
219036 90 75 0.83
219037 95 67 0.71
219038 110 75 0.68
219247 56 38 0.68
219248 129 104 0.81
219253 83 64 0.77
221977 32 22 0.69
Chance with three tests at P < 0.05, if independent: about 0.143
Result: reproduced. With correct trials and a 1.3 s response epoch, 912 of 1,245 putative pyramidal neurons (0.733) are selective, the paper's numbers. These are readings (a)(i)(t1). The other readings give 910 (0.731), 846 (0.680) and 851 (0.684); without the trials with light (t2), 870 (0.699), 868, 795 (0.639) and 792 (0.636). All are within tolerance.
fig2d-preparatory-perimovement¶
Among selective neurons, 219 of 912 had selective preparatory activity only, 389 peri-movement activity only, and 304 both.
Criterion: preparatory means significant in the sample or delay epoch, peri-movement significant in the response epoch, by the test and readings of fig2-selective-fraction; all three counts within 10% under one reading.
tab = SEL[["light_trials", "trials", "response", "selective", "prep_only", "peri_only", "both"]].copy()
for col, (v, t) in dict(prep_only=(219, 21.9), peri_only=(389, 38.9), both=(304, 30.4)).items():
tab[col + "_ok"] = (tab[col] - v).abs() <= t
print(tab.to_string(index=False))
print("Paper: preparatory only 219 (197.1 to 240.9), peri-movement only 389 (350.1 to 427.9), both 304 (273.6 to 334.4)")
r = EP[("correct", 1.3, "any")]
sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
print("\nReading (a)(i), light trials kept: significant in sample", int(sig.p_sample.sum()), ", delay", int(sig.p_delay.sum()),
", response", int(sig.p_response.sum()), "; delay only (not sample)", int((sig.p_delay & ~sig.p_sample).sum()))
print("Preparatory (sample or delay):", int((sig.p_sample | sig.p_delay).sum()), "(paper 219 + 304 = 523); peri-movement (response):",
int(sig.p_response.sum()), "(paper 389 + 304 = 693)")
light_trials trials response selective prep_only peri_only both prep_only_ok peri_only_ok both_ok
kept a_correct i_1.3s 912 231 284 397 True False False
kept a_correct ii_1.5s 910 233 282 395 True False False
kept b_nonearly i_1.3s 846 228 282 336 True False False
kept b_nonearly ii_1.5s 851 233 287 331 True False True
left out a_correct i_1.3s 870 231 294 345 True False False
left out a_correct ii_1.5s 868 231 292 345 True False False
left out b_nonearly i_1.3s 795 229 275 291 True False True
left out b_nonearly ii_1.5s 792 231 272 289 True False True
Paper: preparatory only 219 (197.1 to 240.9), peri-movement only 389 (350.1 to 427.9), both 304 (273.6 to 334.4)
Reading (a)(i), light trials kept: significant in sample 334 , delay 497 , response 681 ; delay only (not sample) 294
Preparatory (sample or delay): 628 (paper 219 + 304 = 523); peri-movement (response): 681 (paper 389 + 304 = 693)
Result: not reproduced. Under the main reading, 231 selective neurons are preparatory only (within tolerance), 284 peri-movement only (paper 389) and 397 both (paper 304). No reading brings the peri-movement count into tolerance (272 to 294, with or without the light trials); the count of both is within it in three readings (289 to 331). The split is shifted towards neurons selective in both epochs. Counted by class, 628 neurons are preparatory (selective in the sample or delay epoch) against the paper's 523 (219 + 304), and 681 peri-movement (selective in the response epoch) against 693 (389 + 304), so the mismatch lies in the preparatory class as the Methods define it.
fig2c-selectivity-time-course¶
Selectivity emerges in the sample epoch, increases through the delay epoch and peaks at the beginning of the response epoch.
Criterion: number of putative pyramidal neurons significantly selective in 200 ms windows (two-tailed t-test, P < 0.05, correct trials), window step 200 ms and 50 ms, both run. Reproduced when, under each step, (1) some sample-epoch window exceeds the 95th percentile of a binomial count with p = 0.05 while the pre-sample windows do not exceed it on average; (2) the last delay window exceeds the first; (3) the maximum falls at the beginning of the response epoch, read as (a) the first window after the go cue or (b) any window starting within 0.5 s after it. Windows are placed on a grid with an edge at the sample event; spikes are stored from 0.57 s before it.
TC = {}
for light, step in [(l, st) for l in ("any", "none") for st in (0.2, 0.05)]:
first = -0.4 if step == 0.2 else -0.55
starts = np.round(np.arange(first, 4.9 - 0.2 + 1e-9, step), 3)
W = alm.window_counts(E, P, starts, light=light)
nsig = np.zeros(len(starts), int)
ntest = np.zeros(len(starts), int)
for R, L in W.values():
p = stats.ttest_ind(R, L, axis=0).pvalue
nsig += p < 0.05
ntest += ~np.isnan(p)
thr = stats.binom.ppf(0.95, ntest, 0.05)
TC[(step, light)] = (starts, nsig, thr)
pre = starts + 0.2 <= 1e-9
samp = (starts >= -1e-9) & (starts + 0.2 <= 1.3 + 1e-9)
dl = (starts >= 1.3 - 1e-9) & (starts + 0.2 <= 2.6 + 1e-9)
resp = starts >= 2.6 - 1e-9
im = int(np.argmax(nsig))
c1 = (nsig[samp] > thr[samp]).any() and nsig[pre].mean() <= thr[pre].mean()
c2 = nsig[dl][-1] > nsig[dl][0]
c3a = abs(starts[im] - 2.6) < 1e-6
c3b = (starts[im] >= 2.6 - 1e-9) and (starts[im] < 3.1 - 1e-9)
print(f"light trials {'kept' if light == 'any' else 'left out'}, step {int(step * 1000)} ms: {len(starts)} windows, neurons tested per window {ntest.min()} to {ntest.max()}, "
f"binomial 95th percentile {thr.min():.0f} to {thr.max():.0f}")
print(" pre-sample windows (start, count):", list(zip(starts[pre], nsig[pre])), "mean", round(nsig[pre].mean(), 1))
print(" sample epoch: max count", nsig[samp].max(), "first window above threshold starts at",
starts[samp][np.argmax(nsig[samp] > thr[samp])], "s; (1) holds:", c1)
print(" delay: first window", starts[dl][0], "s count", nsig[dl][0], "; last window", starts[dl][-1], "s count",
nsig[dl][-1], "; (2) holds:", c2)
print(" maximum", nsig[im], "in the window starting", round(starts[im] - 2.6, 3), "s after the go cue; first response window",
nsig[resp][0], "; (3a) holds:", c3a, "; (3b) holds:", c3b)
print(" five largest windows (start after go cue, count):", [(round(starts[i] - 2.6, 2), int(nsig[i])) for i in np.argsort(-nsig)[:5]])
fig, ax = plt.subplots(figsize=(7, 3))
for (step, light), (starts, nsig, thr) in TC.items():
ax.plot(starts + 0.1 - 2.6, nsig, label=f"step {int(step * 1000)} ms, light trials {'kept' if light == 'any' else 'left out'}")
ax.plot(TC[(0.05, "any")][0] + 0.1 - 2.6, TC[(0.05, "any")][2], "k:", label="binomial 95th pct")
for x in (-2.6, -1.3, 0):
ax.axvline(x, color="0.7")
ax.set_xlabel("window centre, s from go cue"); ax.set_ylabel("selective neurons"); ax.legend(fontsize=7)
plt.tight_layout(); plt.show()
print("Counts plotted:", min(v[1].min() for v in TC.values()), "to", max(v[1].max() for v in TC.values()))
light trials kept, step 200 ms: 26 windows, neurons tested per window 1183 to 1223, binomial 95th percentile 72 to 74 pre-sample windows (start, count): [(np.float64(-0.4), np.int64(59)), (np.float64(-0.2), np.int64(64))] mean 61.5 sample epoch: max count 272 first window above threshold starts at 0.0 s; (1) holds: True delay: first window 1.4 s count 326 ; last window 2.4 s count 408 ; (2) holds: True maximum 574 in the window starting 0.2 s after the go cue; first response window 500 ; (3a) holds: False ; (3b) holds: True five largest windows (start after go cue, count): [(np.float64(0.2), 574), (np.float64(0.4), 565), (np.float64(0.6), 531), (np.float64(0.8), 530), (np.float64(1.0), 527)]
light trials kept, step 50 ms: 106 windows, neurons tested per window 1175 to 1224, binomial 95th percentile 71 to 74 pre-sample windows (start, count): [(np.float64(-0.55), np.int64(61)), (np.float64(-0.5), np.int64(61)), (np.float64(-0.45), np.int64(52)), (np.float64(-0.4), np.int64(59)), (np.float64(-0.35), np.int64(63)), (np.float64(-0.3), np.int64(65)), (np.float64(-0.25), np.int64(62)), (np.float64(-0.2), np.int64(64))] mean 60.9 sample epoch: max count 280 first window above threshold starts at 0.0 s; (1) holds: True delay: first window 1.3 s count 321 ; last window 2.4 s count 408 ; (2) holds: True maximum 582 in the window starting 0.35 s after the go cue; first response window 500 ; (3a) holds: False ; (3b) holds: True five largest windows (start after go cue, count): [(np.float64(0.35), 582), (np.float64(0.25), 580), (np.float64(0.3), 580), (np.float64(0.1), 574), (np.float64(0.2), 574)]
light trials left out, step 200 ms: 26 windows, neurons tested per window 1168 to 1215, binomial 95th percentile 71 to 73 pre-sample windows (start, count): [(np.float64(-0.4), np.int64(66)), (np.float64(-0.2), np.int64(57))] mean 61.5 sample epoch: max count 248 first window above threshold starts at 0.0 s; (1) holds: True delay: first window 1.4 s count 273 ; last window 2.4 s count 377 ; (2) holds: True maximum 525 in the window starting 0.2 s after the go cue; first response window 461 ; (3a) holds: False ; (3b) holds: True five largest windows (start after go cue, count): [(np.float64(0.2), 525), (np.float64(0.4), 514), (np.float64(0.6), 490), (np.float64(0.8), 487), (np.float64(1.0), 478)]
light trials left out, step 50 ms: 106 windows, neurons tested per window 1162 to 1218, binomial 95th percentile 71 to 74 pre-sample windows (start, count): [(np.float64(-0.55), np.int64(62)), (np.float64(-0.5), np.int64(67)), (np.float64(-0.45), np.int64(57)), (np.float64(-0.4), np.int64(66)), (np.float64(-0.35), np.int64(58)), (np.float64(-0.3), np.int64(47)), (np.float64(-0.25), np.int64(60)), (np.float64(-0.2), np.int64(57))] mean 59.2 sample epoch: max count 249 first window above threshold starts at 0.0 s; (1) holds: True delay: first window 1.3 s count 281 ; last window 2.4 s count 377 ; (2) holds: True maximum 538 in the window starting 0.25 s after the go cue; first response window 461 ; (3a) holds: False ; (3b) holds: True five largest windows (start after go cue, count): [(np.float64(0.25), 538), (np.float64(0.1), 531), (np.float64(0.35), 531), (np.float64(0.2), 525), (np.float64(0.3), 525)]
Counts plotted: 47 to 582
Result: reproduced. Under both steps, pre-sample windows hold 52 to 65 selective neurons against a chance limit of 71 to 74, sample-epoch windows reach 272 (200 ms step) and 280 (50 ms step), selectivity grows through the delay (326 to 408 and 321 to 408), and the maximum (574 and 582 neurons) lies 0.2 s and 0.35 s after the go cue. The first window after the go cue holds 500, so reading (3a) fails and reading (3b) holds under both steps. Without the trials with light the same holds (pre-sample 47 to 67, sample maximum 248 and 249, delay 273 to 377 and 281 to 377, maximum 525 and 538 at 0.2 s and 0.25 s, first response window 461).
fig2c-population-dynamics¶
Individual neuron responses are dynamic although selectivity is stable at the level of the population (Fig. 2c, bottom, population selectivity and the correlation of the population response with that at the go cue).
Criterion: pseudo-population of (a) the 912 selective or (b) all 1,245 putative pyramidal neurons; each neuron's response (correct trials, 200 ms bins) mean-subtracted and normalized to its variance over the trial; Pearson correlation of the population vector at each time with that at the go cue, the vector concatenating both trial types or taking their difference; population selectivity as preferred minus non-preferred rate, the preference set from 10 trials and computed on the rest. Reproduced when, under each reading, the mean correlation over the sample epoch is below that over the delay epoch, which is below 1, and the population selectivity stays above 0 through the delay. The claims file names readings for three details the paper leaves open, all run in every combination: normalization (v) by the variance, the text as written, or (s) by the standard deviation; the preference set from (n10) 10 trials in all or (n20) 10 of each type; and the 200 ms bin at the go cue that (gs) starts at, (gc) is centred on, or (ge) ends at the cue (the centred bin comes from a grid shifted by 0.1 s). Trials with tagging light are (t1) kept or (t2) left out, either of which may decide.
rng = np.random.default_rng(2024)
starts = np.round(np.arange(-3.0, 2.0 + 1e-9, 0.2), 3) # bin starts relative to the go cue
nb = len(starts)
# A second grid shifted by 0.1 s holds the bin centred on the go cue, reading (gc).
grids = {"A": starts, "B": np.round(starts - 0.1, 3)}
def epoch_masks(st):
return ((st >= -2.6 - 1e-9) & (st + 0.2 <= -1.3 + 1e-9), (st >= -1.3 - 1e-9) & (st + 0.2 <= 1e-9))
samp, dl = epoch_masks(starts)
refs = (("gs, bin starting at go", "A", 0.0), ("gc, bin centred on go", "B", -0.1), ("ge, bin ending at go", "A", -0.2))
rows = []
curves = {}
for light in ("any", "none"):
lname = "kept" if light == "any" else "left out"
Wg = alm.window_counts(E, P, starts, align="go", light=light)
WgB = alm.window_counts(E, P, grids["B"], align="go", light=light)
sel_idx = set(P.index[SELSET[light].reindex(P.index).values])
Rm = {g: {k: R.mean(0) / 0.2 for k, (R, L) in W.items()} for g, W in (("A", Wg), ("B", WgB))}
Lm = {g: {k: L.mean(0) / 0.2 for k, (R, L) in W.items()} for g, W in (("A", Wg), ("B", WgB))}
popsel = {}
for mode in ("10 in all", "10 per type"):
vals = {}
for k, (R, L) in Wg.items():
n_r, n_l = len(R), len(L)
if mode == "10 in all":
pick = rng.choice(n_r + n_l, 10, replace=False)
pr, pl = pick[pick < n_r], pick[pick >= n_r] - n_r
else:
pr, pl = rng.choice(n_r, min(10, n_r), replace=False), rng.choice(n_l, min(10, n_l), replace=False)
restR, restL = np.setdiff1d(np.arange(n_r), pr), np.setdiff1d(np.arange(n_l), pl)
if len(restR) == 0 or len(restL) == 0:
continue
mr = R[pr].sum(1).mean() if len(pr) else 0.0
ml = L[pl].sum(1).mean() if len(pl) else 0.0
sign = 1 if mr >= ml else -1
vals[k] = sign * (R[restR].mean(0) - L[restL].mean(0)) / 0.2
popsel[mode] = vals
for pop in ("a_selective", "b_all"):
keys = [k for k in Wg if (k in sel_idx or pop == "b_all")]
for norm in ("v, variance", "s, sd"):
for vec in ("concatenated", "difference"):
for ref_name, g, ref_start in refs:
X = np.array([np.concatenate([Rm[g][k], Lm[g][k]]) for k in keys])
scale = X.var(1) if norm.startswith("v") else X.std(1)
keep = scale > 0
Z = (X[keep] - X[keep].mean(1, keepdims=True)) / scale[keep][:, None]
Zr, Zl = Z[:, :nb], Z[:, nb:]
V = np.concatenate([Zr, Zl], 0) if vec == "concatenated" else Zr - Zl
ref = int(np.argmin(abs(grids[g] - ref_start)))
c = np.array([np.corrcoef(V[:, i], V[:, ref])[0, 1] for i in range(nb)])
sm, dm = epoch_masks(grids[g])
curves[(light, pop, norm, vec, ref_name)] = (grids[g], c)
rows.append(dict(light_trials=lname, neurons=pop, n=int(keep.sum()), norm=norm, vector=vec, reference=ref_name,
r_sample=c[sm].mean(), r_delay=c[dm].mean(), holds=c[sm].mean() < c[dm].mean() < 1))
for mode, vals in popsel.items():
ps = np.array([vals[k] for k in keys if k in vals])
boot = np.array([ps[rng.integers(0, len(ps), len(ps))].mean(0) for _ in range(1000)])
lo = np.percentile(boot, 2.5, axis=0)
rows.append(dict(light_trials=lname, neurons=pop, n=len(ps), norm="", vector=f"population selectivity, preference from {mode}",
reference="", r_sample=ps.mean(0)[samp].mean(), r_delay=ps.mean(0)[dl].min(),
holds=bool((ps.mean(0)[dl] > 0).all())))
print(f"light trials {lname}, {pop}, preference from {mode}: population selectivity per delay bin (spikes/s)",
ps.mean(0)[dl].round(2).tolist(), "; bootstrap 2.5th percentile", lo[dl].round(2).tolist(),
f"; neurons without remaining trials {len(keys) - len(ps)}")
res_pd = pd.DataFrame(rows)
print("\nCorrelation rows: r_sample and r_delay are the mean correlations over the sample and delay bins.")
print("Selectivity rows: r_sample is the mean over sample bins, r_delay the minimum over delay bins (spikes/s).")
print(res_pd.round(3).to_string(index=False))
print("Readings that hold:", int(res_pd.holds.sum()), "of", len(res_pd))
fig, ax = plt.subplots(figsize=(7, 3))
for key in [("any", "a_selective", "v, variance", "concatenated", "gs, bin starting at go"),
("any", "a_selective", "s, sd", "difference", "gc, bin centred on go"),
("none", "b_all", "v, variance", "difference", "gs, bin starting at go")]:
ax.plot(curves[key][0] + 0.1, curves[key][1], label=", ".join(key))
for x in (-2.6, -1.3, 0):
ax.axvline(x, color="0.7")
ax.set_xlabel("bin centre, s from go cue"); ax.set_ylabel("correlation with go-cue vector"); ax.legend(fontsize=6)
plt.tight_layout(); plt.show()
print("Correlations range over all readings:", round(min(v[1].min() for v in curves.values()), 3), "to", round(max(v[1].max() for v in curves.values()), 3))
corr = res_pd[res_pd.reference != ""]
print("Correlation readings that hold, by light reading:", corr.groupby("light_trials").holds.agg(["sum", "size"]).to_dict("index"))
print("Every reading holds, light trials kept (t1):", bool(res_pd[res_pd.light_trials == "kept"].holds.all()),
"; left out (t2):", bool(res_pd[res_pd.light_trials == "left out"].holds.all()))
light trials kept, a_selective, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.88, 1.05, 1.23, 1.36, 1.45, 1.48] ; bootstrap 2.5th percentile [0.67, 0.81, 0.95, 1.05, 1.12, 1.14] ; neurons without remaining trials 0 light trials kept, a_selective, preference from 10 per type: population selectivity per delay bin (spikes/s) [1.02, 1.12, 1.27, 1.44, 1.57, 1.61] ; bootstrap 2.5th percentile [0.82, 0.86, 0.98, 1.13, 1.24, 1.27] ; neurons without remaining trials 11 light trials kept, b_all, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.62, 0.77, 0.89, 0.98, 1.06, 1.08] ; bootstrap 2.5th percentile [0.46, 0.57, 0.67, 0.73, 0.82, 0.82] ; neurons without remaining trials 0
light trials kept, b_all, preference from 10 per type: population selectivity per delay bin (spikes/s) [0.7, 0.81, 0.91, 1.04, 1.13, 1.16] ; bootstrap 2.5th percentile [0.52, 0.62, 0.69, 0.81, 0.88, 0.91] ; neurons without remaining trials 14
light trials left out, a_selective, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.93, 1.09, 1.3, 1.42, 1.5, 1.56] ; bootstrap 2.5th percentile [0.7, 0.82, 1.0, 1.09, 1.16, 1.21] ; neurons without remaining trials 0 light trials left out, a_selective, preference from 10 per type: population selectivity per delay bin (spikes/s) [1.04, 1.13, 1.31, 1.41, 1.57, 1.62] ; bootstrap 2.5th percentile [0.81, 0.85, 1.0, 1.11, 1.23, 1.28] ; neurons without remaining trials 11 light trials left out, b_all, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.63, 0.76, 0.89, 0.98, 1.03, 1.08] ; bootstrap 2.5th percentile [0.46, 0.57, 0.69, 0.75, 0.8, 0.84] ; neurons without remaining trials 0
light trials left out, b_all, preference from 10 per type: population selectivity per delay bin (spikes/s) [0.7, 0.76, 0.86, 0.97, 1.08, 1.11] ; bootstrap 2.5th percentile [0.54, 0.58, 0.66, 0.76, 0.84, 0.86] ; neurons without remaining trials 14
Correlation rows: r_sample and r_delay are the mean correlations over the sample and delay bins.
Selectivity rows: r_sample is the mean over sample bins, r_delay the minimum over delay bins (spikes/s).
light_trials neurons n norm vector reference r_sample r_delay holds
kept a_selective 912 v, variance concatenated gs, bin starting at go -0.028 0.252 True
kept a_selective 912 v, variance concatenated gc, bin centred on go -0.019 0.373 True
kept a_selective 912 v, variance concatenated ge, bin ending at go 0.011 0.558 True
kept a_selective 912 v, variance difference gs, bin starting at go 0.071 0.426 True
kept a_selective 912 v, variance difference gc, bin centred on go 0.096 0.483 True
kept a_selective 912 v, variance difference ge, bin ending at go 0.091 0.630 True
kept a_selective 912 s, sd concatenated gs, bin starting at go -0.122 0.249 True
kept a_selective 912 s, sd concatenated gc, bin centred on go -0.027 0.494 True
kept a_selective 912 s, sd concatenated ge, bin ending at go 0.021 0.705 True
kept a_selective 912 s, sd difference gs, bin starting at go 0.148 0.478 True
kept a_selective 912 s, sd difference gc, bin centred on go 0.244 0.673 True
kept a_selective 912 s, sd difference ge, bin ending at go 0.240 0.781 True
kept a_selective 912 population selectivity, preference from 10 in all 0.413 0.880 True
kept a_selective 901 population selectivity, preference from 10 per type 0.552 1.023 True
kept b_all 1245 v, variance concatenated gs, bin starting at go -0.048 0.036 True
kept b_all 1245 v, variance concatenated gc, bin centred on go -0.014 0.114 True
kept b_all 1245 v, variance concatenated ge, bin ending at go 0.018 0.339 True
kept b_all 1245 v, variance difference gs, bin starting at go 0.050 0.057 True
kept b_all 1245 v, variance difference gc, bin centred on go 0.074 0.120 True
kept b_all 1245 v, variance difference ge, bin ending at go 0.068 0.370 True
kept b_all 1245 s, sd concatenated gs, bin starting at go -0.123 0.208 True
kept b_all 1245 s, sd concatenated gc, bin centred on go -0.043 0.437 True
kept b_all 1245 s, sd concatenated ge, bin ending at go 0.013 0.655 True
kept b_all 1245 s, sd difference gs, bin starting at go 0.134 0.423 True
kept b_all 1245 s, sd difference gc, bin centred on go 0.205 0.593 True
kept b_all 1245 s, sd difference ge, bin ending at go 0.205 0.710 True
kept b_all 1245 population selectivity, preference from 10 in all 0.289 0.624 True
kept b_all 1231 population selectivity, preference from 10 per type 0.383 0.705 True
left out a_selective 870 v, variance concatenated gs, bin starting at go -0.042 0.214 True
left out a_selective 870 v, variance concatenated gc, bin centred on go -0.038 0.323 True
left out a_selective 870 v, variance concatenated ge, bin ending at go -0.031 0.535 True
left out a_selective 870 v, variance difference gs, bin starting at go 0.014 0.394 True
left out a_selective 870 v, variance difference gc, bin centred on go 0.063 0.458 True
left out a_selective 870 v, variance difference ge, bin ending at go 0.022 0.605 True
left out a_selective 870 s, sd concatenated gs, bin starting at go -0.125 0.246 True
left out a_selective 870 s, sd concatenated gc, bin centred on go -0.024 0.487 True
left out a_selective 870 s, sd concatenated ge, bin ending at go 0.025 0.696 True
left out a_selective 870 s, sd difference gs, bin starting at go 0.140 0.462 True
left out a_selective 870 s, sd difference gc, bin centred on go 0.237 0.659 True
left out a_selective 870 s, sd difference ge, bin ending at go 0.236 0.765 True
left out a_selective 870 population selectivity, preference from 10 in all 0.528 0.930 True
left out a_selective 859 population selectivity, preference from 10 per type 0.594 1.039 True
left out b_all 1244 v, variance concatenated gs, bin starting at go -0.038 0.065 True
left out b_all 1244 v, variance concatenated gc, bin centred on go 0.016 0.124 True
left out b_all 1244 v, variance concatenated ge, bin ending at go -0.020 0.343 True
left out b_all 1244 v, variance difference gs, bin starting at go 0.078 0.054 False
left out b_all 1244 v, variance difference gc, bin centred on go 0.126 0.103 False
left out b_all 1244 v, variance difference ge, bin ending at go 0.042 0.360 True
left out b_all 1244 s, sd concatenated gs, bin starting at go -0.117 0.200 True
left out b_all 1244 s, sd concatenated gc, bin centred on go -0.039 0.428 True
left out b_all 1244 s, sd concatenated ge, bin ending at go 0.009 0.642 True
left out b_all 1244 s, sd difference gs, bin starting at go 0.125 0.400 True
left out b_all 1244 s, sd difference gc, bin centred on go 0.195 0.574 True
left out b_all 1244 s, sd difference ge, bin ending at go 0.192 0.680 True
left out b_all 1245 population selectivity, preference from 10 in all 0.351 0.633 True
left out b_all 1231 population selectivity, preference from 10 per type 0.424 0.698 True
Readings that hold: 54 of 56
Correlations range over all readings: -0.286 to 1.0
Correlation readings that hold, by light reading: {'kept': {'sum': 24, 'size': 24}, 'left out': {'sum': 22, 'size': 24}}
Every reading holds, light trials kept (t1): True ; left out (t2): False
Result: reproduced. With the light trials kept (t1), all 24 correlation readings hold: the mean correlation with the go-cue vector is lower over the sample epoch (-0.12 to 0.24) than over the delay epoch (0.04 to 0.78), the delay value is below 1, and the population selectivity stays above 0 in every delay bin under all four selectivity readings (minimum 0.62 spikes/s). The centred go-cue bin (gc) gives sample means of -0.04 to 0.24 and delay means of 0.11 to 0.67. Without the light trials (t2), 22 of the 24 correlation readings hold; two fail (all neurons, divided by the variance, difference vector, bin starting at the go cue: sample 0.078 against delay 0.054; bin centred on it: 0.126 against 0.103). Under t1 the claim holds in every reading, and the claims file lets either light reading decide. Under the bin ending at the go cue (ge) that bin lies inside the delay epoch, so its correlation of 1 enters the delay mean; the bin starting at the cue (gs) avoids this and holds in all 8 t1 rows (sample means -0.123 to 0.148, delay means 0.036 to 0.478).
ed2c-error-trials-predict-choice¶
On error trials ALM activity predicts the direction the mouse licked, so most selective neurons switch their trial-type preference relative to correct trials.
Criterion: for the 912 selective neurons and each epoch, selectivity (rate lick right minus rate lick left, by instructed trial type) on correct and on error trials; Pearson correlation across neurons. Readings: at least (a) one or (b) five error trials of each type. Reproduced when r < 0 in the delay and response epochs under each reading.
rows = []
for light in ("any", "none"):
selU = P[SELSET[light].reindex(P.index).values]
c_st = alm.epoch_stats(E, selU, "correct", 1.3, light)
e_st = alm.epoch_stats(E, selU, "error", 1.3, light)
for mn in (1, 5):
ok = (e_st.n_right >= mn) & (e_st.n_left >= mn)
for ep in ("sample", "delay", "response"):
x = (c_st["R_" + ep] - c_st["L_" + ep])[ok]
y = (e_st["R_" + ep] - e_st["L_" + ep])[ok]
r, p = stats.pearsonr(x, y)
rows.append(dict(light_trials="kept" if light == "any" else "left out", min_error_trials=mn, epoch=ep,
neurons=int(ok.sum()), left_out=len(selU) - int(ok.sum()), r=r, p=p,
switched=np.mean(np.sign(x) != np.sign(y))))
if light == "any":
ok = (e_st.n_right >= 1) & (e_st.n_left >= 1)
x = (c_st.R_delay - c_st.L_delay)[ok]; y = (e_st.R_delay - e_st.L_delay)[ok]
per_mouse = pd.DataFrame(dict(subject=selU.subject[ok], x=x, y=y)).groupby("subject").apply(
lambda g: pd.Series(dict(n=len(g), r=stats.pearsonr(g.x, g.y)[0] if len(g) > 2 else np.nan)))
ED2 = pd.DataFrame(rows)
print(ED2.round(4).assign(p=[f"{x:.1e}" for x in ED2.p]).to_string(index=False))
print("Per mouse, delay epoch, at least one error trial of each type, light trials kept:")
print(per_mouse.round(2).to_string())
light_trials min_error_trials epoch neurons left_out r p switched
kept 1 sample 897 15 -0.0002 9.9e-01 0.4928
kept 1 delay 897 15 -0.2970 1.0e-19 0.5708
kept 1 response 897 15 -0.3806 2.7e-32 0.6132
kept 5 sample 833 79 0.0447 2.0e-01 0.4898
kept 5 delay 833 79 -0.2502 2.4e-13 0.5738
kept 5 response 833 79 -0.3363 1.8e-23 0.6170
left out 1 sample 849 21 0.0255 4.6e-01 0.5100
left out 1 delay 849 21 -0.2906 5.5e-18 0.5595
left out 1 response 849 21 -0.3293 6.3e-23 0.5936
left out 5 sample 763 107 -0.0246 5.0e-01 0.5177
left out 5 delay 763 107 -0.3158 4.0e-19 0.5714
left out 5 response 763 107 -0.3018 1.6e-17 0.6055
Per mouse, delay epoch, at least one error trial of each type, light trials kept:
n r
subject
210861 25.0 0.49
210862 27.0 -0.09
210863 50.0 -0.46
214427 18.0 0.01
214429 29.0 -0.51
214430 32.0 0.11
218453 29.0 -0.19
218457 77.0 0.16
218693 46.0 -0.76
219030 32.0 -0.75
219031 61.0 -0.30
219033 41.0 -0.59
219036 75.0 -0.67
219037 67.0 -0.67
219038 75.0 -0.08
219247 38.0 -0.06
219248 104.0 -0.75
219253 49.0 0.41
221977 22.0 -0.21
Result: reproduced. Selectivity on error trials is anticorrelated with that on correct trials in the delay epoch (r = -0.30 with at least one error trial of each type, 897 neurons; -0.25 with at least five, 833 neurons) and the response epoch (-0.38 and -0.34), all P < 1e-12. Without the light trials, r is -0.29 and -0.32 (delay) and -0.33 and -0.30 (response). In the sample epoch the correlation is near 0 (-0.025 to 0.045).
fig2e-no-contra-bias-spikes¶
Contra-preferring and ipsi-preferring neurons are present in equal proportions in left ALM, and the overall spike count shows no significant contralateral bias (P = 0.13; 8% spike rate modulation across all task-selective neurons).
Criterion: a claim of absence. Two-tailed t-test against 0 of the spike count difference (lick right minus lick left, correct trials) across neurons; reproduced when P >= 0.05. The criterion names readings: neurons (a) all 1,245 or (b) the 912 selective; window (i) the three epochs together, (ii) the 570 ms before the go cue, (iii) the delay epoch. The protocol judges absence on the paper's test as written, so the outcome rests on (a)(i), "the overall spike count across the recorded population"; the others are shown. The proportions are tested with the bootstrap of Fig. 3f, j, which must not find a contralateral excess at P < 0.05. The 8% modulation is checked with the readings of fig3f-pt-contra-selectivity.
rng = np.random.default_rng(7)
rows, boots = [], []
for light in ("any", "none"):
lname = "kept" if light == "any" else "left out"
selU = P[SELSET[light].reindex(P.index).values]
for pop, units in (("a_all", P), ("b_selective", selU)):
for win, (t0, t1) in (("i_three_epochs", (None, None)), ("ii_570ms", (-0.57, 0.0)), ("iii_delay", (-1.3, 0.0))):
if t0 is None:
st = alm.epoch_stats(E, units, "correct", 1.3, light)
Rr = sum(st["R_" + e] * 1.3 for e in ("sample", "delay", "response"))
Ll = sum(st["L_" + e] * 1.3 for e in ("sample", "delay", "response"))
else:
w = alm.window_diff(E, units, t0, t1, light=light)
Rr, Ll = w.R * (t1 - t0), w.L * (t1 - t0)
diff = Rr - Ll
t = stats.ttest_1samp(diff, 0)
rows.append(dict(light_trials=lname, neurons=pop, window=win, n=len(diff), mean_count_diff=diff.mean(), t=t.statistic,
P=t.pvalue, mod_x_pct=100 * diff.mean() / ((Rr + Ll) / 2).mean(), mod_y_pct=100 * diff.mean() / Ll.mean()))
contra = (alm.total_count_diff(E, selU, light=light) > 0).values
pb = alm.boot_more_ipsi(contra, rng)
boots.append(dict(light_trials=lname, selective=len(contra), contra=int(contra.sum()), fraction=round(contra.mean(), 3),
P_more_ipsi=pb, P_contra_at_least_ipsi=1 - pb, binomial_P=stats.binomtest(int(contra.sum()), len(contra), 0.5).pvalue))
if light == "any":
by_mouse = pd.DataFrame(dict(subject=selU.subject.values, contra=contra)).groupby("subject").contra.agg(["sum", "size"])
FE = pd.DataFrame(rows)
print(FE.round(4).to_string(index=False))
print("Paper: P = 0.13; modulation 8% (tolerance 7.2 to 8.8)")
both = FE[(FE.P >= 0.05) & (((FE.mod_x_pct - 8).abs() <= 0.8) | ((FE.mod_y_pct - 8).abs() <= 0.8))]
print("Readings with P >= 0.05 and a modulation (x or y) within 7.2 to 8.8%:")
print(both.round(4).to_string(index=False))
print("\nContra- and ipsi-preferring selective neurons, bootstrap (neurons resampled, 10^4):")
print(pd.DataFrame(boots).round(4).to_string(index=False))
print("By mouse, light trials kept (contra-preferring / selective):")
print(by_mouse.to_string())
light_trials neurons window n mean_count_diff t P mod_x_pct mod_y_pct
kept a_all i_three_epochs 1245 0.0631 0.2221 0.8243 0.5375 0.5390
kept a_all ii_570ms 1245 0.1529 2.1054 0.0355 8.5480 8.9297
kept a_all iii_delay 1245 0.2263 1.6436 0.1005 5.7589 5.9297
kept b_selective i_three_epochs 912 0.1596 0.4250 0.6709 1.1766 1.1836
kept b_selective ii_570ms 912 0.1938 1.9626 0.0500 9.2470 9.6952
kept b_selective iii_delay 912 0.2953 1.5774 0.1151 6.4495 6.6644
left out a_all i_three_epochs 1245 0.1862 0.6416 0.5212 1.5809 1.5935
left out a_all ii_570ms 1245 0.1689 2.2994 0.0216 9.3757 9.8369
left out a_all iii_delay 1245 0.2712 1.9564 0.0506 6.8693 7.1136
left out b_selective i_three_epochs 870 0.3731 0.9447 0.3451 2.7038 2.7409
left out b_selective ii_570ms 870 0.2179 2.0830 0.0375 10.1573 10.7008
left out b_selective iii_delay 870 0.3724 1.8854 0.0597 7.9661 8.2965
Paper: P = 0.13; modulation 8% (tolerance 7.2 to 8.8)
Readings with P >= 0.05 and a modulation (x or y) within 7.2 to 8.8%:
light_trials neurons window n mean_count_diff t P mod_x_pct mod_y_pct
left out b_selective iii_delay 870 0.3724 1.8854 0.0597 7.9661 8.2965
Contra- and ipsi-preferring selective neurons, bootstrap (neurons resampled, 10^4):
light_trials selective contra fraction P_more_ipsi P_contra_at_least_ipsi binomial_P
kept 912 419 0.459 0.9929 0.0071 0.0156
left out 870 408 0.469 0.9671 0.0329 0.0723
By mouse, light trials kept (contra-preferring / selective):
sum size
subject
210861 5 25
210862 16 27
210863 22 50
214427 6 18
214429 15 29
214430 15 32
218453 14 29
218457 32 77
218693 18 46
219030 9 32
219031 32 61
219033 18 41
219036 28 75
219037 35 67
219038 32 75
219247 26 38
219248 56 104
219253 29 64
221977 11 22
Result: reproduced. On the paper's test as written, the overall spike count difference across the 1,245 putative pyramidal neurons is not different from 0 (P = 0.82, or 0.52 without the light trials; 0.67 and 0.35 for the selective neurons). The bootstrap finds no contralateral excess: 419 of 912 selective neurons are contra-preferring (0.459; 408 of 870 without light trials). The proportions are not equal, though: ipsi-preferring neurons are the majority (bootstrap P = 0.99 and 0.97 for more ipsi than contra; two-sided binomial P = 0.016 and 0.072). In the 570 ms before the go cue the difference reaches P = 0.022 to 0.050 across neurons and light readings, and the delay epoch gives P = 0.051 to 0.12. The 8% modulation is matched in some readings (all neurons, 570 ms window, difference over the mean rate: 8.5%; selective neurons, delay, light trials left out: 8.0% and 8.3%), not in others (5.8% to 10.7%). Only one reading meets both the absence test and the 8% modulation: the selective neurons, the delay epoch, light trials left out (P = 0.060, 7.97%); it is the measured value. The outcome rests on (a)(i) because a claim of absence is judged on the paper's test as written ("the overall spike count across the recorded population"). The result depends on the window: in the 570 ms before the go cue, where the Results report the same P = 0.13 for all task-selective neurons, all four tests give P = 0.022 to 0.050 with a contralateral excess, the criterion's condition for not reproduced. The criterion sets no rule for deciding a claim of absence across its readings, so the outcome depends on which reading is taken: on the second P = 0.13 test of the paper, for all task-selective neurons, the 570 ms window gives P = 0.050 and 0.038 with a contralateral excess and the delay epoch P = 0.12 and 0.060.
ed1-fast-spiking-properties¶
Fast-spiking neurons have higher baseline firing rates and are less selective than putative pyramidal neurons.
Criterion: baseline (a) the 0.5 s before pole movement onset or (b) all recorded time before it in each trial; selectivity (i) the fraction significantly selective by the test of fig2-selective-fraction or (ii) the mean absolute spike rate difference between trial types over the three epochs. Reproduced when, under every reading, the fast-spiking median and mean baseline exceed the pyramidal ones and their selectivity is lower; a two-sided Mann-Whitney test is reported.
FSu = U[(U.width_class == "FS") & ~U.is_duplicate]
def props(units, light):
st = alm.epoch_stats(E, units, "correct", 1.3, light)
rows = []
for u in units.itertuples():
s = E[u.session]
tr = s["trials"]
t = tr[alm.trial_sets(tr, light)["correct"]]
sp = s["spikes"][u.unit]
rows.append(dict(idx=u.Index,
base_a=(alm.counts(sp, t["sample"].values - 0.5, t["sample"].values) / 0.5).mean(),
base_b=(alm.counts(sp, t.start.values, t["sample"].values) / (t["sample"] - t.start).values).mean()))
d = pd.DataFrame(rows).set_index("idx")
d["selective"] = (st[["p_sample", "p_delay", "p_response"]] < 0.05).any(axis=1)
d["absdiff"] = np.mean([abs(st["R_" + e] - st["L_" + e]) for e in ("sample", "delay", "response")], axis=0)
return d
rows = []
for light in ("any", "none"):
fs, py = props(FSu, light), props(P, light)
for k in ("base_a", "base_b", "absdiff"):
rows.append(dict(light_trials="kept" if light == "any" else "left out", measure=k, FS_median=fs[k].median(), FS_mean=fs[k].mean(),
Pyr_median=py[k].median(), Pyr_mean=py[k].mean(), MW_P=f"{stats.mannwhitneyu(fs[k], py[k]).pvalue:.1e}"))
rows.append(dict(light_trials="kept" if light == "any" else "left out", measure="fraction selective", FS_median=np.nan,
FS_mean=fs.selective.mean(), Pyr_median=np.nan, Pyr_mean=py.selective.mean(),
MW_P=f"Fisher {stats.fisher_exact([[fs.selective.sum(), (~fs.selective).sum()], [py.selective.sum(), (~py.selective).sum()]])[1]:.3f}"))
print(pd.DataFrame(rows).round(4).to_string(index=False))
print("For 'fraction selective', FS_mean and Pyr_mean are the fractions; FS units:", len(FSu), "; Pyr units:", len(P))
print("Pre-sample time recorded per trial (s), the span of baseline (b):",
round(float(np.median(np.concatenate([(s['trials']['sample'] - s['trials'].start).dropna().values for s in E]))), 3))
print("FS units per mouse:", FSu.groupby("subject").size().to_dict())
light_trials measure FS_median FS_mean Pyr_median Pyr_mean MW_P
kept base_a 4.5138 6.8955 1.5000 2.9152 1.3e-17
kept base_b 4.4763 6.8648 1.4906 2.8935 5.1e-18
kept absdiff 1.4574 2.0871 0.6204 1.3909 2.0e-10
kept fraction selective NaN 0.7500 NaN 0.7325 Fisher 0.749
left out base_a 4.3579 6.9517 1.4958 2.9209 1.9e-17
left out base_b 4.4363 6.9258 1.4983 2.9023 8.2e-18
left out absdiff 1.4575 2.1312 0.6202 1.4254 5.5e-11
left out fraction selective NaN 0.7097 NaN 0.6988 Fisher 0.838
For 'fraction selective', FS_mean and Pyr_mean are the fractions; FS units: 124 ; Pyr units: 1245
Pre-sample time recorded per trial (s), the span of baseline (b): 0.572
FS units per mouse: {'210861': 6, '210862': 6, '210863': 9, '214427': 3, '214429': 3, '214430': 3, '218453': 8, '218457': 11, '218693': 6, '219030': 9, '219031': 11, '219033': 5, '219036': 7, '219037': 4, '219038': 8, '219247': 4, '219248': 13, '219253': 6, '221977': 2}
Result: not reproduced. Fast-spiking neurons have higher baseline rates under both readings (median 4.51 against 1.50 spikes/s and mean 6.90 against 2.92 over the 0.5 s before the sample; Mann-Whitney P = 1e-17). They are not less selective: 0.750 of them are selective against 0.733 of putative pyramidal neurons (0.710 and 0.699 without the light trials), and their mean absolute rate difference is larger (median 1.46 against 0.62 spikes/s). The baseline part holds; the selectivity part goes the other way under both selectivity readings, and the criterion requires both parts under every reading.
tagging-counts¶
Of 73 neurons antidromically activated from pyramidal tract axons, 45 passed the collision test (PT neurons), 22 failed and 6 could not be tested; of 134 activated from IT axons, 27 passed (IT neurons), 106 failed and 1 could not be tested.
Criterion: counts of units by the labels the dataset gives for tagging outcome.
lab = U.label.value_counts()
tab = pd.DataFrame([dict(quantity="PT neurons", measured=int(lab.get("PT", 0)), expected=45, tolerance=4.5),
dict(quantity="IT neurons", measured=int(lab.get("IT", 0)), expected=27, tolerance=2.7),
dict(quantity="PT-activated, failed collision", measured=None, expected=22, tolerance=2.2),
dict(quantity="IT-activated, failed collision", measured=None, expected=106, tolerance=10.6)])
tab["within"] = [abs(m - e) <= t if m is not None else None for m, e, t in zip(tab.measured, tab.expected, tab.tolerance)]
print(tab.to_string(index=False))
print("Unit labels in the files:", sorted(U.label.unique()), "; no label marks activated neurons that failed the collision test or could not be tested")
print("Tagged units by mouse and genotype:")
g = pd.DataFrame(dict(subject=U.subject, label=U.label, genotype=[E[s]["genotype"] for s in U.session]))
print(g[g.label.isin(["PT", "IT"])].groupby(["genotype", "subject", "label"]).size().to_string())
quantity measured expected tolerance within
PT neurons 45.0 45 4.5 True
IT neurons 27.0 27 2.7 True
PT-activated, failed collision NaN 22 2.2 False
IT-activated, failed collision NaN 106 10.6 False
Unit labels in the files: [np.str_('FS'), np.str_('IT'), np.str_('PT'), np.str_('Pyr')] ; no label marks activated neurons that failed the collision test or could not be tested
Tagged units by mouse and genotype:
genotype subject label
chr2-eyfp x tlx-cre 218453 IT 5
218457 IT 3
rbp4-cre 214427 IT 3
PT 1
214429 PT 4
sim1-cre 219030 PT 4
219031 PT 1
219033 PT 3
219036 PT 8
219037 PT 12
219038 PT 12
tlx-cre 219247 IT 5
219248 IT 7
219253 IT 3
221977 IT 1
Result: partially reproduced. The files label 45 PT and 27 IT units, the paper's counts. The 22 and 106 activated neurons that failed the collision test, and the 7 that could not be tested, carry no label, so half of the counts cannot be checked.
antidromic-latencies¶
PT neurons had shorter antidromic latencies (mean 2.7 ms) than IT neurons (4.6 ms), and both had smaller jitter (0.1 and 0.2 ms) than activated neurons that failed collision (latency 8.3 and 9.8 ms, jitter 2.8 and 2.4 ms).
Criterion: latency is the time from light pulse onset to the antidromic spike, jitter its s.d. over pulses, per neuron, then averaged over neurons; tested only if the dataset holds the tagging trials, which it does (data check). Reproduced when PT latency is below IT latency and all values are within tolerance (the jitters to their printed precision, 0.1 and 0.2 ms); partially reproduced when the order holds and a value falls outside. The paper gives no rule for which spike is antidromic, so the claims file names three readings: (w) the first spike within a window after the first pulse of each pair, the window open (about 10 ms, up to the second pulse); (c) as (w), leaving out pulses preceded shortly by a spike (collision trials); (r) as (w), with only spikes in the evoked peak of each neuron's latency histogram. Here the window is 5, 8 or 10 ms; "shortly" is 5, 10 or 20 ms; the peak is the 0.1 ms histogram bin with most spikes, and spikes within 0.5 or 1 ms of it are kept. PT units are aligned to the pons light and IT units to the ALM light. The activated neurons that failed the collision test carry no label, so their four values cannot be measured.
readings = [("w", None)] + [("c", ms) for ms in (5, 10, 20)] + [("r", ms) for ms in (0.5, 1.0)]
rows, light_units = [], []
for u in U[U.label.isin(["PT", "IT"])].itertuples():
s = E[u.session]
site = "PONS" if u.label == "PT" else "ALM" # PT neurons are tagged from the pons, IT neurons from the other ALM
series = [v["onsets"] for k, v in s["laser"].items() if site in k]
if not series:
light_units.append(dict(label=u.label, genotype=s["genotype"], light=False))
continue
light_units.append(dict(label=u.label, genotype=s["genotype"], light=True))
on = np.concatenate(series)
sp = s["spikes"][u.unit]
for win in (0.005, 0.008, 0.010):
for rd, par in readings:
lat = alm.antidromic_latencies(sp, on, win, rd, par)
rows.append(dict(label=u.label, unit=u.Index, subject=u.subject, window_ms=win * 1000, reading=rd, par_ms=par,
pairs=len(alm.pair_onsets(on)), kept=len(lat), latency=lat.mean() if len(lat) else np.nan,
jitter=lat.std() if len(lat) > 1 else np.nan))
AL = pd.DataFrame(rows)
LU = pd.DataFrame(light_units)
print("Tagged units with and without a light series of their site:", LU.groupby(["label", "light"]).size().to_dict(),
"; without:", LU[~LU.light].groupby(["genotype", "label"]).size().to_dict())
AL["par_ms"] = AL.par_ms.fillna(0)
g = AL.groupby(["window_ms", "reading", "par_ms", "label"])
T = g[["latency", "jitter"]].mean().unstack("label")
T.columns = [f"{a}_{b}" for a, b in T.columns]
T = T.join(g.kept.median().unstack("label").add_prefix("median_pulses_kept_")).join(
g.latency.count().unstack("label").add_prefix("units_"))
T["order_PT_below_IT"] = T.latency_PT < T.latency_IT
T["PT_latency_ok"] = (T.latency_PT - 2.7).abs() <= 0.27
T["IT_latency_ok"] = (T.latency_IT - 4.6).abs() <= 0.46
T["PT_jitter_ok"] = T.jitter_PT.round(1) == 0.1
T["IT_jitter_ok"] = T.jitter_IT.round(1) == 0.2
print("Means over units (latency and jitter in ms). Readings: (w) first spike in the window after the first pulse of a pair;")
print("(c) as (w) without pulses with a spike in the par_ms before them; (r) as (w) within par_ms of the latency peak.")
print(T.round(3).reset_index().to_string(index=False))
print("Readings where PT latency is below IT latency:", int(T.order_PT_below_IT.sum()), "of", len(T),
"; where the four measurable values are all within tolerance:",
int(T[["PT_latency_ok", "IT_latency_ok", "PT_jitter_ok", "IT_jitter_ok"]].all(axis=1).sum()))
print("Paper: PT 2.7 ms (2.43 to 2.97), IT 4.6 ms (4.14 to 5.06); jitter PT 0.1 ms, IT 0.2 ms (to the printed precision);")
print("PT-activated 8.3 ms, 2.8 ms jitter; IT-activated 9.8 ms, 2.4 ms jitter (no labelled units)")
x = AL[(AL.window_ms == 10) & (AL.reading == "w")]
print("\nPer unit, reading (w), 10 ms window: latency range", x.groupby("label").latency.agg(["min", "max"]).round(2).to_dict("index"),
"; jitter range", x.groupby("label").jitter.agg(["min", "max"]).round(2).to_dict("index"),
"; pulses with a spike, min", x.groupby("label").kept.min().to_dict())
print("Per mouse, reading (w), 10 ms window (mean latency, mean jitter, units):")
print(x.groupby(["label", "subject"]).agg(latency=("latency", "mean"), jitter=("jitter", "mean"), units=("unit", "size")).round(2).to_string())
Tagged units with and without a light series of their site: {(np.str_('IT'), False): 8, (np.str_('IT'), True): 19, (np.str_('PT'), True): 45} ; without: {('chr2-eyfp x tlx-cre', np.str_('IT')): 8}
Means over units (latency and jitter in ms). Readings: (w) first spike in the window after the first pulse of a pair;
(c) as (w) without pulses with a spike in the par_ms before them; (r) as (w) within par_ms of the latency peak.
window_ms reading par_ms latency_IT latency_PT jitter_IT jitter_PT median_pulses_kept_IT median_pulses_kept_PT units_IT units_PT order_PT_below_IT PT_latency_ok IT_latency_ok PT_jitter_ok IT_jitter_ok
5.0 c 5.0 3.613 2.649 0.748 0.311 88.0 252.0 19 45 True True False False False
5.0 c 10.0 3.620 2.649 0.749 0.306 88.0 246.0 19 45 True True False False False
5.0 c 20.0 3.659 2.647 0.749 0.304 88.0 236.0 19 45 True True False False False
5.0 r 0.5 3.622 2.674 0.188 0.115 69.0 237.0 19 45 True True False True True
5.0 r 1.0 3.629 2.671 0.319 0.128 83.0 252.0 19 45 True True False True False
5.0 w 0.0 3.611 2.650 0.751 0.313 88.0 256.0 19 45 True True False False False
8.0 c 5.0 4.633 2.707 0.959 0.472 206.0 254.0 19 45 True True True False False
8.0 c 10.0 4.643 2.708 0.955 0.468 201.0 248.0 19 45 True True True False False
8.0 c 20.0 4.669 2.708 0.934 0.469 195.0 237.0 19 45 True True True False False
8.0 r 0.5 4.905 2.674 0.197 0.115 177.0 237.0 19 45 True True True True True
8.0 r 1.0 4.900 2.672 0.313 0.129 205.0 252.0 19 45 True True True True False
8.0 w 0.0 4.632 2.714 0.959 0.484 209.0 258.0 19 45 True True True False False
10.0 c 5.0 4.976 2.764 0.959 0.615 248.0 256.0 19 45 True True True False False
10.0 c 10.0 4.981 2.766 0.952 0.609 246.0 250.0 19 45 True True True False False
10.0 c 20.0 5.001 2.765 0.931 0.608 241.0 237.0 19 45 True True True False False
10.0 r 0.5 5.094 2.674 0.211 0.115 177.0 237.0 19 45 True True False True True
10.0 r 1.0 5.087 2.672 0.326 0.129 205.0 252.0 19 45 True True False True False
10.0 w 0.0 4.976 2.779 0.963 0.640 249.0 260.0 19 45 True True True False False
Readings where PT latency is below IT latency: 18 of 18 ; where the four measurable values are all within tolerance: 1
Paper: PT 2.7 ms (2.43 to 2.97), IT 4.6 ms (4.14 to 5.06); jitter PT 0.1 ms, IT 0.2 ms (to the printed precision);
PT-activated 8.3 ms, 2.8 ms jitter; IT-activated 9.8 ms, 2.4 ms jitter (no labelled units)
Per unit, reading (w), 10 ms window: latency range {np.str_('IT'): {'min': 3.1, 'max': 8.38}, np.str_('PT'): {'min': 1.79, 'max': 4.33}} ; jitter range {np.str_('IT'): {'min': 0.24, 'max': 2.07}, np.str_('PT'): {'min': 0.08, 'max': 2.03}} ; pulses with a spike, min {np.str_('IT'): 76, np.str_('PT'): 13}
Per mouse, reading (w), 10 ms window (mean latency, mean jitter, units):
latency jitter units
label subject
IT 214427 4.23 1.15 3
219247 5.69 1.03 5
219248 4.56 0.68 7
219253 4.45 1.09 3
221977 8.16 1.68 1
PT 214427 3.00 0.33 1
214429 3.02 0.34 4
219030 3.17 1.74 4
219031 3.39 1.52 1
219033 2.63 0.49 3
219036 2.94 0.57 8
219037 2.56 0.40 12
219038 2.65 0.65 12
Result: partially reproduced. PT latency is below IT latency in all 18 combinations of reading and parameter. PT latency is within tolerance in all of them (2.65 to 2.78 ms, paper 2.7), and IT latency in 10 (4.63 to 5.00 ms with the 8 and 10 ms windows under w and c, and 4.90 to 4.91 ms under r with the 8 ms window; 3.61 to 3.66 ms with the 5 ms window and 5.09 ms under r with the 10 ms window are outside 4.14 to 5.06). The jitter is within the printed precision only under reading (r): 0.115 to 0.129 ms for PT in every (r) row, and 0.188 to 0.211 ms for IT when spikes within 0.5 ms of the peak are kept (0.31 to 0.33 ms within 1 ms); under (w) and (c) it is 0.30 to 0.64 ms (PT) and 0.75 to 0.96 ms (IT), which include spontaneous spikes. All four measurable values are within tolerance in one combination (r, 8 ms window, 0.5 ms around the peak: PT 2.674 ms and 0.115 ms, IT 4.905 ms and 0.197 ms); keeping spikes within 0.5 ms of the peak bounds the s.d., so that agreement is partly set by the reading. The four values of the activated neurons that failed the collision test cannot be measured, so the claim cannot be fully reproduced, and the comparison of jitter with those neurons cannot be made. 19 of the 27 IT units have a light record. The window and the width around the peak are open parameters chosen here, and only 1 of the 18 combinations puts all four measurable values within tolerance.
fig3e-pt-it-both-activity-types¶
Both PT and IT neurons show preparatory and movement-related activity.
Criterion: the 45 PT and 27 IT neurons classified with the epoch rule and readings of fig2d-preparatory-perimovement; reproduced when, in each cell type, at least one neuron has preparatory selectivity and at least one peri-movement selectivity.
rows = []
for (tset, resp, light), r in EP.items():
sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
for tag in ("PT", "IT"):
s = sig.loc[P.index[P.tag == tag]]
prep, per = s.p_sample | s.p_delay, s.p_response
rows.append(dict(light_trials="kept" if light == "any" else "left out", trials=tset, response=resp, type=tag,
n=len(s), selective=int((prep | per).sum()),
preparatory=int(prep.sum()), peri_movement=int(per.sum()), prep_only=int((prep & ~per).sum()),
peri_only=int((per & ~prep).sum()), both=int((prep & per).sum())))
F3E = pd.DataFrame(rows)
print(F3E.to_string(index=False))
print("Holds under every reading:", bool(((F3E.preparatory > 0) & (F3E.peri_movement > 0)).all()))
light_trials trials response type n selective preparatory peri_movement prep_only peri_only both
kept correct 1.3 PT 45 29 17 24 5 12 12
kept correct 1.3 IT 27 18 13 13 5 5 8
kept correct 1.5 PT 45 29 17 23 6 12 11
kept correct 1.5 IT 27 18 13 14 4 5 9
kept nonearly 1.3 PT 45 26 14 21 5 12 9
kept nonearly 1.3 IT 27 17 13 13 4 4 9
kept nonearly 1.5 PT 45 25 14 20 5 11 9
kept nonearly 1.5 IT 27 17 13 13 4 4 9
left out correct 1.3 PT 45 32 17 25 7 15 10
left out correct 1.3 IT 27 18 13 13 5 5 8
left out correct 1.5 PT 45 31 17 24 7 14 10
left out correct 1.5 IT 27 18 13 14 4 5 9
left out nonearly 1.3 PT 45 26 13 22 4 13 9
left out nonearly 1.3 IT 27 17 13 11 6 4 7
left out nonearly 1.5 PT 45 26 13 22 4 13 9
left out nonearly 1.5 IT 27 17 13 11 6 4 7
Holds under every reading: True
Result: reproduced. Under the main reading, 17 of 45 PT neurons have preparatory and 24 peri-movement selectivity, and 13 of 27 IT neurons each; every reading, with or without the light trials, gives at least 11 neurons of each kind in each cell type.
fig3f-pt-contra-preferring¶
Contra-preferring neurons outnumber ipsi-preferring neurons among PT neurons (P < 0.05, bootstrap) but not among IT neurons (P = 0.74).
Criterion: contra- or ipsi-preferring by total spike count over the three epochs, lick right against lick left, correct trials; bootstrap with neurons resampled (10^4), P the fraction of resamples with more ipsi- than contra-preferring neurons. Readings: (a) all tagged neurons of the type, (b) only those selective by the test of fig2-selective-fraction. Reproduced when PT gives P < 0.05 and IT P >= 0.05 under the same reading. Trials with tagging light (t1) kept or (t2) left out, either of which may decide.
rng = np.random.default_rng(31)
rows = []
for light in ("any", "none"):
tot_all = alm.total_count_diff(E, P[P.tag != ""], light=light)
for reading in ("a_all", "b_selective"):
for tag in ("PT", "IT"):
idx = P.index[(P.tag == tag) & (SELSET[light].reindex(P.index).values if reading == "b_selective" else True)]
x = (tot_all.loc[idx] > 0).values
rows.append(dict(light_trials="kept" if light == "any" else "left out", reading=reading, type=tag, n=len(x),
contra=int(x.sum()), fraction=round(x.mean(), 3), P_boot=alm.boot_more_ipsi(x, rng)))
if light == "any":
by_mouse = pd.DataFrame(dict(subject=P.subject[P.tag == "PT"], c=(tot_all.loc[P.index[P.tag == "PT"]] > 0).values)).groupby("subject").c.agg(["sum", "size"])
F3F = pd.DataFrame(rows)
print(F3F.to_string(index=False))
print("Paper: PT P < 0.05, IT P = 0.74")
print("PT neurons by mouse, light trials kept (contra / n):")
print(by_mouse.to_string())
light_trials reading type n contra fraction P_boot
kept a_all PT 45 30 0.667 0.0098
kept a_all IT 27 13 0.481 0.5765
kept b_selective PT 29 18 0.621 0.0925
kept b_selective IT 18 7 0.389 0.7684
left out a_all PT 45 26 0.578 0.1447
left out a_all IT 27 14 0.519 0.4251
left out b_selective PT 32 17 0.531 0.2997
left out b_selective IT 18 8 0.444 0.5960
Paper: PT P < 0.05, IT P = 0.74
PT neurons by mouse, light trials kept (contra / n):
sum size
subject
214427 1 1
214429 3 4
219030 3 4
219031 1 1
219033 2 3
219036 6 8
219037 9 12
219038 5 12
# Balance of the tagging light between lick right and lick left trials (correct trials), and the
# contra-preference of tagged neurons within light and no-light trials. These are checks of reading
# (t1), not readings of the criterion.
rows = []
for i, s in enumerate(E):
tr = s["trials"]
t = tr[alm.trial_sets(tr)["correct"]]
if not t.light.any():
continue
r, l = (t.instruction == "right").values, t.light.values.astype(bool)
rows.append(dict(session=i, subject=s["subject"], n_PT=int(((P.session == i) & (P.tag == "PT")).sum()),
n_IT=int(((P.session == i) & (P.tag == "IT")).sum()), right_light=int((r & l).sum()), right=int(r.sum()),
left_light=int((~r & l).sum()), left=int((~r).sum()),
P_fisher=stats.fisher_exact([[(r & l).sum(), (r & ~l).sum()], [(~r & l).sum(), (~r & ~l).sum()]])[1]))
LB = pd.DataFrame(rows)
LB["frac_right"], LB["frac_left"] = LB.right_light / LB.right, LB.left_light / LB.left
print(f"Sessions with light in correct trials: {len(LB)}. Light in {LB.right_light.sum()} of {LB.right.sum()} lick right trials "
f"({LB.right_light.sum() / LB.right.sum():.3f}) and {LB.left_light.sum()} of {LB.left.sum()} lick left trials ({LB.left_light.sum() / LB.left.sum():.3f})")
print(f"Correct trials with light in these sessions: {LB.right_light.sum() + LB.left_light.sum()} of {LB.right.sum() + LB.left.sum()}")
print("Sessions where light and trial type are associated (Fisher P < 0.05):", int((LB.P_fisher < 0.05).sum()), "of", len(LB),
f"({0.05 * len(LB):.1f} expected by chance)")
for tag in ("PT", "IT"):
w = LB["n_" + tag]
dd = (LB.frac_right - LB.frac_left)[w > 0]
print(f"{tag}: {int(w.sum())} units in {int((w > 0).sum())} sessions with light; light fraction, lick right minus lick left, "
f"weighted by units: {np.average(LB.frac_right - LB.frac_left, weights=w):+.4f} (range {dd.min():+.3f} to {dd.max():+.3f})")
print("Sessions holding PT neurons:")
print(LB[LB.n_PT > 0].drop(columns="n_IT").round(3).to_string(index=False))
rng = np.random.default_rng(33)
rows = []
for u in P[P.tag != ""].itertuples():
s = E[u.session]
tr = s["trials"]
t = tr[alm.trial_sets(tr)["correct"]]
c = sum(alm.counts(s["spikes"][u.unit], a, b) for a, b in alm.epoch_windows(t, 1.3).values())
r, l = (t.instruction == "right").values, t.light.values.astype(bool)
d = lambda m: c[r & m].mean() - c[~r & m].mean() if (r & m).any() and (~r & m).any() else np.nan
rows.append(dict(type=u.tag, all_trials=d(np.ones_like(l)), no_light=d(~l), light_only=d(l),
ratio=c[l].mean() / c[~l].mean() if l.any() and c[~l].mean() > 0 else np.nan))
W = pd.DataFrame(rows)
W["mean_of_both"] = W[["no_light", "light_only"]].mean(axis=1)
out = []
for tag in ("PT", "IT"):
w = W[W.type == tag]
for col in ("all_trials", "no_light", "light_only", "mean_of_both"):
x = w[col].dropna()
out.append(dict(type=tag, trials=col, n=len(x), contra=int((x > 0).sum()), fraction=round((x > 0).mean(), 3),
P_boot=alm.boot_more_ipsi((x > 0).values, rng)))
print("\nContra-preferring tagged neurons by the trials used (mean_of_both: average of the no-light and light-only differences):")
print(pd.DataFrame(out).to_string(index=False))
print("Spike count over the three epochs, light trials over no-light trials, median over neurons: PT",
round(W.ratio[W.type == "PT"].median(), 3), "; IT", round(W.ratio[W.type == "IT"].median(), 3))
Sessions with light in correct trials: 75. Light in 2089 of 7092 lick right trials (0.295) and 2243 of 7296 lick left trials (0.307)
Correct trials with light in these sessions: 4332 of 14388
Sessions where light and trial type are associated (Fisher P < 0.05): 4 of 75 (3.8 expected by chance)
PT: 45 units in 32 sessions with light; light fraction, lick right minus lick left, weighted by units: -0.0049 (range -0.156 to +0.143)
IT: 19 units in 13 sessions with light; light fraction, lick right minus lick left, weighted by units: -0.0290 (range -0.107 to +0.092)
Sessions holding PT neurons:
session subject n_PT right_light right left_light left P_fisher frac_right frac_left
10 214427 1 13 67 19 75 0.428 0.194 0.253
14 214429 2 23 91 18 81 0.721 0.253 0.222
15 214429 1 38 143 42 158 1.000 0.266 0.266
16 214429 1 45 110 52 139 0.602 0.409 0.374
35 219030 1 30 77 23 79 0.237 0.390 0.291
37 219030 1 28 99 36 84 0.044 0.283 0.429
39 219030 2 41 100 33 93 0.461 0.410 0.355
41 219031 1 34 102 38 117 1.000 0.333 0.325
48 219033 1 29 128 37 130 0.319 0.227 0.285
49 219033 1 29 120 36 119 0.312 0.242 0.303
50 219033 1 34 149 38 166 1.000 0.228 0.229
52 219036 3 28 100 29 99 0.876 0.280 0.293
53 219036 1 28 103 28 98 0.876 0.272 0.286
54 219036 1 22 69 22 71 1.000 0.319 0.310
55 219036 1 30 91 32 91 0.876 0.330 0.352
57 219036 1 35 117 29 120 0.380 0.299 0.242
58 219036 1 24 92 28 102 0.872 0.261 0.275
59 219037 1 30 106 24 105 0.431 0.283 0.229
60 219037 2 33 140 34 139 0.889 0.236 0.245
61 219037 2 31 115 34 112 0.660 0.270 0.304
62 219037 1 34 136 39 132 0.414 0.250 0.295
63 219037 1 33 123 35 131 1.000 0.268 0.267
64 219037 1 25 121 34 116 0.135 0.207 0.293
65 219037 4 34 100 32 84 0.644 0.340 0.381
66 219038 2 16 84 27 78 0.032 0.190 0.346
67 219038 1 32 93 25 79 0.747 0.344 0.316
68 219038 2 28 93 27 91 1.000 0.301 0.297
69 219038 1 32 90 24 80 0.514 0.356 0.300
70 219038 2 36 84 22 77 0.071 0.429 0.286
71 219038 1 20 62 18 54 1.000 0.323 0.333
72 219038 1 23 90 15 74 0.462 0.256 0.203
73 219038 2 33 116 28 104 0.880 0.284 0.269
Contra-preferring tagged neurons by the trials used (mean_of_both: average of the no-light and light-only differences):
type trials n contra fraction P_boot
PT all_trials 45 30 0.667 0.0102
PT no_light 45 26 0.578 0.1440
PT light_only 45 26 0.578 0.1481
PT mean_of_both 45 29 0.644 0.0220
IT all_trials 27 13 0.481 0.5718
IT no_light 27 14 0.519 0.4247
IT light_only 19 9 0.474 0.5810
IT mean_of_both 27 14 0.519 0.4389
Spike count over the three epochs, light trials over no-light trials, median over neurons: PT 1.27 ; IT 1.343
Result: reproduced. With the light trials kept (t1), 30 of 45 PT neurons are contra-preferring (0.67; bootstrap P = 0.010) and 13 of 27 IT neurons (0.48; P = 0.58), so the claim holds under reading (a); restricted to selective neurons, PT gives 18 of 29 (P = 0.09). Without the trials with light (t2), PT gives 26 of 45 (0.58; P = 0.14) and 17 of 32 selective (P = 0.30), and the claim holds under no reading. The claims file lets either light reading decide, so t1 decides; the PT result depends on keeping the trials in which the PT neurons were driven by the tagging light. The light is balanced between the trial types: it falls in 0.295 of lick right and 0.307 of lick left correct trials, 4 of 75 sessions show an association at P < 0.05 (3.8 expected by chance), and weighted by PT neurons the light fraction is 0.005 lower on lick right trials, which would favour ipsi preference. PT neurons are contra-preferring in 26 of 45 on the trials without light and in 26 of 45 on the light trials alone (P = 0.14 and 0.15), and in 29 of 45 (P = 0.022) on the average of the two differences; light trials hold 1.27 times the spikes of the others (median over PT neurons). So the t1 result does not come from an imbalance of the light, and it reaches P < 0.05 only when the light and no-light trials are pooled.
fig3f-pt-contra-selectivity¶
Contralateral population selectivity emerges in PT neurons from 570 ms before the response epoch (34% spike rate modulation, P < 0.05), but not in IT neurons (-19%, P = 0.40).
Criterion: contra-selectivity is the rate difference, lick right minus lick left, per neuron, correct trials, averaged across neurons, in 200 ms windows. Readings: neurons (a) all tagged or (b) selective; test window (i) the 570 ms before the go cue or (ii) the delay epoch; modulation (x) the difference over the mean rate of both trial types or (y) over the lick left rate; onset the start of the earliest window from which the population contra-selectivity stays significantly above 0 (t-test across neurons, P < 0.05) until the go cue, windows stepped by 10 ms, each window standing for its (p) start, (q) centre or (r) end. Reproduced when, under one reading, PT gives P < 0.05 with positive selectivity, IT P >= 0.05, and the three values are within tolerance.
starts = np.round(np.arange(-3.0, 0.0 + 1e-9, 0.01), 3)
rows, onsets, curves3 = [], [], {}
for light in ("any", "none"):
lname = "kept" if light == "any" else "left out"
for reading in ("a_all", "b_selective"):
for tag in ("PT", "IT"):
units = P[(P.tag == tag) & (SELSET[light].reindex(P.index).values if reading == "b_selective" else True)]
W = alm.window_counts(E, units, starts, align="go", light=light)
Dm = np.array([R.mean(0) / 0.2 - L.mean(0) / 0.2 for R, L in W.values()])
p = stats.ttest_1samp(Dm, 0, axis=0).pvalue
sigpos = (p < 0.05) & (Dm.mean(0) > 0)
curves3[(lname, reading, tag)] = Dm.mean(0)
for label, off in (("p_start", 0.0), ("q_centre", 0.1), ("r_end", 0.2)):
tt = starts + off
v = tt <= 1e-9
sv, tv = sigpos[v], tt[v]
if sv[-1]:
k = len(sv) - 1
while k > 0 and sv[k - 1]:
k -= 1
onset = -tv[k] * 1000
else:
onset = np.nan
onsets.append(dict(light_trials=lname, reading=reading, type=tag, window_time=label, onset_ms_before_go=onset,
windows_significant_positive=int(sv.sum()), smallest_window_P=float(p[v].min())))
for win, (t0, t1) in (("i_570ms", (-0.57, 0.0)), ("ii_delay", (-1.3, 0.0))):
w = alm.window_diff(E, units, t0, t1, light=light)
d = w.R - w.L
t = stats.ttest_1samp(d, 0)
rows.append(dict(light_trials=lname, reading=reading, type=tag, window=win, n=len(d), diff_spikes_s=d.mean(), P=t.pvalue,
mod_x_pct=100 * d.mean() / ((w.R + w.L) / 2).mean(), mod_y_pct=100 * d.mean() / w.L.mean()))
F3S = pd.DataFrame(rows)
print(F3S.round(4).to_string(index=False))
print("\nOnsets (ms before the go cue; NaN where the last window before the go cue is not significant):")
print(pd.DataFrame(onsets).round(4).to_string(index=False))
print("Paper: PT onset 570 ms (513 to 627), PT modulation 34% (30.6 to 37.4), IT -19% (-20.9 to -17.1); PT P < 0.05, IT P = 0.40")
fig, ax = plt.subplots(figsize=(7, 3))
for key, c in curves3.items():
if key[0] == "kept":
ax.plot(starts + 0.1, c, label=", ".join(key[1:]))
ax.axhline(0, color="0.7"); ax.axvline(-1.3, color="0.7")
ax.set_xlabel("window centre, s from go cue"); ax.set_ylabel("contra-selectivity (spikes/s)"); ax.legend(fontsize=7)
plt.tight_layout(); plt.show()
print("Plotted range (light trials kept):", round(min(c.min() for k, c in curves3.items() if k[0] == "kept"), 2), "to",
round(max(c.max() for k, c in curves3.items() if k[0] == "kept"), 2), "spikes/s")
light_trials reading type window n diff_spikes_s P mod_x_pct mod_y_pct
kept a_all PT i_570ms 45 0.8715 0.1725 22.6074 25.4885
kept a_all PT ii_delay 45 0.5175 0.2923 13.9280 14.9706
kept a_all IT i_570ms 27 -0.4895 0.1096 -20.5699 -18.6516
kept a_all IT ii_delay 27 -0.3994 0.1042 -17.2839 -15.9091
kept b_selective PT i_570ms 29 1.1711 0.2379 23.7877 26.9989
kept b_selective PT ii_delay 29 0.6992 0.3606 14.8768 16.0723
kept b_selective IT i_570ms 18 -0.7431 0.0982 -28.2709 -24.7696
kept b_selective IT ii_delay 18 -0.6547 0.0609 -25.2829 -22.4454
left out a_all PT i_570ms 45 1.0713 0.1211 29.4028 34.4705
left out a_all PT ii_delay 45 0.6694 0.1921 18.8652 20.8300
left out a_all IT i_570ms 27 -0.5273 0.1094 -23.5982 -21.1077
left out a_all IT ii_delay 27 -0.4220 0.1220 -19.3535 -17.6460
left out b_selective PT i_570ms 32 1.4106 0.1463 32.2901 38.5071
left out b_selective PT ii_delay 32 0.8860 0.2189 20.9644 23.4193
left out b_selective IT i_570ms 18 -0.7544 0.1225 -29.6524 -25.8237
left out b_selective IT ii_delay 18 -0.6022 0.1328 -24.4889 -21.8175
Onsets (ms before the go cue; NaN where the last window before the go cue is not significant):
light_trials reading type window_time onset_ms_before_go windows_significant_positive smallest_window_P
kept a_all PT p_start NaN 0 0.0845
kept a_all PT q_centre NaN 0 0.1082
kept a_all PT r_end NaN 0 0.1082
kept a_all IT p_start NaN 0 0.0203
kept a_all IT q_centre NaN 0 0.0203
kept a_all IT r_end NaN 0 0.0203
kept b_selective PT p_start NaN 0 0.0332
kept b_selective PT q_centre NaN 0 0.0332
kept b_selective PT r_end NaN 0 0.0332
kept b_selective IT p_start NaN 0 0.0084
kept b_selective IT q_centre NaN 0 0.0084
kept b_selective IT r_end NaN 0 0.0084
left out a_all PT p_start NaN 16 0.0029
left out a_all PT q_centre NaN 16 0.0029
left out a_all PT r_end NaN 16 0.0029
left out a_all IT p_start NaN 0 0.0114
left out a_all IT q_centre NaN 0 0.0114
left out a_all IT r_end NaN 0 0.0114
left out b_selective PT p_start NaN 8 0.0126
left out b_selective PT q_centre NaN 8 0.0126
left out b_selective PT r_end NaN 8 0.0126
left out b_selective IT p_start NaN 0 0.0011
left out b_selective IT q_centre NaN 0 0.0011
left out b_selective IT r_end NaN 0 0.0011
Paper: PT onset 570 ms (513 to 627), PT modulation 34% (30.6 to 37.4), IT -19% (-20.9 to -17.1); PT P < 0.05, IT P = 0.40
Plotted range (light trials kept): -2.17 to 1.49 spikes/s
Result: not reproduced. PT neurons have a positive contra-selectivity before the go cue, but the test-window t-test is not significant under any reading (P = 0.17 to 0.36 with the light trials, 0.12 to 0.22 without), and the windowed selectivity is not significant up to the go cue, so no onset can be found (without light trials 8 to 16 single windows reach P < 0.05, not continuously). The PT modulation is 13.9% to 27.0% with the light trials and 18.9% to 38.5% without, against 34%. IT neurons are not significant (P = 0.06 to 0.13), with modulations of -15.9% to -29.7%.
imaging-selectivity-counts¶
In two-photon imaging of ALM (59 fields of view, 4 mice), 58% of cells (2,740 of 4,706) were task-related and 57% of those (1,575) trial-type selective, with a slight majority of contra-preferring cells (61%, 971 of 1,575).
Criterion: task-related when a Kruskal-Wallis test across 5-frame bins rejects equal fluorescence at P < 0.01 in contra or in ipsi trials; selective when a Wilcoxon rank sum test of DF/F between contra and ipsi trials gives P < 0.05; preference by the average signal over the trial epochs; DF/F0 with F0 the mean over 0.5 s before trial start, neuropil factor 0.7. Readings: Kruskal-Wallis span (a) sample onset to the end of the response epoch or (b) the whole recorded trial; rank sum of (i) the mean over the three epochs or (ii) each epoch; trials correct only or all non-early. The response epoch, which the paper does not time, is (r1) 1.3 s or (r2) 1.5 s; both are run. Cells are the ROIs marked included; contra trials are lick right trials (left ALM).
PT_ = {tset: [alm.plane_trials(pl, tset) for pl in O] for tset in ("correct", "nonearly")}
IMG = {}
rows = []
for resp in (1.3, 1.5):
for span in ("a", "b"):
for rank in ("i_mean", "ii_each"):
for tset in ("correct", "nonearly"):
res = [alm.classify_plane(X, rel, right, pl["included"], resp, span, rank.split("_")[1])
for (X, rel, right, _), pl in zip(PT_[tset], O)]
IMG[(resp, span, rank, tset)] = res
S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
rows.append(dict(response=resp, span=span, rank=rank, trials=tset,
cells=int(sum(r["included"].sum() for r in res)), task=int(sum(r["task"].sum() for r in res)),
selective=int(S.sum()), contra_frac=round((S & C).sum() / S.sum(), 3), planes=len(res)))
IM = pd.DataFrame(rows)
for col, (v, t) in dict(cells=(4706, 470.6), task=(2740, 274), selective=(1575, 157.5), contra_frac=(0.61, 0.061), planes=(59, 5.9)).items():
IM[col + "_ok"] = (IM[col] - v).abs() <= t + 1e-9
IM["all_ok"] = IM[[c for c in IM.columns if c.endswith("_ok")]].all(axis=1)
print(IM.to_string(index=False))
print("Paper: cells 4706, task-related 2740 (2466 to 3014), selective 1575 (1417.5 to 1732.5), contra 0.61 (0.549 to 0.671), planes 59")
print("Readings with every value within tolerance:", int(IM.all_ok.sum()), "of", len(IM))
print("Trials per plane (correct):", pd.Series([len(r) for _, _, r, _ in PT_["correct"]]).describe().round(1).to_dict())
key = (1.5, "a", "i_mean", "nonearly")
print("\nBy mouse, reading", key, "(task-related, selective, included):")
subj = np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])
r = IMG[key]
print(pd.DataFrame(dict(subject=subj, task=np.concatenate([x["task"] for x in r]), sel=np.concatenate([x["sel"] for x in r]),
inc=np.concatenate([x["included"] for x in r]))).groupby("subject").sum().to_string())
response span rank trials cells task selective contra_frac planes cells_ok task_ok selective_ok contra_frac_ok planes_ok all_ok
1.3 a i_mean correct 4706 2551 1315 0.579 59 True True False True True False
1.3 a i_mean nonearly 4706 2588 1345 0.590 59 True True False True True False
1.3 a ii_each correct 4706 2551 1844 0.560 59 True True False True True False
1.3 a ii_each nonearly 4706 2588 1849 0.557 59 True True False True True False
1.3 b i_mean correct 4706 3104 1469 0.573 59 True False True True True False
1.3 b i_mean nonearly 4706 3134 1480 0.577 59 True False True True True False
1.3 b ii_each correct 4706 3104 2096 0.557 59 True False False True True False
1.3 b ii_each nonearly 4706 3134 2093 0.547 59 True False False False True False
1.5 a i_mean correct 4706 2730 1365 0.581 59 True True False True True False
1.5 a i_mean nonearly 4706 2772 1417 0.588 59 True True False True True False
1.5 a ii_each correct 4706 2730 1956 0.566 59 True True False True True False
1.5 a ii_each nonearly 4706 2772 1957 0.557 59 True True False True True False
1.5 b i_mean correct 4706 3104 1481 0.577 59 True False True True True False
1.5 b i_mean nonearly 4706 3134 1513 0.578 59 True False True True True False
1.5 b ii_each correct 4706 3104 2138 0.561 59 True False False True True False
1.5 b ii_each nonearly 4706 3134 2133 0.549 59 True False False True True False
Paper: cells 4706, task-related 2740 (2466 to 3014), selective 1575 (1417.5 to 1732.5), contra 0.61 (0.549 to 0.671), planes 59
Readings with every value within tolerance: 0 of 16
Trials per plane (correct): {'count': 59.0, 'mean': 54.2, 'std': 9.4, 'min': 25.0, '25%': 49.0, '50%': 54.0, '75%': 60.5, 'max': 71.0}
By mouse, reading (1.5, 'a', 'i_mean', 'nonearly') (task-related, selective, included):
task sel inc
subject
216219 1029 575 1497
217951 404 131 775
221541 628 310 1148
226244 711 401 1286
# Sensitivity of the counts to trials whose baseline F0 is near 0 (a check, not a reading of the
# criterion). F0 is the mean of F over 0.5 s before the trial start, divided here by the ROI's median
# raw fluorescence; ROI-trials at or below a cut are left out of every test.
F0R = {tset: [alm.plane_trials(pl, tset, return_f0=True)[4] for pl in O] for tset in ("correct", "nonearly")}
r0 = np.concatenate([f[:, pl["included"]].ravel() for f, pl in zip(F0R["nonearly"], O)])
print(f"Included ROI-trials (non-early): {len(r0)}; F0 / median raw F, percentiles 0, 0.1, 1, 5, 50, 99:",
np.percentile(r0, [0, 0.1, 1, 5, 50, 99]).round(3).tolist())
print("ROI-trials with F0 <= 0:", int((r0 <= 0).sum()), "; below 0.05:", int((r0 < 0.05).sum()), "; below 0.1:", int((r0 < 0.1).sum()))
print("Included ROIs with F - 0.7 Fnp <= 0 in at least one frame:",
int(sum(((pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]] <= 0).any(0).sum() for pl in O)))
nfr, run, frac = [], [], []
for pl in O:
b = ((pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]] <= 0)
b = b[:, b.any(0)]
cur, best = np.zeros(b.shape[1], int), np.zeros(b.shape[1], int)
for row in b:
cur = np.where(row, cur + 1, 0)
best = np.maximum(best, cur)
nfr += b.sum(0).tolist(); run += best.tolist(); frac += (b.mean(0)).tolist()
nfr, run, frac = map(np.array, (nfr, run, frac))
print("Among them, frames at or below 0 per ROI: median", int(np.median(nfr)), ", max", int(nfr.max()),
"; fraction of the ROI's frames: median", round(float(np.median(frac)), 5), ", max", round(float(frac.max()), 4))
print("Longest run of consecutive frames at or below 0: ROIs with 1 frame", int((run == 1).sum()), ", 2 to 7 frames",
int(((run >= 2) & (run <= 7)).sum()), ", 8 or more (0.5 s or more at 14.84 Hz)", int((run >= 8).sum()), "; max", int(run.max()))
rows = []
for cut in (0.0, 0.05, 0.1):
for resp in (1.3, 1.5):
for span in ("a", "b"):
for rank in ("i_mean", "ii_each"):
for tset in ("correct", "nonearly"):
res = [alm.classify_plane(X, rel, right, pl["included"], resp, span, rank.split("_")[1], drop=f <= cut)
for (X, rel, right, _), pl, f in zip(PT_[tset], O, F0R[tset])]
S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
rows.append(dict(F0_cut=cut, response=resp, span=span, rank=rank, trials=tset, task=int(sum(r["task"].sum() for r in res)),
selective=int(S.sum()), contra_frac=round((S & C).sum() / S.sum(), 3)))
IMF = pd.DataFrame(rows)
IMF["all_ok"] = ((IMF.task - 2740).abs() <= 274) & ((IMF.selective - 1575).abs() <= 157.5) & ((IMF.contra_frac - 0.61).abs() <= 0.061 + 1e-9)
print(IMF.to_string(index=False))
print("Rows with task-related, selective and contra fraction all within tolerance (cells and planes are unchanged):",
IMF[IMF.all_ok][["F0_cut", "response", "span", "rank", "trials"]].values.tolist())
Included ROI-trials (non-early): 305349; F0 / median raw F, percentiles 0, 0.1, 1, 5, 50, 99: [-0.844, 0.062, 0.164, 0.229, 0.409, 2.868] ROI-trials with F0 <= 0: 145 ; below 0.05: 258 ; below 0.1: 622 Included ROIs with F - 0.7 Fnp <= 0 in at least one frame: 3403
Among them, frames at or below 0 per ROI: median 19 , max 1627 ; fraction of the ROI's frames: median 0.00167 , max 0.1993 Longest run of consecutive frames at or below 0: ROIs with 1 frame 2034 , 2 to 7 frames 1199 , 8 or more (0.5 s or more at 14.84 Hz) 170 ; max 518
F0_cut response span rank trials task selective contra_frac all_ok 0.00 1.3 a i_mean correct 2552 1315 0.578 False 0.00 1.3 a i_mean nonearly 2593 1344 0.589 False 0.00 1.3 a ii_each correct 2552 1843 0.560 False 0.00 1.3 a ii_each nonearly 2593 1850 0.556 False 0.00 1.3 b i_mean correct 3112 1468 0.573 False 0.00 1.3 b i_mean nonearly 3137 1479 0.576 False 0.00 1.3 b ii_each correct 3112 2094 0.556 False 0.00 1.3 b ii_each nonearly 3137 2093 0.546 False 0.00 1.5 a i_mean correct 2736 1365 0.581 False 0.00 1.5 a i_mean nonearly 2774 1416 0.588 False 0.00 1.5 a ii_each correct 2736 1954 0.566 False 0.00 1.5 a ii_each nonearly 2774 1958 0.557 False 0.00 1.5 b i_mean correct 3112 1482 0.576 False 0.00 1.5 b i_mean nonearly 3137 1512 0.578 False 0.00 1.5 b ii_each correct 3112 2137 0.561 False 0.00 1.5 b ii_each nonearly 3137 2135 0.549 False 0.05 1.3 a i_mean correct 2553 1315 0.578 False 0.05 1.3 a i_mean nonearly 2594 1343 0.589 False 0.05 1.3 a ii_each correct 2553 1844 0.559 False 0.05 1.3 a ii_each nonearly 2594 1848 0.556 False 0.05 1.3 b i_mean correct 3113 1470 0.573 False 0.05 1.3 b i_mean nonearly 3139 1478 0.576 False 0.05 1.3 b ii_each correct 3113 2094 0.556 False 0.05 1.3 b ii_each nonearly 3139 2090 0.546 False 0.05 1.5 a i_mean correct 2735 1365 0.581 False 0.05 1.5 a i_mean nonearly 2774 1415 0.589 False 0.05 1.5 a ii_each correct 2735 1953 0.566 False 0.05 1.5 a ii_each nonearly 2774 1957 0.557 False 0.05 1.5 b i_mean correct 3113 1483 0.577 False 0.05 1.5 b i_mean nonearly 3139 1511 0.579 False 0.05 1.5 b ii_each correct 3113 2137 0.561 False 0.05 1.5 b ii_each nonearly 3139 2133 0.549 False 0.10 1.3 a i_mean correct 2557 1311 0.577 False 0.10 1.3 a i_mean nonearly 2597 1346 0.588 False 0.10 1.3 a ii_each correct 2557 1845 0.560 False 0.10 1.3 a ii_each nonearly 2597 1850 0.557 False 0.10 1.3 b i_mean correct 3119 1466 0.572 False 0.10 1.3 b i_mean nonearly 3140 1481 0.576 False 0.10 1.3 b ii_each correct 3119 2098 0.557 False 0.10 1.3 b ii_each nonearly 3140 2094 0.546 False 0.10 1.5 a i_mean correct 2741 1366 0.580 False 0.10 1.5 a i_mean nonearly 2779 1419 0.587 True 0.10 1.5 a ii_each correct 2741 1954 0.567 False 0.10 1.5 a ii_each nonearly 2779 1962 0.557 False 0.10 1.5 b i_mean correct 3119 1485 0.575 False 0.10 1.5 b i_mean nonearly 3140 1515 0.578 False 0.10 1.5 b ii_each correct 3119 2139 0.561 False 0.10 1.5 b ii_each nonearly 3140 2138 0.549 False Rows with task-related, selective and contra fraction all within tolerance (cells and planes are unchanged): [[0.1, 1.5, 'a', 'i_mean', 'nonearly']]
# Per reading, how many of the five values are within tolerance and how far the selective count is
# from its range (1,417.5 to 1,732.5). The criterion sets only a reproduced condition, all values
# within tolerance under one reading.
tally = IM[["response", "span", "rank", "trials", "task", "selective", "contra_frac"]].copy()
tally["values_within"] = IM[[c for c in IM.columns if c.endswith("_ok") and c != "all_ok"]].sum(axis=1)
tally["selective_miss"] = np.where(IM.selective < 1417.5, IM.selective - 1417.5, np.where(IM.selective > 1732.5, IM.selective - 1732.5, 0))
print(tally.to_string(index=False))
print("Readings with all five values within tolerance:", int((tally.values_within == 5).sum()), "of", len(tally),
"; with four:", int((tally.values_within == 4).sum()), "; readings with the selective count within tolerance:", int((tally.selective_miss == 0).sum()))
response span rank trials task selective contra_frac values_within selective_miss
1.3 a i_mean correct 2551 1315 0.579 4 -102.5
1.3 a i_mean nonearly 2588 1345 0.590 4 -72.5
1.3 a ii_each correct 2551 1844 0.560 4 111.5
1.3 a ii_each nonearly 2588 1849 0.557 4 116.5
1.3 b i_mean correct 3104 1469 0.573 4 0.0
1.3 b i_mean nonearly 3134 1480 0.577 4 0.0
1.3 b ii_each correct 3104 2096 0.557 3 363.5
1.3 b ii_each nonearly 3134 2093 0.547 2 360.5
1.5 a i_mean correct 2730 1365 0.581 4 -52.5
1.5 a i_mean nonearly 2772 1417 0.588 4 -0.5
1.5 a ii_each correct 2730 1956 0.566 4 223.5
1.5 a ii_each nonearly 2772 1957 0.557 4 224.5
1.5 b i_mean correct 3104 1481 0.577 4 0.0
1.5 b i_mean nonearly 3134 1513 0.578 4 0.0
1.5 b ii_each correct 3104 2138 0.561 3 405.5
1.5 b ii_each nonearly 3134 2133 0.549 3 400.5
Readings with all five values within tolerance: 0 of 16 ; with four: 12 ; readings with the selective count within tolerance: 4
Result: not reproduced. The dandiset holds the paper's 4,706 cells in 59 planes. Several readings put the task-related count within tolerance (2,551 to 2,772 with span a; 3,104 and 3,134 with span b are above it), and all readings but one put the contra-preferring fraction within it (0.547 to 0.590, against a lower limit of 0.549). The selective count is below tolerance with the mean over the epochs (1,315 to 1,417 where the task-related count fits) and above it with each epoch separately (1,844 to 1,957); the paper's 1,575 lies between the two. The closest reading gives 1,417 (1.5 s response epoch, span a, mean over epochs, all non-early trials), just below the lower limit of 1,417.5. No reading puts all five values within tolerance; four match under several readings and the fifth falls just outside.
Baselines near 0 matter little: 145 of 305,349 included ROI-trials have F0 at or below 0, and 622 are below a tenth of the ROI's median fluorescence. The neuropil-corrected F of 3,403 included ROIs reaches 0 in at least one frame, mostly briefly: the longest such run is one frame in 2,034 ROIs, two to seven frames in 1,199 and eight frames (0.5 s) or more in 170 (up to 518 frames). Leaving them out moves the counts by a few cells; in the closest reading the selective count becomes 1,416 (cut at 0) or 1,419 (cut at 0.1), the latter within tolerance. These cuts are checks chosen here, not readings of the criterion, so they do not decide the outcome, but they show that the selective count sits at the edge of the tolerance. Early licks cannot be left out: the imaging files hold no lick events (only the sample, delay and go events) and flag no trial as an early lick.
The criterion sets only a reproduced condition, all values within tolerance under one reading, and no reading meets it (0 of 16 in the tally above). Four values are within tolerance in 12 readings, but the selective count is within tolerance only in the 4 readings with span (b) and the mean over epochs, where the task-related count is above it. The selective count is measured under every reading and misses, so it is not a class the dandiset lacks, as the intermediate units of units-classification are. That the paper's 1,575 lies between the counts of two readings does not put it within tolerance under one. The outcome was partially reproduced until the third verification round, which agreed with the change to not reproduced.
fig4f-spatially-intermingled¶
Contra- and ipsi-preferring neurons are found in all fields of view and are spatially intermingled; nearest-neighbour selectivity and same-type distances do not differ from shuffled data (P = 0.34 and P = 0.61).
Criterion: a claim of absence on the paper's two tests: (1) the probability that each selective cell's nearest neighbour (among (a) selective cells or (b) all cells) has the same preference, against shuffled labels, P the fraction of shuffles with a larger probability; (2) the mean distance to the nearest cell of the same preference, P the fraction of shuffles with a smaller distance; labels shuffled within each field of view or across all; 10^4 shuffles. Reproduced when both tests give P >= 0.05 and both preferences occur in every field of view; not reproduced when either test gives P < 0.05. Distances are between ROI centroids in pixels (the files give no scale). Run for every reading of imaging-selectivity-counts.
rng = np.random.default_rng(4)
rows = []
for key, res in IMG.items():
both = sum(bool((r["sel"] & r["contra"]).any() and (r["sel"] & ~r["contra"]).any()) for r in res)
for nbset in ("selective", "all"):
for shuf in ("within", "across"):
out = alm.spatial_tests(O, res, nbset, shuf, rng)
rows.append(dict(response=key[0], span=key[1], rank=key[2], trials=key[3], neighbours=nbset, shuffle=shuf,
planes_with_both=both, P_prob=out["P_prob"], prob=out["prob"], prob_shuffle=out["prob_shuffle"],
P_dist=out["P_dist"], dist=out["dist"], dist_shuffle=out["dist_shuffle"]))
SP = pd.DataFrame(rows)
SP["absent"] = (SP.P_prob >= 0.05) & (SP.P_dist >= 0.05)
pd.set_option("display.max_rows", 100)
print(SP.round(4).to_string(index=False))
print("\nTest rows with both P >= 0.05:", int(SP.absent.sum()), "of", len(SP),
"; selection readings with both preferences in all 59 planes:", int((SP.groupby(["response", "span", "rank", "trials"]).planes_with_both.first() == 59).sum()), "of 16")
print("Rows with P < 0.05 in at least one test, by neighbour set and shuffle:")
print((~SP.absent).groupby([SP.neighbours, SP.shuffle]).sum().to_string())
response span rank trials neighbours shuffle planes_with_both P_prob prob prob_shuffle P_dist dist dist_shuffle absent
1.3 a i_mean correct selective within 58 0.1033 0.5388 0.5177 0.1066 71.0163 72.1926 True
1.3 a i_mean correct selective across 58 0.0606 0.5388 0.5120 0.0358 71.0163 72.7570 False
1.3 a i_mean correct all within 58 0.0140 0.2002 0.1779 0.0999 71.0163 72.2061 False
1.3 a i_mean correct all across 58 0.0071 0.2002 0.1758 0.0360 71.0163 72.7656 False
1.3 a i_mean nonearly selective within 57 0.0772 0.5420 0.5188 0.0331 69.5117 71.1751 False
1.3 a i_mean nonearly selective across 57 0.0660 0.5420 0.5160 0.0556 69.5117 71.0349 True
1.3 a i_mean nonearly all within 57 0.0075 0.2149 0.1895 0.0300 69.5117 71.1952 False
1.3 a i_mean nonearly all across 57 0.0068 0.2149 0.1887 0.0570 69.5117 71.0461 False
1.3 a ii_each correct selective within 59 0.0001 0.5705 0.5149 0.0020 59.5247 61.4798 False
1.3 a ii_each correct selective across 59 0.0000 0.5705 0.5069 0.0008 59.5247 61.6898 False
1.3 a ii_each correct all within 59 0.0001 0.2674 0.2273 0.0011 59.5247 61.4909 False
1.3 a ii_each correct all across 59 0.0000 0.2674 0.2254 0.0005 59.5247 61.6873 False
1.3 a ii_each nonearly selective within 58 0.0003 0.5622 0.5111 0.0009 59.3822 61.4944 False
1.3 a ii_each nonearly selective across 58 0.0001 0.5622 0.5060 0.0002 59.3822 61.5531 False
1.3 a ii_each nonearly all within 58 0.0002 0.2635 0.2245 0.0008 59.3822 61.4761 False
1.3 a ii_each nonearly all across 58 0.0001 0.2635 0.2235 0.0001 59.3822 61.5436 False
1.3 b i_mean correct selective within 58 0.2489 0.5283 0.5174 0.0867 67.3616 68.5257 True
1.3 b i_mean correct selective across 58 0.1401 0.5283 0.5105 0.0730 67.3616 68.6224 True
1.3 b i_mean correct all within 58 0.0060 0.2158 0.1905 0.0866 67.3616 68.5153 False
1.3 b i_mean correct all across 58 0.0038 0.2158 0.1887 0.0734 67.3616 68.6228 False
1.3 b i_mean nonearly selective within 58 0.0532 0.5459 0.5207 0.0248 65.9053 67.5346 False
1.3 b i_mean nonearly selective across 58 0.0166 0.5459 0.5116 0.0097 65.9053 67.8073 False
1.3 b i_mean nonearly all within 58 0.0017 0.2338 0.2025 0.0249 65.9053 67.5310 False
1.3 b i_mean nonearly all across 58 0.0009 0.2338 0.2005 0.0102 65.9053 67.8140 False
1.3 b ii_each correct selective within 59 0.0004 0.5601 0.5151 0.0197 56.6194 57.7642 False
1.3 b ii_each correct selective across 59 0.0000 0.5601 0.5062 0.0127 56.6194 57.8782 False
1.3 b ii_each correct all within 59 0.0000 0.2863 0.2520 0.0194 56.6194 57.7663 False
1.3 b ii_each correct all across 59 0.0000 0.2863 0.2487 0.0144 56.6194 57.8721 False
1.3 b ii_each nonearly selective within 59 0.0000 0.5748 0.5116 0.0003 55.9740 57.7680 False
1.3 b ii_each nonearly selective across 59 0.0000 0.5748 0.5040 0.0007 55.9740 57.7859 False
1.3 b ii_each nonearly all within 59 0.0000 0.2948 0.2540 0.0004 55.9740 57.7903 False
1.3 b ii_each nonearly all across 59 0.0000 0.2948 0.2505 0.0002 55.9740 57.7905 False
1.5 a i_mean correct selective within 58 0.1340 0.5367 0.5184 0.0782 70.6012 71.8689 True
1.5 a i_mean correct selective across 58 0.0772 0.5367 0.5127 0.0914 70.6012 71.8329 True
1.5 a i_mean correct all within 58 0.0201 0.2038 0.1829 0.0752 70.6012 71.8866 False
1.5 a i_mean correct all across 58 0.0151 0.2038 0.1809 0.0948 70.6012 71.8297 False
1.5 a i_mean nonearly selective within 58 0.0178 0.5558 0.5220 0.0085 67.5254 69.5928 False
1.5 a i_mean nonearly selective across 58 0.0072 0.5558 0.5153 0.0037 67.5254 69.8467 False
1.5 a i_mean nonearly all within 58 0.0029 0.2246 0.1955 0.0095 67.5254 69.5828 False
1.5 a i_mean nonearly all across 58 0.0016 0.2246 0.1936 0.0040 67.5254 69.8442 False
1.5 a ii_each correct selective within 59 0.0006 0.5593 0.5142 0.0046 58.7129 60.2167 False
1.5 a ii_each correct selective across 59 0.0002 0.5593 0.5089 0.0047 58.7129 60.2357 False
1.5 a ii_each correct all within 59 0.0002 0.2669 0.2312 0.0036 58.7129 60.2221 False
1.5 a ii_each correct all across 59 0.0001 0.2669 0.2298 0.0045 58.7129 60.2430 False
1.5 a ii_each nonearly selective within 59 0.0000 0.5723 0.5161 0.0009 57.9501 59.8414 False
1.5 a ii_each nonearly selective across 59 0.0000 0.5723 0.5064 0.0003 57.9501 60.0173 False
1.5 a ii_each nonearly all within 59 0.0000 0.2724 0.2340 0.0006 57.9501 59.8478 False
1.5 a ii_each nonearly all across 59 0.0000 0.2724 0.2306 0.0004 57.9501 60.0099 False
1.5 b i_mean correct selective within 58 0.1631 0.5361 0.5202 0.0515 66.5540 67.9087 True
1.5 b i_mean correct selective across 58 0.0685 0.5361 0.5117 0.0317 66.5540 68.1374 False
1.5 b i_mean correct all within 58 0.0021 0.2235 0.1961 0.0488 66.5540 67.9038 False
1.5 b i_mean correct all across 58 0.0021 0.2235 0.1935 0.0278 66.5540 68.1576 False
1.5 b i_mean nonearly selective within 58 0.0388 0.5506 0.5232 0.0058 65.0816 67.0870 False
1.5 b i_mean nonearly selective across 58 0.0083 0.5506 0.5119 0.0056 65.0816 67.1998 False
1.5 b i_mean nonearly all within 58 0.0022 0.2386 0.2082 0.0064 65.0816 67.0835 False
1.5 b i_mean nonearly all across 58 0.0010 0.2386 0.2052 0.0057 65.0816 67.1971 False
1.5 b ii_each correct selective within 59 0.0002 0.5599 0.5140 0.0176 56.0287 57.1516 False
1.5 b ii_each correct selective across 59 0.0002 0.5599 0.5071 0.0102 56.0287 57.2454 False
1.5 b ii_each correct all within 59 0.0002 0.2881 0.2530 0.0159 56.0287 57.1660 False
1.5 b ii_each correct all across 59 0.0000 0.2881 0.2505 0.0107 56.0287 57.2430 False
1.5 b ii_each nonearly selective within 59 0.0000 0.5715 0.5142 0.0024 55.7341 57.2363 False
1.5 b ii_each nonearly selective across 59 0.0000 0.5715 0.5047 0.0012 55.7341 57.3130 False
1.5 b ii_each nonearly all within 59 0.0001 0.2916 0.2554 0.0022 55.7341 57.2384 False
1.5 b ii_each nonearly all across 59 0.0001 0.2916 0.2507 0.0022 55.7341 57.3091 False
Test rows with both P >= 0.05: 7 of 64 ; selection readings with both preferences in all 59 planes: 7 of 16
Rows with P < 0.05 in at least one test, by neighbour set and shuffle:
neighbours shuffle
all across 16
within 16
selective across 13
within 12
Result: not reproduced. In 57 of the 64 combinations of selection reading, neighbour set and shuffle, at least one test finds clustering at P < 0.05 (nearby cells share their preference more often, and same-preference cells are closer, than with shuffled labels); every combination with all cells as neighbours does. In 9 of the 16 selection readings, some plane lacks one of the two preferences. The effect is small (for example a same-preference probability of 0.539 against 0.517 for shuffles), and neighbouring ROIs share neuropil, which may contribute. The shuffle across planes mixes labels between planes with different contra fractions, so it is not a spatial null; with the shuffle within planes, 28 of the 32 rows still give P < 0.05.
fig4h-pt-contra-imaging¶
Among layer 5 neurons, about a quarter of labelled PT (182 of 534) and IT (174 of 542) neurons were selective; 67% of selective PT neurons were contra-preferring (P < 0.001, binomial test) but only 48% of IT neurons, and PT neurons were more often contra-preferring than IT neurons (P < 0.001, chi-squared test).
Criterion: labelled PT and IT cells imaged 450 to 700 um deep; selectivity and preference as imaging-selectivity-counts, with its readings. Reproduced when, under one reading, the PT binomial test (against 0.5, two-sided; one-sided reported) gives P < 0.001 with a contralateral majority, the IT test P > 0.05, the chi-squared test of PT against IT P < 0.001, and the counts are within tolerance; partially reproduced when the three tests come out so and a value falls outside. The paper selects layer 5 only by imaging depth and says IT neurons were also found in superficial layers. The files give no plane depth, so layer 5 cannot be selected; the claims file says that all labelled cells is a substitute population, not a reading of the paper. The claim is blocked (metadata insufficient), and every included cell labelled PT or IT is shown as a variant.
# Every text field and attribute of the 59 imaging files, searched for a plane depth
MD = alm.ophys_metadata_all(assets)
MD = MD[~MD.field.str.endswith(("@object_id", "@namespace", "@neurodata_type"))]
print("Fields read per file:", MD.groupby("file").size().unique().tolist(), "; distinct fields:", MD.field.nunique())
nv = MD.groupby("field").text.nunique()
print("Fields whose value differs between files:", nv[nv > 1].to_dict())
print("Values of the fields that describe the plane, the images or the session:")
for k in sorted(MD.field.unique()):
if any(w in k for w in ("ImagingPlane", "description", "images", "comments", "identifier", "virus")):
v = MD.text[MD.field == k].unique()
print(f" {k}: {v[0][:150]!r}" + (f" (and {len(v) - 1} other values)" if len(v) > 1 else ""))
hit = MD[MD.text.str.contains(r"depth|micron|\bum\b|\u00b5m|layer|\bL5", case=False, regex=True)]
print("Fields mentioning depth, um, microns or layer:", len(hit))
print("File names (first 3):", sorted(MD.file.unique())[:3])
Fields read per file: [58] ; distinct fields: 58
Fields whose value differs between files: {'general/subject/date_of_birth': 4, 'general/subject/genotype': 2, 'general/subject/subject_id': 4, 'general/virus': 4, 'identifier': 59, 'session_start_time': 18, 'timestamps_reference_time': 18}
Values of the fields that describe the plane, the images or the session:
acquisition/images@description: 'Structural images of a scanning'
general/experiment_description: 'Two-photon experiment recorded in ALM'
general/optophysiology/ImagingPlane/description: 'Imaging session for PT and IT neurons during audio delay task'
general/optophysiology/ImagingPlane/excitation_lambda: '940.0'
general/optophysiology/ImagingPlane/green/description: 'green channel'
general/optophysiology/ImagingPlane/green/emission_lambda: '500.0'
general/optophysiology/ImagingPlane/imaging_rate: '14.840000000000304'
general/optophysiology/ImagingPlane/indicator: 'GCaMP6s'
general/optophysiology/ImagingPlane/location: 'ALM'
general/virus: '[{"injection_id": "1", "virus": "V", "injection_date": "2013-06-27", "injection_volume": "150.0", "virus_dilution": "None", "brain_location_name": "le' (and 3 other values)
identifier: '216219_2013-08-15_5' (and 58 other values)
intervals/trials/early_lick@description: 'varchar(32) #'
intervals/trials/outcome@description: 'varchar(32) #'
intervals/trials/start_time@description: 'Start time of epoch, in seconds'
intervals/trials/stop_time@description: 'Stop time of epoch, in seconds'
intervals/trials/task@description: 'varchar(12) # task type'
intervals/trials/task_protocol@description: 'tinyint # task protocol'
intervals/trials/trial_instruction@description: 'varchar(16) #'
intervals/trials@description: 'experimental trials'
processing/behavior/BehavioralEvents/delay@comments: 'no comments'
processing/behavior/BehavioralEvents/delay@description: 'Time stamps of the beginning of the delay period on each trial.'
processing/behavior/BehavioralEvents/go@comments: 'no comments'
processing/behavior/BehavioralEvents/go@description: 'Time stamps of the go cue signal on each trial.'
processing/behavior/BehavioralEvents/sample@comments: 'no comments'
processing/behavior/BehavioralEvents/sample@description: 'Time stamps of the beginning of the sampling period on each trial.'
processing/behavior@description: 'Time of behavioral events in this session'
processing/ophys/Fluorescence/NeuropilResponseSeries@comments: 'no comments'
processing/ophys/Fluorescence/NeuropilResponseSeries@description: 'average fluorescence of the neuropil surrounding the roi'
processing/ophys/Fluorescence/RoiResponseSeries/rois@description: 'table region for rois in this segmentation'
processing/ophys/Fluorescence/RoiResponseSeries@comments: 'no comments'
processing/ophys/Fluorescence/RoiResponseSeries@description: 'average fluorescence of roi'
processing/ophys/ImageSegmentation/PlaneSegmentation/cell_type@description: 'PT, IT, or unknown'
processing/ophys/ImageSegmentation/PlaneSegmentation/image_mask@description: 'Image masks for each ROI'
processing/ophys/ImageSegmentation/PlaneSegmentation/included@description: 'whether to include this roi into later analyses'
processing/ophys/ImageSegmentation/PlaneSegmentation/neuropil_mask@description: 'mask of neurophil surrounding this roi'
processing/ophys/ImageSegmentation/PlaneSegmentation@description: 'output from segmenting the current imaging plane'
processing/ophys@description: 'Processing result of imaging'
session_description: 'Imaging session'
Fields mentioning depth, um, microns or layer: 0
File names (first 3): ['sub-216219/sub-216219_ses-20130815_obj-12g09i6_behavior+ophys.nwb', 'sub-216219/sub-216219_ses-20130815_obj-1eyeli4_behavior+ophys.nwb', 'sub-216219/sub-216219_ses-20130815_obj-1u51sit_behavior+ophys.nwb']
types = np.concatenate([pl["roi_type"] for pl in O])
inc = np.concatenate([pl["included"] for pl in O])
rows = []
for key, res in IMG.items():
S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
pt, it = (types == "PT") & inc, (types == "IT") & inc
kp, np_ = int((pt & S & C).sum()), int((pt & S).sum())
ki, ni = int((it & S & C).sum()), int((it & S).sum())
tab = [[kp, np_ - kp], [ki, ni - ki]]
rows.append(dict(response=key[0], span=key[1], rank=key[2], trials=key[3], PT=int(pt.sum()), PT_sel=np_,
PT_contra=round(kp / np_, 3), IT=int(it.sum()), IT_sel=ni, IT_contra=round(ki / ni, 3),
P_PT=stats.binomtest(kp, np_, 0.5).pvalue, P_PT_1sided=stats.binomtest(kp, np_, 0.5, alternative="greater").pvalue,
P_IT=stats.binomtest(ki, ni, 0.5).pvalue, P_chi2=stats.chi2_contingency(tab).pvalue,
P_chi2_nocorr=stats.chi2_contingency(tab, correction=False).pvalue))
H = pd.DataFrame(rows)
H["tests_ok"] = (H.P_PT < 0.001) & (H.PT_contra > 0.5) & (H.P_IT > 0.05) & (H.P_chi2 < 0.001)
for col, (v, t) in dict(PT=(534, 53.4), IT=(542, 54.2), PT_sel=(182, 18.2), IT_sel=(174, 17.4), PT_contra=(0.67, 0.067), IT_contra=(0.48, 0.048)).items():
H[col + "_ok"] = (H[col] - v).abs() <= t + 1e-9
print(H.round(4).to_string(index=False))
print("Readings where the three tests come out as in the paper:", int(H.tests_ok.sum()), "of", len(H))
print("Readings with PT binomial P < 0.001:", int((H.P_PT < 0.001).sum()), "; with chi-squared P < 0.001:", int((H.P_chi2 < 0.001).sum()),
"; smallest chi-squared P:", round(H.P_chi2.min(), 4))
print("Labelled cells by mouse (included):")
print(pd.crosstab(np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])[inc], types[inc]).to_string())
response span rank trials PT PT_sel PT_contra IT IT_sel IT_contra P_PT P_PT_1sided P_IT P_chi2 P_chi2_nocorr tests_ok PT_ok IT_ok PT_sel_ok IT_sel_ok PT_contra_ok IT_contra_ok
1.3 a i_mean correct 530 152 0.599 843 231 0.519 0.0184 0.0092 0.5987 0.1557 0.1273 False True False False False False True
1.3 a i_mean nonearly 530 150 0.627 843 231 0.511 0.0024 0.0012 0.7925 0.0342 0.0262 False True False False False True True
1.3 a ii_each correct 530 213 0.592 843 318 0.503 0.0091 0.0045 0.9553 0.0556 0.0452 False True False False False False True
1.3 a ii_each nonearly 530 201 0.592 843 322 0.494 0.0109 0.0055 0.8673 0.0357 0.0285 False True False False False False True
1.3 b i_mean correct 530 173 0.613 843 255 0.510 0.0037 0.0019 0.8023 0.0453 0.0357 False True False True False True True
1.3 b i_mean nonearly 530 170 0.635 843 257 0.494 0.0005 0.0003 0.9007 0.0056 0.0041 False True False True False True True
1.3 b ii_each correct 530 247 0.599 843 357 0.493 0.0022 0.0011 0.8324 0.0128 0.0101 False True False False False False True
1.3 b ii_each nonearly 530 236 0.610 843 368 0.476 0.0009 0.0004 0.3755 0.0016 0.0012 False True False False False True True
1.5 a i_mean correct 530 155 0.594 843 237 0.532 0.0242 0.0121 0.3632 0.2704 0.2278 False True False False False False False
1.5 a i_mean nonearly 530 159 0.635 843 247 0.514 0.0008 0.0004 0.7027 0.0216 0.0164 False True False False False True True
1.5 a ii_each correct 530 223 0.596 843 333 0.517 0.0048 0.0024 0.5838 0.0770 0.0635 False True False False False False True
1.5 a ii_each nonearly 530 220 0.577 843 342 0.497 0.0259 0.0129 0.9569 0.0763 0.0630 False True False False False False True
1.5 b i_mean correct 530 173 0.607 843 256 0.516 0.0060 0.0030 0.6618 0.0773 0.0621 False True False True False True True
1.5 b i_mean nonearly 530 174 0.649 843 263 0.494 0.0001 0.0000 0.9019 0.0020 0.0014 False True False True False True True
1.5 b ii_each correct 530 249 0.610 843 365 0.496 0.0006 0.0003 0.9166 0.0066 0.0052 False True False False False True True
1.5 b ii_each nonearly 530 244 0.602 843 373 0.477 0.0017 0.0008 0.4074 0.0030 0.0023 False True False False False False True
Readings where the three tests come out as in the paper: 0 of 16
Readings with PT binomial P < 0.001: 5 ; with chi-squared P < 0.001: 0 ; smallest chi-squared P: 0.0016
Labelled cells by mouse (included):
col_0 IT PT Pyr interneuron unknown
row_0
216219 374 0 0 2 1121
217951 95 34 0 0 646
221541 233 286 23 7 599
226244 141 210 0 0 935
Result: blocked (metadata insufficient). The paper's layer 5 population cannot be selected, as no plane gives its depth. On the substitute of all labelled cells, 530 included cells are labelled PT (paper 534) and 843 IT (paper 542). Selective PT cells are contra-preferring in a majority under every reading (0.577 to 0.649; binomial P < 0.001 in 5 of 16 readings) and IT cells are not (0.476 to 0.532; P > 0.05 in all). The chi-squared test of PT against IT never reaches P < 0.001 (smallest 0.0016), so on this population the three tests do not come out as in the paper in any reading. The IT count is 301 above the paper's, consistent with the IT cells outside layer 5 that the paper reports. A search of every text field and attribute of the 59 imaging files (58 each, including the ImagingPlane group, the session description and the image descriptions) finds no mention of depth, microns or layer; the fields that differ between files are the subject, its virus record, the dates and the identifier.
reaction-time¶
The reaction time after the go cue was 61.1 +/- 10.5 ms (mean +/- s.d. across mice).
Criterion: reaction time from the mouth-movement signal (photodiode and infrared laser); tolerance
the paper's spread across mice. Readings: mice (a) the electrophysiology mice of Fig. 3 (here the 14
mice with PT or IT units) or (b) all 19 mice with the signal; per-mouse value (i) mean or (ii)
median of per-trial reaction times; correct trials or all non-early trials. The paper gives no rule
for reading a reaction time from the signal, so the claims file names two: (k) the first time after
the go cue the signal crosses a fixed threshold on the raw signal, and (z) the first time it exceeds
the baseline mean plus a number of s.d., with the delay epoch or its last 0.5 s as baseline; the
threshold, the number of s.d. and the baseline are open. The lick_trace series of the
extracellular files is taken as that signal (data check). Several values of each open parameter are
run. A trial without a crossing in the 0.4 s after the cue has no reaction time and is counted. For (k) a
crossing needs the signal below the threshold at the cue.
started_lick = time.time()
LK = alm.lick_all(E)
print(f"Lick trace read for {len(LK)} sessions in {time.time() - started_lick:.0f} s")
fig3_mice = sorted({s["subject"] for s in E if np.isin(s["cell_type"], ["PT", "IT"]).any()})
rows = []
for w in LK:
s = E[w["session"]]
stored = ~np.isnan(w["trace"][:, 0])
base = dict(subject=s["subject"], session=w["session"], correct=w["correct"], light=w["light"], stored=stored)
for bname, key in (("delay", "delay"), ("last 0.5 s", "last")):
for k in (2, 3, 4, 5, 10):
lvl = w[f"base_{key}_mean"] + k * w[f"base_{key}_sd"]
rows.append(pd.DataFrame(dict(base, reading="z", baseline=bname, param=float(k),
rt=alm.first_crossing_ms(w["trace"], lvl, w["rate"]))))
for th in (0.1, 0.15, 0.2, 0.5, 1.0, 2.0, 4.0):
rt = alm.first_crossing_ms(w["trace"], np.full(len(stored), th), w["rate"])
rt[~(w["at_go"] < th)] = np.nan
rows.append(pd.DataFrame(dict(base, reading="k", baseline="raw", param=th, rt=rt)))
RT = pd.concat(rows, ignore_index=True)
print("Non-early trials:", int((RT.reading == "k").sum() / 7), "; without a stored window around the cue:",
int((~RT.stored).sum() / 17))
RT = RT[RT.stored]
out = []
for (rd, bl, par), g in RT.groupby(["reading", "baseline", "param"], sort=False):
for tset in ("correct", "nonearly"):
gt = g[g.correct] if tset == "correct" else g
for mice in ("a_fig3", "b_all"):
h = gt[gt.subject.isin(fig3_mice)] if mice == "a_fig3" else gt
for agg in ("mean", "median"):
pm = h.groupby("subject").rt.agg(agg)
out.append(dict(reading=rd, baseline=bl, param=par, trials=tset, mice=mice, per_mouse=agg,
mice_with_value=int(pm.notna().sum()), trials_n=len(h),
no_crossing=round(h.rt.isna().mean(), 3), mean_ms=round(pm.mean(), 1),
sd_ms=round(pm.std(), 1), within=abs(pm.mean() - 61.1) <= 10.5))
RTs = pd.DataFrame(out)
print("Mean and s.d. across mice of the per-mouse reaction time (param: s.d. above the baseline mean for z, raw threshold in a.u. for k):")
print(RTs.to_string(index=False))
print("Paper: 61.1 ms (50.6 to 71.6), s.d. across mice 10.5 ms")
print("\nRows within tolerance by reading and parameter (of 8 each):")
print(RTs.groupby(["reading", "baseline", "param"], sort=False).within.sum().to_string())
print("Range of the mean across mice, reading z:", RTs[RTs.reading == "z"].mean_ms.min(), "to", RTs[RTs.reading == "z"].mean_ms.max(),
"; reading k:", RTs[RTs.reading == "k"].mean_ms.min(), "to", RTs[RTs.reading == "k"].mean_ms.max())
sub = RT[(RT.reading == "z") & (RT.baseline == "last 0.5 s") & (RT.param == 3) & RT.correct]
print("\nReading z, last 0.5 s, 3 s.d., correct trials, per mouse (mean and median ms, trials, fraction without a crossing):")
print(sub.groupby("subject").rt.agg(["mean", "median", "size", lambda v: v.isna().mean()]).round(2).rename(columns={"<lambda_0>": "no_crossing"}).to_string())
print("Same, light trials left out: mean across mice", round(sub[~sub.light].groupby("subject").rt.mean().mean(), 1), "ms")
sess = []
for w in LK:
ok = ~np.isnan(w["trace"][:, 0]) & w["correct"]
d = w["trace"][ok] - w["base_last_mean"][ok, None]
sess.append(dict(session=w["session"], subject=E[w["session"]]["subject"], baseline=np.median(w["base_last_mean"][ok]),
change_100ms=d[:, 1000].mean(), change_200ms=d[:, 2000].mean()))
sess = pd.DataFrame(sess)
print("\nPer session: median baseline (a.u.) range", round(sess.baseline.min(), 3), "to", round(sess.baseline.max(), 3),
"; sessions where the mean signal 100 ms after the cue is below its baseline:", int((sess.change_100ms < 0).sum()),
"of", len(sess), "in mice", sorted(sess[sess.change_100ms < 0].subject.unique()))
Lick trace read for 99 sessions in 176 s
Non-early trials: 24023 ; without a stored window around the cue: 113
Mean and s.d. across mice of the per-mouse reaction time (param: s.d. above the baseline mean for z, raw threshold in a.u. for k):
reading baseline param trials mice per_mouse mice_with_value trials_n no_crossing mean_ms sd_ms within
z delay 2.00 correct a_fig3 mean 14 15874 0.081 61.3 42.6 True
z delay 2.00 correct a_fig3 median 14 15874 0.081 56.4 40.7 True
z delay 2.00 correct b_all mean 19 18295 0.084 54.7 38.1 True
z delay 2.00 correct b_all median 19 18295 0.084 50.5 36.8 False
z delay 2.00 nonearly a_fig3 mean 14 20691 0.081 60.6 41.3 True
z delay 2.00 nonearly a_fig3 median 14 20691 0.081 56.0 40.6 True
z delay 2.00 nonearly b_all mean 19 23910 0.086 54.5 36.8 True
z delay 2.00 nonearly b_all median 19 23910 0.086 50.3 36.6 False
z delay 3.00 correct a_fig3 mean 14 15874 0.118 79.2 42.0 False
z delay 3.00 correct a_fig3 median 14 15874 0.118 71.8 37.0 False
z delay 3.00 correct b_all mean 19 18295 0.124 73.5 37.5 False
z delay 3.00 correct b_all median 19 18295 0.124 67.9 32.6 True
z delay 3.00 nonearly a_fig3 mean 14 20691 0.115 78.7 40.9 False
z delay 3.00 nonearly a_fig3 median 14 20691 0.115 71.7 37.0 False
z delay 3.00 nonearly b_all mean 19 23910 0.123 73.4 36.5 False
z delay 3.00 nonearly b_all median 19 23910 0.123 67.9 32.6 True
z delay 4.00 correct a_fig3 mean 14 15874 0.144 94.3 43.5 False
z delay 4.00 correct a_fig3 median 14 15874 0.144 82.8 38.0 False
z delay 4.00 correct b_all mean 19 18295 0.149 88.6 40.5 False
z delay 4.00 correct b_all median 19 18295 0.149 79.1 34.8 False
z delay 4.00 nonearly a_fig3 mean 14 20691 0.140 93.3 42.3 False
z delay 4.00 nonearly a_fig3 median 14 20691 0.140 82.0 37.9 False
z delay 4.00 nonearly b_all mean 19 23910 0.147 88.1 39.5 False
z delay 4.00 nonearly b_all median 19 23910 0.147 78.7 34.7 False
z delay 5.00 correct a_fig3 mean 14 15874 0.166 104.4 46.2 False
z delay 5.00 correct a_fig3 median 14 15874 0.166 92.2 41.2 False
z delay 5.00 correct b_all mean 19 18295 0.170 97.8 43.2 False
z delay 5.00 correct b_all median 19 18295 0.170 87.5 37.8 False
z delay 5.00 nonearly a_fig3 mean 14 20691 0.162 103.2 44.7 False
z delay 5.00 nonearly a_fig3 median 14 20691 0.162 91.3 40.4 False
z delay 5.00 nonearly b_all mean 19 23910 0.169 97.1 42.0 False
z delay 5.00 nonearly b_all median 19 23910 0.169 86.8 37.2 False
z delay 10.00 correct a_fig3 mean 14 15874 0.254 129.1 51.4 False
z delay 10.00 correct a_fig3 median 14 15874 0.254 111.7 48.2 False
z delay 10.00 correct b_all mean 19 18295 0.270 120.7 48.1 False
z delay 10.00 correct b_all median 19 18295 0.270 105.3 44.3 False
z delay 10.00 nonearly a_fig3 mean 14 20691 0.255 127.3 49.0 False
z delay 10.00 nonearly a_fig3 median 14 20691 0.255 107.2 42.6 False
z delay 10.00 nonearly b_all mean 19 23910 0.274 119.3 46.1 False
z delay 10.00 nonearly b_all median 19 23910 0.274 101.8 39.4 False
z last 0.5 s 2.00 correct a_fig3 mean 14 15874 0.061 44.3 34.5 False
z last 0.5 s 2.00 correct a_fig3 median 14 15874 0.061 42.2 41.3 False
z last 0.5 s 2.00 correct b_all mean 19 18295 0.065 42.8 29.7 False
z last 0.5 s 2.00 correct b_all median 19 18295 0.065 40.5 36.1 False
z last 0.5 s 2.00 nonearly a_fig3 mean 14 20691 0.063 43.9 33.3 False
z last 0.5 s 2.00 nonearly a_fig3 median 14 20691 0.063 41.7 41.1 False
z last 0.5 s 2.00 nonearly b_all mean 19 23910 0.068 42.7 28.6 False
z last 0.5 s 2.00 nonearly b_all median 19 23910 0.068 40.2 36.0 False
z last 0.5 s 3.00 correct a_fig3 mean 14 15874 0.080 61.9 37.7 True
z last 0.5 s 3.00 correct a_fig3 median 14 15874 0.080 60.8 38.5 True
z last 0.5 s 3.00 correct b_all mean 19 18295 0.089 60.8 32.5 True
z last 0.5 s 3.00 correct b_all median 19 18295 0.089 59.5 33.0 True
z last 0.5 s 3.00 nonearly a_fig3 mean 14 20691 0.081 61.4 36.6 True
z last 0.5 s 3.00 nonearly a_fig3 median 14 20691 0.081 60.3 38.4 True
z last 0.5 s 3.00 nonearly b_all mean 19 23910 0.092 60.5 31.5 True
z last 0.5 s 3.00 nonearly b_all median 19 23910 0.092 59.2 32.9 True
z last 0.5 s 4.00 correct a_fig3 mean 14 15874 0.097 75.5 39.7 False
z last 0.5 s 4.00 correct a_fig3 median 14 15874 0.097 72.8 39.8 False
z last 0.5 s 4.00 correct b_all mean 19 18295 0.106 74.8 35.7 False
z last 0.5 s 4.00 correct b_all median 19 18295 0.106 71.8 35.5 False
z last 0.5 s 4.00 nonearly a_fig3 mean 14 20691 0.097 74.9 38.8 False
z last 0.5 s 4.00 nonearly a_fig3 median 14 20691 0.097 72.2 39.5 False
z last 0.5 s 4.00 nonearly b_all mean 19 23910 0.109 74.4 35.2 False
z last 0.5 s 4.00 nonearly b_all median 19 23910 0.109 71.5 35.3 True
z last 0.5 s 5.00 correct a_fig3 mean 14 15874 0.112 83.8 39.5 False
z last 0.5 s 5.00 correct a_fig3 median 14 15874 0.112 78.1 38.4 False
z last 0.5 s 5.00 correct b_all mean 19 18295 0.123 82.7 36.1 False
z last 0.5 s 5.00 correct b_all median 19 18295 0.123 77.1 34.7 False
z last 0.5 s 5.00 nonearly a_fig3 mean 14 20691 0.111 83.0 38.8 False
z last 0.5 s 5.00 nonearly a_fig3 median 14 20691 0.111 77.5 38.2 False
z last 0.5 s 5.00 nonearly b_all mean 19 23910 0.125 82.2 35.8 False
z last 0.5 s 5.00 nonearly b_all median 19 23910 0.125 76.7 34.6 False
z last 0.5 s 10.00 correct a_fig3 mean 14 15874 0.169 106.3 42.7 False
z last 0.5 s 10.00 correct a_fig3 median 14 15874 0.169 96.3 40.4 False
z last 0.5 s 10.00 correct b_all mean 19 18295 0.195 103.6 39.0 False
z last 0.5 s 10.00 correct b_all median 19 18295 0.195 93.6 36.4 False
z last 0.5 s 10.00 nonearly a_fig3 mean 14 20691 0.169 105.1 41.5 False
z last 0.5 s 10.00 nonearly a_fig3 median 14 20691 0.169 95.4 39.8 False
z last 0.5 s 10.00 nonearly b_all mean 19 23910 0.198 102.6 38.4 False
z last 0.5 s 10.00 nonearly b_all median 19 23910 0.198 92.8 36.2 False
k raw 0.10 correct a_fig3 mean 9 15874 0.586 86.4 52.0 False
k raw 0.10 correct a_fig3 median 9 15874 0.586 91.0 63.6 False
k raw 0.10 correct b_all mean 10 18295 0.641 77.9 56.1 False
k raw 0.10 correct b_all median 10 18295 0.641 82.0 66.4 False
k raw 0.10 nonearly a_fig3 mean 9 20691 0.590 86.6 49.6 False
k raw 0.10 nonearly a_fig3 median 9 20691 0.590 87.7 60.2 False
k raw 0.10 nonearly b_all mean 10 23910 0.645 78.0 54.1 False
k raw 0.10 nonearly b_all median 10 23910 0.645 79.0 63.1 False
k raw 0.15 correct a_fig3 mean 13 15874 0.376 124.9 57.8 False
k raw 0.15 correct a_fig3 median 13 15874 0.376 126.3 64.1 False
k raw 0.15 correct b_all mean 17 18295 0.356 115.2 58.8 False
k raw 0.15 correct b_all median 17 18295 0.356 115.3 65.4 False
k raw 0.15 nonearly a_fig3 mean 13 20691 0.383 124.9 58.0 False
k raw 0.15 nonearly a_fig3 median 13 20691 0.383 126.7 64.3 False
k raw 0.15 nonearly b_all mean 17 23910 0.367 115.7 58.7 False
k raw 0.15 nonearly b_all median 17 23910 0.367 115.7 65.7 False
k raw 0.20 correct a_fig3 mean 14 15874 0.497 159.6 72.0 False
k raw 0.20 correct a_fig3 median 14 15874 0.497 148.4 70.3 False
k raw 0.20 correct b_all mean 19 18295 0.533 160.7 63.3 False
k raw 0.20 correct b_all median 19 18295 0.533 152.8 62.1 False
k raw 0.20 nonearly a_fig3 mean 14 20691 0.524 160.1 70.7 False
k raw 0.20 nonearly a_fig3 median 14 20691 0.524 148.5 69.9 False
k raw 0.20 nonearly b_all mean 19 23910 0.563 160.7 62.2 False
k raw 0.20 nonearly b_all median 19 23910 0.563 151.9 61.6 False
k raw 0.50 correct a_fig3 mean 9 15874 0.811 153.4 79.3 False
k raw 0.50 correct a_fig3 median 9 15874 0.811 140.1 71.8 False
k raw 0.50 correct b_all mean 11 18295 0.826 145.6 77.4 False
k raw 0.50 correct b_all median 11 18295 0.826 132.3 73.6 False
k raw 0.50 nonearly a_fig3 mean 9 20691 0.815 155.0 77.4 False
k raw 0.50 nonearly a_fig3 median 9 20691 0.815 140.5 72.2 False
k raw 0.50 nonearly b_all mean 11 23910 0.831 145.4 78.2 False
k raw 0.50 nonearly b_all median 11 23910 0.831 130.6 76.6 False
k raw 1.00 correct a_fig3 mean 8 15874 0.848 146.8 78.2 False
k raw 1.00 correct a_fig3 median 8 15874 0.848 134.4 73.3 False
k raw 1.00 correct b_all mean 10 18295 0.858 137.9 78.7 False
k raw 1.00 correct b_all median 10 18295 0.858 126.3 76.4 False
k raw 1.00 nonearly a_fig3 mean 8 20691 0.847 149.9 76.6 False
k raw 1.00 nonearly a_fig3 median 8 20691 0.847 134.9 73.4 False
k raw 1.00 nonearly b_all mean 10 23910 0.859 146.5 70.4 False
k raw 1.00 nonearly b_all median 10 23910 0.859 126.3 76.3 False
k raw 2.00 correct a_fig3 mean 5 15874 0.877 142.9 95.1 False
k raw 2.00 correct a_fig3 median 5 15874 0.877 123.5 88.1 False
k raw 2.00 correct b_all mean 7 18295 0.885 130.9 92.3 False
k raw 2.00 correct b_all median 7 18295 0.885 118.0 86.7 False
k raw 2.00 nonearly a_fig3 mean 5 20691 0.874 151.5 95.9 False
k raw 2.00 nonearly a_fig3 median 5 20691 0.874 137.7 88.4 False
k raw 2.00 nonearly b_all mean 7 23910 0.884 136.2 94.3 False
k raw 2.00 nonearly b_all median 7 23910 0.884 126.7 89.7 False
k raw 4.00 correct a_fig3 mean 4 15874 0.884 164.8 69.1 False
k raw 4.00 correct a_fig3 median 4 15874 0.884 152.0 65.5 False
k raw 4.00 correct b_all mean 5 18295 0.899 144.3 75.4 False
k raw 4.00 correct b_all median 5 18295 0.899 128.7 77.0 False
k raw 4.00 nonearly a_fig3 mean 4 20691 0.886 166.1 68.6 False
k raw 4.00 nonearly a_fig3 median 4 20691 0.886 152.3 66.1 False
k raw 4.00 nonearly b_all mean 5 23910 0.900 146.1 74.4 False
k raw 4.00 nonearly b_all median 5 23910 0.900 129.0 77.5 False
Paper: 61.1 ms (50.6 to 71.6), s.d. across mice 10.5 ms
Rows within tolerance by reading and parameter (of 8 each):
reading baseline param
z delay 2.00 6
3.00 2
4.00 0
5.00 0
10.00 0
last 0.5 s 2.00 0
3.00 8
4.00 1
5.00 0
10.00 0
k raw 0.10 0
0.15 0
0.20 0
0.50 0
1.00 0
2.00 0
4.00 0
Range of the mean across mice, reading z: 40.2 to 129.1 ; reading k: 77.9 to 166.1
Reading z, last 0.5 s, 3 s.d., correct trials, per mouse (mean and median ms, trials, fraction without a crossing):
mean median size no_crossing
subject
210861 44.32 45.70 249 0.00
210862 61.53 59.75 396 0.02
210863 62.88 60.65 576 0.22
214427 40.85 40.50 874 0.19
214429 94.51 85.20 943 0.39
214430 67.97 64.50 494 0.46
218453 61.57 52.30 778 0.00
218457 42.21 44.20 1200 0.00
218693 50.82 49.30 706 0.00
219030 80.09 71.80 816 0.34
219031 53.90 55.65 934 0.00
219033 42.01 40.70 1446 0.00
219036 43.92 41.00 1303 0.00
219037 50.55 55.80 1660 0.00
219038 42.32 44.00 1349 0.00
219247 41.15 43.00 738 0.00
219248 178.90 185.70 2013 0.22
219253 32.38 33.70 1302 0.00
221977 62.90 57.35 518 0.02
Same, light trials left out: mean across mice 61.0 ms
Per session: median baseline (a.u.) range 0.069 to 4.83 ; sessions where the mean signal 100 ms after the cue is below its baseline: 15 of 99 in mice ['210863', '214427', '214429', '214430', '219030', '219248']
Result: blocked (methods underspecified). The parameter the paper leaves open sets the value. Under reading (z) the mean across mice is 40.2 to 129.1 ms and rises with the number of s.d.: with the last 0.5 s as baseline, 40.2 to 44.3 ms at 2 s.d., 59.2 to 61.9 ms at 3, 71.5 to 75.5 ms at 4, 76.7 to 83.8 ms at 5 and 92.8 to 106.3 ms at 10. It is within 61.1 +/- 10.5 ms in all 8 rows at 3 s.d. with the last 0.5 s, in 6 of 8 at 2 s.d. and 2 of 8 at 3 s.d. with the delay epoch, in 1 at 4 s.d., and in none at 5 or 10. Under reading (k) no single raw threshold suits every session, whose baselines run from 0.069 to 4.83 a.u.: 36% to 90% of trials have no crossing, and the mean is 77.9 to 166.1 ms, never within tolerance. As the number of s.d. can be chosen to give any mean from about 40 to 130 ms and the paper does not give it, the claim cannot be tested on these data. Where the mean matches (z, last 0.5 s, 3 s.d.), the s.d. across mice is 31.5 to 38.5 ms against the paper's 10.5; mouse 219248 gives 178.9 ms, and the signal falls after the cue in 15 of the 99 sessions, from 6 mice including 219248. Leaving out the light trials changes the mean little (61.0 ms). Reading (z) counts only upward crossings, so in the 15 sessions where the signal falls after the cue the fall is not detected.
Summary¶
| Claim | Outcome |
|---|---|
| behaviour-performance | reproduced |
| fig1d-delay-inhibition-ipsi-bias | blocked: data missing |
| fig1d-sample-inhibition-smaller | blocked: data missing |
| units-classification | partially reproduced |
| fig2-selective-fraction | reproduced |
| fig2d-preparatory-perimovement | not reproduced |
| fig2c-selectivity-time-course | reproduced |
| fig2c-population-dynamics | reproduced |
| ed2c-error-trials-predict-choice | reproduced |
| fig2e-no-contra-bias-spikes | reproduced |
| ed1-fast-spiking-properties | not reproduced |
| tagging-counts | partially reproduced |
| antidromic-latencies | partially reproduced |
| fig3e-pt-it-both-activity-types | reproduced |
| fig3f-pt-contra-preferring | reproduced |
| fig3f-pt-contra-selectivity | not reproduced |
| imaging-selectivity-counts | not reproduced |
| fig4f-spatially-intermingled | not reproduced |
| fig4h-pt-contra-imaging | blocked: metadata insufficient |
| reaction-time | blocked: methods underspecified |
| fig5b-pt-delay-activation-contra-bias | blocked: data missing |
| fig5b-pt-sample-activation-contra-bias | blocked: data missing |
| ed6b-pt-dose-response | blocked: data missing |
| ed6d-it-activation-inconsistent | blocked: data missing |
| ed3e-high-power-contra-licking | blocked: data missing |
| ed7-pt-activation-persistent | blocked: data missing |
| ed3-anatomy-it-pt-separate | blocked: data missing |
A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.
How the analysis differs from the paper's. The behaviour uses the 23 mice of the dandiset, not the paper's 42, and only trials with events; early licks are flagged only in the extracellular files, and trials with tagging light are left out. The electrophysiology claims of Figs 2 and 3 are run with (t1) and without (t2) the trials with tagging light, the two readings the claims file names; for fig3f-pt-contra-preferring only t1 holds, and for fig2c-population-dynamics t1 holds in every reading and t2 in all but two. PT/IT duplicates are counted once. The method details the paper leaves open are run under every reading the claims file names; where readings differ in outcome the notes say which ones match. Open parameters within a reading (the antidromic window, the collision interval and the width of the evoked peak; the reaction-time threshold and number of s.d.) are run at several values. The layer 5 imaging claim is blocked, as no plane gives its depth; all labelled cells are shown as a variant. The inclusion flag of the files stands in for the Methods' brightness rule. Spatial distances are in pixels. Checks added after the first verification (light balance between trial types for fig3f-pt-contra-preferring, imaging baselines near 0, a search of the imaging metadata for a depth) are shown but decide no outcome. After the second verification a change of imaging-selectivity-counts to not reproduced was proposed, as no reading meets its only condition; the third round agreed, and it is not reproduced.
What was found about the dataset. The dandiset holds the paper's 99 extracellular sessions (19 mice) and 59 imaging planes (4 mice, 4,706 included cells), with 1,245 putative pyramidal and 124 fast-spiking units and the 45 PT and 27 IT labels. It holds none of the photoinhibition and photoactivation data (Figures 1 and 5, Extended Data Figures 3, 6 and 7) and no histology. It holds the tagging light of 75 sessions as laser power traces, delivered during behavioural trials that the trial tables do not flag; the traces are labelled watts but hold milliwatts, and the two Tlx x Ai32 tagging mice have none. Every PT and IT unit is deposited twice (as PT or IT and as Pyr). The 39 intermediate-width units are absent. The extracellular trial tables hold 1,540 trials with a lick instruction that carry no events; 1,447 are marked "right", "hit" and "no early" and 1,345 of these hold no spikes. Taken at face value they inflate performance; almost all are in the sessions with tagging light, and they are not the photoactivation trials. The imaging files flag no early licks, give no plane depth, and label the imaging mice "Rosa-lsl-tdtomato" where the paper names C57Bl/6Crl. The lick trace is present but not documented as the reaction time signal, and its level and direction differ between sessions (median baselines 0.069 to 4.83 a.u.; it falls after the go cue in 15 of 99 sessions).