Reproducing Guo et al. (2017) from dandiset 000009¶
Paper. Guo ZV, Inagaki HK, Daie K, Druckmann S, Gerfen CR, Svoboda K (2017). Maintenance of
persistent activity in a frontal thalamocortical loop. Nature 545, 181-186.
doi:10.1038/nature22324. The claims file
(claims/000009/10.1038-nature22324.yaml) was written from the HHMI author manuscript in PubMed
Central (PMC6431254): main text, Methods and the legends of Figures 1 to 6 and Extended Data
Figures 1 to 10; the Supplementary Information and Supplementary Tables were not in the text read.
Dandiset. DANDI:000009, version 0.220126.1903. The paper cites no DANDI version (it names a CRCNS deposit, doi:10.6080/K03F4MH2). This is the only published version; the draft lists the same 173 assets and size, so the most recent published version is pinned.
What the dandiset holds. 137 silicon-probe sessions (ALM, thalamus, SNr) with trial tables, spike times, mean waveforms and unit positions, and 36 whole-cell recordings of ALM neurons during thalamus, M1 or contralateral ALM photoinhibition, from 31 mice. Behaviour-only sessions, the muscimol experiments, the weak photoinhibition of Fig. 5, non-behaving and M1 whole-cell recordings and the anatomy are not in it.
Questions asked and answered (questions/000009.yaml; the answers of 2026-10-20 are written
into the claims file, and this notebook follows them):
- Unit classes. P1 (width computed on the deposited waveforms) is the Methods' rule; P2 (the
files'
cell_typelabel) stays a reading, as the paper does not say how the label was made. For the thalamic claims of Fig. 2 and 6, all units regardless of width is a further reading (P3, convention C), added here to fig2f-vmval-selectivity, fig6c-vmval-reduced, fig6b, fig6c-unmodulated, fig6d (its reading u2), vm1-photoinhibition-no-effect, fig6e and selectivity-time-course. - The PV-Cre x Ai32 ALM sessions with thalamic photostimulation are not part of Fig. 3, which uses VGAT-ChR2-EYFP mice at 10 mW; they stay a variant.
- ALM photoinhibition is the 1.5 mW condition; trials at 0.75 or 0.79 mW are outside it, and all powers pooled is a variant.
- The whole-cell photostimulus onset has two readings, E (1.3 s after
pole_in_time) and M (0.1 s afterpole_out_time); placing it from the Vm change is not a reading. The answer assumed the files hold no laser time; they do (an OptogeneticSeries at 20 kHz, found after the first verification), so the onset of that trace is shown as a third reading, L, and a new question asks whether it should be used (question 7 below). The outcomes of the four whole-cell claims are the same under M and under L. - The paper does not say which deposited condition is vM1; ALM is compared at 1.5 mW, matched power is a variant. The claim stays blocked on the site.
- Selectivity time course: correct control trials (t1) and all correct trials (t2) are both readings.
- Asked after the first verification, not yet answered: should the whole-cell photostimulus onset be taken from the deposited laser trace (reading L)? Under L the onset latency of wc-thalamus-hyperpolarization can be measured; no outcome depends on the answer.
Changes after the first verification: within a trial the extracellular files store spikes only to about 4.8 s after pole onset, so a window that ends later is now counted as missing for that trial (it had been counted with the spikes it held); SNr reading P2 is now the 'Narrow width spiking' label (146 units), as convention C says; the whole-cell laser trace is read; and checks were added for the onset rule, the VGAT delay lengths and the spike-threshold kink.
Changes after the second verification: no outcome changed in that round. The verifier found that nine claims were given partially reproduced under criteria with no partial clause; a section before the Summary counts the readings that meet each whole criterion (none does), and a change to not reproduced was proposed for each. Some sentences were corrected.
Changes after the third verification: the verifier agreed with all nine proposed changes, so alm-unit-classes, fig2e-alm-selectivity, thalamus-unit-classes, fig2f-vmval-selectivity, fig3f-thalamus-silenced, wc-thalamus-hyperpolarization, wc-m1-small-effect, fig6d-localized and snr-photoinhibition are now not reproduced.
| Claim | Tested here |
|---|---|
| task-performance | tested |
| fig1c-alm-photoinhibition-bias | tested (recording sessions) |
| fig1d-thalamus-photoinhibition-bias | blocked: data missing |
| gad2-thalamus-reduction | blocked: metadata insufficient |
| vgat-thalamus-reduction | tested |
| muscimol-vmval-bias | blocked: data missing |
| alm-unit-classes | tested |
| fig2e-alm-selectivity | tested |
| fig2e-alm-contra-ipsi-balance | tested |
| thalamus-unit-classes | tested |
| fig2f-vmval-selectivity | tested |
| fig3-alm-collapse | tested |
| fig3d-all-layers | tested; blocked by its criterion (methods underspecified) |
| fig3b-selectivity-types-equally-inhibited | tested; blocked by its criterion (methods underspecified) |
| fig3f-thalamus-silenced | tested |
| fig3-onset-latencies | tested |
| wc-near-threshold | tested |
| wc-thalamus-hyperpolarization | tested (onset readings E, M and L; the onset latency shown under L, not judged) |
| wc-loss-of-excitation | blocked: data missing |
| wc-m1-small-effect | tested (onset readings E, M and L) |
| wc-contra-alm-small-effect | tested (onset readings E, M and L) |
| thalamus-stronger-than-cortex | tested (onset readings E, M and L) |
| indirect-pathway-too-slow | blocked: data missing |
| conduction-delay | blocked: data missing |
| latency-model-t3 | blocked: methods underspecified |
| fig5-weak-photoinhibition-rates | blocked: data missing |
| fig5c-selectivity-reduced | blocked: data missing |
| network-models | blocked: methods underspecified |
| fig6c-vmval-reduced | tested |
| fig6b-selectivity-types-equally-inhibited | tested; blocked by its criterion (methods underspecified) |
| fig6c-unmodulated-less-selective | tested |
| fig6d-localized | tested |
| vm1-photoinhibition-no-effect | run; blocked: metadata insufficient (site not named) |
| fig6e-onset-latencies | tested |
| snr-photoinhibition | tested |
| snr-unit-properties | run in part; blocked: data missing (the selectivity part is tested) |
| anatomy-reciprocal-thalamus | blocked: data missing |
| selectivity-time-course | tested |
| alm-gabaergic-no-thalamic-projection | blocked: data missing |
# LOAD-CELL
import time
import warnings
from collections import Counter
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
import loop
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 300)
warnings.filterwarnings("ignore", category=RuntimeWarning)
started = time.time()
assets = loop.list_assets()
sessions = loop.load_all_ecephys(assets)
cells = loop.load_all_wholecell(assets)
units = loop.unit_table(sessions)
units["vmval_d"] = loop.vmval_distance(units)
print(f"dandiset {loop.DANDISET_ID} version {loop.DANDISET_VERSION}: {len(assets)} assets; loaded "
f"{len(sessions)} extracellular sessions ({len(units)} units) and {len(cells)} whole-cell recordings "
f"in {time.time() - started:.0f} s")
dandiset 000009 version 0.220126.1903: 173 assets; loaded 137 extracellular sessions (1936 units) and 36 whole-cell recordings in 404 s
Data check¶
Each check of the metadata against the values is stated under its output.
info = loop.version_info()
meta = info["metadata"]
summ = meta["assetsSummary"]
print("Version", info["version"], "status", info["status"], "created", info["created"][:10])
print("Archive summary: subjects", summ["numberOfSubjects"], "| files", summ["numberOfFiles"],
"| variables", summ.get("variableMeasured"))
print("Related resources:", [(r.get("identifier"), r.get("relation")) for r in meta.get("relatedResource", [])])
kind = np.where(assets.path.str.contains("icephys"), "whole-cell", "extracellular")
print("\nFiles by kind:", Counter(kind))
print("Distinct subject folders:", assets.path.str.split("/").str[0].nunique(),
"| extracellular:", assets.path[kind == "extracellular"].str.split("/").str[0].nunique(),
"| whole-cell:", assets.path[kind == "whole-cell"].str.split("/").str[0].nunique())
print("Size: extracellular", round(assets["size"][kind == "extracellular"].sum() / 1e9, 2), "GB; whole-cell",
round(assets["size"][kind == "whole-cell"].sum() / 1e9, 2), "GB")
Version 0.220126.1903 status Valid created 2022-01-26
Archive summary: subjects 31 | files 173 | variables ['Units', 'ElectrodeGroup', 'ProcessingModule', 'BehavioralTimeSeries', 'CurrentClampStimulusSeries', 'OptogeneticSeries']
Related resources: [('doi:10.1038/nature22324', 'dcite:IsDescribedBy')]
Files by kind: Counter({np.str_('extracellular'): 137, np.str_('whole-cell'): 36})
Distinct subject folders: 31 | extracellular: 24 | whole-cell: 7
Size: extracellular 0.21 GB; whole-cell 12.71 GB
sess = pd.DataFrame([{**{k: s["meta"][k] for k in ("subject", "genotype", "region", "ogen_site")},
"units": len(s["units"]), "task_trials": len(s["trials"]), "rows": s["meta"]["n_rows"]}
for s in sessions])
sess["ogen_site"] = sess.ogen_site.fillna("none")
print("Extracellular sessions by genotype, recorded region and photostimulus site:")
print(sess.groupby(["genotype", "region", "ogen_site"]).agg(sessions=("subject", "size"), mice=("subject", "nunique"),
units=("units", "sum"), task_trials=("task_trials", "sum")).to_string())
print("\nBy region (paper, Methods: ALM 1214 units in 57 sessions, 10 mice; thalamus 790 units in 73 sessions, 11 mice;"
" SNr 227 units in 23 sessions):")
print(sess.groupby("region").agg(sessions=("subject", "size"), mice=("subject", "nunique"), units=("units", "sum")).to_string())
lab = Counter()
for s in sessions:
m = s["meta"]
for k, v in s["trials"].stim.value_counts().items():
lab[(m["genotype"], m["region"], m["ogen_site"] or "none", k)] += v
print("\nTask trials by photostimulus label (<site>:<period>:<power mW>):")
print(pd.Series(lab).rename_axis(["genotype", "region", "site", "label"]).to_frame("trials").to_string())
wc = pd.DataFrame([{"subject": c["meta"]["subject"], "genotype": c["meta"]["genotype"], "cell": c["meta"]["cell"],
"description": c["meta"]["description"], "site": c["meta"]["ogen_site"], "trials": len(c["trials"]),
"pi_trials": int(c["trials"].pi.sum())} for c in cells])
print("\nWhole-cell recordings:")
print(wc.groupby(["description", "genotype", "site"]).agg(cells=("cell", "size"), mice=("subject", "nunique"),
trials=("trials", "sum"), pi_trials=("pi_trials", "sum")).to_string())
Extracellular sessions by genotype, recorded region and photostimulus site:
sessions mice units task_trials
genotype region ogen_site
Ai32 x PV-Cre ALM ALM 8 3 191 1716
Thalamus 21 4 434 4675
SNr ALM 18 3 181 5080
Thalamus ALM 40 7 451 11141
Ai35D x GAD2_Cre Thalamus none 9 2 84 2993
C57BL6 Thalamus none 12 2 104 2192
VGAT-ChR2-EYFP ALM Thalamus 18 3 341 4112
Thalamus Thalamus 11 2 150 2361
By region (paper, Methods: ALM 1214 units in 57 sessions, 10 mice; thalamus 790 units in 73 sessions, 11 mice; SNr 227 units in 23 sessions):
sessions mice units
region
ALM 47 10 966
SNr 18 3 181
Thalamus 72 13 789
Task trials by photostimulus label (<site>:<period>:<power mW>):
trials
genotype region site label
Ai35D x GAD2_Cre Thalamus none none 2993
Ai32 x PV-Cre Thalamus ALM none 8708
ALM:delay:1.5 2045
ALM:delay:0.79 261
C57BL6 Thalamus none none 2192
Ai32 x PV-Cre SNr ALM none 3942
ALM:delay:1.5 1137
ALM:sample:1.5 1
ALM ALM none 1336
ALM:delay:1.5 188
ALM:delay:0.75 192
Thalamus none 3649
Thalamus:delay:10 531
Thalamus:delay:22.5 380
Thalamus ALM site0.5:delay:0.79 127
ALM Thalamus Thalamus:delay:45 115
VGAT-ChR2-EYFP Thalamus Thalamus none 1841
Thalamus:delay:2.23 465
Thalamus:sample:2.23 55
ALM Thalamus none 3221
Thalamus:delay:10 891
Whole-cell recordings:
cells mice trials pi_trials
description genotype site
ALM whole cell recording during M1 photoinhibition Ai32 x PV-Cre left-M1 11 4 913 387
ALM whole cell recording during contra ALM photoinhibition Ai32 x PV-Cre right-ALM 9 3 618 271
ALM whole cell recording during thalamus photoinhibition VGAT-ChR2-EYFP left-VM 16 3 1122 395
The dandiset holds 137 extracellular sessions and 36 whole-cell recordings from 31 mice, as the archive says. ALM: 47 sessions from 10 mice with 966 units, against 57 sessions and 1214 units in the paper (10 mice). Thalamus: 72 sessions with 789 units, against 73 and 790. SNr: 18 sessions with 181 units, against 23 sessions and 227 units, of which the paper keeps 181 as putative GABAergic. The whole-cell recordings are 16 ALM cells during thalamus photoinhibition (VGAT-ChR2-EYFP), 11 during M1 and 9 during contralateral ALM photoinhibition (PV-Cre x Ai32), the n of Fig. 3g and 4. Not in the dandiset: behaviour-only sessions, the muscimol experiments, the weak (0.5 mW) thalamus photoinhibition of Fig. 5, non-behaving whole-cell recordings (Extended Data Fig. 5c-h, 6c), M1 whole-cell recordings, and the anatomy.
What the files hold that the paper does not describe, or describes differently:
- The 9 thalamic sessions of two
Ai35D x GAD2_Cremice carry no photostimulus in any trial (every period 'N/A', power 0), yet their 84 units equal the 26 + 41 + 17 Gad2-IRES-Cre optrode units of Extended Data Fig. 2c. The paper's Gad2 mice had ChR2 injected in TRN; Ai35D is an Arch line. 12 thalamic sessions of twoC57BL6mice (104 units) have no photostimulus either. - 21 ALM sessions of four
Ai32 x PV-Cremice have photostimulation of the thalamus at 10, 22.5 and 45 mW. The paper describes ALM recordings during thalamus photoinhibition in VGAT-ChR2-EYFP mice only (Fig. 3); the 18 VGAT ALM sessions here use 10 mW. - The VGAT thalamic sessions record 2.23 mW where the paper gives 10 mW (Extended Data Fig. 2d), and one of them was photostimulated in the sample epoch.
- In thalamic sessions of mouse anm00264943, ALM photoinhibition at 0.79 and 1.5 mW is interleaved with photostimulation at galvo position (0.5, 0.5) mm, which is not named. The paper interleaves ALM and vM1 photoinhibition in 3 mice (Extended Data Fig. 8f, g).
- Trials labelled 'delay' with power 0 (a few per session) are kept apart and used in no analysis.
# Recordings deposited twice: compare trial start times and unit spike counts between every pair.
sig = []
for i, s in enumerate(sessions):
t = s["trials"]
sig.append((i, s["meta"]["subject"], tuple(np.round(t.start_time.values[:20], 3)),
set(zip(s["units"].n_spikes, np.round(s["units"].first_spike, 4)))))
pairs = []
for a in range(len(sig)):
for b in range(a + 1, len(sig)):
same_trials = sig[a][2] == sig[b][2]
shared_units = len(sig[a][3] & sig[b][3])
if same_trials or shared_units:
pairs.append((sessions[a]["meta"]["path"], sessions[b]["meta"]["path"], same_trials, shared_units))
print("Extracellular session pairs sharing trial start times or units (spike count and first spike):", pairs if pairs else "none")
wsig = [(c["meta"]["path"], c["meta"]["n_samples"], c["meta"]["n_spikes"], round(c["meta"]["vm_range"][0], 3)) for c in cells]
dup = [(a[0], b[0]) for i, a in enumerate(wsig) for b in wsig[i + 1:] if a[1:] == b[1:]]
print("Whole-cell pairs with equal length, spike count and minimum Vm:", dup if dup else "none")
starts = pd.DataFrame([(c["meta"]["subject"], c["meta"]["cell"], c["trials"].start_time.min(), c["trials"].stop_time.max())
for c in cells], columns=["subject", "cell", "first", "last"])
print("Whole-cell cells per mouse:", starts.groupby("subject").cell.apply(list).to_dict())
Extracellular session pairs sharing trial start times or units (spike count and first spike): none
Whole-cell pairs with equal length, spike count and minimum Vm: none
Whole-cell cells per mouse: {'anm300476': ['cell_20_1', 'cell_21_1', 'cell_22_1', 'cell_23_1', 'cell_25_1', 'cell_29_1'], 'anm322808': ['cell_55_1', 'cell_56_1', 'cell_57_1', 'cell_58_1', 'cell_59_1', 'cell_60_1'], 'anm323951': ['cell_44_1', 'cell_45_1', 'cell_46_1', 'cell_45_2', 'cell_46_2'], 'anm323952': ['cell_40_2', 'cell_39_1', 'cell_40_1'], 'anm323953': ['cell_30_2', 'cell_30_1', 'cell_31_1', 'cell_32_1', 'cell_34_1', 'cell_35_1'], 'anm324791': ['cell_47_1', 'cell_49_1', 'cell_50_1', 'cell_51_1', 'cell_52_1', 'cell_53_1'], 'anm324792': ['cell_64_1', 'cell_65_1', 'cell_66_1', 'cell_73_2']}
No session or whole-cell recording is deposited twice: no two sessions share trial start times or any unit. Each whole-cell file holds one cell; cells of one mouse are counted as separate cells, as in the paper. A unit is never treated as the same across sessions here.
print("Mean waveform length (samples) and sampling rate by region:")
print(units.groupby("region").agg(wf_len=("wf_len", "unique"), rate=("sampling_rate", "unique")).to_string())
print("\nTrough-to-peak width (ms) of the deposited mean waveform, by region and the file's cell_type label:")
print(units.groupby(["region", "cell_type"]).width_ms.describe()[["count", "min", "25%", "50%", "75%", "max"]].round(2).to_string())
print("\nFraction of units whose waveform peak (after the trough) is its last sample, by region and label:")
print(units.groupby(["region", "cell_type"]).peak_last.mean().round(3).to_string())
print("\nUnit coordinates (mm) by region: unit_x (ML), unit_y (DV), unit_z (AP)")
print(units.groupby("region")[["ml", "dv", "ap"]].agg(["min", "max"]).round(2).to_string())
print("\nSNr unit AP (mm) by mouse:", units[units.region == "SNr"].groupby("subject").ap.agg(["min", "max"]).round(2).to_dict("index"))
eg = Counter((s["meta"]["region"], s["meta"]["eg_location"].get("coordinate_ap"), s["meta"]["eg_location"].get("coordinate_ml"),
s["meta"]["eg_location"].get("coordinate_dv")) for s in sessions)
print("\nElectrode group locations (region, AP, ML, DV):", dict(eg))
og = Counter((k, v.get("coordinate_ap"), v.get("coordinate_ml"), v.get("coordinate_dv")) for s in sessions for k, v in s["meta"]["ogen"].items())
print("Photostimulus sites (name, AP, ML, DV):", dict(og))
print("Units with unsorted spike times:", sum(s["meta"]["unsorted_units"] for s in sessions))
print("\nFraction of its session's task trials a unit spans (first to last trial holding its spikes), by region:")
print(units.groupby("region").trial_coverage.describe()[["min", "25%", "50%", "75%", "max"]].round(3).to_string())
print("Units spanning less than 90% of their session's trials:", int((units.trial_coverage < 0.9).sum()), "of", len(units))
mid = np.array([np.nansum(s["counts"]["mid"]) for s in sessions]) / 2.5
late = np.array([np.nansum(s["counts"]["late"]) for s in sessions]) / 2.5
print("Spikes per s summed over units and trials, 2.0 to 4.5 s after pole onset:", int(mid.sum()), "| 5.5 to 8.0 s:", int(late.sum()),
"| sessions with any spike 5.5 to 8.0 s:", int((late > 0).sum()), "of", len(sessions))
Mean waveform length (samples) and sampling rate by region:
wf_len rate
region
ALM [29, 38, 37] [25000]
SNr [29] [25000]
Thalamus [29, 37] [25000]
Trough-to-peak width (ms) of the deposited mean waveform, by region and the file's cell_type label:
count min 25% 50% 75% max
region cell_type
ALM Cannot be determined 35.0 0.32 0.40 0.44 0.48 0.56
Narrow width spiking 121.0 0.12 0.24 0.28 0.28 0.32
Wide width spiking 810.0 0.44 0.60 0.68 0.72 0.72
SNr Cannot be determined 22.0 0.32 0.32 0.32 0.35 0.36
Narrow width spiking 146.0 0.08 0.16 0.20 0.24 0.28
Wide width spiking 13.0 0.44 0.48 0.48 0.48 0.48
Thalamus Cannot be determined 62.0 0.28 0.32 0.32 0.40 0.44
Narrow width spiking 45.0 0.08 0.16 0.24 0.24 0.32
Wide width spiking 682.0 0.36 0.44 0.44 0.48 0.68
Fraction of units whose waveform peak (after the trough) is its last sample, by region and label:
region cell_type
ALM Cannot be determined 0.000
Narrow width spiking 0.000
Wide width spiking 0.777
SNr Cannot be determined 0.000
Narrow width spiking 0.000
Wide width spiking 0.000
Thalamus Cannot be determined 0.000
Narrow width spiking 0.000
Wide width spiking 0.085
Unit coordinates (mm) by region: unit_x (ML), unit_y (DV), unit_z (AP)
ml dv ap
min max min max min max
region
ALM 0.90 1.90 -1.21 -0.07 2.5 3.00
SNr 0.52 1.38 -4.84 -3.28 -4.2 -1.56
Thalamus 0.35 2.06 -4.79 -2.71 -2.1 -1.23
SNr unit AP (mm) by mouse: {'anm00261334': {'min': -4.02, 'max': -3.86}, 'anm00261335': {'min': -4.2, 'max': -3.6}, 'anm00261336': {'min': -1.56, 'max': -1.56}}
Electrode group locations (region, AP, ML, DV): {('Thalamus', '2.50', '-1.50', '0.00'): 72, ('SNr', '2.50', '-1.50', '0.00'): 18, ('ALM', '2.50', '-1.50', '0.00'): 47}
Photostimulus sites (name, AP, ML, DV): {('left-ALM', '2.50', '-1.50', '0.00'): 66, ('left-Thalamus', '-1.50', '-0.90', '-4.10'): 50}
Units with unsorted spike times: 0
Fraction of its session's task trials a unit spans (first to last trial holding its spikes), by region:
min 25% 50% 75% max
region
ALM 0.119 0.474 0.698 0.924 1.0
SNr 0.176 0.641 0.888 0.992 1.0
Thalamus 0.000 0.477 0.715 0.926 1.0
Units spanning less than 90% of their session's trials: 1358 of 1936
Spikes per s summed over units and trials, 2.0 to 4.5 s after pole onset: 4094260 | 5.5 to 8.0 s: 57086 | sessions with any spike 5.5 to 8.0 s: 75 of 137
The deposited mean waveforms are short: 29 samples at 25 kHz (1.16 ms) in thalamus and SNr, and
the trough sits at sample 15 to 19, so a trough-to-peak interval longer than 0.36 to 0.52 ms cannot
be measured in many units. The peak after the trough is the last sample in 78% of ALM units
labelled 'Wide width spiking'. Widths computed from these waveforms are therefore lower bounds for
wide units, and the classes the Methods define by width (> 0.6 ms in ALM, > 0.5 ms in thalamus)
cannot be recovered from them. The files' own cell_type labels ('Wide width spiking', 'Narrow
width spiking', 'Cannot be determined') separate the units cleanly by computed width (narrow up
to 0.32 ms in ALM, wide from 0.44 ms) and are shown below as the second reading (P2) of every unit
class; reading P1 applies the Methods' thresholds to the computed widths.
Coordinates: unit_x is medio-lateral (positive), unit_y dorso-ventral (negative, mm), unit_z
antero-posterior; thalamic units lie between AP -1.2 and -2.1 mm, inside the "bregma -1 to -2 mm"
of the Methods, and ALM units at AP 2.5 to 3.0. Two inconsistencies: every electrode group, also in
thalamus and SNr, carries the ALM coordinates (AP 2.5, ML -1.5, DV 0); and the SNr units of mouse
anm00261336 are at AP -1.56 mm, where SNr is not (the other two SNr mice are at AP -3.6 to -4.2).
Photostimulus sites give ML negative for the left hemisphere, units give it positive. The thalamic
fibre site is the same nominal coordinate (AP -1.5, ML 0.9, DV -4.1) in all 29 sessions.
Spikes are stored only in part of each trial and for part of each session. Within a trial, spikes run from the trial start (0.57 s before pole onset) to about 4.8 s after pole onset: the summed rate 5.5 to 8 s after pole onset is 1.4% of that 2 to 4.5 s after it, although trials last about 9 s. The end is fixed to pole onset, not to the go cue (next table), so in trials with a long delay the response epoch, and in a few the delay epoch, runs past the stored spikes. Each trial's stored span is taken to end at the last spike of any unit of the session in it, and a window that ends later is counted as missing for that trial. Across a session, most units hold spikes over only some of its trials (median 70%), and the files give no observation interval per unit. A unit's trials are therefore taken to run from the trial holding its first spike to the trial holding its last; trials outside count as missing, not as zero spikes. This is a standard choice recorded as a deviation.
tr = pd.concat([s["trials"].assign(subject=s["meta"]["subject"], region=s["meta"]["region"]) for s in sessions], ignore_index=True)
rows_all = Counter()
for s in sessions:
rows_all.update(s["meta"]["outcomes_all"])
print("Extracellular trial rows by outcome:", dict(rows_all), "| rows without pole times (dropped):",
sum(s["meta"]["n_rows"] for s in sessions) - len(tr))
print("Task trials by type and outcome:")
print(pd.crosstab(tr["type"], tr.response).to_string())
print("\npole_out - pole_in (s), quantiles 1/50/99%:", np.round(np.nanpercentile(tr.pole_out_time - tr.pole_in_time, [1, 50, 99]), 3))
print("cue - pole_in (s), quantiles 1/50/99%:", np.round(np.nanpercentile(tr.cue - tr.pole_in_time, [1, 50, 99]), 3))
print("Delay (cue - pole_in - 1.3 s) within 0.05 s of 1.3 s:", round(float(np.mean(np.abs(tr.delay_s - 1.3) < 0.05)), 3),
"| of 1.2 s:", round(float(np.mean(np.abs(tr.delay_s - 1.2) < 0.05)), 3))
print("Trial duration (stop - start, s), quantiles 1/50/99%:", np.round(np.percentile(tr.stop_time - tr.start_time, [1, 50, 99]), 2))
print("Go cue duration (s):", np.round(np.nanpercentile(tr.cue_end_time - tr.cue_start_time, [1, 50, 99]), 3))
tr["stored_from_cue"] = tr.stored_end - tr.cue
tr["delay_bin"] = pd.cut(tr.delay_s, [0, 1.35, 2.0, 3.0, 10.0])
print("\nEnd of the stored spikes in each trial (last spike of any unit of the session), by delay length (s):")
print(tr.groupby("delay_bin", observed=True).agg(trials=("stored_end", "size"),
after_pole_q10=("stored_from_pole", lambda x: x.quantile(0.1)),
after_pole_median=("stored_from_pole", "median"),
after_pole_q90=("stored_from_pole", lambda x: x.quantile(0.9)),
after_cue_median=("stored_from_cue", "median")).round(2).to_string())
lg = tr[tr.delay_s > 2.0]
print("Trials with delay > 2 s:", len(lg), "| histogram of the stored end after the go cue (s), 0.5 s bins:",
pd.cut(lg.stored_from_cue, np.arange(-3.5, 4.01, 0.5)).value_counts().sort_index().to_dict())
for nm, end in (("delay epoch", tr.cue), ("R1", tr.cue + loop.RESP_S["R1"]), ("R2", tr.cue + loop.RESP_S["R2"]),
("20 to 120 ms window", tr.on + loop.PW[1])):
cut = ~(end <= tr.stored_end)
print(f"Task trials whose {nm} ends after the stored spikes (counted as missing):", int(cut.sum()), f"({100 * cut.mean():.1f}%)",
"| of trials with a 1.3 s delay:", int(cut[np.abs(tr.delay_s - 1.3) < 0.05].sum()))
kp = tr.response.isin(loop.KEEP)
print("Correct and incorrect trials whose R1 ends after the stored spikes, by region:",
{g: f"{int((~(d.cue + loop.RESP_S['R1'] <= d.stored_end)).sum())} of {len(d)}" for g, d in tr[kp].groupby("region")},
"| by region, trials with a delay above 1.35 s:", {g: int((d.delay_s > 1.35).sum()) for g, d in tr[kp].groupby("region")})
wtr = pd.concat([c["trials"].assign(cell=c["meta"]["cell"]) for c in cells], ignore_index=True)
print("\nWhole-cell trials by type and outcome:")
print(pd.crosstab(wtr["type"], wtr.response).to_string())
print("Whole-cell pole_out - pole_in (s):", np.round(np.nanpercentile(wtr.pole_out_time - wtr.pole_in_time, [1, 50, 99]), 3),
"| cue - pole_in:", np.round(np.nanpercentile(wtr.cue - wtr.pole_in_time, [1, 50, 99]), 3))
bad = wtr[~wtr.order_ok]
print("Whole-cell rows whose pole onset is outside [start_time, stop_time]:", len(bad), "in cells", dict(Counter(bad.cell)))
print(bad[["cell", "id", "start_time", "stop_time", "pole_in_time"]].head(3).to_string(index=False))
print("Whole-cell photostimulus columns: periods", set(wtr.photo_stim_period), "powers", set(map(str, wtr.photo_stim_power)),
"| stim_present fraction", round(float(wtr.pi.mean()), 3))
Extracellular trial rows by outcome: {'correct': 19315, 'early lick': 6457, 'incorrect': 8246, 'N/A': 5375, 'no response': 246} | rows without pole times (dropped): 5369
Task trials by type and outcome:
response N/A correct early lick incorrect no response
type
lick left 0 9684 3311 4337 120
lick right 0 9631 3146 3909 126
non-performing 6 0 0 0 0
pole_out - pole_in (s), quantiles 1/50/99%: [1.3 1.3 3.03]
cue - pole_in (s), quantiles 1/50/99%: [2.6 2.6 5.69]
Delay (cue - pole_in - 1.3 s) within 0.05 s of 1.3 s: 0.814 | of 1.2 s: 0.0
Trial duration (stop - start, s), quantiles 1/50/99%: [ 7.82 8.98 13.98]
Go cue duration (s): [0.1 0.1 0.1]
End of the stored spikes in each trial (last spike of any unit of the session), by delay length (s):
trials after_pole_q10 after_pole_median after_pole_q90 after_cue_median
delay_bin
(0.0, 1.35] 27914 4.73 4.84 8.35 2.24
(1.35, 2.0] 2314 4.72 4.83 8.05 1.98
(2.0, 3.0] 3007 4.73 4.83 9.07 1.16
(3.0, 10.0] 1005 4.72 4.81 4.91 -0.20
Trials with delay > 2 s: 4036 | histogram of the stored end after the go cue (s), 0.5 s bins: {Interval(-3.5, -3.0, closed='right'): 12, Interval(-3.0, -2.5, closed='right'): 17, Interval(-2.5, -2.0, closed='right'): 39, Interval(-2.0, -1.5, closed='right'): 68, Interval(-1.5, -1.0, closed='right'): 84, Interval(-1.0, -0.5, closed='right'): 132, Interval(-0.5, 0.0, closed='right'): 213, Interval(0.0, 0.5, closed='right'): 315, Interval(0.5, 1.0, closed='right'): 788, Interval(1.0, 1.5, closed='right'): 1653, Interval(1.5, 2.0, closed='right'): 71, Interval(2.0, 2.5, closed='right'): 0, Interval(2.5, 3.0, closed='right'): 0, Interval(3.0, 3.5, closed='right'): 0, Interval(3.5, 4.0, closed='right'): 0}
Task trials whose delay epoch ends after the stored spikes (counted as missing): 1142 (3.3%) | of trials with a 1.3 s delay: 406
Task trials whose R1 ends after the stored spikes (counted as missing): 3439 (10.0%) | of trials with a 1.3 s delay: 421
Task trials whose R2 ends after the stored spikes (counted as missing): 3980 (11.6%) | of trials with a 1.3 s delay: 432
Task trials whose 20 to 120 ms window ends after the stored spikes (counted as missing): 526 (1.5%) | of trials with a 1.3 s delay: 404
Correct and incorrect trials whose R1 ends after the stored spikes, by region:
{'ALM': '221 of 8991', 'SNr': '19 of 3573', 'Thalamus': '173 of 14997'} | by region, trials with a delay above 1.35 s: {'ALM': 0, 'SNr': 0, 'Thalamus': 0}
Whole-cell trials by type and outcome:
response correct early lick incorrect no response
type
lick left 977 0 188 0
lick right 847 0 344 0
non-performing 0 256 0 41
Whole-cell pole_out - pole_in (s): [1.4 1.401 2.301] | cue - pole_in: [2.6 2.601 3.973]
Whole-cell rows whose pole onset is outside [start_time, stop_time]: 85 in cells {'cell_21_1': 85}
cell id start_time stop_time pole_in_time
cell_21_1 94 774.0480 781.9250 12.0164
cell_21_1 264 11.4447 21.8488 22.4208
cell_21_1 265 21.8488 30.0682 30.6402
Whole-cell photostimulus columns: periods {''} powers {''} | stim_present fraction 0.397
Trial types are 'lick left', 'lick right' and 'non-performing'; contra trials are taken as 'lick
right' (left hemisphere, Fig. 1a). 5375 extracellular rows have no outcome ('N/A', type
'non-performing'); 5369 of them have no pole times and are dropped, and the other 6 enter no
analysis. In the extracellular files the delay (go cue minus 1.3 s after pole onset) is 1.3 s in
81% of trials and never 1.2 s; the rest are longer (up to 4.4 s at the 99th percentile).
pole_out_time is 1.3 s after pole_in_time. Trials last about 9 s. In the whole-cell files pole_out_time is 1.4 s after
pole_in_time and the go cue 2.6 s after it. One whole-cell trial table is not in time order and
has a row whose start time (774 s) does not contain its pole onset (12 s). The whole-cell trial
tables leave every photostimulus column empty; stim_present marks 30 to 50% of trials, the
"25-50%" of the Methods for Fig. 3g and 4, and is read as the photostimulus flag (its effect is
shown below).
The end of the stored spikes is fixed to pole onset: its median is 4.81 to 4.84 s after pole onset whatever the delay, so it falls 2.2 s after the go cue in trials with a 1.3 s delay and before the go cue in trials with a delay above 3 s (table). Before this correction, such windows were counted with the spikes they held, which biased their counts low. Every correct or incorrect trial has a 1.3 s delay; the longer delays occur only in lick-early and no-response trials, which no analysis uses. The correction therefore touches only the kept trials whose stored spikes end early: 221 of 8991 in ALM, 173 of 14997 in thalamus and 19 of 3573 in SNr for R1.
# Clock check, extracellular: the photostimulus effect must start at the onset the trial table gives
# (1.3 s after pole onset), and trial types must be decodable with true but not shifted labels.
t_ms = loop.psth_time_ms()
checks = [("VGAT thalamus, thalamus photostim.", (units.genotype == "VGAT-ChR2-EYFP") & (units.region == "Thalamus"), ["Thalamus:delay:2.23"]),
("VGAT ALM, thalamus photostim.", (units.genotype == "VGAT-ChR2-EYFP") & (units.region == "ALM"), ["Thalamus:delay:10"]),
("PV ALM, thalamus photostim. 10 mW", (units.genotype == "Ai32 x PV-Cre") & (units.region == "ALM") & (units.ogen_site == "Thalamus"), ["Thalamus:delay:10"]),
("PV thalamus, ALM photoinh. 1.5 mW", (units.genotype == "Ai32 x PV-Cre") & (units.region == "Thalamus"), ["ALM:delay:1.5"]),
("PV SNr, ALM photoinh. 1.5 mW", units.region == "SNr", ["ALM:delay:1.5"]),
("PV ALM, ALM photoinh. 1.5 mW", (units.genotype == "Ai32 x PV-Cre") & (units.region == "ALM") & (units.ogen_site == "ALM"), ["ALM:delay:1.5"])]
rows = []
for name, sel, st in checks:
c, p = loop.population_psth(sessions, units[sel], st)
mc, mp = np.nanmean(c, 0), np.nanmean(p, 0)
row = {"group": name, "units": int(sel.sum())}
for lo in (-50, -20, -10, 0, 5, 10, 15, 20, 30, 50, 100):
k = (t_ms >= lo) & (t_ms < lo + 5 if lo >= 0 else t_ms < lo + 10 if lo == -20 or lo == -10 else t_ms < lo + 30)
row[f"{lo}"] = round(mp[k].mean() / mc[k].mean(), 2)
rows.append(row)
print("Population rate under photostimulus / control, in windows starting at the given ms after the onset"
" (5 ms windows from 0 on; -50 is -50 to -20, -20 and -10 are 10 ms):")
print(pd.DataFrame(rows).to_string(index=False))
dec = []
for i, s in enumerate(sessions):
t = s["trials"]
m = (t.response == "correct").values & (t.stim == "none").values
y = t.contra.values[m]
y_shift = np.roll(t.contra.values, 1)[m]
c = s["counts"]["delay"][:, m]
true = np.mean([loop.ttest(u[y], u[~y], True) < 0.05 for u in c])
shift = np.mean([loop.ttest(u[y_shift], u[~y_shift], True) < 0.05 for u in c])
dec.append((s["meta"]["region"], true, shift))
dec = pd.DataFrame(dec, columns=["region", "true", "shifted"])
dec["no_photostim"] = [s["meta"]["ogen_site"] is None for s in sessions]
print("\nFraction of units with delay-epoch contra/ipsi difference (t-test p < 0.05, correct control trials), per session:")
print(dec.groupby("region").agg(sessions=("true", "size"), true_median=("true", "median"), shifted_median=("shifted", "median"),
true_gt_shifted=("true", lambda x: int((x > dec.loc[x.index, "shifted"]).sum()))).round(3).to_string())
d0 = dec[dec.no_photostim]
print("Sessions without photostimulus (Gad2, C57BL6):", len(d0), "| true > shifted in", int((d0.true > d0.shifted).sum()),
"| pooled fraction true", round(d0.true.mean(), 3), "shifted", round(d0.shifted.mean(), 3))
Population rate under photostimulus / control, in windows starting at the given ms after the onset (5 ms windows from 0 on; -50 is -50 to -20, -20 and -10 are 10 ms):
group units -50 -20 -10 0 5 10 15 20 30 50 100
VGAT thalamus, thalamus photostim. 150 1.02 0.98 1.02 0.68 0.33 0.24 0.23 0.19 0.21 0.18 0.20
VGAT ALM, thalamus photostim. 341 0.99 0.90 1.00 0.93 0.86 0.38 0.15 0.08 0.03 0.01 0.00
PV ALM, thalamus photostim. 10 mW 434 1.02 0.86 0.95 1.22 0.77 0.82 0.81 0.65 0.17 0.15 0.27
PV thalamus, ALM photoinh. 1.5 mW 451 0.98 1.03 0.93 0.95 1.00 1.00 0.92 0.68 0.54 0.47 0.61
PV SNr, ALM photoinh. 1.5 mW 181 1.03 1.01 1.03 1.10 1.03 0.95 0.97 0.97 0.96 0.98 0.93
PV ALM, ALM photoinh. 1.5 mW 191 1.09 0.90 0.88 1.28 1.24 1.54 1.76 1.11 0.78 0.21 0.15
Fraction of units with delay-epoch contra/ipsi difference (t-test p < 0.05, correct control trials), per session:
sessions true_median shifted_median true_gt_shifted
region
ALM 47 0.361 0.100 44
SNr 18 0.392 0.174 14
Thalamus 72 0.231 0.080 53
Sessions without photostimulus (Gad2, C57BL6): 21 | true > shifted in 16 | pooled fraction true 0.242 shifted 0.081
In the extracellular files the photostimulus effect starts where the trial table puts the onset, 1.3 s after pole onset: VGAT thalamic units fall within the first 5 ms, ALM units 10 to 15 ms later, and VM/VAL units under ALM photoinhibition from about 20 ms; before the onset the ratio is near 1. More units distinguish trial types in the delay epoch with true labels than with labels shifted by one trial in 44 of 47 ALM, 14 of 18 SNr and 53 of 72 thalamic sessions, and in 16 of the 21 sessions without a photostimulus (table). The spike times share the clock of the trial tables; this is shown for every group with a photostimulus, and through decoding for the sessions without one (Gad2 and C57BL6 thalamus).
# Whole-cell checks: units, current, spikes, and where the photostimulus effect sits in the trial.
wm = pd.DataFrame([{"cell": c["meta"]["cell"], "group": c["meta"]["description"].replace("ALM whole cell recording during ", ""),
"unit": c["meta"]["vm_unit"], "conversion": c["meta"]["vm_conversion"],
"vm_min": round(c["meta"]["vm_range"][0], 1), "vm_max": round(c["meta"]["vm_range"][1], 1),
"spikes": c["meta"]["n_spikes"], "kink_found": round(float(np.mean(~np.isnan(c["meta"]["threshold_mV"]))), 3),
"clip_vs_deposited_median_abs_mV": round(c["meta"]["clip_vs_deposited_mad"], 3),
"current_nonzero": round(c["meta"]["frac_current_nonzero"], 3),
"kept_trials": int(c["trials"].response.isin(loop.KEEP).sum())} for c in cells])
print(wm.to_string(index=False))
cur_tr = pd.concat([c["trials"][["current_nA_E"]].assign(cell=c["meta"]["cell"]) for c in cells])
print("Mean injected current (nA) around the photostimulus, per trial: quantiles 0/50/100%",
np.round(np.percentile(cur_tr.current_nA_E, [0, 50, 100]), 3), "| trials below -0.05 nA:", int((cur_tr.current_nA_E < -0.05).sum()),
"in cells", sorted(set(cur_tr.cell[cur_tr.current_nA_E < -0.05])))
lz = pd.DataFrame([{"cell": c["meta"]["cell"], "group": c["meta"]["description"].replace("ALM whole cell recording during ", ""),
"series": c["meta"]["laser_series"][0][:12], "unit": c["meta"]["laser_unit"], "conversion": c["meta"]["laser_conversion"],
"max": round(c["meta"]["laser_max"], 2), "stim_trials": int(c["trials"].pi.sum()),
"stim_with_laser": int((c["trials"].pi & c["trials"].laser_on.notna()).sum()),
"ctrl_with_laser": int((~c["trials"].pi & c["trials"].laser_on.notna()).sum()),
"onset_after_pole_in_median": round(c["meta"]["laser_lag_from_pole"], 4),
"onset_after_pole_out_median": round(float(np.nanmedian((c["trials"].laser_on - c["trials"].pole_out_time)[c["trials"].pi])), 4),
"stim_onsets_off_median_by_20ms": int((np.abs(c["trials"].laser_on - c["trials"].pole_in_time - c["meta"]["laser_lag_from_pole"]) > 0.02)[c["trials"].pi].sum()),
"onset_1pct_minus_5pct_ms": round(float(np.nanmedian((c["trials"]["laser_on_0.01"] - c["trials"].laser_on)[c["trials"].pi])) * 1000, 2),
"onset_20pct_minus_5pct_ms": round(float(np.nanmedian((c["trials"]["laser_on_0.2"] - c["trials"].laser_on)[c["trials"].pi])) * 1000, 2),
"duration_s": round(float(np.nanmedian((c["trials"].laser_off - c["trials"].laser_on)[c["trials"].pi])), 3),
"end_after_cue_s": round(float(np.nanmedian((c["trials"].laser_off - c["trials"].cue)[c["trials"].pi])), 3),
"peak_median": round(float(np.nanmedian(c["trials"].laser_peak[c["trials"].pi])), 2)} for c in cells])
print("\nDeposited laser trace (stimulus/presentation/photostim_on_*, OptogeneticSeries), per cell; onset at 5% of the file's maximum:")
print(lz.to_string(index=False))
print("By group: onset after pole_in_time (s), median per cell:", lz.groupby("group").onset_after_pole_in_median.agg(["min", "max"]).to_dict("index"))
ex = wtr[~wtr.pi & wtr.laser_on.notna()]
print("Control trials with a laser pulse:", ex[["cell", "id", "response"]].to_dict("records"))
lk = wtr[wtr.response == "correct"]
print("Correct whole-cell trials with a larger lick-trace range after the go cue than before:",
round(float(np.mean(lk.lick_ptp_after_cue > lk.lick_ptp_before_cue)), 3), "of", len(lk))
wide_t = np.arange(loop.WIDE_SPAN[0], loop.WIDE_SPAN[1], 0.001)
diffs = {}
for c in cells:
t = c["trials"]
k = t.response.isin(loop.KEEP).values
g = c["meta"]["description"].replace("ALM whole cell recording during ", "")
d = c["wide"][k & t.pi.values].mean(0) - c["wide"][k & ~t.pi.values].mean(0)
diffs.setdefault(g, []).append(d)
print("\nMean Vm, photostimulus minus control trials (deposited spike-removed trace), by time from pole onset (s):")
rows = []
for g, ds in diffs.items():
m = np.mean(ds, 0)
rows.append({"group": g, "cells": len(ds), **{f"{x:.2f}": round(float(m[(wide_t >= x) & (wide_t < x + 0.02)].mean()), 1)
for x in (1.2, 1.3, 1.32, 1.4, 1.44, 1.46, 1.48, 1.5, 1.52, 1.6, 2.0, 2.5, 2.7)}})
print(pd.DataFrame(rows).to_string(index=False))
first = []
for c in cells:
if "thalamus" not in c["meta"]["description"]:
continue
t = c["trials"]
k = t.response.isin(loop.KEEP).values
d = c["wide"][k & t.pi.values].mean(0) - c["wide"][k & ~t.pi.values].mean(0)
base = d[wide_t < 1.25]
hit = np.flatnonzero((np.abs(d - base.mean()) > max(3 * base.std(), 2)) & (wide_t > 1.25))
po = float(np.median(t.pole_out_time - t.pole_in_time))
first.append((c["meta"]["cell"], round(wide_t[hit[0]], 3) if len(hit) else np.nan, round(po, 3)))
first = pd.DataFrame(first, columns=["cell", "first_change_s_from_pole_onset", "pole_out_s_from_pole_onset"])
print("\nThalamus photoinhibition cells: first time the difference exceeds 3 SD of its value before 1.25 s (and 2 mV):")
print(first.to_string(index=False))
print("Median:", first.first_change_s_from_pole_onset.median(), "| laser onset of these cells, median after pole onset:",
round(float(lz[lz.group == "thalamus photoinhibition"].onset_after_pole_in_median.median()), 4))
print("Dashed lines: E (1.3 s, black), M (1.5 s, grey), laser onset of the thalamus cells (red)")
fig, ax = plt.subplots(figsize=(7, 3))
for g, ds in diffs.items():
ax.plot(wide_t, np.mean(ds, 0), label=f"{g} ({len(ds)} cells)")
for x, col in ((1.3, "k"), (1.5, "grey"), (float(lz[lz.group == "thalamus photoinhibition"].onset_after_pole_in_median.median()), "tab:red")):
ax.axvline(x, ls="--", c=col, lw=0.8)
ax.set_xlim(1.0, 3.0)
ax.set_xlabel("time from pole onset (s)")
ax.set_ylabel("photostim. - control Vm (mV)")
ax.legend(fontsize=7)
plt.show()
print("Range plotted:", round(min(np.mean(ds, 0).min() for ds in diffs.values()), 1), "to",
round(max(np.mean(ds, 0).max() for ds in diffs.values()), 1), "mV")
cell group unit conversion vm_min vm_max spikes kink_found clip_vs_deposited_median_abs_mV current_nonzero kept_trials
cell_20_1 M1 photoinhibition mV 0.001 -95.0 3.7 8399 0.808 0.0 0.011 100
cell_21_1 M1 photoinhibition mV 0.001 -90.6 21.8 20842 0.928 0.0 0.011 172
cell_22_1 M1 photoinhibition mV 0.001 -86.1 9.9 3002 0.932 0.0 0.010 43
cell_23_1 M1 photoinhibition mV 0.001 -86.7 3.2 1243 0.970 0.0 0.010 33
cell_25_1 M1 photoinhibition mV 0.001 -99.2 14.4 9301 0.984 0.0 0.010 93
cell_29_1 M1 photoinhibition mV 0.001 -79.0 12.6 4578 0.942 0.0 0.010 43
cell_55_1 thalamus photoinhibition mV 0.001 -81.3 13.2 1479 0.749 0.0 0.010 75
cell_56_1 thalamus photoinhibition mV 0.001 -77.6 29.6 2745 0.909 0.0 0.010 83
cell_57_1 thalamus photoinhibition mV 0.001 -107.9 10.6 1670 0.879 0.0 0.010 45
cell_58_1 thalamus photoinhibition mV 0.001 -87.0 32.4 1426 0.875 0.0 0.010 48
cell_59_1 thalamus photoinhibition mV 0.001 -79.4 37.7 2780 0.917 0.0 0.010 57
cell_60_1 thalamus photoinhibition mV 0.001 -107.9 23.0 1932 0.775 0.0 0.010 71
cell_44_1 M1 photoinhibition mV 0.001 -69.5 18.8 6777 0.993 0.0 0.010 46
cell_45_1 M1 photoinhibition mV 0.001 -91.3 25.6 3767 0.965 0.0 0.010 49
cell_46_1 M1 photoinhibition mV 0.001 -70.8 33.8 7862 0.965 0.0 0.010 74
cell_45_2 contra ALM photoinhibition mV 0.001 -84.2 14.0 3187 0.909 0.0 0.010 48
cell_46_2 contra ALM photoinhibition mV 0.001 -78.3 13.2 6810 0.960 0.0 0.010 69
cell_40_2 M1 photoinhibition mV 0.001 -74.0 34.5 2051 0.934 0.0 0.010 86
cell_39_1 contra ALM photoinhibition mV 0.001 -61.2 18.7 4120 0.992 0.0 0.010 44
cell_40_1 contra ALM photoinhibition mV 0.001 -87.9 -27.7 931 0.838 0.0 0.010 44
cell_30_2 M1 photoinhibition mV 0.001 -100.8 7.4 653 0.963 0.0 0.009 38
cell_30_1 contra ALM photoinhibition mV 0.001 -86.8 20.9 3264 0.970 0.0 0.010 119
cell_31_1 contra ALM photoinhibition mV 0.001 -71.6 30.0 4718 0.988 0.0 0.010 70
cell_32_1 contra ALM photoinhibition mV 0.001 -72.7 3.2 5853 0.988 0.0 0.011 39
cell_34_1 contra ALM photoinhibition mV 0.001 -86.7 30.5 2576 0.973 0.0 0.010 93
cell_35_1 contra ALM photoinhibition mV 0.001 -107.9 28.0 1937 0.877 0.0 0.010 36
cell_47_1 thalamus photoinhibition mV 0.001 -96.4 30.7 3931 0.398 0.0 0.010 61
cell_49_1 thalamus photoinhibition mV 0.001 -95.4 12.8 2297 0.932 0.0 0.010 23
cell_50_1 thalamus photoinhibition mV 0.001 -75.7 29.0 9756 0.874 0.0 0.010 82
cell_51_1 thalamus photoinhibition mV 0.001 -107.9 27.8 6750 0.949 0.0 0.010 104
cell_52_1 thalamus photoinhibition mV 0.001 -75.9 36.0 8372 0.979 0.0 0.010 121
cell_53_1 thalamus photoinhibition mV 0.001 -65.8 19.9 3736 0.977 0.0 0.010 28
cell_64_1 thalamus photoinhibition mV 0.001 -88.9 27.1 1437 0.896 0.0 0.010 55
cell_65_1 thalamus photoinhibition mV 0.001 -80.2 39.5 2752 0.939 0.0 0.010 66
cell_66_1 thalamus photoinhibition mV 0.001 -77.3 43.4 1534 0.662 0.0 0.010 57
cell_73_2 thalamus photoinhibition mV 0.001 -77.8 7.0 1907 0.918 0.0 0.010 41
Mean injected current (nA) around the photostimulus, per trial: quantiles 0/50/100% [-0. -0. 0.] | trials below -0.05 nA: 0 in cells []
Deposited laser trace (stimulus/presentation/photostim_on_*, OptogeneticSeries), per cell; onset at 5% of the file's maximum:
cell group series unit conversion max stim_trials stim_with_laser ctrl_with_laser onset_after_pole_in_median onset_after_pole_out_median stim_onsets_off_median_by_20ms onset_1pct_minus_5pct_ms onset_20pct_minus_5pct_ms duration_s end_after_cue_s peak_median
cell_20_1 M1 photoinhibition photostim_on watts 0.001 3.01 68 68 0 1.4026 0.0019 8 -1.00 1.85 1.193 -0.005 3.01
cell_21_1 M1 photoinhibition photostim_on watts 0.001 4.28 87 87 1 1.4025 0.0019 4 -1.15 2.30 1.192 -0.006 3.00
cell_22_1 M1 photoinhibition photostim_on watts 0.001 3.00 11 11 0 1.4026 0.0017 0 -1.00 1.85 1.193 -0.005 3.00
cell_23_1 M1 photoinhibition photostim_on watts 0.001 3.00 4 4 0 1.4025 0.0019 0 -1.00 1.80 1.193 -0.005 3.00
cell_25_1 M1 photoinhibition photostim_on watts 0.001 3.00 47 47 0 1.4026 0.0019 2 -1.00 1.85 1.193 -0.005 3.00
cell_29_1 M1 photoinhibition photostim_on watts 0.001 2.99 24 24 0 1.4025 0.0018 1 -1.00 1.85 1.193 -0.005 2.99
cell_55_1 thalamus photoinhibition photostim_on watts 0.001 10.76 28 28 0 1.4727 0.0718 0 0.00 0.00 1.242 0.114 10.00
cell_56_1 thalamus photoinhibition photostim_on watts 0.001 10.81 31 31 0 1.4724 0.0718 0 0.00 0.00 1.242 0.114 10.00
cell_57_1 thalamus photoinhibition photostim_on watts 0.001 10.43 18 18 0 1.4725 0.0719 1 0.00 0.00 1.242 0.114 10.01
cell_58_1 thalamus photoinhibition photostim_on watts 0.001 10.01 19 19 0 1.4728 0.0720 3 0.00 0.00 1.242 0.114 10.01
cell_59_1 thalamus photoinhibition photostim_on watts 0.001 10.78 24 24 0 1.4725 0.0718 0 0.00 0.00 1.242 0.114 10.01
cell_60_1 thalamus photoinhibition photostim_on watts 0.001 10.38 33 33 0 1.4725 0.0719 4 0.00 0.00 1.242 0.114 10.01
cell_44_1 M1 photoinhibition photostim_on watts 0.001 3.00 19 19 0 1.4744 0.0736 1 -1.05 1.90 1.237 0.110 3.00
cell_45_1 M1 photoinhibition photostim_on watts 0.001 3.00 26 26 0 1.4743 0.0738 1 -1.05 1.90 1.237 0.110 3.00
cell_46_1 M1 photoinhibition photostim_on watts 0.001 3.00 44 44 0 1.4744 0.0738 4 -1.05 1.90 1.237 0.110 3.00
cell_45_2 contra ALM photoinhibition photostim_on watts 0.001 3.00 30 30 0 1.4745 0.0738 4 -1.08 2.05 1.237 0.110 3.00
cell_46_2 contra ALM photoinhibition photostim_on watts 0.001 3.00 42 42 0 1.4744 0.0737 5 -1.10 2.05 1.237 0.110 3.00
cell_40_2 M1 photoinhibition photostim_on watts 0.001 3.00 40 40 0 1.4744 0.0738 1 -1.05 1.90 1.237 0.110 3.00
cell_39_1 contra ALM photoinhibition photostim_on watts 0.001 2.98 18 18 0 1.4743 0.0735 0 -1.20 2.05 1.237 0.110 2.98
cell_40_1 contra ALM photoinhibition photostim_on watts 0.001 3.00 19 19 0 1.4744 0.0736 0 -1.05 2.05 1.237 0.110 3.00
cell_30_2 M1 photoinhibition photostim_on watts 0.001 3.00 17 17 0 1.4744 0.0738 1 -1.05 1.90 1.237 0.110 3.00
cell_30_1 contra ALM photoinhibition photostim_on watts 0.001 3.00 46 46 0 1.4744 0.0739 2 -1.05 2.05 1.237 0.110 3.00
cell_31_1 contra ALM photoinhibition photostim_on watts 0.001 3.00 36 36 0 1.4744 0.0737 1 -1.13 2.05 1.237 0.110 3.00
cell_32_1 contra ALM photoinhibition photostim_on watts 0.001 3.00 20 20 0 1.4744 0.0738 0 -1.05 2.05 1.237 0.110 3.00
cell_34_1 contra ALM photoinhibition photostim_on watts 0.001 4.58 42 42 0 1.4749 0.0743 1 -1.35 2.45 1.235 0.109 3.00
cell_35_1 contra ALM photoinhibition photostim_on watts 0.001 3.00 18 18 0 1.4745 0.0738 2 -1.05 2.10 1.237 0.110 3.00
cell_47_1 thalamus photoinhibition photostim_on watts 0.001 10.70 14 14 0 1.4727 0.0719 0 0.00 0.00 1.242 0.114 10.01
cell_49_1 thalamus photoinhibition photostim_on watts 0.001 10.00 11 11 0 1.4724 0.0716 0 0.00 0.00 1.243 0.114 10.00
cell_50_1 thalamus photoinhibition photostim_on watts 0.001 10.62 35 35 0 1.4724 0.0720 1 0.00 0.00 1.242 0.114 10.06
cell_51_1 thalamus photoinhibition photostim_on watts 0.001 10.73 40 40 0 1.4725 0.0718 1 0.00 0.00 1.242 0.114 10.01
cell_52_1 thalamus photoinhibition photostim_on watts 0.001 10.82 34 34 0 1.4723 0.0718 0 0.00 0.00 1.242 0.114 10.00
cell_53_1 thalamus photoinhibition photostim_on watts 0.001 10.59 13 13 0 1.4726 0.0718 0 0.00 0.00 1.242 0.114 10.00
cell_64_1 thalamus photoinhibition photostim_on watts 0.001 10.04 20 20 0 1.4725 0.0718 0 0.00 0.00 1.243 0.114 10.00
cell_65_1 thalamus photoinhibition photostim_on watts 0.001 10.01 26 26 0 1.4726 0.0717 0 0.00 0.00 1.243 0.114 10.00
cell_66_1 thalamus photoinhibition photostim_on watts 0.001 10.81 29 29 0 1.4726 0.0718 2 0.00 0.00 1.242 0.114 10.00
cell_73_2 thalamus photoinhibition photostim_on watts 0.001 10.01 20 20 0 1.4725 0.0717 0 0.00 0.00 1.243 0.114 10.00
By group: onset after pole_in_time (s), median per cell: {'M1 photoinhibition': {'min': 1.4025, 'max': 1.4744}, 'contra ALM photoinhibition': {'min': 1.4743, 'max': 1.4749}, 'thalamus photoinhibition': {'min': 1.4723, 'max': 1.4728}}
Control trials with a laser pulse: [{'cell': 'cell_21_1', 'id': 349, 'response': 'incorrect'}]
Correct whole-cell trials with a larger lick-trace range after the go cue than before: 0.998 of 1824
Mean Vm, photostimulus minus control trials (deposited spike-removed trace), by time from pole onset (s):
group cells 1.20 1.30 1.32 1.40 1.44 1.46 1.48 1.50 1.52 1.60 2.00 2.50 2.70
M1 photoinhibition 11 0.3 -0.1 -0.1 -0.0 -1.3 -1.1 -1.3 -2.3 -2.8 -2.2 -1.1 -0.8 0.0
thalamus photoinhibition 16 -0.0 0.1 0.2 0.2 -0.1 -0.0 -2.4 -8.5 -13.3 -18.4 -12.7 -16.5 -1.1
contra ALM photoinhibition 9 0.8 0.9 0.8 1.0 0.6 0.8 0.7 -0.9 -1.6 -1.6 0.3 0.1 1.1
Thalamus photoinhibition cells: first time the difference exceeds 3 SD of its value before 1.25 s (and 2 mV):
cell first_change_s_from_pole_onset pole_out_s_from_pole_onset
cell_55_1 1.493 1.401
cell_56_1 1.494 1.401
cell_57_1 1.505 1.401
cell_58_1 1.469 1.401
cell_59_1 1.485 1.401
cell_60_1 1.489 1.401
cell_47_1 1.486 1.401
cell_49_1 1.351 1.401
cell_50_1 1.496 1.401
cell_51_1 1.493 1.401
cell_52_1 1.502 1.401
cell_53_1 1.504 1.401
cell_64_1 1.501 1.401
cell_65_1 1.494 1.401
cell_66_1 1.488 1.401
cell_73_2 1.490 1.401
Median: 1.493 | laser onset of these cells, median after pole onset: 1.4725
Dashed lines: E (1.3 s, black), M (1.5 s, grey), laser onset of the thalamus cells (red)
Range plotted: -18.4 to 2.3 mV
The whole-cell Vm is stored with unit 'mV' and conversion 0.001, but the values are already in
mV (minimum -61 to -108 mV, spikes peaking up to +43 mV); applying the conversion, as NWB
prescribes, would give values a thousand times too small. The injected current around the
photostimulus is 0 nA in every trial.
The spike clipping of convention V, computed here from the raw trace, leaves the trace equal to
the deposited spike-removed trace (median absolute difference 0 mV in every cell), so the deposited
trace is clipped by the same rule. Licks follow the go cue in the lick traces.
The whole-cell files hold the laser trace, as an OptogeneticSeries
(stimulus/presentation/photostim_on_*, 20 kHz, unit 'watts' with conversion 0.001, so the values
are in mW): a step to 10 mW in the 16 thalamus photoinhibition cells and a sinusoid peaking at
3 mW (1.5 mW mean) in the 20 cortical photoinhibition cells. Before the first verification the
loader read only general/optogenetics and this notebook said the files record no laser time; that
was wrong. The trace is present in every stim_present trial and in one control trial (cell_21_1,
an incorrect trial). The laser starts 1.4725 s after pole_in_time (0.072 s after
pole_out_time) in the thalamus cells, 1.474 s after it in 14 cortical cells and 1.4026 s after it
(0.002 s after pole_out_time) in the six M1 cells of anm300476. It lasts 1.19 to 1.24 s and ends
between 5 ms before and 114 ms after the go cue. In 0 to 8 photostimulus trials per cell it starts
more than 20 ms from the cell's median; reading L uses each trial's own onset. Onsets taken at 1% or
20% of the maximum, in place of 5%, differ by 0 ms (step) or 1.0 to 2.5 ms (sinusoid).
Neither onset of the claims file is the laser onset: in the thalamus cells E (1.3 s after pole
onset) is 0.17 s before it and M (pole_out_time + 0.1 s, 1.5 s) 28 ms after it. Thalamus
photoinhibition hyperpolarizes ALM neurons from a median 1.493 s after pole onset (first crossing of
3 SD and 2 mV on 1 ms samples; 1.47 to 1.51 s in 15 of 16 cells), 20 ms after the laser onset by
this coarse check. Under E the 20 to 120 ms window (1.32 to 1.42 s) lies before the change in all
three groups (table: 0.2 mV for thalamus photoinhibition at 1.32 and 1.40 s). Under M (1.52 to
1.62 s) and L (1.49 to 1.59 s) it lies inside the change. The claims file (answer of 2026-10-20)
gives E and M as readings, because it took the files to hold no laser time. Reading L, the onset of
the deposited laser trace, is shown here as a third reading, and question 7 asks whether it should
be used. Control trials are aligned at the cell's median laser onset. The extracellular files hold
no laser trace; there the onset is the delay onset of the trial table, which the clock check above
supports.
Selections and readings used by several claims¶
Unit classes (convention C of the claims file): P1 applies the Methods' width thresholds to the
trough-to-peak width of the deposited mean waveform (ALM pyramidal > 0.6 ms, thalamic > 0.5 ms, SNr
GABAergic < 0.45 ms); P2 uses the files' cell_type label ('Wide width spiking' for ALM pyramidal
and thalamic units; 'Narrow width spiking' for SNr GABAergic units, as convention C says; before the
first verification this reading took every SNr unit not labelled wide); P3, for the thalamic claims of Fig. 2
and 6 only, takes all thalamic units regardless of width (VM26). VM/VAL: reading L1 (a
VM/VAL label in the dataset) does not exist, as every thalamic unit is labelled 'Thalamus'; reading
L2 is within 0.4 mm of ML 0.85, DV -4.0 mm in the coronal plane.
Selectivity (convention S) is computed once per reading: trials (control only, or all), response window (R1, R2), t-test (Student, Welch) and outcomes (c1 correct only, c2 correct and incorrect), 16 readings.
alm = units[units.region == "ALM"]
thal = units[units.region == "Thalamus"]
snr = units[units.region == "SNr"]
PYR = {"P1": alm[alm.width_ms > 0.6], "P2": alm[alm.cell_type == "Wide width spiking"]}
THP = {"P1": thal[thal.width_ms > 0.5], "P2": thal[thal.cell_type == "Wide width spiking"]}
GABA = {"P1": snr[snr.width_ms < 0.45], "P2": snr[snr.cell_type == "Narrow width spiking"]}
VMVAL = {p: d[d.vmval_d < 0.4] for p, d in THP.items()}
# Convention C, reading P3: all thalamic units regardless of width, for the thalamic claims of Fig. 2 and 6 only.
VM26 = {**VMVAL, "P3": thal[thal.vmval_d < 0.4]}
SEL_READINGS = [(ts, r, ev, oc) for ts in ("control", "all") for r in ("R1", "R2") for ev in (True, False)
for oc in (("correct",), ("correct", "incorrect"))]
def sel_key(ts, r, ev, oc):
return f"{ts}/{r}/{'Student' if ev else 'Welch'}/{'c1' if len(oc) == 1 else 'c2'}"
t0 = time.time()
SEL = {sel_key(*k): loop.selectivity_table(sessions, units, resp=k[1], equal_var=k[2], outcomes=k[3], trials_sel=k[0])
for k in SEL_READINGS}
print(f"selectivity under {len(SEL)} readings for {len(units)} units in {time.time() - t0:.0f} s")
print("Units per class:", {f"ALM pyramidal {p}": len(d) for p, d in PYR.items()},
{f"thalamic {p}": len(d) for p, d in THP.items()}, {f"VM/VAL {p}": len(d) for p, d in VM26.items()},
{f"SNr GABAergic {p}": len(d) for p, d in GABA.items()})
selectivity under 16 readings for 1936 units in 8 s
Units per class: {'ALM pyramidal P1': 577, 'ALM pyramidal P2': 810} {'thalamic P1': 148, 'thalamic P2': 682} {'VM/VAL P1': 20, 'VM/VAL P2': 222, 'VM/VAL P3': 259} {'SNr GABAergic P1': 171, 'SNr GABAergic P2': 146}
task-performance¶
Mice performed the delayed-response task at 77.6% correct on average, with 10.3% lick-early trials and 1.1% no-response trials.
Readings of the criterion: (a) all trials of all sessions pooled, (b) mean over sessions, (c) control trials only, pooled and per session. Percent correct excludes lick-early and no-response trials; those two are fractions of all task trials. Sessions are every behavioural recording in the dandiset (extracellular sessions and whole-cell recordings), shown also by kind.
prow = []
for s in sessions:
t = s["trials"]
prow.append({"kind": "extracellular", "subject": s["meta"]["subject"], "resp": t.response.values, "ctrl": (t.stim == "none").values})
for c in cells:
t = c["trials"]
prow.append({"kind": "whole-cell", "subject": c["meta"]["subject"], "resp": t.response.values, "ctrl": ~t.pi.values})
def perf(resp):
n = len(resp)
cor, inc = (resp == "correct").sum(), (resp == "incorrect").sum()
return {"n": n, "correct": 100 * cor / (cor + inc), "early": 100 * (resp == "early lick").sum() / n,
"noresp": 100 * (resp == "no response").sum() / n}
out = []
for kinds in (("extracellular", "whole-cell"), ("extracellular",), ("whole-cell",)):
sel = [r for r in prow if r["kind"] in kinds]
for reading, use_ctrl in (("a/b all trials", False), ("c control trials", True)):
per = pd.DataFrame([perf(r["resp"][r["ctrl"]] if use_ctrl else r["resp"]) for r in sel])
pooled = perf(np.concatenate([r["resp"][r["ctrl"]] if use_ctrl else r["resp"] for r in sel]))
out.append({"files": "+".join(kinds), "trials": reading, "how": "pooled", "sessions": len(sel), "trials_n": pooled["n"],
"correct_%": pooled["correct"], "lick_early_%": pooled["early"], "no_response_%": pooled["noresp"]})
out.append({"files": "+".join(kinds), "trials": reading, "how": "mean over sessions", "sessions": len(sel), "trials_n": int(per.n.sum()),
"correct_%": per.correct.mean(), "lick_early_%": per.early.mean(), "no_response_%": per.noresp.mean()})
perf_tab = pd.DataFrame(out)
perf_tab["within_tol"] = ((perf_tab["correct_%"] - 77.6).abs() <= 7.76) & ((perf_tab["lick_early_%"] - 10.3).abs() <= 1.03) & \
((perf_tab["no_response_%"] - 1.1).abs() <= 0.11)
print(perf_tab.round(2).to_string(index=False))
print("Paper: 77.6 +/- 7.76 % correct, 10.3 +/- 1.03 % lick early, 1.1 +/- 0.11 % no response")
by_mouse = pd.DataFrame([{"subject": r["subject"], **perf(r["resp"])} for r in prow]).groupby("subject")[["correct", "early", "noresp"]].mean()
print("\nPer mouse (mean over its sessions, all trials): correct", by_mouse.correct.round(1).min(), "to", by_mouse.correct.round(1).max(),
"| lick early", by_mouse.early.round(1).min(), "to", by_mouse.early.round(1).max(), "| no response", by_mouse.noresp.round(2).min(), "to", by_mouse.noresp.round(2).max())
files trials how sessions trials_n correct_% lick_early_% no_response_% within_tol
extracellular+whole-cell a/b all trials pooled 173 36923 70.66 18.18 0.78 False
extracellular+whole-cell a/b all trials mean over sessions 173 36923 71.48 15.91 0.98 False
extracellular+whole-cell c control trials pooled 173 29482 74.20 17.08 0.73 False
extracellular+whole-cell c control trials mean over sessions 173 29482 75.46 14.66 0.90 False
extracellular a/b all trials pooled 137 34270 70.08 18.84 0.72 False
extracellular a/b all trials mean over sessions 137 34270 69.85 17.81 0.87 False
extracellular c control trials pooled 137 27882 73.69 17.58 0.71 False
extracellular c control trials mean over sessions 137 27882 73.35 16.43 0.89 False
whole-cell a/b all trials pooled 36 2653 77.42 9.65 1.55 False
whole-cell a/b all trials mean over sessions 36 2653 77.65 8.68 1.43 False
whole-cell c control trials pooled 36 1600 82.33 8.38 1.06 False
whole-cell c control trials mean over sessions 36 1600 83.47 7.93 0.96 False
Paper: 77.6 +/- 7.76 % correct, 10.3 +/- 1.03 % lick early, 1.1 +/- 0.11 % no response
Per mouse (mean over its sessions, all trials): correct 57.6 to 86.8 | lick early 3.6 to 37.2 | no response 0.0 to 4.5
Result: not reproduced. Over all 173 behavioural recordings, percent correct is 70.7% (all trials pooled) to 75.5% (control trials, mean over sessions), within 77.6 +/- 7.8%, but lick-early trials are 14.7 to 18.2% against 10.3 +/- 1.0%, and no-response trials 0.73 to 0.98% against 1.1 +/- 0.11%. No reading puts all three within tolerance, and two of the three values are outside it under every reading of the full set of recordings. The whole-cell recordings alone come closest (77.4 to 83.5% correct, 7.9 to 9.7% lick early, 0.96 to 1.55% no response), with at most two values within tolerance. The dandiset holds recording sessions only, not the behaviour sessions the paper's averages may include. (Partially reproduced before the first verification; changed on the verifier's reading that no reading puts all three values within tolerance.)
fig1c-alm-photoinhibition-bias¶
Unilateral photoinhibition of ALM during the delay epoch biased responses to the ipsilateral side, lowering performance on contra trials and raising it on ipsi trials.
The dandiset has no behaviour-only sessions; the test uses the behavioural trials of the 66 recording sessions in PV-Cre x Ai32 mice with left-ALM photoinhibition in the delay epoch (ALM, thalamic and SNr recordings, 13 mice). Main reading: photoinhibition trials at 1.5 mW (the Methods' power); variant: all delay-epoch powers (1.5 and 0.75/0.79 mW), outside the condition by convention P (answer of 2026-10-20). Per session, fraction correct with lick-early and no-response trials excluded; two-sided paired t-test over sessions.
def fig1c(stims):
rows = []
for s in sessions:
m = s["meta"]
if m["genotype"] != "Ai32 x PV-Cre" or m["ogen_site"] != "ALM":
continue
t = s["trials"]
k = t.response.isin(loop.KEEP).values
r = {"subject": m["subject"], "region": m["region"]}
for side, con in (("contra", True), ("ipsi", False)):
for cond, msk in (("ctrl", (t.stim == "none").values), ("pi", t.stim.isin(stims).values)):
sel = k & msk & (t.contra.values == con)
r[f"{side}_{cond}"] = (t.response.values[sel] == "correct").mean() if sel.any() else np.nan
r[f"n_{side}_{cond}"] = int(sel.sum())
rows.append(r)
return pd.DataFrame(rows)
for name, stims in (("1.5 mW", ["ALM:delay:1.5"]), ("all delay powers", ["ALM:delay:1.5", "ALM:delay:0.79", "ALM:delay:0.75"])):
d = fig1c(stims)
full = d.dropna(subset=["contra_ctrl", "contra_pi", "ipsi_ctrl", "ipsi_pi"])
print(f"--- {name}: {len(full)} of {len(d)} sessions with all four cells, {full.subject.nunique()} mice;"
f" photoinhibition trials per session median {int((full.n_contra_pi + full.n_ipsi_pi).median())}")
for side in ("contra", "ipsi"):
tt = stats.ttest_rel(full[f"{side}_pi"], full[f"{side}_ctrl"])
print(f"{side}: control {full[side + '_ctrl'].mean():.3f}, photoinhibition {full[side + '_pi'].mean():.3f}, "
f"change {(full[side + '_pi'] - full[side + '_ctrl']).mean():+.3f} (sessions with an increase "
f"{int((full[side + '_pi'] > full[side + '_ctrl']).sum())}/{len(full)}), paired t = {tt.statistic:.2f}, p = {tt.pvalue:.2g}")
if name == "1.5 mW":
print("Per mouse (means over sessions):")
print(full.groupby("subject")[["contra_ctrl", "contra_pi", "ipsi_ctrl", "ipsi_pi"]].mean().round(3).assign(
sessions=full.groupby("subject").size()).to_string())
print("By recorded region:")
print(full.groupby("region")[["contra_ctrl", "contra_pi", "ipsi_ctrl", "ipsi_pi"]].mean().round(3).to_string())
--- 1.5 mW: 66 of 66 sessions with all four cells, 11 mice; photoinhibition trials per session median 41
contra: control 0.774, photoinhibition 0.368, change -0.405 (sessions with an increase 0/66), paired t = -16.67, p = 3.6e-25
ipsi: control 0.673, photoinhibition 0.744, change +0.071 (sessions with an increase 46/66), paired t = 3.99, p = 0.00017
Per mouse (means over sessions):
contra_ctrl contra_pi ipsi_ctrl ipsi_pi sessions
subject
anm00234218 0.800 0.301 0.573 0.671 4
anm00239123 0.730 0.334 0.673 0.715 3
anm00260487 0.726 0.317 0.688 0.741 11
anm00260488 0.751 0.326 0.635 0.771 6
anm00260490 0.665 0.383 0.594 0.699 7
anm00260491 0.670 0.442 0.490 0.667 3
anm00261334 0.880 0.514 0.686 0.728 5
anm00261335 0.803 0.446 0.648 0.657 9
anm00261336 0.810 0.641 0.782 0.761 6
anm00264941 0.725 0.333 0.647 0.833 1
anm00264943 0.847 0.173 0.775 0.878 11
By recorded region:
contra_ctrl contra_pi ipsi_ctrl ipsi_pi
region
ALM 0.817 0.203 0.753 0.874
SNr 0.832 0.534 0.707 0.709
Thalamus 0.739 0.327 0.643 0.734
--- all delay powers: 66 of 66 sessions with all four cells, 11 mice; photoinhibition trials per session median 44
contra: control 0.774, photoinhibition 0.418, change -0.356 (sessions with an increase 0/66), paired t = -16.59, p = 4.7e-25 ipsi: control 0.673, photoinhibition 0.735, change +0.062 (sessions with an increase 44/66), paired t = 3.64, p = 0.00054
Result: reproduced. In 66 recording sessions of 11 PV-Cre x Ai32 mice, ALM photoinhibition at 1.5 mW lowers contra performance from 0.774 to 0.368 (paired t = -16.67, p = 3.6e-25; no session increases) and raises ipsi performance from 0.673 to 0.744 (t = 3.99, p = 0.00017; 46 of 66 sessions increase). Both hold at p < 0.001, and with all powers pooled (p = 4.7e-25 and 0.00054). These are the behavioural trials of recording sessions, not the paper's 84 behaviour sessions.
alm-unit-classes¶
Of 1214 ALM units, 1006 were putative pyramidal neurons (spike width > 0.6 ms), 166 putative fast-spiking (< 0.4 ms) and 42 unclassified; the spike-width distribution was bimodal.
w = alm.width_ms
cnt = {"units": len(alm), "< 0.4 ms": int((w < 0.4).sum()), "0.4 to 0.6 ms": int(((w >= 0.4) & (w <= 0.6)).sum()), "> 0.6 ms": int((w > 0.6).sum())}
print("P1, computed widths:", cnt)
print("P2, file labels:", alm.cell_type.value_counts().to_dict())
print("Paper: 1214 +/- 121 units, 1006 +/- 101 pyramidal, 166 +/- 17 fast-spiking (42 unclassified)")
print("Scaled to the paper's total (x 1214/966): > 0.6 ms", round(cnt["> 0.6 ms"] * 1214 / len(alm)), "| wide label",
round((alm.cell_type == "Wide width spiking").sum() * 1214 / len(alm)), "| < 0.4 ms", round(cnt["< 0.4 ms"] * 1214 / len(alm)),
"| narrow label", round((alm.cell_type == "Narrow width spiking").sum() * 1214 / len(alm)))
print("Fractions: > 0.6 ms", round(cnt["> 0.6 ms"] / len(alm), 3), "| wide label", round((alm.cell_type == "Wide width spiking").mean(), 3),
"| paper pyramidal", round(1006 / 1214, 3), "; < 0.4 ms", round(cnt["< 0.4 ms"] / len(alm), 3), "| narrow label",
round((alm.cell_type == "Narrow width spiking").mean(), 3), "| paper FS", round(166 / 1214, 3))
print("\nHistogram of computed widths (ms): count per 0.04 ms step")
print(w.round(2).value_counts().sort_index().to_string())
print("Units > 0.6 ms whose peak is the last waveform sample:", int(((w > 0.6) & alm.peak_last).sum()), "of", cnt["> 0.6 ms"],
"| units 0.4 to 0.6 ms with the peak at the last sample:", int(((w >= 0.4) & (w <= 0.6) & alm.peak_last).sum()))
print("Per session: ALM units", alm.groupby("session").size().describe()[["min", "50%", "max"]].to_dict())
fig, ax = plt.subplots(figsize=(5, 2.5))
ax.hist(w, bins=np.arange(0.06, 0.78, 0.04), color="0.5")
ax.set_xlabel("trough-to-peak width (ms), deposited waveform")
ax.set_ylabel("ALM units")
plt.show()
print("All", len(w), "widths within the axes (0.06 to 0.78 ms)")
P1, computed widths: {'units': 966, '< 0.4 ms': 127, '0.4 to 0.6 ms': 262, '> 0.6 ms': 577}
P2, file labels: {'Wide width spiking': 810, 'Narrow width spiking': 121, 'Cannot be determined': 35}
Paper: 1214 +/- 121 units, 1006 +/- 101 pyramidal, 166 +/- 17 fast-spiking (42 unclassified)
Scaled to the paper's total (x 1214/966): > 0.6 ms 725 | wide label 1018 | < 0.4 ms 160 | narrow label 152
Fractions: > 0.6 ms 0.597 | wide label 0.839 | paper pyramidal 0.829 ; < 0.4 ms 0.131 | narrow label 0.125 | paper FS 0.137
Histogram of computed widths (ms): count per 0.04 ms step
width_ms
0.12 2
0.16 7
0.20 16
0.24 14
0.28 62
0.32 22
0.36 4
0.40 9
0.44 16
0.48 47
0.52 117
0.56 5
0.60 68
0.64 61
0.68 267
0.72 249
Units > 0.6 ms whose peak is the last waveform sample: 514 of 577 | units 0.4 to 0.6 ms with the peak at the last sample: 115
Per session: ALM units {'min': 3.0, '50%': 20.0, 'max': 46.0}
All 966 widths within the axes (0.06 to 0.78 ms)
Result: not reproduced. The dandiset holds 966 ALM units (paper 1214 +/- 121). The file labels give 810 wide and 121 narrow units (35 undetermined), fractions 0.839 and 0.125 against the paper's 0.829 and 0.137. Computed widths give 577 units above 0.6 ms and 127 below 0.4 ms, because the deposited waveforms are too short (514 of the 577 have their peak at the last sample). No count is within tolerance under either reading; P1 is the Methods' rule, and it fails because of the waveforms, not the units. The width histogram has two modes, at 0.28 ms (62 units) and 0.68 to 0.72 ms (267 and 249), with a trough at 0.36 ms (4 units).
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
fig2e-alm-selectivity¶
Most ALM putative pyramidal neurons (70%, 704/1006) distinguished trial types; 145 had preparatory activity only, 272 peri-movement selectivity only and 287 both.
Every reading of the criterion (trials control or all; R1, R2; Student, Welch; c1, c2), for both unit readings P1 and P2.
def sel_counts(idx, key):
d = SEL[key].loc[idx]
return {"n": len(d), "selective": int(d.selective.sum()), "prep_only": int((d.kind == "prep_only").sum()),
"peri_only": int((d.kind == "peri_only").sum()), "both": int((d.kind == "both").sum()),
"contra_pref": int((d.selective & d.contra_pref).sum())}
rows = []
for p, d in PYR.items():
for k in SEL:
rows.append({"units": p, "reading": k, **sel_counts(d.index, k)})
f2e = pd.DataFrame(rows)
f2e["frac_selective"] = (f2e.selective / f2e.n).round(3)
f2e["within_tol"] = ((f2e.selective - 704).abs() <= 70) & ((f2e.prep_only - 145).abs() <= 15) & \
((f2e.peri_only - 272).abs() <= 27) & ((f2e.both - 287).abs() <= 29)
for col, ref in (("selective", 704), ("prep_only", 145), ("peri_only", 272), ("both", 287)):
f2e[col + "_scaled"] = (f2e[col] * 1006 / f2e.n).round(0)
print(f2e.to_string(index=False))
print("Paper: 704/1006 selective (0.70), 145 prep only, 272 peri only, 287 both")
print("Readings with all four counts within tolerance:", int(f2e.within_tol.sum()), "of", len(f2e))
k0 = "control/R1/Student/c1"
print(f"\nBy mouse ({k0}, P2):")
d = SEL[k0].loc[PYR["P2"].index].join(PYR["P2"][["subject", "genotype"]])
print(d.groupby(["genotype", "subject"]).agg(n=("selective", "size"), selective=("selective", "mean")).round(3).to_string())
units reading n selective prep_only peri_only both contra_pref frac_selective within_tol selective_scaled prep_only_scaled peri_only_scaled both_scaled
P1 control/R1/Student/c1 577 398 95 154 149 201 0.690 False 694.0 166.0 268.0 260.0
P1 control/R1/Student/c2 577 360 90 141 129 183 0.624 False 628.0 157.0 246.0 225.0
P1 control/R1/Welch/c1 577 398 91 149 158 197 0.690 False 694.0 159.0 260.0 275.0
P1 control/R1/Welch/c2 577 363 90 145 128 181 0.629 False 633.0 157.0 253.0 223.0
P1 control/R2/Student/c1 577 393 93 149 151 202 0.681 False 685.0 162.0 260.0 263.0
P1 control/R2/Student/c2 577 365 87 146 132 194 0.633 False 636.0 152.0 255.0 230.0
P1 control/R2/Welch/c1 577 399 90 150 159 202 0.692 False 696.0 157.0 262.0 277.0
P1 control/R2/Welch/c2 577 370 91 152 127 192 0.641 False 645.0 159.0 265.0 221.0
P1 all/R1/Student/c1 577 413 95 146 172 213 0.716 False 720.0 166.0 255.0 300.0
P1 all/R1/Student/c2 577 360 93 142 125 182 0.624 False 628.0 162.0 248.0 218.0
P1 all/R1/Welch/c1 577 411 90 149 172 210 0.712 False 717.0 157.0 260.0 300.0
P1 all/R1/Welch/c2 577 367 94 144 129 183 0.636 False 640.0 164.0 251.0 225.0
P1 all/R2/Student/c1 577 417 98 150 169 214 0.723 False 727.0 171.0 262.0 295.0
P1 all/R2/Student/c2 577 367 91 149 127 192 0.636 False 640.0 159.0 260.0 221.0
P1 all/R2/Welch/c1 577 410 95 148 167 209 0.711 False 715.0 166.0 258.0 291.0
P1 all/R2/Welch/c2 577 373 93 150 130 192 0.646 False 650.0 162.0 262.0 227.0
P2 control/R1/Student/c1 810 571 132 222 217 297 0.705 False 709.0 164.0 276.0 270.0
P2 control/R1/Student/c2 810 517 128 203 186 272 0.638 False 642.0 159.0 252.0 231.0
P2 control/R1/Welch/c1 810 571 129 218 224 296 0.705 False 709.0 160.0 271.0 278.0
P2 control/R1/Welch/c2 810 521 129 206 186 274 0.643 False 647.0 160.0 256.0 231.0
P2 control/R2/Student/c1 810 565 127 216 222 298 0.698 False 702.0 158.0 268.0 276.0
P2 control/R2/Student/c2 810 519 124 205 190 279 0.641 False 645.0 154.0 255.0 236.0
P2 control/R2/Welch/c1 810 572 126 219 227 302 0.706 False 710.0 156.0 272.0 282.0
P2 control/R2/Welch/c2 810 526 126 211 189 281 0.649 False 653.0 156.0 262.0 235.0
P2 all/R1/Student/c1 810 589 133 207 249 318 0.727 False 732.0 165.0 257.0 309.0
P2 all/R1/Student/c2 810 514 131 199 184 272 0.635 False 638.0 163.0 247.0 229.0
P2 all/R1/Welch/c1 810 586 126 213 247 313 0.723 False 728.0 156.0 265.0 307.0
P2 all/R1/Welch/c2 810 521 127 208 186 275 0.643 False 647.0 158.0 258.0 231.0
P2 all/R2/Student/c1 810 594 134 212 248 320 0.733 False 738.0 166.0 263.0 308.0
P2 all/R2/Student/c2 810 523 129 208 186 284 0.646 False 650.0 160.0 258.0 231.0
P2 all/R2/Welch/c1 810 587 130 214 243 313 0.725 False 729.0 161.0 266.0 302.0
P2 all/R2/Welch/c2 810 528 126 215 187 285 0.652 False 656.0 156.0 267.0 232.0
Paper: 704/1006 selective (0.70), 145 prep only, 272 peri only, 287 both
Readings with all four counts within tolerance: 0 of 32
By mouse (control/R1/Student/c1, P2):
n selective
genotype subject
Ai32 x PV-Cre anm00261335 29 0.690
anm00264941 6 0.833
anm00264942 76 0.803
anm00264943 130 0.685
anm00264944 86 0.674
anm00267975 108 0.657
anm00279338 91 0.451
VGAT-ChR2-EYFP anm00322805 69 0.812
anm00322807 165 0.782
anm00323634 50 0.820
Result: not reproduced. With the file labels (P2, 810 units) and the first selectivity reading, 571 units (70.5%) are selective against 70% in the paper: 132 preparatory only, 222 peri-movement only and 217 both, which scaled to 1006 units give 164, 276 and 270 against 145, 272 and 287. The selective fraction ranges from 0.62 to 0.73 over all 32 readings. No reading puts the four counts within tolerance, because the dandiset holds 810 (P2) or 577 (P1) putative pyramidal units against the paper's 1006.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
fig2e-alm-contra-ipsi-balance¶
Approximately equal numbers of selective ALM neurons preferred contra and ipsi movements.
Readings: (a) two-sided binomial test of the contra count against one half does not reject at p < 0.05; (b) the contra fraction lies within 0.45 to 0.55. Under each reading of fig2e-alm-selectivity.
bal = f2e[["units", "reading", "selective", "contra_pref"]].copy()
bal["contra_frac"] = (bal.contra_pref / bal.selective).round(3)
bal["binom_p"] = [stats.binomtest(int(c), int(n), 0.5).pvalue for c, n in zip(bal.contra_pref, bal.selective)]
bal["a_binomial_holds"] = bal.binom_p >= 0.05
bal["b_fraction_holds"] = bal.contra_frac.between(0.45, 0.55)
print(bal.round(4).to_string(index=False))
print("Readings where (a) holds:", int(bal.a_binomial_holds.sum()), "| (b) holds:", int(bal.b_fraction_holds.sum()), "| of", len(bal))
print("Contra fraction range:", bal.contra_frac.min(), "to", bal.contra_frac.max())
units reading selective contra_pref contra_frac binom_p a_binomial_holds b_fraction_holds P1 control/R1/Student/c1 398 201 0.505 0.8805 True True P1 control/R1/Student/c2 360 183 0.508 0.7922 True True P1 control/R1/Welch/c1 398 197 0.495 0.8805 True True P1 control/R1/Welch/c2 363 181 0.499 1.0000 True True P1 control/R2/Student/c1 393 202 0.514 0.6140 True True P1 control/R2/Student/c2 365 194 0.532 0.2495 True True P1 control/R2/Welch/c1 399 202 0.506 0.8413 True True P1 control/R2/Welch/c2 370 192 0.519 0.4992 True True P1 all/R1/Student/c1 413 213 0.516 0.5549 True True P1 all/R1/Student/c2 360 182 0.506 0.8744 True True P1 all/R1/Welch/c1 411 210 0.511 0.6932 True True P1 all/R1/Welch/c2 367 183 0.499 1.0000 True True P1 all/R2/Student/c1 417 214 0.513 0.6244 True True P1 all/R2/Student/c2 367 192 0.523 0.4036 True True P1 all/R2/Welch/c1 410 209 0.510 0.7296 True True P1 all/R2/Welch/c2 373 192 0.515 0.6047 True True P2 control/R1/Student/c1 571 297 0.520 0.3572 True True P2 control/R1/Student/c2 517 272 0.526 0.2528 True True P2 control/R1/Welch/c1 571 296 0.518 0.4026 True True P2 control/R1/Welch/c2 521 274 0.526 0.2546 True True P2 control/R2/Student/c1 565 298 0.527 0.2069 True True P2 control/R2/Student/c2 519 279 0.538 0.0952 True True P2 control/R2/Welch/c1 572 302 0.528 0.1949 True True P2 control/R2/Welch/c2 526 281 0.534 0.1269 True True P2 all/R1/Student/c1 589 318 0.540 0.0579 True True P2 all/R1/Student/c2 514 272 0.529 0.2008 True True P2 all/R1/Welch/c1 586 313 0.534 0.1071 True True P2 all/R1/Welch/c2 521 275 0.528 0.2199 True True P2 all/R2/Student/c1 594 320 0.539 0.0647 True True P2 all/R2/Student/c2 523 284 0.543 0.0543 True True P2 all/R2/Welch/c1 587 313 0.533 0.1167 True True P2 all/R2/Welch/c2 528 285 0.540 0.0743 True True Readings where (a) holds: 32 | (b) holds: 32 | of 32 Contra fraction range: 0.495 to 0.543
Result: reproduced. Under all 32 readings the contra-preferring fraction of selective ALM neurons is 0.495 to 0.543, and the binomial test does not reject one half: both readings of the criterion hold in 32 of 32.
thalamus-unit-classes¶
Of 790 thalamic units, 672 were putative thalamic neurons (spike width > 0.5 ms) and 313 lay within 0.4 mm of the VM/VAL centre; 295 VM/VAL units were analysed.
near = thal.vmval_d < 0.4
print("Thalamic units:", len(thal), "(paper 790 +/- 79)")
for p, d in THP.items():
print(f"{p}: putative thalamic {len(d)} (paper 672 +/- 67); within 0.4 mm (L2), all units {int(near.sum())} (paper 313 +/- 31);"
f" putative and within 0.4 mm {len(VMVAL[p])} (paper 295 +/- 30)")
print("L1: thalamic location labels in the files:", Counter(s["meta"]["eg_location"].get("brain_subregion") for s in sessions if s["meta"]["region"] == "Thalamus"))
print("\nDistance from the L2 centre (mm), all thalamic units: quantiles 0/10/50/90/100%",
np.round(np.percentile(thal.vmval_d, [0, 10, 50, 90, 100]), 2))
print("Units within 0.4, 0.5, 0.6, 0.8, 1.0 mm:", [int((thal.vmval_d < r).sum()) for r in (0.4, 0.5, 0.6, 0.8, 1.0)])
print("By group (genotype, all units / within 0.4 mm):")
print(thal.assign(near=near).groupby("genotype").agg(units=("near", "size"), within_04=("near", "sum")).to_string())
print("Computed width of thalamic units (ms):", thal.width_ms.round(2).value_counts().sort_index().to_dict())
Thalamic units: 789 (paper 790 +/- 79)
P1: putative thalamic 148 (paper 672 +/- 67); within 0.4 mm (L2), all units 259 (paper 313 +/- 31); putative and within 0.4 mm 20 (paper 295 +/- 30)
P2: putative thalamic 682 (paper 672 +/- 67); within 0.4 mm (L2), all units 259 (paper 313 +/- 31); putative and within 0.4 mm 222 (paper 295 +/- 30)
L1: thalamic location labels in the files: Counter({'N/A': 72})
Distance from the L2 centre (mm), all thalamic units: quantiles 0/10/50/90/100% [0.02 0.22 0.5 1.08 1.66]
Units within 0.4, 0.5, 0.6, 0.8, 1.0 mm: [259, 383, 476, 612, 675]
By group (genotype, all units / within 0.4 mm):
units within_04
genotype
Ai32 x PV-Cre 451 205
Ai35D x GAD2_Cre 84 15
C57BL6 104 39
VGAT-ChR2-EYFP 150 0
Computed width of thalamic units (ms): {0.08: 2, 0.12: 7, 0.16: 3, 0.2: 10, 0.24: 20, 0.28: 7, 0.32: 38, 0.36: 94, 0.4: 49, 0.44: 226, 0.48: 185, 0.52: 52, 0.56: 32, 0.6: 40, 0.64: 9, 0.68: 15}
Result: not reproduced. The dandiset holds 789 thalamic units (paper 790) and, by file label (P2), 682 putative thalamic units (paper 672 +/- 67). The location counts are below tolerance: 259 units lie within 0.4 mm of the L2 centre (313 +/- 31) and 222 putative units among them (295 +/- 30). The files have no VM/VAL label (reading L1), and the L2 centre is a proxy. By computed width (P1) only 148 units exceed 0.5 ms, an effect of the short waveforms.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
fig2f-vmval-selectivity¶
A majority of VM/VAL neurons (69%, 204/295) distinguished trial types, a fraction indistinguishable from ALM; 43 had preparatory activity only, 82 peri-movement selectivity only and 79 both, a distribution similar to ALM.
VM/VAL units of thalamus-unit-classes (L2), under every selectivity reading and P1, P2, and P3 (all VM/VAL units, compared with ALM P1 and P2). Chi-squared tests against ALM pyramidal units under the same readings: 2 x 2 (selective or not), and for the types (i) 2 x 4 with non-selective as a class, (ii) 2 x 3 among selective neurons.
rows = []
for p, pa in (("P1", "P1"), ("P2", "P2"), ("P3", "P1"), ("P3", "P2")):
for k in SEL:
v, a = sel_counts(VM26[p].index, k), sel_counts(PYR[pa].index, k)
chi_sel = stats.chi2_contingency([[v["selective"], v["n"] - v["selective"]], [a["selective"], a["n"] - a["selective"]]])[1]
t4 = [[v["prep_only"], v["peri_only"], v["both"], v["n"] - v["selective"]], [a["prep_only"], a["peri_only"], a["both"], a["n"] - a["selective"]]]
chi4 = stats.chi2_contingency(t4)[1]
chi3 = stats.chi2_contingency([r[:3] for r in t4])[1]
rows.append({"units": p, "alm_units": pa, "reading": k, **{x: v[x] for x in ("n", "selective", "prep_only", "peri_only", "both")},
"frac": round(v["selective"] / v["n"], 3), "alm_frac": round(a["selective"] / a["n"], 3),
"p_chi_selective": chi_sel, "p_chi_2x4": chi4, "p_chi_2x3": chi3})
f2f = pd.DataFrame(rows)
f2f["counts_within_tol"] = ((f2f.selective - 204).abs() <= 20) & ((f2f.prep_only - 43).abs() <= 4.3) & \
((f2f.peri_only - 82).abs() <= 8.2) & ((f2f.both - 79).abs() <= 7.9)
f2f["chi_ok_i"] = (f2f.p_chi_selective >= 0.05) & (f2f.p_chi_2x4 >= 0.05)
f2f["chi_ok_ii"] = (f2f.p_chi_selective >= 0.05) & (f2f.p_chi_2x3 >= 0.05)
print(f2f.round(4).to_string(index=False))
print("Paper: 204/295 (0.69), 43, 82, 79; chi-squared p = 0.79 (fraction), p > 0.5 (types)")
print("Readings with counts within tolerance:", int(f2f.counts_within_tol.sum()), "| with both chi-squared tests p >= 0.05 (i):",
int(f2f.chi_ok_i.sum()), "(ii):", int(f2f.chi_ok_ii.sum()), "| all of counts and (i) or (ii):",
int((f2f.counts_within_tol & (f2f.chi_ok_i | f2f.chi_ok_ii)).sum()), "of", len(f2f))
print("\nBy VM/VAL unit reading (ALM reading in brackets): selective fraction range, readings with counts within tolerance,"
" readings with the 2 x 2 test p >= 0.05, with (i) and with (ii) holding")
print(f2f.groupby(["units", "alm_units"]).agg(frac_min=("frac", "min"), frac_max=("frac", "max"), n=("n", "first"),
counts_ok=("counts_within_tol", "sum"), chi_sel_ok=("p_chi_selective", lambda x: int((x >= 0.05).sum())),
ok_i=("chi_ok_i", "sum"), ok_ii=("chi_ok_ii", "sum"), readings=("frac", "size")).to_string())
print("Scaled to 295 units: selective", (f2f.selective * 295 / f2f.n).round(0).min(), "to", (f2f.selective * 295 / f2f.n).round(0).max())
units alm_units reading n selective prep_only peri_only both frac alm_frac p_chi_selective p_chi_2x4 p_chi_2x3 counts_within_tol chi_ok_i chi_ok_ii
P1 P1 control/R1/Student/c1 20 13 3 7 3 0.650 0.690 0.8950 0.6805 0.4860 False True True
P1 P1 control/R1/Student/c2 20 14 3 7 4 0.700 0.624 0.6481 0.7477 0.7158 False True True
P1 P1 control/R1/Welch/c1 20 14 4 7 3 0.700 0.690 1.0000 0.5837 0.3848 False True True
P1 P1 control/R1/Welch/c2 20 14 3 7 4 0.700 0.629 0.6816 0.7893 0.7512 False True True
P1 P1 control/R2/Student/c1 20 14 3 8 3 0.700 0.681 1.0000 0.4846 0.3079 False True True
P1 P1 control/R2/Student/c2 20 12 2 5 5 0.600 0.633 0.9512 0.9343 0.8362 False True True
P1 P1 control/R2/Welch/c1 20 15 4 8 3 0.750 0.692 0.7557 0.3902 0.2863 False True True
P1 P1 control/R2/Welch/c2 20 12 2 5 5 0.600 0.641 0.8880 0.8973 0.7857 False True True
P1 P1 all/R1/Student/c1 20 15 3 6 6 0.750 0.716 0.9350 0.9650 0.9257 False True True
P1 P1 all/R1/Student/c2 20 12 2 4 6 0.600 0.624 1.0000 0.7377 0.5320 False True True
P1 P1 all/R1/Welch/c1 20 15 3 6 6 0.750 0.712 0.9084 0.9725 0.9553 False True True
P1 P1 all/R1/Welch/c2 20 12 2 4 6 0.600 0.636 0.9259 0.7445 0.5515 False True True
P1 P1 all/R2/Student/c1 20 14 2 5 7 0.700 0.723 1.0000 0.8447 0.6724 False True True
P1 P1 all/R2/Student/c2 20 12 2 4 6 0.600 0.636 0.9259 0.7311 0.5353 False True True
P1 P1 all/R2/Welch/c1 20 14 2 5 7 0.700 0.711 1.0000 0.8611 0.6869 False True True
P1 P1 all/R2/Welch/c2 20 12 2 4 6 0.600 0.646 0.8500 0.7256 0.5448 False True True
P2 P2 control/R1/Student/c1 222 149 38 75 36 0.671 0.705 0.3745 0.0110 0.0053 False False False
P2 P2 control/R1/Student/c2 222 133 44 65 24 0.599 0.638 0.3210 0.0010 0.0004 False False False
P2 P2 control/R1/Welch/c1 222 149 39 75 35 0.671 0.705 0.3745 0.0034 0.0014 False False False
P2 P2 control/R1/Welch/c2 222 132 43 65 24 0.595 0.643 0.2103 0.0011 0.0006 False False False
P2 P2 control/R2/Student/c1 222 150 38 76 36 0.676 0.698 0.5870 0.0052 0.0018 False False False
P2 P2 control/R2/Student/c2 222 133 45 65 23 0.599 0.641 0.2886 0.0003 0.0001 False False False
P2 P2 control/R2/Welch/c1 222 150 39 76 35 0.676 0.706 0.4263 0.0023 0.0009 False False False
P2 P2 control/R2/Welch/c2 222 130 43 63 24 0.586 0.649 0.0947 0.0007 0.0006 False False False
P2 P2 all/R1/Student/c1 222 149 35 74 40 0.671 0.727 0.1203 0.0012 0.0010 False False False
P2 P2 all/R1/Student/c2 222 138 46 66 26 0.622 0.635 0.7827 0.0025 0.0007 False False False
P2 P2 all/R1/Welch/c1 222 150 35 75 40 0.676 0.723 0.1899 0.0021 0.0014 False False False
P2 P2 all/R1/Welch/c2 222 138 46 66 26 0.622 0.643 0.6070 0.0021 0.0007 False False False
P2 P2 all/R2/Student/c1 222 150 35 75 40 0.676 0.733 0.1069 0.0013 0.0012 False False False
P2 P2 all/R2/Student/c2 222 138 46 66 26 0.622 0.646 0.5600 0.0023 0.0008 False False False
P2 P2 all/R2/Welch/c1 222 151 35 76 40 0.680 0.725 0.2233 0.0027 0.0016 False False False
P2 P2 all/R2/Welch/c2 222 139 47 67 25 0.626 0.652 0.5280 0.0010 0.0003 False False False
P3 P1 control/R1/Student/c1 259 174 40 89 45 0.672 0.690 0.6627 0.0242 0.0098 False False False
P3 P1 control/R1/Student/c2 259 162 48 79 35 0.625 0.624 1.0000 0.0144 0.0051 False False False
P3 P1 control/R1/Welch/c1 259 174 41 88 45 0.672 0.690 0.6627 0.0083 0.0030 False False False
P3 P1 control/R1/Welch/c2 259 161 47 79 35 0.622 0.629 0.8967 0.0228 0.0084 False False False
P3 P1 control/R2/Student/c1 259 175 40 90 45 0.676 0.681 0.9398 0.0120 0.0042 False False False
P3 P1 control/R2/Student/c2 259 159 49 76 34 0.614 0.633 0.6606 0.0096 0.0035 False False False
P3 P1 control/R2/Welch/c1 259 176 42 90 44 0.680 0.692 0.7912 0.0044 0.0014 False False False
P3 P1 control/R2/Welch/c2 259 156 47 74 35 0.602 0.641 0.3172 0.0397 0.0254 False False False
P3 P1 all/R1/Student/c1 259 175 37 87 51 0.676 0.716 0.2749 0.0052 0.0029 False False False
P3 P1 all/R1/Student/c2 259 166 50 80 36 0.641 0.624 0.6940 0.0239 0.0104 False False False
P3 P1 all/R1/Welch/c1 259 176 37 87 52 0.680 0.712 0.3809 0.0118 0.0059 False False False
P3 P1 all/R1/Welch/c2 259 166 50 80 36 0.641 0.636 0.9538 0.0205 0.0077 False False False
P3 P1 all/R2/Student/c1 259 176 37 88 51 0.680 0.723 0.2346 0.0067 0.0044 False False False
P3 P1 all/R2/Student/c2 259 164 50 78 36 0.633 0.636 0.9990 0.0356 0.0137 False False False
P3 P1 all/R2/Welch/c1 259 177 37 88 52 0.683 0.711 0.4760 0.0132 0.0059 False False False
P3 P1 all/R2/Welch/c2 259 165 51 79 35 0.637 0.646 0.8542 0.0181 0.0066 False False False
P3 P2 control/R1/Student/c1 259 174 40 89 45 0.672 0.705 0.3513 0.0109 0.0055 False False False
P3 P2 control/R1/Student/c2 259 162 48 79 35 0.625 0.638 0.7657 0.0087 0.0030 False False False
P3 P2 control/R1/Welch/c1 259 174 41 88 45 0.672 0.705 0.3513 0.0062 0.0030 False False False
P3 P2 control/R1/Welch/c2 259 161 47 79 35 0.622 0.643 0.5789 0.0106 0.0041 False False False
P3 P2 control/R2/Student/c1 259 175 40 90 45 0.676 0.698 0.5579 0.0053 0.0020 False False False
P3 P2 control/R2/Student/c2 259 159 49 76 34 0.614 0.641 0.4798 0.0043 0.0016 False False False
P3 P2 control/R2/Welch/c1 259 176 42 90 44 0.680 0.706 0.4616 0.0029 0.0011 False False False
P3 P2 control/R2/Welch/c2 259 156 47 74 35 0.602 0.649 0.1944 0.0092 0.0067 False False False
P3 P2 all/R1/Student/c1 259 175 37 87 51 0.676 0.727 0.1290 0.0014 0.0012 False False False
P3 P2 all/R1/Student/c2 259 166 50 80 36 0.641 0.635 0.9116 0.0090 0.0032 False False False
P3 P2 all/R1/Welch/c1 259 176 37 87 52 0.680 0.723 0.2002 0.0049 0.0034 False False False
P3 P2 all/R1/Welch/c2 259 166 50 80 36 0.641 0.643 1.0000 0.0102 0.0035 False False False
P3 P2 all/R2/Student/c1 259 176 37 88 51 0.680 0.733 0.1097 0.0016 0.0015 False False False
P3 P2 all/R2/Student/c2 259 164 50 78 36 0.633 0.646 0.7717 0.0139 0.0050 False False False
P3 P2 all/R2/Welch/c1 259 177 37 88 52 0.683 0.725 0.2294 0.0058 0.0036 False False False
P3 P2 all/R2/Welch/c2 259 165 51 79 35 0.637 0.652 0.7196 0.0076 0.0026 False False False
Paper: 204/295 (0.69), 43, 82, 79; chi-squared p = 0.79 (fraction), p > 0.5 (types)
Readings with counts within tolerance: 0 | with both chi-squared tests p >= 0.05 (i): 16 (ii): 16 | all of counts and (i) or (ii): 0 of 64
By VM/VAL unit reading (ALM reading in brackets): selective fraction range, readings with counts within tolerance, readings with the 2 x 2 test p >= 0.05, with (i) and with (ii) holding
frac_min frac_max n counts_ok chi_sel_ok ok_i ok_ii readings
units alm_units
P1 P1 0.600 0.750 20 0 16 16 16 16
P2 P2 0.586 0.680 222 0 16 0 0 16
P3 P1 0.602 0.683 259 0 16 0 0 16
P2 0.602 0.683 259 0 16 0 0 16
Scaled to 295 units: selective 173.0 to 221.0
Result: not reproduced. With P2, 149 of 222 VM/VAL units (67%) are selective under the first reading (paper 204/295, 69%), and the fraction does not differ from ALM's 70.5% (chi-squared p = 0.37). The selectivity types differ from ALM in all 16 P2 readings (both type tests p < 0.05; first reading 38 preparatory only, 75 peri-movement only, 36 both, p = 0.011 and 0.0053), where the paper finds p > 0.5. With P3 (all 259 VM/VAL units), 174 are selective under the first reading (67%; 156 to 177 over the readings, below 204 +/- 20), the fraction does not differ from ALM in any reading, and the types differ from ALM in all 32 readings (first reading 40, 89, 45; p = 0.024 and 0.0098 against ALM P1). The counts are below tolerance in every reading; with P1 only 20 units qualify, and their types do not differ from ALM. The selective fraction holds; the type distribution holds only on the 20 P1 units.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
fig3-alm-collapse¶
Strong photoinhibition of thalALM (VGAT-ChR2-EYFP) decreased ALM activity to 6% of control, inhibiting 309 of 314 neurons (155 significantly) and significantly activating none.
ALM putative pyramidal units of the VGAT-ChR2-EYFP sessions (thalamus photostimulus, 10 mW, delay epoch), at least 5 photostimulus trials; 20 to 120 ms after onset; readings T1 (correct and incorrect) and T2 (correct only), Q1 to Q3, Student and Welch, P1 and P2. Variant, not part of the criterion: the ALM sessions of PV-Cre x Ai32 mice with thalamic photostimulation at 10 mW, which the answer of 2026-10-20 places outside Fig. 3.
T_READ = {"T1": ("correct", "incorrect"), "T2": ("correct",)}
def effect_table(sel_units, stims, window="pw", min_trials=5, extra=None):
rows, keep = [], {}
for p, d in sel_units.items():
for tname, oc in T_READ.items():
for ev in (True, False):
df = loop.photo_effect(sessions, d, stims, oc, window, min_trials, ev)
keep[(p, tname, ev)] = df
rows.append({"units": p, "trials": tname, "test": "Student" if ev else "Welch", **loop.effect_counts(df),
**{k: round(v, 2) if isinstance(v, float) else v for k, v in loop.ratio_readings(df).items()},
"mice": d.loc[df.index].subject.nunique()})
return pd.DataFrame(rows), keep
vg_alm = {p: d[d.genotype == "VGAT-ChR2-EYFP"] for p, d in PYR.items()}
f3, F3 = effect_table(vg_alm, "Thalamus:delay:10")
for q in ("Q1", "Q2", "Q3"):
f3[f"within_tol_{q}"] = ((f3[q] - 6).abs() <= 0.6) & ((f3.n - 314).abs() <= 31) & ((f3.inhibited - 309).abs() <= 31) & \
((f3.activated - 3).abs() <= 0.3) & ((f3.sig_inhibited - 155).abs() <= 16) & (f3.sig_activated == 0)
print(f3.to_string(index=False))
print("Paper: 6% of control; 314 neurons, 309 inhibited, 3 activated, 155 significantly inhibited, 0 significantly activated")
print("Readings within tolerance on all values:", int(f3[[c for c in f3 if c.startswith("within_tol")]].any(axis=1).sum()), "of", len(f3))
print("Partial condition (activity falls, inhibited > activated, none significantly activated) in every reading:",
bool(((f3.Q1 < 100) & (f3.inhibited > f3.activated) & (f3.sig_activated == 0)).all()))
d = F3[("P2", "T1", True)].join(units[["subject"]])
print("\nBy mouse (P2, T1, Student):")
print(d.groupby("subject").agg(n=("delta", "size"), inhibited=("delta", lambda x: int((x < 0).sum())),
rate_ctrl=("rate_ctrl", "mean"), rate_pi=("rate_pi", "mean")).round(2).to_string())
pv_alm = {p: d[(d.genotype == "Ai32 x PV-Cre") & (d.ogen_site == "Thalamus")] for p, d in PYR.items()}
v3, _ = effect_table(pv_alm, "Thalamus:delay:10")
print("\nVariant, PV-Cre x Ai32 ALM sessions with thalamic photostimulation at 10 mW:")
print(v3.to_string(index=False))
both = {p: pd.concat([vg_alm[p], pv_alm[p]]) for p in PYR}
v3b, _ = effect_table(both, "Thalamus:delay:10")
print("Variant, VGAT and PV-Cre x Ai32 together at 10 mW:")
print(v3b.to_string(index=False))
units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice within_tol_Q1 within_tol_Q2 within_tol_Q3
P1 T1 Student 234 230 2 2 109 0 2.368752 5.211661 0.0 2 3 False False False
P1 T1 Welch 234 230 2 2 186 0 2.368752 5.211661 0.0 2 3 False False False
P1 T2 Student 230 222 4 4 75 0 2.466225 5.241414 0.0 4 3 False False False
P1 T2 Welch 230 222 4 4 171 0 2.466225 5.241414 0.0 4 3 False False False
P2 T1 Student 261 256 2 3 127 0 2.147048 5.050987 0.0 3 3 False False False
P2 T1 Welch 261 256 2 3 208 0 2.147048 5.050987 0.0 3 3 False False False
P2 T2 Student 256 247 4 5 86 0 2.289574 5.090505 0.0 5 3 False False False
P2 T2 Welch 256 247 4 5 194 0 2.289574 5.090505 0.0 5 3 False False False
Paper: 6% of control; 314 neurons, 309 inhibited, 3 activated, 155 significantly inhibited, 0 significantly activated
Readings within tolerance on all values: 0 of 8
Partial condition (activity falls, inhibited > activated, none significantly activated) in every reading: True
By mouse (P2, T1, Student):
n inhibited rate_ctrl rate_pi
subject
anm00322805 69 67 5.26 0.13
anm00322807 142 139 4.04 0.04
anm00323634 50 50 2.92 0.17
Variant, PV-Cre x Ai32 ALM sessions with thalamic photostimulation at 10 mW: units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice P1 T1 Student 233 196 21 16 50 4 21.25 89.63 0.0 16 4 P1 T1 Welch 233 196 21 16 157 3 21.25 89.63 0.0 16 4 P1 T2 Student 130 115 4 11 15 2 12.89 26.01 0.0 11 3 P1 T2 Welch 130 115 4 11 88 1 12.89 26.01 0.0 11 3 P2 T1 Student 261 221 24 16 64 7 21.65 84.98 0.0 16 4 P2 T1 Welch 261 221 24 16 181 5 21.65 84.98 0.0 16 4 P2 T2 Student 149 132 6 11 23 4 15.77 27.88 0.0 11 3 P2 T2 Welch 149 132 6 11 102 3 15.77 27.88 0.0 11 3
Variant, VGAT and PV-Cre x Ai32 together at 10 mW: units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice P1 T1 Student 467 426 23 18 159 4 10.71 46.01 0.0 18 7 P1 T1 Welch 467 426 23 18 343 3 10.71 46.01 0.0 18 7 P1 T2 Student 360 337 8 15 90 2 5.48 12.41 0.0 15 6 P1 T2 Welch 360 337 8 15 259 1 5.48 12.41 0.0 15 6 P2 T1 Student 522 477 26 19 191 7 11.08 43.98 0.0 19 7 P2 T1 Welch 522 477 26 19 389 5 11.08 43.98 0.0 19 7 P2 T2 Student 405 379 10 16 109 4 6.54 13.17 0.0 16 6 P2 T2 Welch 405 379 10 16 296 3 6.54 13.17 0.0 16 6
Result: partially reproduced. In 261 VGAT ALM putative pyramidal units (P2; 234 with P1) from 3 mice, thalamus photoinhibition lowers activity to 2.1 to 2.5% (Q1) or 5.1 to 5.2% (Q2) of control, against 6 +/- 0.6%. With P2 and correct and incorrect trials, 256 of 261 units are inhibited, 2 activated, 127 (Student) or 208 (Welch) significantly inhibited and none significantly activated. The number of units (230 to 261) is below 314 +/- 31, and no reading puts all values within tolerance. The criterion's partial condition (activity falls, inhibited outnumber activated, none significantly activated) holds in every reading. In the variant with the PV-Cre x Ai32 sessions at 10 mW, activity falls to 21.7% (P2, T1) and 7 units are significantly activated.
fig3d-all-layers¶
Thalamus photoinhibition reduced ALM activity across all cortical layers.
Units of fig3-alm-collapse; depth is -unit_y (mm). Bins (a) 100 um, (b) 200 um; modulated (m1)
by the sign of the change, (m2) significant by convention P. Per bin, inhibited against activated
neurons, and the population rate ratio.
rows = []
for p in ("P1", "P2"):
for tname in T_READ:
for mod, ev in (("m1", True), ("m2 Student", True), ("m2 Welch", False)):
df = F3[(p, tname, ev)].join(units[["dv"]])
depth = -df.dv * 1000
for bw in (100, 200):
b = (depth // bw * bw).astype(int)
for lo, g in df.groupby(b):
sel = g.sig if mod != "m1" else np.ones(len(g), bool)
inh, act = int(((g.delta < 0) & sel).sum()), int(((g.delta > 0) & sel).sum())
rows.append({"units": p, "trials": tname, "bin_um": bw, "modulated": mod, "depth_from_um": lo, "neurons": len(g),
"inhibited": inh, "activated": act, "rate_ratio": round(g.rate_pi.mean() / g.rate_ctrl.mean(), 3)})
f3d = pd.DataFrame(rows)
f3d["inh_gt_act"] = f3d.inhibited > f3d.activated
summary = f3d.groupby(["units", "trials", "bin_um", "modulated"]).agg(bins=("neurons", "size"), bins_ok=("inh_gt_act", "sum"),
max_ratio=("rate_ratio", "max")).reset_index()
print(summary.to_string(index=False))
print("\nBins where inhibited do not outnumber activated:")
print(f3d[~f3d.inh_gt_act].to_string(index=False))
print("Depth (-unit_y, um) of these units: range", int(-units.loc[F3[("P2", "T1", True)].index].dv.max() * 1000), "to",
int(-units.loc[F3[("P2", "T1", True)].index].dv.min() * 1000))
print("\nPer 100 um bin (P2, T1, m1):")
print(f3d[(f3d.units == "P2") & (f3d.trials == "T1") & (f3d.bin_um == 100) & (f3d.modulated == "m1")].to_string(index=False))
units trials bin_um modulated bins bins_ok max_ratio P1 T1 100 m1 12 12 0.205 P1 T1 100 m2 Student 12 10 0.205 P1 T1 100 m2 Welch 12 11 0.205 P1 T1 200 m1 7 7 0.205 P1 T1 200 m2 Student 7 6 0.205 P1 T1 200 m2 Welch 7 6 0.205 P1 T2 100 m1 12 12 0.163 P1 T2 100 m2 Student 12 9 0.163 P1 T2 100 m2 Welch 12 11 0.163 P1 T2 200 m1 7 7 0.163 P1 T2 200 m2 Student 7 5 0.163 P1 T2 200 m2 Welch 7 6 0.163 P2 T1 100 m1 12 12 0.205 P2 T1 100 m2 Student 12 10 0.205 P2 T1 100 m2 Welch 12 11 0.205 P2 T1 200 m1 7 7 0.205 P2 T1 200 m2 Student 7 6 0.205 P2 T1 200 m2 Welch 7 6 0.205 P2 T2 100 m1 12 12 0.163 P2 T2 100 m2 Student 12 9 0.163 P2 T2 100 m2 Welch 12 11 0.163 P2 T2 200 m1 7 7 0.163 P2 T2 200 m2 Student 7 5 0.163 P2 T2 200 m2 Welch 7 6 0.163 Bins where inhibited do not outnumber activated: units trials bin_um modulated depth_from_um neurons inhibited activated rate_ratio inh_gt_act P1 T1 100 m2 Student 1100 1 0 0 0.000 False P1 T1 100 m2 Student 1200 1 0 0 0.000 False P1 T1 200 m2 Student 1200 1 0 0 0.000 False P1 T1 100 m2 Welch 1200 1 0 0 0.000 False P1 T1 200 m2 Welch 1200 1 0 0 0.000 False P1 T2 100 m2 Student 100 4 0 0 0.163 False P1 T2 100 m2 Student 1100 1 0 0 0.000 False P1 T2 100 m2 Student 1200 1 0 0 0.000 False P1 T2 200 m2 Student 0 4 0 0 0.163 False P1 T2 200 m2 Student 1200 1 0 0 0.000 False P1 T2 100 m2 Welch 1200 1 0 0 0.000 False P1 T2 200 m2 Welch 1200 1 0 0 0.000 False P2 T1 100 m2 Student 1100 1 0 0 0.000 False P2 T1 100 m2 Student 1200 1 0 0 0.000 False P2 T1 200 m2 Student 1200 1 0 0 0.000 False P2 T1 100 m2 Welch 1200 1 0 0 0.000 False P2 T1 200 m2 Welch 1200 1 0 0 0.000 False P2 T2 100 m2 Student 100 4 0 0 0.163 False P2 T2 100 m2 Student 1100 1 0 0 0.000 False P2 T2 100 m2 Student 1200 1 0 0 0.000 False P2 T2 200 m2 Student 0 4 0 0 0.163 False P2 T2 200 m2 Student 1200 1 0 0 0.000 False P2 T2 100 m2 Welch 1200 1 0 0 0.000 False P2 T2 200 m2 Welch 1200 1 0 0 0.000 False Depth (-unit_y, um) of these units: range 101 to 1212 Per 100 um bin (P2, T1, m1): units trials bin_um modulated depth_from_um neurons inhibited activated rate_ratio inh_gt_act P2 T1 100 m1 100 4 3 1 0.205 True P2 T1 100 m1 200 12 12 0 0.020 True P2 T1 100 m1 300 20 20 0 0.047 True P2 T1 100 m1 400 29 29 0 0.012 True P2 T1 100 m1 500 43 43 0 0.029 True P2 T1 100 m1 600 53 52 0 0.012 True P2 T1 100 m1 700 46 44 1 0.026 True P2 T1 100 m1 800 31 30 0 0.011 True P2 T1 100 m1 900 13 13 0 0.005 True P2 T1 100 m1 1000 8 8 0 0.026 True P2 T1 100 m1 1100 1 1 0 0.000 True P2 T1 100 m1 1200 1 1 0 0.000 True
Result: blocked (methods underspecified). Counting by the sign of the change (m1), inhibited
neurons outnumber activated ones in every depth bin under every reading (12 of 12 bins of 100 um,
7 of 7 of 200 um), and the population rate under photoinhibition is at most 0.205 of control in
any bin. Counting significant changes (m2), 1 to 3 bins fail with Student's test and 1 with Welch's,
each holding 1 to 4 neurons with no significant change in either direction (0 against 0). The
criterion asks every reading to agree, so the claim is blocked as it directs. The reduction itself
is seen at every depth sampled (101 to 1212 um of -unit_y). The files describe unit_y only as
"y-coordinate of this unit (mm)", so whether it is depth below the pia is not stated.
fig3b-selectivity-types-equally-inhibited¶
ALM neurons with preparatory activity, peri-movement selectivity or both were equally inhibited by thalamus photoinhibition.
A claim of absence, on the paper's test: two-sided t-tests between each pair of the three groups, measures (a) rate ratio and (b) rate difference, for the neurons of fig3-alm-collapse. Shown under every selectivity reading, both trial readings and P1, P2 (Student's t-test for the group comparison; Welch shown as a count).
rows = []
for p in ("P1", "P2"):
for tname in T_READ:
eff = F3[(p, tname, True)]
for k in SEL:
kind = SEL[k].loc[eff.index, "kind"]
for meas in ("ratio", "delta"):
g = {n: eff[meas][kind == n].dropna() for n in ("prep_only", "peri_only", "both")}
ps = {f"{a}-{b}": stats.ttest_ind(g[a], g[b]).pvalue for a, b in (("prep_only", "peri_only"), ("prep_only", "both"), ("peri_only", "both"))}
pw = [stats.ttest_ind(g[a], g[b], equal_var=False).pvalue for a, b in (("prep_only", "peri_only"), ("prep_only", "both"), ("peri_only", "both"))]
rows.append({"units": p, "trials": tname, "sel_reading": k, "measure": meas, **{f"n_{n}": len(v) for n, v in g.items()},
**{f"mean_{n}": round(v.mean(), 3) for n, v in g.items()}, **{f"p_{a}": round(v, 4) for a, v in ps.items()},
"min_p": round(min(ps.values()), 4), "min_p_welch": round(min(pw), 4)})
f3b = pd.DataFrame(rows)
print(f3b[f3b.sel_reading == "control/R1/Student/c1"].to_string(index=False))
agg = f3b.groupby(["units", "trials", "measure"]).agg(readings=("min_p", "size"), any_pair_p_lt_05=("min_p", lambda x: int((x < 0.05).sum())),
min_p=("min_p", "min"), max_min_p=("min_p", "max"),
welch_lt_05=("min_p_welch", lambda x: int((x < 0.05).sum()))).reset_index()
print("\nOver the 16 selectivity readings:")
print(agg.to_string(index=False))
print("Readings (all combinations) where a pair differs at p < 0.05 under both measures:",
int((f3b.pivot_table(index=["units", "trials", "sel_reading"], columns="measure", values="min_p") < 0.05).all(axis=1).sum()),
"of", len(f3b) // 2)
units trials sel_reading measure n_prep_only n_peri_only n_both mean_prep_only mean_peri_only mean_both p_prep_only-peri_only p_prep_only-both p_peri_only-both min_p min_p_welch P1 T1 control/R1/Student/c1 ratio 51 49 81 0.133 0.042 0.023 0.1266 0.0158 0.3636 0.0158 0.0527 P1 T1 control/R1/Student/c1 delta 51 50 81 -4.291 -1.578 -7.035 0.0050 0.0598 0.0000 0.0000 0.0000 P1 T2 control/R1/Student/c1 ratio 49 48 81 0.132 0.048 0.023 0.2678 0.0574 0.4035 0.0574 0.1276 P1 T2 control/R1/Student/c1 delta 49 49 81 -4.293 -1.636 -7.096 0.0075 0.0597 0.0000 0.0000 0.0000 P2 T1 control/R1/Student/c1 ratio 53 54 99 0.128 0.045 0.024 0.1380 0.0104 0.2652 0.0104 0.0550 P2 T1 control/R1/Student/c1 delta 53 55 99 -4.447 -1.632 -6.811 0.0027 0.0781 0.0000 0.0000 0.0000 P2 T2 control/R1/Student/c1 ratio 50 53 99 0.129 0.056 0.019 0.3142 0.0316 0.1696 0.0316 0.1130 P2 T2 control/R1/Student/c1 delta 50 54 99 -4.546 -1.675 -6.834 0.0032 0.0969 0.0000 0.0000 0.0000 Over the 16 selectivity readings: units trials measure readings any_pair_p_lt_05 min_p max_min_p welch_lt_05 P1 T1 delta 16 16 0.0000 0.0009 16 P1 T1 ratio 16 4 0.0158 0.4567 0 P1 T2 delta 16 16 0.0000 0.0011 16 P1 T2 ratio 16 2 0.0244 0.6575 0 P2 T1 delta 16 16 0.0000 0.0006 16 P2 T1 ratio 16 4 0.0104 0.5181 0 P2 T2 delta 16 16 0.0000 0.0007 16 P2 T2 ratio 16 3 0.0196 0.7587 0 Readings (all combinations) where a pair differs at p < 0.05 under both measures: 13 of 64
Result: blocked (methods underspecified). Under the first selectivity reading both measures find a difference between groups: the rate difference (p < 0.0001 between peri-movement-only and both) and the rate ratio (p = 0.016 with P1, 0.010 with P2, T1). Over all readings the difference measure finds one in all 64 combinations and the ratio measure in 13, so the two measures disagree in most readings, which the criterion calls methods underspecified. No reading supports equal inhibition under the difference measure.
fig3f-thalamus-silenced¶
Strong photoinhibition in VGAT-ChR2-EYFP mice abolished thalALM activity nearly completely, to 2% of control.
Thalamic units of the VGAT-ChR2-EYFP thalamic sessions (thalamus photostimulus in the delay epoch; the files give 2.23 mW). Readings (a) all putative thalamic units, (b) those in VM/VAL (L2); 20 to 120 ms; T1, T2; Q1 to Q3. The session photostimulated in the sample epoch has no delay photostimulus trials and drops out under the 5-trial rule.
vg_th = {p: d[d.genotype == "VGAT-ChR2-EYFP"] for p, d in THP.items()}
vg_th_all = {"all units": thal[thal.genotype == "VGAT-ChR2-EYFP"]}
f3f, F3F = effect_table({**vg_th, **vg_th_all}, "Thalamus:delay:2.23")
for q in ("Q1", "Q2", "Q3"):
f3f[f"within_tol_{q}"] = ((f3f[q] - 2).abs() <= 0.2) & ((f3f.n - 148).abs() <= 15)
print("(a) putative thalamic units (and all units):")
print(f3f.to_string(index=False))
print("(b) VM/VAL (L2): VGAT thalamic units within 0.4 mm of the centre:", int((vg_th_all["all units"].vmval_d < 0.4).sum()),
"| distance range (mm):", round(vg_th_all["all units"].vmval_d.min(), 2), "to", round(vg_th_all["all units"].vmval_d.max(), 2))
print("Paper: 2% of control, n = 148")
print("Any reading within tolerance:", bool(f3f[[c for c in f3f if c.startswith("within_tol")]].any(axis=None)))
d = F3F[("all units", "T1", True)]
print("Ratio in the window 20-120 ms, all units T1, per unit: quantiles 10/50/90%", np.round(np.nanpercentile(d.ratio, [10, 50, 90]), 3),
"| units", len(d), "| per mouse Q1:",
{s: round(100 * g.rate_pi.mean() / g.rate_ctrl.mean(), 1) for s, g in d.join(units[["subject"]]).groupby("subject")})
(a) putative thalamic units (and all units):
units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice within_tol_Q1 within_tol_Q2 within_tol_Q3
P1 T1 Student 93 93 0 0 83 0 1.11 1.40 0.0 0 2 False False False
P1 T1 Welch 93 93 0 0 89 0 1.11 1.40 0.0 0 2 False False False
P1 T2 Student 89 89 0 0 69 0 0.91 1.03 0.0 0 2 False False False
P1 T2 Welch 89 89 0 0 84 0 0.91 1.03 0.0 0 2 False False False
P2 T1 Student 113 113 0 0 101 0 1.11 1.23 0.0 0 2 False False False
P2 T1 Welch 113 113 0 0 109 0 1.11 1.23 0.0 0 2 False False False
P2 T2 Student 109 109 0 0 86 0 0.92 0.91 0.0 0 2 False False False
P2 T2 Welch 109 109 0 0 103 0 0.92 0.91 0.0 0 2 False False False
all units T1 Student 134 133 1 0 120 1 18.17 7.64 0.0 0 2 False False False
all units T1 Welch 134 133 1 0 129 1 18.17 7.64 0.0 0 2 False False False
all units T2 Student 130 129 1 0 102 1 18.43 7.48 0.0 0 2 False False False
all units T2 Welch 130 129 1 0 123 1 18.43 7.48 0.0 0 2 False False False
(b) VM/VAL (L2): VGAT thalamic units within 0.4 mm of the centre: 0 | distance range (mm): 0.46 to 1.37
Paper: 2% of control, n = 148
Any reading within tolerance: False
Ratio in the window 20-120 ms, all units T1, per unit: quantiles 10/50/90% [0. 0. 0.023] | units 134 | per mouse Q1: {'anm00313106': np.float64(1.8), 'anm00313111': np.float64(35.2)}
Result: not reproduced. Putative thalamic units of the VGAT sessions fall to 1.1% (T1) or 0.9% (T2) of control (Q1; Q2 0.9 to 1.4%), on 113 units (P2) or 93 (P1) against 148 +/- 15; none is activated, and 101 of 113 are significantly inhibited (P2, T1, Student). With all units (134 under T1) the count is within tolerance but narrow units raise Q1 to 18%. The silencing is nearly complete, as the paper says, but the 2% and the unit count are not both within tolerance under any reading. No VGAT thalamic unit lies within 0.4 mm of the L2 centre, so reading (b) has no units.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
vgat-thalamus-reduction¶
In VGAT-ChR2-EYFP mice, thalamus photoinhibition reduced activity 0.5 to 1.1 mm from the fibre tip to 15.9% of control.
Putative thalamic units of the VGAT thalamic sessions, at 0.5 to 1.1 mm from the fibre. The files give one photostimulus site for every session (AP -1.5, ML 0.9, DV -4.1 mm), a nominal coordinate; distance from it is computed in 3 dimensions and in the coronal plane (ML, DV). Windows W1 (whole delay epoch) and W2 (20 to 120 ms), T1, T2, Q1 to Q3.
fib = np.array([0.9, -4.1, -1.5])
rows = []
for p, d in {**vg_th, **vg_th_all}.items():
d3 = np.sqrt((d.ml.abs() - fib[0]) ** 2 + (d.dv - fib[1]) ** 2 + (d.ap - fib[2]) ** 2)
d2 = np.hypot(d.ml.abs() - fib[0], d.dv - fib[1])
for dname, dist in (("3D", d3), ("coronal", d2)):
sub = d[(dist >= 0.5) & (dist <= 1.1)]
groups = np.select([dist < 0.7, dist < 0.9], ["0.6", "0.8"], "1.0")
for wname, win, mx in (("W1", "delay", None), ("W2", "pw", None), ("variant: W1, 1.3 s delays only", "delay", 1.35)):
for tname, oc in T_READ.items():
df = loop.photo_effect(sessions, sub, "Thalamus:delay:2.23", oc, win, 5, True, max_delay=mx)
r = loop.ratio_readings(df)
rows.append({"units": p, "distance": dname, "window": wname, "trials": tname, "n": len(df),
"Q1": round(r["Q1"], 1), "Q2": round(r["Q2"], 1), "Q3": round(r["Q3"], 1),
"n_by_0.6/0.8/1.0": tuple(int(((groups == g) & (dist >= 0.5) & (dist <= 1.1)).sum()) for g in ("0.6", "0.8", "1.0"))})
vg2 = pd.DataFrame(rows)
vg2["within_tol"] = vg2[["Q1", "Q2", "Q3"]].sub(15.9).abs().le(9.3).any(axis=1)
print(vg2.to_string(index=False))
print("Paper: 15.9 +/- 9.3 % (n = 34, 42, 38 at 0.6, 0.8, 1.0 mm)")
print("Readings within tolerance:", int(vg2[vg2.window.isin(["W1", "W2"])].within_tol.sum()), "of", int(vg2.window.isin(["W1", "W2"]).sum()),
"| variant rows within tolerance:", int(vg2[~vg2.window.isin(["W1", "W2"])].within_tol.sum()), "of", int((~vg2.window.isin(["W1", "W2"])).sum()))
# delay lengths in these sessions, and the ratio across the delay in 100 ms bins
vs = sorted(set(vg_th_all["all units"].session))
vt = pd.concat([sessions[i]["trials"] for i in vs])
k = vt.response.isin(loop.KEEP)
print("\nVGAT thalamic sessions, kept trials: delay length (s) quantiles 0/25/50/75/90/100%, photostimulus trials",
np.round(np.percentile(vt.delay_s[k & (vt.stim == "Thalamus:delay:2.23")], [0, 25, 50, 75, 90, 100]), 2),
"| control trials", np.round(np.percentile(vt.delay_s[k & (vt.stim == "none")], [0, 25, 50, 75, 90, 100]), 2))
print("Fraction of photostimulus trials with a delay of 1.3 s:", round(float(np.mean(np.abs(vt.delay_s[k & (vt.stim == "Thalamus:delay:2.23")] - 1.3) < 0.05)), 3))
d = vg_th["P2"]
d3 = np.sqrt((d.ml.abs() - fib[0]) ** 2 + (d.dv - fib[1]) ** 2 + (d.ap - fib[2]) ** 2)
sub = d[(d3 >= 0.5) & (d3 <= 1.1)]
tcb = loop.TC_SPAN[0] + loop.BIN_S * np.arange(int(round((loop.TC_SPAN[1] - loop.TC_SPAN[0]) / loop.BIN_S)))
binrows = []
for dname, lo, hi in (("1.3 s delay", 0, 1.35), ("delay > 1.35 s", 1.35, 99)):
rc, rp = [], []
for r in sub.itertuples():
s = sessions[r.session]
t = s["trials"]
m = t.response.isin(loop.KEEP).values & s["valid"][r.unit] & t.tc_ok.values & (t.delay_s.values > lo) & (t.delay_s.values <= hi)
c_, p_ = m & (t.stim == "none").values, m & (t.stim == "Thalamus:delay:2.23").values
if p_.sum() < 1 or c_.sum() < 1:
continue
rc.append(s["tc"][r.unit][c_].mean(0) / loop.BIN_S)
rp.append(s["tc"][r.unit][p_].mean(0) / loop.BIN_S)
if not rc:
binrows.append({"trials": dname, "units": 0})
continue
rc, rp = np.array(rc), np.array(rp)
q1 = 100 * rp.mean(0) / rc.mean(0)
binrows.append({"trials": dname, "units": len(rc), **{f"{x:.1f}": round(float(q1[np.argmin(np.abs(tcb - x))]), 1) for x in np.arange(1.0, 4.05, 0.2)}})
print("\nPutative thalamic units (P2) 0.5 to 1.1 mm (3D) from the site, T1: photostimulus / control population rate (Q1, %) in the 100 ms bin"
" starting at the given time after pole onset (delay onset 1.3 s):")
print(pd.DataFrame(binrows).to_string(index=False))
d = vg_th_all["all units"]
print("Distance of VGAT thalamic units from the site (3D, mm): quantiles 0/50/100%",
np.round(np.percentile(np.sqrt((d.ml.abs() - fib[0]) ** 2 + (d.dv - fib[1]) ** 2 + (d.ap - fib[2]) ** 2), [0, 50, 100]), 2))
units distance window trials n Q1 Q2 Q3 n_by_0.6/0.8/1.0 within_tol
P1 3D W1 T1 66 18.4 41.9 18.4 (23, 21, 31) True
P1 3D W1 T2 63 20.3 51.2 19.3 (23, 21, 31) True
P1 3D W2 T1 66 1.5 2.0 0.0 (23, 21, 31) False
P1 3D W2 T2 63 1.2 1.5 0.0 (23, 21, 31) False
P1 3D variant: W1, 1.3 s delays only T1 66 18.4 41.9 18.4 (23, 21, 31) True
P1 3D variant: W1, 1.3 s delays only T2 63 20.3 51.2 19.3 (23, 21, 31) True
P1 coronal W1 T1 66 18.4 41.9 18.4 (23, 21, 31) True
P1 coronal W1 T2 63 20.3 51.2 19.3 (23, 21, 31) True
P1 coronal W2 T1 66 1.5 2.0 0.0 (23, 21, 31) False
P1 coronal W2 T2 63 1.2 1.5 0.0 (23, 21, 31) False
P1 coronal variant: W1, 1.3 s delays only T1 66 18.4 41.9 18.4 (23, 21, 31) True
P1 coronal variant: W1, 1.3 s delays only T2 63 20.3 51.2 19.3 (23, 21, 31) True
P2 3D W1 T1 79 17.0 37.5 16.8 (24, 24, 42) True
P2 3D W1 T2 76 18.6 45.5 16.5 (24, 24, 42) True
P2 3D W2 T1 79 1.4 1.7 0.0 (24, 24, 42) False
P2 3D W2 T2 76 1.2 1.3 0.0 (24, 24, 42) False
P2 3D variant: W1, 1.3 s delays only T1 79 17.0 37.5 16.8 (24, 24, 42) True
P2 3D variant: W1, 1.3 s delays only T2 76 18.6 45.5 16.5 (24, 24, 42) True
P2 coronal W1 T1 79 17.0 37.5 16.8 (24, 24, 42) True
P2 coronal W1 T2 76 18.6 45.5 16.5 (24, 24, 42) True
P2 coronal W2 T1 79 1.4 1.7 0.0 (24, 24, 42) False
P2 coronal W2 T2 76 1.2 1.3 0.0 (24, 24, 42) False
P2 coronal variant: W1, 1.3 s delays only T1 79 17.0 37.5 16.8 (24, 24, 42) True
P2 coronal variant: W1, 1.3 s delays only T2 76 18.6 45.5 16.5 (24, 24, 42) True
all units 3D W1 T1 87 15.8 34.9 13.4 (26, 26, 48) True
all units 3D W1 T2 84 17.2 42.0 15.0 (26, 26, 48) True
all units 3D W2 T1 87 1.5 1.8 0.0 (26, 26, 48) False
all units 3D W2 T2 84 1.2 1.3 0.0 (26, 26, 48) False
all units 3D variant: W1, 1.3 s delays only T1 87 15.8 34.9 13.4 (26, 26, 48) True
all units 3D variant: W1, 1.3 s delays only T2 84 17.2 42.0 15.0 (26, 26, 48) True
all units coronal W1 T1 87 15.8 34.9 13.4 (26, 26, 48) True
all units coronal W1 T2 84 17.2 42.0 15.0 (26, 26, 48) True
all units coronal W2 T1 87 1.5 1.8 0.0 (26, 26, 48) False
all units coronal W2 T2 84 1.2 1.3 0.0 (26, 26, 48) False
all units coronal variant: W1, 1.3 s delays only T1 87 15.8 34.9 13.4 (26, 26, 48) True
all units coronal variant: W1, 1.3 s delays only T2 84 17.2 42.0 15.0 (26, 26, 48) True
Paper: 15.9 +/- 9.3 % (n = 34, 42, 38 at 0.6, 0.8, 1.0 mm)
Readings within tolerance: 12 of 24 | variant rows within tolerance: 12 of 12
VGAT thalamic sessions, kept trials: delay length (s) quantiles 0/25/50/75/90/100%, photostimulus trials [1.3 1.3 1.3 1.3 1.3 1.31] | control trials [1.3 1.3 1.3 1.3 1.3 1.31]
Fraction of photostimulus trials with a delay of 1.3 s: 1.0
Putative thalamic units (P2) 0.5 to 1.1 mm (3D) from the site, T1: photostimulus / control population rate (Q1, %) in the 100 ms bin starting at the given time after pole onset (delay onset 1.3 s):
trials units 1.0 1.2 1.4 1.6 1.8 2.0 2.2 2.4 2.6 2.8 3.0 3.2 3.4 3.6 3.8 4.0
1.3 s delay 79 104.0 104.0 21.6 23.7 19.4 11.2 10.1 9.1 99.7 103.8 115.7 112.6 116.9 110.4 112.2 105.2
delay > 1.35 s 0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Distance of VGAT thalamic units from the site (3D, mm): quantiles 0/50/100% [0.4 0.98 1.38]
Result: reproduced. Over the whole photostimulus (W1), putative thalamic units 0.5 to 1.1 mm from the fibre site fall to 17.0 to 20.3% (Q1) or 16.5 to 19.3% (Q3) of control, within 15.9 +/- 9.3%; Q2 is 37.5 to 51.2%. In the 20 to 120 ms window (W2) they fall to 1.2 to 1.5% (Q1). Every trial of the VGAT thalamic sessions has a 1.3 s delay, so W1 ends with the delay epoch and holds no time after the photostimulus; restricting W1 to 1.3 s delays changes nothing. In 100 ms bins the ratio is 21.6% in the first bin after 1.4 s, falls to 9 to 11% by the end of the delay and returns to 100% in the first bin of the response epoch: activity, silenced at first (W2), partly recovers during the photostimulus. 63 to 79 units qualify against the paper's 114, the site is one nominal coordinate for all sessions, so the distances are approximate, and the files give the power as 2.23 mW where the paper gives 10 mW.
fig3-onset-latencies¶
Photoinhibition reduced thalALM activity 2.5 ms after photostimulus onset, and ALM spike rates were reduced 8.2 ms after onset, a latency difference consistent with direct thalamocortical drive.
Convention O on population PSTHs (1 ms bins), readings O1 and O2, T1 and T2; the thalamic population of fig3f-thalamus-silenced (putative thalamic units, reading a) and the ALM population of fig3-alm-collapse, both with at least 5 photostimulus trials; SEM from 1000 resamplings of units. P1 and P2.
rng = np.random.default_rng(301)
def onset_rows(label, sel, stims, direction=-1, flip=None):
rows = []
if len(sel) == 0:
return [{"population": label, "n": 0}]
for tname, oc in T_READ.items():
c, p = loop.population_psth(sessions, sel, stims, oc)
if flip is not None:
p = np.where(flip[:, None], 2 * c - p, p)
for rd in ("O1", "O2"):
est, sem, nan = loop.bootstrap_onset(c, p, rd, rng, 1000, direction)
rows.append({"population": label, "n": len(sel), "trials": tname, "reading": rd, "onset_ms": est, "sem_ms": round(sem, 2),
"boot_no_onset": nan})
return rows
rows = []
for p in ("P1", "P2"):
th_units = vg_th[p].loc[F3F[(p, "T1", True)].index]
alm_units = vg_alm[p].loc[F3[(p, "T1", True)].index]
rows += onset_rows(f"thalamus VGAT {p}", th_units, "Thalamus:delay:2.23")
rows += onset_rows(f"ALM VGAT {p}", alm_units, "Thalamus:delay:10")
on3 = pd.DataFrame(rows)
print(on3.to_string(index=False))
print("Paper: thalamus 2.5 +/- 0.8 ms, ALM 8.2 +/- 1.5 ms")
w = on3.pivot_table(index=["trials", "reading"], columns="population", values="onset_ms")
for p in ("P1", "P2"):
ok = (w[f"thalamus VGAT {p}"].sub(2.5).abs() <= 0.8) & (w[f"ALM VGAT {p}"].sub(8.2).abs() <= 1.5) & (w[f"ALM VGAT {p}"] > w[f"thalamus VGAT {p}"])
print(p, "readings with both onsets within tolerance and ALM later:", ok[ok].index.tolist())
# Reliability of the onset rule (convention O as implemented): photostimulus PSTHs simulated from each unit's own control
# PSTH and number of photostimulus trials, with no effect (any onset is false) or with a step to 20% of control at 8 ms.
rng_c = np.random.default_rng(302)
rel = []
for nm, sel, st in (("thalamus VGAT P2", vg_th["P2"].loc[F3F[("P2", "T1", True)].index], "Thalamus:delay:2.23"),
("ALM VGAT P2", vg_alm["P2"].loc[F3[("P2", "T1", True)].index], "Thalamus:delay:10")):
cu, _, npi = loop.population_psth(sessions, sel, st, loop.KEEP, with_n=True)
for rd in ("O1", "O2"):
nul = loop.onset_reliability(cu, npi, rd, rng_c, 200)
stp = loop.onset_reliability(cu, npi, rd, rng_c, 200, step_ms=8, step_ratio=0.2)
rel.append({"population": nm, "reading": rd, "null: runs with an onset": float(np.mean(~np.isnan(nul))),
"null: onset < 10 ms": float(np.mean(nul < 10)), "null: onset < 20 ms": float(np.mean(nul < 20)),
"step at 8 ms: median onset": float(np.nanmedian(stp)), "step: onset < 8 ms": float(np.mean(stp < 8)),
"step: onset 6.5 to 9.5 ms": float(np.mean((stp >= 6.5) & (stp <= 9.5)))})
print("\nOnset rule on simulated data (200 runs each; T1 populations):")
print(pd.DataFrame(rel).round(3).to_string(index=False))
t_ms = loop.psth_time_ms()
c, pp = loop.population_psth(sessions, vg_th["P2"].loc[F3F[("P2", "T1", True)].index], "Thalamus:delay:2.23")
c2, pp2 = loop.population_psth(sessions, vg_alm["P2"].loc[F3[("P2", "T1", True)].index], "Thalamus:delay:10")
fig, axs = plt.subplots(1, 2, figsize=(9, 2.8))
for ax, (cc, ppp, nm) in zip(axs, ((c, pp, "thalamus (VGAT)"), (c2, pp2, "ALM (VGAT)"))):
k = (t_ms > -20) & (t_ms < 40)
ax.plot(t_ms[k], np.nanmean(cc, 0)[k], "k", label="control")
ax.plot(t_ms[k], np.nanmean(ppp, 0)[k], "b", label="photostim.")
ax.set_title(nm, fontsize=9)
ax.set_xlabel("ms from photostimulus onset")
axs[0].set_ylabel("spikes/s (1 ms bins)")
axs[0].legend(fontsize=7)
plt.show()
print("Plotted range (spikes/s): thalamus", round(np.nanmean(pp, 0)[k].min(), 1), "to", round(np.nanmean(c, 0)[k].max(), 1),
"| ALM", round(np.nanmean(pp2, 0)[k].min(), 1), "to", round(np.nanmean(c2, 0)[k].max(), 1))
population n trials reading onset_ms sem_ms boot_no_onset
thalamus VGAT P1 93 T1 O1 2.0 1.24 0
thalamus VGAT P1 93 T1 O2 2.0 1.08 0
thalamus VGAT P1 93 T2 O1 2.0 1.53 0
thalamus VGAT P1 93 T2 O2 2.0 1.49 0
ALM VGAT P1 234 T1 O1 0.0 4.31 0
ALM VGAT P1 234 T1 O2 9.0 3.68 0
ALM VGAT P1 234 T2 O1 0.0 4.24 0
ALM VGAT P1 234 T2 O2 6.0 3.31 0
thalamus VGAT P2 113 T1 O1 1.0 0.96 0
thalamus VGAT P2 113 T1 O2 2.0 0.96 0
thalamus VGAT P2 113 T2 O1 2.0 0.95 0
thalamus VGAT P2 113 T2 O2 2.0 1.20 0
ALM VGAT P2 261 T1 O1 0.0 3.72 0
ALM VGAT P2 261 T1 O2 7.0 3.16 0
ALM VGAT P2 261 T2 O1 6.0 3.08 0
ALM VGAT P2 261 T2 O2 6.0 2.51 0
Paper: thalamus 2.5 +/- 0.8 ms, ALM 8.2 +/- 1.5 ms
P1 readings with both onsets within tolerance and ALM later: [('T1', 'O2')]
P2 readings with both onsets within tolerance and ALM later: [('T1', 'O2')]
Onset rule on simulated data (200 runs each; T1 populations):
population reading null: runs with an onset null: onset < 10 ms null: onset < 20 ms step at 8 ms: median onset step: onset < 8 ms step: onset 6.5 to 9.5 ms
thalamus VGAT P2 O1 1.0 0.355 0.605 8.0 0.255 0.750
thalamus VGAT P2 O2 1.0 0.380 0.680 8.0 0.210 0.825
ALM VGAT P2 O1 1.0 0.475 0.690 8.0 0.430 0.595
ALM VGAT P2 O2 1.0 0.300 0.510 8.0 0.185 0.835
Plotted range (spikes/s): thalamus 0.0 to 12.1 | ALM 0.0 to 5.3
Result: reproduced. Under reading O2 with correct and incorrect trials (T1), the thalamic onset is 2.0 ms (SEM 1.08 with P1, 0.96 with P2) and the ALM onset 9.0 ms (P1, SEM 3.68) or 7.0 ms (P2, SEM 3.16), within 2.5 +/- 0.8 and 8.2 +/- 1.5 ms, with ALM later. The other readings fail: under O1 the ALM onset is 0 ms in three of four cases (the first 1 ms bin already lies 3 SD below control), and under T2 with O2 it is 6 ms. The pass rests on one reading of four and the rule is fragile: on photostimulus PSTHs simulated from each unit's control PSTH with no effect, it still reports an onset in every run, below 10 ms in 30 to 48% of them; with a simulated step at 8 ms it reports an onset before 8 ms in 18 to 43% of runs and within 6.5 to 9.5 ms in 60 to 84%. The ALM onset of 0 ms under O1 is such a false onset. The bootstrap SEMs of the ALM onset under T1 and O2 (3.2 and 3.7 ms) are twice the paper's 1.5 ms.
fig6c-vmval-reduced¶
ALM photoinhibition decreased the activity of VM/VAL neurons to 25% of control, inhibiting 190 of 201 neurons (139 significantly) and significantly activating none.
VM/VAL putative thalamic units (L2) of the PV-Cre x Ai32 thalamic sessions, ALM photoinhibition at 1.5 mW in the delay epoch, at least 5 trials; T1, T2, Q1 to Q3, Student and Welch, P1 and P2. P3 (all VM/VAL units regardless of width) as a further unit reading. Variant: all ALM photoinhibition powers (1.5 and 0.79 mW).
pv_vm = {p: d[(d.genotype == "Ai32 x PV-Cre")] for p, d in VM26.items()}
f6, F6 = effect_table(pv_vm, "ALM:delay:1.5")
for q in ("Q1", "Q2", "Q3"):
f6[f"within_tol_{q}"] = ((f6[q] - 25).abs() <= 2.5) & ((f6.n - 201).abs() <= 20) & ((f6.inhibited - 190).abs() <= 19) & \
((f6.activated - 11).abs() <= 1.1) & ((f6.sig_inhibited - 139).abs() <= 14) & (f6.sig_activated == 0)
print(f6.to_string(index=False))
print("Paper: 25% of control; 201 neurons, 190 inhibited, 11 activated, 139 significantly inhibited, 0 significantly activated")
print("Readings within tolerance on all values:", int(f6[[c for c in f6 if c.startswith("within_tol")]].any(axis=1).sum()), "of", len(f6))
part6 = (f6.Q1 < 100) & (f6.inhibited > f6.activated) & (f6.sig_activated == 0)
print("Partial condition in every reading:", bool(part6.all()), "| readings where it holds, by units:", f6[part6].groupby("units").size().to_dict())
v6, _ = effect_table(pv_vm, ["ALM:delay:1.5", "ALM:delay:0.79"])
print("\nVariant, all ALM powers:")
print(v6.to_string(index=False))
d = F6[("P2", "T1", True)].join(units[["subject"]])
print("\nBy mouse (P2, T1, Student):")
print(d.groupby("subject").agg(n=("delta", "size"), inhibited=("delta", lambda x: int((x < 0).sum())), sig_inh=("sig", "sum"),
rate_ctrl=("rate_ctrl", "mean"), rate_pi=("rate_pi", "mean")).round(2).to_string())
units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice within_tol_Q1 within_tol_Q2 within_tol_Q3
P1 T1 Student 15 12 3 0 7 0 50.022751 57.536102 45.518208 0 6 False False False
P1 T1 Welch 15 12 3 0 7 0 50.022751 57.536102 45.518208 0 6 False False False
P1 T2 Student 15 11 4 0 6 0 43.019531 51.445492 37.904472 0 6 False False False
P1 T2 Welch 15 11 4 0 8 0 43.019531 51.445492 37.904472 0 6 False False False
P2 T1 Student 172 151 21 0 99 2 47.450802 46.522335 36.436440 0 7 False False False
P2 T1 Welch 172 151 21 0 105 2 47.450802 46.522335 36.436440 0 7 False False False
P2 T2 Student 163 136 27 0 77 1 46.173172 45.253078 29.946522 0 7 False False False
P2 T2 Welch 163 136 27 0 102 1 46.173172 45.253078 29.946522 0 7 False False False
P3 T1 Student 201 173 28 0 107 5 55.930027 51.027489 40.000000 0 7 False False False
P3 T1 Welch 201 173 28 0 117 4 55.930027 51.027489 40.000000 0 7 False False False
P3 T2 Student 190 157 33 0 85 2 53.376141 49.018391 36.960785 0 7 False False False
P3 T2 Welch 190 157 33 0 111 2 53.376141 49.018391 36.960785 0 7 False False False
Paper: 25% of control; 201 neurons, 190 inhibited, 11 activated, 139 significantly inhibited, 0 significantly activated
Readings within tolerance on all values: 0 of 12
Partial condition in every reading: False | readings where it holds, by units: {'P1': 4}
Variant, all ALM powers:
units trials test n inhibited activated unchanged sig_inhibited sig_activated Q1 Q2 Q3 n_ratio_undefined mice
P1 T1 Student 15 11 4 0 6 0 61.777534 68.751122 65.024635 0 6
P1 T1 Welch 15 11 4 0 6 0 61.777534 68.751122 65.024635 0 6
P1 T2 Student 15 10 5 0 6 0 55.967133 63.710762 54.321808 0 6
P1 T2 Welch 15 10 5 0 7 0 55.967133 63.710762 54.321808 0 6
P2 T1 Student 173 148 25 0 87 2 57.693043 56.443779 51.241833 0 7
P2 T1 Welch 173 148 25 0 91 2 57.693043 56.443779 51.241833 0 7
P2 T2 Student 170 140 30 0 71 1 58.263138 56.436657 52.397762 0 7
P2 T2 Welch 170 140 30 0 85 1 58.263138 56.436657 52.397762 0 7
P3 T1 Student 203 170 33 0 93 5 64.935181 60.717876 59.815323 0 7
P3 T1 Welch 203 170 33 0 101 4 64.935181 60.717876 59.815323 0 7
P3 T2 Student 199 162 37 0 76 2 64.274574 59.808064 55.367233 0 7
P3 T2 Welch 199 162 37 0 91 2 64.274574 59.808064 55.367233 0 7
By mouse (P2, T1, Student):
n inhibited sig_inh rate_ctrl rate_pi
subject
anm00234218 20 19 15 11.89 3.73
anm00239123 2 1 0 10.01 8.34
anm00260487 52 47 40 9.88 4.00
anm00260488 28 21 12 10.26 6.87
anm00260490 26 21 9 10.13 5.99
anm00260491 1 1 1 6.60 3.62
anm00264943 43 41 24 11.82 5.13
Result: not reproduced. In 172 VM/VAL putative units (P2, T1; 15 with P1) from 7 mice, ALM photoinhibition at 1.5 mW lowers activity to 47.5% (Q1), 46.5% (Q2) or 36.4% (Q3) of control, against 25%: 151 are inhibited, 21 activated, 99 (Student) or 105 (Welch) significantly inhibited and 2 significantly activated. With P3 (all VM/VAL units, T1) 201 units qualify, the paper's n, and 173 are inhibited (190 +/- 19), but activity falls only to 55.9% (Q1) or 40.0% (Q3), 28 are activated and 4 or 5 significantly activated. No reading puts every value within tolerance, and the partial condition fails because under P2 and P3 1 to 5 units are significantly activated (it holds in the 4 P1 readings only). The reduction is present, about half as deep as the paper's.
fig6b-selectivity-types-equally-inhibited¶
VM/VAL neurons selective during the sample, delay or response epochs were equally inhibited by ALM photoinhibition.
Neurons of fig6c-vmval-reduced. Groups (a) preparatory only, peri-movement only, both; (b) selective in the sample, the delay, the response epoch (overlapping). Measures ratio and difference; two-sided t-tests between each pair; every selectivity reading, T1 and T2, P1, P2 and P3.
rows = []
for p in ("P1", "P2", "P3"):
for tname in T_READ:
eff = F6[(p, tname, True)]
for k in SEL:
st = SEL[k].loc[eff.index]
grp_a = {n: st.kind == n for n in ("prep_only", "peri_only", "both")}
grp_b = {"sample": st.p_sample < 0.05, "delay": st.p_delay < 0.05, "response": st.p_resp < 0.05}
for gname, grp in (("a", grp_a), ("b", grp_b)):
for meas in ("ratio", "delta"):
g = {n: eff[meas][m].dropna() for n, m in grp.items()}
names = list(g)
ps = [stats.ttest_ind(g[x], g[y]).pvalue if min(len(g[x]), len(g[y])) > 1 else np.nan
for i, x in enumerate(names) for y in names[i + 1:]]
rows.append({"units": p, "trials": tname, "sel_reading": k, "groups": gname, "measure": meas,
"n": tuple(len(v) for v in g.values()), "means": tuple(round(v.mean(), 2) for v in g.values()),
"min_p": np.nanmin(ps) if np.isfinite(ps).any() else np.nan})
f6b = pd.DataFrame(rows)
print(f6b[(f6b.sel_reading == "control/R1/Student/c1")].to_string(index=False))
agg = f6b.groupby(["units", "trials", "groups", "measure"]).agg(readings=("min_p", "size"), pair_p_lt_05=("min_p", lambda x: int((x < 0.05).sum())),
min_p=("min_p", "min")).reset_index()
print("\nOver the 16 selectivity readings:")
print(agg.round(4).to_string(index=False))
units trials sel_reading groups measure n means min_p P1 T1 control/R1/Student/c1 a ratio (3, 5, 1) (0.21, 0.83, 0.37) 0.140760 P1 T1 control/R1/Student/c1 a delta (3, 5, 1) (-9.94, -2.36, -5.02) 0.098936 P1 T1 control/R1/Student/c1 b ratio (2, 4, 6) (0.09, 0.25, 0.75) 0.129032 P1 T1 control/R1/Student/c1 b delta (2, 4, 6) (-10.64, -8.71, -2.8) 0.100106 P1 T2 control/R1/Student/c1 a ratio (3, 5, 1) (0.16, 0.84, 0.38) 0.073640 P1 T2 control/R1/Student/c1 a delta (3, 5, 1) (-12.58, -2.15, -4.47) 0.035060 P1 T2 control/R1/Student/c1 b ratio (2, 4, 6) (0.1, 0.21, 0.76) 0.070154 P1 T2 control/R1/Student/c1 b delta (2, 4, 6) (-11.6, -10.55, -2.54) 0.045526 P2 T1 control/R1/Student/c1 a ratio (29, 53, 31) (0.32, 0.54, 0.37) 0.042351 P2 T1 control/R1/Student/c1 a delta (29, 53, 31) (-7.83, -4.17, -7.24) 0.002698 P2 T1 control/R1/Student/c1 b ratio (33, 40, 84) (0.38, 0.35, 0.48) 0.170284 P2 T1 control/R1/Student/c1 b delta (33, 40, 84) (-6.77, -8.02, -5.3) 0.015391 P2 T2 control/R1/Student/c1 a ratio (28, 49, 30) (0.3, 0.56, 0.38) 0.034153 P2 T2 control/R1/Student/c1 a delta (28, 49, 30) (-8.41, -4.03, -7.26) 0.002514 P2 T2 control/R1/Student/c1 b ratio (32, 39, 79) (0.39, 0.35, 0.49) 0.162201 P2 T2 control/R1/Student/c1 b delta (32, 39, 79) (-6.9, -8.53, -5.26) 0.010743 P3 T1 control/R1/Student/c1 a ratio (30, 63, 39) (0.31, 0.58, 0.48) 0.011654 P3 T1 control/R1/Student/c1 a delta (30, 63, 39) (-7.74, -4.09, -5.92) 0.001411 P3 T1 control/R1/Student/c1 b ratio (35, 49, 102) (0.43, 0.44, 0.54) 0.233433 P3 T1 control/R1/Student/c1 b delta (35, 49, 102) (-6.18, -6.79, -4.79) 0.052089 P3 T2 control/R1/Student/c1 a ratio (29, 58, 38) (0.29, 0.59, 0.48) 0.011077 P3 T2 control/R1/Student/c1 a delta (29, 58, 38) (-8.33, -4.17, -6.13) 0.002264 P3 T2 control/R1/Student/c1 b ratio (34, 48, 96) (0.44, 0.42, 0.54) 0.190564 P3 T2 control/R1/Student/c1 b delta (34, 48, 96) (-6.31, -7.37, -4.94) 0.035557 Over the 16 selectivity readings: units trials groups measure readings pair_p_lt_05 min_p P1 T1 a delta 16 10 0.0013 P1 T1 a ratio 16 8 0.0002 P1 T1 b delta 16 4 0.0294 P1 T1 b ratio 16 0 0.1050 P1 T2 a delta 16 11 0.0002 P1 T2 a ratio 16 9 0.0003 P1 T2 b delta 16 5 0.0235 P1 T2 b ratio 16 1 0.0462 P2 T1 a delta 16 16 0.0022 P2 T1 a ratio 16 7 0.0052 P2 T1 b delta 16 6 0.0154 P2 T1 b ratio 16 0 0.1180 P2 T2 a delta 16 16 0.0011 P2 T2 a ratio 16 8 0.0216 P2 T2 b delta 16 10 0.0107 P2 T2 b ratio 16 0 0.1496 P3 T1 a delta 16 11 0.0005 P3 T1 a ratio 16 7 0.0061 P3 T1 b delta 16 1 0.0450 P3 T1 b ratio 16 0 0.1239 P3 T2 a delta 16 10 0.0003 P3 T2 a ratio 16 8 0.0028 P3 T2 b delta 16 5 0.0328 P3 T2 b ratio 16 0 0.1151
Result: blocked (methods underspecified). With groups (a), a pair differs at p < 0.05 in 10 to 16 of the 16 selectivity readings for the rate difference and 7 to 9 for the ratio; with groups (b), in 1 to 10 readings for the difference and 0 or 1 for the ratio (P1, P2 and P3). The readings neither all find nor all miss a difference, which the criterion calls methods underspecified.
fig6c-unmodulated-less-selective¶
The few VM/VAL neurons not changed by ALM photoinhibition were significantly less selective in the delay epoch, while neurons strongly reduced by it carried trial-type information; the change of rate and delay selectivity were negatively related (regression slope -0.73, r -0.33).
Neurons of fig6c-vmval-reduced split by the significance of their change. Delay selectivity on correct control trials, (a) |contra - ipsi| rate, (b) preferred minus non-preferred rate (preference by total count, convention S). Two-sided t-test; regression (i) selectivity on change, (ii) change on selectivity. P1, P2 and P3.
rows = []
for p in ("P1", "P2", "P3"):
for tname in T_READ:
for ev in (True, False):
eff = F6[(p, tname, ev)]
rc, ri = [], []
for idx in eff.index:
r = units.loc[idx]
s = sessions[r.session]
t = s["trials"]
m = (t.response == "correct").values & (t.stim == "none").values
c = s["counts"]["delay"][r.unit] / t.delay_s.values
rc.append(np.nanmean(c[m & t.contra.values]))
ri.append(np.nanmean(c[m & ~t.contra.values]))
rc, ri = np.array(rc), np.array(ri)
pref = SEL["control/R1/Student/c1"].loc[eff.index, "contra_pref"].values
sel = {"a": np.abs(rc - ri), "b": np.where(pref, rc - ri, ri - rc)}
for sname, sv in sel.items():
tt = stats.ttest_ind(sv[eff.sig.values], sv[~eff.sig.values], equal_var=ev)
for ax in ("i", "ii"):
x, y = (eff.delta.values, sv) if ax == "i" else (sv, eff.delta.values)
lr = stats.linregress(x, y)
rows.append({"units": p, "trials": tname, "test": "Student" if ev else "Welch", "selectivity": sname, "axes": ax,
"n_sig": int(eff.sig.sum()), "n_not": int((~eff.sig).sum()),
"sel_sig": round(sv[eff.sig.values].mean(), 2), "sel_not": round(sv[~eff.sig.values].mean(), 2),
"t_p": tt.pvalue, "slope": round(lr.slope, 3), "r": round(lr.rvalue, 3)})
f6u = pd.DataFrame(rows)
f6u["group_ok"] = (f6u.sel_not < f6u.sel_sig) & (f6u.t_p < 0.05)
f6u["within_tol"] = f6u.group_ok & ((f6u.slope + 0.73).abs() <= 0.073) & ((f6u.r + 0.33).abs() <= 0.033)
f6u["partial"] = f6u.group_ok & (f6u.r < 0)
print(f6u.round(4).to_string(index=False))
print("Paper: P = 0.002; slope -0.73, r -0.33")
print("Readings reproduced:", int(f6u.within_tol.sum()), "| partial:", int(f6u.partial.sum()), "| group difference holds:",
int(f6u.group_ok.sum()), "of", len(f6u))
print(f6u.groupby("units").agg(readings=("r", "size"), group_ok=("group_ok", "sum"), partial=("partial", "sum"), within_tol=("within_tol", "sum"),
r_min=("r", "min"), r_max=("r", "max")).to_string())
units trials test selectivity axes n_sig n_not sel_sig sel_not t_p slope r group_ok within_tol partial
P1 T1 Student a i 7 8 2.98 0.72 0.0771 -0.195 -0.445 False False False
P1 T1 Student a ii 7 8 2.98 0.72 0.0771 -1.016 -0.445 False False False
P1 T1 Student b i 7 8 2.87 -0.03 0.0382 -0.254 -0.520 True False True
P1 T1 Student b ii 7 8 2.87 -0.03 0.0382 -1.065 -0.520 True False True
P1 T1 Welch a i 7 8 2.98 0.72 0.1209 -0.195 -0.445 False False False
P1 T1 Welch a ii 7 8 2.98 0.72 0.1209 -1.016 -0.445 False False False
P1 T1 Welch b i 7 8 2.87 -0.03 0.0680 -0.254 -0.520 False False False
P1 T1 Welch b ii 7 8 2.87 -0.03 0.0680 -1.065 -0.520 False False False
P1 T2 Student a i 6 9 2.94 1.00 0.1454 -0.187 -0.464 False False False
P1 T2 Student a ii 6 9 2.94 1.00 0.1454 -1.151 -0.464 False False False
P1 T2 Student b i 6 9 2.80 0.34 0.0920 -0.251 -0.558 False False False
P1 T2 Student b ii 6 9 2.80 0.34 0.0920 -1.241 -0.558 False False False
P1 T2 Welch a i 8 7 2.81 0.59 0.0829 -0.187 -0.464 False False False
P1 T2 Welch a ii 8 7 2.81 0.59 0.0829 -1.151 -0.464 False False False
P1 T2 Welch b i 8 7 2.71 -0.26 0.0345 -0.251 -0.558 True False True
P1 T2 Welch b ii 8 7 2.71 -0.26 0.0345 -1.241 -0.558 True False True
P2 T1 Student a i 101 71 1.77 1.13 0.0030 -0.074 -0.278 True False True
P2 T1 Student a ii 101 71 1.77 1.13 0.0030 -1.048 -0.278 True False True
P2 T1 Student b i 101 71 1.12 0.39 0.0130 -0.081 -0.226 True False True
P2 T1 Student b ii 101 71 1.12 0.39 0.0130 -0.631 -0.226 True False True
P2 T1 Welch a i 107 65 1.72 1.17 0.0058 -0.074 -0.278 True False True
P2 T1 Welch a ii 107 65 1.72 1.17 0.0058 -1.048 -0.278 True False True
P2 T1 Welch b i 107 65 1.02 0.48 0.0461 -0.081 -0.226 True False True
P2 T1 Welch b ii 107 65 1.02 0.48 0.0461 -0.631 -0.226 True False True
P2 T2 Student a i 78 85 1.83 1.23 0.0063 -0.068 -0.284 True False True
P2 T2 Student a ii 78 85 1.83 1.23 0.0063 -1.183 -0.284 True False True
P2 T2 Student b i 78 85 1.22 0.46 0.0109 -0.091 -0.282 True False True
P2 T2 Student b ii 78 85 1.22 0.46 0.0109 -0.875 -0.282 True False True
P2 T2 Welch a i 103 60 1.75 1.12 0.0021 -0.068 -0.284 True False True
P2 T2 Welch a ii 103 60 1.75 1.12 0.0021 -1.183 -0.284 True False True
P2 T2 Welch b i 103 60 1.10 0.35 0.0085 -0.091 -0.282 True False True
P2 T2 Welch b ii 103 60 1.10 0.35 0.0085 -0.875 -0.282 True False True
P3 T1 Student a i 112 89 1.81 1.21 0.0031 -0.048 -0.181 True False True
P3 T1 Student a ii 112 89 1.81 1.21 0.0031 -0.683 -0.181 True False True
P3 T1 Student b i 112 89 1.15 0.54 0.0238 -0.051 -0.142 True False True
P3 T1 Student b ii 112 89 1.15 0.54 0.0238 -0.400 -0.142 True False True
P3 T1 Welch a i 121 80 1.76 1.23 0.0056 -0.048 -0.181 True False True
P3 T1 Welch a ii 121 80 1.76 1.23 0.0056 -0.683 -0.181 True False True
P3 T1 Welch b i 121 80 1.08 0.57 0.0492 -0.051 -0.142 True False True
P3 T1 Welch b ii 121 80 1.08 0.57 0.0492 -0.400 -0.142 True False True
P3 T2 Student a i 87 103 1.81 1.36 0.0308 -0.049 -0.201 True False True
P3 T2 Student a ii 87 103 1.81 1.36 0.0308 -0.819 -0.201 True False True
P3 T2 Student b i 87 103 1.18 0.67 0.0710 -0.062 -0.189 False False False
P3 T2 Student b ii 87 103 1.18 0.67 0.0710 -0.574 -0.189 False False False
P3 T2 Welch a i 113 77 1.74 1.31 0.0353 -0.049 -0.201 True False True
P3 T2 Welch a ii 113 77 1.74 1.31 0.0353 -0.819 -0.201 True False True
P3 T2 Welch b i 113 77 1.08 0.63 0.1063 -0.062 -0.189 False False False
P3 T2 Welch b ii 113 77 1.08 0.63 0.1063 -0.574 -0.189 False False False
Paper: P = 0.002; slope -0.73, r -0.33
Readings reproduced: 0 | partial: 32 | group difference holds: 32 of 48
readings group_ok partial within_tol r_min r_max
units
P1 16 4 4 0 -0.558 -0.445
P2 16 16 16 0 -0.284 -0.226
P3 16 12 12 0 -0.201 -0.142
Result: partially reproduced. With P2 and correct and incorrect trials, the 71 neurons not significantly changed (Student) are less selective in the delay epoch than the 101 changed ones (|contra - ipsi| 1.13 against 1.77 spikes/s, p = 0.0030; the paper's P = 0.002), and the change of rate and delay selectivity are negatively correlated (r = -0.278 against -0.33 +/- 0.033; slope -0.074 with selectivity on change, -1.048 with change on selectivity, against -0.73). The group difference and a negative r hold in all 16 P2 readings, 12 of 16 P3 readings (r -0.142 to -0.201) and 4 of 16 P1 readings (P1 has 15 neurons); slope and r are out of tolerance in every reading.
fig6d-localized¶
The reduction of thalamic activity by ALM photoinhibition was mainly limited to VM/VAL; neurons within 1.0 mm of the VM/VAL centre were inhibited to 36% of control, and neurons farther away less.
Thalamic units of the PV-Cre x Ai32 thalamic sessions (ALM photoinhibition 1.5 mW), at least 5 trials, grouped by distance from the L2 centre. Units (u1) putative thalamic (P1, P2), (u2) all, which is reading P3; windows (w1) 20 to 120 ms, (w2) 0 to 100 ms; ratio Q1 to Q3; T1, T2.
pv_th = thal[thal.genotype == "Ai32 x PV-Cre"]
sets = {"u1-P1": pv_th[pv_th.width_ms > 0.5], "u1-P2": pv_th[pv_th.cell_type == "Wide width spiking"], "u2 (P3)": pv_th}
rows = []
for uname, d in sets.items():
for wname, win in (("w1", "pw"), ("w2", "pw0")):
for tname, oc in T_READ.items():
df = loop.photo_effect(sessions, d, "ALM:delay:1.5", oc, win, 5, True)
dist = units.loc[df.index, "vmval_d"]
g = {"<0.5": df[dist < 0.5], "0.5-1.0": df[(dist >= 0.5) & (dist < 1.0)], ">=1.0": df[dist >= 1.0], "<1.0": df[dist < 1.0]}
r = {k: loop.ratio_readings(v) for k, v in g.items()}
rows.append({"units": uname, "window": wname, "trials": tname, "n<0.5": len(g["<0.5"]), "n0.5-1": len(g["0.5-1.0"]),
"n>=1": len(g[">=1.0"]), **{f"{q}<1.0": round(r["<1.0"][q], 1) for q in ("Q1", "Q2", "Q3")},
**{f"Q1{k}": round(r[k]["Q1"], 1) for k in ("<0.5", "0.5-1.0", ">=1.0")},
**{f"Q2{k}": round(r[k]["Q2"], 1) for k in ("<0.5", ">=1.0")}})
f6d = pd.DataFrame(rows)
cnt_ok = ((f6d["n<0.5"] - 250).abs() <= 25) & ((f6d["n0.5-1"] - 160).abs() <= 16) & ((f6d["n>=1"] - 46).abs() <= 4.6)
f6d["counts_ok"] = cnt_ok
f6d["value_ok_any_Q"] = f6d[["Q1<1.0", "Q2<1.0", "Q3<1.0"]].sub(36).abs().le(3.6).any(axis=1)
f6d["near_lower_than_far_Q1"] = f6d["Q1<0.5"] < f6d["Q1>=1.0"]
print(f6d.to_string(index=False))
print("Paper: 36% within 1.0 mm; n = 250 (< 0.5 mm), 160 (0.5 to 1.0), 46 (>= 1.0)")
print("Readings with counts, value and order all holding:", int((f6d.counts_ok & f6d.value_ok_any_Q & f6d.near_lower_than_far_Q1).sum()), "of", len(f6d))
units window trials n<0.5 n0.5-1 n>=1 Q1<1.0 Q2<1.0 Q3<1.0 Q1<0.5 Q10.5-1.0 Q1>=1.0 Q2<0.5 Q2>=1.0 counts_ok value_ok_any_Q near_lower_than_far_Q1 u1-P1 w1 T1 23 6 0 50.700001 56.900002 48.900002 49.500000 54.299999 NaN 56.200001 NaN False False False u1-P1 w1 T2 23 6 0 45.299999 52.299999 49.500000 46.099998 43.099998 NaN 54.500000 NaN False False False u1-P1 w2 T1 23 6 0 60.599998 65.900002 66.699997 59.000000 65.599998 NaN 62.799999 NaN False False False u1-P1 w2 T2 23 6 0 57.700001 59.799999 52.299999 59.200001 53.200001 NaN 63.400002 NaN False False False u1-P2 w1 T1 250 111 26 53.400002 53.299999 45.500000 48.000000 64.699997 84.900002 48.099998 104.500000 False False True u1-P2 w1 T2 240 110 26 52.599998 53.500000 39.599998 47.799999 62.299999 81.400002 48.299999 89.199997 False True True u1-P2 w2 T1 250 111 26 59.200001 58.700001 51.500000 54.000000 69.900002 86.900002 54.299999 95.500000 False False True u1-P2 w2 T2 240 110 26 57.799999 57.400002 51.799999 53.099998 67.500000 81.300003 53.599998 86.400002 False False True u2 (P3) w1 T1 286 132 27 58.299999 56.299999 48.200001 55.000000 65.400002 85.300003 51.900002 104.599998 False False True u2 (P3) w1 T2 273 131 27 56.400002 55.900002 44.200001 53.200001 63.200001 82.900002 50.900002 102.199997 False False True u2 (P3) w2 T1 286 132 27 63.299999 61.299999 55.200001 60.299999 69.599998 86.000000 57.799999 93.900002 False False True u2 (P3) w2 T2 273 131 27 61.099998 59.599998 53.299999 58.200001 67.099998 81.199997 56.500000 85.900002 False False True Paper: 36% within 1.0 mm; n = 250 (< 0.5 mm), 160 (0.5 to 1.0), 46 (>= 1.0) Readings with counts, value and order all holding: 0 of 12
Result: not reproduced. With P2 and the 20 to 120 ms window, ALM photoinhibition lowers thalamic activity to 48% (Q1) within 0.5 mm of the L2 centre, 62 to 65% at 0.5 to 1.0 mm and 81 to 85% at 1.0 mm or more: the reduction is concentrated near the centre. Within 1.0 mm it is to 52.6 to 53.4% (Q1) or 39.6 to 45.5% (Q3); only Q3 with correct trials (39.6%) is within 36 +/- 3.6%. With all units (u2, reading P3 of the answer) it is to 56.4 to 58.3% (Q1) or 44.2 to 48.2% (Q3). Near the centre the group counts (240 to 286) are within 250 +/- 25 except 286 (all units, correct and incorrect trials); farther away they (110 to 132, and 26 or 27) are below the paper's 160 and 46 in every reading, so no reading meets the whole criterion.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
vm1-photoinhibition-no-effect¶
Photoinhibiting vibrissal M1 did not reduce thalALM activity (Results), or reduced VM/VAL activity less than ALM photoinhibition in the same neurons (Extended Data Fig. 8g legend).
The dandiset names no vM1 site. In the six thalamic sessions of mouse anm00264943, photostimulation at galvo position (0.5, 0.5) mm is interleaved with ALM photoinhibition, which fits the interleaved design of Extended Data Fig. 8f, g; it is taken here as the vM1 condition, an assumption the files cannot confirm. The paper has 46 cells from 3 mice; here 1 mouse. (a) paired t-test over neurons of the rate under vM1 photoinhibition against control; (b) paired t-test of the reduction under vM1 against the reduction under ALM photoinhibition. ALM photoinhibition at 1.5 mW (convention P, answer of 2026-10-20); variants, not judged: ALM at 0.79 mW (the power of the (0.5, 0.5) trials) and both powers. Window 20 to 120 ms; T1 and T2; VM/VAL (L2) under P1, P2 and P3, and, as variants, all putative and all units of the mouse.
m3 = thal[thal.subject == "anm00264943"]
sets = {"VM/VAL P1": m3[(m3.width_ms > 0.5) & (m3.vmval_d < 0.4)], "VM/VAL P2": m3[(m3.cell_type == "Wide width spiking") & (m3.vmval_d < 0.4)],
"VM/VAL P3": m3[m3.vmval_d < 0.4], "putative P2": m3[m3.cell_type == "Wide width spiking"], "all": m3}
rows = []
for sname, d in sets.items():
for tname, oc in T_READ.items():
v = loop.photo_effect(sessions, d, "site0.5:delay:0.79", oc, "pw", 5, True)
for aname, astim in (("ALM 1.5", "ALM:delay:1.5"), ("variant ALM 0.79", "ALM:delay:0.79"), ("variant ALM both", ["ALM:delay:0.79", "ALM:delay:1.5"])):
a = loop.photo_effect(sessions, d, astim, oc, "pw", 5, True)
j = v.index.intersection(a.index)
if len(j) < 3:
rows.append({"units": sname, "trials": tname, "alm": aname, "n": len(j)})
continue
pa = stats.ttest_rel(v.loc[j, "rate_pi"], v.loc[j, "rate_ctrl"])
pb = stats.ttest_rel(-v.loc[j, "delta"], -a.loc[j, "delta"])
rows.append({"units": sname, "trials": tname, "alm": aname, "n": len(j),
"ctrl": round(v.loc[j, "rate_ctrl"].mean(), 2), "vM1": round(v.loc[j, "rate_pi"].mean(), 2), "ALM": round(a.loc[j, "rate_pi"].mean(), 2),
"vM1_Q1": round(100 * v.loc[j, "rate_pi"].mean() / v.loc[j, "rate_ctrl"].mean(), 1),
"ALM_Q1": round(100 * a.loc[j, "rate_pi"].mean() / a.loc[j, "rate_ctrl"].mean(), 1),
"vM1_Q3": round(100 * v.loc[j, "ratio"].median(), 1), "ALM_Q3": round(100 * a.loc[j, "ratio"].median(), 1),
"a_p": pa.pvalue, "a_vM1_reduces": bool(v.loc[j, "delta"].mean() < 0 and pa.pvalue < 0.05),
"b_p": pb.pvalue, "b_vM1_less": bool((-v.loc[j, "delta"]).mean() < (-a.loc[j, "delta"]).mean() and pb.pvalue < 0.05)})
vm1 = pd.DataFrame(rows)
print(vm1.round(4).to_string(index=False))
m15 = vm1[vm1.alm == "ALM 1.5"]
print("ALM at 1.5 mW (convention P): rows where (a) no significant reduction under the (0.5, 0.5) site holds:",
m15[~m15.a_vM1_reduces][["units", "trials"]].values.tolist(), "| where (b) holds:", m15[m15.b_vM1_less][["units", "trials"]].values.tolist())
print("Sessions of anm00264943 with (0.5, 0.5) trials:", sum(1 for s in sessions if (s["trials"].stim == "site0.5:delay:0.79").any()),
"| trials:", sum(int((s["trials"].stim == "site0.5:delay:0.79").sum()) for s in sessions))
units trials alm n ctrl vM1 ALM vM1_Q1 ALM_Q1 vM1_Q3 ALM_Q3 a_p a_vM1_reduces b_p b_vM1_less
VM/VAL P1 T1 ALM 1.5 3 11.68 6.99 3.37 59.799999 28.900000 60.599998 7.700000 0.0706 False 0.3173 False
VM/VAL P1 T1 variant ALM 0.79 3 11.68 6.99 13.77 59.799999 117.900002 60.599998 119.599998 0.0706 False 0.0537 False
VM/VAL P1 T1 variant ALM both 3 11.68 6.99 9.39 59.799999 80.400002 60.599998 90.199997 0.0706 False 0.0833 False
VM/VAL P1 T2 ALM 1.5 3 11.95 7.45 2.51 62.299999 21.000000 58.200001 10.600000 0.1030 False 0.1572 False
VM/VAL P1 T2 variant ALM 0.79 3 11.95 7.45 13.32 62.299999 111.500000 58.200001 106.000000 0.1030 False 0.0300 False
VM/VAL P1 T2 variant ALM both 3 11.95 7.45 9.44 62.299999 79.000000 58.200001 91.000000 0.1030 False 0.1750 False
VM/VAL P2 T1 ALM 1.5 41 12.19 10.93 5.28 89.599998 43.299999 82.400002 32.700001 0.0583 False 0.0000 True
VM/VAL P2 T1 variant ALM 0.79 42 12.19 10.87 11.16 89.199997 91.599998 82.300003 88.800003 0.0450 True 0.7256 False
VM/VAL P2 T1 variant ALM both 42 12.19 10.87 8.48 89.199997 69.599998 82.300003 69.000000 0.0450 True 0.0012 True
VM/VAL P2 T2 ALM 1.5 36 12.16 11.77 4.98 96.800003 41.000000 92.900002 25.000000 0.6197 False 0.0000 True
VM/VAL P2 T2 variant ALM 0.79 37 12.30 11.99 11.54 97.500000 93.900002 97.400002 87.900002 0.6935 False 0.6450 False
VM/VAL P2 T2 variant ALM both 37 12.30 11.99 9.16 97.500000 74.500000 97.400002 69.400002 0.6935 False 0.0016 True
VM/VAL P3 T1 ALM 1.5 48 12.89 11.66 6.41 90.400002 49.700001 82.699997 35.700001 0.0404 True 0.0000 True
VM/VAL P3 T1 variant ALM 0.79 49 12.87 11.59 12.05 90.099998 93.599998 82.400002 90.199997 0.0312 True 0.5184 False
VM/VAL P3 T1 variant ALM both 49 12.87 11.59 9.46 90.099998 73.500000 82.400002 70.400002 0.0312 True 0.0010 True
VM/VAL P3 T2 ALM 1.5 41 13.02 12.53 5.92 96.199997 45.500000 97.400002 25.299999 0.5017 False 0.0000 True
VM/VAL P3 T2 variant ALM 0.79 42 13.12 12.71 12.45 96.900002 94.800003 98.099998 89.400002 0.5663 False 0.7614 False
VM/VAL P3 T2 variant ALM both 42 13.12 12.71 10.01 96.900002 76.300003 98.099998 69.400002 0.5663 False 0.0009 True
putative P2 T1 ALM 1.5 60 11.52 9.85 4.86 85.500000 42.200001 80.400002 29.799999 0.0020 True 0.0000 True
putative P2 T1 variant ALM 0.79 61 11.53 9.83 10.87 85.300003 94.300003 80.199997 90.199997 0.0014 True 0.1140 False
putative P2 T1 variant ALM both 61 11.53 9.83 8.24 85.300003 71.500000 80.199997 71.400002 0.0014 True 0.0050 True
putative P2 T2 ALM 1.5 54 11.50 10.39 4.64 90.400002 40.299999 83.000000 24.200001 0.0769 False 0.0000 True
putative P2 T2 variant ALM 0.79 56 11.62 10.48 11.13 90.199997 95.800003 83.000000 95.900002 0.0655 False 0.4081 False
putative P2 T2 variant ALM both 56 11.62 10.48 8.73 90.199997 75.099998 83.000000 69.400002 0.0655 False 0.0110 True
all T1 ALM 1.5 67 12.09 10.48 5.71 86.699997 47.200001 80.599998 32.700001 0.0014 True 0.0000 True
all T1 variant ALM 0.79 68 12.09 10.46 11.54 86.500000 95.500000 80.400002 91.000000 0.0010 True 0.0671 False
all T1 variant ALM both 68 12.09 10.46 8.96 86.500000 74.199997 80.400002 72.599998 0.0010 True 0.0040 True
all T2 ALM 1.5 59 12.16 11.04 5.32 90.800003 43.799999 83.099998 25.299999 0.0603 False 0.0000 True
all T2 variant ALM 0.79 61 12.24 11.10 11.79 90.699997 96.300003 83.099998 95.500000 0.0514 False 0.3464 False
all T2 variant ALM both 61 12.24 11.10 9.35 90.699997 76.400002 83.099998 69.500000 0.0514 False 0.0066 True
ALM at 1.5 mW (convention P): rows where (a) no significant reduction under the (0.5, 0.5) site holds: [['VM/VAL P1', 'T1'], ['VM/VAL P1', 'T2'], ['VM/VAL P2', 'T1'], ['VM/VAL P2', 'T2'], ['VM/VAL P3', 'T2'], ['putative P2', 'T2'], ['all', 'T2']] | where (b) holds: [['VM/VAL P2', 'T1'], ['VM/VAL P2', 'T2'], ['VM/VAL P3', 'T1'], ['VM/VAL P3', 'T2'], ['putative P2', 'T1'], ['putative P2', 'T2'], ['all', 'T1'], ['all', 'T2']]
Sessions of anm00264943 with (0.5, 0.5) trials: 6 | trials: 127
Result: blocked (metadata insufficient). The site at galvo (0.5, 0.5) mm is not named in the files, and the answer of 2026-10-20 says the paper does not say which condition is vM1. Taking it as vM1 and comparing with ALM at 1.5 mW, as the answer fixes: in 41 VM/VAL units (P2, T1) of one mouse it lowers activity to 89.6% of control (Q1; paired p = 0.058; T2 96.8%, p = 0.62), and ALM photoinhibition lowers the same units to 43.3%, a larger reduction (p < 0.0001). (a) and (b) hold together under P2 with T1 and T2 and under P3 with T2; under P3 with T1 (48 units) the (0.5, 0.5) site reduces activity significantly (90.4%, p = 0.040), so (a) fails there; under P1 only 3 units enter. In the variant with ALM at 0.79 mW the ALM reduction (91.6%, P2, T1) is no larger (p = 0.73). The paper's 46 cells come from 3 mice, these from 1.
snr-photoinhibition¶
ALM photoinhibition changed only a small fraction of SNr GABAergic neurons (48/181 significantly inhibited, 23/181 activated), more decreasing than increasing, and reduced SNr activity overall by 3.6 spikes per s (8.3%), which argues against SNr mediating the thalamic reduction.
SNr units of the dandiset (181; the paper's 46 wider units are not deposited, see snr-unit-properties) under P1 (< 0.45 ms, 171 units) and P2 (label 'Narrow width spiking', 146 units), and all 181 as a variant; ALM photoinhibition 1.5 mW, 20 to 120 ms; minimum photostimulus trials (a) none, (b) 5; T1, T2; Student, Welch. Bootstrap over neurons, 10000 rounds; p is the fraction of rounds with at least as many up- as down-modulated neurons.
rng = np.random.default_rng(601)
GABA["all 181"] = snr
rows, SNR = [], {}
for p, d in GABA.items():
for mt in (0, 5):
for tname, oc in T_READ.items():
for ev in (True, False):
df = loop.photo_effect(sessions, d, "ALM:delay:1.5", oc, "pw", mt, ev)
SNR[(p, mt, tname, ev)] = df
down = (df.sig & (df.delta < 0)).values
up = (df.sig & (df.delta > 0)).values
k = rng.integers(0, len(df), (10000, len(df)))
pb = float(np.mean(up[k].sum(1) >= down[k].sum(1)))
red = df.rate_ctrl.mean() - df.rate_pi.mean()
rows.append({"units": p, "min_trials": mt, "trials": tname, "test": "Student" if ev else "Welch", "n": len(df),
"sig_inhibited": int(down.sum()), "sig_activated": int(up.sum()), "reduction": round(red, 2),
"reduction_%": round(100 * red / df.rate_ctrl.mean(), 2), "rate_ctrl": round(df.rate_ctrl.mean(), 2), "boot_p": pb,
"p_nan": int(df.p.isna().sum())})
sn = pd.DataFrame(rows)
sn["within_tol"] = ((sn.n - 181).abs() <= 18) & ((sn.sig_inhibited - 48).abs() <= 4.8) & ((sn.sig_activated - 23).abs() <= 2.3) & \
((sn.reduction - 3.6).abs() <= 0.36) & ((sn["reduction_%"] - 8.3).abs() <= 0.83) & (sn.boot_p < 0.01)
print(sn.to_string(index=False))
print("Paper: 227 SNr units, 181 putative GABAergic; 48 inhibited, 23 activated; reduction 3.6 spikes/s (8.3%); bootstrap p < 0.01")
print("SNr units in the dandiset:", len(snr), "(paper 227 +/- 23)")
print("Readings within tolerance:", int(sn.within_tol.sum()), "of", len(sn))
d = SNR[("P2", 5, "T1", True)].join(units[["subject"]])
print("\nBy mouse (P2, 5 trials, T1, Student):")
print(d.groupby("subject").agg(n=("delta", "size"), sig_inh=("sig", lambda x: int((x & (d.loc[x.index, "delta"] < 0)).sum())),
sig_act=("sig", lambda x: int((x & (d.loc[x.index, "delta"] > 0)).sum())),
ctrl=("rate_ctrl", "mean"), pi=("rate_pi", "mean")).round(2).to_string())
units min_trials trials test n sig_inhibited sig_activated reduction reduction_% rate_ctrl boot_p p_nan within_tol
P1 0 T1 Student 171 45 21 2.33 5.97 39.009998 0.0009 0 False
P1 0 T1 Welch 171 50 21 2.33 5.97 39.009998 0.0003 0 False
P1 0 T2 Student 171 36 18 2.40 6.12 39.209999 0.0075 4 False
P1 0 T2 Welch 171 41 17 2.40 6.12 39.209999 0.0014 4 False
P1 5 T1 Student 170 44 21 2.25 5.77 39.080002 0.0019 0 False
P1 5 T1 Welch 170 50 21 2.25 5.77 39.080002 0.0003 0 False
P1 5 T2 Student 166 36 18 2.44 6.26 38.950001 0.0074 0 False
P1 5 T2 Welch 166 41 17 2.44 6.26 38.950001 0.0005 0 False
P2 0 T1 Student 146 35 18 2.68 6.49 41.279999 0.0087 0 False
P2 0 T1 Welch 146 40 18 2.68 6.49 41.279999 0.0020 0 False
P2 0 T2 Student 146 31 15 2.76 6.64 41.520000 0.0102 3 False
P2 0 T2 Welch 146 36 14 2.76 6.64 41.520000 0.0014 3 False
P2 5 T1 Student 146 35 18 2.68 6.49 41.279999 0.0105 0 False
P2 5 T1 Welch 146 40 18 2.68 6.49 41.279999 0.0022 0 False
P2 5 T2 Student 142 31 15 2.98 7.23 41.180000 0.0080 0 False
P2 5 T2 Welch 142 36 14 2.98 7.23 41.180000 0.0009 0 False
all 181 0 T1 Student 181 46 23 2.17 5.76 37.650002 0.0030 0 False
all 181 0 T1 Welch 181 50 22 2.17 5.76 37.650002 0.0004 0 False
all 181 0 T2 Student 181 36 20 2.21 5.84 37.840000 0.0191 4 False
all 181 0 T2 Welch 181 41 18 2.21 5.84 37.840000 0.0013 4 False
all 181 5 T1 Student 180 45 23 2.10 5.57 37.709999 0.0030 0 False
all 181 5 T1 Welch 180 50 22 2.10 5.57 37.709999 0.0003 0 False
all 181 5 T2 Student 176 36 20 2.24 5.97 37.549999 0.0163 0 False
all 181 5 T2 Welch 176 41 18 2.24 5.97 37.549999 0.0007 0 False
Paper: 227 SNr units, 181 putative GABAergic; 48 inhibited, 23 activated; reduction 3.6 spikes/s (8.3%); bootstrap p < 0.01
SNr units in the dandiset: 181 (paper 227 +/- 23)
Readings within tolerance: 0 of 24
By mouse (P2, 5 trials, T1, Student):
n sig_inh sig_act ctrl pi
subject
anm00261334 36 5 2 39.680000 36.459999
anm00261335 49 9 11 35.950001 35.410000
anm00261336 61 21 5 46.500000 42.419998
Result: not reproduced. The dandiset holds 181 SNr units, 171 under P1 and 146 under P2 (the 'Narrow width spiking' label); the paper's 46 wider units are not deposited (snr-unit-properties). Under P1 with correct and incorrect trials, 44 to 50 units are significantly inhibited and 21 activated (paper 48 and 23), and more decrease than increase (bootstrap p = 0.0003 to 0.0019); under P2, 35 to 40 and 18 (p = 0.0020 to 0.0105). The overall reduction is 2.25 to 2.44 spikes/s (5.8 to 6.3% of control) under P1 and 2.68 to 2.98 spikes/s (6.5 to 7.2%) under P2, against 3.6 spikes/s (8.3%), out of tolerance in every reading. Under P1 the counts and the test agree with the paper; the reduction is weaker.
The criterion has no partial clause, and no reading meets it in full (see Changes of outcome, before the Summary). The third verification round agreed.
snr-unit-properties¶
Putative GABAergic SNr neurons had high spike rates (40.9 spikes per s) compared with the other SNr units (23.4), and most of them (152/181) were selective for trial type, like ALM and VM/VAL.
Mean rate per unit, (a) over the session (spikes over the time from the first trial start to the last trial end), (b) within trials (spikes in trials over the summed trial durations); then the mean over units. Selectivity by convention S under every reading.
rate_a = pd.Series(snr.n_spikes.values / (snr.last_spike - snr.first_spike).values, index=snr.index)
rate_b = pd.Series([np.nansum(sessions[r.session]["counts"]["trial"][r.unit]) /
(sessions[r.session]["trials"].stop_time - sessions[r.session]["trials"].start_time)[sessions[r.session]["valid"][r.unit]].sum()
for r in snr.itertuples()], index=snr.index)
rate_s = pd.Series([np.nansum(sessions[r.session]["counts"]["stored"][r.unit]) /
(sessions[r.session]["trials"].stored_end - sessions[r.session]["trials"].start_time)[sessions[r.session]["valid"][r.unit]].sum()
for r in snr.itertuples()], index=snr.index)
rate_w = pd.Series([np.nanmean(sessions[r.session]["counts"]["sample"][r.unit]) / loop.SAMPLE_S for r in snr.itertuples()], index=snr.index)
rows = []
for p in ("P1", "P2"):
g = GABA[p].index
o = snr.index.difference(g)
for rname, rv in (("a session (unit's span)", rate_a), ("b trials", rate_b), ("variant: stored part of trials", rate_s),
("variant: sample epoch", rate_w)):
rows.append({"units": p, "rate": rname, "n_gaba": len(g), "gaba_mean": round(rv[g].mean(), 2), "gaba_sd": round(rv[g].std(), 2),
"n_other": len(o), "other_mean": round(rv[o].mean(), 2) if len(o) else np.nan, "other_sd": round(rv[o].std(), 2) if len(o) > 1 else np.nan})
print(pd.DataFrame(rows).to_string(index=False))
print("Paper: 40.9 +/- 21.5 (n = 181) against 23.4 +/- 17.0 (n = 46)")
sel_rows = [{"units": p, "reading": k, "n": len(GABA[p]), "selective": int(SEL[k].loc[GABA[p].index, "selective"].sum())} for p in ("P1", "P2") for k in SEL]
sel_rows = pd.DataFrame(sel_rows)
sel_rows["within_tol"] = (sel_rows.selective - 152).abs() <= 15
print(sel_rows.to_string(index=False))
print("Paper: 152/181 selective")
units rate n_gaba gaba_mean gaba_sd n_other other_mean other_sd P1 a session (unit's span) 171 20.770000 11.22 10 7.58 5.48 P1 b trials 171 20.710000 11.20 10 7.56 5.46 P1 variant: stored part of trials 171 35.960000 19.35 10 13.07 9.48 P1 variant: sample epoch 171 37.250000 19.99 10 13.86 9.81 P2 a session (unit's span) 146 22.020000 11.03 35 11.80 8.96 P2 b trials 146 21.960000 11.02 35 11.77 8.93 P2 variant: stored part of trials 146 38.130000 19.04 35 20.34 15.28 P2 variant: sample epoch 146 39.299999 19.68 35 22.01 16.60 Paper: 40.9 +/- 21.5 (n = 181) against 23.4 +/- 17.0 (n = 46) units reading n selective within_tol P1 control/R1/Student/c1 171 139 True P1 control/R1/Student/c2 171 133 False P1 control/R1/Welch/c1 171 137 True P1 control/R1/Welch/c2 171 133 False P1 control/R2/Student/c1 171 141 True P1 control/R2/Student/c2 171 135 False P1 control/R2/Welch/c1 171 140 True P1 control/R2/Welch/c2 171 134 False P1 all/R1/Student/c1 171 142 True P1 all/R1/Student/c2 171 134 False P1 all/R1/Welch/c1 171 140 True P1 all/R1/Welch/c2 171 134 False P1 all/R2/Student/c1 171 143 True P1 all/R2/Student/c2 171 134 False P1 all/R2/Welch/c1 171 143 True P1 all/R2/Welch/c2 171 133 False P2 control/R1/Student/c1 146 120 False P2 control/R1/Student/c2 146 117 False P2 control/R1/Welch/c1 146 119 False P2 control/R1/Welch/c2 146 117 False P2 control/R2/Student/c1 146 121 False P2 control/R2/Student/c2 146 118 False P2 control/R2/Welch/c1 146 121 False P2 control/R2/Welch/c2 146 117 False P2 all/R1/Student/c1 146 122 False P2 all/R1/Student/c2 146 115 False P2 all/R1/Welch/c1 146 120 False P2 all/R1/Welch/c2 146 115 False P2 all/R2/Student/c1 146 122 False P2 all/R2/Student/c2 146 115 False P2 all/R2/Welch/c1 146 122 False P2 all/R2/Welch/c2 146 114 False Paper: 152/181 selective
Result: blocked (data missing). Spikes are stored for only about 5 s of each 9 s trial, so neither reading of the mean rate can be computed as defined; as computed, both give 20.7 (P1) or 22.0 (P2) spikes/s for the putative GABAergic units against 40.9, biased low because they divide by time with no stored spikes. Over the stored part of each trial (a variant, not deciding) the same units fire 36.0 (P1) or 38.1 (P2) spikes/s, and the other units 13.1 (10 units, P1) or 20.3 (35 units, P2) against 23.4. Only 10 (P1) or 35 (P2) other SNr units are deposited, against 46, and under P2 most of them are labelled 'Cannot be determined'. The selectivity count can be tested: 139 of 171 units (P1) are selective under the first reading, within 152 +/- 15 (137 to 143 in the eight c1 readings); under P2, 114 to 122 of 146, below it.
fig6e-onset-latencies¶
After ALM photoinhibition onset, ALM pyramidal neurons were silenced after 8.1 ms and VM/VAL activity reduced after 10.9 ms; SNr neurons changed later, after 15.2 ms.
Convention O (O1, O2), T1 and T2, bootstrap SEM (1000). Populations: ALM putative pyramidal units of the PV-Cre x Ai32 ALM sessions with ALM photoinhibition at 1.5 mW (at least 5 trials; the paper has 256), VM/VAL units of fig6c-vmval-reduced (P1, P2, and P3 with the ALM P1 or P2 population), and the significantly modulated SNr units of snr-photoinhibition (P2, 5 trials, T1, Student): (a) inhibited only, (b) inhibited and activated with the sign of the activated flipped. Readings (d1) onsets from photostimulus onset; (d2) SNr and VM/VAL onsets minus the ALM onset.
rng = np.random.default_rng(602)
rows = []
for p in ("P1", "P2"):
pa = PYR[p][(PYR[p].genotype == "Ai32 x PV-Cre") & (PYR[p].ogen_site == "ALM")]
pa = pa.loc[loop.photo_effect(sessions, pa, "ALM:delay:1.5", loop.KEEP, "pw", 5).index]
rows += onset_rows(f"ALM PV {p}", pa, "ALM:delay:1.5")
vm = pv_vm[p].loc[F6[(p, "T1", True)].index]
rows += onset_rows(f"VM/VAL {p}", vm, "ALM:delay:1.5")
sd = SNR[("P2", 5, "T1", True)]
inh = sd[sd.sig & (sd.delta < 0)]
mod = sd[sd.sig]
rows += onset_rows("SNr inhibited (a)", snr.loc[inh.index], "ALM:delay:1.5")
rows += onset_rows("SNr modulated, flipped (b)", snr.loc[mod.index], "ALM:delay:1.5", -1, flip=(mod.delta > 0).values)
rows += onset_rows("VM/VAL P3", pv_vm["P3"].loc[F6[("P3", "T1", True)].index], "ALM:delay:1.5")
on6 = pd.DataFrame(rows)
print(on6.to_string(index=False))
print("Paper: ALM 8.1 +/- 1.2 ms (n = 256), VM/VAL 10.9 +/- 2.9 ms (n = 201), SNr 15.2 +/- 4.6 ms (48 inhibited)")
w = on6.pivot_table(index=["trials", "reading"], columns="population", values="onset_ms")
res = []
for p, pv in (("P1", "P1"), ("P2", "P2"), ("P1", "P3"), ("P2", "P3")):
for sn_ in ("SNr inhibited (a)", "SNr modulated, flipped (b)"):
for d in ("d1", "d2"):
if f"ALM PV {p}" not in w or f"VM/VAL {pv}" not in w:
res.append({"units": p, "vmval_units": pv, "snr": sn_, "reading": d, "rows_ok": "no ALM or VM/VAL population"})
continue
a_, v_, s_ = w[f"ALM PV {p}"], w[f"VM/VAL {pv}"], w[sn_]
if d == "d2":
v_, s_ = v_ - a_, s_ - a_
ok = (a_.sub(8.1).abs() <= 1.2) & (v_.sub(10.9).abs() <= 2.9) & (s_.sub(15.2).abs() <= 4.6) & (s_ > v_) & (s_ > a_ if d == "d1" else True)
res.append({"units": p, "vmval_units": pv, "snr": sn_, "reading": d, "rows_ok": ok[ok].index.tolist()})
print(pd.DataFrame(res).to_string(index=False))
population n trials reading onset_ms sem_ms boot_no_onset
ALM PV P1 0 NaN NaN NaN NaN NaN
VM/VAL P1 15 T1 O1 20.0 8.44 0.0
VM/VAL P1 15 T1 O2 22.0 7.20 592.0
VM/VAL P1 15 T2 O1 7.0 10.32 0.0
VM/VAL P1 15 T2 O2 7.0 2.28 967.0
ALM PV P2 151 T1 O1 7.0 2.33 0.0
ALM PV P2 151 T1 O2 7.0 2.22 0.0
ALM PV P2 151 T2 O1 6.0 2.45 0.0
ALM PV P2 151 T2 O2 7.0 2.17 1.0
VM/VAL P2 172 T1 O1 14.0 5.75 0.0
VM/VAL P2 172 T1 O2 14.0 6.29 0.0
VM/VAL P2 172 T2 O1 4.0 4.44 0.0
VM/VAL P2 172 T2 O2 4.0 5.43 0.0
SNr inhibited (a) 35 T1 O1 12.0 6.09 0.0
SNr inhibited (a) 35 T1 O2 31.0 11.44 0.0
SNr inhibited (a) 35 T2 O1 12.0 5.93 0.0
SNr inhibited (a) 35 T2 O2 32.0 10.67 0.0
SNr modulated, flipped (b) 53 T1 O1 20.0 6.26 0.0
SNr modulated, flipped (b) 53 T1 O2 27.0 7.35 0.0
SNr modulated, flipped (b) 53 T2 O1 12.0 6.56 0.0
SNr modulated, flipped (b) 53 T2 O2 27.0 8.64 0.0
VM/VAL P3 201 T1 O1 14.0 6.57 0.0
VM/VAL P3 201 T1 O2 14.0 6.49 0.0
VM/VAL P3 201 T2 O1 7.0 5.76 0.0
VM/VAL P3 201 T2 O2 14.0 5.99 0.0
Paper: ALM 8.1 +/- 1.2 ms (n = 256), VM/VAL 10.9 +/- 2.9 ms (n = 201), SNr 15.2 +/- 4.6 ms (48 inhibited)
units vmval_units snr reading rows_ok
P1 P1 SNr inhibited (a) d1 no ALM or VM/VAL population
P1 P1 SNr inhibited (a) d2 no ALM or VM/VAL population
P1 P1 SNr modulated, flipped (b) d1 no ALM or VM/VAL population
P1 P1 SNr modulated, flipped (b) d2 no ALM or VM/VAL population
P2 P2 SNr inhibited (a) d1 []
P2 P2 SNr inhibited (a) d2 []
P2 P2 SNr modulated, flipped (b) d1 []
P2 P2 SNr modulated, flipped (b) d2 []
P1 P3 SNr inhibited (a) d1 no ALM or VM/VAL population
P1 P3 SNr inhibited (a) d2 no ALM or VM/VAL population
P1 P3 SNr modulated, flipped (b) d1 no ALM or VM/VAL population
P1 P3 SNr modulated, flipped (b) d2 no ALM or VM/VAL population
P2 P3 SNr inhibited (a) d1 []
P2 P3 SNr inhibited (a) d2 []
P2 P3 SNr modulated, flipped (b) d1 []
P2 P3 SNr modulated, flipped (b) d2 []
Result: not reproduced. ALM putative pyramidal units of the PV-Cre x Ai32 ALM sessions with ALM photoinhibition number 151 with P2 and none with P1 (all computed widths in those sessions are 0.6 ms or less), against 256; their onset is 6 or 7 ms (8.1 +/- 1.2). VM/VAL onsets are 14 ms (T1) or 4 ms (T2) with P2 and 14 ms (T1) or 7 to 14 ms (T2) with P3 (201 units), against 10.9 +/- 2.9, and SNr onsets 12 to 32 ms (P2 SNr units, 35 inhibited) against 15.2 +/- 4.6. No reading puts all three within tolerance with SNr latest.
selectivity-time-course¶
In ALM and in VM/VAL, population selectivity emerged in the sample epoch and increased throughout the delay epoch; it peaked at the beginning of the response epoch in ALM and during the response epoch in VM/VAL.
Extended Data Fig. 4c: per neuron, the preferred trial type from spike counts (sample, delay and response epochs, R1) in a random half of the correct trials, selectivity as preferred minus non-preferred rate in the other half, 200 ms windows stepped by 100 ms, SEM by bootstrapping over neurons (1000). Neurons (a) all, (b) selective (reading control/R1/Student/c1, the first of each list). Trials: readings (t1) correct control trials and (t2) all correct trials (answer of 2026-10-20). VM/VAL units under P1, P2 and P3 (all VM/VAL units). Only trials with a 1.3 s delay enter, so that the epochs align. "Emerged": mean above twice its SEM in a window inside the sample epoch; "increased": last 200 ms of the delay above the first; the maximum is located.
rng = np.random.default_rng(701)
tc_t = loop.TC_SPAN[0] + loop.BIN_S * np.arange(int(round((loop.TC_SPAN[1] - loop.TC_SPAN[0]) / loop.BIN_S)))
win_t = tc_t[:-1] + 0.1 # centre of each 200 ms window (two 100 ms bins)
def time_course(sel, ctrl_only):
out = []
for r in sel.itertuples():
s = sessions[r.session]
t = s["trials"]
m = (t.response == "correct").values & (np.abs(t.delay_s.values - 1.3) < 0.05) & s["valid"][r.unit] & t.tc_ok.values
if ctrl_only:
m = m & (t.stim == "none").values
ci, ii = np.flatnonzero(m & t.contra.values), np.flatnonzero(m & ~t.contra.values)
if len(ci) < 4 or len(ii) < 4:
continue
ci, ii = rng.permutation(ci), rng.permutation(ii)
ca, cb, ia, ib = ci[: len(ci) // 2], ci[len(ci) // 2:], ii[: len(ii) // 2], ii[len(ii) // 2:]
tot = s["counts"]["sample"][r.unit] + s["counts"]["delay"][r.unit] + s["counts"]["R1"][r.unit]
pref_c = tot[ca].mean() >= tot[ia].mean()
x = s["tc"][r.unit].astype(float)
w2 = (x[:, :-1] + x[:, 1:]) / 0.2
sel_ = w2[cb].mean(0) - w2[ib].mean(0)
out.append(sel_ if pref_c else -sel_)
return np.array(out)
rows = []
curves = {}
for area, base in (("ALM P1", PYR["P1"]), ("ALM P2", PYR["P2"]), ("VM/VAL P1", VMVAL["P1"]), ("VM/VAL P2", VMVAL["P2"]),
("VM/VAL P3", VM26["P3"])):
for nsel in ("a", "b"):
sel = base if nsel == "a" else base[SEL["control/R1/Student/c1"].loc[base.index, "selective"]]
for ctrl_only in (True, False):
tc = time_course(sel, ctrl_only)
mean = tc.mean(0)
boot = np.array([tc[rng.integers(0, len(tc), len(tc))].mean(0) for _ in range(1000)])
sem = boot.std(0)
in_sample = (win_t - 0.1 >= 0) & (win_t + 0.1 <= 1.3)
emerged = bool((mean[in_sample] > 2 * sem[in_sample]).any())
first_d = np.argmin(np.abs(win_t - 1.4))
last_d = np.argmin(np.abs(win_t - 2.5))
imax = int(np.argmax(mean))
tmax = win_t[imax]
epoch = "sample" if tmax < 1.3 else "delay" if tmax < 2.6 else "response"
curves[(area, nsel, ctrl_only)] = (mean, sem)
rows.append({"area": area, "neurons": nsel, "trials": "t1 correct control" if ctrl_only else "t2 all correct", "n": len(tc),
"first_emerging_window_s": round(float(win_t[in_sample][mean[in_sample] > 2 * sem[in_sample]][0]), 2) if emerged else np.nan,
"emerged_in_sample": emerged, "delay_first": round(mean[first_d], 2), "delay_last": round(mean[last_d], 2),
"increased": bool(mean[last_d] > mean[first_d]), "max_at_s": round(float(tmax), 2), "max_epoch": epoch,
"max_within_first_200ms_of_response": bool(2.6 <= tmax <= 2.8), "max_value": round(float(mean[imax]), 2)})
stc = pd.DataFrame(rows)
print(stc.to_string(index=False))
print("Time from pole onset: sample 0 to 1.3 s, delay 1.3 to 2.6 s, response from 2.6 s; window centres from",
round(win_t[0], 2), "to", round(win_t[-1], 2), "s")
ok = stc.emerged_in_sample & stc.increased & (stc.max_epoch == "response")
print("Rows meeting all three:", stc[ok][["area", "neurons", "trials"]].values.tolist())
print("Readings (unit reading, neurons, trials) meeting all three in both ALM and VM/VAL:",
[(pa, pv, nsel, tr) for pa in ("ALM P1", "ALM P2") for pv in ("VM/VAL P1", "VM/VAL P2", "VM/VAL P3") for nsel in ("a", "b")
for tr in ("t1 correct control", "t2 all correct")
if ok[(stc.area == pa) & (stc.neurons == nsel) & (stc.trials == tr)].all() and ok[(stc.area == pv) & (stc.neurons == nsel) & (stc.trials == tr)].all()])
fig, ax = plt.subplots(figsize=(7, 2.8))
for key, col in ((("ALM P2", "a", True), "k"), (("VM/VAL P2", "a", True), "tab:blue")):
m, se = curves[key]
ax.plot(win_t, m, color=col, label=f"{key[0]} (all neurons, control)")
ax.fill_between(win_t, m - se, m + se, color=col, alpha=0.2)
for x in (0, 1.3, 2.6):
ax.axvline(x, ls="--", c="grey", lw=0.7)
ax.set_xlabel("s from pole onset")
ax.set_ylabel("selectivity (spikes/s)")
ax.legend(fontsize=7)
plt.show()
print("Plotted range (spikes/s):", round(min(curves[k][0].min() for k in (("ALM P2", "a", True), ("VM/VAL P2", "a", True))), 2), "to",
round(max(curves[k][0].max() for k in (("ALM P2", "a", True), ("VM/VAL P2", "a", True))), 2))
area neurons trials n first_emerging_window_s emerged_in_sample delay_first delay_last increased max_at_s max_epoch max_within_first_200ms_of_response max_value
ALM P1 a t1 correct control 577 0.1 True 1.03 1.02 False 3.2 response False 2.17
ALM P1 a t2 all correct 577 0.1 True 1.00 1.05 True 3.0 response False 2.26
ALM P1 b t1 correct control 398 0.1 True 1.57 1.65 True 3.1 response False 3.15
ALM P1 b t2 all correct 398 0.1 True 1.30 1.50 True 3.1 response False 3.26
ALM P2 a t1 correct control 810 0.1 True 0.82 1.15 True 3.0 response False 2.38
ALM P2 a t2 all correct 810 0.1 True 0.85 1.10 True 3.0 response False 2.31
ALM P2 b t1 correct control 571 0.1 True 1.33 1.69 True 3.0 response False 3.19
ALM P2 b t2 all correct 571 0.2 True 1.09 1.34 True 3.0 response False 3.24
VM/VAL P1 a t1 correct control 20 0.5 True 1.71 1.05 False 3.1 response False 5.49
VM/VAL P1 a t2 all correct 20 0.8 True 1.90 1.43 False 3.1 response False 6.16
VM/VAL P1 b t1 correct control 13 NaN False 3.22 2.03 False 3.0 response False 7.95
VM/VAL P1 b t2 all correct 13 0.2 True 3.09 2.31 False 3.0 response False 7.40
VM/VAL P2 a t1 correct control 220 0.2 True 0.83 0.17 False 3.4 response False 2.75
VM/VAL P2 a t2 all correct 221 0.3 True 0.54 0.41 False 3.1 response False 2.26
VM/VAL P2 b t1 correct control 149 0.2 True 0.98 0.59 False 3.3 response False 3.62
VM/VAL P2 b t2 all correct 149 0.1 True 1.09 0.94 False 3.4 response False 3.63
VM/VAL P3 a t1 correct control 257 0.5 True 0.57 0.53 False 3.3 response False 2.68
VM/VAL P3 a t2 all correct 258 0.3 True -0.05 0.38 True 3.3 response False 2.85
VM/VAL P3 b t1 correct control 174 0.1 True 0.42 0.57 True 3.0 response False 4.25
VM/VAL P3 b t2 all correct 174 0.2 True 0.65 0.80 True 3.3 response False 4.25
Time from pole onset: sample 0 to 1.3 s, delay 1.3 to 2.6 s, response from 2.6 s; window centres from -0.4 to 4.0 s
Rows meeting all three: [['ALM P1', 'a', 't2 all correct'], ['ALM P1', 'b', 't1 correct control'], ['ALM P1', 'b', 't2 all correct'], ['ALM P2', 'a', 't1 correct control'], ['ALM P2', 'a', 't2 all correct'], ['ALM P2', 'b', 't1 correct control'], ['ALM P2', 'b', 't2 all correct'], ['VM/VAL P3', 'a', 't2 all correct'], ['VM/VAL P3', 'b', 't1 correct control'], ['VM/VAL P3', 'b', 't2 all correct']]
Readings (unit reading, neurons, trials) meeting all three in both ALM and VM/VAL: [('ALM P1', 'VM/VAL P3', 'a', 't2 all correct'), ('ALM P1', 'VM/VAL P3', 'b', 't1 correct control'), ('ALM P1', 'VM/VAL P3', 'b', 't2 all correct'), ('ALM P2', 'VM/VAL P3', 'a', 't2 all correct'), ('ALM P2', 'VM/VAL P3', 'b', 't1 correct control'), ('ALM P2', 'VM/VAL P3', 'b', 't2 all correct')]
Plotted range (spikes/s): -0.3 to 2.75
Result: reproduced. Under P3 for VM/VAL (all 258 VM/VAL units, all correct trials, t2) with ALM P2 (810 units, t2), both areas meet the criterion: selectivity emerges in the sample epoch (first window above twice its SEM centred at 0.1 s in ALM, 0.3 s in VM/VAL), is higher at the end of the delay than at its start (ALM 0.85 to 1.10, VM/VAL -0.05 to 0.38 spikes/s), and peaks in the response epoch (3.0 and 3.3 s). Six combinations of readings meet it in both areas, all with VM/VAL P3 (all neurons with t2, selective neurons with t1 and t2) and ALM P1 or P2. With P1 or P2, VM/VAL selectivity is lower at the end of the delay than at its start in every reading (P2, all neurons, t1: 0.83 against 0.17), and with P3, all neurons and t1 as well (0.57 against 0.53), so the delay increase in VM/VAL holds in 3 of 12 VM/VAL readings. The ALM peak lies 0.4 to 0.6 s after the go cue, not within its first 200 ms. Only trials whose spikes are stored to 4.1 s after pole onset enter.
wc-near-threshold¶
During the delay epoch the mean membrane potential of ALM neurons was close to spike threshold, 12.7 mV below it on average.
All 36 whole-cell recordings (each has more than 20 kept trials). Threshold: Vm at the kink of
convention V, mean over the cell's spikes (all spikes in the recording). Basal Vm: (a) 0 to 0.5 s
before trial onset (start_time), kept trials; (b) the delay epoch of kept control trials, spikes
clipped. Per cell threshold minus basal, then the mean over cells with its SEM. Variants: the
deposited kink (first sample of each clipped stretch plus 0.5 ms), and (a) without cell_21_1, whose
trial start times are shifted by one row.
rows = []
for c in cells:
t = c["trials"]
k = t.response.isin(loop.KEEP).values
rows.append({"cell": c["meta"]["cell"], "subject": c["meta"]["subject"],
"group": c["meta"]["description"].replace("ALM whole cell recording during ", ""), "kept_trials": int(k.sum()),
"spikes": c["meta"]["n_spikes"], "kink_at_window_start": round(float(np.mean(c["meta"]["kink_at_edge"])), 3),
"threshold": np.nanmean(c["meta"]["threshold_mV"]),
"threshold_kink_not_at_start": np.nanmean(c["meta"]["threshold_mV"][~c["meta"]["kink_at_edge"]]),
"threshold_deposited_kink": np.nanmean(c["meta"]["deposited_kink_mV"]),
"basal_a": np.nanmean(t.basal_vm[k]), "basal_b": np.nanmean(t.delay_vm[k & ~t.pi.values])})
wt = pd.DataFrame(rows)
wt["a"] = wt.threshold - wt.basal_a
wt["b"] = wt.threshold - wt.basal_b
wt["a_dep"] = wt.threshold_deposited_kink - wt.basal_a
wt["b_dep"] = wt.threshold_deposited_kink - wt.basal_b
wt["a_not_edge"] = wt.threshold_kink_not_at_start - wt.basal_a
print(wt.round(2).to_string(index=False))
res = []
for col in ("a", "b", "a_dep", "b_dep", "a_not_edge"):
for nm, d in (("all cells", wt), ("without cell_21_1", wt[wt.cell != "cell_21_1"])):
res.append({"reading": col, "cells": nm, "n": len(d), "mean_mV": round(d[col].mean(), 2), "sem_mV": round(d[col].std(ddof=1) / np.sqrt(len(d)), 2),
"within_tol": abs(d[col].mean() - 12.7) <= 0.7})
res = pd.DataFrame(res)
print(res.to_string(index=False))
print("Paper: 12.7 +/- 0.7 mV, n = 60 cells (60 +/- 6)")
print("Spikes whose kink is the first sample searched (1.5 ms before the peak), per cell: quantiles 0/50/100%",
np.round(np.percentile(wt.kink_at_window_start, [0, 50, 100]), 3), "| over all spikes:",
round(float(np.concatenate([c["meta"]["kink_at_edge"] for c in cells]).mean()), 3))
print("By group, reading a:", wt.groupby("group")["a"].mean().round(2).to_dict(), "| reading b:", wt.groupby("group")["b"].mean().round(2).to_dict())
cell subject group kept_trials spikes kink_at_window_start threshold threshold_kink_not_at_start threshold_deposited_kink basal_a basal_b a b a_dep b_dep a_not_edge
cell_20_1 anm300476 M1 photoinhibition 100 8399 0.01 -43.37 -43.37 -44.21 -48.10 -46.54 4.73 3.17 3.88 2.33 4.72
cell_21_1 anm300476 M1 photoinhibition 172 20842 0.04 -43.76 -43.58 -44.09 -47.88 -45.50 4.12 1.74 3.78 1.40 4.30
cell_22_1 anm300476 M1 photoinhibition 43 3002 0.01 -42.82 -42.82 -42.94 -47.15 -49.11 4.33 6.28 4.21 6.16 4.33
cell_23_1 anm300476 M1 photoinhibition 33 1243 0.01 -45.71 -45.69 -45.21 -54.15 -53.57 8.44 7.86 8.94 8.36 8.46
cell_25_1 anm300476 M1 photoinhibition 93 9301 0.07 -49.98 -49.64 -50.13 -51.22 -54.10 1.24 4.13 1.09 3.97 1.58
cell_29_1 anm300476 M1 photoinhibition 43 4578 0.01 -41.44 -41.43 -41.51 -45.52 -46.36 4.09 4.92 4.01 4.85 4.09
cell_55_1 anm322808 thalamus photoinhibition 75 1479 0.01 -51.69 -51.70 -52.40 -59.13 -57.23 7.43 5.53 6.72 4.82 7.43
cell_56_1 anm322808 thalamus photoinhibition 83 2745 0.01 -40.86 -40.86 -41.71 -53.57 -51.67 12.71 10.80 11.87 9.96 12.71
cell_57_1 anm322808 thalamus photoinhibition 45 1670 0.01 -41.21 -41.16 -41.78 -49.22 -49.64 8.01 8.44 7.44 7.86 8.06
cell_58_1 anm322808 thalamus photoinhibition 48 1426 0.01 -46.95 -46.90 -47.94 -53.68 -53.16 6.73 6.20 5.75 5.22 6.79
cell_59_1 anm322808 thalamus photoinhibition 57 2780 0.01 -43.59 -43.58 -44.10 -52.51 -49.54 8.92 5.95 8.40 5.44 8.92
cell_60_1 anm322808 thalamus photoinhibition 71 1932 0.01 -52.12 -52.07 -53.44 -58.57 -57.93 6.45 5.82 5.13 4.50 6.50
cell_44_1 anm323951 M1 photoinhibition 46 6777 0.01 -32.27 -32.26 -31.88 -37.34 -37.24 5.07 4.97 5.46 5.36 5.08
cell_45_1 anm323951 M1 photoinhibition 49 3767 0.01 -38.37 -38.38 -38.75 -48.70 -48.71 10.32 10.33 9.95 9.96 10.32
cell_46_1 anm323951 M1 photoinhibition 74 7862 0.01 -40.34 -40.33 -40.51 -47.03 -46.67 6.70 6.34 6.52 6.16 6.70
cell_45_2 anm323951 contra ALM photoinhibition 48 3187 0.01 -35.55 -35.51 -36.41 -44.07 -42.37 8.52 6.82 7.66 5.96 8.55
cell_46_2 anm323951 contra ALM photoinhibition 69 6810 0.01 -38.37 -38.35 -38.64 -44.40 -44.03 6.03 5.66 5.76 5.39 6.05
cell_40_2 anm323952 M1 photoinhibition 86 2051 0.01 -40.26 -40.24 -40.81 -51.17 -48.80 10.91 8.53 10.37 7.99 10.93
cell_39_1 anm323952 contra ALM photoinhibition 44 4120 0.03 -38.35 -38.24 -37.38 -43.33 -43.89 4.98 5.55 5.95 6.51 5.09
cell_40_1 anm323952 contra ALM photoinhibition 44 931 0.01 -51.84 -51.76 -52.18 -60.18 -58.98 8.34 7.15 8.00 6.80 8.42
cell_30_2 anm323953 M1 photoinhibition 38 653 0.01 -41.28 -41.08 -38.31 -47.95 -46.04 6.67 4.76 9.64 7.73 6.87
cell_30_1 anm323953 contra ALM photoinhibition 119 3264 0.01 -50.27 -50.25 -49.18 -56.77 -54.21 6.51 3.94 7.59 5.02 6.53
cell_31_1 anm323953 contra ALM photoinhibition 70 4718 0.01 -33.53 -33.49 -33.01 -38.56 -39.14 5.03 5.61 5.56 6.13 5.07
cell_32_1 anm323953 contra ALM photoinhibition 39 5853 0.01 -43.27 -43.31 -43.02 -47.84 -47.21 4.56 3.94 4.82 4.19 4.53
cell_34_1 anm323953 contra ALM photoinhibition 93 2576 0.01 -48.93 -48.89 -47.63 -53.78 -55.89 4.85 6.96 6.14 8.26 4.88
cell_35_1 anm323953 contra ALM photoinhibition 36 1937 0.01 -44.51 -44.44 -45.12 -53.20 -54.45 8.69 9.94 8.07 9.33 8.76
cell_47_1 anm324791 thalamus photoinhibition 61 3931 0.00 -53.61 -53.56 -57.57 -63.91 -62.12 10.30 8.51 6.34 4.55 10.35
cell_49_1 anm324791 thalamus photoinhibition 23 2297 0.01 -40.48 -40.46 -40.87 -48.71 -47.58 8.24 7.11 7.84 6.71 8.25
cell_50_1 anm324791 thalamus photoinhibition 82 9756 0.01 -36.28 -36.26 -37.35 -46.49 -43.42 10.20 7.13 9.14 6.07 10.23
cell_51_1 anm324791 thalamus photoinhibition 104 6750 0.01 -36.31 -36.30 -36.35 -43.91 -44.90 7.60 8.60 7.55 8.55 7.61
cell_52_1 anm324791 thalamus photoinhibition 121 8372 0.01 -36.74 -36.73 -36.73 -45.78 -43.57 9.04 6.83 9.04 6.84 9.05
cell_53_1 anm324791 thalamus photoinhibition 28 3736 0.01 -41.29 -41.29 -41.37 -46.14 -45.53 4.85 4.24 4.77 4.16 4.85
cell_64_1 anm324792 thalamus photoinhibition 55 1437 0.01 -43.63 -43.62 -44.11 -52.97 -52.46 9.35 8.84 8.87 8.35 9.35
cell_65_1 anm324792 thalamus photoinhibition 66 2752 0.01 -39.71 -39.69 -40.06 -48.91 -48.85 9.20 9.14 8.85 8.79 9.21
cell_66_1 anm324792 thalamus photoinhibition 57 1534 0.00 -49.42 -49.43 -51.79 -53.42 -53.87 4.01 4.45 1.64 2.08 3.99
cell_73_2 anm324792 thalamus photoinhibition 41 1907 0.01 -35.10 -35.05 -35.67 -46.65 -41.35 11.55 6.25 10.98 5.68 11.61
reading cells n mean_mV sem_mV within_tol
a all cells 36 7.19 0.43 False
a without cell_21_1 35 7.27 0.43 False
b all cells 36 6.46 0.35 False
b without cell_21_1 35 6.59 0.33 False
a_dep all cells 36 6.88 0.41 False
a_dep without cell_21_1 35 6.97 0.42 False
b_dep all cells 36 6.15 0.35 False
b_dep without cell_21_1 35 6.29 0.33 False
a_not_edge all cells 36 7.23 0.42 False
a_not_edge without cell_21_1 35 7.31 0.43 False
Paper: 12.7 +/- 0.7 mV, n = 60 cells (60 +/- 6)
Spikes whose kink is the first sample searched (1.5 ms before the peak), per cell: quantiles 0/50/100% [0.004 0.008 0.068] | over all spikes: 0.017
By group, reading a: {'M1 photoinhibition': 6.06, 'contra ALM photoinhibition': 6.39, 'thalamus photoinhibition': 8.41} | reading b: {'M1 photoinhibition': 5.73, 'contra ALM photoinhibition': 6.17, 'thalamus photoinhibition': 7.11}
Result: not reproduced. In the 36 deposited cells (paper 60), threshold minus basal Vm is 7.19 +/- 0.43 mV with the Methods' baseline (a) and 6.46 +/- 0.35 mV with the delay epoch (b), against 12.7 +/- 0.7 mV; with the deposited kink, 6.88 and 6.15 mV. Leaving out cell_21_1 changes these by at most 0.14 mV. The kink is the first sample searched (1.5 ms before the peak) in 1.7% of spikes (0.4 to 6.8% per cell); leaving those spikes out gives 7.23 mV. Neither the value nor the number of cells is within tolerance.
Whole-cell photoinhibition: wc-thalamus-hyperpolarization, wc-m1-small-effect, wc-contra-alm-small-effect, thalamus-stronger-than-cortex¶
wc-thalamus-hyperpolarization: Thalamus photoinhibition hyperpolarized ALM neurons by 18.7 mV, preventing spiking, with an onset 7.9 ms after photostimulus onset.
wc-m1-small-effect: Photoinhibition of M1 during the delay epoch caused only a slight hyperpolarization (3.3 mV) and a small drop in spike rate (1.4 spikes per s) in ALM neurons.
wc-contra-alm-small-effect: Photoinhibition of contralateral ALM barely changed the membrane potential of ALM neurons (1.4 mV).
thalamus-stronger-than-cortex: thalALM drives ALM more strongly than M1 or contralateral ALM, the other reciprocally connected structures tested.
The claims file (answer of 2026-10-20) gives two readings of the photostimulus onset: E, 1.3 s
after pole_in_time, and M, 0.1 s after pole_out_time. The files also hold the laser trace (data
check), whose onset is reading L, shown here pending question 7. Each measure is computed under all
three. Per cell: mean Vm (spikes clipped) 20 to 120 ms after the onset,
photostimulus minus control kept trials, and the spike rate in the same window; then mean and SEM
over cells. Onsets by convention V (V1 on the cell-mean trace, SEM from 1000 resamplings of cells;
V2 the mean of per-cell onsets) are shown under every reading; the claims file says the 7.9 ms
latency needs the laser time, which none of its readings gives, so the onset is not judged; it is
shown under L pending question 7.
rng = np.random.default_rng(801)
GROUPS = {"thalamus": "thalamus photoinhibition", "M1": "M1 photoinhibition", "contra ALM": "contra ALM photoinhibition"}
rows, per_cell, cell_rows = [], {}, []
for reading in loop.WC_ONSETS:
for g, desc in GROUPS.items():
cs = [c for c in cells if c["meta"]["description"].endswith(desc)]
dv, dsp, mean_tr, sp_c, sp_p = [], [], [], [], []
onsets = []
for c in cs:
t = c["trials"]
k = t.response.isin(loop.KEEP).values
pi, ct = k & t.pi.values, k & ~t.pi.values
dv.append(t[f"vm_pw_{reading}"][pi].mean() - t[f"vm_pw_{reading}"][ct].mean())
dsp.append((t[f"spikes_pw_{reading}"][pi].mean() - t[f"spikes_pw_{reading}"][ct].mean()) / (loop.PW[1] - loop.PW[0]))
sp_c.append(t[f"spikes_pw_{reading}"][ct].mean() / (loop.PW[1] - loop.PW[0]))
sp_p.append(t[f"spikes_pw_{reading}"][pi].mean() / (loop.PW[1] - loop.PW[0]))
tr = c["trace"][reading][pi].mean(0)
mean_tr.append(tr)
onsets.append(loop.vm_onset_ms(tr))
cell_rows.append({"onset_reading": reading, "group": g, "cell": c["meta"]["cell"], "subject": c["meta"]["subject"],
"dVm": round(dv[-1], 2), "dRate": round(dsp[-1], 2), "onset_ms": onsets[-1]})
dv, dsp, mean_tr, onsets = np.array(dv), np.array(dsp), np.array(mean_tr), np.array(onsets)
per_cell[(reading, g)] = dv
v1 = loop.vm_onset_ms(mean_tr.mean(0))
boot = [loop.vm_onset_ms(mean_tr[rng.integers(0, len(cs), len(cs))].mean(0)) for _ in range(1000)]
rows.append({"onset_reading": reading, "group": g, "cells": len(cs), "dVm_mean": round(dv.mean(), 2),
"dVm_sem": round(dv.std(ddof=1) / np.sqrt(len(dv)), 2), "dRate_mean": round(dsp.mean(), 2),
"dRate_sem": round(dsp.std(ddof=1) / np.sqrt(len(dsp)), 2), "rate_ctrl": round(np.mean(sp_c), 2), "rate_pi": round(np.mean(sp_p), 2), "V1_onset_ms": v1, "V1_sem": round(float(np.nanstd(boot)), 2),
"V2_onset_ms": round(float(np.nanmean(onsets)), 2), "V2_sem": round(float(np.nanstd(onsets, ddof=1) / np.sqrt(np.sum(~np.isnan(onsets)))), 2),
"cells_without_onset": int(np.isnan(onsets).sum())})
wcp = pd.DataFrame(rows)
print(wcp.to_string(index=False))
print("Paper: thalamus -18.7 +/- 1.1 mV, onset 7.9 +/- 1.7 ms (n = 16); M1 -3.3 +/- 1.0 mV and -1.4 +/- 0.6 spikes/s (n = 11);"
" contra ALM -1.4 +/- 0.5 mV (n = 9)")
tol = {"thalamus": [("dVm_mean", -18.7, 1.1), ("V1_onset_ms", 7.9, 1.7), ("V2_onset_ms", 7.9, 1.7)],
"M1": [("dVm_mean", -3.3, 1.0), ("dRate_mean", -1.4, 0.6)], "contra ALM": [("dVm_mean", -1.4, 0.5)]}
print("\nWithin tolerance, per onset reading (the thalamus onset of 7.9 ms is shown only under L, the laser trace, which the claims file does not list; E and M cannot judge it):")
for _, r in wcp.iterrows():
print(r.onset_reading, r.group, {q: bool(abs(r[q] - v) <= e) for q, v, e in tol[r.group] if r.onset_reading == "L" or "onset" not in q})
pc = pd.DataFrame(cell_rows)
print("\nPer cell under L:")
print(pc[pc.onset_reading == "L"].drop(columns="onset_reading").to_string(index=False))
print("By mouse under L (mean dVm):", pc[pc.onset_reading == "L"].groupby(["group", "subject"]).dVm.agg(["size", "mean"]).round(2).to_dict("index"))
wl = wcp[wcp.onset_reading == "L"].set_index("group")
print("\nindirect-pathway-too-slow, the part the dandiset holds: ALM Vm onset after M1 photoinhibition under L, V1",
wl.loc["M1", "V1_onset_ms"], "+/-", wl.loc["M1", "V1_sem"], "ms, V2", wl.loc["M1", "V2_onset_ms"], "+/-", wl.loc["M1", "V2_sem"],
"ms (paper 13.9 +/- 3.2); no M1 neurons are deposited")
print("\nthalamus-stronger-than-cortex, per reading: (a) thalamus mean more negative than M1 and contra ALM; (b) as (a), two-sided t-tests over cells")
for reading in loop.WC_ONSETS:
th, m1, ca = per_cell[(reading, "thalamus")], per_cell[(reading, "M1")], per_cell[(reading, "contra ALM")]
out = {"reading": reading, "a": bool(th.mean() < m1.mean() and th.mean() < ca.mean())}
for ev in (True, False):
p1 = stats.ttest_ind(th, m1, equal_var=ev).pvalue
p2 = stats.ttest_ind(th, ca, equal_var=ev).pvalue
out[f"p_vs_M1_{'Student' if ev else 'Welch'}"] = p1
out[f"p_vs_contra_{'Student' if ev else 'Welch'}"] = p2
print(out)
onset_reading group cells dVm_mean dVm_sem dRate_mean dRate_sem rate_ctrl rate_pi V1_onset_ms V1_sem V2_onset_ms V2_sem cells_without_onset
E thalamus 16 0.31 0.52 -0.09 0.30 3.16 3.07 5.90 18.14 18.79 4.75 1
E M1 11 -0.09 0.42 0.94 1.36 5.36 6.30 127.85 47.61 19.38 7.11 1
E contra ALM 9 0.86 0.41 -0.75 0.68 5.28 4.53 9.15 32.81 53.20 20.82 2
M thalamus 16 -16.83 1.24 -3.08 0.99 3.14 0.06 9.05 0.55 9.33 0.67 0
M M1 11 -2.75 1.30 0.76 1.29 4.99 5.75 2.40 13.88 27.07 12.53 1
M contra ALM 9 -1.60 0.64 -2.19 1.30 4.79 2.60 6.45 4.78 14.79 4.48 1
L thalamus 16 -13.68 1.14 -3.10 1.03 3.24 0.14 0.40 3.27 9.60 1.98 0
L M1 11 -3.26 1.08 -0.01 1.16 4.82 4.81 8.70 4.60 27.30 6.61 0
L contra ALM 9 -1.38 0.58 -2.08 1.31 4.57 2.50 23.70 2.66 19.68 2.46 0
Paper: thalamus -18.7 +/- 1.1 mV, onset 7.9 +/- 1.7 ms (n = 16); M1 -3.3 +/- 1.0 mV and -1.4 +/- 0.6 spikes/s (n = 11); contra ALM -1.4 +/- 0.5 mV (n = 9)
Within tolerance, per onset reading (the thalamus onset of 7.9 ms is shown only under L, the laser trace, which the claims file does not list; E and M cannot judge it):
E thalamus {'dVm_mean': False}
E M1 {'dVm_mean': False, 'dRate_mean': False}
E contra ALM {'dVm_mean': False}
M thalamus {'dVm_mean': False}
M M1 {'dVm_mean': True, 'dRate_mean': False}
M contra ALM {'dVm_mean': True}
L thalamus {'dVm_mean': False, 'V1_onset_ms': False, 'V2_onset_ms': True}
L M1 {'dVm_mean': True, 'dRate_mean': False}
L contra ALM {'dVm_mean': True}
Per cell under L:
group cell subject dVm dRate onset_ms
thalamus cell_55_1 anm322808 -9.48 0.00 0.40
thalamus cell_56_1 anm322808 -13.22 -2.19 17.70
thalamus cell_57_1 anm322808 -6.22 -0.36 6.05
thalamus cell_58_1 anm322808 -20.38 -1.25 11.05
thalamus cell_59_1 anm322808 -21.72 -2.35 8.55
thalamus cell_60_1 anm322808 -15.48 0.00 0.40
thalamus cell_47_1 anm324791 -12.07 -0.21 16.85
thalamus cell_49_1 anm324791 -17.65 -5.71 0.45
thalamus cell_50_1 anm324791 -18.18 -9.90 14.75
thalamus cell_51_1 anm324791 -12.63 -1.67 11.65
thalamus cell_52_1 anm324791 -13.98 -8.33 20.75
thalamus cell_53_1 anm324791 -11.29 -13.33 24.50
thalamus cell_64_1 anm324792 -4.69 0.02 0.40
thalamus cell_65_1 anm324792 -13.99 0.00 11.60
thalamus cell_66_1 anm324792 -14.29 -0.56 8.05
thalamus cell_73_2 anm324792 -13.64 -3.75 0.40
M1 cell_20_1 anm300476 -3.11 -2.79 18.25
M1 cell_21_1 anm300476 -3.51 4.60 29.75
M1 cell_22_1 anm300476 -0.01 -0.43 23.80
M1 cell_23_1 anm300476 -3.93 -1.72 24.45
M1 cell_25_1 anm300476 0.10 8.24 16.40
M1 cell_29_1 anm300476 1.02 1.01 18.40
M1 cell_44_1 anm323951 -0.96 0.20 90.00
M1 cell_45_1 anm323951 -8.39 -6.67 21.15
M1 cell_46_1 anm323951 -1.24 -1.26 32.70
M1 cell_40_2 anm323952 -9.96 -0.40 6.80
M1 cell_30_2 anm323953 -5.90 -0.87 18.55
contra ALM cell_45_2 anm323951 -1.99 -2.08 29.45
contra ALM cell_46_2 anm323951 -0.62 1.05 14.35
contra ALM cell_39_1 anm323952 0.15 -8.63 15.35
contra ALM cell_40_1 anm323952 -2.69 0.00 26.20
contra ALM cell_30_1 anm323953 -4.21 -0.57 21.15
contra ALM cell_31_1 anm323953 0.97 0.90 20.30
contra ALM cell_32_1 anm323953 -1.14 -8.98 4.65
contra ALM cell_34_1 anm323953 -3.16 -0.36 21.60
contra ALM cell_35_1 anm323953 0.30 0.00 24.05
By mouse under L (mean dVm): {('M1', 'anm300476'): {'size': 6, 'mean': -1.57}, ('M1', 'anm323951'): {'size': 3, 'mean': -3.53}, ('M1', 'anm323952'): {'size': 1, 'mean': -9.96}, ('M1', 'anm323953'): {'size': 1, 'mean': -5.9}, ('contra ALM', 'anm323951'): {'size': 2, 'mean': -1.3}, ('contra ALM', 'anm323952'): {'size': 2, 'mean': -1.27}, ('contra ALM', 'anm323953'): {'size': 5, 'mean': -1.45}, ('thalamus', 'anm322808'): {'size': 6, 'mean': -14.42}, ('thalamus', 'anm324791'): {'size': 6, 'mean': -14.3}, ('thalamus', 'anm324792'): {'size': 4, 'mean': -11.65}}
indirect-pathway-too-slow, the part the dandiset holds: ALM Vm onset after M1 photoinhibition under L, V1 8.7 +/- 4.6 ms, V2 27.3 +/- 6.61 ms (paper 13.9 +/- 3.2); no M1 neurons are deposited
thalamus-stronger-than-cortex, per reading: (a) thalamus mean more negative than M1 and contra ALM; (b) as (a), two-sided t-tests over cells
{'reading': 'E', 'a': False, 'p_vs_M1_Student': np.float64(0.5843269846903805), 'p_vs_contra_Student': np.float64(0.4735437434688235), 'p_vs_M1_Welch': np.float64(0.556445035758204), 'p_vs_contra_Welch': np.float64(0.4091498760592985)}
{'reading': 'M', 'a': True, 'p_vs_M1_Student': np.float64(5.235938752095844e-08), 'p_vs_contra_Student': np.float64(7.851743860033135e-09), 'p_vs_M1_Welch': np.float64(5.046219327045784e-08), 'p_vs_contra_Welch': np.float64(3.41188328278441e-10)}
{'reading': 'L', 'a': True, 'p_vs_M1_Student': np.float64(1.2611897901878665e-06), 'p_vs_contra_Student': np.float64(8.080084514863154e-08), 'p_vs_M1_Welch': np.float64(6.725331963528889e-07), 'p_vs_contra_Welch': np.float64(3.719981175503937e-09)}
Result: wc-thalamus-hyperpolarization not reproduced; wc-m1-small-effect not reproduced; wc-contra-alm-small-effect reproduced; thalamus-stronger-than-cortex reproduced. Under onset E the window lies before the change (data check) and no value is within tolerance: thalamus photoinhibition changes Vm by +0.31 mV, M1 by -0.09 mV and +0.94 spikes/s, contralateral ALM by +0.86 mV, and thalamus is not more negative than the others (p = 0.41 to 0.58).
Under the laser onset (L), thalamus photoinhibition hyperpolarizes ALM neurons by -13.68 +/- 1.14 mV, outside -18.7 +/- 1.1, and spiking stops (3.24 to 0.14 spikes/s); every one of the 16 cells hyperpolarizes (-4.7 to -21.7 mV). The onset is 9.60 +/- 1.98 ms by V2, within 7.9 +/- 1.7, and 0.40 ms by V1, which is not (five cells give 0.40 to 0.45 ms, at the laser onset itself). Under M, whose window starts 28 ms later in these cells, the change is -16.83 +/- 1.24 mV, also outside. The hyperpolarization and the loss of spiking are present at 73% (L) or 90% (M) of the paper's size; no reading puts both values within tolerance.
M1 photoinhibition changes Vm by -3.26 +/- 1.08 mV under L (-2.75 under M), within -3.3 +/- 1.0, and the spike rate by -0.01 +/- 1.16 spikes/s under L (+0.76 under M), outside -1.4 +/- 0.6: the slight hyperpolarization holds, the drop in spike rate is not seen. Contralateral ALM photoinhibition changes Vm by -1.38 +/- 0.58 mV under L and -1.60 +/- 0.64 under M, within -1.4 +/- 0.5. Thalamus photoinhibition hyperpolarizes more than M1 or contralateral ALM photoinhibition under L (p = 1.3e-6 and 8.1e-8, Student; 6.7e-7 and 3.7e-9, Welch) and under M (p = 5e-8 and 8e-9, Student), so readings (a) and (b) both hold. The outcomes are the same under M and under L.
Neither wc-thalamus-hyperpolarization nor wc-m1-small-effect has a partial clause, and no reading meets either criterion in full (see Changes of outcome, before the Summary). The third verification round agreed.
For indirect-pathway-too-slow (blocked: no M1 neurons are deposited), the ALM onset after M1 photoinhibition under L is 8.7 +/- 4.6 ms (V1) or 27.3 +/- 6.6 ms (V2), against 13.9 +/- 3.2.
Claims blocked from the start¶
| Claim | Reason | Where looked |
|---|---|---|
| fig1d-thalamus-photoinhibition-bias | data missing | The only Gad2 sessions (Ai35D x GAD2_Cre, 9 thalamic recordings) carry no photostimulus trial; there are no behaviour-only sessions. |
| gad2-thalamus-reduction | metadata insufficient | The 84 units of the Gad2 sessions match the n of Extended Data Fig. 2c, but no trial is marked as photostimulated (period 'N/A', power 0 everywhere). |
| muscimol-vmval-bias | data missing | No muscimol sessions, no infusion metadata. |
| wc-loss-of-excitation | data missing | No non-behaving recordings and no current-injection experiments with TRN photostimulation (all 36 cells are behaving; the injected current is a holding current). |
| indirect-pathway-too-slow | data missing | No whole-cell recordings of M1 neurons (every cell is in ALM). Its other onset, ALM after M1 photoinhibition, is shown under the laser onset in the whole-cell section; the claim needs both. |
| conduction-delay | data missing | No Olig3-cre x Ai32 recordings. |
| latency-model-t3 | methods underspecified | A simulation whose parameters are in the Supplementary Information, not in the text the claims were written from. |
| fig5-weak-photoinhibition-rates | data missing | No ALM session with 0.5 mW thalamus photoinhibition (VGAT ALM sessions use 10 mW only); the dandiset holds 966 of the 1214 ALM units, consistent with the Fig. 5 sessions (JRclust sorting) being absent. |
| fig5c-selectivity-reduced | data missing | Depends on fig5-weak-photoinhibition-rates. |
| network-models | methods underspecified | A modelling result specified in the Supplementary Information. |
| anatomy-reciprocal-thalamus | data missing | No histology or cell counts in the dandiset. |
| alm-gabaergic-no-thalamic-projection | data missing | No histology in the dandiset. |
print("Genotypes in the extracellular files:", dict(Counter(s["meta"]["genotype"] for s in sessions)))
print("Genotypes in the whole-cell files:", dict(Counter(c["meta"]["genotype"] for c in cells)))
gad = [s for s in sessions if "GAD2" in s["meta"]["genotype"]]
print("Gad2 sessions:", len(gad), "| units:", sum(len(s["units"]) for s in gad), "| photostimulus labels:",
dict(Counter(x for s in gad for x in s["trials"].stim)), "| photostimulus sites:", {s["meta"]["ogen_site"] for s in gad})
print("Photostimulus powers in ALM sessions with thalamic photostimulation:",
dict(Counter((s["meta"]["genotype"], x) for s in sessions if s["meta"]["region"] == "ALM" and s["meta"]["ogen_site"] == "Thalamus"
for x in s["trials"].stim if x != "none")))
print("Whole-cell recording locations:", dict(Counter(c["meta"]["cell_location"].get("brain_region") for c in cells)),
"| descriptions:", dict(Counter(c["meta"]["description"] for c in cells)))
print("Session descriptions (extracellular):", dict(Counter(s["meta"]["path"].split("_")[-1] for s in sessions)))
st_alm = pd.to_datetime(pd.Series([s["meta"]["session_start"][:19] for s in sessions if s["meta"]["region"] == "ALM"]))
print("ALM sessions: session_start_time from", st_alm.min(), "to", st_alm.max(), "| distinct dates", st_alm.dt.date.nunique(),
"| ALM sessions per mouse:", dict(Counter(s["meta"]["subject"] for s in sessions if s["meta"]["region"] == "ALM")))
print("Units table description:", {s["meta"]["units_description"] for s in sessions}, "| unit_y description:", {s["meta"]["unit_y_description"] for s in sessions})
Genotypes in the extracellular files: {'Ai35D x GAD2_Cre': 9, 'Ai32 x PV-Cre': 87, 'C57BL6': 12, 'VGAT-ChR2-EYFP': 29}
Genotypes in the whole-cell files: {'Ai32 x PV-Cre': 20, 'VGAT-ChR2-EYFP': 16}
Gad2 sessions: 9 | units: 84 | photostimulus labels: {'none': 2993} | photostimulus sites: {None}
Photostimulus powers in ALM sessions with thalamic photostimulation: {('Ai32 x PV-Cre', 'Thalamus:delay:10'): 531, ('Ai32 x PV-Cre', 'Thalamus:delay:22.5'): 380, ('Ai32 x PV-Cre', 'Thalamus:delay:45'): 115, ('VGAT-ChR2-EYFP', 'Thalamus:delay:10'): 891}
Whole-cell recording locations: {'ALM': 36} | descriptions: {'ALM whole cell recording during M1 photoinhibition': 11, 'ALM whole cell recording during thalamus photoinhibition': 16, 'ALM whole cell recording during contra ALM photoinhibition': 9}
Session descriptions (extracellular): {'ecephys.nwb': 21, 'ecephys+ogen.nwb': 116}
ALM sessions: session_start_time from 2017-06-27 09:38:44 to 2017-06-27 09:43:22 | distinct dates 1 | ALM sessions per mouse: {'anm00261335': 2, 'anm00264941': 1, 'anm00264942': 6, 'anm00264943': 5, 'anm00264944': 5, 'anm00267975': 6, 'anm00279338': 4, 'anm00322805': 5, 'anm00322807': 8, 'anm00323634': 5}
Units table description: {'Autogenerated by NWBFile'} | unit_y description: {'y-coordinate of this unit (mm)'}
Changes of outcome¶
The second verification found that nine claims were given partially reproduced under criteria that have no partial clause: each says only "Reproduced when ...". The cell below counts, for each of them, the readings of the claims file that meet the whole criterion, from the tables above. Reading L of the whole-cell claims (the laser trace, question 7) is not in the claims file and is counted apart.
# Readings that meet the whole criterion, per claim whose criterion has no partial clause.
full = {}
cls = {"P1": (len(alm), len(PYR["P1"]), int((alm.width_ms < 0.4).sum())),
"P2": (len(alm), len(PYR["P2"]), int((alm.cell_type == "Narrow width spiking").sum()))}
ok = [all(abs(v - r) <= e for v, (r, e) in zip(c, ((1214, 121), (1006, 101), (166, 17)))) for c in cls.values()]
full["alm-unit-classes"] = (sum(ok), len(ok))
# L1 has no label in the files, so only L2 can be counted.
ok = [all(abs(v - r) <= e for v, (r, e) in zip((len(thal), len(THP[p]), int((thal.vmval_d < 0.4).sum()), len(VMVAL[p])),
((790, 79), (672, 67), (313, 31), (295, 30)))) for p in THP]
full["thalamus-unit-classes"] = (sum(ok), len(ok))
full["fig2e-alm-selectivity"] = (int(f2e.within_tol.sum()), len(f2e))
full["fig2f-vmval-selectivity"] = (int((f2f.counts_within_tol & (f2f.chi_ok_i | f2f.chi_ok_ii)).sum()), len(f2f))
f3f_r = f3f[f3f.units.isin(["P1", "P2"])]
full["fig3f-thalamus-silenced"] = (int(f3f_r[[c for c in f3f_r if c.startswith("within_tol")]].any(axis=1).sum()), len(f3f_r))
full["fig6d-localized"] = (int((f6d.counts_ok & f6d.value_ok_any_Q & f6d.near_lower_than_far_Q1).sum()), len(f6d))
sn_r = sn[sn.units.isin(["P1", "P2"])]
full["snr-photoinhibition"] = (int(sn_r.within_tol.sum()), len(sn_r))
for claim, group, checks in (("wc-thalamus-hyperpolarization", "thalamus", [("dVm_mean", -18.7, 1.1)]),
("wc-m1-small-effect", "M1", [("dVm_mean", -3.3, 1.0), ("dRate_mean", -1.4, 0.6)])):
for reading in ("E", "M", "L"):
r = wcp[(wcp.onset_reading == reading) & (wcp.group == group)].iloc[0]
print(claim, reading, {q: (r[q], bool(abs(r[q] - v) <= e)) for q, v, e in checks})
# The Vm change is a necessary part of either criterion; the 7.9 ms onset cannot be judged under E or M.
ok = [all(abs(wcp[(wcp.onset_reading == rd) & (wcp.group == group)].iloc[0][q] - v) <= e for q, v, e in checks) for rd in ("E", "M")]
full[claim] = (sum(ok), len(ok))
print()
for claim, (n_ok, n) in full.items():
print(f"{claim}: {n_ok} of {n} readings meet the whole criterion")
wc-thalamus-hyperpolarization E {'dVm_mean': (np.float64(0.31), False)}
wc-thalamus-hyperpolarization M {'dVm_mean': (np.float64(-16.83), False)}
wc-thalamus-hyperpolarization L {'dVm_mean': (np.float64(-13.68), False)}
wc-m1-small-effect E {'dVm_mean': (np.float64(-0.09), False), 'dRate_mean': (np.float64(0.94), False)}
wc-m1-small-effect M {'dVm_mean': (np.float64(-2.75), True), 'dRate_mean': (np.float64(0.76), False)}
wc-m1-small-effect L {'dVm_mean': (np.float64(-3.26), True), 'dRate_mean': (np.float64(-0.01), False)}
alm-unit-classes: 0 of 2 readings meet the whole criterion
thalamus-unit-classes: 0 of 2 readings meet the whole criterion
fig2e-alm-selectivity: 0 of 32 readings meet the whole criterion
fig2f-vmval-selectivity: 0 of 64 readings meet the whole criterion
fig3f-thalamus-silenced: 0 of 8 readings meet the whole criterion
fig6d-localized: 0 of 12 readings meet the whole criterion
snr-photoinhibition: 0 of 16 readings meet the whole criterion
wc-thalamus-hyperpolarization: 0 of 2 readings meet the whole criterion
wc-m1-small-effect: 0 of 2 readings meet the whole criterion
No reading of the claims file meets the whole criterion of any of the nine claims. For wc-thalamus-hyperpolarization the Vm change alone is outside tolerance under E and M (and under L, which is not a reading of the claims file), so the onset, which no listed reading can judge, does not bear on the outcome; for wc-m1-small-effect the spike-rate change is outside tolerance under every onset. Partial agreement (class fractions, selective fractions, near-complete silencing, the order of the distance groups, the SNr counts under P1, the Vm change of M1 photoinhibition) is not an outcome these criteria define. The outcome of each of the nine claims was proposed to change from partially reproduced to not reproduced after the second verification; the third verification round agreed with all nine, and each is now not reproduced.
Summary¶
| Claim | Outcome |
|---|---|
| task-performance | not reproduced |
| fig1c-alm-photoinhibition-bias | reproduced |
| fig1d-thalamus-photoinhibition-bias | blocked: data missing |
| gad2-thalamus-reduction | blocked: metadata insufficient |
| vgat-thalamus-reduction | reproduced |
| muscimol-vmval-bias | blocked: data missing |
| alm-unit-classes | not reproduced |
| fig2e-alm-selectivity | not reproduced |
| fig2e-alm-contra-ipsi-balance | reproduced |
| thalamus-unit-classes | not reproduced |
| fig2f-vmval-selectivity | not reproduced |
| fig3-alm-collapse | partially reproduced |
| fig3d-all-layers | blocked: methods underspecified |
| fig3b-selectivity-types-equally-inhibited | blocked: methods underspecified |
| fig3f-thalamus-silenced | not reproduced |
| fig3-onset-latencies | reproduced |
| wc-near-threshold | not reproduced |
| wc-thalamus-hyperpolarization | not reproduced |
| wc-loss-of-excitation | blocked: data missing |
| wc-m1-small-effect | not reproduced |
| wc-contra-alm-small-effect | reproduced |
| thalamus-stronger-than-cortex | reproduced |
| indirect-pathway-too-slow | blocked: data missing |
| conduction-delay | blocked: data missing |
| latency-model-t3 | blocked: methods underspecified |
| fig5-weak-photoinhibition-rates | blocked: data missing |
| fig5c-selectivity-reduced | blocked: data missing |
| network-models | blocked: methods underspecified |
| fig6c-vmval-reduced | not reproduced |
| fig6b-selectivity-types-equally-inhibited | blocked: methods underspecified |
| fig6c-unmodulated-less-selective | partially reproduced |
| fig6d-localized | not reproduced |
| vm1-photoinhibition-no-effect | blocked: metadata insufficient |
| fig6e-onset-latencies | not reproduced |
| snr-photoinhibition | not reproduced |
| snr-unit-properties | blocked: data missing (selectivity part within tolerance under P1) |
| anatomy-reciprocal-thalamus | blocked: data missing |
| selectivity-time-course | reproduced |
| alm-gabaergic-no-thalamic-projection | blocked: data missing |
7 reproduced, 2 partially reproduced, 13 not reproduced, 17 blocked. Nine claims, whose criteria have no partial clause and which no reading meets in full, were changed from partially reproduced to not reproduced after the third verification round agreed (see Changes of outcome above).
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. Unit classes are run under the readings of convention C: widths computed on the deposited waveforms (P1, the Methods' rule), which are too short for the width thresholds, and the files' labels (P2), which match the paper's class sizes; for the thalamic claims of Fig. 2 and 6, all units (P3) as well. Each unit's trials are taken as those from the trial holding its first spike to the trial holding its last, as the files give no observation interval per unit, and a window that ends after a trial's stored spikes is counted as missing for that trial. Behaviour is measured in the recording sessions, not in behaviour sessions. The VM/VAL centre is the claims file's proxy (reading L2). Distances from the thalamic fibre use the files' single nominal site. The whole-cell claims are judged under the two onsets the claims file allows (E and M) and under the onset of the deposited laser trace (L), which the claims file did not know of (question 7); the outcomes are the same under M and L, nothing holds under E, and the onset latency of 7.9 ms cannot be judged under E or M, the readings the claims file lists; under L, shown pending question 7, it is 9.60 ms by V2 and 0.40 ms by V1. The (0.5, 0.5) galvo site is taken as vM1 only to show what the data give, compared with ALM at 1.5 mW.
What was found about the dataset.
- The dandiset holds 966 of the paper's 1214 ALM units (47 of 57 sessions), 789 of 790 thalamic units and 181 of 227 SNr units (apparently only the putative GABAergic ones: 171 of the 181 by computed width, 146 by the files' label). Behaviour-only, muscimol, weak photoinhibition (Fig. 5), non-behaving and M1 whole-cell recordings and histology are absent.
- The mean waveforms are cut short (29 to 38 samples at 25 kHz), so trough-to-peak widths longer
than about 0.5 ms cannot be measured; the
cell_typelabels carry the classification instead, without a stated rule. - Spikes are stored only from the trial start to about 4.8 s after pole onset, although trials last about 9 s, so in trials with long delays the response epoch is not stored; most units hold spikes over only part of their session, with no per-unit observation interval.
- The whole-cell trial tables leave the photostimulus columns empty; the laser time is in an
OptogeneticSeries described only as 'no description'. It starts between the two delay onsets
the trial times allow, 0.10 to 0.17 s after E and 0.03 to 0.10 s before M (1.40 s after pole
onset in one mouse, 1.47 s in the others). One whole-cell trial table has start and stop times shifted by one row. The Vm
conversion(0.001 with unit 'mV') contradicts the values, which are in mV. - Every electrode group, in thalamus and SNr too, carries the ALM coordinates; the SNr units of one
mouse are at AP -1.56 mm; there is no VM/VAL location label;
unit_yhas no stated reference, so whether it is depth below the pia is not said. Session start times are the date of conversion (all on 2017-06-27), not of recording, so the missing ALM sessions cannot be identified by date. - The Gad2 sessions (genotype given as
Ai35D x GAD2_Cre, an Arch line, where the paper used Gad2-IRES-Cre with ChR2 in TRN) carry no photostimulus trial, though their 84 units match the optrode units of Extended Data Fig. 2c. The VGAT thalamic sessions give 2.23 mW where the paper gives 10 mW. PV-Cre x Ai32 ALM sessions with thalamic photostimulation at 10 to 45 mW and C57BL6 thalamic sessions are not described in the paper; the (0.5, 0.5) photostimulation site is not named.
print(f"Total run time: {time.time() - started:.0f} s")
Total run time: 464 s