Reproducing Li et al. (2015) from dandiset 000010¶

Paper. Li N, Chen T-W, Guo ZV, Gerfen CR, Svoboda K (2015). A motor cortex circuit for motor planning and movement. Nature 519, 51-56. doi:10.1038/nature14178. The claims file (claims/000010/10.1038-nature14178.yaml) was written from the version of record (main text, Methods and Extended Data legends) and checked by a second reader.

Dandiset. DANDI:000010, version 0.220126.1905. The paper cites no DANDI version (it predates the deposit and names the CRCNS alm-1 data). This is the only published version, and so the most recent one; the draft lists the same 158 assets and total size (its later modification was not examined further). The analysis streams the published version.

What the dandiset holds. 99 extracellular sessions from 19 mice (left ALM, sorted units, trial tables, behavioural events, a lick trace and, in 75 sessions, the laser power of the optogenetic tagging light delivered during behavioural trials) and 59 two-photon imaging planes from 4 mice (raw and neuropil fluorescence, ROI masks, PT/IT labels, trial tables). It holds no data from the photoinhibition and photoactivation experiments and no histology.

Questions. Seven questions were asked of a reader (questions/000010.yaml) and answered. The paper settles none of them; each answer wrote the readings the paper allows into the claims file, and every claim they touch is run under those readings here: the rule for a reaction time, whether no-lick trials count, three details of the population dynamics, the length of the imaging response epoch, the layer 5 selection, the trials with tagging light (readings t1 and t2 for every electrophysiology claim of Figs 2 and 3), and the rule for the antidromic spike.

Claim Tested Why not
behaviour-performance yes
fig1d-delay-inhibition-ipsi-bias no data missing: no photoinhibition mice or photostimulation trials
fig1d-sample-inhibition-smaller no data missing (as above)
units-classification yes
fig2-selective-fraction yes
fig2d-preparatory-perimovement yes
fig2c-selectivity-time-course yes
fig2c-population-dynamics yes
ed2c-error-trials-predict-choice yes
fig2e-no-contra-bias-spikes yes
ed1-fast-spiking-properties yes
tagging-counts yes, in part the failed-collision classes carry no label
antidromic-latencies yes, in part the activated neurons that failed the collision test carry no label
fig3e-pt-it-both-activity-types yes
fig3f-pt-contra-preferring yes
fig3f-pt-contra-selectivity yes
imaging-selectivity-counts yes
fig4f-spatially-intermingled yes
fig4h-pt-contra-imaging no metadata insufficient: no plane depth, so no layer 5 selection; all labelled cells are shown as a variant
reaction-time no methods underspecified: the threshold the paper leaves open sets the value; the readings are run and shown
fig5b-pt-delay-activation-contra-bias no data missing: no photostimulation trials
fig5b-pt-sample-activation-contra-bias no data missing (as above)
ed6b-pt-dose-response no data missing (as above)
ed6d-it-activation-inconsistent no data missing (as above)
ed3e-high-power-contra-licking no data missing (as above)
ed7-pt-activation-persistent no data missing (as above)
ed3-anatomy-it-pt-separate no data missing: no histology
In [1]:
import time
import warnings

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats

import alm

warnings.filterwarnings("ignore", category=RuntimeWarning)
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 200)

started = time.time()
assets = alm.list_assets()
meta = alm.version_metadata()
E = alm.load_all(assets, "ephys")
O = alm.load_all(assets, "ophys")
print(f"dandiset {alm.DANDISET} version {alm.VERSION}: {len(assets)} assets; loaded {len(E)} extracellular "
      f"sessions and {len(O)} imaging planes in {time.time() - started:.0f} s")
dandiset 000010 version 0.220126.1905: 158 assets; loaded 99 extracellular sessions and 59 imaging planes in 477 s

Data check¶

Each check of the metadata against the values is stated under its output.

In [2]:
print("Archive summary:", meta["assetsSummary"]["numberOfSubjects"], "subjects,",
      meta["assetsSummary"]["numberOfFiles"], "files; approaches:",
      [a["name"] for a in meta["assetsSummary"]["approach"]])
print("Related resource:", [r["identifier"] for r in meta.get("relatedResource", [])])
print(assets.groupby("kind").agg(files=("path", "size"), subjects=("subject", "nunique"),
                                 GB=("size", lambda x: round(x.sum() / 1e9, 1))).to_string())
info = pd.DataFrame([dict(kind="ephys", subject=s["subject"], genotype=s["genotype"], region=",".join(s["region"]),
                          optogenetics=",".join(s["optogenetics"])) for s in E] +
                    [dict(kind="ophys", subject=s["subject"], genotype=s["genotype"], region=s["plane_location"],
                          optogenetics="") for s in O])
print("\nSessions per subject, with genotype, recording region and optogenetic sites named in the files:")
print(info.groupby(["kind", "subject", "genotype", "region", "optogenetics"]).size().rename("sessions").to_string())
print("\nData objects in the files (acquisition, stimulus, processing, analysis):")
print(pd.Series([g for s in E + O for g in s["groups"]]).value_counts().to_string())
print("\nTrial table columns:", sorted({tuple(s["trial_colnames"]) for s in E + O}))
print("Unit table columns:", sorted({tuple(s["unit_colnames"]) for s in E}))
print("ROI table columns:", sorted({tuple(s["roi_colnames"]) for s in O}))
print("Imaging plane descriptions:", sorted({s["plane_description"] for s in O}))
Archive summary: 23 subjects, 158 files; approaches: ['electrophysiological approach', 'behavioral approach', 'microscopy approach; cell population imaging']
Related resource: ['doi:10.1038/nature14178']
       files  subjects    GB
kind                        
ephys     99        19  36.2
ophys     59         4   3.8

Sessions per subject, with genotype, recording region and optogenetic sites named in the files:
kind   subject  genotype              region   optogenetics           
ephys  210861   sim1-cre              left M2  left ALM,left PONS          3
       210862   sim1-cre              left M2  left ALM,left PONS          3
       210863   sim1-cre              left M2  left ALM,left PONS          3
       214427   rbp4-cre              left M2  bilateral ALM,left PONS     4
       214429   rbp4-cre              left M2  bilateral ALM,left PONS     4
       214430   rbp4-cre              left M2  bilateral ALM,left PONS     4
       218453   chr2-eyfp x tlx-cre   left M2                              5
       218457   chr2-eyfp x tlx-cre   left M2                              6
       218693   chr2-eyfp x sim1-cre  left M2                              3
       219030   sim1-cre              left M2  left ALM,left PONS          5
       219031   sim1-cre              left M2  left ALM,left PONS          5
       219033   sim1-cre              left M2  left ALM,left PONS          7
       219036   sim1-cre              left M2  left ALM,left PONS          7
       219037   sim1-cre              left M2  left ALM,left PONS          7
       219038   sim1-cre              left M2  left ALM,left PONS          8
       219247   tlx-cre               left M2  bilateral ALM               5
       219248   tlx-cre               left M2  bilateral ALM               7
       219253   tlx-cre               left M2  bilateral ALM              10
       221977   tlx-cre               left M2  bilateral ALM               3
ophys  216219   Rosa-lsl-tdtomato     ALM                                 21
       217951                         ALM                                 11
       221541                         ALM                                 13
       226244                         ALM                                 14

Data objects in the files (acquisition, stimulus, processing, analysis):
acquisition/BehavioralEvents                           99
acquisition/BehavioralTimeSeries                       99
acquisition/images                                     59
processing/behavior/BehavioralEvents                   59
processing/ophys/Fluorescence                          59
processing/ophys/ImageSegmentation                     59
stimulus/presentation/left PONS_aom_input_trace        48
stimulus/presentation/left PONS_laser_power            48
stimulus/presentation/bilateral ALM_aom_input_trace    36
stimulus/presentation/bilateral ALM_laser_power        36

Trial table columns: [('start_time', 'stop_time', 'task', 'task_protocol', 'trial_instruction', 'early_lick', 'outcome')]
Unit table columns: [('sampling_rate', 'quality', 'posx', 'posy', 'cell_type', 'spike_times', 'obs_intervals', 'electrodes', 'electrode_group', 'waveform_mean', 'waveform_sd')]
ROI table columns: [('cell_type', 'neuropil_mask', 'included', 'image_mask')]
Imaging plane descriptions: ['Imaging session for PT and IT neurons during audio delay task']

The files hold 99 extracellular sessions from 19 mice and 59 imaging planes from 4 mice, the numbers of the paper (99 sessions and 19 mice; 59 fields of view and 4 mice). Every extracellular session is in left ALM ("left M2"). The only stimulus records are the laser power and AOM input traces of the tagging light in 75 sessions (next cells); the trial tables have no column for photostimulation. The VGAT-ChR2-EYFP and PV-ires-Cre x ReaChR mice of Figure 1 are not among the subjects, and the three Ai32 crosses (the photoactivation lines of Figure 5) have no stimulus record, so no claim on photoinhibition or photoactivation can be tested. The imaging mice are given the genotype "Rosa-lsl-tdtomato"; the Methods use C57Bl/6Crl mice. No imaging plane gives its depth.

In [3]:
rows = []
for kind, S in (("ephys", E), ("ophys", O)):
    for i, s in enumerate(S):
        tr = s["trials"].assign(kind=kind, session=i, subject=s["subject"])
        rows.append(tr)
T = pd.concat(rows, ignore_index=True)
T["matched"] = T["sample"].notna()
print("Trials by kind, instruction, outcome and early-lick flag; `matched` counts trials with a sample event "
      f"{alm.MATCH_WIN[0]} to {alm.MATCH_WIN[1]} s after the trial start:")
print(T.groupby(["kind", "instruction", "outcome", "early"]).matched.agg(trials="size", matched="sum").to_string())
off = (T["sample"] - T.start)[T.matched]
print("\nSample event minus trial start (s), by kind:")
print(off.groupby(T.kind[T.matched]).describe().round(4).to_string())
n_ev = {k: sum(s["n_events"]["sample"] for s in S) for k, S in (("ephys", E), ("ophys", O))}
print("\nSample events in the files:", n_ev, "; matched to a trial:", T.groupby("kind").matched.sum().to_dict())

# Performing trials without events: their spikes
unm = []
for i, s in enumerate(E):
    tr = s["trials"]
    m = tr["sample"].isna() & tr.instruction.isin(["left", "right"])
    if m.any():
        allsp = np.sort(np.concatenate(s["spikes"]))
        n = alm.counts(allsp, tr.start[m].values, tr.stop[m].values)
        unm.append(pd.DataFrame(dict(session=i, subject=s["subject"], instruction=tr.instruction[m],
                                     outcome=tr.outcome[m], early=tr.early[m], spikes=n,
                                     dur=(tr.stop - tr.start)[m].round(2))))
unm = pd.concat(unm)
print(f"\nExtracellular trials with a lick instruction but no events: {len(unm)} in {unm.session.nunique()} sessions")
print(unm.groupby(["instruction", "outcome", "early"]).agg(trials=("spikes", "size"), with_spikes=("spikes", lambda x: int((x > 0).sum())),
      max_spikes=("spikes", "max"), lasting_5_5_s=("dur", lambda x: int((x == 5.5).sum()))).to_string())
print("Per session, fraction of lick-instruction trials without events (top 10):")
frac = unm.groupby("session").size() / pd.Series({i: s["trials"].instruction.isin(["left", "right"]).sum() for i, s in enumerate(E)})
print(frac.dropna().sort_values(ascending=False).head(10).round(3).to_string())

d = (T.go - T["sample"])
ne = T.early.isin(["no early", "False"]) & T.matched & T.instruction.isin(["left", "right"])
print("\nGo cue minus sample event (s) in trials without an early lick:")
print(d[ne].describe().round(4).to_string())
print("Trials without an early lick whose go cue is not 2.6 s after the sample event (dropped):",
      int((ne & ((d - 2.6).abs() >= 0.01)).sum()), "of", int(ne.sum()))
bad = ne & ((d - 2.6).abs() >= 0.01)
print("  of these, by kind:", T.kind[bad].value_counts().to_dict(), "; go cue minus sample event (s) from",
      round(float(d[bad].min()), 4), "to", round(float(d[bad].max()), 4), "; shorter than 2.6 s:", int((bad & (d < 2.6)).sum()),
      ", longer:", int((bad & (d > 2.6)).sum()))
print("  without an early lick and with no go cue:", T.kind[ne & d.isna()].value_counts().to_dict())
print("Go cue minus sample event (s) in early-lick trials:", d[T.early.eq("early") & T.matched].describe().round(2).to_dict())
print("Delay event minus sample event (s), median by kind:", (T.delay - T["sample"]).groupby(T.kind).median().round(3).to_dict())
print("\nEarly-lick flags in the imaging files:", T[T.kind == "ophys"].early.value_counts().to_dict())

# Trials with a lick instruction but no events, by mouse, and where they fall in the session
ph, pos = [], []
for i, s in enumerate(E):
    tr = s["trials"].reset_index(drop=True)
    m = tr["sample"].isna() & tr.instruction.isin(["left", "right"])
    fill = m & tr.instruction.eq("right") & tr.outcome.eq("hit") & tr.early.eq("no early") & ((tr.stop - tr.start).round(2) == 5.5)
    ph.append(dict(subject=s["subject"], genotype=s["genotype"], session=i, light_session=len(s["laser"]) > 0,
                   trials=int(tr.instruction.isin(["left", "right"]).sum()), no_events=int(m.sum()), right_hit_5_5_s=int(fill.sum())))
    pos += list(np.where(m)[0] / len(tr))
PH = pd.DataFrame(ph)
print("\nTrials with a lick instruction but no events, by mouse:")
print(PH.groupby(["subject", "genotype"]).agg(sessions=("session", "size"), light_sessions=("light_session", "sum"), trials=("trials", "sum"),
      no_events=("no_events", "sum"), right_hit_5_5_s=("right_hit_5_5_s", "sum"),
      sessions_with_them=("no_events", lambda x: int((x > 0).sum()))).to_string())
print("In sessions with a light record:", int(PH.no_events[PH.light_session].sum()), "; without:", int(PH.no_events[~PH.light_session].sum()))
hist = np.histogram(pos, bins=10, range=(0, 1))[0]
print("Position within the session (trial index / trials), counts per tenth:", hist.tolist(),
      f"; first or last tenth: {hist[0] + hist[-1]} of {hist.sum()} ({(hist[0] + hist[-1]) / hist.sum():.2f})")
Trials by kind, instruction, outcome and early-lick flag; `matched` counts trials with a sample event 0.5 to 0.7 s after the trial start:
                                              trials  matched
kind  instruction    outcome        early                    
ephys left           hit            early       1512     1507
                                    no early    9217     9177
                     ignore         early         12       12
                                    no early     212      203
                     miss           early        393      393
                                    no early    3038     3025
      non-performing non-performing no early    1654        0
      right          hit            early       1368     1364
                                    no early   10769     9322
                     ignore         early          9        9
                                    no early     224      213
                     miss           early        382      380
                                    no early    2649     2640
ophys left           hit            False       1659     1659
                     ignore         False         44       44
                     miss           False        223      223
      non-performing non-performing False        590       30
      right          hit            False       1574     1574
                     ignore         False         12       11
                     miss           False        417      417

Sample event minus trial start (s), by kind:
         count    mean     std     min     25%     50%     75%     max
kind                                                                  
ephys  28245.0  0.5723  0.0005  0.5240  0.5722  0.5723  0.5725  0.5733
ophys   3958.0  0.5836  0.0075  0.5788  0.5828  0.5830  0.5832  0.6995

Sample events in the files: {'ephys': 28465, 'ophys': 4519} ; matched to a trial: {'ephys': 28245, 'ophys': 3958}
Extracellular trials with a lick instruction but no events: 1540 in 78 sessions
                              trials  with_spikes  max_spikes  lasting_5_5_s
instruction outcome early                                                   
left        hit     early          5            5         368              0
                    no early      40           40         634             40
            ignore  no early       9            9         680              9
            miss    no early      13           13         434             13
right       hit     early          4            3         407              0
                    no early    1447          102         661           1447
            ignore  no early      11           11         632             11
            miss    early          2            2         203              0
                    no early       9            9         791              9
Per session, fraction of lick-instruction trials without events (top 10):
session
12    0.275
16    0.245
43    0.200
15    0.183
11    0.172
14    0.164
78    0.136
18    0.132
19    0.123
17    0.116

Go cue minus sample event (s) in trials without an early lick:
count    28431.0000
mean         2.5975
std          0.0735
min          0.0283
25%          2.6006
50%          2.6008
75%          2.6008
max          2.6778
Trials without an early lick whose go cue is not 2.6 s after the sample event (dropped): 66 of 28508
  of these, by kind: {'ephys': 66} ; go cue minus sample event (s) from 0.0283 to 2.6778 ; shorter than 2.6 s: 65 , longer: 1
  without an early lick and with no go cue: {'ephys': 75, 'ophys': 2}
Go cue minus sample event (s) in early-lick trials: {'count': 3635.0, 'mean': 3.62, 'std': 1.2, 'min': 0.04, '25%': 2.89, '50%': 3.36, '75%': 3.85, 'max': 17.34}
Delay event minus sample event (s), median by kind: {'ephys': 1.3, 'ophys': 1.2}

Early-lick flags in the imaging files: {'False': 4519}

Trials with a lick instruction but no events, by mouse:
                              sessions  light_sessions  trials  no_events  right_hit_5_5_s  sessions_with_them
subject genotype                                                                                              
210861  sim1-cre                     3               0    1239          0                0                   0
210862  sim1-cre                     3               0     870         18               15                   2
210863  sim1-cre                     3               0     946          0                0                   0
214427  rbp4-cre                     4               4    1411        217              209                   4
214429  rbp4-cre                     4               4    1947        297              289                   4
214430  rbp4-cre                     4               4    1166        139              137                   4
218453  chr2-eyfp x tlx-cre          5               0    1419          1                0                   1
218457  chr2-eyfp x tlx-cre          6               0    1770         10                0                   5
218693  chr2-eyfp x sim1-cre         3               0     889          7                4                   3
219030  sim1-cre                     5               5    1573         74               70                   5
219031  sim1-cre                     5               5    1407        119              110                   5
219033  sim1-cre                     7               7    1992         42               36                   5
219036  sim1-cre                     7               7    1791         23               21                   5
219037  sim1-cre                     7               7    2159         40               39                   5
219038  sim1-cre                     8               8    2275        132              126                   8
219247  tlx-cre                      5               5    1634        151              145                   5
219248  tlx-cre                      7               7    2669        221              207                   7
219253  tlx-cre                     10               9    1729         14                5                   7
221977  tlx-cre                      3               3     899         35               34                   3
In sessions with a light record: 1502 ; without: 38
Position within the session (trial index / trials), counts per tenth: [231, 18, 46, 97, 119, 219, 105, 100, 82, 523] ; first or last tenth: 754 of 1540 (0.49)

Trials are matched to their behavioural events by the sample event, which falls 0.572 s (extracellular) or 0.583 s (imaging) after the trial start in almost every trial. In the extracellular files, the trials that carry no events are the "non-performing" trials and 1,540 trials with a lick instruction. Of these, 1,447 are marked "right", "hit" and "no early", last exactly 5.5 s, and 1,345 of them hold no spike from any unit; in some sessions such trials are a quarter of the trials. They look like placeholders for trials that were removed, filled with default values. Almost all (1,502 of 1,540) are in the 75 sessions with a record of the tagging light; 754 of them (49%) fall in the first or last tenth of a session (231 and 523), and 7 are in the Ai32 mouse 218693 (4 of the placeholder kind), so they are not the missing photoactivation trials of Figure 5 and Extended Data Fig. 7. The other 93 hold spikes but no sample event in the matching window. All 1,540 are left out of every analysis here, the behaviour included; counted as given, the placeholders would raise performance. In trials without an early lick, the go cue follows the sample event by 2.6 s, the 1.3 s sample and 1.3 s delay epochs of the Methods. 66 trials break this, all extracellular (of 28,508 matched trials without an early lick, extracellular and imaging together): in 65 the go cue comes sooner (as little as 0.03 s after the sample event) and in one later (2.68 s). They are dropped, as are the 75 extracellular and 2 imaging trials without an early lick that have no go cue. In early-lick trials the go cue comes later, consistent with the delay being restarted after an early lick. The imaging files flag no trial as an early lick, so early licks cannot be excluded there.

In [4]:
rows = []
for i, s in enumerate(E):
    tr = s["trials"]
    for k, v in s["laser"].items():
        on = np.sort(v["onsets"])
        rows.append(dict(session=i, subject=s["subject"], genotype=s["genotype"], series=k, unit=v["unit"],
                         max=round(v["max"], 1), pulses=len(on), segments=v["n_segments"],
                         width_ms=np.round(np.median(v["width_ms"]), 2) if len(v["width_ms"]) else np.nan,
                         pair_gap_ms=round(1000 * np.median(np.diff(on)[np.diff(on) < 0.05]), 1) if len(on) > 1 else np.nan,
                         pair_period_s=round(np.median(np.diff(alm.pair_onsets(on))), 3) if len(on) > 2 else np.nan,
                         light_trials=int(tr.light.sum()), trials=len(tr), outside=s["pulses_outside_trials"]))
LZ = pd.DataFrame(rows)
print("Sessions with a laser power series:", LZ.session.nunique(), "of", len(E))
print(LZ.groupby(["genotype", "series", "unit", "max"]).agg(sessions=("session", "size"), pulses=("pulses", "sum"),
      light_trials=("light_trials", "sum"), trials=("trials", "sum"), width_ms=("width_ms", "median"),
      pair_gap_ms=("pair_gap_ms", "median"), pair_period_s=("pair_period_s", "median"), outside=("outside", "sum")).to_string())
print("Genotypes without any stimulus series:", sorted({s["genotype"] for s in E} - set(LZ.genotype)))
seg = sorted({int(x) for s in E for v in s["laser"].values() for x in v["segment_samples"]})
print("Samples per stored segment of the laser series:", seg, "; rate (Hz):", sorted({round(v["rate"]) for s in E for v in s["laser"].values()}))
TL = pd.concat([s["trials"].assign(session=i) for i, s in enumerate(E)], ignore_index=True)
print("\nTrials with light, by outcome (extracellular, trials with events):")
print(pd.crosstab([TL.outcome[TL["sample"].notna()]], TL.light[TL["sample"].notna()]).to_string())
pw = np.concatenate([v["power"] for s in E for v in s["laser"].values()])
print("Laser power at the pulses (file units):", np.unique(np.round(pw)).tolist())
Sessions with a laser power series: 75 of 99
                                               sessions  pulses  light_trials  trials  width_ms  pair_gap_ms  pair_period_s  outside
genotype series                    unit  max                                                                                        
rbp4-cre bilateral ALM_laser_power watts 47.1        12   16368          1361    4781       1.0         11.0            0.5      460
         left PONS_laser_power     watts 82.0         9   11074          1087    3665       1.0         11.0            0.5      460
sim1-cre left PONS_laser_power     watts 82.0        39   65880          3277   11701       1.0         11.0            0.5      486
tlx-cre  bilateral ALM_laser_power watts 47.1        24   43937          2198    7022       1.0         11.0            0.5        0
Genotypes without any stimulus series: ['chr2-eyfp x sim1-cre', 'chr2-eyfp x tlx-cre']
Samples per stored segment of the laser series: [50000, 100000] ; rate (Hz): [10000]

Trials with light, by outcome (extracellular, trials with events):
light    False  True 
outcome              
hit      16587   4783
ignore     179    258
miss      4837   1601
Laser power at the pulses (file units): [47.0, 82.0]

In 75 sessions (the virus-injected Sim1, Rbp4 and Tlx mice; 84 series, as the Rbp4 sessions have one for each site), stimulus/presentation holds a laser power series stored in 5 s segments at 10 kHz, one per trial with light (10 s where two such trials follow without a gap). The two Tlx x Ai32 mice, whose 8 IT units the files label, have no light record. The pulses are 1 ms wide, in pairs 10 ms apart, every 0.5 s, at 82 (left pons) or 47.1 (bilateral ALM): the antidromic tagging protocol of the Methods ("Pairs of laser pulses (1 ms duration, 47-82 mW peak power, separated by 10 ms) were deployed every 500ms"). The series are labelled "watts", but the values match the Methods' milliwatts. The light was given during behavioural trials, which the trial tables do not flag; here a trial has light when a pulse falls in its window. The paper does not say whether these trials belong in the task analyses; the claims file names two readings for every electrophysiology claim of Figs 2 and 3, (t1) all trials, light or not, the literal reading of the Methods, and (t2) only trials without the light, either of which may decide. The behaviour claim asks for trials without photostimulation, so it leaves them out.

In [5]:
U = alm.unit_table(E)
print("Deposited units:", len(U), "; quality labels:", pd.Series(np.concatenate([s["quality"] for s in E])).value_counts().to_dict())
print("Sampling rate of the waveforms (Hz):", sorted(set(np.concatenate([s["unit_rate"] for s in E]))),
      "; waveform length (samples):", sorted({s["waveform"].shape[1] for s in E}))
print("\nUnits by file label, width class (trough to peak of the mean waveform) and duplicate flag:")
print(U.groupby(["label", "width_class", "is_duplicate"]).size().to_string())
print("\nEvery PT or IT unit has an identical spike train in a unit labelled Pyr in the same file:",
      int(U.is_duplicate.sum()), "of", int(U.label.isin(["PT", "IT"]).sum()))
print("Spike widths (ms) by label:")
print(U.groupby("label").width.describe().round(3).to_string())
print("Units whose waveform peak after the trough is the last sample:", int(U.peak_at_end.sum()),
      f"(the waveform ends {(28 - 15) / 19531.25 * 1000:.3f} ms after the trough at sample 15)")
print("Trough sample index:", sorted({int(np.argmin(w)) for s in E for w in s["waveform"]}))
spans = np.concatenate([np.diff(s["obs_intervals"], axis=1).ravel() for s in E])
print("\nObservation intervals per unit: lengths (s)", np.unique(spans.round(3)))
inside, where = [], []
for i, s in enumerate(E):
    tr = s["trials"]
    allsp = np.sort(np.concatenate(s["spikes"]))
    k = np.searchsorted(tr.start.values, allsp, side="right") - 1
    ins = (k >= 0) & (allsp <= tr.stop.values[np.clip(k, 0, None)] + 1e-6)
    inside.append(np.mean(ins))
    where.append(dict(session=i, subject=s["subject"], spikes=len(allsp), outside=int((~ins).sum()),
                      before_first=int((allsp < tr.start.min()).sum()), after_last=int((allsp > tr.stop.max() + 1e-6).sum()),
                      gaps_between_trials=int((tr.start.values[1:] - tr.stop.values[:-1] > 0.01).sum())))
inside = np.array(inside)
WH = pd.DataFrame(where)
WH["between"] = WH.outside - WH.before_first - WH.after_last
print("Fraction of spikes inside a trial window [start, stop], per session: min", round(inside.min(), 4),
      "median", round(float(np.median(inside)), 4), "; sessions with all spikes inside:", int((inside > 0.99999).sum()),
      ", with at least 1% outside:", int((inside < 0.99).sum()), "of", len(inside))
print("Spikes outside every trial window: before the first trial", int(WH.before_first.sum()), ", after the last",
      int(WH.after_last.sum()), ", between trials", int(WH.between.sum()), "of", int(WH.outside.sum()))
print("Sessions with at least 1% of spikes outside the trial windows:")
print(WH[inside < 0.99].assign(inside=inside[inside < 0.99].round(4)).to_string(index=False))
Deposited units: 1441 ; quality labels: {'good': 1441}
Sampling rate of the waveforms (Hz): [np.float64(19531.25)] ; waveform length (samples): [29]

Units by file label, width class (trough to peak of the mean waveform) and duplicate flag:
label  width_class  is_duplicate
FS     FS           False            124
IT     Pyr          True              27
PT     Pyr          True              45
Pyr    Pyr          False           1245

Every PT or IT unit has an identical spike train in a unit labelled Pyr in the same file: 72 of 72
Spike widths (ms) by label:
        count   mean    std    min    25%    50%    75%    max
label                                                         
FS      124.0  0.225  0.046  0.154  0.154  0.256  0.256  0.256
IT       27.0  0.647  0.025  0.614  0.614  0.666  0.666  0.666
PT       45.0  0.606  0.048  0.461  0.563  0.614  0.614  0.666
Pyr    1245.0  0.636  0.043  0.461  0.614  0.666  0.666  0.666
Units whose waveform peak after the trough is the last sample: 746 (the waveform ends 0.666 ms after the trough at sample 15)
Trough sample index: [15]

Observation intervals per unit: lengths (s) [5.5]
Fraction of spikes inside a trial window [start, stop], per session: min 0.7019 median 0.9997 ; sessions with all spikes inside: 11 , with at least 1% outside: 2 of 99
Spikes outside every trial window: before the first trial 0 , after the last 0 , between trials 18174 of 18174
Sessions with at least 1% of spikes outside the trial windows:
 session subject  spikes  outside  before_first  after_last  gaps_between_trials  between  inside
      14  214429   36634     8851             0           0                  573     8851  0.7584
      43  219031   18711     5577             0           0                  143     5577  0.7019

The deposited units are 1,245 labelled "Pyr", 124 "FS", 45 "PT" and 27 "IT". Every PT and IT unit is a second copy of a unit labelled Pyr in the same file (identical spike trains), so the recordings hold 1,369 distinct units; duplicates are counted once, and the PT/IT label is carried to its Pyr twin. The spike widths computed from the waveforms put every Pyr unit above 0.45 ms and every FS unit below 0.35 ms, so the labels agree with the width rule, and no unit has an intermediate width: the 39 intermediate units of the Methods are not in the dandiset. The waveforms are 29 samples with the trough at sample 15, so widths above 0.666 ms cannot be measured; this does not affect the classification at 0.35 and 0.45 ms. Spikes are stored almost only within the 5.5 s trial windows, which run from 0.57 s before the sample event to 2.33 s after the go cue in trials without an early lick and cover every epoch used here. In 97 of 99 sessions at least 99% of the spikes fall in a trial window (all of them in 11). In two sessions (mice 214429 and 219031) 24% and 30% fall outside, all of them between trials, where those sessions have gaps between the windows; no spike comes before the first trial or after the last.

In [6]:
# Clock check, extracellular: selectivity of each session's putative pyramidal units before the
# sample event (where no task information exists) and in the response epoch.
P = U[(U.width_class == "Pyr") & ~U.is_duplicate]
rows = []
for si in sorted(P.session.unique()):
    Ps = P[P.session == si]
    pre = alm.window_counts(E, Ps, [-0.5], width=0.5)
    resp = alm.window_counts(E, Ps, [2.6], width=1.3)
    p_pre = [alm.ttest_p(R[:, 0], L[:, 0]) for R, L in pre.values()]
    p_resp = [alm.ttest_p(R[:, 0], L[:, 0]) for R, L in resp.values()]
    rows.append(dict(session=si, subject=E[si]["subject"], units=len(Ps),
                     sel_pre=np.mean(np.array(p_pre) < 0.05), sel_resp=np.mean(np.array(p_resp) < 0.05)))
clock = pd.DataFrame(rows)
print("Fraction of units with lick right vs lick left t-test P < 0.05, correct trials, over all sessions:")
print(clock[["sel_pre", "sel_resp"]].describe().round(3).to_string())
print("Sessions where the response-epoch fraction does not exceed the pre-sample fraction:")
print(clock[clock.sel_resp <= clock.sel_pre].to_string(index=False))
Fraction of units with lick right vs lick left t-test P < 0.05, correct trials, over all sessions:
       sel_pre  sel_resp
count   99.000    99.000
mean     0.044     0.556
std      0.055     0.182
min      0.000     0.182
25%      0.000     0.429
50%      0.000     0.545
75%      0.089     0.692
max      0.200     1.000
Sessions where the response-epoch fraction does not exceed the pre-sample fraction:
Empty DataFrame
Columns: [session, subject, units, sel_pre, sel_resp]
Index: []
In [7]:
# Clock check, imaging: rank sum test between trial types before the sample event and in the
# response epoch, per plane, included ROIs, correct trials.
rows = []
for i, pl in enumerate(O):
    X, rel, right, dropped = alm.plane_trials(pl, "correct")
    inc = pl["included"]
    pre = X[:, (rel >= -0.5) & (rel < 0), :][:, :, inc].mean(1)
    resp = X[:, (rel >= 2.6) & (rel < 3.9), :][:, :, inc].mean(1)
    rows.append(dict(plane=i, subject=pl["subject"], rois=int(inc.sum()), trials=len(right), dropped=dropped,
                     sel_pre=np.mean(stats.ranksums(pre[right], pre[~right], axis=0).pvalue < 0.05),
                     sel_resp=np.mean(stats.ranksums(resp[right], resp[~right], axis=0).pvalue < 0.05)))
iclock = pd.DataFrame(rows)
print("Fraction of included ROIs with rank sum P < 0.05:")
print(iclock[["sel_pre", "sel_resp"]].describe().round(3).to_string())
print("Planes where the response-epoch fraction does not exceed the pre-sample fraction:")
print(iclock[iclock.sel_resp <= iclock.sel_pre].to_string(index=False))
print("Trials dropped for lack of 0.5 s of frames before the trial start:", int(iclock.dropped.sum()))
Fraction of included ROIs with rank sum P < 0.05:
       sel_pre  sel_resp
count   59.000    59.000
mean     0.064     0.438
std      0.034     0.110
min      0.000     0.136
25%      0.040     0.367
50%      0.059     0.438
75%      0.086     0.500
max      0.145     0.692
Planes where the response-epoch fraction does not exceed the pre-sample fraction:
Empty DataFrame
Columns: [plane, subject, rois, trials, dropped, sel_pre, sel_resp]
Index: []
Trials dropped for lack of 0.5 s of frames before the trial start: 34
In [8]:
print("Imaging rate (Hz):", sorted({round(pl["rate"], 3) for pl in O}), "; first frame time (s):",
      sorted({round(pl["t0"], 4) for pl in O}))
dur = [(pl["t0"] + len(pl["F"]) / pl["rate"], pl["trials"].stop.max()) for pl in O]
print("Last frame time minus last trial stop (s): min", round(min(a - b for a, b in dur), 2), "max", round(max(a - b for a, b in dur), 2))
roi = pd.DataFrame(dict(type=np.concatenate([pl["roi_type"] for pl in O]),
                        included=np.concatenate([pl["included"] for pl in O]),
                        subject=np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])))
print("ROIs:", len(roi), "; included:", int(roi.included.sum()))
print(pd.crosstab([roi.subject, roi.type], roi.included).to_string())
Fall = np.concatenate([(pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]].min(0) for pl in O])
print("Included ROIs whose neuropil-corrected F (r = 0.7) reaches 0 or below:", int((Fall <= 0).sum()))
print("Raw F range (a.u.):", round(min(pl["F"].min() for pl in O), 1), "to", round(max(pl["F"].max() for pl in O), 1))
ratio = np.concatenate([np.median(pl["F"] / np.maximum(pl["Fnp"], 1e-9), 0)[pl["included"]] for pl in O])
print("Included ROIs whose median F / F_neuropil is below 1.05 (Methods: cells at least 5% brighter than the neuropil):",
      int((ratio < 1.05).sum()), "of", len(ratio))
f0bad = 0; f0all = 0
for pl in O:
    X, rel, right, _ = alm.plane_trials(pl, "nonearly")
    f0all += X.shape[0] * int(pl["included"].sum())
    f0bad += int((~np.isfinite(X[:, :, pl["included"]]).all(1)).sum())
print("Included ROI-trials with a non-finite DF/F (F0 of 0):", f0bad, "of", f0all)
Imaging rate (Hz): [14.84] ; first frame time (s): [0.0674]
Last frame time minus last trial stop (s): min 2.57 max 20.22
ROIs: 5215 ; included: 4706
included             False  True 
subject type                     
216219  IT             156    374
        interneuron      0      2
        unknown         83   1121
217951  IT              13     95
        PT               0     34
        unknown         31    646
221541  IT              60    233
        PT              20    286
        Pyr              0     23
        interneuron      0      7
        unknown         27    599
226244  IT              38    141
        PT              10    210
        unknown         71    935
Included ROIs whose neuropil-corrected F (r = 0.7) reaches 0 or below: 3403
Raw F range (a.u.): 0.0 to 2631.2
Included ROIs whose median F / F_neuropil is below 1.05 (Methods: cells at least 5% brighter than the neuropil): 424 of 4706
Included ROI-trials with a non-finite DF/F (F0 of 0): 0 of 305349
In [9]:
lick = alm.lick_trace_check(assets[assets.kind == "ephys"].asset_id.iloc[0])
print({k: v for k, v in lick.items() if k not in ("mean_trace", "t_ms")})
print("Mean lick trace around the go cue of correct trials (a.u.), 5 ms bins:")
print(pd.Series(lick["mean_trace"].round(4), index=lick["t_ms"]).iloc[30:70].to_string())
{'n_samples': 21100000, 'rate': np.float64(9999.999999668034), 'n_segments': 422, 'seg_len': array([50000]), 'seg_start_is_trial_start': np.float64(1.0), 'n_trials': 423, 'unit': 'a.u.', 'description': "Time-series of the animal's tongue movement when licking"}
Mean lick trace around the go cue of correct trials (a.u.), 5 ms bins:
-50.0     0.1225
-45.0     0.1225
-40.0     0.1223
-35.0     0.1223
-30.0     0.1222
-25.0     0.1221
-20.0     0.1220
-15.0     0.1219
-10.0     0.1219
-5.0      0.1216
 0.0      0.1212
 5.0      0.1218
 10.0     0.1218
 15.0     0.1218
 20.0     0.1218
 25.0     0.1218
 30.0     0.1220
 35.0     0.1225
 40.0     0.1238
 45.0     0.1256
 50.0     0.1278
 55.0     0.1303
 60.0     0.1324
 65.0     0.1343
 70.0     0.1357
 75.0     0.1370
 80.0     0.1386
 85.0     0.1404
 90.0     0.1423
 95.0     0.1432
 100.0    0.1427
 105.0    0.1441
 110.0    0.1433
 115.0    0.1427
 120.0    0.1425
 125.0    0.1420
 130.0    0.1416
 135.0    0.1405
 140.0    0.1395
 145.0    0.1387

The imaging rate is 14.84 Hz (Methods, 15 Hz), and the fluorescence spans every trial. There are 5,215 ROIs, of which 4,706 are marked included, the paper's cell count; the inclusion flag is taken to stand for the Methods' selection, which the files describe only as "whether to include this roi into later analyses" (fig4h section) (some included ROIs are not 5% brighter than their neuropil by the median ratio; counts above). The neuropil-corrected signal of most included ROIs touches 0 or below in some frame, which is noise on a small signal; F0 is averaged over 0.5 s, and the count of ROI-trials where it is exactly 0 is printed above; the imaging-selectivity-counts section shows how many are near 0 and what leaving them out changes. Besides PT, IT and unknown, the files label 23 ROIs "Pyr" and 9 "interneuron", labels the paper does not describe. Both clock checks show selectivity in the response epoch far above the pre-sample level in almost every session and plane (sessions and planes where it does not are listed above), so the spike times, the fluorescence and the trial events are consistent with one clock. The lick trace is stored in 5 s segments at 10 kHz, one per trial from its start, and rises within about 50 to 100 ms after the go cue, consistent with the mouth-movement signal of the Methods; it is the signal used for reaction-time.

behaviour-performance¶

Mice in the object location discrimination task performed at 78.4% correct on average, and fewer than 13% of responses came before the go cue.

Criterion: per cent correct excluding lick early trials, on trials without photostimulation; mean formed (a) over all trials pooled, (b) over sessions, (c) over mice; within 78.4 +/- 7.84%, and the fraction of lick early trials below 14.3% under the same reading. Trials with tagging light are left out (the data check finds them from the laser series); the result with them is a variant. Trials without events are left out (see the data check). Early licks are flagged only in the extracellular files, so those 99 sessions are the main test and the imaging sessions a variant. Trials without a lick in the response window (outcome "ignore") are, under the claims file's readings, (x) kept in the denominator as not correct, the literal reading of the Methods, or (y) left out; either may decide.

In [10]:
def behaviour(S, kind, light="none", timing=False):
    rows = []
    for i, s in enumerate(S):
        tr = s["trials"]
        has = tr["sample"].notna() & tr.instruction.isin(["left", "right"])
        if light == "none" and "light" in tr:
            has &= ~tr.light
        early = tr.early.eq("early")
        if timing:  # drop the trials without an early lick whose go cue is not 2.6 s after the sample event
            has &= early | ((tr.go - tr["sample"] - (alm.SAMPLE_LEN + alm.DELAY_LEN)).abs() < 0.01)
        rows.append(dict(kind=kind, session=i, subject=s["subject"], trials=int(has.sum()),
                         hit=int((has & ~early & (tr.outcome == "hit")).sum()),
                         miss=int((has & ~early & (tr.outcome == "miss")).sum()),
                         ignore=int((has & ~early & (tr.outcome == "ignore")).sum()),
                         early=int((has & early).sum())))
    return pd.DataFrame(rows)


out = []
early = {}
for light in ("none", "any"):
    B = pd.concat([behaviour(E, "ephys", light), behaviour(O, "ophys", light)], ignore_index=True)
    for kind in ("ephys", "ophys", "both"):
        d = B if kind == "both" else B[B.kind == kind]
        for ign in ("(x) not correct", "(y) left out"):
            den = d.hit + d.miss + (d.ignore if ign.startswith("(x)") else 0)
            m = d.assign(den=den).groupby("subject")[["hit", "den"]].sum()
            out.append(dict(light_trials="left out" if light == "none" else "kept", sessions=kind, no_lick=ign,
                            n_sessions=len(d), n_mice=d.subject.nunique(), trials=int(d.trials.sum()),
                            a_pooled=100 * d.hit.sum() / den.sum(), b_sessions=100 * (d.hit / den).mean(),
                            c_mice=100 * (m.hit / m.den).mean()))
    d = B[B.kind == "ephys"]
    early[light] = dict(a_pooled=100 * d.early.sum() / d.trials.sum(), b_sessions=100 * (d.early / d.trials).mean(),
                        c_mice=100 * (d.groupby("subject").early.sum() / d.groupby("subject").trials.sum()).mean())
perf = pd.DataFrame(out)
print("Per cent correct (non-early trials with events), readings (a) pooled, (b) mean over sessions, (c) mean over mice:")
print(perf.round(1).to_string(index=False))
print("\nPer cent of trials with an early lick (extracellular sessions), light trials left out:",
      {k: round(v, 1) for k, v in early["none"].items()}, "; kept:", {k: round(v, 1) for k, v in early["any"].items()})
B = pd.concat([behaviour(E, "ephys"), behaviour(O, "ophys")], ignore_index=True)
d = B[B.kind == "ephys"]
print("Paper: 78.4% correct (tolerance 70.56 to 86.24), early responses below 13% (limit 14.3%)")
print("\nPer mouse, extracellular sessions (no-lick trials left out, reading y):")
pm = d.groupby("subject")[["trials", "hit", "miss", "early"]].sum()
pm["pct_correct"] = 100 * pm.hit / (pm.hit + pm.miss)
pm["pct_early"] = 100 * pm.early / pm.trials
print(pm.round(1).to_string())
print("Range of per-session per cent correct:", round((100 * d.hit / (d.hit + d.miss)).min(), 1), "to",
      round((100 * d.hit / (d.hit + d.miss)).max(), 1))

# Variant: the 2.6 s timing filter of the other claims' trial masks also applied here
Bt = behaviour(E, "ephys", timing=True)
den_x, den_y = Bt.hit + Bt.miss + Bt.ignore, Bt.hit + Bt.miss
print(f"\nWith the {int(d.trials.sum() - Bt.trials.sum())} trials without an early lick whose go cue is missing or not 2.6 s after "
      f"the sample event also dropped (light trials left out): per cent correct over sessions {100 * (Bt.hit / den_x).mean():.1f} (x) and {100 * (Bt.hit / den_y).mean():.1f} (y); "
      f"early licks {100 * Bt.early.sum() / Bt.trials.sum():.1f} pooled, {100 * (Bt.early / Bt.trials).mean():.1f} over sessions, "
      f"{100 * (Bt.groupby('subject').early.sum() / Bt.groupby('subject').trials.sum()).mean():.1f} over mice")
Per cent correct (non-early trials with events), readings (a) pooled, (b) mean over sessions, (c) mean over mice:
light_trials sessions         no_lick  n_sessions  n_mice  trials  a_pooled  b_sessions  c_mice
    left out    ephys (x) not correct          99      19   21603      76.5        75.8    75.2
    left out    ephys    (y) left out          99      19   21603      77.2        76.5    75.9
    left out    ophys (x) not correct          59       4    3928      82.3        83.0    82.2
    left out    ophys    (y) left out          59       4    3928      83.5        84.2    83.6
    left out     both (x) not correct         158      23   25531      77.5        78.5    76.4
    left out     both    (y) left out         158      23   25531      78.3        79.4    77.2
        kept    ephys (x) not correct          99      19   28245      75.3        75.0    74.4
        kept    ephys    (y) left out          99      19   28245      76.6        76.3    75.7
        kept    ophys (x) not correct          59       4    3928      82.3        83.0    82.2
        kept    ophys    (y) left out          59       4    3928      83.5        84.2    83.6
        kept     both (x) not correct         158      23   32173      76.2        78.0    75.7
        kept     both    (y) left out         158      23   32173      77.5        79.2    77.1

Per cent of trials with an early lick (extracellular sessions), light trials left out: {'a_pooled': np.float64(14.5), 'b_sessions': np.float64(11.6), 'c_mice': np.float64(14.7)} ; kept: {'a_pooled': np.float64(13.0), 'b_sessions': np.float64(11.7), 'c_mice': np.float64(14.7)}
Paper: 78.4% correct (tolerance 70.56 to 86.24), early responses below 13% (limit 14.3%)

Per mouse, extracellular sessions (no-lick trials left out, reading y):
         trials   hit  miss  early  pct_correct  pct_early
subject                                                   
210861     1239   249    84    904         74.8       73.0
210862      852   396   131    325         75.1       38.1
210863      946   576   189    181         75.3       19.1
214427      882   653   190     27         77.5        3.1
214429     1188   787   121    272         86.7       22.9
214430      497   270   144     80         65.2       16.1
218453     1418   778   210    420         78.7       29.6
218457     1760  1207   294    240         80.4       13.6
218693      882   706   119     22         85.6        2.5
219030      915   514   323     78         61.4        8.5
219031      944   683   226     35         75.1        3.7
219033     1436  1082   227    127         82.7        8.8
219036     1251   932   285     28         76.6        2.2
219037     1528  1208   238     75         83.5        4.9
219038     1481   943   491     47         65.8        3.2
219247      924   480   314    129         60.5       14.0
219248     1685  1416   214     42         86.9        2.5
219253     1165   882   214     27         80.5        2.3
221977      610   373   167     67         69.1       11.0
Range of per-session per cent correct: 50.0 to 92.5

With the 103 trials without an early lick whose go cue is missing or not 2.6 s after the sample event also dropped (light trials left out): per cent correct over sessions 75.8 (x) and 76.5 (y); early licks 14.5 pooled, 11.7 over sessions, 14.8 over mice

Result: reproduced. In the 99 extracellular sessions without the trials with light, per cent correct is 76.5% pooled, 75.8% over sessions and 75.2% over mice with no-lick trials counted as not correct (reading x), and 77.2%, 76.5% and 75.9% with them left out (reading y), all within 78.4 +/- 7.84%. Early licks are 11.6% of trials over sessions, below 14.3%, but 14.5% pooled and 14.7% over mice, above it, so the claim holds under reading (b), with either no-lick reading. Keeping the light trials gives 75.3%, 75.0% and 74.4% correct (x; 76.6%, 76.3% and 75.7% under y) and 13.0%, 11.7% and 14.7% early licks. The imaging sessions, where early licks are not flagged, give 82.3% (x) and 83.5% (y) pooled. The dandiset holds 23 of the paper's 42 mice. Dropping also the 103 trials without an early lick whose go cue is missing or not 2.6 s after the sample event, as the trial masks of the other claims do, gives 75.8% (x) and 76.5% (y) over sessions and 11.7% early licks over sessions (14.5% pooled, 14.8% over mice), the same outcome.

units-classification¶

1,408 single units were recorded from left ALM in 19 mice over 99 sessions; by spike width 124 were fast-spiking, 1,245 putative pyramidal and 39 intermediate.

Criterion: spike width is the trough-to-peak interval of the mean waveform; fast-spiking below 0.35 ms, putative pyramidal above 0.45 ms, intermediate between; each count within 10%.

In [11]:
D = U[~U.is_duplicate]
expected = dict(single_units=(1408, 140.8), sessions=(99, 9.9), mice=(19, 1.9), fast_spiking=(124, 12.4),
                putative_pyramidal=(1245, 124.5), intermediate=(39, 3.9))
measured = dict(single_units=len(D), sessions=len(E), mice=len({s["subject"] for s in E}),
                fast_spiking=int((D.width_class == "FS").sum()), putative_pyramidal=int((D.width_class == "Pyr").sum()),
                intermediate=int((D.width_class == "intermediate").sum()))
tab = pd.DataFrame([dict(quantity=k, measured=measured[k], expected=v, tolerance=t,
                         within=abs(measured[k] - v) <= t) for k, (v, t) in expected.items()])
print(tab.to_string(index=False))
print("\nCounting the PT/IT duplicates as units: single units", len(U), ", putative pyramidal by width",
      int((U.width_class == "Pyr").sum()))
print("Units per mouse (distinct):")
print(D.groupby("subject").size().to_string())
plt.figure(figsize=(5, 3))
plt.hist(D.width, bins=np.arange(0.1, 0.75, 1000 / 19531.25) - 0.0256, color="0.4")  # one bin per sample interval
plt.axvline(0.35, color="r"); plt.axvline(0.45, color="r")
plt.xlabel("spike width (ms)"); plt.ylabel("units"); plt.title("trough to peak, distinct units")
plt.tight_layout(); plt.show()
print("Widths plotted:", round(D.width.min(), 3), "to", round(D.width.max(), 3), "ms; none outside the axis")
          quantity  measured  expected  tolerance  within
      single_units      1369      1408      140.8    True
          sessions        99        99        9.9    True
              mice        19        19        1.9    True
      fast_spiking       124       124       12.4    True
putative_pyramidal      1245      1245      124.5    True
      intermediate         0        39        3.9   False

Counting the PT/IT duplicates as units: single units 1441 , putative pyramidal by width 1317
Units per mouse (distinct):
subject
210861     54
210862     53
210863     87
214427     26
214429     47
214430     40
218453     53
218457    111
218693     57
219030     56
219031     92
219033     54
219036     97
219037     99
219038    118
219247     60
219248    142
219253     89
221977     34
No description has been provided for this image
Widths plotted: 0.154 to 0.666 ms; none outside the axis

Result: partially reproduced. The dandiset holds 1,369 distinct units (1,441 with the PT/IT duplicates) in 99 sessions from 19 mice; by spike width 124 are fast-spiking and 1,245 putative pyramidal, as in the paper. No unit has an intermediate width, against 39 in the paper: five of the six counts match and the intermediate units are not in the dandiset.

fig2-selective-fraction¶

73% (912 of 1,245) of putative pyramidal neurons distinguished the trial types in at least one trial epoch.

Criterion: per neuron, spike counts in the sample, delay and response epochs compared between lick right and lick left trials (two-tailed t-test, P < 0.05, uncorrected); selective when significant in any epoch. Readings: trials (a) correct only or (b) all non-early trials by instruction; response epoch (i) 1.3 s or (ii) 1.5 s after the go cue; trials with tagging light (t1) kept or (t2) left out, a reading the claims file sets for every electrophysiology claim of Figs 2 and 3, either of which may decide.

In [12]:
P = U[(U.width_class == "Pyr") & ~U.is_duplicate]
EP = {}
rows = []
for light in ("any", "none"):
  for tset, tname in (("correct", "a_correct"), ("nonearly", "b_nonearly")):
    for resp, rname in ((1.3, "i_1.3s"), (1.5, "ii_1.5s")):
        r = alm.epoch_stats(E, P, tset, resp, light)
        EP[(tset, resp, light)] = r
        sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
        sel = sig.any(axis=1)
        prep = sig.p_sample | sig.p_delay
        per = sig.p_response
        rows.append(dict(light_trials="kept" if light == "any" else "left out", trials=tname, response=rname,
                         neurons=len(r), selective=int(sel.sum()),
                         fraction=round(sel.mean(), 3), untestable=int(r[["p_sample", "p_delay", "p_response"]].isna().all(axis=1).sum()),
                         min_trials_right=int(r.n_right.min()), min_trials_left=int(r.n_left.min()),
                         prep_only=int((prep & ~per).sum()), peri_only=int((per & ~prep).sum()), both=int((prep & per).sum())))
SEL = pd.DataFrame(rows)
print(SEL[["light_trials", "trials", "response", "neurons", "selective", "fraction", "untestable", "min_trials_right", "min_trials_left"]].to_string(index=False))
print("Paper: 912 (tolerance 820.8 to 1003.2), fraction 0.73 (0.657 to 0.803)")
SELSET = {light: (EP[("correct", 1.3, light)][["p_sample", "p_delay", "p_response"]] < 0.05).any(axis=1) for light in ("any", "none")}
sel_main = SELSET["any"]
print("\nPer mouse, reading (a)(i):")
print(pd.DataFrame(dict(subject=P.subject, sel=sel_main)).groupby("subject").sel.agg(["size", "sum", "mean"]).round(2).to_string())
print("Chance with three tests at P < 0.05, if independent: about", round(1 - 0.95 ** 3, 3))
light_trials     trials response  neurons  selective  fraction  untestable  min_trials_right  min_trials_left
        kept  a_correct   i_1.3s     1245        912     0.733           0                31               10
        kept  a_correct  ii_1.5s     1245        910     0.731           0                31               10
        kept b_nonearly   i_1.3s     1245        846     0.680           0                35               21
        kept b_nonearly  ii_1.5s     1245        851     0.684           0                35               21
    left out  a_correct   i_1.3s     1245        870     0.699           1                17               10
    left out  a_correct  ii_1.5s     1245        868     0.697           1                17               10
    left out b_nonearly   i_1.3s     1245        795     0.639           1                19               21
    left out b_nonearly  ii_1.5s     1245        792     0.636           1                19               21
Paper: 912 (tolerance 820.8 to 1003.2), fraction 0.73 (0.657 to 0.803)

Per mouse, reading (a)(i):
         size  sum  mean
subject                 
210861     48   25  0.52
210862     47   27  0.57
210863     78   50  0.64
214427     23   18  0.78
214429     44   29  0.66
214430     37   32  0.86
218453     45   29  0.64
218457    100   77  0.77
218693     51   46  0.90
219030     47   32  0.68
219031     81   61  0.75
219033     49   41  0.84
219036     90   75  0.83
219037     95   67  0.71
219038    110   75  0.68
219247     56   38  0.68
219248    129  104  0.81
219253     83   64  0.77
221977     32   22  0.69
Chance with three tests at P < 0.05, if independent: about 0.143

Result: reproduced. With correct trials and a 1.3 s response epoch, 912 of 1,245 putative pyramidal neurons (0.733) are selective, the paper's numbers. These are readings (a)(i)(t1). The other readings give 910 (0.731), 846 (0.680) and 851 (0.684); without the trials with light (t2), 870 (0.699), 868, 795 (0.639) and 792 (0.636). All are within tolerance.

fig2d-preparatory-perimovement¶

Among selective neurons, 219 of 912 had selective preparatory activity only, 389 peri-movement activity only, and 304 both.

Criterion: preparatory means significant in the sample or delay epoch, peri-movement significant in the response epoch, by the test and readings of fig2-selective-fraction; all three counts within 10% under one reading.

In [13]:
tab = SEL[["light_trials", "trials", "response", "selective", "prep_only", "peri_only", "both"]].copy()
for col, (v, t) in dict(prep_only=(219, 21.9), peri_only=(389, 38.9), both=(304, 30.4)).items():
    tab[col + "_ok"] = (tab[col] - v).abs() <= t
print(tab.to_string(index=False))
print("Paper: preparatory only 219 (197.1 to 240.9), peri-movement only 389 (350.1 to 427.9), both 304 (273.6 to 334.4)")
r = EP[("correct", 1.3, "any")]
sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
print("\nReading (a)(i), light trials kept: significant in sample", int(sig.p_sample.sum()), ", delay", int(sig.p_delay.sum()),
      ", response", int(sig.p_response.sum()), "; delay only (not sample)", int((sig.p_delay & ~sig.p_sample).sum()))
print("Preparatory (sample or delay):", int((sig.p_sample | sig.p_delay).sum()), "(paper 219 + 304 = 523); peri-movement (response):",
      int(sig.p_response.sum()), "(paper 389 + 304 = 693)")
light_trials     trials response  selective  prep_only  peri_only  both  prep_only_ok  peri_only_ok  both_ok
        kept  a_correct   i_1.3s        912        231        284   397          True         False    False
        kept  a_correct  ii_1.5s        910        233        282   395          True         False    False
        kept b_nonearly   i_1.3s        846        228        282   336          True         False    False
        kept b_nonearly  ii_1.5s        851        233        287   331          True         False     True
    left out  a_correct   i_1.3s        870        231        294   345          True         False    False
    left out  a_correct  ii_1.5s        868        231        292   345          True         False    False
    left out b_nonearly   i_1.3s        795        229        275   291          True         False     True
    left out b_nonearly  ii_1.5s        792        231        272   289          True         False     True
Paper: preparatory only 219 (197.1 to 240.9), peri-movement only 389 (350.1 to 427.9), both 304 (273.6 to 334.4)

Reading (a)(i), light trials kept: significant in sample 334 , delay 497 , response 681 ; delay only (not sample) 294
Preparatory (sample or delay): 628 (paper 219 + 304 = 523); peri-movement (response): 681 (paper 389 + 304 = 693)

Result: not reproduced. Under the main reading, 231 selective neurons are preparatory only (within tolerance), 284 peri-movement only (paper 389) and 397 both (paper 304). No reading brings the peri-movement count into tolerance (272 to 294, with or without the light trials); the count of both is within it in three readings (289 to 331). The split is shifted towards neurons selective in both epochs. Counted by class, 628 neurons are preparatory (selective in the sample or delay epoch) against the paper's 523 (219 + 304), and 681 peri-movement (selective in the response epoch) against 693 (389 + 304), so the mismatch lies in the preparatory class as the Methods define it.

fig2c-selectivity-time-course¶

Selectivity emerges in the sample epoch, increases through the delay epoch and peaks at the beginning of the response epoch.

Criterion: number of putative pyramidal neurons significantly selective in 200 ms windows (two-tailed t-test, P < 0.05, correct trials), window step 200 ms and 50 ms, both run. Reproduced when, under each step, (1) some sample-epoch window exceeds the 95th percentile of a binomial count with p = 0.05 while the pre-sample windows do not exceed it on average; (2) the last delay window exceeds the first; (3) the maximum falls at the beginning of the response epoch, read as (a) the first window after the go cue or (b) any window starting within 0.5 s after it. Windows are placed on a grid with an edge at the sample event; spikes are stored from 0.57 s before it.

In [14]:
TC = {}
for light, step in [(l, st) for l in ("any", "none") for st in (0.2, 0.05)]:
    first = -0.4 if step == 0.2 else -0.55
    starts = np.round(np.arange(first, 4.9 - 0.2 + 1e-9, step), 3)
    W = alm.window_counts(E, P, starts, light=light)
    nsig = np.zeros(len(starts), int)
    ntest = np.zeros(len(starts), int)
    for R, L in W.values():
        p = stats.ttest_ind(R, L, axis=0).pvalue
        nsig += p < 0.05
        ntest += ~np.isnan(p)
    thr = stats.binom.ppf(0.95, ntest, 0.05)
    TC[(step, light)] = (starts, nsig, thr)
    pre = starts + 0.2 <= 1e-9
    samp = (starts >= -1e-9) & (starts + 0.2 <= 1.3 + 1e-9)
    dl = (starts >= 1.3 - 1e-9) & (starts + 0.2 <= 2.6 + 1e-9)
    resp = starts >= 2.6 - 1e-9
    im = int(np.argmax(nsig))
    c1 = (nsig[samp] > thr[samp]).any() and nsig[pre].mean() <= thr[pre].mean()
    c2 = nsig[dl][-1] > nsig[dl][0]
    c3a = abs(starts[im] - 2.6) < 1e-6
    c3b = (starts[im] >= 2.6 - 1e-9) and (starts[im] < 3.1 - 1e-9)
    print(f"light trials {'kept' if light == 'any' else 'left out'}, step {int(step * 1000)} ms: {len(starts)} windows, neurons tested per window {ntest.min()} to {ntest.max()}, "
          f"binomial 95th percentile {thr.min():.0f} to {thr.max():.0f}")
    print("  pre-sample windows (start, count):", list(zip(starts[pre], nsig[pre])), "mean", round(nsig[pre].mean(), 1))
    print("  sample epoch: max count", nsig[samp].max(), "first window above threshold starts at",
          starts[samp][np.argmax(nsig[samp] > thr[samp])], "s; (1) holds:", c1)
    print("  delay: first window", starts[dl][0], "s count", nsig[dl][0], "; last window", starts[dl][-1], "s count",
          nsig[dl][-1], "; (2) holds:", c2)
    print("  maximum", nsig[im], "in the window starting", round(starts[im] - 2.6, 3), "s after the go cue; first response window",
          nsig[resp][0], "; (3a) holds:", c3a, "; (3b) holds:", c3b)
    print("  five largest windows (start after go cue, count):", [(round(starts[i] - 2.6, 2), int(nsig[i])) for i in np.argsort(-nsig)[:5]])
fig, ax = plt.subplots(figsize=(7, 3))
for (step, light), (starts, nsig, thr) in TC.items():
    ax.plot(starts + 0.1 - 2.6, nsig, label=f"step {int(step * 1000)} ms, light trials {'kept' if light == 'any' else 'left out'}")
ax.plot(TC[(0.05, "any")][0] + 0.1 - 2.6, TC[(0.05, "any")][2], "k:", label="binomial 95th pct")
for x in (-2.6, -1.3, 0):
    ax.axvline(x, color="0.7")
ax.set_xlabel("window centre, s from go cue"); ax.set_ylabel("selective neurons"); ax.legend(fontsize=7)
plt.tight_layout(); plt.show()
print("Counts plotted:", min(v[1].min() for v in TC.values()), "to", max(v[1].max() for v in TC.values()))
light trials kept, step 200 ms: 26 windows, neurons tested per window 1183 to 1223, binomial 95th percentile 72 to 74
  pre-sample windows (start, count): [(np.float64(-0.4), np.int64(59)), (np.float64(-0.2), np.int64(64))] mean 61.5
  sample epoch: max count 272 first window above threshold starts at 0.0 s; (1) holds: True
  delay: first window 1.4 s count 326 ; last window 2.4 s count 408 ; (2) holds: True
  maximum 574 in the window starting 0.2 s after the go cue; first response window 500 ; (3a) holds: False ; (3b) holds: True
  five largest windows (start after go cue, count): [(np.float64(0.2), 574), (np.float64(0.4), 565), (np.float64(0.6), 531), (np.float64(0.8), 530), (np.float64(1.0), 527)]
light trials kept, step 50 ms: 106 windows, neurons tested per window 1175 to 1224, binomial 95th percentile 71 to 74
  pre-sample windows (start, count): [(np.float64(-0.55), np.int64(61)), (np.float64(-0.5), np.int64(61)), (np.float64(-0.45), np.int64(52)), (np.float64(-0.4), np.int64(59)), (np.float64(-0.35), np.int64(63)), (np.float64(-0.3), np.int64(65)), (np.float64(-0.25), np.int64(62)), (np.float64(-0.2), np.int64(64))] mean 60.9
  sample epoch: max count 280 first window above threshold starts at 0.0 s; (1) holds: True
  delay: first window 1.3 s count 321 ; last window 2.4 s count 408 ; (2) holds: True
  maximum 582 in the window starting 0.35 s after the go cue; first response window 500 ; (3a) holds: False ; (3b) holds: True
  five largest windows (start after go cue, count): [(np.float64(0.35), 582), (np.float64(0.25), 580), (np.float64(0.3), 580), (np.float64(0.1), 574), (np.float64(0.2), 574)]
light trials left out, step 200 ms: 26 windows, neurons tested per window 1168 to 1215, binomial 95th percentile 71 to 73
  pre-sample windows (start, count): [(np.float64(-0.4), np.int64(66)), (np.float64(-0.2), np.int64(57))] mean 61.5
  sample epoch: max count 248 first window above threshold starts at 0.0 s; (1) holds: True
  delay: first window 1.4 s count 273 ; last window 2.4 s count 377 ; (2) holds: True
  maximum 525 in the window starting 0.2 s after the go cue; first response window 461 ; (3a) holds: False ; (3b) holds: True
  five largest windows (start after go cue, count): [(np.float64(0.2), 525), (np.float64(0.4), 514), (np.float64(0.6), 490), (np.float64(0.8), 487), (np.float64(1.0), 478)]
light trials left out, step 50 ms: 106 windows, neurons tested per window 1162 to 1218, binomial 95th percentile 71 to 74
  pre-sample windows (start, count): [(np.float64(-0.55), np.int64(62)), (np.float64(-0.5), np.int64(67)), (np.float64(-0.45), np.int64(57)), (np.float64(-0.4), np.int64(66)), (np.float64(-0.35), np.int64(58)), (np.float64(-0.3), np.int64(47)), (np.float64(-0.25), np.int64(60)), (np.float64(-0.2), np.int64(57))] mean 59.2
  sample epoch: max count 249 first window above threshold starts at 0.0 s; (1) holds: True
  delay: first window 1.3 s count 281 ; last window 2.4 s count 377 ; (2) holds: True
  maximum 538 in the window starting 0.25 s after the go cue; first response window 461 ; (3a) holds: False ; (3b) holds: True
  five largest windows (start after go cue, count): [(np.float64(0.25), 538), (np.float64(0.1), 531), (np.float64(0.35), 531), (np.float64(0.2), 525), (np.float64(0.3), 525)]
No description has been provided for this image
Counts plotted: 47 to 582

Result: reproduced. Under both steps, pre-sample windows hold 52 to 65 selective neurons against a chance limit of 71 to 74, sample-epoch windows reach 272 (200 ms step) and 280 (50 ms step), selectivity grows through the delay (326 to 408 and 321 to 408), and the maximum (574 and 582 neurons) lies 0.2 s and 0.35 s after the go cue. The first window after the go cue holds 500, so reading (3a) fails and reading (3b) holds under both steps. Without the trials with light the same holds (pre-sample 47 to 67, sample maximum 248 and 249, delay 273 to 377 and 281 to 377, maximum 525 and 538 at 0.2 s and 0.25 s, first response window 461).

fig2c-population-dynamics¶

Individual neuron responses are dynamic although selectivity is stable at the level of the population (Fig. 2c, bottom, population selectivity and the correlation of the population response with that at the go cue).

Criterion: pseudo-population of (a) the 912 selective or (b) all 1,245 putative pyramidal neurons; each neuron's response (correct trials, 200 ms bins) mean-subtracted and normalized to its variance over the trial; Pearson correlation of the population vector at each time with that at the go cue, the vector concatenating both trial types or taking their difference; population selectivity as preferred minus non-preferred rate, the preference set from 10 trials and computed on the rest. Reproduced when, under each reading, the mean correlation over the sample epoch is below that over the delay epoch, which is below 1, and the population selectivity stays above 0 through the delay. The claims file names readings for three details the paper leaves open, all run in every combination: normalization (v) by the variance, the text as written, or (s) by the standard deviation; the preference set from (n10) 10 trials in all or (n20) 10 of each type; and the 200 ms bin at the go cue that (gs) starts at, (gc) is centred on, or (ge) ends at the cue (the centred bin comes from a grid shifted by 0.1 s). Trials with tagging light are (t1) kept or (t2) left out, either of which may decide.

In [15]:
rng = np.random.default_rng(2024)
starts = np.round(np.arange(-3.0, 2.0 + 1e-9, 0.2), 3)  # bin starts relative to the go cue
nb = len(starts)
# A second grid shifted by 0.1 s holds the bin centred on the go cue, reading (gc).
grids = {"A": starts, "B": np.round(starts - 0.1, 3)}
def epoch_masks(st):
    return ((st >= -2.6 - 1e-9) & (st + 0.2 <= -1.3 + 1e-9), (st >= -1.3 - 1e-9) & (st + 0.2 <= 1e-9))
samp, dl = epoch_masks(starts)
refs = (("gs, bin starting at go", "A", 0.0), ("gc, bin centred on go", "B", -0.1), ("ge, bin ending at go", "A", -0.2))
rows = []
curves = {}
for light in ("any", "none"):
    lname = "kept" if light == "any" else "left out"
    Wg = alm.window_counts(E, P, starts, align="go", light=light)
    WgB = alm.window_counts(E, P, grids["B"], align="go", light=light)
    sel_idx = set(P.index[SELSET[light].reindex(P.index).values])
    Rm = {g: {k: R.mean(0) / 0.2 for k, (R, L) in W.items()} for g, W in (("A", Wg), ("B", WgB))}
    Lm = {g: {k: L.mean(0) / 0.2 for k, (R, L) in W.items()} for g, W in (("A", Wg), ("B", WgB))}
    popsel = {}
    for mode in ("10 in all", "10 per type"):
        vals = {}
        for k, (R, L) in Wg.items():
            n_r, n_l = len(R), len(L)
            if mode == "10 in all":
                pick = rng.choice(n_r + n_l, 10, replace=False)
                pr, pl = pick[pick < n_r], pick[pick >= n_r] - n_r
            else:
                pr, pl = rng.choice(n_r, min(10, n_r), replace=False), rng.choice(n_l, min(10, n_l), replace=False)
            restR, restL = np.setdiff1d(np.arange(n_r), pr), np.setdiff1d(np.arange(n_l), pl)
            if len(restR) == 0 or len(restL) == 0:
                continue
            mr = R[pr].sum(1).mean() if len(pr) else 0.0
            ml = L[pl].sum(1).mean() if len(pl) else 0.0
            sign = 1 if mr >= ml else -1
            vals[k] = sign * (R[restR].mean(0) - L[restL].mean(0)) / 0.2
        popsel[mode] = vals
    for pop in ("a_selective", "b_all"):
        keys = [k for k in Wg if (k in sel_idx or pop == "b_all")]
        for norm in ("v, variance", "s, sd"):
            for vec in ("concatenated", "difference"):
                for ref_name, g, ref_start in refs:
                    X = np.array([np.concatenate([Rm[g][k], Lm[g][k]]) for k in keys])
                    scale = X.var(1) if norm.startswith("v") else X.std(1)
                    keep = scale > 0
                    Z = (X[keep] - X[keep].mean(1, keepdims=True)) / scale[keep][:, None]
                    Zr, Zl = Z[:, :nb], Z[:, nb:]
                    V = np.concatenate([Zr, Zl], 0) if vec == "concatenated" else Zr - Zl
                    ref = int(np.argmin(abs(grids[g] - ref_start)))
                    c = np.array([np.corrcoef(V[:, i], V[:, ref])[0, 1] for i in range(nb)])
                    sm, dm = epoch_masks(grids[g])
                    curves[(light, pop, norm, vec, ref_name)] = (grids[g], c)
                    rows.append(dict(light_trials=lname, neurons=pop, n=int(keep.sum()), norm=norm, vector=vec, reference=ref_name,
                                     r_sample=c[sm].mean(), r_delay=c[dm].mean(), holds=c[sm].mean() < c[dm].mean() < 1))
        for mode, vals in popsel.items():
            ps = np.array([vals[k] for k in keys if k in vals])
            boot = np.array([ps[rng.integers(0, len(ps), len(ps))].mean(0) for _ in range(1000)])
            lo = np.percentile(boot, 2.5, axis=0)
            rows.append(dict(light_trials=lname, neurons=pop, n=len(ps), norm="", vector=f"population selectivity, preference from {mode}",
                             reference="", r_sample=ps.mean(0)[samp].mean(), r_delay=ps.mean(0)[dl].min(),
                             holds=bool((ps.mean(0)[dl] > 0).all())))
            print(f"light trials {lname}, {pop}, preference from {mode}: population selectivity per delay bin (spikes/s)",
                  ps.mean(0)[dl].round(2).tolist(), "; bootstrap 2.5th percentile", lo[dl].round(2).tolist(),
                  f"; neurons without remaining trials {len(keys) - len(ps)}")
res_pd = pd.DataFrame(rows)
print("\nCorrelation rows: r_sample and r_delay are the mean correlations over the sample and delay bins.")
print("Selectivity rows: r_sample is the mean over sample bins, r_delay the minimum over delay bins (spikes/s).")
print(res_pd.round(3).to_string(index=False))
print("Readings that hold:", int(res_pd.holds.sum()), "of", len(res_pd))
fig, ax = plt.subplots(figsize=(7, 3))
for key in [("any", "a_selective", "v, variance", "concatenated", "gs, bin starting at go"),
            ("any", "a_selective", "s, sd", "difference", "gc, bin centred on go"),
            ("none", "b_all", "v, variance", "difference", "gs, bin starting at go")]:
    ax.plot(curves[key][0] + 0.1, curves[key][1], label=", ".join(key))
for x in (-2.6, -1.3, 0):
    ax.axvline(x, color="0.7")
ax.set_xlabel("bin centre, s from go cue"); ax.set_ylabel("correlation with go-cue vector"); ax.legend(fontsize=6)
plt.tight_layout(); plt.show()
print("Correlations range over all readings:", round(min(v[1].min() for v in curves.values()), 3), "to", round(max(v[1].max() for v in curves.values()), 3))
corr = res_pd[res_pd.reference != ""]
print("Correlation readings that hold, by light reading:", corr.groupby("light_trials").holds.agg(["sum", "size"]).to_dict("index"))
print("Every reading holds, light trials kept (t1):", bool(res_pd[res_pd.light_trials == "kept"].holds.all()),
      "; left out (t2):", bool(res_pd[res_pd.light_trials == "left out"].holds.all()))
light trials kept, a_selective, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.88, 1.05, 1.23, 1.36, 1.45, 1.48] ; bootstrap 2.5th percentile [0.67, 0.81, 0.95, 1.05, 1.12, 1.14] ; neurons without remaining trials 0
light trials kept, a_selective, preference from 10 per type: population selectivity per delay bin (spikes/s) [1.02, 1.12, 1.27, 1.44, 1.57, 1.61] ; bootstrap 2.5th percentile [0.82, 0.86, 0.98, 1.13, 1.24, 1.27] ; neurons without remaining trials 11
light trials kept, b_all, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.62, 0.77, 0.89, 0.98, 1.06, 1.08] ; bootstrap 2.5th percentile [0.46, 0.57, 0.67, 0.73, 0.82, 0.82] ; neurons without remaining trials 0
light trials kept, b_all, preference from 10 per type: population selectivity per delay bin (spikes/s) [0.7, 0.81, 0.91, 1.04, 1.13, 1.16] ; bootstrap 2.5th percentile [0.52, 0.62, 0.69, 0.81, 0.88, 0.91] ; neurons without remaining trials 14
light trials left out, a_selective, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.93, 1.09, 1.3, 1.42, 1.5, 1.56] ; bootstrap 2.5th percentile [0.7, 0.82, 1.0, 1.09, 1.16, 1.21] ; neurons without remaining trials 0
light trials left out, a_selective, preference from 10 per type: population selectivity per delay bin (spikes/s) [1.04, 1.13, 1.31, 1.41, 1.57, 1.62] ; bootstrap 2.5th percentile [0.81, 0.85, 1.0, 1.11, 1.23, 1.28] ; neurons without remaining trials 11
light trials left out, b_all, preference from 10 in all: population selectivity per delay bin (spikes/s) [0.63, 0.76, 0.89, 0.98, 1.03, 1.08] ; bootstrap 2.5th percentile [0.46, 0.57, 0.69, 0.75, 0.8, 0.84] ; neurons without remaining trials 0
light trials left out, b_all, preference from 10 per type: population selectivity per delay bin (spikes/s) [0.7, 0.76, 0.86, 0.97, 1.08, 1.11] ; bootstrap 2.5th percentile [0.54, 0.58, 0.66, 0.76, 0.84, 0.86] ; neurons without remaining trials 14

Correlation rows: r_sample and r_delay are the mean correlations over the sample and delay bins.
Selectivity rows: r_sample is the mean over sample bins, r_delay the minimum over delay bins (spikes/s).
light_trials     neurons    n        norm                                              vector              reference  r_sample  r_delay  holds
        kept a_selective  912 v, variance                                        concatenated gs, bin starting at go    -0.028    0.252   True
        kept a_selective  912 v, variance                                        concatenated  gc, bin centred on go    -0.019    0.373   True
        kept a_selective  912 v, variance                                        concatenated   ge, bin ending at go     0.011    0.558   True
        kept a_selective  912 v, variance                                          difference gs, bin starting at go     0.071    0.426   True
        kept a_selective  912 v, variance                                          difference  gc, bin centred on go     0.096    0.483   True
        kept a_selective  912 v, variance                                          difference   ge, bin ending at go     0.091    0.630   True
        kept a_selective  912       s, sd                                        concatenated gs, bin starting at go    -0.122    0.249   True
        kept a_selective  912       s, sd                                        concatenated  gc, bin centred on go    -0.027    0.494   True
        kept a_selective  912       s, sd                                        concatenated   ge, bin ending at go     0.021    0.705   True
        kept a_selective  912       s, sd                                          difference gs, bin starting at go     0.148    0.478   True
        kept a_selective  912       s, sd                                          difference  gc, bin centred on go     0.244    0.673   True
        kept a_selective  912       s, sd                                          difference   ge, bin ending at go     0.240    0.781   True
        kept a_selective  912               population selectivity, preference from 10 in all                            0.413    0.880   True
        kept a_selective  901             population selectivity, preference from 10 per type                            0.552    1.023   True
        kept       b_all 1245 v, variance                                        concatenated gs, bin starting at go    -0.048    0.036   True
        kept       b_all 1245 v, variance                                        concatenated  gc, bin centred on go    -0.014    0.114   True
        kept       b_all 1245 v, variance                                        concatenated   ge, bin ending at go     0.018    0.339   True
        kept       b_all 1245 v, variance                                          difference gs, bin starting at go     0.050    0.057   True
        kept       b_all 1245 v, variance                                          difference  gc, bin centred on go     0.074    0.120   True
        kept       b_all 1245 v, variance                                          difference   ge, bin ending at go     0.068    0.370   True
        kept       b_all 1245       s, sd                                        concatenated gs, bin starting at go    -0.123    0.208   True
        kept       b_all 1245       s, sd                                        concatenated  gc, bin centred on go    -0.043    0.437   True
        kept       b_all 1245       s, sd                                        concatenated   ge, bin ending at go     0.013    0.655   True
        kept       b_all 1245       s, sd                                          difference gs, bin starting at go     0.134    0.423   True
        kept       b_all 1245       s, sd                                          difference  gc, bin centred on go     0.205    0.593   True
        kept       b_all 1245       s, sd                                          difference   ge, bin ending at go     0.205    0.710   True
        kept       b_all 1245               population selectivity, preference from 10 in all                            0.289    0.624   True
        kept       b_all 1231             population selectivity, preference from 10 per type                            0.383    0.705   True
    left out a_selective  870 v, variance                                        concatenated gs, bin starting at go    -0.042    0.214   True
    left out a_selective  870 v, variance                                        concatenated  gc, bin centred on go    -0.038    0.323   True
    left out a_selective  870 v, variance                                        concatenated   ge, bin ending at go    -0.031    0.535   True
    left out a_selective  870 v, variance                                          difference gs, bin starting at go     0.014    0.394   True
    left out a_selective  870 v, variance                                          difference  gc, bin centred on go     0.063    0.458   True
    left out a_selective  870 v, variance                                          difference   ge, bin ending at go     0.022    0.605   True
    left out a_selective  870       s, sd                                        concatenated gs, bin starting at go    -0.125    0.246   True
    left out a_selective  870       s, sd                                        concatenated  gc, bin centred on go    -0.024    0.487   True
    left out a_selective  870       s, sd                                        concatenated   ge, bin ending at go     0.025    0.696   True
    left out a_selective  870       s, sd                                          difference gs, bin starting at go     0.140    0.462   True
    left out a_selective  870       s, sd                                          difference  gc, bin centred on go     0.237    0.659   True
    left out a_selective  870       s, sd                                          difference   ge, bin ending at go     0.236    0.765   True
    left out a_selective  870               population selectivity, preference from 10 in all                            0.528    0.930   True
    left out a_selective  859             population selectivity, preference from 10 per type                            0.594    1.039   True
    left out       b_all 1244 v, variance                                        concatenated gs, bin starting at go    -0.038    0.065   True
    left out       b_all 1244 v, variance                                        concatenated  gc, bin centred on go     0.016    0.124   True
    left out       b_all 1244 v, variance                                        concatenated   ge, bin ending at go    -0.020    0.343   True
    left out       b_all 1244 v, variance                                          difference gs, bin starting at go     0.078    0.054  False
    left out       b_all 1244 v, variance                                          difference  gc, bin centred on go     0.126    0.103  False
    left out       b_all 1244 v, variance                                          difference   ge, bin ending at go     0.042    0.360   True
    left out       b_all 1244       s, sd                                        concatenated gs, bin starting at go    -0.117    0.200   True
    left out       b_all 1244       s, sd                                        concatenated  gc, bin centred on go    -0.039    0.428   True
    left out       b_all 1244       s, sd                                        concatenated   ge, bin ending at go     0.009    0.642   True
    left out       b_all 1244       s, sd                                          difference gs, bin starting at go     0.125    0.400   True
    left out       b_all 1244       s, sd                                          difference  gc, bin centred on go     0.195    0.574   True
    left out       b_all 1244       s, sd                                          difference   ge, bin ending at go     0.192    0.680   True
    left out       b_all 1245               population selectivity, preference from 10 in all                            0.351    0.633   True
    left out       b_all 1231             population selectivity, preference from 10 per type                            0.424    0.698   True
Readings that hold: 54 of 56
No description has been provided for this image
Correlations range over all readings: -0.286 to 1.0
Correlation readings that hold, by light reading: {'kept': {'sum': 24, 'size': 24}, 'left out': {'sum': 22, 'size': 24}}
Every reading holds, light trials kept (t1): True ; left out (t2): False

Result: reproduced. With the light trials kept (t1), all 24 correlation readings hold: the mean correlation with the go-cue vector is lower over the sample epoch (-0.12 to 0.24) than over the delay epoch (0.04 to 0.78), the delay value is below 1, and the population selectivity stays above 0 in every delay bin under all four selectivity readings (minimum 0.62 spikes/s). The centred go-cue bin (gc) gives sample means of -0.04 to 0.24 and delay means of 0.11 to 0.67. Without the light trials (t2), 22 of the 24 correlation readings hold; two fail (all neurons, divided by the variance, difference vector, bin starting at the go cue: sample 0.078 against delay 0.054; bin centred on it: 0.126 against 0.103). Under t1 the claim holds in every reading, and the claims file lets either light reading decide. Under the bin ending at the go cue (ge) that bin lies inside the delay epoch, so its correlation of 1 enters the delay mean; the bin starting at the cue (gs) avoids this and holds in all 8 t1 rows (sample means -0.123 to 0.148, delay means 0.036 to 0.478).

ed2c-error-trials-predict-choice¶

On error trials ALM activity predicts the direction the mouse licked, so most selective neurons switch their trial-type preference relative to correct trials.

Criterion: for the 912 selective neurons and each epoch, selectivity (rate lick right minus rate lick left, by instructed trial type) on correct and on error trials; Pearson correlation across neurons. Readings: at least (a) one or (b) five error trials of each type. Reproduced when r < 0 in the delay and response epochs under each reading.

In [16]:
rows = []
for light in ("any", "none"):
    selU = P[SELSET[light].reindex(P.index).values]
    c_st = alm.epoch_stats(E, selU, "correct", 1.3, light)
    e_st = alm.epoch_stats(E, selU, "error", 1.3, light)
    for mn in (1, 5):
        ok = (e_st.n_right >= mn) & (e_st.n_left >= mn)
        for ep in ("sample", "delay", "response"):
            x = (c_st["R_" + ep] - c_st["L_" + ep])[ok]
            y = (e_st["R_" + ep] - e_st["L_" + ep])[ok]
            r, p = stats.pearsonr(x, y)
            rows.append(dict(light_trials="kept" if light == "any" else "left out", min_error_trials=mn, epoch=ep,
                             neurons=int(ok.sum()), left_out=len(selU) - int(ok.sum()), r=r, p=p,
                             switched=np.mean(np.sign(x) != np.sign(y))))
    if light == "any":
        ok = (e_st.n_right >= 1) & (e_st.n_left >= 1)
        x = (c_st.R_delay - c_st.L_delay)[ok]; y = (e_st.R_delay - e_st.L_delay)[ok]
        per_mouse = pd.DataFrame(dict(subject=selU.subject[ok], x=x, y=y)).groupby("subject").apply(
            lambda g: pd.Series(dict(n=len(g), r=stats.pearsonr(g.x, g.y)[0] if len(g) > 2 else np.nan)))
ED2 = pd.DataFrame(rows)
print(ED2.round(4).assign(p=[f"{x:.1e}" for x in ED2.p]).to_string(index=False))
print("Per mouse, delay epoch, at least one error trial of each type, light trials kept:")
print(per_mouse.round(2).to_string())
light_trials  min_error_trials    epoch  neurons  left_out       r       p  switched
        kept                 1   sample      897        15 -0.0002 9.9e-01    0.4928
        kept                 1    delay      897        15 -0.2970 1.0e-19    0.5708
        kept                 1 response      897        15 -0.3806 2.7e-32    0.6132
        kept                 5   sample      833        79  0.0447 2.0e-01    0.4898
        kept                 5    delay      833        79 -0.2502 2.4e-13    0.5738
        kept                 5 response      833        79 -0.3363 1.8e-23    0.6170
    left out                 1   sample      849        21  0.0255 4.6e-01    0.5100
    left out                 1    delay      849        21 -0.2906 5.5e-18    0.5595
    left out                 1 response      849        21 -0.3293 6.3e-23    0.5936
    left out                 5   sample      763       107 -0.0246 5.0e-01    0.5177
    left out                 5    delay      763       107 -0.3158 4.0e-19    0.5714
    left out                 5 response      763       107 -0.3018 1.6e-17    0.6055
Per mouse, delay epoch, at least one error trial of each type, light trials kept:
             n     r
subject             
210861    25.0  0.49
210862    27.0 -0.09
210863    50.0 -0.46
214427    18.0  0.01
214429    29.0 -0.51
214430    32.0  0.11
218453    29.0 -0.19
218457    77.0  0.16
218693    46.0 -0.76
219030    32.0 -0.75
219031    61.0 -0.30
219033    41.0 -0.59
219036    75.0 -0.67
219037    67.0 -0.67
219038    75.0 -0.08
219247    38.0 -0.06
219248   104.0 -0.75
219253    49.0  0.41
221977    22.0 -0.21

Result: reproduced. Selectivity on error trials is anticorrelated with that on correct trials in the delay epoch (r = -0.30 with at least one error trial of each type, 897 neurons; -0.25 with at least five, 833 neurons) and the response epoch (-0.38 and -0.34), all P < 1e-12. Without the light trials, r is -0.29 and -0.32 (delay) and -0.33 and -0.30 (response). In the sample epoch the correlation is near 0 (-0.025 to 0.045).

fig2e-no-contra-bias-spikes¶

Contra-preferring and ipsi-preferring neurons are present in equal proportions in left ALM, and the overall spike count shows no significant contralateral bias (P = 0.13; 8% spike rate modulation across all task-selective neurons).

Criterion: a claim of absence. Two-tailed t-test against 0 of the spike count difference (lick right minus lick left, correct trials) across neurons; reproduced when P >= 0.05. The criterion names readings: neurons (a) all 1,245 or (b) the 912 selective; window (i) the three epochs together, (ii) the 570 ms before the go cue, (iii) the delay epoch. The protocol judges absence on the paper's test as written, so the outcome rests on (a)(i), "the overall spike count across the recorded population"; the others are shown. The proportions are tested with the bootstrap of Fig. 3f, j, which must not find a contralateral excess at P < 0.05. The 8% modulation is checked with the readings of fig3f-pt-contra-selectivity.

In [17]:
rng = np.random.default_rng(7)
rows, boots = [], []
for light in ("any", "none"):
    lname = "kept" if light == "any" else "left out"
    selU = P[SELSET[light].reindex(P.index).values]
    for pop, units in (("a_all", P), ("b_selective", selU)):
        for win, (t0, t1) in (("i_three_epochs", (None, None)), ("ii_570ms", (-0.57, 0.0)), ("iii_delay", (-1.3, 0.0))):
            if t0 is None:
                st = alm.epoch_stats(E, units, "correct", 1.3, light)
                Rr = sum(st["R_" + e] * 1.3 for e in ("sample", "delay", "response"))
                Ll = sum(st["L_" + e] * 1.3 for e in ("sample", "delay", "response"))
            else:
                w = alm.window_diff(E, units, t0, t1, light=light)
                Rr, Ll = w.R * (t1 - t0), w.L * (t1 - t0)
            diff = Rr - Ll
            t = stats.ttest_1samp(diff, 0)
            rows.append(dict(light_trials=lname, neurons=pop, window=win, n=len(diff), mean_count_diff=diff.mean(), t=t.statistic,
                             P=t.pvalue, mod_x_pct=100 * diff.mean() / ((Rr + Ll) / 2).mean(), mod_y_pct=100 * diff.mean() / Ll.mean()))
    contra = (alm.total_count_diff(E, selU, light=light) > 0).values
    pb = alm.boot_more_ipsi(contra, rng)
    boots.append(dict(light_trials=lname, selective=len(contra), contra=int(contra.sum()), fraction=round(contra.mean(), 3),
                      P_more_ipsi=pb, P_contra_at_least_ipsi=1 - pb, binomial_P=stats.binomtest(int(contra.sum()), len(contra), 0.5).pvalue))
    if light == "any":
        by_mouse = pd.DataFrame(dict(subject=selU.subject.values, contra=contra)).groupby("subject").contra.agg(["sum", "size"])
FE = pd.DataFrame(rows)
print(FE.round(4).to_string(index=False))
print("Paper: P = 0.13; modulation 8% (tolerance 7.2 to 8.8)")
both = FE[(FE.P >= 0.05) & (((FE.mod_x_pct - 8).abs() <= 0.8) | ((FE.mod_y_pct - 8).abs() <= 0.8))]
print("Readings with P >= 0.05 and a modulation (x or y) within 7.2 to 8.8%:")
print(both.round(4).to_string(index=False))
print("\nContra- and ipsi-preferring selective neurons, bootstrap (neurons resampled, 10^4):")
print(pd.DataFrame(boots).round(4).to_string(index=False))
print("By mouse, light trials kept (contra-preferring / selective):")
print(by_mouse.to_string())
light_trials     neurons         window    n  mean_count_diff      t      P  mod_x_pct  mod_y_pct
        kept       a_all i_three_epochs 1245           0.0631 0.2221 0.8243     0.5375     0.5390
        kept       a_all       ii_570ms 1245           0.1529 2.1054 0.0355     8.5480     8.9297
        kept       a_all      iii_delay 1245           0.2263 1.6436 0.1005     5.7589     5.9297
        kept b_selective i_three_epochs  912           0.1596 0.4250 0.6709     1.1766     1.1836
        kept b_selective       ii_570ms  912           0.1938 1.9626 0.0500     9.2470     9.6952
        kept b_selective      iii_delay  912           0.2953 1.5774 0.1151     6.4495     6.6644
    left out       a_all i_three_epochs 1245           0.1862 0.6416 0.5212     1.5809     1.5935
    left out       a_all       ii_570ms 1245           0.1689 2.2994 0.0216     9.3757     9.8369
    left out       a_all      iii_delay 1245           0.2712 1.9564 0.0506     6.8693     7.1136
    left out b_selective i_three_epochs  870           0.3731 0.9447 0.3451     2.7038     2.7409
    left out b_selective       ii_570ms  870           0.2179 2.0830 0.0375    10.1573    10.7008
    left out b_selective      iii_delay  870           0.3724 1.8854 0.0597     7.9661     8.2965
Paper: P = 0.13; modulation 8% (tolerance 7.2 to 8.8)
Readings with P >= 0.05 and a modulation (x or y) within 7.2 to 8.8%:
light_trials     neurons    window   n  mean_count_diff      t      P  mod_x_pct  mod_y_pct
    left out b_selective iii_delay 870           0.3724 1.8854 0.0597     7.9661     8.2965

Contra- and ipsi-preferring selective neurons, bootstrap (neurons resampled, 10^4):
light_trials  selective  contra  fraction  P_more_ipsi  P_contra_at_least_ipsi  binomial_P
        kept        912     419     0.459       0.9929                  0.0071      0.0156
    left out        870     408     0.469       0.9671                  0.0329      0.0723
By mouse, light trials kept (contra-preferring / selective):
         sum  size
subject           
210861     5    25
210862    16    27
210863    22    50
214427     6    18
214429    15    29
214430    15    32
218453    14    29
218457    32    77
218693    18    46
219030     9    32
219031    32    61
219033    18    41
219036    28    75
219037    35    67
219038    32    75
219247    26    38
219248    56   104
219253    29    64
221977    11    22

Result: reproduced. On the paper's test as written, the overall spike count difference across the 1,245 putative pyramidal neurons is not different from 0 (P = 0.82, or 0.52 without the light trials; 0.67 and 0.35 for the selective neurons). The bootstrap finds no contralateral excess: 419 of 912 selective neurons are contra-preferring (0.459; 408 of 870 without light trials). The proportions are not equal, though: ipsi-preferring neurons are the majority (bootstrap P = 0.99 and 0.97 for more ipsi than contra; two-sided binomial P = 0.016 and 0.072). In the 570 ms before the go cue the difference reaches P = 0.022 to 0.050 across neurons and light readings, and the delay epoch gives P = 0.051 to 0.12. The 8% modulation is matched in some readings (all neurons, 570 ms window, difference over the mean rate: 8.5%; selective neurons, delay, light trials left out: 8.0% and 8.3%), not in others (5.8% to 10.7%). Only one reading meets both the absence test and the 8% modulation: the selective neurons, the delay epoch, light trials left out (P = 0.060, 7.97%); it is the measured value. The outcome rests on (a)(i) because a claim of absence is judged on the paper's test as written ("the overall spike count across the recorded population"). The result depends on the window: in the 570 ms before the go cue, where the Results report the same P = 0.13 for all task-selective neurons, all four tests give P = 0.022 to 0.050 with a contralateral excess, the criterion's condition for not reproduced. The criterion sets no rule for deciding a claim of absence across its readings, so the outcome depends on which reading is taken: on the second P = 0.13 test of the paper, for all task-selective neurons, the 570 ms window gives P = 0.050 and 0.038 with a contralateral excess and the delay epoch P = 0.12 and 0.060.

ed1-fast-spiking-properties¶

Fast-spiking neurons have higher baseline firing rates and are less selective than putative pyramidal neurons.

Criterion: baseline (a) the 0.5 s before pole movement onset or (b) all recorded time before it in each trial; selectivity (i) the fraction significantly selective by the test of fig2-selective-fraction or (ii) the mean absolute spike rate difference between trial types over the three epochs. Reproduced when, under every reading, the fast-spiking median and mean baseline exceed the pyramidal ones and their selectivity is lower; a two-sided Mann-Whitney test is reported.

In [18]:
FSu = U[(U.width_class == "FS") & ~U.is_duplicate]
def props(units, light):
    st = alm.epoch_stats(E, units, "correct", 1.3, light)
    rows = []
    for u in units.itertuples():
        s = E[u.session]
        tr = s["trials"]
        t = tr[alm.trial_sets(tr, light)["correct"]]
        sp = s["spikes"][u.unit]
        rows.append(dict(idx=u.Index,
                         base_a=(alm.counts(sp, t["sample"].values - 0.5, t["sample"].values) / 0.5).mean(),
                         base_b=(alm.counts(sp, t.start.values, t["sample"].values) / (t["sample"] - t.start).values).mean()))
    d = pd.DataFrame(rows).set_index("idx")
    d["selective"] = (st[["p_sample", "p_delay", "p_response"]] < 0.05).any(axis=1)
    d["absdiff"] = np.mean([abs(st["R_" + e] - st["L_" + e]) for e in ("sample", "delay", "response")], axis=0)
    return d
rows = []
for light in ("any", "none"):
    fs, py = props(FSu, light), props(P, light)
    for k in ("base_a", "base_b", "absdiff"):
        rows.append(dict(light_trials="kept" if light == "any" else "left out", measure=k, FS_median=fs[k].median(), FS_mean=fs[k].mean(),
                         Pyr_median=py[k].median(), Pyr_mean=py[k].mean(), MW_P=f"{stats.mannwhitneyu(fs[k], py[k]).pvalue:.1e}"))
    rows.append(dict(light_trials="kept" if light == "any" else "left out", measure="fraction selective", FS_median=np.nan,
                     FS_mean=fs.selective.mean(), Pyr_median=np.nan, Pyr_mean=py.selective.mean(),
                     MW_P=f"Fisher {stats.fisher_exact([[fs.selective.sum(), (~fs.selective).sum()], [py.selective.sum(), (~py.selective).sum()]])[1]:.3f}"))
print(pd.DataFrame(rows).round(4).to_string(index=False))
print("For 'fraction selective', FS_mean and Pyr_mean are the fractions; FS units:", len(FSu), "; Pyr units:", len(P))
print("Pre-sample time recorded per trial (s), the span of baseline (b):",
      round(float(np.median(np.concatenate([(s['trials']['sample'] - s['trials'].start).dropna().values for s in E]))), 3))
print("FS units per mouse:", FSu.groupby("subject").size().to_dict())
light_trials            measure  FS_median  FS_mean  Pyr_median  Pyr_mean         MW_P
        kept             base_a     4.5138   6.8955      1.5000    2.9152      1.3e-17
        kept             base_b     4.4763   6.8648      1.4906    2.8935      5.1e-18
        kept            absdiff     1.4574   2.0871      0.6204    1.3909      2.0e-10
        kept fraction selective        NaN   0.7500         NaN    0.7325 Fisher 0.749
    left out             base_a     4.3579   6.9517      1.4958    2.9209      1.9e-17
    left out             base_b     4.4363   6.9258      1.4983    2.9023      8.2e-18
    left out            absdiff     1.4575   2.1312      0.6202    1.4254      5.5e-11
    left out fraction selective        NaN   0.7097         NaN    0.6988 Fisher 0.838
For 'fraction selective', FS_mean and Pyr_mean are the fractions; FS units: 124 ; Pyr units: 1245
Pre-sample time recorded per trial (s), the span of baseline (b): 0.572
FS units per mouse: {'210861': 6, '210862': 6, '210863': 9, '214427': 3, '214429': 3, '214430': 3, '218453': 8, '218457': 11, '218693': 6, '219030': 9, '219031': 11, '219033': 5, '219036': 7, '219037': 4, '219038': 8, '219247': 4, '219248': 13, '219253': 6, '221977': 2}

Result: not reproduced. Fast-spiking neurons have higher baseline rates under both readings (median 4.51 against 1.50 spikes/s and mean 6.90 against 2.92 over the 0.5 s before the sample; Mann-Whitney P = 1e-17). They are not less selective: 0.750 of them are selective against 0.733 of putative pyramidal neurons (0.710 and 0.699 without the light trials), and their mean absolute rate difference is larger (median 1.46 against 0.62 spikes/s). The baseline part holds; the selectivity part goes the other way under both selectivity readings, and the criterion requires both parts under every reading.

tagging-counts¶

Of 73 neurons antidromically activated from pyramidal tract axons, 45 passed the collision test (PT neurons), 22 failed and 6 could not be tested; of 134 activated from IT axons, 27 passed (IT neurons), 106 failed and 1 could not be tested.

Criterion: counts of units by the labels the dataset gives for tagging outcome.

In [19]:
lab = U.label.value_counts()
tab = pd.DataFrame([dict(quantity="PT neurons", measured=int(lab.get("PT", 0)), expected=45, tolerance=4.5),
                    dict(quantity="IT neurons", measured=int(lab.get("IT", 0)), expected=27, tolerance=2.7),
                    dict(quantity="PT-activated, failed collision", measured=None, expected=22, tolerance=2.2),
                    dict(quantity="IT-activated, failed collision", measured=None, expected=106, tolerance=10.6)])
tab["within"] = [abs(m - e) <= t if m is not None else None for m, e, t in zip(tab.measured, tab.expected, tab.tolerance)]
print(tab.to_string(index=False))
print("Unit labels in the files:", sorted(U.label.unique()), "; no label marks activated neurons that failed the collision test or could not be tested")
print("Tagged units by mouse and genotype:")
g = pd.DataFrame(dict(subject=U.subject, label=U.label, genotype=[E[s]["genotype"] for s in U.session]))
print(g[g.label.isin(["PT", "IT"])].groupby(["genotype", "subject", "label"]).size().to_string())
                      quantity  measured  expected  tolerance  within
                    PT neurons      45.0        45        4.5    True
                    IT neurons      27.0        27        2.7    True
PT-activated, failed collision       NaN        22        2.2   False
IT-activated, failed collision       NaN       106       10.6   False
Unit labels in the files: [np.str_('FS'), np.str_('IT'), np.str_('PT'), np.str_('Pyr')] ; no label marks activated neurons that failed the collision test or could not be tested
Tagged units by mouse and genotype:
genotype             subject  label
chr2-eyfp x tlx-cre  218453   IT        5
                     218457   IT        3
rbp4-cre             214427   IT        3
                              PT        1
                     214429   PT        4
sim1-cre             219030   PT        4
                     219031   PT        1
                     219033   PT        3
                     219036   PT        8
                     219037   PT       12
                     219038   PT       12
tlx-cre              219247   IT        5
                     219248   IT        7
                     219253   IT        3
                     221977   IT        1

Result: partially reproduced. The files label 45 PT and 27 IT units, the paper's counts. The 22 and 106 activated neurons that failed the collision test, and the 7 that could not be tested, carry no label, so half of the counts cannot be checked.

antidromic-latencies¶

PT neurons had shorter antidromic latencies (mean 2.7 ms) than IT neurons (4.6 ms), and both had smaller jitter (0.1 and 0.2 ms) than activated neurons that failed collision (latency 8.3 and 9.8 ms, jitter 2.8 and 2.4 ms).

Criterion: latency is the time from light pulse onset to the antidromic spike, jitter its s.d. over pulses, per neuron, then averaged over neurons; tested only if the dataset holds the tagging trials, which it does (data check). Reproduced when PT latency is below IT latency and all values are within tolerance (the jitters to their printed precision, 0.1 and 0.2 ms); partially reproduced when the order holds and a value falls outside. The paper gives no rule for which spike is antidromic, so the claims file names three readings: (w) the first spike within a window after the first pulse of each pair, the window open (about 10 ms, up to the second pulse); (c) as (w), leaving out pulses preceded shortly by a spike (collision trials); (r) as (w), with only spikes in the evoked peak of each neuron's latency histogram. Here the window is 5, 8 or 10 ms; "shortly" is 5, 10 or 20 ms; the peak is the 0.1 ms histogram bin with most spikes, and spikes within 0.5 or 1 ms of it are kept. PT units are aligned to the pons light and IT units to the ALM light. The activated neurons that failed the collision test carry no label, so their four values cannot be measured.

In [20]:
readings = [("w", None)] + [("c", ms) for ms in (5, 10, 20)] + [("r", ms) for ms in (0.5, 1.0)]
rows, light_units = [], []
for u in U[U.label.isin(["PT", "IT"])].itertuples():
    s = E[u.session]
    site = "PONS" if u.label == "PT" else "ALM"  # PT neurons are tagged from the pons, IT neurons from the other ALM
    series = [v["onsets"] for k, v in s["laser"].items() if site in k]
    if not series:
        light_units.append(dict(label=u.label, genotype=s["genotype"], light=False))
        continue
    light_units.append(dict(label=u.label, genotype=s["genotype"], light=True))
    on = np.concatenate(series)
    sp = s["spikes"][u.unit]
    for win in (0.005, 0.008, 0.010):
        for rd, par in readings:
            lat = alm.antidromic_latencies(sp, on, win, rd, par)
            rows.append(dict(label=u.label, unit=u.Index, subject=u.subject, window_ms=win * 1000, reading=rd, par_ms=par,
                             pairs=len(alm.pair_onsets(on)), kept=len(lat), latency=lat.mean() if len(lat) else np.nan,
                             jitter=lat.std() if len(lat) > 1 else np.nan))
AL = pd.DataFrame(rows)
LU = pd.DataFrame(light_units)
print("Tagged units with and without a light series of their site:", LU.groupby(["label", "light"]).size().to_dict(),
      "; without:", LU[~LU.light].groupby(["genotype", "label"]).size().to_dict())
AL["par_ms"] = AL.par_ms.fillna(0)
g = AL.groupby(["window_ms", "reading", "par_ms", "label"])
T = g[["latency", "jitter"]].mean().unstack("label")
T.columns = [f"{a}_{b}" for a, b in T.columns]
T = T.join(g.kept.median().unstack("label").add_prefix("median_pulses_kept_")).join(
    g.latency.count().unstack("label").add_prefix("units_"))
T["order_PT_below_IT"] = T.latency_PT < T.latency_IT
T["PT_latency_ok"] = (T.latency_PT - 2.7).abs() <= 0.27
T["IT_latency_ok"] = (T.latency_IT - 4.6).abs() <= 0.46
T["PT_jitter_ok"] = T.jitter_PT.round(1) == 0.1
T["IT_jitter_ok"] = T.jitter_IT.round(1) == 0.2
print("Means over units (latency and jitter in ms). Readings: (w) first spike in the window after the first pulse of a pair;")
print("(c) as (w) without pulses with a spike in the par_ms before them; (r) as (w) within par_ms of the latency peak.")
print(T.round(3).reset_index().to_string(index=False))
print("Readings where PT latency is below IT latency:", int(T.order_PT_below_IT.sum()), "of", len(T),
      "; where the four measurable values are all within tolerance:",
      int(T[["PT_latency_ok", "IT_latency_ok", "PT_jitter_ok", "IT_jitter_ok"]].all(axis=1).sum()))
print("Paper: PT 2.7 ms (2.43 to 2.97), IT 4.6 ms (4.14 to 5.06); jitter PT 0.1 ms, IT 0.2 ms (to the printed precision);")
print("PT-activated 8.3 ms, 2.8 ms jitter; IT-activated 9.8 ms, 2.4 ms jitter (no labelled units)")
x = AL[(AL.window_ms == 10) & (AL.reading == "w")]
print("\nPer unit, reading (w), 10 ms window: latency range", x.groupby("label").latency.agg(["min", "max"]).round(2).to_dict("index"),
      "; jitter range", x.groupby("label").jitter.agg(["min", "max"]).round(2).to_dict("index"),
      "; pulses with a spike, min", x.groupby("label").kept.min().to_dict())
print("Per mouse, reading (w), 10 ms window (mean latency, mean jitter, units):")
print(x.groupby(["label", "subject"]).agg(latency=("latency", "mean"), jitter=("jitter", "mean"), units=("unit", "size")).round(2).to_string())
Tagged units with and without a light series of their site: {(np.str_('IT'), False): 8, (np.str_('IT'), True): 19, (np.str_('PT'), True): 45} ; without: {('chr2-eyfp x tlx-cre', np.str_('IT')): 8}
Means over units (latency and jitter in ms). Readings: (w) first spike in the window after the first pulse of a pair;
(c) as (w) without pulses with a spike in the par_ms before them; (r) as (w) within par_ms of the latency peak.
 window_ms reading  par_ms  latency_IT  latency_PT  jitter_IT  jitter_PT  median_pulses_kept_IT  median_pulses_kept_PT  units_IT  units_PT  order_PT_below_IT  PT_latency_ok  IT_latency_ok  PT_jitter_ok  IT_jitter_ok
       5.0       c     5.0       3.613       2.649      0.748      0.311                   88.0                  252.0        19        45               True           True          False         False         False
       5.0       c    10.0       3.620       2.649      0.749      0.306                   88.0                  246.0        19        45               True           True          False         False         False
       5.0       c    20.0       3.659       2.647      0.749      0.304                   88.0                  236.0        19        45               True           True          False         False         False
       5.0       r     0.5       3.622       2.674      0.188      0.115                   69.0                  237.0        19        45               True           True          False          True          True
       5.0       r     1.0       3.629       2.671      0.319      0.128                   83.0                  252.0        19        45               True           True          False          True         False
       5.0       w     0.0       3.611       2.650      0.751      0.313                   88.0                  256.0        19        45               True           True          False         False         False
       8.0       c     5.0       4.633       2.707      0.959      0.472                  206.0                  254.0        19        45               True           True           True         False         False
       8.0       c    10.0       4.643       2.708      0.955      0.468                  201.0                  248.0        19        45               True           True           True         False         False
       8.0       c    20.0       4.669       2.708      0.934      0.469                  195.0                  237.0        19        45               True           True           True         False         False
       8.0       r     0.5       4.905       2.674      0.197      0.115                  177.0                  237.0        19        45               True           True           True          True          True
       8.0       r     1.0       4.900       2.672      0.313      0.129                  205.0                  252.0        19        45               True           True           True          True         False
       8.0       w     0.0       4.632       2.714      0.959      0.484                  209.0                  258.0        19        45               True           True           True         False         False
      10.0       c     5.0       4.976       2.764      0.959      0.615                  248.0                  256.0        19        45               True           True           True         False         False
      10.0       c    10.0       4.981       2.766      0.952      0.609                  246.0                  250.0        19        45               True           True           True         False         False
      10.0       c    20.0       5.001       2.765      0.931      0.608                  241.0                  237.0        19        45               True           True           True         False         False
      10.0       r     0.5       5.094       2.674      0.211      0.115                  177.0                  237.0        19        45               True           True          False          True          True
      10.0       r     1.0       5.087       2.672      0.326      0.129                  205.0                  252.0        19        45               True           True          False          True         False
      10.0       w     0.0       4.976       2.779      0.963      0.640                  249.0                  260.0        19        45               True           True           True         False         False
Readings where PT latency is below IT latency: 18 of 18 ; where the four measurable values are all within tolerance: 1
Paper: PT 2.7 ms (2.43 to 2.97), IT 4.6 ms (4.14 to 5.06); jitter PT 0.1 ms, IT 0.2 ms (to the printed precision);
PT-activated 8.3 ms, 2.8 ms jitter; IT-activated 9.8 ms, 2.4 ms jitter (no labelled units)

Per unit, reading (w), 10 ms window: latency range {np.str_('IT'): {'min': 3.1, 'max': 8.38}, np.str_('PT'): {'min': 1.79, 'max': 4.33}} ; jitter range {np.str_('IT'): {'min': 0.24, 'max': 2.07}, np.str_('PT'): {'min': 0.08, 'max': 2.03}} ; pulses with a spike, min {np.str_('IT'): 76, np.str_('PT'): 13}
Per mouse, reading (w), 10 ms window (mean latency, mean jitter, units):
               latency  jitter  units
label subject                        
IT    214427      4.23    1.15      3
      219247      5.69    1.03      5
      219248      4.56    0.68      7
      219253      4.45    1.09      3
      221977      8.16    1.68      1
PT    214427      3.00    0.33      1
      214429      3.02    0.34      4
      219030      3.17    1.74      4
      219031      3.39    1.52      1
      219033      2.63    0.49      3
      219036      2.94    0.57      8
      219037      2.56    0.40     12
      219038      2.65    0.65     12

Result: partially reproduced. PT latency is below IT latency in all 18 combinations of reading and parameter. PT latency is within tolerance in all of them (2.65 to 2.78 ms, paper 2.7), and IT latency in 10 (4.63 to 5.00 ms with the 8 and 10 ms windows under w and c, and 4.90 to 4.91 ms under r with the 8 ms window; 3.61 to 3.66 ms with the 5 ms window and 5.09 ms under r with the 10 ms window are outside 4.14 to 5.06). The jitter is within the printed precision only under reading (r): 0.115 to 0.129 ms for PT in every (r) row, and 0.188 to 0.211 ms for IT when spikes within 0.5 ms of the peak are kept (0.31 to 0.33 ms within 1 ms); under (w) and (c) it is 0.30 to 0.64 ms (PT) and 0.75 to 0.96 ms (IT), which include spontaneous spikes. All four measurable values are within tolerance in one combination (r, 8 ms window, 0.5 ms around the peak: PT 2.674 ms and 0.115 ms, IT 4.905 ms and 0.197 ms); keeping spikes within 0.5 ms of the peak bounds the s.d., so that agreement is partly set by the reading. The four values of the activated neurons that failed the collision test cannot be measured, so the claim cannot be fully reproduced, and the comparison of jitter with those neurons cannot be made. 19 of the 27 IT units have a light record. The window and the width around the peak are open parameters chosen here, and only 1 of the 18 combinations puts all four measurable values within tolerance.

fig3e-pt-it-both-activity-types¶

Both PT and IT neurons show preparatory and movement-related activity.

Criterion: the 45 PT and 27 IT neurons classified with the epoch rule and readings of fig2d-preparatory-perimovement; reproduced when, in each cell type, at least one neuron has preparatory selectivity and at least one peri-movement selectivity.

In [21]:
rows = []
for (tset, resp, light), r in EP.items():
    sig = r[["p_sample", "p_delay", "p_response"]] < 0.05
    for tag in ("PT", "IT"):
        s = sig.loc[P.index[P.tag == tag]]
        prep, per = s.p_sample | s.p_delay, s.p_response
        rows.append(dict(light_trials="kept" if light == "any" else "left out", trials=tset, response=resp, type=tag,
                         n=len(s), selective=int((prep | per).sum()),
                         preparatory=int(prep.sum()), peri_movement=int(per.sum()), prep_only=int((prep & ~per).sum()),
                         peri_only=int((per & ~prep).sum()), both=int((prep & per).sum())))
F3E = pd.DataFrame(rows)
print(F3E.to_string(index=False))
print("Holds under every reading:", bool(((F3E.preparatory > 0) & (F3E.peri_movement > 0)).all()))
light_trials   trials  response type  n  selective  preparatory  peri_movement  prep_only  peri_only  both
        kept  correct       1.3   PT 45         29           17             24          5         12    12
        kept  correct       1.3   IT 27         18           13             13          5          5     8
        kept  correct       1.5   PT 45         29           17             23          6         12    11
        kept  correct       1.5   IT 27         18           13             14          4          5     9
        kept nonearly       1.3   PT 45         26           14             21          5         12     9
        kept nonearly       1.3   IT 27         17           13             13          4          4     9
        kept nonearly       1.5   PT 45         25           14             20          5         11     9
        kept nonearly       1.5   IT 27         17           13             13          4          4     9
    left out  correct       1.3   PT 45         32           17             25          7         15    10
    left out  correct       1.3   IT 27         18           13             13          5          5     8
    left out  correct       1.5   PT 45         31           17             24          7         14    10
    left out  correct       1.5   IT 27         18           13             14          4          5     9
    left out nonearly       1.3   PT 45         26           13             22          4         13     9
    left out nonearly       1.3   IT 27         17           13             11          6          4     7
    left out nonearly       1.5   PT 45         26           13             22          4         13     9
    left out nonearly       1.5   IT 27         17           13             11          6          4     7
Holds under every reading: True

Result: reproduced. Under the main reading, 17 of 45 PT neurons have preparatory and 24 peri-movement selectivity, and 13 of 27 IT neurons each; every reading, with or without the light trials, gives at least 11 neurons of each kind in each cell type.

fig3f-pt-contra-preferring¶

Contra-preferring neurons outnumber ipsi-preferring neurons among PT neurons (P < 0.05, bootstrap) but not among IT neurons (P = 0.74).

Criterion: contra- or ipsi-preferring by total spike count over the three epochs, lick right against lick left, correct trials; bootstrap with neurons resampled (10^4), P the fraction of resamples with more ipsi- than contra-preferring neurons. Readings: (a) all tagged neurons of the type, (b) only those selective by the test of fig2-selective-fraction. Reproduced when PT gives P < 0.05 and IT P >= 0.05 under the same reading. Trials with tagging light (t1) kept or (t2) left out, either of which may decide.

In [22]:
rng = np.random.default_rng(31)
rows = []
for light in ("any", "none"):
    tot_all = alm.total_count_diff(E, P[P.tag != ""], light=light)
    for reading in ("a_all", "b_selective"):
        for tag in ("PT", "IT"):
            idx = P.index[(P.tag == tag) & (SELSET[light].reindex(P.index).values if reading == "b_selective" else True)]
            x = (tot_all.loc[idx] > 0).values
            rows.append(dict(light_trials="kept" if light == "any" else "left out", reading=reading, type=tag, n=len(x),
                             contra=int(x.sum()), fraction=round(x.mean(), 3), P_boot=alm.boot_more_ipsi(x, rng)))
    if light == "any":
        by_mouse = pd.DataFrame(dict(subject=P.subject[P.tag == "PT"], c=(tot_all.loc[P.index[P.tag == "PT"]] > 0).values)).groupby("subject").c.agg(["sum", "size"])
F3F = pd.DataFrame(rows)
print(F3F.to_string(index=False))
print("Paper: PT P < 0.05, IT P = 0.74")
print("PT neurons by mouse, light trials kept (contra / n):")
print(by_mouse.to_string())
light_trials     reading type  n  contra  fraction  P_boot
        kept       a_all   PT 45      30     0.667  0.0098
        kept       a_all   IT 27      13     0.481  0.5765
        kept b_selective   PT 29      18     0.621  0.0925
        kept b_selective   IT 18       7     0.389  0.7684
    left out       a_all   PT 45      26     0.578  0.1447
    left out       a_all   IT 27      14     0.519  0.4251
    left out b_selective   PT 32      17     0.531  0.2997
    left out b_selective   IT 18       8     0.444  0.5960
Paper: PT P < 0.05, IT P = 0.74
PT neurons by mouse, light trials kept (contra / n):
         sum  size
subject           
214427     1     1
214429     3     4
219030     3     4
219031     1     1
219033     2     3
219036     6     8
219037     9    12
219038     5    12
In [23]:
# Balance of the tagging light between lick right and lick left trials (correct trials), and the
# contra-preference of tagged neurons within light and no-light trials. These are checks of reading
# (t1), not readings of the criterion.
rows = []
for i, s in enumerate(E):
    tr = s["trials"]
    t = tr[alm.trial_sets(tr)["correct"]]
    if not t.light.any():
        continue
    r, l = (t.instruction == "right").values, t.light.values.astype(bool)
    rows.append(dict(session=i, subject=s["subject"], n_PT=int(((P.session == i) & (P.tag == "PT")).sum()),
                     n_IT=int(((P.session == i) & (P.tag == "IT")).sum()), right_light=int((r & l).sum()), right=int(r.sum()),
                     left_light=int((~r & l).sum()), left=int((~r).sum()),
                     P_fisher=stats.fisher_exact([[(r & l).sum(), (r & ~l).sum()], [(~r & l).sum(), (~r & ~l).sum()]])[1]))
LB = pd.DataFrame(rows)
LB["frac_right"], LB["frac_left"] = LB.right_light / LB.right, LB.left_light / LB.left
print(f"Sessions with light in correct trials: {len(LB)}. Light in {LB.right_light.sum()} of {LB.right.sum()} lick right trials "
      f"({LB.right_light.sum() / LB.right.sum():.3f}) and {LB.left_light.sum()} of {LB.left.sum()} lick left trials ({LB.left_light.sum() / LB.left.sum():.3f})")
print(f"Correct trials with light in these sessions: {LB.right_light.sum() + LB.left_light.sum()} of {LB.right.sum() + LB.left.sum()}")
print("Sessions where light and trial type are associated (Fisher P < 0.05):", int((LB.P_fisher < 0.05).sum()), "of", len(LB),
      f"({0.05 * len(LB):.1f} expected by chance)")
for tag in ("PT", "IT"):
    w = LB["n_" + tag]
    dd = (LB.frac_right - LB.frac_left)[w > 0]
    print(f"{tag}: {int(w.sum())} units in {int((w > 0).sum())} sessions with light; light fraction, lick right minus lick left, "
          f"weighted by units: {np.average(LB.frac_right - LB.frac_left, weights=w):+.4f} (range {dd.min():+.3f} to {dd.max():+.3f})")
print("Sessions holding PT neurons:")
print(LB[LB.n_PT > 0].drop(columns="n_IT").round(3).to_string(index=False))

rng = np.random.default_rng(33)
rows = []
for u in P[P.tag != ""].itertuples():
    s = E[u.session]
    tr = s["trials"]
    t = tr[alm.trial_sets(tr)["correct"]]
    c = sum(alm.counts(s["spikes"][u.unit], a, b) for a, b in alm.epoch_windows(t, 1.3).values())
    r, l = (t.instruction == "right").values, t.light.values.astype(bool)
    d = lambda m: c[r & m].mean() - c[~r & m].mean() if (r & m).any() and (~r & m).any() else np.nan
    rows.append(dict(type=u.tag, all_trials=d(np.ones_like(l)), no_light=d(~l), light_only=d(l),
                     ratio=c[l].mean() / c[~l].mean() if l.any() and c[~l].mean() > 0 else np.nan))
W = pd.DataFrame(rows)
W["mean_of_both"] = W[["no_light", "light_only"]].mean(axis=1)
out = []
for tag in ("PT", "IT"):
    w = W[W.type == tag]
    for col in ("all_trials", "no_light", "light_only", "mean_of_both"):
        x = w[col].dropna()
        out.append(dict(type=tag, trials=col, n=len(x), contra=int((x > 0).sum()), fraction=round((x > 0).mean(), 3),
                        P_boot=alm.boot_more_ipsi((x > 0).values, rng)))
print("\nContra-preferring tagged neurons by the trials used (mean_of_both: average of the no-light and light-only differences):")
print(pd.DataFrame(out).to_string(index=False))
print("Spike count over the three epochs, light trials over no-light trials, median over neurons: PT",
      round(W.ratio[W.type == "PT"].median(), 3), "; IT", round(W.ratio[W.type == "IT"].median(), 3))
Sessions with light in correct trials: 75. Light in 2089 of 7092 lick right trials (0.295) and 2243 of 7296 lick left trials (0.307)
Correct trials with light in these sessions: 4332 of 14388
Sessions where light and trial type are associated (Fisher P < 0.05): 4 of 75 (3.8 expected by chance)
PT: 45 units in 32 sessions with light; light fraction, lick right minus lick left, weighted by units: -0.0049 (range -0.156 to +0.143)
IT: 19 units in 13 sessions with light; light fraction, lick right minus lick left, weighted by units: -0.0290 (range -0.107 to +0.092)
Sessions holding PT neurons:
 session subject  n_PT  right_light  right  left_light  left  P_fisher  frac_right  frac_left
      10  214427     1           13     67          19    75     0.428       0.194      0.253
      14  214429     2           23     91          18    81     0.721       0.253      0.222
      15  214429     1           38    143          42   158     1.000       0.266      0.266
      16  214429     1           45    110          52   139     0.602       0.409      0.374
      35  219030     1           30     77          23    79     0.237       0.390      0.291
      37  219030     1           28     99          36    84     0.044       0.283      0.429
      39  219030     2           41    100          33    93     0.461       0.410      0.355
      41  219031     1           34    102          38   117     1.000       0.333      0.325
      48  219033     1           29    128          37   130     0.319       0.227      0.285
      49  219033     1           29    120          36   119     0.312       0.242      0.303
      50  219033     1           34    149          38   166     1.000       0.228      0.229
      52  219036     3           28    100          29    99     0.876       0.280      0.293
      53  219036     1           28    103          28    98     0.876       0.272      0.286
      54  219036     1           22     69          22    71     1.000       0.319      0.310
      55  219036     1           30     91          32    91     0.876       0.330      0.352
      57  219036     1           35    117          29   120     0.380       0.299      0.242
      58  219036     1           24     92          28   102     0.872       0.261      0.275
      59  219037     1           30    106          24   105     0.431       0.283      0.229
      60  219037     2           33    140          34   139     0.889       0.236      0.245
      61  219037     2           31    115          34   112     0.660       0.270      0.304
      62  219037     1           34    136          39   132     0.414       0.250      0.295
      63  219037     1           33    123          35   131     1.000       0.268      0.267
      64  219037     1           25    121          34   116     0.135       0.207      0.293
      65  219037     4           34    100          32    84     0.644       0.340      0.381
      66  219038     2           16     84          27    78     0.032       0.190      0.346
      67  219038     1           32     93          25    79     0.747       0.344      0.316
      68  219038     2           28     93          27    91     1.000       0.301      0.297
      69  219038     1           32     90          24    80     0.514       0.356      0.300
      70  219038     2           36     84          22    77     0.071       0.429      0.286
      71  219038     1           20     62          18    54     1.000       0.323      0.333
      72  219038     1           23     90          15    74     0.462       0.256      0.203
      73  219038     2           33    116          28   104     0.880       0.284      0.269

Contra-preferring tagged neurons by the trials used (mean_of_both: average of the no-light and light-only differences):
type       trials  n  contra  fraction  P_boot
  PT   all_trials 45      30     0.667  0.0102
  PT     no_light 45      26     0.578  0.1440
  PT   light_only 45      26     0.578  0.1481
  PT mean_of_both 45      29     0.644  0.0220
  IT   all_trials 27      13     0.481  0.5718
  IT     no_light 27      14     0.519  0.4247
  IT   light_only 19       9     0.474  0.5810
  IT mean_of_both 27      14     0.519  0.4389
Spike count over the three epochs, light trials over no-light trials, median over neurons: PT 1.27 ; IT 1.343

Result: reproduced. With the light trials kept (t1), 30 of 45 PT neurons are contra-preferring (0.67; bootstrap P = 0.010) and 13 of 27 IT neurons (0.48; P = 0.58), so the claim holds under reading (a); restricted to selective neurons, PT gives 18 of 29 (P = 0.09). Without the trials with light (t2), PT gives 26 of 45 (0.58; P = 0.14) and 17 of 32 selective (P = 0.30), and the claim holds under no reading. The claims file lets either light reading decide, so t1 decides; the PT result depends on keeping the trials in which the PT neurons were driven by the tagging light. The light is balanced between the trial types: it falls in 0.295 of lick right and 0.307 of lick left correct trials, 4 of 75 sessions show an association at P < 0.05 (3.8 expected by chance), and weighted by PT neurons the light fraction is 0.005 lower on lick right trials, which would favour ipsi preference. PT neurons are contra-preferring in 26 of 45 on the trials without light and in 26 of 45 on the light trials alone (P = 0.14 and 0.15), and in 29 of 45 (P = 0.022) on the average of the two differences; light trials hold 1.27 times the spikes of the others (median over PT neurons). So the t1 result does not come from an imbalance of the light, and it reaches P < 0.05 only when the light and no-light trials are pooled.

fig3f-pt-contra-selectivity¶

Contralateral population selectivity emerges in PT neurons from 570 ms before the response epoch (34% spike rate modulation, P < 0.05), but not in IT neurons (-19%, P = 0.40).

Criterion: contra-selectivity is the rate difference, lick right minus lick left, per neuron, correct trials, averaged across neurons, in 200 ms windows. Readings: neurons (a) all tagged or (b) selective; test window (i) the 570 ms before the go cue or (ii) the delay epoch; modulation (x) the difference over the mean rate of both trial types or (y) over the lick left rate; onset the start of the earliest window from which the population contra-selectivity stays significantly above 0 (t-test across neurons, P < 0.05) until the go cue, windows stepped by 10 ms, each window standing for its (p) start, (q) centre or (r) end. Reproduced when, under one reading, PT gives P < 0.05 with positive selectivity, IT P >= 0.05, and the three values are within tolerance.

In [24]:
starts = np.round(np.arange(-3.0, 0.0 + 1e-9, 0.01), 3)
rows, onsets, curves3 = [], [], {}
for light in ("any", "none"):
    lname = "kept" if light == "any" else "left out"
    for reading in ("a_all", "b_selective"):
        for tag in ("PT", "IT"):
            units = P[(P.tag == tag) & (SELSET[light].reindex(P.index).values if reading == "b_selective" else True)]
            W = alm.window_counts(E, units, starts, align="go", light=light)
            Dm = np.array([R.mean(0) / 0.2 - L.mean(0) / 0.2 for R, L in W.values()])
            p = stats.ttest_1samp(Dm, 0, axis=0).pvalue
            sigpos = (p < 0.05) & (Dm.mean(0) > 0)
            curves3[(lname, reading, tag)] = Dm.mean(0)
            for label, off in (("p_start", 0.0), ("q_centre", 0.1), ("r_end", 0.2)):
                tt = starts + off
                v = tt <= 1e-9
                sv, tv = sigpos[v], tt[v]
                if sv[-1]:
                    k = len(sv) - 1
                    while k > 0 and sv[k - 1]:
                        k -= 1
                    onset = -tv[k] * 1000
                else:
                    onset = np.nan
                onsets.append(dict(light_trials=lname, reading=reading, type=tag, window_time=label, onset_ms_before_go=onset,
                                   windows_significant_positive=int(sv.sum()), smallest_window_P=float(p[v].min())))
            for win, (t0, t1) in (("i_570ms", (-0.57, 0.0)), ("ii_delay", (-1.3, 0.0))):
                w = alm.window_diff(E, units, t0, t1, light=light)
                d = w.R - w.L
                t = stats.ttest_1samp(d, 0)
                rows.append(dict(light_trials=lname, reading=reading, type=tag, window=win, n=len(d), diff_spikes_s=d.mean(), P=t.pvalue,
                                 mod_x_pct=100 * d.mean() / ((w.R + w.L) / 2).mean(), mod_y_pct=100 * d.mean() / w.L.mean()))
F3S = pd.DataFrame(rows)
print(F3S.round(4).to_string(index=False))
print("\nOnsets (ms before the go cue; NaN where the last window before the go cue is not significant):")
print(pd.DataFrame(onsets).round(4).to_string(index=False))
print("Paper: PT onset 570 ms (513 to 627), PT modulation 34% (30.6 to 37.4), IT -19% (-20.9 to -17.1); PT P < 0.05, IT P = 0.40")
fig, ax = plt.subplots(figsize=(7, 3))
for key, c in curves3.items():
    if key[0] == "kept":
        ax.plot(starts + 0.1, c, label=", ".join(key[1:]))
ax.axhline(0, color="0.7"); ax.axvline(-1.3, color="0.7")
ax.set_xlabel("window centre, s from go cue"); ax.set_ylabel("contra-selectivity (spikes/s)"); ax.legend(fontsize=7)
plt.tight_layout(); plt.show()
print("Plotted range (light trials kept):", round(min(c.min() for k, c in curves3.items() if k[0] == "kept"), 2), "to",
      round(max(c.max() for k, c in curves3.items() if k[0] == "kept"), 2), "spikes/s")
light_trials     reading type   window  n  diff_spikes_s      P  mod_x_pct  mod_y_pct
        kept       a_all   PT  i_570ms 45         0.8715 0.1725    22.6074    25.4885
        kept       a_all   PT ii_delay 45         0.5175 0.2923    13.9280    14.9706
        kept       a_all   IT  i_570ms 27        -0.4895 0.1096   -20.5699   -18.6516
        kept       a_all   IT ii_delay 27        -0.3994 0.1042   -17.2839   -15.9091
        kept b_selective   PT  i_570ms 29         1.1711 0.2379    23.7877    26.9989
        kept b_selective   PT ii_delay 29         0.6992 0.3606    14.8768    16.0723
        kept b_selective   IT  i_570ms 18        -0.7431 0.0982   -28.2709   -24.7696
        kept b_selective   IT ii_delay 18        -0.6547 0.0609   -25.2829   -22.4454
    left out       a_all   PT  i_570ms 45         1.0713 0.1211    29.4028    34.4705
    left out       a_all   PT ii_delay 45         0.6694 0.1921    18.8652    20.8300
    left out       a_all   IT  i_570ms 27        -0.5273 0.1094   -23.5982   -21.1077
    left out       a_all   IT ii_delay 27        -0.4220 0.1220   -19.3535   -17.6460
    left out b_selective   PT  i_570ms 32         1.4106 0.1463    32.2901    38.5071
    left out b_selective   PT ii_delay 32         0.8860 0.2189    20.9644    23.4193
    left out b_selective   IT  i_570ms 18        -0.7544 0.1225   -29.6524   -25.8237
    left out b_selective   IT ii_delay 18        -0.6022 0.1328   -24.4889   -21.8175

Onsets (ms before the go cue; NaN where the last window before the go cue is not significant):
light_trials     reading type window_time  onset_ms_before_go  windows_significant_positive  smallest_window_P
        kept       a_all   PT     p_start                 NaN                             0             0.0845
        kept       a_all   PT    q_centre                 NaN                             0             0.1082
        kept       a_all   PT       r_end                 NaN                             0             0.1082
        kept       a_all   IT     p_start                 NaN                             0             0.0203
        kept       a_all   IT    q_centre                 NaN                             0             0.0203
        kept       a_all   IT       r_end                 NaN                             0             0.0203
        kept b_selective   PT     p_start                 NaN                             0             0.0332
        kept b_selective   PT    q_centre                 NaN                             0             0.0332
        kept b_selective   PT       r_end                 NaN                             0             0.0332
        kept b_selective   IT     p_start                 NaN                             0             0.0084
        kept b_selective   IT    q_centre                 NaN                             0             0.0084
        kept b_selective   IT       r_end                 NaN                             0             0.0084
    left out       a_all   PT     p_start                 NaN                            16             0.0029
    left out       a_all   PT    q_centre                 NaN                            16             0.0029
    left out       a_all   PT       r_end                 NaN                            16             0.0029
    left out       a_all   IT     p_start                 NaN                             0             0.0114
    left out       a_all   IT    q_centre                 NaN                             0             0.0114
    left out       a_all   IT       r_end                 NaN                             0             0.0114
    left out b_selective   PT     p_start                 NaN                             8             0.0126
    left out b_selective   PT    q_centre                 NaN                             8             0.0126
    left out b_selective   PT       r_end                 NaN                             8             0.0126
    left out b_selective   IT     p_start                 NaN                             0             0.0011
    left out b_selective   IT    q_centre                 NaN                             0             0.0011
    left out b_selective   IT       r_end                 NaN                             0             0.0011
Paper: PT onset 570 ms (513 to 627), PT modulation 34% (30.6 to 37.4), IT -19% (-20.9 to -17.1); PT P < 0.05, IT P = 0.40
No description has been provided for this image
Plotted range (light trials kept): -2.17 to 1.49 spikes/s

Result: not reproduced. PT neurons have a positive contra-selectivity before the go cue, but the test-window t-test is not significant under any reading (P = 0.17 to 0.36 with the light trials, 0.12 to 0.22 without), and the windowed selectivity is not significant up to the go cue, so no onset can be found (without light trials 8 to 16 single windows reach P < 0.05, not continuously). The PT modulation is 13.9% to 27.0% with the light trials and 18.9% to 38.5% without, against 34%. IT neurons are not significant (P = 0.06 to 0.13), with modulations of -15.9% to -29.7%.

imaging-selectivity-counts¶

In two-photon imaging of ALM (59 fields of view, 4 mice), 58% of cells (2,740 of 4,706) were task-related and 57% of those (1,575) trial-type selective, with a slight majority of contra-preferring cells (61%, 971 of 1,575).

Criterion: task-related when a Kruskal-Wallis test across 5-frame bins rejects equal fluorescence at P < 0.01 in contra or in ipsi trials; selective when a Wilcoxon rank sum test of DF/F between contra and ipsi trials gives P < 0.05; preference by the average signal over the trial epochs; DF/F0 with F0 the mean over 0.5 s before trial start, neuropil factor 0.7. Readings: Kruskal-Wallis span (a) sample onset to the end of the response epoch or (b) the whole recorded trial; rank sum of (i) the mean over the three epochs or (ii) each epoch; trials correct only or all non-early. The response epoch, which the paper does not time, is (r1) 1.3 s or (r2) 1.5 s; both are run. Cells are the ROIs marked included; contra trials are lick right trials (left ALM).

In [25]:
PT_ = {tset: [alm.plane_trials(pl, tset) for pl in O] for tset in ("correct", "nonearly")}
IMG = {}
rows = []
for resp in (1.3, 1.5):
    for span in ("a", "b"):
        for rank in ("i_mean", "ii_each"):
            for tset in ("correct", "nonearly"):
                res = [alm.classify_plane(X, rel, right, pl["included"], resp, span, rank.split("_")[1])
                       for (X, rel, right, _), pl in zip(PT_[tset], O)]
                IMG[(resp, span, rank, tset)] = res
                S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
                rows.append(dict(response=resp, span=span, rank=rank, trials=tset,
                                 cells=int(sum(r["included"].sum() for r in res)), task=int(sum(r["task"].sum() for r in res)),
                                 selective=int(S.sum()), contra_frac=round((S & C).sum() / S.sum(), 3), planes=len(res)))
IM = pd.DataFrame(rows)
for col, (v, t) in dict(cells=(4706, 470.6), task=(2740, 274), selective=(1575, 157.5), contra_frac=(0.61, 0.061), planes=(59, 5.9)).items():
    IM[col + "_ok"] = (IM[col] - v).abs() <= t + 1e-9
IM["all_ok"] = IM[[c for c in IM.columns if c.endswith("_ok")]].all(axis=1)
print(IM.to_string(index=False))
print("Paper: cells 4706, task-related 2740 (2466 to 3014), selective 1575 (1417.5 to 1732.5), contra 0.61 (0.549 to 0.671), planes 59")
print("Readings with every value within tolerance:", int(IM.all_ok.sum()), "of", len(IM))
print("Trials per plane (correct):", pd.Series([len(r) for _, _, r, _ in PT_["correct"]]).describe().round(1).to_dict())
key = (1.5, "a", "i_mean", "nonearly")
print("\nBy mouse, reading", key, "(task-related, selective, included):")
subj = np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])
r = IMG[key]
print(pd.DataFrame(dict(subject=subj, task=np.concatenate([x["task"] for x in r]), sel=np.concatenate([x["sel"] for x in r]),
                        inc=np.concatenate([x["included"] for x in r]))).groupby("subject").sum().to_string())
 response span    rank   trials  cells  task  selective  contra_frac  planes  cells_ok  task_ok  selective_ok  contra_frac_ok  planes_ok  all_ok
      1.3    a  i_mean  correct   4706  2551       1315        0.579      59      True     True         False            True       True   False
      1.3    a  i_mean nonearly   4706  2588       1345        0.590      59      True     True         False            True       True   False
      1.3    a ii_each  correct   4706  2551       1844        0.560      59      True     True         False            True       True   False
      1.3    a ii_each nonearly   4706  2588       1849        0.557      59      True     True         False            True       True   False
      1.3    b  i_mean  correct   4706  3104       1469        0.573      59      True    False          True            True       True   False
      1.3    b  i_mean nonearly   4706  3134       1480        0.577      59      True    False          True            True       True   False
      1.3    b ii_each  correct   4706  3104       2096        0.557      59      True    False         False            True       True   False
      1.3    b ii_each nonearly   4706  3134       2093        0.547      59      True    False         False           False       True   False
      1.5    a  i_mean  correct   4706  2730       1365        0.581      59      True     True         False            True       True   False
      1.5    a  i_mean nonearly   4706  2772       1417        0.588      59      True     True         False            True       True   False
      1.5    a ii_each  correct   4706  2730       1956        0.566      59      True     True         False            True       True   False
      1.5    a ii_each nonearly   4706  2772       1957        0.557      59      True     True         False            True       True   False
      1.5    b  i_mean  correct   4706  3104       1481        0.577      59      True    False          True            True       True   False
      1.5    b  i_mean nonearly   4706  3134       1513        0.578      59      True    False          True            True       True   False
      1.5    b ii_each  correct   4706  3104       2138        0.561      59      True    False         False            True       True   False
      1.5    b ii_each nonearly   4706  3134       2133        0.549      59      True    False         False            True       True   False
Paper: cells 4706, task-related 2740 (2466 to 3014), selective 1575 (1417.5 to 1732.5), contra 0.61 (0.549 to 0.671), planes 59
Readings with every value within tolerance: 0 of 16
Trials per plane (correct): {'count': 59.0, 'mean': 54.2, 'std': 9.4, 'min': 25.0, '25%': 49.0, '50%': 54.0, '75%': 60.5, 'max': 71.0}

By mouse, reading (1.5, 'a', 'i_mean', 'nonearly') (task-related, selective, included):
         task  sel   inc
subject                 
216219   1029  575  1497
217951    404  131   775
221541    628  310  1148
226244    711  401  1286
In [26]:
# Sensitivity of the counts to trials whose baseline F0 is near 0 (a check, not a reading of the
# criterion). F0 is the mean of F over 0.5 s before the trial start, divided here by the ROI's median
# raw fluorescence; ROI-trials at or below a cut are left out of every test.
F0R = {tset: [alm.plane_trials(pl, tset, return_f0=True)[4] for pl in O] for tset in ("correct", "nonearly")}
r0 = np.concatenate([f[:, pl["included"]].ravel() for f, pl in zip(F0R["nonearly"], O)])
print(f"Included ROI-trials (non-early): {len(r0)}; F0 / median raw F, percentiles 0, 0.1, 1, 5, 50, 99:",
      np.percentile(r0, [0, 0.1, 1, 5, 50, 99]).round(3).tolist())
print("ROI-trials with F0 <= 0:", int((r0 <= 0).sum()), "; below 0.05:", int((r0 < 0.05).sum()), "; below 0.1:", int((r0 < 0.1).sum()))
print("Included ROIs with F - 0.7 Fnp <= 0 in at least one frame:",
      int(sum(((pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]] <= 0).any(0).sum() for pl in O)))
nfr, run, frac = [], [], []
for pl in O:
    b = ((pl["F"] - 0.7 * pl["Fnp"])[:, pl["included"]] <= 0)
    b = b[:, b.any(0)]
    cur, best = np.zeros(b.shape[1], int), np.zeros(b.shape[1], int)
    for row in b:
        cur = np.where(row, cur + 1, 0)
        best = np.maximum(best, cur)
    nfr += b.sum(0).tolist(); run += best.tolist(); frac += (b.mean(0)).tolist()
nfr, run, frac = map(np.array, (nfr, run, frac))
print("Among them, frames at or below 0 per ROI: median", int(np.median(nfr)), ", max", int(nfr.max()),
      "; fraction of the ROI's frames: median", round(float(np.median(frac)), 5), ", max", round(float(frac.max()), 4))
print("Longest run of consecutive frames at or below 0: ROIs with 1 frame", int((run == 1).sum()), ", 2 to 7 frames",
      int(((run >= 2) & (run <= 7)).sum()), ", 8 or more (0.5 s or more at 14.84 Hz)", int((run >= 8).sum()), "; max", int(run.max()))
rows = []
for cut in (0.0, 0.05, 0.1):
    for resp in (1.3, 1.5):
        for span in ("a", "b"):
            for rank in ("i_mean", "ii_each"):
                for tset in ("correct", "nonearly"):
                    res = [alm.classify_plane(X, rel, right, pl["included"], resp, span, rank.split("_")[1], drop=f <= cut)
                           for (X, rel, right, _), pl, f in zip(PT_[tset], O, F0R[tset])]
                    S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
                    rows.append(dict(F0_cut=cut, response=resp, span=span, rank=rank, trials=tset, task=int(sum(r["task"].sum() for r in res)),
                                     selective=int(S.sum()), contra_frac=round((S & C).sum() / S.sum(), 3)))
IMF = pd.DataFrame(rows)
IMF["all_ok"] = ((IMF.task - 2740).abs() <= 274) & ((IMF.selective - 1575).abs() <= 157.5) & ((IMF.contra_frac - 0.61).abs() <= 0.061 + 1e-9)
print(IMF.to_string(index=False))
print("Rows with task-related, selective and contra fraction all within tolerance (cells and planes are unchanged):",
      IMF[IMF.all_ok][["F0_cut", "response", "span", "rank", "trials"]].values.tolist())
Included ROI-trials (non-early): 305349; F0 / median raw F, percentiles 0, 0.1, 1, 5, 50, 99: [-0.844, 0.062, 0.164, 0.229, 0.409, 2.868]
ROI-trials with F0 <= 0: 145 ; below 0.05: 258 ; below 0.1: 622
Included ROIs with F - 0.7 Fnp <= 0 in at least one frame: 3403
Among them, frames at or below 0 per ROI: median 19 , max 1627 ; fraction of the ROI's frames: median 0.00167 , max 0.1993
Longest run of consecutive frames at or below 0: ROIs with 1 frame 2034 , 2 to 7 frames 1199 , 8 or more (0.5 s or more at 14.84 Hz) 170 ; max 518
 F0_cut  response span    rank   trials  task  selective  contra_frac  all_ok
   0.00       1.3    a  i_mean  correct  2552       1315        0.578   False
   0.00       1.3    a  i_mean nonearly  2593       1344        0.589   False
   0.00       1.3    a ii_each  correct  2552       1843        0.560   False
   0.00       1.3    a ii_each nonearly  2593       1850        0.556   False
   0.00       1.3    b  i_mean  correct  3112       1468        0.573   False
   0.00       1.3    b  i_mean nonearly  3137       1479        0.576   False
   0.00       1.3    b ii_each  correct  3112       2094        0.556   False
   0.00       1.3    b ii_each nonearly  3137       2093        0.546   False
   0.00       1.5    a  i_mean  correct  2736       1365        0.581   False
   0.00       1.5    a  i_mean nonearly  2774       1416        0.588   False
   0.00       1.5    a ii_each  correct  2736       1954        0.566   False
   0.00       1.5    a ii_each nonearly  2774       1958        0.557   False
   0.00       1.5    b  i_mean  correct  3112       1482        0.576   False
   0.00       1.5    b  i_mean nonearly  3137       1512        0.578   False
   0.00       1.5    b ii_each  correct  3112       2137        0.561   False
   0.00       1.5    b ii_each nonearly  3137       2135        0.549   False
   0.05       1.3    a  i_mean  correct  2553       1315        0.578   False
   0.05       1.3    a  i_mean nonearly  2594       1343        0.589   False
   0.05       1.3    a ii_each  correct  2553       1844        0.559   False
   0.05       1.3    a ii_each nonearly  2594       1848        0.556   False
   0.05       1.3    b  i_mean  correct  3113       1470        0.573   False
   0.05       1.3    b  i_mean nonearly  3139       1478        0.576   False
   0.05       1.3    b ii_each  correct  3113       2094        0.556   False
   0.05       1.3    b ii_each nonearly  3139       2090        0.546   False
   0.05       1.5    a  i_mean  correct  2735       1365        0.581   False
   0.05       1.5    a  i_mean nonearly  2774       1415        0.589   False
   0.05       1.5    a ii_each  correct  2735       1953        0.566   False
   0.05       1.5    a ii_each nonearly  2774       1957        0.557   False
   0.05       1.5    b  i_mean  correct  3113       1483        0.577   False
   0.05       1.5    b  i_mean nonearly  3139       1511        0.579   False
   0.05       1.5    b ii_each  correct  3113       2137        0.561   False
   0.05       1.5    b ii_each nonearly  3139       2133        0.549   False
   0.10       1.3    a  i_mean  correct  2557       1311        0.577   False
   0.10       1.3    a  i_mean nonearly  2597       1346        0.588   False
   0.10       1.3    a ii_each  correct  2557       1845        0.560   False
   0.10       1.3    a ii_each nonearly  2597       1850        0.557   False
   0.10       1.3    b  i_mean  correct  3119       1466        0.572   False
   0.10       1.3    b  i_mean nonearly  3140       1481        0.576   False
   0.10       1.3    b ii_each  correct  3119       2098        0.557   False
   0.10       1.3    b ii_each nonearly  3140       2094        0.546   False
   0.10       1.5    a  i_mean  correct  2741       1366        0.580   False
   0.10       1.5    a  i_mean nonearly  2779       1419        0.587    True
   0.10       1.5    a ii_each  correct  2741       1954        0.567   False
   0.10       1.5    a ii_each nonearly  2779       1962        0.557   False
   0.10       1.5    b  i_mean  correct  3119       1485        0.575   False
   0.10       1.5    b  i_mean nonearly  3140       1515        0.578   False
   0.10       1.5    b ii_each  correct  3119       2139        0.561   False
   0.10       1.5    b ii_each nonearly  3140       2138        0.549   False
Rows with task-related, selective and contra fraction all within tolerance (cells and planes are unchanged): [[0.1, 1.5, 'a', 'i_mean', 'nonearly']]
In [27]:
# Per reading, how many of the five values are within tolerance and how far the selective count is
# from its range (1,417.5 to 1,732.5). The criterion sets only a reproduced condition, all values
# within tolerance under one reading.
tally = IM[["response", "span", "rank", "trials", "task", "selective", "contra_frac"]].copy()
tally["values_within"] = IM[[c for c in IM.columns if c.endswith("_ok") and c != "all_ok"]].sum(axis=1)
tally["selective_miss"] = np.where(IM.selective < 1417.5, IM.selective - 1417.5, np.where(IM.selective > 1732.5, IM.selective - 1732.5, 0))
print(tally.to_string(index=False))
print("Readings with all five values within tolerance:", int((tally.values_within == 5).sum()), "of", len(tally),
      "; with four:", int((tally.values_within == 4).sum()), "; readings with the selective count within tolerance:", int((tally.selective_miss == 0).sum()))
 response span    rank   trials  task  selective  contra_frac  values_within  selective_miss
      1.3    a  i_mean  correct  2551       1315        0.579              4          -102.5
      1.3    a  i_mean nonearly  2588       1345        0.590              4           -72.5
      1.3    a ii_each  correct  2551       1844        0.560              4           111.5
      1.3    a ii_each nonearly  2588       1849        0.557              4           116.5
      1.3    b  i_mean  correct  3104       1469        0.573              4             0.0
      1.3    b  i_mean nonearly  3134       1480        0.577              4             0.0
      1.3    b ii_each  correct  3104       2096        0.557              3           363.5
      1.3    b ii_each nonearly  3134       2093        0.547              2           360.5
      1.5    a  i_mean  correct  2730       1365        0.581              4           -52.5
      1.5    a  i_mean nonearly  2772       1417        0.588              4            -0.5
      1.5    a ii_each  correct  2730       1956        0.566              4           223.5
      1.5    a ii_each nonearly  2772       1957        0.557              4           224.5
      1.5    b  i_mean  correct  3104       1481        0.577              4             0.0
      1.5    b  i_mean nonearly  3134       1513        0.578              4             0.0
      1.5    b ii_each  correct  3104       2138        0.561              3           405.5
      1.5    b ii_each nonearly  3134       2133        0.549              3           400.5
Readings with all five values within tolerance: 0 of 16 ; with four: 12 ; readings with the selective count within tolerance: 4

Result: not reproduced. The dandiset holds the paper's 4,706 cells in 59 planes. Several readings put the task-related count within tolerance (2,551 to 2,772 with span a; 3,104 and 3,134 with span b are above it), and all readings but one put the contra-preferring fraction within it (0.547 to 0.590, against a lower limit of 0.549). The selective count is below tolerance with the mean over the epochs (1,315 to 1,417 where the task-related count fits) and above it with each epoch separately (1,844 to 1,957); the paper's 1,575 lies between the two. The closest reading gives 1,417 (1.5 s response epoch, span a, mean over epochs, all non-early trials), just below the lower limit of 1,417.5. No reading puts all five values within tolerance; four match under several readings and the fifth falls just outside.

Baselines near 0 matter little: 145 of 305,349 included ROI-trials have F0 at or below 0, and 622 are below a tenth of the ROI's median fluorescence. The neuropil-corrected F of 3,403 included ROIs reaches 0 in at least one frame, mostly briefly: the longest such run is one frame in 2,034 ROIs, two to seven frames in 1,199 and eight frames (0.5 s) or more in 170 (up to 518 frames). Leaving them out moves the counts by a few cells; in the closest reading the selective count becomes 1,416 (cut at 0) or 1,419 (cut at 0.1), the latter within tolerance. These cuts are checks chosen here, not readings of the criterion, so they do not decide the outcome, but they show that the selective count sits at the edge of the tolerance. Early licks cannot be left out: the imaging files hold no lick events (only the sample, delay and go events) and flag no trial as an early lick.

The criterion sets only a reproduced condition, all values within tolerance under one reading, and no reading meets it (0 of 16 in the tally above). Four values are within tolerance in 12 readings, but the selective count is within tolerance only in the 4 readings with span (b) and the mean over epochs, where the task-related count is above it. The selective count is measured under every reading and misses, so it is not a class the dandiset lacks, as the intermediate units of units-classification are. That the paper's 1,575 lies between the counts of two readings does not put it within tolerance under one. The outcome was partially reproduced until the third verification round, which agreed with the change to not reproduced.

fig4f-spatially-intermingled¶

Contra- and ipsi-preferring neurons are found in all fields of view and are spatially intermingled; nearest-neighbour selectivity and same-type distances do not differ from shuffled data (P = 0.34 and P = 0.61).

Criterion: a claim of absence on the paper's two tests: (1) the probability that each selective cell's nearest neighbour (among (a) selective cells or (b) all cells) has the same preference, against shuffled labels, P the fraction of shuffles with a larger probability; (2) the mean distance to the nearest cell of the same preference, P the fraction of shuffles with a smaller distance; labels shuffled within each field of view or across all; 10^4 shuffles. Reproduced when both tests give P >= 0.05 and both preferences occur in every field of view; not reproduced when either test gives P < 0.05. Distances are between ROI centroids in pixels (the files give no scale). Run for every reading of imaging-selectivity-counts.

In [28]:
rng = np.random.default_rng(4)
rows = []
for key, res in IMG.items():
    both = sum(bool((r["sel"] & r["contra"]).any() and (r["sel"] & ~r["contra"]).any()) for r in res)
    for nbset in ("selective", "all"):
        for shuf in ("within", "across"):
            out = alm.spatial_tests(O, res, nbset, shuf, rng)
            rows.append(dict(response=key[0], span=key[1], rank=key[2], trials=key[3], neighbours=nbset, shuffle=shuf,
                             planes_with_both=both, P_prob=out["P_prob"], prob=out["prob"], prob_shuffle=out["prob_shuffle"],
                             P_dist=out["P_dist"], dist=out["dist"], dist_shuffle=out["dist_shuffle"]))
SP = pd.DataFrame(rows)
SP["absent"] = (SP.P_prob >= 0.05) & (SP.P_dist >= 0.05)
pd.set_option("display.max_rows", 100)
print(SP.round(4).to_string(index=False))
print("\nTest rows with both P >= 0.05:", int(SP.absent.sum()), "of", len(SP),
      "; selection readings with both preferences in all 59 planes:", int((SP.groupby(["response", "span", "rank", "trials"]).planes_with_both.first() == 59).sum()), "of 16")
print("Rows with P < 0.05 in at least one test, by neighbour set and shuffle:")
print((~SP.absent).groupby([SP.neighbours, SP.shuffle]).sum().to_string())
 response span    rank   trials neighbours shuffle  planes_with_both  P_prob   prob  prob_shuffle  P_dist    dist  dist_shuffle  absent
      1.3    a  i_mean  correct  selective  within                58  0.1033 0.5388        0.5177  0.1066 71.0163       72.1926    True
      1.3    a  i_mean  correct  selective  across                58  0.0606 0.5388        0.5120  0.0358 71.0163       72.7570   False
      1.3    a  i_mean  correct        all  within                58  0.0140 0.2002        0.1779  0.0999 71.0163       72.2061   False
      1.3    a  i_mean  correct        all  across                58  0.0071 0.2002        0.1758  0.0360 71.0163       72.7656   False
      1.3    a  i_mean nonearly  selective  within                57  0.0772 0.5420        0.5188  0.0331 69.5117       71.1751   False
      1.3    a  i_mean nonearly  selective  across                57  0.0660 0.5420        0.5160  0.0556 69.5117       71.0349    True
      1.3    a  i_mean nonearly        all  within                57  0.0075 0.2149        0.1895  0.0300 69.5117       71.1952   False
      1.3    a  i_mean nonearly        all  across                57  0.0068 0.2149        0.1887  0.0570 69.5117       71.0461   False
      1.3    a ii_each  correct  selective  within                59  0.0001 0.5705        0.5149  0.0020 59.5247       61.4798   False
      1.3    a ii_each  correct  selective  across                59  0.0000 0.5705        0.5069  0.0008 59.5247       61.6898   False
      1.3    a ii_each  correct        all  within                59  0.0001 0.2674        0.2273  0.0011 59.5247       61.4909   False
      1.3    a ii_each  correct        all  across                59  0.0000 0.2674        0.2254  0.0005 59.5247       61.6873   False
      1.3    a ii_each nonearly  selective  within                58  0.0003 0.5622        0.5111  0.0009 59.3822       61.4944   False
      1.3    a ii_each nonearly  selective  across                58  0.0001 0.5622        0.5060  0.0002 59.3822       61.5531   False
      1.3    a ii_each nonearly        all  within                58  0.0002 0.2635        0.2245  0.0008 59.3822       61.4761   False
      1.3    a ii_each nonearly        all  across                58  0.0001 0.2635        0.2235  0.0001 59.3822       61.5436   False
      1.3    b  i_mean  correct  selective  within                58  0.2489 0.5283        0.5174  0.0867 67.3616       68.5257    True
      1.3    b  i_mean  correct  selective  across                58  0.1401 0.5283        0.5105  0.0730 67.3616       68.6224    True
      1.3    b  i_mean  correct        all  within                58  0.0060 0.2158        0.1905  0.0866 67.3616       68.5153   False
      1.3    b  i_mean  correct        all  across                58  0.0038 0.2158        0.1887  0.0734 67.3616       68.6228   False
      1.3    b  i_mean nonearly  selective  within                58  0.0532 0.5459        0.5207  0.0248 65.9053       67.5346   False
      1.3    b  i_mean nonearly  selective  across                58  0.0166 0.5459        0.5116  0.0097 65.9053       67.8073   False
      1.3    b  i_mean nonearly        all  within                58  0.0017 0.2338        0.2025  0.0249 65.9053       67.5310   False
      1.3    b  i_mean nonearly        all  across                58  0.0009 0.2338        0.2005  0.0102 65.9053       67.8140   False
      1.3    b ii_each  correct  selective  within                59  0.0004 0.5601        0.5151  0.0197 56.6194       57.7642   False
      1.3    b ii_each  correct  selective  across                59  0.0000 0.5601        0.5062  0.0127 56.6194       57.8782   False
      1.3    b ii_each  correct        all  within                59  0.0000 0.2863        0.2520  0.0194 56.6194       57.7663   False
      1.3    b ii_each  correct        all  across                59  0.0000 0.2863        0.2487  0.0144 56.6194       57.8721   False
      1.3    b ii_each nonearly  selective  within                59  0.0000 0.5748        0.5116  0.0003 55.9740       57.7680   False
      1.3    b ii_each nonearly  selective  across                59  0.0000 0.5748        0.5040  0.0007 55.9740       57.7859   False
      1.3    b ii_each nonearly        all  within                59  0.0000 0.2948        0.2540  0.0004 55.9740       57.7903   False
      1.3    b ii_each nonearly        all  across                59  0.0000 0.2948        0.2505  0.0002 55.9740       57.7905   False
      1.5    a  i_mean  correct  selective  within                58  0.1340 0.5367        0.5184  0.0782 70.6012       71.8689    True
      1.5    a  i_mean  correct  selective  across                58  0.0772 0.5367        0.5127  0.0914 70.6012       71.8329    True
      1.5    a  i_mean  correct        all  within                58  0.0201 0.2038        0.1829  0.0752 70.6012       71.8866   False
      1.5    a  i_mean  correct        all  across                58  0.0151 0.2038        0.1809  0.0948 70.6012       71.8297   False
      1.5    a  i_mean nonearly  selective  within                58  0.0178 0.5558        0.5220  0.0085 67.5254       69.5928   False
      1.5    a  i_mean nonearly  selective  across                58  0.0072 0.5558        0.5153  0.0037 67.5254       69.8467   False
      1.5    a  i_mean nonearly        all  within                58  0.0029 0.2246        0.1955  0.0095 67.5254       69.5828   False
      1.5    a  i_mean nonearly        all  across                58  0.0016 0.2246        0.1936  0.0040 67.5254       69.8442   False
      1.5    a ii_each  correct  selective  within                59  0.0006 0.5593        0.5142  0.0046 58.7129       60.2167   False
      1.5    a ii_each  correct  selective  across                59  0.0002 0.5593        0.5089  0.0047 58.7129       60.2357   False
      1.5    a ii_each  correct        all  within                59  0.0002 0.2669        0.2312  0.0036 58.7129       60.2221   False
      1.5    a ii_each  correct        all  across                59  0.0001 0.2669        0.2298  0.0045 58.7129       60.2430   False
      1.5    a ii_each nonearly  selective  within                59  0.0000 0.5723        0.5161  0.0009 57.9501       59.8414   False
      1.5    a ii_each nonearly  selective  across                59  0.0000 0.5723        0.5064  0.0003 57.9501       60.0173   False
      1.5    a ii_each nonearly        all  within                59  0.0000 0.2724        0.2340  0.0006 57.9501       59.8478   False
      1.5    a ii_each nonearly        all  across                59  0.0000 0.2724        0.2306  0.0004 57.9501       60.0099   False
      1.5    b  i_mean  correct  selective  within                58  0.1631 0.5361        0.5202  0.0515 66.5540       67.9087    True
      1.5    b  i_mean  correct  selective  across                58  0.0685 0.5361        0.5117  0.0317 66.5540       68.1374   False
      1.5    b  i_mean  correct        all  within                58  0.0021 0.2235        0.1961  0.0488 66.5540       67.9038   False
      1.5    b  i_mean  correct        all  across                58  0.0021 0.2235        0.1935  0.0278 66.5540       68.1576   False
      1.5    b  i_mean nonearly  selective  within                58  0.0388 0.5506        0.5232  0.0058 65.0816       67.0870   False
      1.5    b  i_mean nonearly  selective  across                58  0.0083 0.5506        0.5119  0.0056 65.0816       67.1998   False
      1.5    b  i_mean nonearly        all  within                58  0.0022 0.2386        0.2082  0.0064 65.0816       67.0835   False
      1.5    b  i_mean nonearly        all  across                58  0.0010 0.2386        0.2052  0.0057 65.0816       67.1971   False
      1.5    b ii_each  correct  selective  within                59  0.0002 0.5599        0.5140  0.0176 56.0287       57.1516   False
      1.5    b ii_each  correct  selective  across                59  0.0002 0.5599        0.5071  0.0102 56.0287       57.2454   False
      1.5    b ii_each  correct        all  within                59  0.0002 0.2881        0.2530  0.0159 56.0287       57.1660   False
      1.5    b ii_each  correct        all  across                59  0.0000 0.2881        0.2505  0.0107 56.0287       57.2430   False
      1.5    b ii_each nonearly  selective  within                59  0.0000 0.5715        0.5142  0.0024 55.7341       57.2363   False
      1.5    b ii_each nonearly  selective  across                59  0.0000 0.5715        0.5047  0.0012 55.7341       57.3130   False
      1.5    b ii_each nonearly        all  within                59  0.0001 0.2916        0.2554  0.0022 55.7341       57.2384   False
      1.5    b ii_each nonearly        all  across                59  0.0001 0.2916        0.2507  0.0022 55.7341       57.3091   False

Test rows with both P >= 0.05: 7 of 64 ; selection readings with both preferences in all 59 planes: 7 of 16
Rows with P < 0.05 in at least one test, by neighbour set and shuffle:
neighbours  shuffle
all         across     16
            within     16
selective   across     13
            within     12

Result: not reproduced. In 57 of the 64 combinations of selection reading, neighbour set and shuffle, at least one test finds clustering at P < 0.05 (nearby cells share their preference more often, and same-preference cells are closer, than with shuffled labels); every combination with all cells as neighbours does. In 9 of the 16 selection readings, some plane lacks one of the two preferences. The effect is small (for example a same-preference probability of 0.539 against 0.517 for shuffles), and neighbouring ROIs share neuropil, which may contribute. The shuffle across planes mixes labels between planes with different contra fractions, so it is not a spatial null; with the shuffle within planes, 28 of the 32 rows still give P < 0.05.

fig4h-pt-contra-imaging¶

Among layer 5 neurons, about a quarter of labelled PT (182 of 534) and IT (174 of 542) neurons were selective; 67% of selective PT neurons were contra-preferring (P < 0.001, binomial test) but only 48% of IT neurons, and PT neurons were more often contra-preferring than IT neurons (P < 0.001, chi-squared test).

Criterion: labelled PT and IT cells imaged 450 to 700 um deep; selectivity and preference as imaging-selectivity-counts, with its readings. Reproduced when, under one reading, the PT binomial test (against 0.5, two-sided; one-sided reported) gives P < 0.001 with a contralateral majority, the IT test P > 0.05, the chi-squared test of PT against IT P < 0.001, and the counts are within tolerance; partially reproduced when the three tests come out so and a value falls outside. The paper selects layer 5 only by imaging depth and says IT neurons were also found in superficial layers. The files give no plane depth, so layer 5 cannot be selected; the claims file says that all labelled cells is a substitute population, not a reading of the paper. The claim is blocked (metadata insufficient), and every included cell labelled PT or IT is shown as a variant.

In [29]:
# Every text field and attribute of the 59 imaging files, searched for a plane depth
MD = alm.ophys_metadata_all(assets)
MD = MD[~MD.field.str.endswith(("@object_id", "@namespace", "@neurodata_type"))]
print("Fields read per file:", MD.groupby("file").size().unique().tolist(), "; distinct fields:", MD.field.nunique())
nv = MD.groupby("field").text.nunique()
print("Fields whose value differs between files:", nv[nv > 1].to_dict())
print("Values of the fields that describe the plane, the images or the session:")
for k in sorted(MD.field.unique()):
    if any(w in k for w in ("ImagingPlane", "description", "images", "comments", "identifier", "virus")):
        v = MD.text[MD.field == k].unique()
        print(f"  {k}: {v[0][:150]!r}" + (f" (and {len(v) - 1} other values)" if len(v) > 1 else ""))
hit = MD[MD.text.str.contains(r"depth|micron|\bum\b|\u00b5m|layer|\bL5", case=False, regex=True)]
print("Fields mentioning depth, um, microns or layer:", len(hit))
print("File names (first 3):", sorted(MD.file.unique())[:3])
Fields read per file: [58] ; distinct fields: 58
Fields whose value differs between files: {'general/subject/date_of_birth': 4, 'general/subject/genotype': 2, 'general/subject/subject_id': 4, 'general/virus': 4, 'identifier': 59, 'session_start_time': 18, 'timestamps_reference_time': 18}
Values of the fields that describe the plane, the images or the session:
  acquisition/images@description: 'Structural images of a scanning'
  general/experiment_description: 'Two-photon experiment recorded in ALM'
  general/optophysiology/ImagingPlane/description: 'Imaging session for PT and IT neurons during audio delay task'
  general/optophysiology/ImagingPlane/excitation_lambda: '940.0'
  general/optophysiology/ImagingPlane/green/description: 'green channel'
  general/optophysiology/ImagingPlane/green/emission_lambda: '500.0'
  general/optophysiology/ImagingPlane/imaging_rate: '14.840000000000304'
  general/optophysiology/ImagingPlane/indicator: 'GCaMP6s'
  general/optophysiology/ImagingPlane/location: 'ALM'
  general/virus: '[{"injection_id": "1", "virus": "V", "injection_date": "2013-06-27", "injection_volume": "150.0", "virus_dilution": "None", "brain_location_name": "le' (and 3 other values)
  identifier: '216219_2013-08-15_5' (and 58 other values)
  intervals/trials/early_lick@description: 'varchar(32) #'
  intervals/trials/outcome@description: 'varchar(32) #'
  intervals/trials/start_time@description: 'Start time of epoch, in seconds'
  intervals/trials/stop_time@description: 'Stop time of epoch, in seconds'
  intervals/trials/task@description: 'varchar(12) # task type'
  intervals/trials/task_protocol@description: 'tinyint # task protocol'
  intervals/trials/trial_instruction@description: 'varchar(16) #'
  intervals/trials@description: 'experimental trials'
  processing/behavior/BehavioralEvents/delay@comments: 'no comments'
  processing/behavior/BehavioralEvents/delay@description: 'Time stamps of the beginning of the delay period on each trial.'
  processing/behavior/BehavioralEvents/go@comments: 'no comments'
  processing/behavior/BehavioralEvents/go@description: 'Time stamps of the go cue signal on each trial.'
  processing/behavior/BehavioralEvents/sample@comments: 'no comments'
  processing/behavior/BehavioralEvents/sample@description: 'Time stamps of the beginning of the sampling period on each trial.'
  processing/behavior@description: 'Time of behavioral events in this session'
  processing/ophys/Fluorescence/NeuropilResponseSeries@comments: 'no comments'
  processing/ophys/Fluorescence/NeuropilResponseSeries@description: 'average fluorescence of the neuropil surrounding the roi'
  processing/ophys/Fluorescence/RoiResponseSeries/rois@description: 'table region for rois in this segmentation'
  processing/ophys/Fluorescence/RoiResponseSeries@comments: 'no comments'
  processing/ophys/Fluorescence/RoiResponseSeries@description: 'average fluorescence of roi'
  processing/ophys/ImageSegmentation/PlaneSegmentation/cell_type@description: 'PT, IT, or unknown'
  processing/ophys/ImageSegmentation/PlaneSegmentation/image_mask@description: 'Image masks for each ROI'
  processing/ophys/ImageSegmentation/PlaneSegmentation/included@description: 'whether to include this roi into later analyses'
  processing/ophys/ImageSegmentation/PlaneSegmentation/neuropil_mask@description: 'mask of neurophil surrounding this roi'
  processing/ophys/ImageSegmentation/PlaneSegmentation@description: 'output from segmenting the current imaging plane'
  processing/ophys@description: 'Processing result of imaging'
  session_description: 'Imaging session'
Fields mentioning depth, um, microns or layer: 0
File names (first 3): ['sub-216219/sub-216219_ses-20130815_obj-12g09i6_behavior+ophys.nwb', 'sub-216219/sub-216219_ses-20130815_obj-1eyeli4_behavior+ophys.nwb', 'sub-216219/sub-216219_ses-20130815_obj-1u51sit_behavior+ophys.nwb']
In [30]:
types = np.concatenate([pl["roi_type"] for pl in O])
inc = np.concatenate([pl["included"] for pl in O])
rows = []
for key, res in IMG.items():
    S = np.concatenate([r["sel"] for r in res]); C = np.concatenate([r["contra"] for r in res])
    pt, it = (types == "PT") & inc, (types == "IT") & inc
    kp, np_ = int((pt & S & C).sum()), int((pt & S).sum())
    ki, ni = int((it & S & C).sum()), int((it & S).sum())
    tab = [[kp, np_ - kp], [ki, ni - ki]]
    rows.append(dict(response=key[0], span=key[1], rank=key[2], trials=key[3], PT=int(pt.sum()), PT_sel=np_,
                     PT_contra=round(kp / np_, 3), IT=int(it.sum()), IT_sel=ni, IT_contra=round(ki / ni, 3),
                     P_PT=stats.binomtest(kp, np_, 0.5).pvalue, P_PT_1sided=stats.binomtest(kp, np_, 0.5, alternative="greater").pvalue,
                     P_IT=stats.binomtest(ki, ni, 0.5).pvalue, P_chi2=stats.chi2_contingency(tab).pvalue,
                     P_chi2_nocorr=stats.chi2_contingency(tab, correction=False).pvalue))
H = pd.DataFrame(rows)
H["tests_ok"] = (H.P_PT < 0.001) & (H.PT_contra > 0.5) & (H.P_IT > 0.05) & (H.P_chi2 < 0.001)
for col, (v, t) in dict(PT=(534, 53.4), IT=(542, 54.2), PT_sel=(182, 18.2), IT_sel=(174, 17.4), PT_contra=(0.67, 0.067), IT_contra=(0.48, 0.048)).items():
    H[col + "_ok"] = (H[col] - v).abs() <= t + 1e-9
print(H.round(4).to_string(index=False))
print("Readings where the three tests come out as in the paper:", int(H.tests_ok.sum()), "of", len(H))
print("Readings with PT binomial P < 0.001:", int((H.P_PT < 0.001).sum()), "; with chi-squared P < 0.001:", int((H.P_chi2 < 0.001).sum()),
      "; smallest chi-squared P:", round(H.P_chi2.min(), 4))
print("Labelled cells by mouse (included):")
print(pd.crosstab(np.concatenate([[pl["subject"]] * len(pl["roi_type"]) for pl in O])[inc], types[inc]).to_string())
 response span    rank   trials  PT  PT_sel  PT_contra  IT  IT_sel  IT_contra   P_PT  P_PT_1sided   P_IT  P_chi2  P_chi2_nocorr  tests_ok  PT_ok  IT_ok  PT_sel_ok  IT_sel_ok  PT_contra_ok  IT_contra_ok
      1.3    a  i_mean  correct 530     152      0.599 843     231      0.519 0.0184       0.0092 0.5987  0.1557         0.1273     False   True  False      False      False         False          True
      1.3    a  i_mean nonearly 530     150      0.627 843     231      0.511 0.0024       0.0012 0.7925  0.0342         0.0262     False   True  False      False      False          True          True
      1.3    a ii_each  correct 530     213      0.592 843     318      0.503 0.0091       0.0045 0.9553  0.0556         0.0452     False   True  False      False      False         False          True
      1.3    a ii_each nonearly 530     201      0.592 843     322      0.494 0.0109       0.0055 0.8673  0.0357         0.0285     False   True  False      False      False         False          True
      1.3    b  i_mean  correct 530     173      0.613 843     255      0.510 0.0037       0.0019 0.8023  0.0453         0.0357     False   True  False       True      False          True          True
      1.3    b  i_mean nonearly 530     170      0.635 843     257      0.494 0.0005       0.0003 0.9007  0.0056         0.0041     False   True  False       True      False          True          True
      1.3    b ii_each  correct 530     247      0.599 843     357      0.493 0.0022       0.0011 0.8324  0.0128         0.0101     False   True  False      False      False         False          True
      1.3    b ii_each nonearly 530     236      0.610 843     368      0.476 0.0009       0.0004 0.3755  0.0016         0.0012     False   True  False      False      False          True          True
      1.5    a  i_mean  correct 530     155      0.594 843     237      0.532 0.0242       0.0121 0.3632  0.2704         0.2278     False   True  False      False      False         False         False
      1.5    a  i_mean nonearly 530     159      0.635 843     247      0.514 0.0008       0.0004 0.7027  0.0216         0.0164     False   True  False      False      False          True          True
      1.5    a ii_each  correct 530     223      0.596 843     333      0.517 0.0048       0.0024 0.5838  0.0770         0.0635     False   True  False      False      False         False          True
      1.5    a ii_each nonearly 530     220      0.577 843     342      0.497 0.0259       0.0129 0.9569  0.0763         0.0630     False   True  False      False      False         False          True
      1.5    b  i_mean  correct 530     173      0.607 843     256      0.516 0.0060       0.0030 0.6618  0.0773         0.0621     False   True  False       True      False          True          True
      1.5    b  i_mean nonearly 530     174      0.649 843     263      0.494 0.0001       0.0000 0.9019  0.0020         0.0014     False   True  False       True      False          True          True
      1.5    b ii_each  correct 530     249      0.610 843     365      0.496 0.0006       0.0003 0.9166  0.0066         0.0052     False   True  False      False      False          True          True
      1.5    b ii_each nonearly 530     244      0.602 843     373      0.477 0.0017       0.0008 0.4074  0.0030         0.0023     False   True  False      False      False         False          True
Readings where the three tests come out as in the paper: 0 of 16
Readings with PT binomial P < 0.001: 5 ; with chi-squared P < 0.001: 0 ; smallest chi-squared P: 0.0016
Labelled cells by mouse (included):
col_0    IT   PT  Pyr  interneuron  unknown
row_0                                      
216219  374    0    0            2     1121
217951   95   34    0            0      646
221541  233  286   23            7      599
226244  141  210    0            0      935

Result: blocked (metadata insufficient). The paper's layer 5 population cannot be selected, as no plane gives its depth. On the substitute of all labelled cells, 530 included cells are labelled PT (paper 534) and 843 IT (paper 542). Selective PT cells are contra-preferring in a majority under every reading (0.577 to 0.649; binomial P < 0.001 in 5 of 16 readings) and IT cells are not (0.476 to 0.532; P > 0.05 in all). The chi-squared test of PT against IT never reaches P < 0.001 (smallest 0.0016), so on this population the three tests do not come out as in the paper in any reading. The IT count is 301 above the paper's, consistent with the IT cells outside layer 5 that the paper reports. A search of every text field and attribute of the 59 imaging files (58 each, including the ImagingPlane group, the session description and the image descriptions) finds no mention of depth, microns or layer; the fields that differ between files are the subject, its virus record, the dates and the identifier.

reaction-time¶

The reaction time after the go cue was 61.1 +/- 10.5 ms (mean +/- s.d. across mice).

Criterion: reaction time from the mouth-movement signal (photodiode and infrared laser); tolerance the paper's spread across mice. Readings: mice (a) the electrophysiology mice of Fig. 3 (here the 14 mice with PT or IT units) or (b) all 19 mice with the signal; per-mouse value (i) mean or (ii) median of per-trial reaction times; correct trials or all non-early trials. The paper gives no rule for reading a reaction time from the signal, so the claims file names two: (k) the first time after the go cue the signal crosses a fixed threshold on the raw signal, and (z) the first time it exceeds the baseline mean plus a number of s.d., with the delay epoch or its last 0.5 s as baseline; the threshold, the number of s.d. and the baseline are open. The lick_trace series of the extracellular files is taken as that signal (data check). Several values of each open parameter are run. A trial without a crossing in the 0.4 s after the cue has no reaction time and is counted. For (k) a crossing needs the signal below the threshold at the cue.

In [31]:
started_lick = time.time()
LK = alm.lick_all(E)
print(f"Lick trace read for {len(LK)} sessions in {time.time() - started_lick:.0f} s")
fig3_mice = sorted({s["subject"] for s in E if np.isin(s["cell_type"], ["PT", "IT"]).any()})
rows = []
for w in LK:
    s = E[w["session"]]
    stored = ~np.isnan(w["trace"][:, 0])
    base = dict(subject=s["subject"], session=w["session"], correct=w["correct"], light=w["light"], stored=stored)
    for bname, key in (("delay", "delay"), ("last 0.5 s", "last")):
        for k in (2, 3, 4, 5, 10):
            lvl = w[f"base_{key}_mean"] + k * w[f"base_{key}_sd"]
            rows.append(pd.DataFrame(dict(base, reading="z", baseline=bname, param=float(k),
                                          rt=alm.first_crossing_ms(w["trace"], lvl, w["rate"]))))
    for th in (0.1, 0.15, 0.2, 0.5, 1.0, 2.0, 4.0):
        rt = alm.first_crossing_ms(w["trace"], np.full(len(stored), th), w["rate"])
        rt[~(w["at_go"] < th)] = np.nan
        rows.append(pd.DataFrame(dict(base, reading="k", baseline="raw", param=th, rt=rt)))
RT = pd.concat(rows, ignore_index=True)
print("Non-early trials:", int((RT.reading == "k").sum() / 7), "; without a stored window around the cue:",
      int((~RT.stored).sum() / 17))
RT = RT[RT.stored]
out = []
for (rd, bl, par), g in RT.groupby(["reading", "baseline", "param"], sort=False):
    for tset in ("correct", "nonearly"):
        gt = g[g.correct] if tset == "correct" else g
        for mice in ("a_fig3", "b_all"):
            h = gt[gt.subject.isin(fig3_mice)] if mice == "a_fig3" else gt
            for agg in ("mean", "median"):
                pm = h.groupby("subject").rt.agg(agg)
                out.append(dict(reading=rd, baseline=bl, param=par, trials=tset, mice=mice, per_mouse=agg,
                                mice_with_value=int(pm.notna().sum()), trials_n=len(h),
                                no_crossing=round(h.rt.isna().mean(), 3), mean_ms=round(pm.mean(), 1),
                                sd_ms=round(pm.std(), 1), within=abs(pm.mean() - 61.1) <= 10.5))
RTs = pd.DataFrame(out)
print("Mean and s.d. across mice of the per-mouse reaction time (param: s.d. above the baseline mean for z, raw threshold in a.u. for k):")
print(RTs.to_string(index=False))
print("Paper: 61.1 ms (50.6 to 71.6), s.d. across mice 10.5 ms")
print("\nRows within tolerance by reading and parameter (of 8 each):")
print(RTs.groupby(["reading", "baseline", "param"], sort=False).within.sum().to_string())
print("Range of the mean across mice, reading z:", RTs[RTs.reading == "z"].mean_ms.min(), "to", RTs[RTs.reading == "z"].mean_ms.max(),
      "; reading k:", RTs[RTs.reading == "k"].mean_ms.min(), "to", RTs[RTs.reading == "k"].mean_ms.max())
sub = RT[(RT.reading == "z") & (RT.baseline == "last 0.5 s") & (RT.param == 3) & RT.correct]
print("\nReading z, last 0.5 s, 3 s.d., correct trials, per mouse (mean and median ms, trials, fraction without a crossing):")
print(sub.groupby("subject").rt.agg(["mean", "median", "size", lambda v: v.isna().mean()]).round(2).rename(columns={"<lambda_0>": "no_crossing"}).to_string())
print("Same, light trials left out: mean across mice", round(sub[~sub.light].groupby("subject").rt.mean().mean(), 1), "ms")
sess = []
for w in LK:
    ok = ~np.isnan(w["trace"][:, 0]) & w["correct"]
    d = w["trace"][ok] - w["base_last_mean"][ok, None]
    sess.append(dict(session=w["session"], subject=E[w["session"]]["subject"], baseline=np.median(w["base_last_mean"][ok]),
                     change_100ms=d[:, 1000].mean(), change_200ms=d[:, 2000].mean()))
sess = pd.DataFrame(sess)
print("\nPer session: median baseline (a.u.) range", round(sess.baseline.min(), 3), "to", round(sess.baseline.max(), 3),
      "; sessions where the mean signal 100 ms after the cue is below its baseline:", int((sess.change_100ms < 0).sum()),
      "of", len(sess), "in mice", sorted(sess[sess.change_100ms < 0].subject.unique()))
Lick trace read for 99 sessions in 176 s
Non-early trials: 24023 ; without a stored window around the cue: 113
Mean and s.d. across mice of the per-mouse reaction time (param: s.d. above the baseline mean for z, raw threshold in a.u. for k):
reading   baseline  param   trials   mice per_mouse  mice_with_value  trials_n  no_crossing  mean_ms  sd_ms  within
      z      delay   2.00  correct a_fig3      mean               14     15874        0.081     61.3   42.6    True
      z      delay   2.00  correct a_fig3    median               14     15874        0.081     56.4   40.7    True
      z      delay   2.00  correct  b_all      mean               19     18295        0.084     54.7   38.1    True
      z      delay   2.00  correct  b_all    median               19     18295        0.084     50.5   36.8   False
      z      delay   2.00 nonearly a_fig3      mean               14     20691        0.081     60.6   41.3    True
      z      delay   2.00 nonearly a_fig3    median               14     20691        0.081     56.0   40.6    True
      z      delay   2.00 nonearly  b_all      mean               19     23910        0.086     54.5   36.8    True
      z      delay   2.00 nonearly  b_all    median               19     23910        0.086     50.3   36.6   False
      z      delay   3.00  correct a_fig3      mean               14     15874        0.118     79.2   42.0   False
      z      delay   3.00  correct a_fig3    median               14     15874        0.118     71.8   37.0   False
      z      delay   3.00  correct  b_all      mean               19     18295        0.124     73.5   37.5   False
      z      delay   3.00  correct  b_all    median               19     18295        0.124     67.9   32.6    True
      z      delay   3.00 nonearly a_fig3      mean               14     20691        0.115     78.7   40.9   False
      z      delay   3.00 nonearly a_fig3    median               14     20691        0.115     71.7   37.0   False
      z      delay   3.00 nonearly  b_all      mean               19     23910        0.123     73.4   36.5   False
      z      delay   3.00 nonearly  b_all    median               19     23910        0.123     67.9   32.6    True
      z      delay   4.00  correct a_fig3      mean               14     15874        0.144     94.3   43.5   False
      z      delay   4.00  correct a_fig3    median               14     15874        0.144     82.8   38.0   False
      z      delay   4.00  correct  b_all      mean               19     18295        0.149     88.6   40.5   False
      z      delay   4.00  correct  b_all    median               19     18295        0.149     79.1   34.8   False
      z      delay   4.00 nonearly a_fig3      mean               14     20691        0.140     93.3   42.3   False
      z      delay   4.00 nonearly a_fig3    median               14     20691        0.140     82.0   37.9   False
      z      delay   4.00 nonearly  b_all      mean               19     23910        0.147     88.1   39.5   False
      z      delay   4.00 nonearly  b_all    median               19     23910        0.147     78.7   34.7   False
      z      delay   5.00  correct a_fig3      mean               14     15874        0.166    104.4   46.2   False
      z      delay   5.00  correct a_fig3    median               14     15874        0.166     92.2   41.2   False
      z      delay   5.00  correct  b_all      mean               19     18295        0.170     97.8   43.2   False
      z      delay   5.00  correct  b_all    median               19     18295        0.170     87.5   37.8   False
      z      delay   5.00 nonearly a_fig3      mean               14     20691        0.162    103.2   44.7   False
      z      delay   5.00 nonearly a_fig3    median               14     20691        0.162     91.3   40.4   False
      z      delay   5.00 nonearly  b_all      mean               19     23910        0.169     97.1   42.0   False
      z      delay   5.00 nonearly  b_all    median               19     23910        0.169     86.8   37.2   False
      z      delay  10.00  correct a_fig3      mean               14     15874        0.254    129.1   51.4   False
      z      delay  10.00  correct a_fig3    median               14     15874        0.254    111.7   48.2   False
      z      delay  10.00  correct  b_all      mean               19     18295        0.270    120.7   48.1   False
      z      delay  10.00  correct  b_all    median               19     18295        0.270    105.3   44.3   False
      z      delay  10.00 nonearly a_fig3      mean               14     20691        0.255    127.3   49.0   False
      z      delay  10.00 nonearly a_fig3    median               14     20691        0.255    107.2   42.6   False
      z      delay  10.00 nonearly  b_all      mean               19     23910        0.274    119.3   46.1   False
      z      delay  10.00 nonearly  b_all    median               19     23910        0.274    101.8   39.4   False
      z last 0.5 s   2.00  correct a_fig3      mean               14     15874        0.061     44.3   34.5   False
      z last 0.5 s   2.00  correct a_fig3    median               14     15874        0.061     42.2   41.3   False
      z last 0.5 s   2.00  correct  b_all      mean               19     18295        0.065     42.8   29.7   False
      z last 0.5 s   2.00  correct  b_all    median               19     18295        0.065     40.5   36.1   False
      z last 0.5 s   2.00 nonearly a_fig3      mean               14     20691        0.063     43.9   33.3   False
      z last 0.5 s   2.00 nonearly a_fig3    median               14     20691        0.063     41.7   41.1   False
      z last 0.5 s   2.00 nonearly  b_all      mean               19     23910        0.068     42.7   28.6   False
      z last 0.5 s   2.00 nonearly  b_all    median               19     23910        0.068     40.2   36.0   False
      z last 0.5 s   3.00  correct a_fig3      mean               14     15874        0.080     61.9   37.7    True
      z last 0.5 s   3.00  correct a_fig3    median               14     15874        0.080     60.8   38.5    True
      z last 0.5 s   3.00  correct  b_all      mean               19     18295        0.089     60.8   32.5    True
      z last 0.5 s   3.00  correct  b_all    median               19     18295        0.089     59.5   33.0    True
      z last 0.5 s   3.00 nonearly a_fig3      mean               14     20691        0.081     61.4   36.6    True
      z last 0.5 s   3.00 nonearly a_fig3    median               14     20691        0.081     60.3   38.4    True
      z last 0.5 s   3.00 nonearly  b_all      mean               19     23910        0.092     60.5   31.5    True
      z last 0.5 s   3.00 nonearly  b_all    median               19     23910        0.092     59.2   32.9    True
      z last 0.5 s   4.00  correct a_fig3      mean               14     15874        0.097     75.5   39.7   False
      z last 0.5 s   4.00  correct a_fig3    median               14     15874        0.097     72.8   39.8   False
      z last 0.5 s   4.00  correct  b_all      mean               19     18295        0.106     74.8   35.7   False
      z last 0.5 s   4.00  correct  b_all    median               19     18295        0.106     71.8   35.5   False
      z last 0.5 s   4.00 nonearly a_fig3      mean               14     20691        0.097     74.9   38.8   False
      z last 0.5 s   4.00 nonearly a_fig3    median               14     20691        0.097     72.2   39.5   False
      z last 0.5 s   4.00 nonearly  b_all      mean               19     23910        0.109     74.4   35.2   False
      z last 0.5 s   4.00 nonearly  b_all    median               19     23910        0.109     71.5   35.3    True
      z last 0.5 s   5.00  correct a_fig3      mean               14     15874        0.112     83.8   39.5   False
      z last 0.5 s   5.00  correct a_fig3    median               14     15874        0.112     78.1   38.4   False
      z last 0.5 s   5.00  correct  b_all      mean               19     18295        0.123     82.7   36.1   False
      z last 0.5 s   5.00  correct  b_all    median               19     18295        0.123     77.1   34.7   False
      z last 0.5 s   5.00 nonearly a_fig3      mean               14     20691        0.111     83.0   38.8   False
      z last 0.5 s   5.00 nonearly a_fig3    median               14     20691        0.111     77.5   38.2   False
      z last 0.5 s   5.00 nonearly  b_all      mean               19     23910        0.125     82.2   35.8   False
      z last 0.5 s   5.00 nonearly  b_all    median               19     23910        0.125     76.7   34.6   False
      z last 0.5 s  10.00  correct a_fig3      mean               14     15874        0.169    106.3   42.7   False
      z last 0.5 s  10.00  correct a_fig3    median               14     15874        0.169     96.3   40.4   False
      z last 0.5 s  10.00  correct  b_all      mean               19     18295        0.195    103.6   39.0   False
      z last 0.5 s  10.00  correct  b_all    median               19     18295        0.195     93.6   36.4   False
      z last 0.5 s  10.00 nonearly a_fig3      mean               14     20691        0.169    105.1   41.5   False
      z last 0.5 s  10.00 nonearly a_fig3    median               14     20691        0.169     95.4   39.8   False
      z last 0.5 s  10.00 nonearly  b_all      mean               19     23910        0.198    102.6   38.4   False
      z last 0.5 s  10.00 nonearly  b_all    median               19     23910        0.198     92.8   36.2   False
      k        raw   0.10  correct a_fig3      mean                9     15874        0.586     86.4   52.0   False
      k        raw   0.10  correct a_fig3    median                9     15874        0.586     91.0   63.6   False
      k        raw   0.10  correct  b_all      mean               10     18295        0.641     77.9   56.1   False
      k        raw   0.10  correct  b_all    median               10     18295        0.641     82.0   66.4   False
      k        raw   0.10 nonearly a_fig3      mean                9     20691        0.590     86.6   49.6   False
      k        raw   0.10 nonearly a_fig3    median                9     20691        0.590     87.7   60.2   False
      k        raw   0.10 nonearly  b_all      mean               10     23910        0.645     78.0   54.1   False
      k        raw   0.10 nonearly  b_all    median               10     23910        0.645     79.0   63.1   False
      k        raw   0.15  correct a_fig3      mean               13     15874        0.376    124.9   57.8   False
      k        raw   0.15  correct a_fig3    median               13     15874        0.376    126.3   64.1   False
      k        raw   0.15  correct  b_all      mean               17     18295        0.356    115.2   58.8   False
      k        raw   0.15  correct  b_all    median               17     18295        0.356    115.3   65.4   False
      k        raw   0.15 nonearly a_fig3      mean               13     20691        0.383    124.9   58.0   False
      k        raw   0.15 nonearly a_fig3    median               13     20691        0.383    126.7   64.3   False
      k        raw   0.15 nonearly  b_all      mean               17     23910        0.367    115.7   58.7   False
      k        raw   0.15 nonearly  b_all    median               17     23910        0.367    115.7   65.7   False
      k        raw   0.20  correct a_fig3      mean               14     15874        0.497    159.6   72.0   False
      k        raw   0.20  correct a_fig3    median               14     15874        0.497    148.4   70.3   False
      k        raw   0.20  correct  b_all      mean               19     18295        0.533    160.7   63.3   False
      k        raw   0.20  correct  b_all    median               19     18295        0.533    152.8   62.1   False
      k        raw   0.20 nonearly a_fig3      mean               14     20691        0.524    160.1   70.7   False
      k        raw   0.20 nonearly a_fig3    median               14     20691        0.524    148.5   69.9   False
      k        raw   0.20 nonearly  b_all      mean               19     23910        0.563    160.7   62.2   False
      k        raw   0.20 nonearly  b_all    median               19     23910        0.563    151.9   61.6   False
      k        raw   0.50  correct a_fig3      mean                9     15874        0.811    153.4   79.3   False
      k        raw   0.50  correct a_fig3    median                9     15874        0.811    140.1   71.8   False
      k        raw   0.50  correct  b_all      mean               11     18295        0.826    145.6   77.4   False
      k        raw   0.50  correct  b_all    median               11     18295        0.826    132.3   73.6   False
      k        raw   0.50 nonearly a_fig3      mean                9     20691        0.815    155.0   77.4   False
      k        raw   0.50 nonearly a_fig3    median                9     20691        0.815    140.5   72.2   False
      k        raw   0.50 nonearly  b_all      mean               11     23910        0.831    145.4   78.2   False
      k        raw   0.50 nonearly  b_all    median               11     23910        0.831    130.6   76.6   False
      k        raw   1.00  correct a_fig3      mean                8     15874        0.848    146.8   78.2   False
      k        raw   1.00  correct a_fig3    median                8     15874        0.848    134.4   73.3   False
      k        raw   1.00  correct  b_all      mean               10     18295        0.858    137.9   78.7   False
      k        raw   1.00  correct  b_all    median               10     18295        0.858    126.3   76.4   False
      k        raw   1.00 nonearly a_fig3      mean                8     20691        0.847    149.9   76.6   False
      k        raw   1.00 nonearly a_fig3    median                8     20691        0.847    134.9   73.4   False
      k        raw   1.00 nonearly  b_all      mean               10     23910        0.859    146.5   70.4   False
      k        raw   1.00 nonearly  b_all    median               10     23910        0.859    126.3   76.3   False
      k        raw   2.00  correct a_fig3      mean                5     15874        0.877    142.9   95.1   False
      k        raw   2.00  correct a_fig3    median                5     15874        0.877    123.5   88.1   False
      k        raw   2.00  correct  b_all      mean                7     18295        0.885    130.9   92.3   False
      k        raw   2.00  correct  b_all    median                7     18295        0.885    118.0   86.7   False
      k        raw   2.00 nonearly a_fig3      mean                5     20691        0.874    151.5   95.9   False
      k        raw   2.00 nonearly a_fig3    median                5     20691        0.874    137.7   88.4   False
      k        raw   2.00 nonearly  b_all      mean                7     23910        0.884    136.2   94.3   False
      k        raw   2.00 nonearly  b_all    median                7     23910        0.884    126.7   89.7   False
      k        raw   4.00  correct a_fig3      mean                4     15874        0.884    164.8   69.1   False
      k        raw   4.00  correct a_fig3    median                4     15874        0.884    152.0   65.5   False
      k        raw   4.00  correct  b_all      mean                5     18295        0.899    144.3   75.4   False
      k        raw   4.00  correct  b_all    median                5     18295        0.899    128.7   77.0   False
      k        raw   4.00 nonearly a_fig3      mean                4     20691        0.886    166.1   68.6   False
      k        raw   4.00 nonearly a_fig3    median                4     20691        0.886    152.3   66.1   False
      k        raw   4.00 nonearly  b_all      mean                5     23910        0.900    146.1   74.4   False
      k        raw   4.00 nonearly  b_all    median                5     23910        0.900    129.0   77.5   False
Paper: 61.1 ms (50.6 to 71.6), s.d. across mice 10.5 ms

Rows within tolerance by reading and parameter (of 8 each):
reading  baseline    param
z        delay       2.00     6
                     3.00     2
                     4.00     0
                     5.00     0
                     10.00    0
         last 0.5 s  2.00     0
                     3.00     8
                     4.00     1
                     5.00     0
                     10.00    0
k        raw         0.10     0
                     0.15     0
                     0.20     0
                     0.50     0
                     1.00     0
                     2.00     0
                     4.00     0
Range of the mean across mice, reading z: 40.2 to 129.1 ; reading k: 77.9 to 166.1

Reading z, last 0.5 s, 3 s.d., correct trials, per mouse (mean and median ms, trials, fraction without a crossing):
           mean  median  size  no_crossing
subject                                   
210861    44.32   45.70   249         0.00
210862    61.53   59.75   396         0.02
210863    62.88   60.65   576         0.22
214427    40.85   40.50   874         0.19
214429    94.51   85.20   943         0.39
214430    67.97   64.50   494         0.46
218453    61.57   52.30   778         0.00
218457    42.21   44.20  1200         0.00
218693    50.82   49.30   706         0.00
219030    80.09   71.80   816         0.34
219031    53.90   55.65   934         0.00
219033    42.01   40.70  1446         0.00
219036    43.92   41.00  1303         0.00
219037    50.55   55.80  1660         0.00
219038    42.32   44.00  1349         0.00
219247    41.15   43.00   738         0.00
219248   178.90  185.70  2013         0.22
219253    32.38   33.70  1302         0.00
221977    62.90   57.35   518         0.02
Same, light trials left out: mean across mice 61.0 ms

Per session: median baseline (a.u.) range 0.069 to 4.83 ; sessions where the mean signal 100 ms after the cue is below its baseline: 15 of 99 in mice ['210863', '214427', '214429', '214430', '219030', '219248']

Result: blocked (methods underspecified). The parameter the paper leaves open sets the value. Under reading (z) the mean across mice is 40.2 to 129.1 ms and rises with the number of s.d.: with the last 0.5 s as baseline, 40.2 to 44.3 ms at 2 s.d., 59.2 to 61.9 ms at 3, 71.5 to 75.5 ms at 4, 76.7 to 83.8 ms at 5 and 92.8 to 106.3 ms at 10. It is within 61.1 +/- 10.5 ms in all 8 rows at 3 s.d. with the last 0.5 s, in 6 of 8 at 2 s.d. and 2 of 8 at 3 s.d. with the delay epoch, in 1 at 4 s.d., and in none at 5 or 10. Under reading (k) no single raw threshold suits every session, whose baselines run from 0.069 to 4.83 a.u.: 36% to 90% of trials have no crossing, and the mean is 77.9 to 166.1 ms, never within tolerance. As the number of s.d. can be chosen to give any mean from about 40 to 130 ms and the paper does not give it, the claim cannot be tested on these data. Where the mean matches (z, last 0.5 s, 3 s.d.), the s.d. across mice is 31.5 to 38.5 ms against the paper's 10.5; mouse 219248 gives 178.9 ms, and the signal falls after the cue in 15 of the 99 sessions, from 6 mice including 219248. Leaving out the light trials changes the mean little (61.0 ms). Reading (z) counts only upward crossings, so in the 15 sessions where the signal falls after the cue the fall is not detected.

Summary¶

Claim Outcome
behaviour-performance reproduced
fig1d-delay-inhibition-ipsi-bias blocked: data missing
fig1d-sample-inhibition-smaller blocked: data missing
units-classification partially reproduced
fig2-selective-fraction reproduced
fig2d-preparatory-perimovement not reproduced
fig2c-selectivity-time-course reproduced
fig2c-population-dynamics reproduced
ed2c-error-trials-predict-choice reproduced
fig2e-no-contra-bias-spikes reproduced
ed1-fast-spiking-properties not reproduced
tagging-counts partially reproduced
antidromic-latencies partially reproduced
fig3e-pt-it-both-activity-types reproduced
fig3f-pt-contra-preferring reproduced
fig3f-pt-contra-selectivity not reproduced
imaging-selectivity-counts not reproduced
fig4f-spatially-intermingled not reproduced
fig4h-pt-contra-imaging blocked: metadata insufficient
reaction-time blocked: methods underspecified
fig5b-pt-delay-activation-contra-bias blocked: data missing
fig5b-pt-sample-activation-contra-bias blocked: data missing
ed6b-pt-dose-response blocked: data missing
ed6d-it-activation-inconsistent blocked: data missing
ed3e-high-power-contra-licking blocked: data missing
ed7-pt-activation-persistent blocked: data missing
ed3-anatomy-it-pt-separate blocked: data missing

A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.

How the analysis differs from the paper's. The behaviour uses the 23 mice of the dandiset, not the paper's 42, and only trials with events; early licks are flagged only in the extracellular files, and trials with tagging light are left out. The electrophysiology claims of Figs 2 and 3 are run with (t1) and without (t2) the trials with tagging light, the two readings the claims file names; for fig3f-pt-contra-preferring only t1 holds, and for fig2c-population-dynamics t1 holds in every reading and t2 in all but two. PT/IT duplicates are counted once. The method details the paper leaves open are run under every reading the claims file names; where readings differ in outcome the notes say which ones match. Open parameters within a reading (the antidromic window, the collision interval and the width of the evoked peak; the reaction-time threshold and number of s.d.) are run at several values. The layer 5 imaging claim is blocked, as no plane gives its depth; all labelled cells are shown as a variant. The inclusion flag of the files stands in for the Methods' brightness rule. Spatial distances are in pixels. Checks added after the first verification (light balance between trial types for fig3f-pt-contra-preferring, imaging baselines near 0, a search of the imaging metadata for a depth) are shown but decide no outcome. After the second verification a change of imaging-selectivity-counts to not reproduced was proposed, as no reading meets its only condition; the third round agreed, and it is not reproduced.

What was found about the dataset. The dandiset holds the paper's 99 extracellular sessions (19 mice) and 59 imaging planes (4 mice, 4,706 included cells), with 1,245 putative pyramidal and 124 fast-spiking units and the 45 PT and 27 IT labels. It holds none of the photoinhibition and photoactivation data (Figures 1 and 5, Extended Data Figures 3, 6 and 7) and no histology. It holds the tagging light of 75 sessions as laser power traces, delivered during behavioural trials that the trial tables do not flag; the traces are labelled watts but hold milliwatts, and the two Tlx x Ai32 tagging mice have none. Every PT and IT unit is deposited twice (as PT or IT and as Pyr). The 39 intermediate-width units are absent. The extracellular trial tables hold 1,540 trials with a lick instruction that carry no events; 1,447 are marked "right", "hit" and "no early" and 1,345 of these hold no spikes. Taken at face value they inflate performance; almost all are in the sessions with tagging light, and they are not the photoactivation trials. The imaging files flag no early licks, give no plane depth, and label the imaging mice "Rosa-lsl-tdtomato" where the paper names C57Bl/6Crl. The lick trace is present but not documented as the reaction time signal, and its level and direction differ between sessions (median baselines 0.069 to 4.83 a.u.; it falls after the go cue in 15 of 99 sessions).