Reproducing Zhai et al. (2025) from dandiset 001538¶
Paper. Zhai S, Cui Q, Wokosin D, Sun L, Tkatch T, Crittenden JR, Graybiel AM, Surmeier DJ (2025). State-dependent modulation of spiny projection neurons controls levodopa-induced dyskinesia in a mouse model of Parkinson's disease. Science Advances 11, eadv8224. doi:10.1126/sciadv.adv8224. This attempt used the full text (open access, PubMed Central PMC12674127).
Dandiset. DANDI:001538, version 0.260527.1302. The paper cites no DANDI version (its data statement points to a key resources table on Zenodo). This is the only published version; the draft holds the same 468 files with the same sizes and differs only in metadata (a link to the journal article among others), which the analysis does not use.
What the dandiset holds. Patch-clamp recordings of somatic excitability and of the dendritic back-propagation assay with two-photon line scans, for dSPNs and iSPNs in the LID off- and on-states and under the drug and genetic manipulations of Figures 1, 3, 6, 7 and 9; GRAB-ACh3.0 imaging (one region per mouse, Figure 5); Sr2+-oEPSC recordings (one cell per mouse, Figures 2 and 4); AIM score tables for 9 mice and rotation videos (Figures 8 and 10); and raw spine image stacks (Figures 2, 4, 6, 7 and 9). It holds no recordings from unlesioned or 6-OHDA-lesioned mice for the patch-clamp figures, no spine counts, no rotation counts and no open-field data.
Outside inputs. The paper's per-cell and per-animal tables (Zenodo
doi:10.5281/zenodo.17307059) are used to test whether the dandiset's recordings are the paper's
cells (the link claims), for the genotype of the AIM mice, and for the claims on data the dandiset
does not hold (marked "elsewhere"). The analysis parameters come from the paper's analysis scripts
on Zenodo (doi:10.5281/zenodo.14145776, 14163502 and 14163731); see lid.py.
No random numbers are drawn anywhere in this notebook.
| Claim | Data | Status |
|---|---|---|
| link-somatic-cells | with inputs | tested |
| link-dendritic-cells | with inputs | tested |
| link-sr-cells | with inputs | tested |
| link-grab-rois | with inputs | tested |
| link-aim-mice | with labels | tested |
| fig1f-dspn-rheobase-on-lower | dandiset | tested |
| fig1e-dspn-fi-on-higher | dandiset | tested |
| fig1f-dspn-sch-no-change | dandiset | tested |
| fig1i-dspn-dendritic-on-higher | dandiset | tested |
| fig1i-dspn-dendritic-sch-lower | dandiset | tested |
| fig3d-ispn-rheobase-on-higher | dandiset | tested |
| fig3c-ispn-fi-on-lower | dandiset | tested |
| fig3d-ispn-sul-no-change | dandiset | tested |
| fig3f-ispn-dendritic-on-lower | dandiset | tested |
| fig3f-ispn-dendritic-sul-no-change | dandiset | tested |
| fig5f-ach-off-elevated | dandiset | tested |
| fig5f-ach-off-like-6ohda | dandiset | tested |
| fig6c-m1r-antag-somatic-unchanged | dandiset | tested |
| fig6d-m1r-antag-dendritic-lower | dandiset | tested |
| fig7c-cdgi-ko-somatic-oscillation | dandiset | tested |
| fig7f-cdgi-ko-dendritic-unchanged | dandiset | tested |
| fig9d-m1r-crispr-somatic-unchanged | dandiset | tested |
| fig2h, fig2i, fig4g, fig4h (Sr2+-oEPSC) | dandiset | blocked: one cell per mouse, 3 against 4 cells; the paper's test cannot reach P < 0.05 |
| fig5f-ach-6ohda-elevated, fig5f-ach-d2-control, fig5f-ach-da-suppresses | dandiset | blocked: 3 mice per group or 3 to 5 pairs; the paper's test cannot reach P < 0.05 |
| fig8bc-cdgi-aims, fig10b-m1r-aims | with labels | blocked: 1 wild-type mouse in Figure 8, 1 mouse per group in Figure 10 |
| fig2bc, fig4bc, fig4k, fig6f, fig7h, fig9f (spine density) | dandiset | blocked: methods underspecified; raw stacks only, no counts stored, and counting used AutoQuant, NeuronStudio or Imaris with manual correction (fig4bc also lacks control and 6-OHDA stacks) |
| fig1-dspn-lesion-and-off-somatic, fig1-dspn-lesion-dendritic-unchanged, fig3-ispn-lesion-and-off-somatic, fig3-ispn-lesion-dendritic-up, fig7e-oxom-cdgi, fig8a-cdgi-rotations, fig10a-m1r-rotations, fig10ef-open-field | elsewhere | tested on the paper's tables |
The numbers for the blocked Sr2+-oEPSC, GRAB-ACh and AIM claims are shown under the link claims.
import time
import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
from scipy.optimize import linear_sum_assignment
from scipy.signal import butter, sosfiltfilt
import lid
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 300)
pd.set_option("display.max_colwidth", 60)
warnings.filterwarnings("ignore", category=RuntimeWarning)
started = time.time()
urls = lid.asset_urls()
patch_items = [(p, u) for p, u in urls if "_icephys" in p and "+ogen" not in p]
sr_items = [(p, u) for p, u in urls if "_icephys+ogen" in p]
grab_items = [(p, u) for p, u in urls if p.endswith("_ophys.nwb")]
aim_items = [(p, u) for p, u in urls if "_ses-pan++ON" in p and not p.endswith("_image.nwb")]
video_items = [(p, u) for p, u in urls if p.endswith("_image.nwb")]
days = lid.load_many(lid.load_patch, patch_items, workers=12)
srs = lid.load_many(lid.load_sr, sr_items, workers=12)
grabs = lid.load_many(lid.load_grab, grab_items, workers=11)
aim = pd.concat(lid.load_many(lid.load_aim, aim_items, workers=12), ignore_index=True)
videos = pd.DataFrame(lid.load_many(lid.load_subject, video_items, workers=12))
tidy = lid.tidy_tables()
som = lid.somatic_table(days)
print(f"{len(urls)} NWB files listed at dandiset {lid.DANDISET_ID} version {lid.DANDISET_VERSION}; loaded "
f"{len(days)} patch-clamp, {len(srs)} Sr2+-oEPSC, {len(grabs)} GRAB-ACh, {len(aim_items)} AIM and "
f"{len(video_items)} video files in {time.time() - started:.0f} s")
print("Paper tables (Zenodo 17307059):", sorted(tidy))
def mw(a, b):
"""Two-sided Mann-Whitney test: n, medians, U, P and the rank-biserial effect (positive when b > a).
P is scipy's default (exact for small samples without ties, otherwise asymptotic); P_exact is
the exact P where there are no ties (NaN otherwise), as Prism reports for small samples."""
a, b = np.asarray(a, float), np.asarray(b, float)
a, b = a[~np.isnan(a)], b[~np.isnan(b)]
u, p = stats.mannwhitneyu(a, b, alternative="two-sided")
ties = len(np.unique(np.concatenate([a, b]))) < len(a) + len(b)
p_exact = np.nan if ties else stats.mannwhitneyu(a, b, alternative="two-sided", method="exact").pvalue
return {"n_a": len(a), "n_b": len(b), "median_a": np.median(a), "median_b": np.median(b),
"U": u, "P": p, "P_exact": p_exact, "effect_r": 1 - 2 * u / (len(a) * len(b))}
def show(d):
"""Rounded for printing; P to three significant digits."""
out = {}
for k, v in d.items():
if isinstance(v, (float, np.floating)):
out[k] = float(f"{v:.3g}") if k in ("P", "P_exact") else round(float(v), 4)
else:
out[k] = int(v) if isinstance(v, np.integer) else v
return out
226 NWB files listed at dandiset 001538 version 0.260527.1302; loaded 58 patch-clamp, 14 Sr2+-oEPSC, 11 GRAB-ACh, 45 AIM and 12 video files in 369 s Paper tables (Zenodo 17307059): ['Fig 1', 'Fig 10', 'Fig 2', 'Fig 3', 'Fig 4', 'Fig 5', 'Fig 6', 'Fig 7', 'Fig 8', 'Fig 9', 'SupFigs']
Data check¶
Each check of the description against the values is stated under its output.
def kind(p):
if "_icephys+ogen" in p: return "Sr2+-oEPSC"
if "_icephys" in p: return "somatic/dendritic patch"
if p.endswith("_ophys.nwb"): return "GRAB-ACh imaging"
if p.endswith("_image.nwb"): return "behaviour video"
if "_ses-pan++ON" in p: return "AIM scores"
if "confocal" in p: return "confocal spine stacks"
return "2PLSM spine stacks"
inv = pd.DataFrame([{"kind": kind(p), **lid.parse_session(p)} for p, _ in urls])
print("NWB files by kind and session-name fields:")
print(inv.groupby(["kind", "cell_type", "state", "drug", "genotype"]).size().to_string())
print("Subject folders in the archive:", len({p.split("/")[0] for p, _ in urls}))
NWB files by kind and session-name fields:
kind cell_type state drug genotype
dSPN OffState none WT 19
OnState none WT 9
iSPN CTRL none WT 3
OFF none WT 3
ON none WT 2
OffState M1RaThp WT 5
none CDGIKO 6
WT 25
iSPN-M1RKO 3
OnState none CDGIKO 3
WT 5
PD none WT 3
pan ON none M1RCRISPR 5
WT 40
icephys dSPN OffState none WT 1
iSPN Control none M1RCRISPR 2
M1RAntag none WT 2
M1RCRISPR none M1RCRISPR 3
icephys+ogen dSPN OffState none WT 3
OnState none WT 4
iSPN OffState none WT 3
OnState none WT 4
icephys+ophys dSPN OffState none WT 7
OnState D1RaSch WT 5
none WT 5
iSPN Control none WT 5
M1RAntag none WT 4
OffState none CDGIKO 5
WT 5
OnState D2RaSul WT 6
none CDGIKO 4
WT 4
image pan ON none CDGIKO 7
M1RCRISPR 5
ophys pan CTRL none WT 3
OFF none WT 5
PD none WT 3
Subject folders in the archive: 185
The session names give cell type, state, drug and genotype. The dandiset holds patch-clamp files for the LID off- and on-states, the drug and genetic manipulations, the GRAB-ACh imaging (3 unlesioned, 3 6-OHDA and 5 off-state mice), Sr2+-oEPSC cells, AIM tables, behaviour videos and spine image stacks. There are no patch-clamp files for unlesioned control or 6-OHDA-lesioned mice (the "control" groups of Figures 1 and 3), and no open-field data (Figure 10D to F).
print("Somatic cells by group (condition_subfolder) against the paper's per-cell tables:")
rows = []
for g, spec in lid.TIDY_SOMATIC.items():
s = som[som.group == g]
rows.append({"group": g, "cells": len(s), "mice (dates)": s.date.nunique(),
"paper cells": len(lid.tidy_group(tidy, spec)),
"session drug labels": sorted(s.session_drug.unique())})
print(pd.DataFrame(rows).to_string(index=False))
print("\nCells with no group:", int((som.group == "?").sum()))
print("Cells whose session name says a drug but whose condition is baseline:")
base_in_drug = som[(som.session_drug != "none") & ~som.condition.str.contains("with")][["cell_type", "date", "cell", "session_drug", "condition"]]
with_drug = set(map(tuple, som[som.condition.str.contains("with")][["cell_type", "date", "cell"]].to_numpy()))
base_in_drug = base_in_drug.assign(also_with_drug=[tuple(r) in with_drug for r in base_in_drug[["cell_type", "date", "cell"]].to_numpy()])
print(base_in_drug.to_string(index=False))
print(f"Of these {len(base_in_drug)}, recorded also with the drug: {int(base_in_drug.also_with_drug.sum())}; baseline only: {int((~base_in_drug.also_with_drug).sum())}")
print("\nSweeps per cell and condition:", som.n_sweeps.value_counts().sort_index().to_dict())
print("Cells with a repeated step:", int((som.duplicates > 0).sum()),
"; cells missing a step between 20 and 300 pA:", int(som[list(range(20, 301, 20))].isna().any(axis=1).sum()))
print(som[som[list(range(20, 301, 20))].isna().any(axis=1)][["group", "date", "cell"] + [a for a in range(20, 301, 20) if som[a].isna().any()]].to_string(index=False))
print(f"\nResting potential before the step (mV): {som.vrest_mV.min():.1f} to {som.vrest_mV.max():.1f}, median {som.vrest_mV.median():.1f}")
print(f"Largest difference between the stored stimulus and stimulus_current_pA (pA): {som.stim_error_pA.max():.3g}")
print(f"Action potentials inside the 0.2-0.7 s step: {som.spikes_in_step.sum()}; outside it: {som.spikes_outside_step.sum()} "
f"(cells with any outside: {(som.spikes_outside_step > 0).sum()})")
Somatic cells by group (condition_subfolder) against the paper's per-cell tables:
group cells mice (dates) paper cells session drug labels
dSPN off 15 8 15 [none]
dSPN on 15 10 15 [D1RaSch, none]
dSPN on+SCH 10 5 10 [D1RaSch]
iSPN off 12 5 12 [none]
iSPN on 11 8 11 [D2RaSul, none]
iSPN on+sul 7 4 7 [D2RaSul]
Fig6 vehicle 8 4 8 [none]
Fig6 M1R antagonist 10 5 10 [none]
KO off 13 5 13 [none]
KO on 8 4 8 [none]
Fig9 control 6 2 6 [none]
Fig9 M1R CRISPR 8 3 8 [none]
Cells with no group: 0
Cells whose session name says a drug but whose condition is baseline:
cell_type date cell session_drug condition also_with_drug
iSPN 20190408 1 D2RaSul LID on-state False
iSPN 20190409 1 D2RaSul LID on-state False
dSPN 20190411 1 D1RaSch LID on-state True
dSPN 20190412 1 D1RaSch LID on-state True
iSPN 20190426 2 D2RaSul LID on-state True
iSPN 20190429 1 D2RaSul LID on-state True
dSPN 20190501 1 D1RaSch LID on-state True
dSPN 20190503 1 D1RaSch LID on-state True
dSPN 20190507 1 D1RaSch LID on-state False
dSPN 20190507 2 D1RaSch LID on-state True
iSPN 20190508 2 D2RaSul LID on-state False
Of these 11, recorded also with the drug: 7; baseline only: 4
Sweeps per cell and condition: {16: 1, 20: 4, 21: 109, 24: 1, 26: 8}
Cells with a repeated step: 14 ; cells missing a step between 20 and 300 pA: 18
group date cell 80 100 120 160 180 200 220 240 260 280 300
iSPN off 20160524 4 0.0 0.0 4.0 10.0 12.0 NaN 15.0 16.0 16.0 17.0 17.0
KO off 20160527 3 0.0 0.0 0.0 0.0 3.0 7.0 NaN 12.0 15.0 17.0 19.0
iSPN on 20160609 2 0.0 0.0 1.0 7.0 9.0 11.0 12.0 14.0 15.0 NaN 17.0
KO on 20160913 2 0.0 NaN 0.0 0.0 0.0 0.0 0.0 2.0 4.0 7.0 10.0
dSPN off 20161116 3 NaN 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
dSPN off 20170202 1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 NaN 6.0
dSPN off 20170202 3 0.0 0.0 0.0 0.0 0.0 3.0 5.0 NaN 10.0 11.0 13.0
dSPN off 20170203 3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 NaN 0.0 3.0 5.0
dSPN off 20170204 1 0.0 0.0 0.0 NaN 6.0 9.0 11.0 13.0 15.0 16.0 17.0
KO off 20170616 1 0.0 0.0 0.0 4.0 8.0 NaN 13.0 15.0 18.0 20.0 21.0
dSPN on 20170707 2 8.0 NaN 13.0 16.0 16.0 16.0 NaN NaN NaN NaN NaN
dSPN on 20170720 2 0.0 0.0 0.0 0.0 1.0 1.0 4.0 7.0 9.0 11.0 NaN
iSPN on+sul 20190426 2 0.0 0.0 2.0 6.0 NaN 9.0 11.0 12.0 13.0 13.0 13.0
dSPN on 20190507 1 0.0 0.0 NaN 3.0 5.0 7.0 10.0 11.0 13.0 15.0 NaN
Fig6 vehicle 20200217 1 5.0 NaN NaN 16.0 17.0 18.0 19.0 20.0 21.0 20.0 20.0
Fig6 vehicle 20200225 1 0.0 NaN 1.0 11.0 16.0 14.0 16.0 18.0 20.0 21.0 22.0
Fig6 M1R antagonist 20200312 1 4.0 6.0 7.0 11.0 12.0 13.0 9.0 10.0 11.0 10.0 NaN
Fig6 vehicle 20200812 2 1.0 6.0 9.0 14.0 17.0 18.0 19.0 20.0 20.0 16.0 NaN
Resting potential before the step (mV): -93.9 to -71.2, median -85.0
Largest difference between the stored stimulus and stimulus_current_pA (pA): 1.53e-05
Action potentials inside the 0.2-0.7 s step: 11782; outside it: 34 (cells with any outside: 28)
Somatic sweeps are stored in volts at 10 kHz; resting potentials of -93 to -71 mV confirm the unit.
The stored stimulus of each somatic sweep equals the step amplitude in the index table, and the
step runs from 0.2 to 0.7 s (500 ms, as in the legends of Figures 3B and 6B). Nearly all action
potentials fall inside the step, so the voltage and the stimulus share a clock. The group of a cell
has to be taken from condition_subfolder: files named for SCH 23390 or sulpiride also hold
baseline (on-state) cells, and some cells were recorded in both conditions. Counted this way, every
group present matches the paper's n.
den_sweeps = pd.DataFrame([{"date": d["date"], "cell_type": d["cell_type"], "cell": r["cell"], "condition": r["condition"],
"compartment": r["compartment"], "n_spikes": len(r["spike_times"]),
"first_spike_s": r["spike_times"][0] if len(r["spike_times"]) else np.nan,
"stim_max_abs_A": r["stim_max_abs_A"], "ls_ok": r["ls1_ok"] and r["ls2_ok"],
"rate": r["ch2_rate"], "n_samples": len(r["ch2"]), "md5": r["md5"]}
for d in days for r in d["dendritic"]])
print("Dendritic sweeps:", len(den_sweeps), "from", den_sweeps.groupby(["date", "cell"]).ngroups, "cells")
print("Spikes per sweep:", den_sweeps.n_spikes.value_counts().sort_index().to_dict())
print("First spike (s): median %.4f, range %.4f to %.4f" % (den_sweeps.first_spike_s.median(), den_sweeps.first_spike_s.min(), den_sweeps.first_spike_s.max()))
print("Largest absolute value of the stored dendritic stimulus (A):", den_sweeps.stim_max_abs_A.max())
print("Line-scan rate (Hz): %.1f to %.1f; samples per sweep: %s" % (den_sweeps.rate.min(), den_sweeps.rate.max(), den_sweeps.n_samples.value_counts().to_dict()))
print("Sweeps with no line-scan kymograph slice in the index table (reference -1 or past the data):", int((~den_sweeps.ls_ok).sum()), "of", len(den_sweeps))
sites = []
for d in days:
if d["dendritic"]:
s = lid.dendritic_sites(d)
s["date"], s["cell_type"] = d["date"], d["cell_type"]
s["group"] = [lid.GROUPS.get((d["cell_type"], c), "?") for c in s.condition]
sites.append(s)
sites = pd.concat(sites, ignore_index=True)
sites_lab = pd.concat([lid.dendritic_sites(d, split_merged=False).assign(date=d["date"], cell_type=d["cell_type"]) for d in days if d["dendritic"]], ignore_index=True)
print("Sites (series) with other than 3 trials, as labelled:")
print(sites_lab[sites_lab.n_trials != 3][["date", "cell_type", "cell", "condition", "series", "n_trials"]].to_string(index=False))
print("The 6-trial series split into their first and last three trials (the half with the smaller area taken as distal):")
print(sites[sites.split][["date", "cell_type", "cell", "series", "compartment", "n_trials", "R0", "G0", "area"]].round(4).to_string(index=False))
print("\nSites:", len(sites), "; trials per site:", sites.n_trials.value_counts().sort_index().to_dict())
print("Area of dG/R0 from 0.95 to 1.5 s (after the spikes): median %.4f; the same window 1.5 s later: median %.4f; "
"sites where the first is larger: %d of %d" % (sites.area.median(), sites.late_area.median(),
(sites.area > sites.late_area).sum(), len(sites)))
print("R0 (red, background subtracted) range %.0f to %.0f; G0 range %.0f to %.0f" % (sites.R0.min(), sites.R0.max(), sites.G0.min(), sites.G0.max()))
Dendritic sweeps: 997 from 91 cells
Spikes per sweep: {0: 55, 1: 12, 2: 24, 3: 905, 4: 1}
First spike (s): median 1.0013, range 1.0004 to 1.0423
Largest absolute value of the stored dendritic stimulus (A): 0.0
Line-scan rate (Hz): 678.2 to 692.9; samples per sweep: {2500: 997}
Sweeps with no line-scan kymograph slice in the index table (reference -1 or past the data): 53 of 997
Sites (series) with other than 3 trials, as labelled:
date cell_type cell condition series n_trials
20160524 iSPN 2 LID off-state RoiResponseSeriesFluo4Cell2Proximal1 1
20160526 iSPN 3 KO off-state RoiResponseSeriesFluo4Cell3Distal1 6
20160608 iSPN 1 LID on-state RoiResponseSeriesFluo4Cell1Proximal1 6
20170204 dSPN 1 LID off-state RoiResponseSeriesFluo4Cell1Proximal1 6
20170707 dSPN 1 LID on-state RoiResponseSeriesFluo4Cell1Distal2 2
20240430 iSPN 2 M1R antagonist RoiResponseSeriesFluo4Cell2Distal2 4
The 6-trial series split into their first and last three trials (the half with the smaller area taken as distal):
date cell_type cell series compartment n_trials R0 G0 area
20160526 iSPN 3 RoiResponseSeriesFluo4Cell3Distal1 Distal 3 2786.8701 185.7470 0.0081
20160526 iSPN 3 RoiResponseSeriesFluo4Cell3Distal1 Proximal 3 2905.8514 212.7647 0.0277
20160608 iSPN 1 RoiResponseSeriesFluo4Cell1Proximal1 Distal 3 2775.3280 440.6128 0.0031
20160608 iSPN 1 RoiResponseSeriesFluo4Cell1Proximal1 Proximal 3 2761.1671 333.3551 0.0215
20170204 dSPN 1 RoiResponseSeriesFluo4Cell1Proximal1 Distal 3 2892.4742 212.0222 0.0166
20170204 dSPN 1 RoiResponseSeriesFluo4Cell1Proximal1 Proximal 3 3294.2187 207.9278 0.0656
Sites: 333 ; trials per site: {1: 1, 2: 1, 3: 330, 4: 1}
Area of dG/R0 from 0.95 to 1.5 s (after the spikes): median 0.0253; the same window 1.5 s later: median 0.0010; sites where the first is larger: 331 of 333
R0 (red, background subtracted) range 640 to 3597; G0 range 71 to 956
The dendritic assay evokes three spikes, the first at 1.00 s, and the Ca2+ signal rises after it: the area in the analysis window is larger than in the same window 1.5 s later at nearly every site, so the line scans and the voltage share the sweep clock. The stored dendritic stimulus is zero in every sweep, so the injected current is not in the files; the three spikes show it was delivered. The line-scan rate (678 to 693 Hz, 2500 lines per sweep) is the rate of the region series used here. Some sweeps have no kymograph slice in the index table (count above; the kymographs hold fewer trials than the region traces); the analysis uses the region traces (mean along the scan line), which have a slice for every sweep.
Six site series do not hold the usual three trials. Three hold six trials under one name while the
cell has no series of the other compartment with that number (iSPN 20160526 cell 3 Distal1, iSPN
20160608 cell 1 Proximal1, dSPN 20170204 cell 1 Proximal1). In each, the first and last three
trials differ in baseline (G0 or R0) and their areas differ 3.4- to 6.9-fold, so each series holds
two sites. The analysis splits them and takes the half with the smaller area as the distal site.
This gives the three cells an index of 0.293, 0.146 and 0.327, against the paper's 0.299, 0.146
and 0.309 (link section). The sites as labelled are shown as a variant. One series holds a single
trial (iSPN 20160524 cell 2 Proximal1), one two and one four.
sr_rows = []
for s in srs:
led = [w["led_trace_nonzero"] for w in s["sweeps"]]
on = [w["led_onset_s"] for w in s["sweeps"] if w["led_trace_nonzero"]]
pid = (s["date"][4:8] + s["date"][:4] if s["cell_type"] == "dSPN" else s["date"]) + s["session_letter"][0]
sr_rows.append({"cell_type": s["cell_type"], "state": s["state"], "date": s["date"], "paper_id": pid,
"cell_number": s["cell_number"], "letter": s["session_letter"], "led_setting": s["led"],
"sweeps": len(s["sweeps"]), "n_vc": s["n_vc"], "n_og": s["n_og"],
"rate_kHz": sorted({round(w["rate"] / 1000) for w in s["sweeps"]}),
"sweeps_with_LED_trace": int(sum(led)),
"LED_onset_s": round(float(np.median(on)), 4) if on else np.nan, "epoch_stim_s": s["epoch_stim_s"],
"epoch_detect_s": s["epoch_detect"]})
sr_tab = pd.DataFrame(sr_rows)
print(sr_tab.to_string(index=False))
# Clock check: events in the detection window (40-400 ms after the pulse) against a window of the same
# length 560-920 ms after it, with the same detector.
ev_rows = []
for s in srs:
for w in s["sweeps"]:
onset = w["led_onset_s"] if w["led_trace_nonzero"] else s["epoch_stim_s"]
e1 = lid.detect_events(w["current"], w["rate"], onset)
e2 = lid.detect_events(w["current"], w["rate"], onset, window=(0.56, 0.92))
ev_rows.append({"date": s["date"], "cell_type": s["cell_type"], "events_40_400": len(e1), "events_560_920": len(e2)})
ev = pd.DataFrame(ev_rows)
print("\nEvents per sweep, 40-400 ms after the pulse: %.2f; 560-920 ms after it: %.2f; cells with more in the first window: %d of %d"
% (ev.events_40_400.mean(), ev.events_560_920.mean(),
(ev.groupby("date").events_40_400.sum() > ev.groupby("date").events_560_920.sum()).sum(), ev.date.nunique()))
cell_type state date paper_id cell_number letter led_setting sweeps n_vc n_og rate_kHz sweeps_with_LED_trace LED_onset_s epoch_stim_s epoch_detect_s
dSPN OffState 20230706 07062023c [3] [c] [100b] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
dSPN OffState 20230713 07132023d [4] [d] [8] 10 10 10 [10] 4 0.019 0.02 (0.06, 0.42000000000000004)
dSPN OffState 20230817 08172023d [4] [d] [30] 10 10 10 [10] 10 0.020 0.02 (0.06, 0.42000000000000004)
dSPN OnState 20230705 07052023b [2] [b] [12] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
dSPN OnState 20230710 07102023b [2] [b] [12] 10 10 10 [10] 9 0.019 0.02 (0.06, 0.42000000000000004)
dSPN OnState 20230802 08022023b [2] [b] [12] 10 10 10 [10] 4 0.019 0.02 (0.06, 0.42000000000000004)
dSPN OnState 20230816 08162023b [2] [b] [10] 10 10 10 [10] 6 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OffState 20230222 20230222b [2] [b] [14] 10 10 10 [20] 8 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OffState 20230330 20230330b [2] [b] [100] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OffState 20230406 20230406c [3] [c] [8] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OnState 20230228 20230228b [2] [b] [16] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OnState 20230301 20230301b [2] [b] [4_real] 10 10 10 [20] 3 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OnState 20230331 20230331b [2] [b] [8] 10 10 10 [20] 3 0.019 0.02 (0.06, 0.42000000000000004)
iSPN OnState 20230405 20230405b [2] [b] [7] 10 10 10 [20] 4 0.019 0.02 (0.06, 0.42000000000000004)
Events per sweep, 40-400 ms after the pulse: 4.41; 560-920 ms after it: 1.37; cells with more in the first window: 13 of 14
Each Sr2+-oEPSC file holds one cell (10 sweeps of 1 s), recorded at 10 or 20 kHz. The light pulse is stored as an OptogeneticSeries per sweep, but in many sweeps that trace is all zero; where it is not, the pulse starts at 19 to 20 ms, as the epochs table states (stimulation at 0.02 s, detection from 0.06 to 0.42 s). Sweeps without a stored pulse use the epochs table's time. The intracellular recordings table does not reference the stimulus (its stimulus rows are empty). Events are more frequent after the pulse than late in the sweep, consistent with asynchronous release evoked by the light, so the pulse time and the current share a clock. Each file's cell number and session letter give an identifier of the form the paper uses (date and letter).
g_rows = []
for m in grabs:
gm = lid.grab_measures(m)
sp = gm[gm.stimulation == "single_pulse"]
g_rows.append({"state": m["state"], "date": m["date"], "regions": m["n_regions"], "frame_rate": m["frame_rate_2p"],
"treatments": sorted(sp.treatment), "reps": sp.n_reps.tolist(), "stim_rel_s": sorted(set(sp.stim_rel_s.round(2))),
"F0_range": (round(sp.F0.min()), round(sp.F0.max())), "Fmax": round(sp.Fmax.iloc[0]), "Fmin_TTX": round(sp.Fmin_ttx.iloc[0]),
"bg_reference_mean": round(m["bg_reference"]),
"auc_after_stim": round(float(sp[sp.treatment == "control"].auc_norm.iloc[0]), 3),
"auc_before_stim": round(float(sp[sp.treatment == "control"].auc_norm_pre.iloc[0]), 3)})
g_tab = pd.DataFrame(g_rows)
print(g_tab.to_string(index=False))
state date regions frame_rate treatments reps stim_rel_s F0_range Fmax Fmin_TTX bg_reference_mean auc_after_stim auc_before_stim CTRL 20240402 1 [5.009] [control, quinpirole, sulpiride] [2, 2, 3] [10.0] (214, 218) 493 221 389 0.112 -0.001 CTRL 20240405 1 [5.009] [control, quinpirole, sulpiride] [2, 2, 2] [10.0] (215, 245) 366 244 399 0.113 0.009 CTRL 20240410 1 [5.009] [control, quinpirole, sulpiride] [2, 2, 2] [10.0] (246, 264) 341 262 436 0.089 0.007 PD 20240412 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [10.0] (196, 208) 287 220 388 0.240 0.016 PD 20240416 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [10.0] (179, 206) 261 249 381 0.269 0.011 PD 20240509 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [3.0] (249, 289) 585 275 409 0.179 0.001 OFF 20240524 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [3, 2, 2, 2] [3.0] (267, 323) 395 318 485 0.193 0.029 OFF 20240529 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 3, 2] [3.0] (287, 320) 410 309 459 0.359 0.015 OFF 20240531 1 [5.009, 21.261] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [3.0] (276, 346) 367 319 460 0.296 0.008 OFF 20240603 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [3.0] (271, 345) 387 344 458 0.401 0.064 OFF 20240604 1 [5.009] [50nM_dopamine, control, quinpirole, sulpiride] [2, 2, 2, 2] [3.0] (326, 355) 418 329 525 0.723 0.019
Each GRAB-ACh file holds one region of one slice per mouse, with two (sometimes three) repetitions per treatment and stimulation, at 5.01 frames per second as the Methods state. The stimulus time in the trial table is 10 s after the trial start in five files and 3 s in six; the analysis uses the table's time, where the paper's script fixes 10 s. Without drug, the normalised area after the stimulus is several times the area of the 2 s before it in every mouse, so the trial table's stimulus time and the traces share a clock. The background reference images are described as taken at zero laser power, but their mean (381 to 525) is above the signal itself (F0 179 to 355), so they cannot be the PMT background; the normalised area does not depend on a constant background, so this does not affect Figure 5F.
aim["animal_id"] = aim.animal_id.astype(int)
print("AIM rows by animal, the table's genotype column, the subject genotype and the session name's genotype:")
aim["session_genotype"] = [lid.parse_session(p)["genotype"] for p in aim.path]
print(aim.groupby(["animal_id", "genotype", "subject_genotype", "session_genotype"]).agg(
rows=("total_score", "size"), sessions=("session_number", lambda x: sorted(set(x)))).to_string())
print("\nTime points (min after levodopa):", aim.time_minutes.value_counts().sort_index().to_dict())
for c in ["axial_score", "limb_score", "orolingual_score", "total_score"]:
print(f"{c}: {aim[c].min()} to {aim[c].max()}")
bad = aim[~np.isclose(aim.total_score, aim.axial_score + aim.limb_score + aim.orolingual_score)]
print("Rows where total differs from axial + limb + orolingual:", len(bad), "of", len(aim))
print(bad[["animal_id", "session_number", "time_minutes", "axial_score", "limb_score", "orolingual_score", "total_score"]].to_string(index=False))
videos["animal"] = videos.subject_id.str.extract(r"(\d+)$")[0]
print("\nBehaviour video files (rotation videos; no counts): subject genotype by animal")
print(videos[["animal", "genotype", "subject_id"]].to_string(index=False))
AIM rows by animal, the table's genotype column, the subject genotype and the session name's genotype:
rows sessions
animal_id genotype subject_genotype session_genotype
1945 CDGI Knockout Wild-type CDGI WT 8 [1, 2]
unknown Wild-type CDGI WT 18 [3, 4, 5]
2519 CDGI Knockout Wild-type CDGI WT 4 [1]
unknown Wild-type CDGI WT 20 [2, 3, 4, 5]
2587 Wild Type Wild-type CDGI WT 4 [1]
unknown Wild-type CDGI WT 21 [2, 3, 4, 5]
2828 CDGI Knockout Wild-type CDGI WT 25 [1, 2, 3, 4, 5]
4124 unknown Wild-type CDGI WT 29 [1, 2, 3, 4, 5]
5844 Control Control WT 27 [1, 2, 3, 4, 5]
5846 M1R CRISPR M1R CRISPR knockout M1RCRISPR 25 [1, 2, 3, 4, 5]
5871 Wild Type Wild-type CDGI WT 28 [1, 2, 3, 4, 5]
6572 CDGI Knockout Wild-type CDGI WT 25 [1, 2, 3, 4, 5]
Time points (min after levodopa): {20: 45, 40: 45, 60: 45, 80: 45, 100: 40, 120: 14}
axial_score: 0.0 to 4.0
limb_score: 0.0 to 4.0
orolingual_score: 0.0 to 4.0
total_score: 0.0 to 12.0
Rows where total differs from axial + limb + orolingual: 1 of 234
animal_id session_number time_minutes axial_score limb_score orolingual_score total_score
5844 3 120 NaN 0.0 NaN 0.0
Behaviour video files (rotation videos; no counts): subject genotype by animal
animal genotype subject_id
0549 CDGI KO CDGI_mouse_0549
07272018 CDGI KO CDGI_mouse_07272018
1945 CDGI KO CDGI_mouse_1945
2519 CDGI KO CDGI_mouse_2519
2587 CDGI KO CDGI_mouse_2587
2828 CDGI KO CDGI_mouse_2828
6572 CDGI KO CDGI_mouse_6572
5846 M1R CRISPR M1R_CRISPR_mouse_5846
7831 M1R CRISPR M1R_CRISPR_mouse_7831
8240 M1R CRISPR M1R_CRISPR_mouse_8240
8594 M1R CRISPR M1R_CRISPR_mouse_8594
9368 M1R CRISPR M1R_CRISPR_mouse_9368
AIM scores are stored per time point (0 to 4 for each of axial, limb and orolingual, every 20 min), and the total is their sum. The genotype labels contradict each other: in the CDGI study every subject is "Wild-type CDGI" and every session name says WT, while the table's genotype column says "CDGI Knockout" for some sessions, "Wild Type" or "unknown" for others, changing within an animal, and the behaviour video files of the same animals (1945, 2519, 2587, 2828, 6572) say "CDGI KO". The genotype of these mice cannot be read from the dandiset. The videos carry no rotation counts.
dups = som[som.duplicated("md5", keep=False)]
print("Somatic cells whose voltage traces are identical to another cell's:", len(dups))
print(dups[["group", "date", "cell", "condition", "md5"]].to_string(index=False) if len(dups) else "")
same_fi = som[som.duplicated(list(range(20, 301, 20)), keep=False)].sort_values(list(range(20, 301, 20)))
print("\nCells with identical spike counts at every step (not identical traces):")
print(same_fi[["group", "date", "cell", "md5"]].to_string(index=False))
dd = den_sweeps[den_sweeps.duplicated("md5", keep=False)]
print("\nDendritic voltage sweeps identical to another sweep:", len(dd))
sr_md5 = pd.Series([w["md5"] for s in srs for w in s["sweeps"]])
print("Sr2+-oEPSC sweeps identical to another sweep:", int(sr_md5.duplicated(keep=False).sum()))
aim_keys = aim.groupby(["animal_id", "session_number"]).total_score.apply(tuple)
print("AIM sessions with the same score sequence as another animal's session:",
int(aim_keys.reset_index().assign(a=lambda x: x.total_score.astype(str)).duplicated("a", keep=False).sum()))
Somatic cells whose voltage traces are identical to another cell's: 0 Cells with identical spike counts at every step (not identical traces): group date cell md5 iSPN on 20160609 1 02aaaa246e2a80369d4f89e73603afea KO on 20160913 1 b2f84eb482a27e598dab37dfd8dfe8ac Dendritic voltage sweeps identical to another sweep: 0 Sr2+-oEPSC sweeps identical to another sweep: 0 AIM sessions with the same score sequence as another animal's session: 0
No recording is deposited twice: no two somatic cells, dendritic sweeps or Sr2+-oEPSC sweeps have identical traces. An iSPN on-state cell (20160609, cell 1) and a CDGI KO on-state cell (20160913, cell 1) have identical spike counts at every step but different traces, so they are different cells. No two AIM sessions have the same score sequence.
spine_items = [(p, u) for p, u in urls if kind(p) in ("confocal spine stacks", "2PLSM spine stacks")]
spines = pd.DataFrame(lid.load_many(lid.file_contents, spine_items, workers=12))
spines["kind"] = [kind(p) for p in spines.path]
spines["state"] = [p.split("_ses-")[1].split("++")[1] if "++" in p else "" for p in spines.path]
print("Spine image files by kind and state:")
print(spines.groupby(["kind", "state"]).agg(files=("path", "size"), image_objects=("acquisition_objects", "sum"), images=("images", "sum")).to_string())
print("Neurodata types in acquisition:", sorted({t for ts in spines.acquisition_types for t in ts}))
k4 = tidy["Fig 4"]["Fig 4K_confocal spine density"]
print("Paper's Figure 4K table: dendrites by group", k4.iloc[:, 1].value_counts().to_dict(), "; total", len(k4))
print("Files with a processing module:", int((spines.processing.str.len() > 0).sum()), "; with an analysis entry:", int((spines.analysis.str.len() > 0).sum()), "of", len(spines))
Spine image files by kind and state:
files image_objects images
kind state
2PLSM spine stacks OffState 58 192 19168
OnState 17 48 4982
confocal spine stacks CTRL 3 11 473
OFF 3 3 193
ON 2 2 129
PD 3 3 157
Neurodata types in acquisition: ['Images']
Paper's Figure 4K table: dendrites by group {'LID off': 25, '6-OHDA': 22, 'LID on': 22, 'Control': 18} ; total 87
Files with a processing module: 0 ; with an analysis entry: 0 of 86
The 86 spine files hold only image stacks (Images objects in acquisition): 75 two-photon files of
off- and on-state dendrites and 11 confocal files of unlesioned, 6-OHDA, off- and on-state dendrites.
None has a processing module or an analysis entry, so no spine counts, reconstructions or
annotations are stored.
Links to the paper's per-cell tables¶
The paper deposited per-cell and per-animal tables (Zenodo doi:10.5281/zenodo.17307059). These sections test whether the dandiset's recordings are the paper's cells; the claims on the dandiset below use only the dandiset.
link-somatic-cells¶
The dandiset's somatic current-step recordings are those behind the paper's somatic excitability figures, and the spike counts computed from them agree with the paper's per-cell tables.
amps = list(range(20, 301, 20))
link_rows, costs = [], {}
for g, spec in lid.TIDY_SOMATIC.items():
paper = lid.tidy_fi(lid.tidy_group(tidy, spec))
P = paper[amps].to_numpy(float)
prheo = [next((a for a, v in zip(amps, r) if v >= 1), np.nan) for r in P]
costs[g] = []
for _, c in som[som.group == g].sort_values(["date", "cell"]).iterrows():
v = c[amps].to_numpy(float)
ok = ~np.isnan(v)
dist = np.abs(P[:, ok] - v[ok]).sum(axis=1)
costs[g].append(dist)
j = int(np.argmin(dist))
vl = np.asarray(c.counts_last, float)
dist_last = np.abs(P[:, ok] - vl[ok]).sum(axis=1).min() if c.duplicates else np.nan
link_rows.append({"group": g, "date": c.date, "cell": c.cell, "best_row": paper["cell id"].iloc[j], "best_pos": j,
"spike_diff": dist[j], "exact": dist[j] == 0, "n_exact_rows": int((dist == 0).sum()),
"rheobase": c.rheobase, "paper_rheobase": prheo[j], "duplicates": c.duplicates, "spike_diff_last": dist_last})
link_som = pd.DataFrame(link_rows)
summary = link_som.groupby("group").agg(cells=("cell", "size"), exact=("exact", "sum"),
within3=("spike_diff", lambda x: int((x <= 3).sum())),
rheobase_equal=("rheobase", "size"))
summary["rheobase_equal"] = link_som.assign(eq=link_som.rheobase == link_som.paper_rheobase).groupby("group").eq.sum()
summary["paper_n"] = [len(lid.tidy_group(tidy, lid.TIDY_SOMATIC[g])) for g in summary.index]
print(summary.to_string())
n = len(link_som)
print(f"\nAll groups: {n} cells; exact match {link_som.exact.sum()} ({link_som.exact.mean():.1%}); "
f"best match within 3 spikes {(link_som.spike_diff <= 3).sum()} ({(link_som.spike_diff <= 3).mean():.1%}); "
f"rheobase equal to the best match {(link_som.rheobase == link_som.paper_rheobase).sum()}")
print("Groups whose cell count equals the paper's n:", int((summary.cells == summary.paper_n).sum()), "of", len(summary))
print("\nCells without an exact match:")
print(link_som[~link_som.exact].drop(columns=["best_pos"]).to_string(index=False))
print("\nOrder: position of each cell's best-match row among the paper's rows of its group, cells in the dandiset's date and cell order")
for g, x in link_som.groupby("group", sort=False):
pos = x.best_pos.tolist()
print(f" {g}: {pos}; rising steps {sum(b > a for a, b in zip(pos, pos[1:]))} of {len(pos) - 1}; "
f"position equal to the cell's rank {sum(p == i for i, p in enumerate(pos))} of {len(pos)}")
print("Paper rows used as best match by more than one cell:", link_som.groupby(["group", "best_row"]).size().loc[lambda s: s > 1].to_dict())
d1 = np.concatenate([np.array(c)[linear_sum_assignment(np.array(c))] for c in costs.values()])
print(f"One-to-one assignment (each paper row used once): exact {int((d1 == 0).sum())} ({(d1 == 0).mean():.1%}), "
f"within 3 spikes {int((d1 <= 3).sum())} ({(d1 <= 3).mean():.1%}) of {len(d1)}")
dup = link_som[link_som.duplicates > 0]
print(f"\nCells with a repeated step ({len(dup)}): best-match difference keeping the first sweep against keeping the last; "
f"first closer {int((dup.spike_diff < dup.spike_diff_last).sum())}, last closer {int((dup.spike_diff > dup.spike_diff_last).sum())}, equal {int((dup.spike_diff == dup.spike_diff_last).sum())}")
print(dup[["group", "date", "cell", "duplicates", "spike_diff", "spike_diff_last"]].to_string(index=False))
cells exact within3 rheobase_equal paper_n
group
Fig6 M1R antagonist 10 8 9 9 10
Fig6 vehicle 8 6 7 8 8
Fig9 M1R CRISPR 8 7 7 8 8
Fig9 control 6 5 5 6 6
KO off 13 9 12 12 13
KO on 8 8 8 8 8
dSPN off 15 11 14 10 15
dSPN on 15 6 9 11 15
dSPN on+SCH 10 8 10 10 10
iSPN off 12 8 10 12 12
iSPN on 11 10 10 10 11
iSPN on+sul 7 4 4 4 7
All groups: 123 cells; exact match 90 (73.2%); best match within 3 spikes 105 (85.4%); rheobase equal to the best match 108
Groups whose cell count equals the paper's n: 12 of 12
Cells without an exact match:
group date cell best_row spike_diff exact n_exact_rows rheobase paper_rheobase duplicates spike_diff_last
dSPN off 20170202 1 23 1.0 False 0 260.0 260.0 0 NaN
dSPN off 20170202 2 26 2.0 False 0 280.0 260.0 0 NaN
dSPN off 20170203 3 23 1.0 False 0 280.0 260.0 1 1.0
dSPN off 20170204 1 35 5.0 False 0 180.0 160.0 1 5.0
dSPN on 20170706 2 39 5.0 False 0 80.0 80.0 0 NaN
dSPN on 20170707 2 39 20.0 False 0 60.0 80.0 1 20.0
dSPN on 20170720 1 44 6.0 False 0 100.0 120.0 0 NaN
dSPN on 20170720 2 45 2.0 False 0 180.0 180.0 1 2.0
dSPN on 20190411 1 39 13.0 False 0 100.0 80.0 0 NaN
dSPN on 20190501 1 49 1.0 False 0 120.0 120.0 0 NaN
dSPN on 20190503 1 44 6.0 False 0 100.0 120.0 0 NaN
dSPN on 20190507 1 40 6.0 False 0 160.0 160.0 2 6.0
dSPN on 20190507 2 52 1.0 False 0 140.0 140.0 0 NaN
dSPN on+SCH 20190411 1 53 3.0 False 0 80.0 80.0 0 NaN
dSPN on+SCH 20190501 2 58 1.0 False 0 120.0 120.0 0 NaN
iSPN off 20160523 3 35 1.0 False 0 80.0 80.0 0 NaN
iSPN off 20160524 2 38 4.0 False 0 80.0 80.0 0 NaN
iSPN off 20160524 4 33 11.0 False 0 120.0 120.0 1 11.0
iSPN off 20170525 2 41 2.0 False 0 140.0 140.0 0 NaN
iSPN on 20190429 1 52 8.0 False 0 160.0 140.0 0 NaN
iSPN on+sul 20190426 2 56 6.0 False 0 120.0 140.0 0 NaN
iSPN on+sul 20190429 2 59 11.0 False 0 100.0 160.0 0 NaN
iSPN on+sul 20190429 3 60 11.0 False 0 100.0 160.0 0 NaN
Fig6 vehicle 20200217 1 0217a 5.0 False 0 80.0 80.0 1 4.0
Fig6 vehicle 20200812 2 0812b 1.0 False 0 80.0 80.0 0 NaN
Fig6 M1R antagonist 20200312 1 0312a 24.0 False 0 60.0 80.0 1 24.0
Fig6 M1R antagonist 20200721 3 0721c 1.0 False 0 140.0 140.0 0 NaN
KO off 20160525 2 Cell_2 1.0 False 0 140.0 140.0 0 NaN
KO off 20160525 3 Cell_3 1.0 False 0 120.0 120.0 0 NaN
KO off 20160527 3 Cell_9 7.0 False 0 180.0 160.0 1 7.0
KO off 20170619 3 Cell_13 1.0 False 0 140.0 140.0 0 NaN
Fig9 control 20221012 1 1 17.0 False 0 80.0 80.0 0 NaN
Fig9 M1R CRISPR 20221129 1 12 14.0 False 0 100.0 100.0 0 NaN
Order: position of each cell's best-match row among the paper's rows of its group, cells in the dandiset's date and cell order
dSPN off: [0, 1, 2, 3, 4, 5, 0, 3, 1, 9, 10, 0, 12, 5, 14]; rising steps 10 of 14; position equal to the cell's rank 10 of 15
dSPN on: [0, 1, 2, 1, 4, 5, 6, 7, 8, 1, 10, 11, 6, 2, 14]; rising steps 10 of 14; position equal to the cell's rank 11 of 15
dSPN on+SCH: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; rising steps 9 of 9; position equal to the cell's rank 10 of 10
iSPN off: [0, 1, 2, 3, 4, 5, 0, 7, 8, 9, 10, 11]; rising steps 10 of 11; position equal to the cell's rank 11 of 12
iSPN on: [0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 10]; rising steps 9 of 10; position equal to the cell's rank 10 of 11
iSPN on+sul: [0, 1, 2, 3, 4, 5, 6]; rising steps 6 of 6; position equal to the cell's rank 7 of 7
Fig6 vehicle: [0, 1, 2, 3, 4, 5, 6, 7]; rising steps 7 of 7; position equal to the cell's rank 8 of 8
Fig6 M1R antagonist: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; rising steps 9 of 9; position equal to the cell's rank 10 of 10
KO off: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; rising steps 12 of 12; position equal to the cell's rank 13 of 13
KO on: [0, 1, 2, 3, 4, 5, 6, 7]; rising steps 7 of 7; position equal to the cell's rank 8 of 8
Fig9 control: [0, 1, 2, 3, 4, 5]; rising steps 5 of 5; position equal to the cell's rank 6 of 6
Fig9 M1R CRISPR: [0, 1, 2, 3, 4, 5, 6, 7]; rising steps 7 of 7; position equal to the cell's rank 8 of 8
Paper rows used as best match by more than one cell: {('dSPN off', np.int64(23)): 3, ('dSPN off', np.int64(24)): 2, ('dSPN off', np.int64(26)): 2, ('dSPN off', np.int64(28)): 2, ('dSPN on', np.int64(39)): 3, ('dSPN on', np.int64(40)): 2, ('dSPN on', np.int64(44)): 2, ('iSPN off', np.int64(33)): 2, ('iSPN on', np.int64(52)): 2}
One-to-one assignment (each paper row used once): exact 89 (72.4%), within 3 spikes 103 (83.7%) of 123
Cells with a repeated step (14): best-match difference keeping the first sweep against keeping the last; first closer 0, last closer 1, equal 13
group date cell duplicates spike_diff spike_diff_last
dSPN off 20161116 3 1 0.0 0.0
dSPN off 20170202 3 1 0.0 0.0
dSPN off 20170203 3 1 1.0 1.0
dSPN off 20170204 1 1 5.0 5.0
dSPN on 20170707 2 1 20.0 20.0
dSPN on 20170720 2 1 2.0 2.0
dSPN on 20190507 1 2 6.0 6.0
iSPN off 20160524 4 1 11.0 11.0
Fig6 vehicle 20200217 1 1 5.0 4.0
Fig6 vehicle 20200225 1 1 0.0 0.0
Fig6 M1R antagonist 20200312 1 1 24.0 24.0
KO off 20160527 3 1 7.0 7.0
KO off 20170616 1 1 0.0 0.0
KO on 20160913 2 1 0.0 0.0
Result: partially reproduced. Every group the dandiset holds has the paper's n (12 of 12 groups, 123 cells). 90 of 123 cells (73.2%) match a row of the paper's table exactly over 20 to 300 pA and 105 (85.4%) within 3 spikes, below the 80% and 95% the criterion asks for; 108 have the same rheobase as their best match. The mismatches fall in most groups, most often in dSPN on (6 of 15 exact). The best matches are not one-to-one (9 paper rows are the best match of two or three cells); with each row used once, 89 cells (72.4%) match exactly and 103 (83.7%) within 3 spikes. The best-match rows follow the dandiset's order by date and cell number: in 8 of 12 groups every cell's best match is the row at its own rank, and in the others 10 of 15 (dSPN off), 11 of 15 (dSPN on), 11 of 12 (iSPN off) and 10 of 11 (iSPN on) are. Of the 14 cells with a repeated step, keeping the last sweep in place of the first gives the same best-match difference in 13 and a closer one in 1.
link-dendritic-cells¶
The dandiset's dendritic line scans are those behind the paper's dendritic excitability index figures, and the index computed from them agrees with the paper's per-cell values.
def cell_index(sites, basal_range=lid.BASAL_RATIO_RANGE):
out = []
for (date, ct), s in sites.groupby(["date", "cell_type"]):
di = lid.dendritic_index(s, basal_range)
di["date"], di["cell_type"] = date, ct
out.append(di)
out = pd.concat(out, ignore_index=True)
out["group"] = [lid.GROUPS.get((ct, c), "?") for ct, c in zip(out.cell_type, out.condition)]
return out.sort_values(["group", "date", "cell"]).reset_index(drop=True)
dend = cell_index(sites)
print("Cells with dendritic sites:", len(dend), "; with an index:", int(dend["index"].notna().sum()))
print("Cells without an index (no proximal and distal site of the same number):")
print(dend[dend["index"].isna()][["group", "date", "cell", "pairs", "unpaired_sites"]].to_string(index=False))
print("Pairs:", int(dend.pairs.sum()), "; dropped by the basal-ratio rule:", int((dend.pairs - dend.pairs_kept).sum()))
rows, pairs_rows = [], []
for g, spec in lid.TIDY_DENDRITIC.items():
paper = lid.tidy_group(tidy, spec)
pv = paper.iloc[:, -1].to_numpy(float)
allc = dend[dend.group == g]
mine = allc[allc["index"].notna()]
rows.append({"group": g, "cells_with_sites": len(allc), "cells": len(mine), "paper_n": len(paper), "median": mine["index"].median(), "paper_median": np.median(pv)})
if g.startswith("Fig6"):
ids = paper.iloc[:, 0].astype(str)
for _, c in mine.iterrows():
key = f"{c.date[4:8]}{c.date[:4]}_{'abcdefgh'[c.cell - 1]}"
hit = paper[ids == key]
pairs_rows.append({"group": g, "how": "identifier", "date": c.date, "cell": c.cell, "paper_id": key, "sites": c.sites,
"split": c.split, "basal": c.basal_ratios, "index": c["index"], "paper": float(hit.iloc[0, -1]) if len(hit) else np.nan})
elif len(allc) == len(paper):
for k, ((_, c), p) in enumerate(zip(allc.iterrows(), pv)):
pairs_rows.append({"group": g, "how": "order", "date": c.date, "cell": c.cell, "paper_id": f"row {k + 1}", "sites": c.sites,
"split": c.split, "basal": c.basal_ratios, "index": c["index"], "paper": p})
dsum = pd.DataFrame(rows)
print(dsum.round(3).to_string(index=False))
dpairs = pd.DataFrame(pairs_rows)
dpairs["rel_diff"] = (dpairs["index"] - dpairs.paper).abs() / dpairs.paper
dpairs = dpairs[dpairs["index"].notna()].reset_index(drop=True)
print("\nMatched cells:", dpairs.paper.notna().sum(), "; median relative difference %.3f; within 10%%: %d; by group:"
% (dpairs.rel_diff.median(), (dpairs.rel_diff <= 0.10).sum()))
print(dpairs.groupby(["group", "how"]).rel_diff.agg(["size", "median", "max"]).round(3).to_string())
print("Spearman correlation of matched values: %.3f" % stats.spearmanr(dpairs["index"], dpairs.paper, nan_policy="omit")[0])
print("Groups whose count of cells with an index equals the paper's n:", int((dsum.cells == dsum.paper_n).sum()), "of", len(dsum),
"; whose count of cells with dendritic sites does:", int((dsum.cells_with_sites == dsum.paper_n).sum()))
print("\nEvery matched cell (sites: P proximal, D distal, with their number; split: a series holding two sites, see the data check):")
print(dpairs.round(3).to_string(index=False))
print("\nLargest differences:"); print(dpairs.sort_values("rel_diff", ascending=False).head(5)[["group", "date", "cell", "paper_id", "sites", "index", "paper", "rel_diff"]].round(3).to_string(index=False))
br = np.concatenate([b for b in dend.basal_ratios])
print("\nBasal ratios of all %d pairs: %.2f to %.2f, median %.2f; outside 0.8 to 1.25: %d; outside 0.5 to 2: %d"
% (len(br), br.min(), br.max(), np.median(br), ((br < 0.8) | (br > 1.25)).sum(), ((br < 0.5) | (br > 2)).sum()))
lab = dend_lab_link = cell_index(sites_lab)
print("Variant, sites as labelled (no split): cells with an index", int(lab["index"].notna().sum()), "of", len(lab))
full = cell_index(sites[sites.n_trials >= 3])
chg = dpairs.merge(full[["date", "cell", "condition", "index"]].rename(columns={"index": "index_3trials"}),
left_on=["date", "cell"], right_on=["date", "cell"]).merge(dend[["date", "cell", "condition", "group"]], on=["date", "cell", "condition", "group"])
chg = chg[(chg["index"] - chg.index_3trials).abs() > 1e-9]
print("Variant, sites with fewer than 3 trials dropped (%s): cells whose index changes" % sites[sites.n_trials < 3][["date", "cell", "compartment", "site", "n_trials"]].values.tolist())
print(chg[["group", "date", "cell", "index", "index_3trials", "paper"]].round(3).to_string(index=False))
Cells with dendritic sites: 92 ; with an index: 91
Cells without an index (no proximal and distal site of the same number):
group date cell pairs unpaired_sites
iSPN on+sul 20190508 3 0 1
Pairs: 165 ; dropped by the basal-ratio rule: 0
group cells_with_sites cells paper_n median paper_median
dSPN off 10 10 10 0.330 0.333
dSPN on 10 10 10 0.476 0.463
dSPN on+SCH 9 9 9 0.419 0.338
iSPN off 10 10 10 0.524 0.519
iSPN on 10 10 10 0.339 0.345
iSPN on+sul 8 7 8 0.506 0.499
Fig6 vehicle 9 9 9 0.451 0.465
Fig6 M1R antagonist 7 7 7 0.269 0.261
KO off 11 11 11 0.312 0.313
KO on 8 8 8 0.286 0.283
Matched cells: 91 ; median relative difference 0.029; within 10%: 78; by group:
size median max
group how
Fig6 M1R antagonist identifier 7 0.064 0.254
Fig6 vehicle identifier 9 0.035 0.283
KO off order 11 0.041 0.294
KO on order 8 0.024 0.178
dSPN off order 10 0.040 0.137
dSPN on order 10 0.048 0.193
dSPN on+SCH order 9 0.040 1.559
iSPN off order 10 0.025 1.014
iSPN on order 10 0.013 0.072
iSPN on+sul order 7 0.003 0.017
Spearman correlation of matched values: 0.938
Groups whose count of cells with an index equals the paper's n: 9 of 10 ; whose count of cells with dendritic sites does: 10
Every matched cell (sites: P proximal, D distal, with their number; split: a series holding two sites, see the data check):
group how date cell paper_id sites split basal index paper rel_diff
dSPN off order 20160812 3 row 1 D1 P1 False [0.73] 0.468 0.543 0.137
dSPN off order 20161111 1 row 2 D1 P1 D2 P2 False [0.98, 0.78] 0.373 0.374 0.002
dSPN off order 20161111 3 row 3 D2 P2 False [0.71] 0.451 0.441 0.023
dSPN off order 20161114 1 row 4 D1 P1 D2 P2 D3 P3 False [0.9, 1.23, 0.91] 0.475 0.518 0.082
dSPN off order 20161116 3 row 5 D1 P1 D2 P2 False [0.86, 0.74] 0.317 0.337 0.057
dSPN off order 20170202 1 row 6 D1 P1 D2 P2 False [0.91, 0.91] 0.139 0.148 0.059
dSPN off order 20170203 1 row 7 D1 P1 D2 P2 D3 P3 False [1.01, 1.17, 1.09] 0.332 0.330 0.008
dSPN off order 20170203 3 row 8 D1 P1 D2 P2 False [0.81, 0.83] 0.265 0.266 0.005
dSPN off order 20170204 1 row 9 D1 P1 D2 P2 True [1.16, 1.17] 0.327 0.309 0.060
dSPN off order 20170204 2 row 10 D1 P1 D2 P2 False [1.1, 1.13] 0.293 0.296 0.010
dSPN on order 20170706 1 row 1 D1 P1 D2 P2 False [0.77, 1.0] 0.725 0.716 0.013
dSPN on order 20170706 2 row 2 D1 P1 False [1.22] 0.393 0.382 0.029
dSPN on order 20170707 1 row 3 D1 P1 D2 P2 False [0.77, 0.99] 0.610 0.581 0.051
dSPN on order 20170707 2 row 4 D2 P2 False [1.43] 0.547 0.678 0.193
dSPN on order 20170719 1 row 5 D1 P1 D2 P2 False [0.85, 0.83] 0.420 0.400 0.050
dSPN on order 20170719 2 row 6 D1 P1 D2 P2 False [1.05, 1.05] 0.475 0.475 0.001
dSPN on order 20170720 1 row 7 D1 P1 False [1.15] 0.477 0.452 0.056
dSPN on order 20170720 2 row 8 D1 P1 D2 P2 False [0.72, 1.27] 0.424 0.415 0.022
dSPN on order 20170721 1 row 9 D1 P1 D2 P2 False [1.01, 1.15] 0.374 0.358 0.045
dSPN on order 20170721 2 row 10 D1 P1 D2 P2 False [1.32, 1.11] 0.617 0.579 0.065
dSPN on+SCH order 20190411 1 row 1 D2 P2 False [1.09] 0.584 0.228 1.559
dSPN on+SCH order 20190411 2 row 2 D1 P1 False [1.39] 0.303 0.292 0.040
dSPN on+SCH order 20190412 1 row 3 D1 P1 D2 P2 False [1.31, 1.02] 0.494 0.482 0.024
dSPN on+SCH order 20190412 2 row 4 D1 P1 D2 P2 False [1.48, 1.08] 0.419 0.408 0.028
dSPN on+SCH order 20190501 2 row 5 D1 P1 D2 P2 False [0.85, 1.01] 0.220 0.207 0.059
dSPN on+SCH order 20190503 1 row 6 D3 P3 False [0.72] 0.420 0.404 0.040
dSPN on+SCH order 20190503 3 row 7 D1 P1 D2 P2 False [1.16, 1.42] 0.289 0.261 0.109
dSPN on+SCH order 20190507 2 row 8 D1 P1 False [1.39] 0.349 0.338 0.030
dSPN on+SCH order 20190507 3 row 9 D1 P1 D2 P2 False [1.38, 1.26] 0.431 0.403 0.069
iSPN off order 20160523 1 row 1 D1 P1 D2 P2 False [0.93, 0.92] 0.396 0.410 0.034
iSPN off order 20160523 2 row 2 D1 P1 D2 P2 False [1.02, 1.02] 0.543 0.565 0.039
iSPN off order 20160523 3 row 3 D1 P1 D2 P2 False [1.03, 1.26] 0.731 0.737 0.008
iSPN off order 20160523 4 row 4 D1 P1 D2 P2 False [0.84, 1.49] 0.763 0.947 0.194
iSPN off order 20160524 1 row 5 D1 P1 D2 P2 False [1.18, 1.02] 0.504 0.510 0.012
iSPN off order 20160524 2 row 6 D1 P1 D2 P2 False [0.95, 1.16] 1.062 0.527 1.014
iSPN off order 20160524 3 row 7 D1 P1 D2 P2 False [0.98, 0.76] 0.623 0.626 0.005
iSPN off order 20161116 1 row 8 D1 P1 D2 P2 False [1.27, 0.7] 0.388 0.414 0.062
iSPN off order 20170525 2 row 9 D1 P1 D2 P2 False [0.81, 1.35] 0.488 0.489 0.001
iSPN off order 20170526 2 row 10 D1 P1 D2 P2 False [1.12, 0.78] 0.462 0.455 0.016
iSPN on order 20160608 1 row 1 D1 P1 True [1.32] 0.146 0.146 0.000
iSPN on order 20160608 2 row 2 D1 P1 False [1.02] 0.339 0.353 0.040
iSPN on order 20160608 3 row 3 D1 D2 P2 False [1.15] 0.234 0.223 0.049
iSPN on order 20160609 1 row 4 D1 P1 D2 P2 False [0.96, 1.14] 0.644 0.601 0.072
iSPN on order 20160609 2 row 5 D1 P1 D2 P2 False [1.15, 1.37] 0.475 0.469 0.012
iSPN on order 20160718 1 row 6 D1 P1 D2 P2 False [1.29, 0.95] 0.398 0.403 0.013
iSPN on order 20160718 2 row 7 D1 P1 D2 P2 False [0.95, 0.81] 0.252 0.257 0.018
iSPN on order 20160719 1 row 8 D1 P1 D2 P2 False [0.95, 0.99] 0.418 0.421 0.007
iSPN on order 20160719 2 row 9 D1 P1 D2 P2 False [1.19, 0.91] 0.295 0.293 0.005
iSPN on order 20190408 1 row 10 D1 P1 D2 P2 False [1.09, 0.91] 0.340 0.337 0.009
iSPN on+sul order 20190408 1 row 1 D1 P1 D2 P2 False [1.02, 1.17] 0.430 0.422 0.017
iSPN on+sul order 20190409 1 row 2 D1 P1 D2 P2 False [0.9, 1.22] 0.488 0.488 0.001
iSPN on+sul order 20190426 1 row 3 D1 P1 D2 P2 False [1.04, 1.16] 0.521 0.513 0.016
iSPN on+sul order 20190426 4 row 4 D1 P1 D2 P2 False [1.19, 0.99] 0.389 0.388 0.002
iSPN on+sul order 20190429 1 row 5 D1 P1 D2 P2 False [1.25, 1.02] 0.703 0.704 0.001
iSPN on+sul order 20190429 3 row 6 D2 P2 False [1.26] 0.852 0.854 0.003
iSPN on+sul order 20190509 2 row 8 D1 P1 D2 P2 False [0.95, 0.85] 0.506 0.511 0.009
Fig6 vehicle identifier 20200217 1 02172020_a D1 P1 D2 P2 False [0.8, 0.9] 0.531 0.550 0.035
Fig6 vehicle identifier 20200217 2 02172020_b D1 P1 D2 P2 False [1.04, 0.9] 0.451 0.465 0.029
Fig6 vehicle identifier 20200225 1 02252020_a D1 P1 D2 P2 False [0.81, 1.0] 0.490 0.466 0.051
Fig6 vehicle identifier 20200225 2 02252020_b D1 P1 D2 P2 False [0.87, 1.03] 0.230 0.265 0.132
Fig6 vehicle identifier 20200811 1 08112020_a D1 P1 D2 P2 False [1.17, 1.0] 0.462 0.463 0.003
Fig6 vehicle identifier 20200812 1 08122020_a D1 P1 D2 P2 False [0.99, 1.01] 0.435 0.436 0.003
Fig6 vehicle identifier 20200812 2 08122020_b D1 P1 D2 P2 False [0.78, 1.03] 0.655 0.650 0.008
Fig6 vehicle identifier 20240425 1 04252024_a D1 P1 D2 P2 False [1.15, 0.89] 0.421 0.392 0.072
Fig6 vehicle identifier 20240425 2 04252024_b D1 P1 D2 P2 False [1.01, 1.75] 0.361 0.504 0.283
Fig6 M1R antagonist identifier 20200218 2 02182020_b D1 P1 D2 P2 False [1.12, 1.09] 0.269 0.237 0.133
Fig6 M1R antagonist identifier 20200218 3 02182020_c D1 P1 D2 P2 False [0.87, 0.86] 0.366 0.344 0.064
Fig6 M1R antagonist identifier 20200220 3 02202020_c D1 P1 D2 P2 False [1.2, 0.89] 0.319 0.303 0.054
Fig6 M1R antagonist identifier 20200312 1 03122020_a D1 P1 D2 P2 False [0.84, 0.95] 0.465 0.468 0.008
Fig6 M1R antagonist identifier 20200312 2 03122020_b D1 P1 D2 P2 False [0.96, 1.17] 0.194 0.261 0.254
Fig6 M1R antagonist identifier 20240430 1 04302024_a D1 P1 D2 P2 False [0.88, 1.22] 0.168 0.172 0.025
Fig6 M1R antagonist identifier 20240430 2 04302024_b D1 P1 D2 P2 False [1.24, 1.1] 0.183 0.213 0.142
KO off order 20160525 1 row 1 D1 P1 D2 P2 False [1.28, 1.09] 0.176 0.188 0.061
KO off order 20160525 2 row 2 D1 P1 D2 P2 False [1.18, 0.82] 0.312 0.313 0.005
KO off order 20160525 3 row 3 D1 P1 D2 P2 False [1.08, 0.87] 0.309 0.292 0.057
KO off order 20160526 1 row 4 D1 P1 D2 P2 False [0.92, 0.88] 0.562 0.549 0.024
KO off order 20160526 2 row 5 D1 P1 D3 P3 False [1.28, 0.99] 0.303 0.296 0.025
KO off order 20160526 3 row 6 D1 P1 True [0.91] 0.293 0.299 0.021
KO off order 20160527 1 row 7 D1 P1 D2 P2 False [1.13, 0.94] 0.451 0.427 0.055
KO off order 20160527 2 row 8 D1 P1 D2 P2 False [1.09, 1.2] 0.589 0.566 0.041
KO off order 20170616 1 row 9 D2 P2 False [0.87] 0.383 0.386 0.008
KO off order 20170619 2 row 10 P1 D2 P2 False [1.25] 0.433 0.335 0.294
KO off order 20170619 3 row 11 D1 P1 False [0.95] 0.299 0.286 0.045
KO on order 20160816 1 row 1 D1 P1 D2 P2 False [1.24, 0.75] 0.288 0.283 0.019
KO on order 20160816 2 row 2 D1 P1 False [1.1] 0.205 0.190 0.075
KO on order 20160901 1 row 3 D1 P1 D2 P2 False [1.1, 0.92] 0.661 0.693 0.046
KO on order 20160902 2 row 4 D1 P1 D2 P2 False [0.99, 0.82] 0.279 0.282 0.011
KO on order 20160902 3 row 5 D1 P1 D2 P2 False [0.67, 1.08] 0.500 0.608 0.178
KO on order 20160902 4 row 6 D1 P1 False [0.86] 0.212 0.208 0.021
KO on order 20160913 1 row 7 D1 P1 D3 P3 False [0.88, 0.78] 0.314 0.323 0.027
KO on order 20160913 2 row 8 D1 P1 D2 P2 False [0.78, 0.79] 0.284 0.284 0.000
Largest differences:
group date cell paper_id sites index paper rel_diff
dSPN on+SCH 20190411 1 row 1 D2 P2 0.584 0.228 1.559
iSPN off 20160524 2 row 6 D1 P1 D2 P2 1.062 0.527 1.014
KO off 20170619 2 row 10 P1 D2 P2 0.433 0.335 0.294
Fig6 vehicle 20240425 2 04252024_b D1 P1 D2 P2 0.361 0.504 0.283
Fig6 M1R antagonist 20200312 2 03122020_b D1 P1 D2 P2 0.194 0.261 0.254
Basal ratios of all 165 pairs: 0.67 to 1.75, median 1.01; outside 0.8 to 1.25: 38; outside 0.5 to 2: 0
Variant, sites as labelled (no split): cells with an index 89 of 92
Variant, sites with fewer than 3 trials dropped ([['20160524', 2, 'Proximal', 1, 1], ['20170707', 1, 'Distal', 2, 2]]): cells whose index changes group date cell index index_3trials paper dSPN on 20170707 1 0.610 0.630 0.581 iSPN off 20160524 2 1.062 0.553 0.527
Result: reproduced. With the three series that hold two sites split (data check), 91 of 92 cells have an index; iSPN on+sul 20190508 cell 3 has only a distal site. Cells with an index equal the paper's n in 9 of 10 groups, and cells with dendritic sites in 10 of 10, so every group is matched by order or by identifier. Over 91 matched cells the index differs from the paper's by a median of 2.9% (78 within 10%, Spearman correlation 0.938). Two cells differ by more than the paper's value: dSPN on+SCH 20190411 cell 1 (0.584 against 0.228), which has only its second pair of sites, and iSPN off 20160524 cell 2 (1.062 against 0.527), whose first proximal site has one trial; dropping sites with fewer than 3 trials gives that cell 0.553. The basal-ratio rule (0.5 to 2) drops none of the 165 pairs (0.67 to 1.75); 38 lie outside 0.8 to 1.25. With the sites as labelled, 89 of 92 cells have an index.
link-sr-cells¶
The dandiset's Sr2+-oEPSC recordings are cells of the paper's Figures 2 and 4, and their events agree with the paper's per-cell values.
amp_t = pd.concat([tidy["Fig 2"]["Fig 2H_dSPN Sr-oEPSC amp"].set_axis(["id", "group", "amp"], axis=1),
tidy["Fig 4"]["Fig 4G_iSPN Sr-oEPSC amp"].set_axis(["id", "group", "amp"], axis=1)])
frq_t = pd.concat([tidy["Fig 2"]["Fig 2I_dSPN Sr-oEPSC freq"].set_axis(["id", "group", "freq"], axis=1),
tidy["Fig 4"]["Fig 4H_iSPN Sr-oEPSC freq"].set_axis(["id", "group", "freq"], axis=1)])
evt_t = pd.concat([tidy["Fig 2"]["Fig 2G_dSPN Sr-oEPSC histogram"].set_axis(["id", "group", "event", "amp"], axis=1),
tidy["Fig 4"]["Fig 4F_iSPN Sr-oEPSC histogram"].set_axis(["id", "group", "event", "amp"], axis=1)])
for t in (amp_t, frq_t, evt_t):
t["id"] = t["id"].astype(str)
def sr_cell(s, threshold_sd=lid.SR_THRESHOLD_SD):
evs = []
for w in s["sweeps"]:
onset = w["led_onset_s"] if w["led_trace_nonzero"] else s["epoch_stim_s"]
evs.append(lid.detect_events(w["current"], w["rate"], onset, threshold_sd=threshold_sd))
e = pd.concat(evs)
return len(e), e.amp.mean(), len(e) / (0.36 * len(s["sweeps"])), float(np.median([x.attrs["noise"] for x in evs]))
rows = []
for s, r in zip(srs, sr_rows):
n5, a5, f5, noise = sr_cell(s)
n3, a3, f3, _ = sr_cell(s, 3.0)
pe = evt_t[evt_t.id == r["paper_id"]]
rows.append({"cell_type": r["cell_type"], "state": r["state"], "paper_id": r["paper_id"],
"in_paper": r["paper_id"] in set(amp_t.id), "noise_pA": noise,
"events": n5, "paper_events": len(pe), "mean_amp": a5, "paper_event_mean": pe.amp.mean(),
"paper_cell_amp": float(amp_t[amp_t.id == r["paper_id"]].amp.iloc[0]) if r["paper_id"] in set(amp_t.id) else np.nan,
"freq_Hz": f5, "paper_freq": float(frq_t[frq_t.id == r["paper_id"]].freq.iloc[0]) if r["paper_id"] in set(frq_t.id) else np.nan,
"events_3sd": n3, "mean_amp_3sd": a3})
sr_link = pd.DataFrame(rows)
print(sr_link.round(2).to_string(index=False))
print("\nPaper's frequency equals its event count / (0.36 s x 10 sweeps):",
bool(np.allclose(sr_link.paper_freq, sr_link.paper_events / 3.6)))
ok_amp = (sr_link.mean_amp - sr_link.paper_event_mean).abs() <= 0.25 * sr_link.paper_event_mean
ok_n = (sr_link.events - sr_link.paper_events).abs() <= 0.25 * sr_link.paper_events
print(f"Cells matched by identifier: {sr_link.in_paper.sum()} of {len(sr_link)}; the paper has "
f"{amp_t.id.nunique()} cells ({(amp_t.group.str.strip() == 'LID off').sum()} off, {(amp_t.group.str.strip() == 'LID on').sum()} on)")
print(f"Cells with mean amplitude within 25% of the paper's events: {ok_amp.sum()}; with event count within 25%: {ok_n.sum()}; both: {(ok_amp & ok_n).sum()} of {len(sr_link)}")
ok_cell = (sr_link.mean_amp - sr_link.paper_cell_amp).abs() <= 0.25 * sr_link.paper_cell_amp
print(f"Reading the paper's amplitude as its per-cell value (Fig. 2H, 4G sheets): within 25% {ok_cell.sum()}; with the event count also: {(ok_cell & ok_n).sum()} of {len(sr_link)}")
print("Paper's per-cell amplitude against the mean of its histogram events, median ratio %.3f (range %.3f to %.3f)"
% ((sr_link.paper_cell_amp / sr_link.paper_event_mean).median(), (sr_link.paper_cell_amp / sr_link.paper_event_mean).min(), (sr_link.paper_cell_amp / sr_link.paper_event_mean).max()))
print("\nBy group (dandiset cells only; the paper's test cannot reach P < 0.05 with 3 against 4 cells):")
for ct in ("dSPN", "iSPN"):
for col in ("mean_amp", "freq_Hz", "paper_cell_amp", "paper_freq"):
a = sr_link[(sr_link.cell_type == ct) & (sr_link.state == "OffState")][col]
b = sr_link[(sr_link.cell_type == ct) & (sr_link.state == "OnState")][col]
print(f" {ct} {col}: off {np.round(a.values, 2)} on {np.round(b.values, 2)}; MW P = {stats.mannwhitneyu(a, b).pvalue:.3f}")
cell_type state paper_id in_paper noise_pA events paper_events mean_amp paper_event_mean paper_cell_amp freq_Hz paper_freq events_3sd mean_amp_3sd
dSPN OffState 07062023c True 1.24 47 56 13.43 12.37 11.13 13.06 15.56 65 11.37
dSPN OffState 07132023d True 1.25 26 45 17.33 13.63 11.61 7.22 12.50 35 15.29
dSPN OffState 08172023d True 1.75 35 53 17.15 13.93 13.13 9.72 14.72 49 14.56
dSPN OnState 07052023b True 1.50 73 94 18.08 16.60 15.00 20.28 26.11 89 16.08
dSPN OnState 07102023b True 2.10 44 49 26.50 20.95 16.85 12.22 13.61 60 21.89
dSPN OnState 08022023b True 2.06 85 127 27.94 23.95 20.85 23.61 35.28 109 25.07
dSPN OnState 08162023b True 3.01 37 74 33.23 22.79 17.85 10.28 20.56 59 26.10
iSPN OffState 20230222b True 2.41 58 106 21.27 15.69 14.05 16.11 29.44 81 18.86
iSPN OffState 20230330b True 1.68 40 66 15.45 12.36 11.88 11.11 18.33 52 13.60
iSPN OffState 20230406c True 1.18 71 96 15.98 14.05 12.44 19.72 26.67 83 14.67
iSPN OnState 20230228b True 1.25 32 56 12.02 9.27 7.60 8.89 15.56 49 9.88
iSPN OnState 20230301b True 1.50 14 29 18.21 10.20 7.10 3.89 8.06 23 13.76
iSPN OnState 20230331b True 1.09 54 72 13.73 10.19 9.36 15.00 20.00 67 12.33
iSPN OnState 20230405b True 2.40 2 73 14.51 7.54 6.58 0.56 20.28 18 10.81
Paper's frequency equals its event count / (0.36 s x 10 sweeps): True
Cells matched by identifier: 14 of 14; the paper has 33 cells (17 off, 16 on)
Cells with mean amplitude within 25% of the paper's events: 6; with event count within 25%: 4; both: 2 of 14
Reading the paper's amplitude as its per-cell value (Fig. 2H, 4G sheets): within 25% 2; with the event count also: 2 of 14
Paper's per-cell amplitude against the mean of its histogram events, median ratio 0.879 (range 0.696 to 0.961)
By group (dandiset cells only; the paper's test cannot reach P < 0.05 with 3 against 4 cells):
dSPN mean_amp: off [13.43 17.33 17.15] on [18.08 26.5 27.94 33.23]; MW P = 0.057
dSPN freq_Hz: off [13.06 7.22 9.72] on [20.28 12.22 23.61 10.28]; MW P = 0.229
dSPN paper_cell_amp: off [11.13 11.61 13.13] on [15. 16.85 20.85 17.85]; MW P = 0.057
dSPN paper_freq: off [15.56 12.5 14.72] on [26.11 13.61 35.28 20.56]; MW P = 0.229
iSPN mean_amp: off [21.27 15.45 15.98] on [12.02 18.21 13.73 14.51]; MW P = 0.229
iSPN freq_Hz: off [16.11 11.11 19.72] on [ 8.89 3.89 15. 0.56]; MW P = 0.114
iSPN paper_cell_amp: off [14.05 11.88 12.44] on [7.6 7.1 9.36 6.58]; MW P = 0.057
iSPN paper_freq: off [29.44 18.33 26.67] on [15.56 8.06 20. 20.28]; MW P = 0.229
# The detector on two cells: the one with the largest shortfall of events and one within 25% on both.
show_ids = [sr_link.assign(r=sr_link.events / sr_link.paper_events).sort_values("r").paper_id.iloc[0],
sr_link[ok_amp & ok_n].paper_id.iloc[0]]
fig, axes = plt.subplots(2, 3, figsize=(13, 5.5), sharex=True)
for row, pid in zip(axes, show_ids):
s = srs[[r["paper_id"] for r in sr_rows].index(pid)]
lo, hi = np.inf, -np.inf
for ax, w in zip(row, s["sweeps"][:3]):
onset = w["led_onset_s"] if w["led_trace_nonzero"] else s["epoch_stim_s"]
x = sosfiltfilt(butter(4, lid.SR_LOWPASS_HZ, fs=w["rate"], output="sos"), w["current"] - np.median(w["current"]))
t = np.arange(len(x)) / w["rate"] - onset
keep = (t >= 0) & (t <= 0.45)
e = lid.detect_events(w["current"], w["rate"], onset)
ax.plot(t[keep] * 1e3, x[keep], lw=0.5, color="0.3")
ax.plot(e.t * 1e3, np.interp(e.t, t, x), "v", color="tab:red", ms=5)
ax.axvspan(lid.SR_WINDOW[0] * 1e3, lid.SR_WINDOW[1] * 1e3, color="tab:blue", alpha=0.07)
ax.set_title(f"{pid} {w['name'][-8:]}: {len(e)} events", fontsize=9)
lo, hi = min(lo, x[keep].min()), max(hi, x[keep].max())
print(f"{pid} {w['name'][-8:]}: peak of the plotted current {x[keep].min():.1f} pA")
row[0].set_ylabel("current (pA, low-passed)")
print(f"{pid}: plotted current {lo:.1f} to {hi:.1f} pA, all inside the axes; the paper lists {int(sr_link[sr_link.paper_id == pid].paper_events.iloc[0])} events over 10 sweeps, "
f"the detector finds {int(sr_link[sr_link.paper_id == pid].events.iloc[0])}")
for ax in axes[1]:
ax.set_xlabel("time after the light pulse (ms)")
fig.suptitle("Detected events (red) in the first three sweeps; shaded: detection window 40 to 400 ms")
plt.tight_layout(); plt.show()
20230405b Sweep001: peak of the plotted current -124.6 pA 20230405b Sweep002: peak of the plotted current -140.0 pA
20230405b Sweep003: peak of the plotted current -165.3 pA 20230405b: plotted current -165.3 to 7.7 pA, all inside the axes; the paper lists 73 events over 10 sweeps, the detector finds 2 07062023c Sweep001: peak of the plotted current -101.5 pA
07062023c Sweep002: peak of the plotted current -51.6 pA 07062023c Sweep003: peak of the plotted current -90.9 pA 07062023c: plotted current -101.5 to 5.8 pA, all inside the axes; the paper lists 56 events over 10 sweeps, the detector finds 47
Result: partially reproduced. All 14 cells match a paper identifier; the dandiset holds 14 of the paper's 33 cells, one per mouse. In every cell the detector here finds fewer events and a higher mean amplitude than the paper's events, and only 2 cells are within 25% on both, so the event detection differs from the paper's. Reading the paper's amplitude as its per-cell value (0.70 to 0.96 times the mean of its histogram events) also gives 2 of 14. The figure shows why in 20230405b, where the detector finds 2 events against the paper's 73: the detection window opens on the slow decay of a large synchronous current (peak -125 to -165 pA), and the small events on that decay stay below the threshold. In 07062023c it finds 47 against 56. With the paper's own per-cell amplitudes the 4 on-state dSPNs are all above the 3 off-state ones and the 4 on-state iSPNs all below the 3 off-state ones (MW P = 0.057 each, the smallest attainable).
link-grab-rois¶
The dandiset's GRAB-ACh recordings are regions of the paper's Figure 5F, and the normalised areas computed from them agree with the paper's per-region values.
p5 = tidy["Fig 5"]["Fig 5F_GRAB-ACh_single-AUC"].set_axis(["roi", "group", "drug", "auc"], axis=1)
p5wide = p5.pivot_table(index=["group", "roi"], columns="drug", values="auc")
name = {"control": "No drug", "quinpirole": "quinpirole", "sulpiride": "sulpiride", "50nM_dopamine": "DA"}
pgroup = {"CTRL": "unlesioned", "PD": "6-OHDA", "OFF": "LID off"}
grows = []
for m in grabs:
gm = lid.grab_measures(m)
sp = gm[gm.stimulation == "single_pulse"].set_index("treatment").auc_norm.rename(index=name)
cand = p5wide.loc[pgroup[m["state"]]][sp.index]
dist = (cand - sp).abs()
j = dist.sum(axis=1).idxmin()
grows.append({"state": m["state"], "date": m["date"], **{f"{k}": round(v, 3) for k, v in sp.items()},
"best_roi": j, **{f"paper_{k}": round(cand.loc[j, k], 3) for k in sp.index},
"max_diff": round(dist.loc[j].max(), 3)})
glink = pd.DataFrame(grows)
print(glink.to_string(index=False))
print("\nRegions per group, dandiset against paper:",
{pgroup[s]: (int((glink.state == s).sum()), int(p5wide.loc[pgroup[s]].shape[0])) for s in pgroup})
print("Mice with a match within 0.05 for every treatment:", int((glink.max_diff <= 0.05).sum()), "of", len(glink))
print("Paper regions used as best match by more than one mouse:", glink.groupby(["state", "best_roi"]).size().loc[lambda s: s > 1].to_dict())
one = []
for st, x in glink.groupby("state"):
cols = [c for c in ["No drug", "quinpirole", "sulpiride", "DA"] if c in x and x[c].notna().all()]
cand = p5wide.loc[pgroup[st]][cols].dropna()
cost = np.array([[np.abs(cand.loc[r, cols].to_numpy(float) - x.loc[i, cols].to_numpy(float)).max() for r in cand.index] for i in x.index])
ri, ci = linear_sum_assignment(np.array([[np.abs(cand.loc[r, cols].to_numpy(float) - x.loc[i, cols].to_numpy(float)).sum() for r in cand.index] for i in x.index]))
one += [{"state": st, "date": x.loc[x.index[a], "date"], "roi": cand.index[b], "max_diff": round(cost[a, b], 3)} for a, b in zip(ri, ci)]
one = pd.DataFrame(one)
print("One-to-one assignment (each paper region used once):"); print(one.to_string(index=False))
print("Mice within 0.05 for every treatment under the one-to-one assignment:", int((one.max_diff <= 0.05).sum()), "of", len(one))
print("\nPer-mouse areas by treatment (the paper's tests cannot reach P < 0.05 with 3 mice per group or 3 or 5 pairs):")
print(glink.groupby("state")[["No drug", "quinpirole", "sulpiride", "DA"]].agg(lambda x: list(np.round(x.dropna(), 3))).to_string())
state date No drug quinpirole sulpiride best_roi paper_No drug paper_quinpirole paper_sulpiride max_diff DA paper_DA
CTRL 20240402 0.112 0.014 0.181 6 0.108 0.007 0.181 0.007 NaN NaN
CTRL 20240405 0.113 0.036 0.136 9 0.122 0.025 0.106 0.030 NaN NaN
CTRL 20240410 0.089 0.073 0.441 13 0.054 0.052 0.392 0.049 NaN NaN
PD 20240412 0.240 0.035 0.605 17 0.206 0.021 0.520 0.085 0.127 0.092
PD 20240416 0.269 0.006 0.294 18 0.344 0.047 0.306 0.075 0.133 0.098
PD 20240509 0.179 -0.005 0.062 23 0.220 -0.010 0.067 0.041 0.040 0.050
OFF 20240524 0.193 -0.007 0.142 29 0.283 -0.008 0.099 0.090 0.036 0.068
OFF 20240529 0.359 0.038 0.316 26 0.333 0.044 0.261 0.055 0.035 0.049
OFF 20240531 0.296 -0.048 0.044 29 0.283 -0.008 0.099 0.055 0.078 0.068
OFF 20240603 0.401 0.007 0.301 30 0.360 0.012 0.278 0.041 0.126 0.116
OFF 20240604 0.723 -0.010 0.142 33 0.687 0.042 0.408 0.266 0.104 0.109
Regions per group, dandiset against paper: {'unlesioned': (3, 13), '6-OHDA': (3, 10), 'LID off': (5, 11)}
Mice with a match within 0.05 for every treatment: 5 of 11
Paper regions used as best match by more than one mouse: {('OFF', 29): 2}
One-to-one assignment (each paper region used once):
state date roi max_diff
CTRL 20240402 6 0.007
CTRL 20240405 9 0.030
CTRL 20240410 13 0.049
OFF 20240524 34 0.106
OFF 20240529 26 0.055
OFF 20240531 29 0.055
OFF 20240603 30 0.041
OFF 20240604 33 0.266
PD 20240412 17 0.085
PD 20240416 18 0.075
PD 20240509 23 0.041
Mice within 0.05 for every treatment under the one-to-one assignment: 5 of 11
Per-mouse areas by treatment (the paper's tests cannot reach P < 0.05 with 3 mice per group or 3 or 5 pairs):
No drug quinpirole sulpiride DA
state
CTRL [0.112, 0.113, 0.089] [0.014, 0.036, 0.073] [0.181, 0.136, 0.441] []
OFF [0.193, 0.359, 0.296, 0.401, 0.723] [-0.007, 0.038, -0.048, 0.007, -0.01] [0.142, 0.316, 0.044, 0.301, 0.142] [0.036, 0.035, 0.078, 0.126, 0.104]
PD [0.24, 0.269, 0.179] [0.035, 0.006, -0.005] [0.605, 0.294, 0.062] [0.127, 0.133, 0.04]
Result: not reproduced. The dandiset holds one region per mouse, 11 of the paper's 34 regions (3 of 13 unlesioned, 3 of 10 6-OHDA, 5 of 11 off-state). Only 5 of the 11 mice have a paper region within 0.05 on every treatment, so the regions cannot be identified in the paper's table. One paper region is the best match of two off-state mice; with each region used once, 5 of 11 mice are still within 0.05.
link-aim-mice¶
The dandiset's AIM score tables are mice of the paper's Figures 8 and 10, with genotypes and scores that agree with the paper's tables.
t8 = tidy["Fig 8"]["Fig 8B_total AIMs"].copy()
t8["animal"] = t8["Mouse ID"].astype(str).str.extract(r"(\d+)")[0].astype(int)
t10 = tidy["Fig 10"]["Fig 10B_total AIMs"].copy()
t10["animal"] = t10["Mouse ID"].astype(int)
paper_aim = pd.concat([t8.assign(fig="Fig 8", label=t8.Genotype.astype(str).str.strip())[["fig", "animal", "label", "Session#", "Total AIM score"]],
t10.assign(fig="Fig 10", label=t10.Group.astype(str).str.strip())[["fig", "animal", "label", "Session#", "Total AIM score"]]])
mine = aim.groupby(["animal_id", "session_number"]).agg(total=("total_score", "sum"), points=("total_score", "size")).reset_index()
rows = []
for a, g in mine.groupby("animal_id"):
p = paper_aim[paper_aim.animal == a]
lab = ", ".join(sorted(set(aim[aim.animal_id == a].genotype)))
eq = [float(p[p["Session#"] == s]["Total AIM score"].iloc[0]) == t if (p["Session#"] == s).any() else None for s, t in zip(g.session_number, g.total)]
rows.append({"animal": a, "in_paper": len(p) > 0, "paper_fig": p.fig.iloc[0] if len(p) else "", "paper_genotype": p.label.iloc[0] if len(p) else "",
"dandiset_table_genotypes": lab, "sessions": len(g), "totals": g.total.tolist(),
"paper_totals": p.sort_values("Session#")["Total AIM score"].tolist(), "sessions_equal": sum(e is True for e in eq)})
alink = pd.DataFrame(rows)
print(alink.to_string(index=False))
print("\nDandiset mice in the paper:", int(alink.in_paper.sum()), "of", len(alink), "; matched sessions equal:",
int(alink.sessions_equal.sum()), "of", int(alink[alink.in_paper].sessions.sum()))
print("Paper mice: Fig 8", t8.animal.nunique(), "(", t8.groupby("Genotype").animal.nunique().to_dict(), "), Fig 10", t10.animal.nunique(),
"(", t10.groupby("Group").animal.nunique().to_dict(), ")")
print("Paper's KO or deletion mice in the dandiset:", alink[alink.paper_genotype.isin(["KO", '"-M1R"'])].animal.tolist(),
"; wild-type or control:", alink[alink.paper_genotype.isin(["WT", "control"])].animal.tolist())
print("\nFigure 8 mice: dandiset total score per time point (min), its session sum, the sum over 20 to 80 min, and the paper's session total")
for a in alink[alink.paper_fig == "Fig 8"].animal:
pv = aim[aim.animal_id == a].pivot_table(index="session_number", columns="time_minutes", values="total_score", aggfunc="sum")
pv["sum"] = pv.sum(axis=1)
pv["sum 20-80"] = pv[[20, 40, 60, 80]].sum(axis=1)
pv["paper"] = t8[t8.animal == a].set_index("Session#")["Total AIM score"]
print(f"mouse {a}:"); print(pv.to_string())
animal in_paper paper_fig paper_genotype dandiset_table_genotypes sessions totals paper_totals sessions_equal
1945 True Fig 8 KO CDGI Knockout, unknown 5 [33.5, 31.5, 44.0, 45.5, 43.0] [27.0, 31.0, 46.0, 48.5, 47.5] 0
2519 True Fig 8 KO CDGI Knockout, unknown 5 [29.0, 28.0, 35.0, 36.0, 36.0] [26.0, 27.0, 36.5, 34.0, 39.0] 0
2587 True Fig 8 WT Wild Type, unknown 5 [36.5, 46.0, 53.5, 40.0, 43.0] [35.5, 44.0, 43.5, 45.0, 41.0] 0
2828 True Fig 8 KO CDGI Knockout 5 [32.0, 28.5, 34.5, 30.5, 32.5] [31.0, 27.0, 34.5, 30.5, 28.5] 2
4124 False unknown 5 [36.5, 41.5, 44.5, 47.0, 45.0] [] 0
5844 True Fig 10 control Control 5 [36.5, 41.5, 45.0, 46.0, 45.5] [36.5, 41.5, 45.0, 46.0, 45.5] 5
5846 True Fig 10 "-M1R" M1R CRISPR 5 [33.5, 32.5, 37.0, 38.5, 41.5] [33.5, 32.5, 37.0, 38.5, 41.5] 5
5871 False Wild Type 5 [38.0, 47.0, 52.5, 51.0, 52.0] [] 0
6572 True Fig 8 KO CDGI Knockout 5 [36.0, 32.5, 34.5, 29.5, 35.0] [36.0, 32.5, 34.5, 31.5, 32.5] 3
Dandiset mice in the paper: 7 of 9 ; matched sessions equal: 15 of 35
Paper mice: Fig 8 23 ( {'KO': 11, 'WT': 12} ), Fig 10 21 ( {'"-M1R"': 10, 'control': 11} )
Paper's KO or deletion mice in the dandiset: [1945, 2519, 2828, 5846, 6572] ; wild-type or control: [2587, 5844]
Figure 8 mice: dandiset total score per time point (min), its session sum, the sum over 20 to 80 min, and the paper's session total
mouse 1945:
time_minutes 20 40 60 80 100 120 sum sum 20-80 paper
session_number
1 9.5 8.0 8.0 8.0 NaN NaN 33.5 33.5 27.0
2 8.0 8.0 8.0 7.5 NaN NaN 31.5 31.5 31.0
3 8.0 8.0 8.5 8.5 8.0 3.0 44.0 33.0 46.0
4 8.0 8.0 8.0 8.0 9.5 4.0 45.5 32.0 48.5
5 8.0 8.0 8.0 8.0 7.0 4.0 43.0 32.0 47.5
mouse 2519:
time_minutes 20 40 60 80 100 120 sum sum 20-80 paper
session_number
1 7.0 7.0 8.0 7.0 NaN NaN 29.0 29.0 26.0
2 7.5 8.0 6.5 6.0 NaN NaN 28.0 28.0 27.0
3 7.0 9.0 7.0 6.0 6.0 NaN 35.0 29.0 36.5
4 7.5 6.5 6.0 7.0 6.0 3.0 36.0 27.0 34.0
5 7.0 8.0 8.0 8.0 5.0 NaN 36.0 31.0 39.0
mouse 2587:
time_minutes 20 40 60 80 100 120 sum sum 20-80 paper
session_number
1 10.0 9.0 8.5 9.0 NaN NaN 36.5 36.5 35.5
2 10.0 9.0 10.0 8.5 8.5 NaN 46.0 37.5 44.0
3 9.0 10.0 10.0 8.0 8.5 8.0 53.5 37.0 43.5
4 10.0 10.0 8.0 8.0 4.0 NaN 40.0 36.0 45.0
5 10.0 9.0 8.5 8.5 7.0 NaN 43.0 36.0 41.0
mouse 2828:
time_minutes 20 40 60 80 100 sum sum 20-80 paper
session_number
1 7.0 7.0 6.0 8.0 4.0 32.0 28.0 31.0
2 8.5 7.0 8.0 4.0 1.0 28.5 27.5 27.0
3 7.0 7.0 6.5 7.0 7.0 34.5 27.5 34.5
4 7.0 7.0 8.0 7.0 1.5 30.5 29.0 30.5
5 6.0 9.0 7.0 7.5 3.0 32.5 29.5 28.5
mouse 6572:
time_minutes 20 40 60 80 100 sum sum 20-80 paper
session_number
1 8.0 7.0 7.0 7.0 7.0 36.0 29.0 36.0
2 8.0 8.5 8.5 6.5 1.0 32.5 31.5 32.5
3 6.0 7.0 7.0 8.0 6.5 34.5 28.0 34.5
4 6.0 6.5 6.0 6.0 5.0 29.5 24.5 31.5
5 6.0 6.0 8.0 7.0 8.0 35.0 27.0 32.5
Result: not reproduced. 7 of the 9 dandiset mice are in the paper's tables (4124 and 5871 are not). The two Figure 10 mice (5844 control, 5846 M1R deletion) have session totals equal to the paper's in all 10 sessions, but the five Figure 8 mice agree in only 5 of 25 sessions (mouse 1945, session 1, 33.5 here against 27.0). The paper's tables give session totals only. Its totals for these mice equal neither the dandiset's sums over 20 to 80 min nor, except in those 5 sessions, the sums over all time points; mouse 1945 session 1 has four time points that sum to 33.5. So the scores differ, not only the time points summed. Of the paper's 23 Figure 8 mice the dandiset holds 4 KO and 1 wild type, and of its 21 Figure 10 mice one per group. The genotype labels in the dandiset contradict each other (data check); the paper's labels are the ones used.
Claims on the dandiset: dSPNs (Figures 1 and 2)¶
fig1f-dspn-rheobase-on-lower¶
In dSPNs the rheobase is lower in the LID on-state than in the off-state.
def by_mouse(df, value, groups):
out = df[df.group.isin(groups)].groupby(["group", "date"])[value].agg(["size", "median"]).round(3)
return out
off, on = som[som.group == "dSPN off"], som[som.group == "dSPN on"]
r = mw(off.rheobase, on.rheobase)
print("Rheobase (pA), off (a) against on (b):", show(r))
print("Off values:", sorted(off.rheobase.tolist())); print("On values:", sorted(on.rheobase.tolist()))
print("\nBy mouse:"); print(by_mouse(som, "rheobase", ["dSPN off", "dSPN on"]).to_string())
mo = som[som.group.isin(["dSPN off", "dSPN on"])].groupby(["group", "date"]).rheobase.mean().reset_index()
print("\nVariant, one value per mouse (mean rheobase):", show(mw(mo[mo.group == "dSPN off"].rheobase, mo[mo.group == "dSPN on"].rheobase)))
Rheobase (pA), off (a) against on (b): {'n_a': 15, 'n_b': 15, 'median_a': 220.0, 'median_b': 140.0, 'U': 219.5, 'P': 8.86e-06, 'P_exact': nan, 'effect_r': -0.9511}
Off values: [160.0, 160.0, 180.0, 180.0, 180.0, 200.0, 200.0, 220.0, 260.0, 260.0, 260.0, 280.0, 280.0, 340.0, 340.0]
On values: [60.0, 80.0, 100.0, 100.0, 100.0, 120.0, 120.0, 140.0, 140.0, 140.0, 140.0, 140.0, 160.0, 160.0, 180.0]
By mouse:
size median
group date
dSPN off 20160812 1 260.0
20161111 2 180.0
20161114 2 220.0
20161116 1 340.0
20170202 3 260.0
20170203 3 220.0
20170204 2 260.0
20170525 1 160.0
dSPN on 20170706 2 110.0
20170707 2 110.0
20170719 2 140.0
20170720 2 140.0
20170721 1 140.0
20190411 1 100.0
20190412 1 120.0
20190501 1 120.0
20190503 1 100.0
20190507 2 150.0
Variant, one value per mouse (mean rheobase): {'n_a': 8, 'n_b': 10, 'median_a': 236.6667, 'median_b': 120.0, 'U': 80.0, 'P': 0.000425, 'P_exact': nan, 'effect_r': -1.0}
Result: reproduced. On-state median 140 pA against off-state 220 pA (15 cells each), MW P = 8.86e-06, effect r = -0.95. With one value per mouse (8 off, 10 on) P = 0.000425, and every on-state mouse is below every off-state mouse (r = -1.0).
fig1e-dspn-fi-on-higher¶
In on-state dSPNs suprathreshold current steps evoke more spikes than in off-state dSPNs across a range of intensities.
def fi_sum(df):
return df[amps].sum(axis=1, min_count=1)
r = mw(fi_sum(off), fi_sum(on))
print("Sum of spikes 20-300 pA, off (a) against on (b):", show(r))
complete = lambda df: df[df[amps].notna().all(axis=1)]
print("Variant, cells with every step only:", show(mw(fi_sum(complete(off)), fi_sum(complete(on)))))
print("\nMedian spike count by step (off / on) and per-step MW P:")
print(pd.DataFrame({a: [off[a].median(), on[a].median(), stats.mannwhitneyu(off[a].dropna(), on[a].dropna()).pvalue] for a in amps},
index=["off", "on", "P"]).round(3).to_string())
Sum of spikes 20-300 pA, off (a) against on (b): {'n_a': 15, 'n_b': 15, 'median_a': 36.0, 'median_b': 88.0, 'U': 17.5, 'P': 8.8e-05, 'P_exact': nan, 'effect_r': 0.8444}
Variant, cells with every step only: {'n_a': 10, 'n_b': 12, 'median_a': 37.5, 'median_b': 88.5, 'U': 2.0, 'P': 0.00015, 'P_exact': 1.24e-05, 'effect_r': 0.9667}
Median spike count by step (off / on) and per-step MW P:
20 40 60 80 100 120 140 160 180 200 220 240 260 280 300
off 0.0 0.0 0.000 0.00 0.000 0.000 0.0 0.0 0.0 0.0 4.0 5.0 7.0 8.500 9.000
on 0.0 0.0 0.000 0.00 0.000 0.500 3.0 6.0 8.0 10.0 11.0 12.5 14.0 15.000 15.000
P NaN NaN 0.351 0.18 0.032 0.002 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.003 0.012
Result: reproduced. Summed spikes from 20 to 300 pA: median 88 on against 36 off, MW P = 8.8e-05 (P = 0.00015 for the 22 cells with every step). The on-state median is higher at every step from 120 to 300 pA, with per-step P at most 0.012.
fig1f-dspn-sch-no-change¶
Bath application of the D1R antagonist SCH 23390 does not reverse the lower rheobase of on-state dSPNs.
sch = som[som.group == "dSPN on+SCH"]
r = mw(on.rheobase, sch.rheobase)
print("Rheobase, on (a) against on+SCH (b):", show(r))
print("On+SCH values:", sorted(sch.rheobase.tolist()))
paired = on.merge(sch, on=["date", "cell"], suffixes=("_on", "_sch"))
print(f"\nVariant, cells recorded in both conditions (n = {len(paired)}):")
print(paired[["date", "cell", "rheobase_on", "rheobase_sch"]].to_string(index=False))
w = stats.wilcoxon(paired.rheobase_on, paired.rheobase_sch) if (paired.rheobase_on != paired.rheobase_sch).any() else None
print("Wilcoxon:", None if w is None else f"statistic {w.statistic}, P = {w.pvalue:.3f}",
"; median paired difference (SCH - on):", float((paired.rheobase_sch - paired.rheobase_on).median()))
print("\nBy mouse:"); print(by_mouse(som, "rheobase", ["dSPN on+SCH"]).to_string())
Rheobase, on (a) against on+SCH (b): {'n_a': 15, 'n_b': 10, 'median_a': 140.0, 'median_b': 140.0, 'U': 74.5, 'P': 1.0, 'P_exact': nan, 'effect_r': 0.0067}
On+SCH values: [80.0, 80.0, 120.0, 120.0, 140.0, 140.0, 140.0, 140.0, 140.0, 160.0]
Variant, cells recorded in both conditions (n = 5):
date cell rheobase_on rheobase_sch
20190411 1 100.0 80.0
20190412 1 120.0 120.0
20190501 1 120.0 140.0
20190503 1 100.0 80.0
20190507 2 140.0 140.0
Wilcoxon: statistic 2.0, P = 1.000 ; median paired difference (SCH - on): 0.0
By mouse:
size median
group date
dSPN on+SCH 20190411 2 110.0
20190412 2 140.0
20190501 2 130.0
20190503 2 110.0
20190507 2 140.0
Result: reproduced. Rheobase on-state 140 pA (15 cells) against on-state with SCH 140 pA (10 cells), MW P = 1.0, effect r = 0.007. In the 5 cells recorded in both conditions the median paired difference is 0 pA (Wilcoxon P = 1.0).
fig1i-dspn-dendritic-on-higher¶
The dendritic excitability index of dSPNs is higher in the on-state than in the off-state.
dend_nobg = cell_index(pd.concat([lid.dendritic_sites(d, 0.0, 0.0).assign(date=d["date"], cell_type=d["cell_type"]) for d in days if d["dendritic"]], ignore_index=True))
dend_strict = cell_index(sites, (0.8, 1.25))
dend_all = cell_index(sites, None)
dend_lab = cell_index(sites_lab)
dend_full = cell_index(sites[sites.n_trials >= 3])
def dend_test(ga, gb):
out = {}
for name, tab in [("main", dend), ("no background", dend_nobg), ("basal 0.8-1.25", dend_strict), ("all pairs", dend_all), ("sites as labelled", dend_lab), ("sites with 3+ trials", dend_full)]:
out[name] = show(mw(tab[tab.group == ga]["index"], tab[tab.group == gb]["index"]))
return pd.DataFrame(out).T
print("Index, off (a) against on (b):"); print(dend_test("dSPN off", "dSPN on").to_string())
print("\nOff:", np.round(sorted(dend[dend.group == "dSPN off"]["index"].dropna()), 3))
print("On: ", np.round(sorted(dend[dend.group == "dSPN on"]["index"].dropna()), 3))
print("\nBy mouse:"); print(by_mouse(dend, "index", ["dSPN off", "dSPN on"]).to_string())
Index, off (a) against on (b):
n_a n_b median_a median_b U P P_exact effect_r
main 10.0 10.0 0.3298 0.4756 13.0 0.00580 0.00389 0.7400
no background 10.0 10.0 0.3299 0.4771 12.0 0.00459 0.00288 0.7600
basal 0.8-1.25 8.0 8.0 0.3290 0.4756 7.0 0.00699 0.00699 0.7812
all pairs 10.0 10.0 0.3298 0.4756 13.0 0.00580 0.00389 0.7400
sites as labelled 10.0 10.0 0.3525 0.4756 15.0 0.00911 0.00684 0.7000
sites with 3+ trials 10.0 10.0 0.3298 0.4756 13.0 0.00580 0.00389 0.7400
Off: [0.139 0.265 0.293 0.317 0.327 0.332 0.373 0.451 0.468 0.475]
On: [0.374 0.393 0.42 0.424 0.475 0.477 0.547 0.61 0.617 0.725]
By mouse:
size median
group date
dSPN off 20160812 1 0.468
20161111 2 0.412
20161114 1 0.475
20161116 1 0.317
20170202 1 0.139
20170203 2 0.299
20170204 2 0.310
dSPN on 20170706 2 0.559
20170707 2 0.579
20170719 2 0.447
20170720 2 0.450
20170721 2 0.496
Result: reproduced. Index on-state 0.476 against off-state 0.330 (10 cells each), MW P = 0.0058 (exact 0.0039), effect r = 0.74. It holds in every variant: without background P = 0.0046, with a basal-ratio rule of 0.8 to 1.25 (8 cells each) P = 0.0070, with the sites as labelled P = 0.0091.
fig1i-dspn-dendritic-sch-lower¶
SCH 23390 reverses the higher dendritic excitability of on-state dSPNs.
print("Index, on (a) against on+SCH (b):"); print(dend_test("dSPN on", "dSPN on+SCH").to_string())
print("\nOn+SCH:", np.round(sorted(dend[dend.group == "dSPN on+SCH"]["index"].dropna()), 3))
print("Cells with an on-state index recorded in the SCH files:", int(((dend.group == "dSPN on") & dend.date.str.startswith("2019")).sum()))
print("\nBy mouse:"); print(by_mouse(dend, "index", ["dSPN on+SCH"]).to_string())
far = dpairs[dpairs.group == "dSPN on+SCH"].sort_values("rel_diff").iloc[-1]
print(f"\nSCH cell farthest from the paper's value (matched by order): {far.date} cell {far.cell}, index {far['index']:.3f}, "
f"paper {far.paper:.3f}, sites {far.sites}")
print(sites[(sites.date == far.date) & (sites.cell == far.cell)][["compartment", "site", "series", "n_trials", "R0", "G0", "area"]].round(4).to_string(index=False))
sch_on = dend[dend.group == "dSPN on"]["index"]
sch_rest = dend[(dend.group == "dSPN on+SCH") & ~((dend.date == far.date) & (dend.cell == far.cell))]["index"]
print("Variant, without that cell:", show(mw(sch_on, sch_rest)))
print("Variant, with the paper's value for that cell:", show(mw(sch_on, pd.concat([sch_rest, pd.Series([far.paper])]))))
Index, on (a) against on+SCH (b):
n_a n_b median_a median_b U P P_exact effect_r
main 10.0 9.0 0.4756 0.4191 68.0 0.0662 0.0653 -0.5111
no background 10.0 9.0 0.4771 0.4192 68.0 0.0662 0.0653 -0.5111
basal 0.8-1.25 8.0 5.0 0.4756 0.4239 25.0 0.5240 0.5240 -0.2500
all pairs 10.0 9.0 0.4756 0.4191 68.0 0.0662 0.0653 -0.5111
sites as labelled 10.0 9.0 0.4756 0.4191 68.0 0.0662 0.0653 -0.5111
sites with 3+ trials 10.0 9.0 0.4756 0.4191 68.0 0.0662 0.0653 -0.5111
On+SCH: [0.22 0.289 0.303 0.349 0.419 0.42 0.431 0.494 0.584]
Cells with an on-state index recorded in the SCH files: 0
By mouse:
size median
group date
dSPN on+SCH 20190411 2 0.443
20190412 2 0.457
20190501 1 0.220
20190503 2 0.355
20190507 2 0.390
SCH cell farthest from the paper's value (matched by order): 20190411 cell 1, index 0.584, paper 0.228, sites D2 P2
compartment site series n_trials R0 G0 area
Proximal 2 RoiResponseSeriesFluo4Cell1Proximal2 3 2568.2180 216.8403 0.0609
Distal 2 RoiResponseSeriesFluo4Cell1Distal2 3 2786.3757 255.5474 0.0356
Variant, without that cell: {'n_a': 10, 'n_b': 8, 'median_a': 0.4756, 'median_b': 0.3839, 'U': 65.0, 'P': 0.0266, 'P_exact': 0.0266, 'effect_r': -0.625}
Variant, with the paper's value for that cell: {'n_a': 10, 'n_b': 9, 'median_a': 0.4756, 'median_b': 0.3486, 'U': 75.0, 'P': 0.016, 'P_exact': 0.0133, 'effect_r': -0.6667}
Result: not reproduced. The criterion (P < 0.05 with the SCH median lower) is not met, though the difference has the stated direction: on-state with SCH 0.419 (9 cells) against on-state 0.476 (10 cells), MW P = 0.0662 (exact 0.0653), effect r = -0.51. With the stricter basal-ratio rule P = 0.524. The difference from the paper comes from one cell. SCH 20190411 cell 1 has an index of 0.584 here and 0.228 in the paper's table, and the dandiset holds only its second pair of sites. Without that cell P = 0.0266; with the paper's value for it P = 0.016 (exact 0.0133, the P the paper reports). The other eight SCH cells differ from the paper's values by 2.4% to 10.9%. That the missing sites explain the difference is an inference from the recovered P; the paper does not list the sites it used for each cell.
Claims on the dandiset: iSPNs (Figure 3)¶
fig3d-ispn-rheobase-on-higher¶
In iSPNs the rheobase is higher in the on-state than in the off-state.
ioff, ion, isul = som[som.group == "iSPN off"], som[som.group == "iSPN on"], som[som.group == "iSPN on+sul"]
print("Rheobase, off (a) against on (b):", show(mw(ioff.rheobase, ion.rheobase)))
print("Off:", sorted(ioff.rheobase.tolist())); print("On: ", sorted(ion.rheobase.tolist()))
print("\nBy mouse:"); print(by_mouse(som, "rheobase", ["iSPN off", "iSPN on"]).to_string())
mo = som[som.group.isin(["iSPN off", "iSPN on"])].groupby(["group", "date"]).rheobase.mean().reset_index()
print("\nVariant, one value per mouse:", show(mw(mo[mo.group == "iSPN off"].rheobase, mo[mo.group == "iSPN on"].rheobase)))
Rheobase, off (a) against on (b): {'n_a': 12, 'n_b': 11, 'median_a': 120.0, 'median_b': 160.0, 'U': 12.5, 'P': 0.000829, 'P_exact': nan, 'effect_r': 0.8106}
Off: [80.0, 80.0, 120.0, 120.0, 120.0, 120.0, 120.0, 140.0, 140.0, 140.0, 140.0, 140.0]
On: [120.0, 140.0, 140.0, 160.0, 160.0, 160.0, 180.0, 180.0, 200.0, 220.0, 240.0]
By mouse:
size median
group date
iSPN off 20160523 4 120.0
20160524 3 120.0
20161116 1 140.0
20170525 1 140.0
20170526 3 120.0
iSPN on 20160609 2 160.0
20160718 2 190.0
20160719 2 210.0
20190408 1 180.0
20190409 1 140.0
20190426 1 160.0
20190429 1 160.0
20190508 1 140.0
Variant, one value per mouse: {'n_a': 5, 'n_b': 8, 'median_a': 126.6667, 'median_b': 160.0, 'U': 2.0, 'P': 0.00899, 'P_exact': nan, 'effect_r': 0.9}
Result: reproduced. iSPN rheobase on-state 160 pA (11 cells) against off-state 120 pA (12 cells), MW P = 0.000829, effect r = 0.81. With one value per mouse (5 off, 8 on) P = 0.00899.
fig3c-ispn-fi-on-lower¶
The F-I relationship of iSPNs shifts to the right in the on-state.
print("Sum of spikes 20-300 pA, off (a) against on (b):", show(mw(fi_sum(ioff), fi_sum(ion))))
print("Variant, cells with every step only:", show(mw(fi_sum(complete(ioff)), fi_sum(complete(ion)))))
print(pd.DataFrame({a: [ioff[a].median(), ion[a].median(), stats.mannwhitneyu(ioff[a].dropna(), ion[a].dropna()).pvalue] for a in amps},
index=["off", "on", "P"]).round(3).to_string())
Sum of spikes 20-300 pA, off (a) against on (b): {'n_a': 12, 'n_b': 11, 'median_a': 118.5, 'median_b': 65.0, 'U': 131.0, 'P': 7.2e-05, 'P_exact': 2.96e-06, 'effect_r': -0.9848}
Variant, cells with every step only: {'n_a': 11, 'n_b': 10, 'median_a': 119.0, 'median_b': 59.0, 'U': 109.0, 'P': 0.000165, 'P_exact': 1.13e-05, 'effect_r': -0.9818}
20 40 60 80 100 120 140 160 180 200 220 240 260 280 300
off 0.0 0.0 0.0 0.000 0.000 1.000 4.0 8.0 10.5 12.0 14.5 15.0 16.5 17.5 18.500
on 0.0 0.0 0.0 0.000 0.000 0.000 0.0 1.0 3.0 5.0 8.0 9.0 11.0 11.5 13.000
P NaN NaN NaN 0.186 0.186 0.012 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.001
Result: reproduced. Summed spikes 65 on against 118.5 off, MW P = 7.2e-05 (0.000165 for cells with every step). The on-state median is lower at every step from 120 to 300 pA, with per-step P at most 0.012.
fig3d-ispn-sul-no-change¶
Sulpiride does not alter the somatic excitability of on-state iSPNs.
print("Rheobase, on (a) against on+sul (b):", show(mw(ion.rheobase, isul.rheobase)))
print("On+sul:", sorted(isul.rheobase.tolist()))
paired = ion.merge(isul, on=["date", "cell"], suffixes=("_on", "_sul"))
print(f"\nVariant, cells recorded in both conditions (n = {len(paired)}):")
print(paired[["date", "cell", "rheobase_on", "rheobase_sul"]].to_string(index=False))
print("By mouse:"); print(by_mouse(som, "rheobase", ["iSPN on+sul"]).to_string())
Rheobase, on (a) against on+sul (b): {'n_a': 11, 'n_b': 7, 'median_a': 160.0, 'median_b': 120.0, 'U': 55.0, 'P': 0.143, 'P_exact': nan, 'effect_r': -0.4286}
On+sul: [100.0, 100.0, 100.0, 120.0, 160.0, 200.0, 200.0]
Variant, cells recorded in both conditions (n = 2):
date cell rheobase_on rheobase_sul
20190426 2 160.0 120.0
20190429 1 160.0 100.0
By mouse:
size median
group date
iSPN on+sul 20190426 2 140.0
20190429 3 100.0
20190508 1 200.0
20190509 1 200.0
Result: reproduced. Rheobase on-state 160 pA (11 cells) against on-state with sulpiride 120 pA (7 cells), MW P = 0.143, effect r = -0.43. The difference leans toward a lower rheobase with sulpiride, as in the two cells recorded in both conditions (160 to 120 and 160 to 100 pA).
fig3f-ispn-dendritic-on-lower¶
The dendritic excitability index of iSPNs falls in the on-state compared with the off-state.
print("Index, off (a) against on (b):"); print(dend_test("iSPN off", "iSPN on").to_string())
print("\nOff:", np.round(sorted(dend[dend.group == "iSPN off"]["index"].dropna()), 3))
print("On: ", np.round(sorted(dend[dend.group == "iSPN on"]["index"].dropna()), 3))
print("\nBy mouse:"); print(by_mouse(dend, "index", ["iSPN off", "iSPN on"]).to_string())
Index, off (a) against on (b):
n_a n_b median_a median_b U P P_exact effect_r
main 10.0 10.0 0.5236 0.3392 86.0 0.00728 0.00520 -0.7200
no background 10.0 10.0 0.5230 0.3385 86.0 0.00728 0.00520 -0.7200
basal 0.8-1.25 9.0 9.0 0.5432 0.3397 61.0 0.07740 0.07700 -0.5062
all pairs 10.0 10.0 0.5236 0.3392 86.0 0.00728 0.00520 -0.7200
sites as labelled 10.0 9.0 0.5236 0.3397 76.0 0.01280 0.01010 -0.6889
sites with 3+ trials 10.0 10.0 0.5236 0.3392 85.0 0.00911 0.00684 -0.7000
Off: [0.388 0.396 0.462 0.488 0.504 0.543 0.623 0.731 0.763 1.062]
On: [0.146 0.234 0.252 0.295 0.339 0.34 0.398 0.418 0.475 0.644]
By mouse:
size median
group date
iSPN off 20160523 4 0.637
20160524 3 0.623
20161116 1 0.388
20170525 1 0.488
20170526 1 0.462
iSPN on 20160608 3 0.234
20160609 2 0.560
20160718 2 0.325
20160719 2 0.356
20190408 1 0.340
Result: reproduced. Index on-state 0.339 (10 cells) against off-state 0.524 (10 cells), MW P = 0.0073 (exact 0.0052), effect r = -0.72. Without background P = 0.0073; with the sites as labelled (9 on-state cells) P = 0.0128; dropping sites with fewer than 3 trials P = 0.0091. With the stricter basal-ratio rule (9 cells each) P = 0.077, so the result depends on keeping pairs whose basal ratios differ by more than 25%. The stricter rule moves fig3f-ispn-dendritic-sul-no-change the other way (P = 0.0663, which would meet that criterion).
fig3f-ispn-dendritic-sul-no-change¶
Sulpiride does not significantly alter dendritic excitability in on-state iSPNs.
print("Index, on (a) against on+sul (b):"); print(dend_test("iSPN on", "iSPN on+sul").to_string())
print("\nOn+sul:", np.round(sorted(dend[dend.group == "iSPN on+sul"]["index"].dropna()), 3))
print("By mouse:"); print(by_mouse(dend, "index", ["iSPN on+sul"]).to_string())
sul_all = dend[dend.group == "iSPN on+sul"].reset_index(drop=True)
paper_sul = lid.tidy_group(tidy, lid.TIDY_DENDRITIC["iSPN on+sul"]).iloc[:, -1].to_numpy(float)
k = int(np.flatnonzero(sul_all["index"].isna())[0])
print(f"\nCell without an index: {sul_all.date[k]} cell {sul_all.cell[k]} (sites {sul_all.sites[k]}); the paper's value at its rank: {paper_sul[k]:.3f}")
print("Variant, with the paper's value for that cell:",
show(mw(dend[dend.group == "iSPN on"]["index"], pd.concat([sul_all["index"].dropna(), pd.Series([paper_sul[k]])]))))
Index, on (a) against on+sul (b):
n_a n_b median_a median_b U P P_exact effect_r
main 10.0 7.0 0.3392 0.5060 9.0 0.00967 0.00967 0.7429
no background 10.0 7.0 0.3385 0.5072 9.0 0.00967 0.00967 0.7429
basal 0.8-1.25 9.0 6.0 0.3397 0.4969 11.0 0.06630 0.06630 0.5926
all pairs 10.0 7.0 0.3392 0.5060 9.0 0.00967 0.00967 0.7429
sites as labelled 9.0 7.0 0.3397 0.5060 9.0 0.01640 0.01640 0.7143
sites with 3+ trials 10.0 7.0 0.3392 0.5060 9.0 0.00967 0.00967 0.7429
On+sul: [0.389 0.43 0.488 0.506 0.521 0.703 0.852]
By mouse:
size median
group date
iSPN on+sul 20190408 1 0.430
20190409 1 0.488
20190426 2 0.455
20190429 2 0.778
20190508 1 NaN
20190509 1 0.506
Cell without an index: 20190508 cell 3 (sites D2); the paper's value at its rank: 0.217
Variant, with the paper's value for that cell: {'n_a': 10, 'n_b': 8, 'median_a': 0.3392, 'median_b': 0.4969, 'U': 18.0, 'P': 0.0545, 'P_exact': 0.0545, 'effect_r': 0.55}
Result: not reproduced. The paper's test finds the difference the claim says is absent: on-state with sulpiride 0.506 (7 cells) against on-state 0.339 (10 cells), MW P = 0.0097, effect r = 0.74 (the paper reports P = 0.0545 with 8 and 10 cells). With the sites as labelled P = 0.0164 and with the stricter basal-ratio rule P = 0.0663. The paper's own medians go the same way (0.499 and 0.345, link section). The sulpiride cell without an index here (20190508 cell 3, only a distal site) has 0.217 in the paper's table; with that value P = 0.0545, the P the paper reports. Under the main basal-ratio rule the outcome turns on that one cell. Under the stricter rule (0.8 to 1.25) P = 0.0663 without it, which would also meet the criterion, so the outcome depends on the basal-ratio rule as well. That the missing sites explain the difference is an inference from the recovered P; the paper does not list the sites it used for each cell.
Claims on the dandiset: ACh release (Figure 5)¶
fig5f-ach-off-elevated¶
Evoked ACh release (GRAB-ACh3.0) in slices from LID off-state mice is greater than in unlesioned striatum.
nod = glink.set_index("state")["No drug"]
print("Single-pulse normalised area, no drug, unlesioned (a) against off-state (b):", show(mw(nod["CTRL"], nod["OFF"])))
print("Unlesioned:", nod["CTRL"].round(3).tolist(), "; off-state:", nod["OFF"].round(3).tolist())
print("Smallest attainable two-sided P for 3 against 5 values: %.4f" % (2 / 56))
burst = []
for m in grabs:
gm = lid.grab_measures(m)
b = gm[(gm.stimulation == "burst_stimulation") & (gm.treatment == "control")]
burst.append({"state": m["state"], "auc": float(b.auc_norm.iloc[0]) if len(b) else np.nan})
burst = pd.DataFrame(burst)
print("Variant, burst stimulation (fig. S1):", show(mw(burst[burst.state == "CTRL"].auc, burst[burst.state == "OFF"].auc)))
gd = []
for m in grabs:
gm = lid.grab_measures(m)
gd.append({"state": m["state"], "dff0": float(gm[(gm.stimulation == "single_pulse") & (gm.treatment == "control")].auc_dff0.iloc[0])})
gd = pd.DataFrame(gd)
print("Variant, unnormalised dF/F0 area (background 162):", show(mw(gd[gd.state == "CTRL"].dff0, gd[gd.state == "OFF"].dff0)))
Single-pulse normalised area, no drug, unlesioned (a) against off-state (b): {'n_a': 3, 'n_b': 5, 'median_a': 0.112, 'median_b': 0.359, 'U': 0.0, 'P': 0.0357, 'P_exact': 0.0357, 'effect_r': 1.0}
Unlesioned: [0.112, 0.113, 0.089] ; off-state: [0.193, 0.359, 0.296, 0.401, 0.723]
Smallest attainable two-sided P for 3 against 5 values: 0.0357
Variant, burst stimulation (fig. S1): {'n_a': 3, 'n_b': 5, 'median_a': 0.2853, 'median_b': 1.0095, 'U': 0.0, 'P': 0.0357, 'P_exact': 0.0357, 'effect_r': 1.0}
Variant, unnormalised dF/F0 area (background 162): {'n_a': 3, 'n_b': 5, 'median_a': 0.0768, 'median_b': 0.1375, 'U': 3.0, 'P': 0.25, 'P_exact': 0.25, 'effect_r': 0.6}
Result: reproduced. Off-state 0.359 (5 mice) against unlesioned 0.112 (3 mice), MW P = 0.0357, the smallest attainable with these numbers: every off-state value is above every unlesioned one. Burst stimulation gives the same (P = 0.0357); the unnormalised dF/F0 area does not (P = 0.25).
fig5f-ach-off-like-6ohda¶
Evoked ACh release in the off-state is similar to that after a 6-OHDA lesion.
print("Single-pulse normalised area, no drug, 6-OHDA (a) against off-state (b):", show(mw(nod["PD"], nod["OFF"])))
print("6-OHDA:", nod["PD"].round(3).tolist())
print("Variant, burst:", show(mw(burst[burst.state == "PD"].auc, burst[burst.state == "OFF"].auc)))
Single-pulse normalised area, no drug, 6-OHDA (a) against off-state (b): {'n_a': 3, 'n_b': 5, 'median_a': 0.24, 'median_b': 0.359, 'U': 2.0, 'P': 0.143, 'P_exact': 0.143, 'effect_r': 0.7333}
6-OHDA: [0.24, 0.269, 0.179]
Variant, burst: {'n_a': 3, 'n_b': 5, 'median_a': 1.4329, 'median_b': 1.0095, 'U': 8.0, 'P': 1.0, 'P_exact': 1.0, 'effect_r': -0.0667}
Result: reproduced. 6-OHDA 0.240 (3 mice) against off-state 0.359 (5 mice), MW P = 0.143, effect r = 0.73; with burst stimulation P = 1.0. With 3 and 5 mice only complete separation would be significant (P = 0.0357), so the test detects only a large difference.
Claims on the dandiset: M1R and CDGI (Figures 6, 7 and 9)¶
fig6c-m1r-antag-somatic-unchanged¶
M1R antagonism throughout the off-state does not have a strong impact on the elevated somatic excitability of off-state iSPNs.
veh, ant = som[som.group == "Fig6 vehicle"], som[som.group == "Fig6 M1R antagonist"]
print("Rheobase, vehicle (a) against M1R antagonist (b):", show(mw(veh.rheobase, ant.rheobase)))
print("Vehicle:", sorted(veh.rheobase.tolist()), "; antagonist:", sorted(ant.rheobase.tolist()))
print("Variant, sum of spikes 20-300 pA:", show(mw(fi_sum(veh), fi_sum(ant))))
print("By mouse:"); print(by_mouse(som, "rheobase", ["Fig6 vehicle", "Fig6 M1R antagonist"]).to_string())
Rheobase, vehicle (a) against M1R antagonist (b): {'n_a': 8, 'n_b': 10, 'median_a': 120.0, 'median_b': 140.0, 'U': 35.0, 'P': 0.681, 'P_exact': nan, 'effect_r': 0.125}
Vehicle: [80.0, 80.0, 100.0, 120.0, 120.0, 140.0, 160.0, 160.0] ; antagonist: [60.0, 100.0, 120.0, 120.0, 140.0, 140.0, 140.0, 140.0, 140.0, 160.0]
Variant, sum of spikes 20-300 pA: {'n_a': 8, 'n_b': 10, 'median_a': 139.0, 'median_b': 111.0, 'U': 49.0, 'P': 0.45, 'P_exact': nan, 'effect_r': -0.225}
By mouse:
size median
group date
Fig6 M1R antagonist 20200218 2 130.0
20200220 2 130.0
20200312 2 100.0
20200721 3 140.0
20200722 1 100.0
Fig6 vehicle 20200217 2 90.0
20200225 3 160.0
20200811 1 120.0
20200812 2 110.0
Result: reproduced. Rheobase vehicle 120 pA (8 cells) against M1R antagonist 140 pA (10 cells), MW P = 0.681, effect r = 0.125; summed spikes P = 0.45.
fig6d-m1r-antag-dendritic-lower¶
M1R antagonism prevents the off-state elevation of iSPN dendritic excitability.
print("Index, vehicle (a) against M1R antagonist (b):"); print(dend_test("Fig6 vehicle", "Fig6 M1R antagonist").to_string())
print("\nVehicle:", np.round(sorted(dend[dend.group == "Fig6 vehicle"]["index"].dropna()), 3))
print("Antagonist:", np.round(sorted(dend[dend.group == "Fig6 M1R antagonist"]["index"].dropna()), 3))
print("By mouse:"); print(by_mouse(dend, "index", ["Fig6 vehicle", "Fig6 M1R antagonist"]).to_string())
Index, vehicle (a) against M1R antagonist (b):
n_a n_b median_a median_b U P P_exact effect_r
main 9.0 7.0 0.4515 0.2689 53.0 0.0229 0.0229 -0.6825
no background 9.0 7.0 0.4525 0.2695 53.0 0.0229 0.0229 -0.6825
basal 0.8-1.25 9.0 7.0 0.4515 0.2689 54.0 0.0164 0.0164 -0.7143
all pairs 9.0 7.0 0.4515 0.2689 53.0 0.0229 0.0229 -0.6825
sites as labelled 9.0 7.0 0.4515 0.2689 53.0 0.0229 0.0229 -0.6825
sites with 3+ trials 9.0 7.0 0.4515 0.2689 53.0 0.0229 0.0229 -0.6825
Vehicle: [0.23 0.361 0.421 0.435 0.451 0.462 0.49 0.531 0.655]
Antagonist: [0.168 0.183 0.194 0.269 0.319 0.366 0.465]
By mouse:
size median
group date
Fig6 M1R antagonist 20200218 2 0.318
20200220 1 0.319
20200312 2 0.329
20240430 2 0.175
Fig6 vehicle 20200217 2 0.491
20200225 2 0.360
20200811 1 0.462
20200812 2 0.545
20240425 2 0.391
Result: reproduced. Index vehicle 0.451 (9 cells) against M1R antagonist 0.269 (7 cells), MW P = 0.0229, effect r = -0.68; with the stricter basal-ratio rule P = 0.0164.
fig7c-cdgi-ko-somatic-oscillation¶
Deletion of CDGI does not blunt the oscillation of iSPN somatic excitability between on- and off-states.
koff, kon = som[som.group == "KO off"], som[som.group == "KO on"]
print("Rheobase, KO off (a) against KO on (b):", show(mw(koff.rheobase, kon.rheobase)))
print("KO off:", sorted(koff.rheobase.tolist()), "; KO on:", sorted(kon.rheobase.tolist()))
print("Variant, sum of spikes:", show(mw(fi_sum(koff), fi_sum(kon))))
print("By mouse:"); print(by_mouse(som, "rheobase", ["KO off", "KO on"]).to_string())
Rheobase, KO off (a) against KO on (b): {'n_a': 13, 'n_b': 8, 'median_a': 140.0, 'median_b': 210.0, 'U': 7.0, 'P': 0.00104, 'P_exact': nan, 'effect_r': 0.8654}
KO off: [120.0, 120.0, 120.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 160.0, 160.0, 180.0, 200.0] ; KO on: [160.0, 160.0, 200.0, 200.0, 220.0, 240.0, 240.0, 260.0]
Variant, sum of spikes: {'n_a': 13, 'n_b': 8, 'median_a': 100.0, 'median_b': 37.0, 'U': 93.0, 'P': 0.00335, 'P_exact': nan, 'effect_r': -0.7885}
By mouse:
size median
group date
KO off 20160525 3 140.0
20160526 3 140.0
20160527 3 180.0
20170616 2 130.0
20170619 2 150.0
KO on 20160816 2 210.0
20160901 1 240.0
20160902 3 160.0
20160913 2 220.0
Result: reproduced. In CDGI KO iSPNs the rheobase is 210 pA on-state (8 cells) against 140 pA off-state (13 cells), MW P = 0.00104, effect r = 0.87; summed spikes P = 0.00335.
fig7f-cdgi-ko-dendritic-unchanged¶
The off-state elevation of iSPN dendritic excitability is absent in CDGI KO mice (off against on not different), while it is present in wild type.
print("Index, KO off (a) against KO on (b):"); print(dend_test("KO off", "KO on").to_string())
print("\nKO off:", np.round(sorted(dend[dend.group == "KO off"]["index"].dropna()), 3))
print("KO on: ", np.round(sorted(dend[dend.group == "KO on"]["index"].dropna()), 3))
print("Wild type (fig3f):", show(mw(dend[dend.group == "iSPN off"]["index"], dend[dend.group == "iSPN on"]["index"])))
print("By mouse:"); print(by_mouse(dend, "index", ["KO off", "KO on"]).to_string())
Index, KO off (a) against KO on (b):
n_a n_b median_a median_b U P P_exact effect_r
main 11.0 8.0 0.3117 0.2859 57.0 0.310 0.310 -0.2955
no background 11.0 8.0 0.3106 0.2859 57.0 0.310 0.310 -0.2955
basal 0.8-1.25 11.0 7.0 0.3826 0.3252 44.0 0.659 0.659 -0.1429
all pairs 11.0 8.0 0.3117 0.2859 57.0 0.310 0.310 -0.2955
sites as labelled 10.0 8.0 0.3472 0.2859 52.0 0.315 0.315 -0.3000
sites with 3+ trials 11.0 8.0 0.3117 0.2859 57.0 0.310 0.310 -0.2955
KO off: [0.176 0.293 0.299 0.303 0.309 0.312 0.383 0.433 0.451 0.562 0.589]
KO on: [0.205 0.212 0.279 0.284 0.288 0.314 0.5 0.661]
Wild type (fig3f): {'n_a': 10, 'n_b': 10, 'median_a': 0.5236, 'median_b': 0.3392, 'U': 86.0, 'P': 0.00728, 'P_exact': 0.0052, 'effect_r': -0.72}
By mouse:
size median
group date
KO off 20160525 3 0.309
20160526 3 0.303
20160527 2 0.520
20170616 1 0.383
20170619 2 0.366
KO on 20160816 2 0.246
20160901 1 0.661
20160902 3 0.279
20160913 2 0.299
Result: reproduced. KO index off-state 0.312 (11 cells) against on-state 0.286 (8 cells), MW P = 0.310, effect r = -0.30 (P = 0.659 with the stricter rule, 0.315 with the sites as labelled), while the wild-type difference is significant (P = 0.0073).
fig9d-m1r-crispr-somatic-unchanged¶
Deletion of M1Rs from iSPNs does not alter the elevated somatic excitability of off-state iSPNs.
cc, cr = som[som.group == "Fig9 control"], som[som.group == "Fig9 M1R CRISPR"]
print("Rheobase, control (a) against M1R CRISPR (b):", show(mw(cc.rheobase, cr.rheobase)))
print("Control:", sorted(cc.rheobase.tolist()), "; CRISPR:", sorted(cr.rheobase.tolist()))
print("Variant, sum of spikes:", show(mw(fi_sum(cc), fi_sum(cr))))
print("By mouse:"); print(by_mouse(som, "rheobase", ["Fig9 control", "Fig9 M1R CRISPR"]).to_string())
Rheobase, control (a) against M1R CRISPR (b): {'n_a': 6, 'n_b': 8, 'median_a': 160.0, 'median_b': 120.0, 'U': 35.5, 'P': 0.147, 'P_exact': nan, 'effect_r': -0.4792}
Control: [80.0, 120.0, 140.0, 180.0, 180.0, 200.0] ; CRISPR: [100.0, 100.0, 100.0, 120.0, 120.0, 120.0, 140.0, 140.0]
Variant, sum of spikes: {'n_a': 6, 'n_b': 8, 'median_a': 99.0, 'median_b': 138.5, 'U': 14.5, 'P': 0.245, 'P_exact': nan, 'effect_r': 0.3958}
By mouse:
size median
group date
Fig9 M1R CRISPR 20221004 2 110.0
20221005 3 120.0
20221129 3 140.0
Fig9 control 20221012 3 120.0
20221130 3 180.0
Result: reproduced. Rheobase interleaved control 160 pA (6 cells) against M1R CRISPR 120 pA (8 cells), MW P = 0.147, effect r = -0.48; summed spikes P = 0.245.
Claims on the paper's tables (data elsewhere)¶
These claims test data the dandiset does not hold, on the paper's per-cell and per-animal tables (Zenodo doi:10.5281/zenodo.17307059). They say nothing about the dandiset.
fig1-dspn-lesion-and-off-somatic¶
dSPN somatic excitability (rheobase) rises after the 6-OHDA lesion and falls in the LID off-state.
def tg(fig, sheet, group):
return lid.tidy_group(tidy, (fig, sheet, group)).iloc[:, -1].astype(float)
print("Control (a) against 6-OHDA (b):", show(mw(tg("Fig 1", "Fig 1F_dSPN rheobase", "control"), tg("Fig 1", "Fig 1F_dSPN rheobase", "6-OHDA"))))
print("6-OHDA (a) against off (b):", show(mw(tg("Fig 1", "Fig 1F_dSPN rheobase", "6-OHDA"), tg("Fig 1", "Fig 1F_dSPN rheobase", "LID off"))))
Control (a) against 6-OHDA (b): {'n_a': 10, 'n_b': 12, 'median_a': 220.0, 'median_b': 120.0, 'U': 115.0, 'P': 0.000295, 'P_exact': nan, 'effect_r': -0.9167}
6-OHDA (a) against off (b): {'n_a': 12, 'n_b': 15, 'median_a': 120.0, 'median_b': 200.0, 'U': 10.0, 'P': 9.54e-05, 'P_exact': nan, 'effect_r': 0.8889}
Result: reproduced. On the paper's table, the rheobase is 120 pA after the lesion against 220 pA in controls (P = 0.000295) and 200 pA in the off-state against 120 pA after the lesion (P = 9.54e-05). The paper reports P < 0.0001 for both; the first is 0.000295 here.
fig1-dspn-lesion-dendritic-unchanged¶
The dSPN dendritic excitability index is not changed by the 6-OHDA lesion or in the off-state.
sh = "Fig 1I_dSPN dendritic excitabil"
print("Control (a) against 6-OHDA (b):", show(mw(tg("Fig 1", sh, "control"), tg("Fig 1", sh, "6-OHDA"))))
print("6-OHDA (a) against off (b):", show(mw(tg("Fig 1", sh, "6-OHDA"), tg("Fig 1", sh, "LID off"))))
Control (a) against 6-OHDA (b): {'n_a': 11, 'n_b': 9, 'median_a': 0.3576, 'median_b': 0.3924, 'U': 46.0, 'P': 0.82, 'P_exact': 0.824, 'effect_r': 0.0707}
6-OHDA (a) against off (b): {'n_a': 9, 'n_b': 10, 'median_a': 0.3924, 'median_b': 0.3332, 'U': 47.0, 'P': 0.903, 'P_exact': 0.905, 'effect_r': -0.0444}
Result: reproduced. On the paper's table, control against 6-OHDA P = 0.82 and 6-OHDA against off-state P = 0.903 (the paper reports 0.824 and 0.905).
fig3-ispn-lesion-and-off-somatic¶
iSPN somatic excitability is lowered by the 6-OHDA lesion and restored in the LID off-state.
sh = "Fig 3D_iSPN rheobase"
print("Control (a) against 6-OHDA (b):", show(mw(tg("Fig 3", sh, "Control"), tg("Fig 3", sh, "6-OHDA"))))
print("6-OHDA (a) against off (b):", show(mw(tg("Fig 3", sh, "6-OHDA"), tg("Fig 3", sh, "LID off"))))
Control (a) against 6-OHDA (b): {'n_a': 21, 'n_b': 11, 'median_a': 120.0, 'median_b': 200.0, 'U': 28.0, 'P': 0.000401, 'P_exact': nan, 'effect_r': 0.7576}
6-OHDA (a) against off (b): {'n_a': 11, 'n_b': 12, 'median_a': 200.0, 'median_b': 130.0, 'U': 121.0, 'P': 0.000608, 'P_exact': nan, 'effect_r': -0.8333}
Result: reproduced. On the paper's table, the rheobase is 200 pA after the lesion against 120 pA in controls (P = 0.000401) and 130 pA in the off-state (P = 0.000608 against 6-OHDA); the paper reports P = 0.002 for both.
fig3-ispn-lesion-dendritic-up¶
The iSPN dendritic excitability index rises after the lesion and stays raised in the off-state.
sh = "Fig 3F_iSPN dendritic excitabil"
print("Control (a) against 6-OHDA (b):", show(mw(tg("Fig 3", sh, "Control"), tg("Fig 3", sh, "6-OHDA"))))
print("Control (a) against off (b):", show(mw(tg("Fig 3", sh, "Control"), tg("Fig 3", sh, "LID off"))))
Control (a) against 6-OHDA (b): {'n_a': 14, 'n_b': 9, 'median_a': 0.4047, 'median_b': 0.5161, 'U': 29.0, 'P': 0.0348, 'P_exact': 0.0327, 'effect_r': 0.5397}
Control (a) against off (b): {'n_a': 14, 'n_b': 10, 'median_a': 0.4047, 'median_b': 0.5187, 'U': 31.0, 'P': 0.0242, 'P_exact': 0.022, 'effect_r': 0.5571}
Result: reproduced. On the paper's table, the index is 0.516 after the lesion and 0.519 in the off-state against 0.405 in controls (P = 0.0348 and 0.0242; the paper reports 0.0327 and 0.0220).
fig7e-oxom-cdgi¶
Oxotremorine-M increases bAP invasion of iSPN dendrites in wild-type but not in CDGI KO mice.
t7 = tidy["Fig 7"]["Fig 7E_oxoM effect on DE"].set_axis(["expt", "genotype", "treatment", "index"], axis=1)
for gt in ("WT", "KO"):
w = t7[t7.genotype == gt].pivot(index="expt", columns="treatment", values="index")
res = stats.wilcoxon(w["before (control)"], w["oxoM"])
print(f"{gt}: n = {len(w)}, median before {w['before (control)'].median():.3f}, after {w['oxoM'].median():.3f}, "
f"increases {(w['oxoM'] > w['before (control)']).sum()}, Wilcoxon P = {res.pvalue:.4g}")
print(f" variant, unpaired Mann-Whitney: P = {stats.mannwhitneyu(w['before (control)'], w['oxoM']).pvalue:.4g}")
if gt == "KO":
print(" KO pairs (rows paired by the table's experiment number):")
print(w.assign(change=w["oxoM"] - w["before (control)"]).round(3).to_string())
WT: n = 10, median before 0.214, after 0.335, increases 8, Wilcoxon P = 0.009766 variant, unpaired Mann-Whitney: P = 0.0539 KO: n = 12, median before 0.295, after 0.324, increases 4, Wilcoxon P = 0.4697 variant, unpaired Mann-Whitney: P = 0.8852 KO pairs (rows paired by the table's experiment number): treatment before (control) oxoM change expt KO_1 0.312 0.415 0.103 KO_10 0.288 0.202 -0.086 KO_11 0.156 0.360 0.204 KO_12 0.186 0.185 -0.001 KO_2 0.209 0.167 -0.043 KO_3 0.795 0.483 -0.312 KO_4 0.571 0.521 -0.050 KO_5 0.430 0.512 0.081 KO_6 0.358 0.311 -0.048 KO_7 0.278 0.180 -0.098 KO_8 0.277 0.184 -0.093 KO_9 0.303 0.337 0.034
Result: reproduced. On the paper's table, oxo-M raises the index in wild type (8 of 10 increase, Wilcoxon P = 0.009766, as the paper reports) and not in KO (4 of 12, P = 0.4697; the paper reports 0.910). Pairs are formed by the table's experiment number (KO_1 to KO_12, printed). An unpaired Mann-Whitney test on the same values gives 0.885 for KO and 0.0539 for wild type; neither test gives the reported 0.910.
fig8a-cdgi-rotations¶
Levodopa induces more contralateral rotations in CDGI KO mice than in wild type at the fifth dose.
t = tidy["Fig 8"]["Fig 8A_rotations 5th"].set_axis(["mouse", "genotype", "rot"], axis=1)
print("Wild type (a) against KO (b):", show(mw(t[t.genotype.str.strip() == "WT"].rot, t[t.genotype.str.strip() == "KO"].rot)))
Wild type (a) against KO (b): {'n_a': 12, 'n_b': 10, 'median_a': 4.25, 'median_b': 8.5, 'U': 21.5, 'P': 0.012, 'P_exact': nan, 'effect_r': 0.6417}
Result: reproduced. On the paper's table, 8.5 rotations in KO against 4.25 in wild type (10 and 12 mice), MW P = 0.012 (the text reports 0.0092).
fig10a-m1r-rotations¶
Levodopa induces more contralateral rotations in mice lacking M1Rs in iSPNs.
t = tidy["Fig 10"]["Fig 10A_rotations in M1R CRISPR"].set_axis(["mouse", "group", "rot"], axis=1)
print("Control (a) against M1R deletion (b):", show(mw(t[t.group.str.strip() == "control"].rot, t[t.group.str.strip() == '"-M1R"'].rot)))
Control (a) against M1R deletion (b): {'n_a': 11, 'n_b': 10, 'median_a': 6.0, 'median_b': 8.5, 'U': 17.0, 'P': 0.00782, 'P_exact': nan, 'effect_r': 0.6909}
Result: reproduced. On the paper's table, 8.5 rotations with M1R deletion against 6.0 in controls, MW P = 0.00782 (the paper reports 0.0056).
fig10ef-open-field¶
In MCI-Park mice, a low dose of levodopa increases locomotion profoundly only when M1Rs are deleted from the striatum; M1R deletion alone does not.
for sheet in ("Fig 10E_distance traveled", "Fig 10F_time mobile"):
t = tidy["Fig 10"][sheet].set_axis(["mouse", "group", "v"], axis=1)
t["group"] = t.group.astype(str).str.strip()
gv = lambda g: t[t.group == g].v
print(sheet)
print(" control (a) against MCI-Park (b):", show(mw(gv("control"), gv("MCI-Park"))))
print(" MCI-Park (a) against M1R deletion (b):", show(mw(gv("MCI-Park"), gv('"-M1R"'))))
print(" MCI-Park (a) against levodopa (b):", show(mw(gv("MCI-Park"), gv('"+L-dopa"'))))
print(" levodopa (a) against levodopa with deletion (b):", show(mw(gv('"+L-dopa"'), gv('"+L-dopa, -M1R"'))))
print(f"\nRun time {time.time() - started:.0f} s")
Fig 10E_distance traveled
control (a) against MCI-Park (b): {'n_a': 12, 'n_b': 17, 'median_a': 10.35, 'median_b': 4.678, 'U': 202.0, 'P': 1.05e-05, 'P_exact': 1.54e-07, 'effect_r': -0.9804}
MCI-Park (a) against M1R deletion (b): {'n_a': 17, 'n_b': 9, 'median_a': 4.678, 'median_b': 3.965, 'U': 67.0, 'P': 0.628, 'P_exact': 0.634, 'effect_r': 0.1242}
MCI-Park (a) against levodopa (b): {'n_a': 17, 'n_b': 12, 'median_a': 4.678, 'median_b': 6.5215, 'U': 54.0, 'P': 0.0354, 'P_exact': 0.0339, 'effect_r': 0.4706}
levodopa (a) against levodopa with deletion (b): {'n_a': 12, 'n_b': 9, 'median_a': 6.5215, 'median_b': 28.09, 'U': 3.0, 'P': 0.000332, 'P_exact': 4.76e-05, 'effect_r': 0.9444}
Fig 10F_time mobile
control (a) against MCI-Park (b): {'n_a': 12, 'n_b': 17, 'median_a': 189.8, 'median_b': 112.0, 'U': 204.0, 'P': 6.97e-06, 'P_exact': 3.85e-08, 'effect_r': -1.0}
MCI-Park (a) against M1R deletion (b): {'n_a': 17, 'n_b': 9, 'median_a': 112.0, 'median_b': 101.3, 'U': 62.5, 'P': 0.467, 'P_exact': nan, 'effect_r': 0.183}
MCI-Park (a) against levodopa (b): {'n_a': 17, 'n_b': 12, 'median_a': 112.0, 'median_b': 127.45, 'U': 65.0, 'P': 0.106, 'P_exact': 0.107, 'effect_r': 0.3627}
levodopa (a) against levodopa with deletion (b): {'n_a': 12, 'n_b': 9, 'median_a': 127.45, 'median_b': 276.4, 'U': 5.0, 'P': 0.000567, 'P_exact': 0.000129, 'effect_r': 0.9074}
Run time 1010 s
Result: reproduced. On the paper's tables, MCI-Park mice travel less than controls (4.68 against 10.35 m, P = 1.05e-05; time mobile P = 6.97e-06), M1R deletion alone does not change this (P = 0.628 and 0.467), and with levodopa the deletion raises distance from 6.52 to 28.09 m (P = 0.000332) and time mobile from 127 to 276 s (P = 0.000567).
Summary¶
| Claim | Data | Outcome |
|---|---|---|
| link-somatic-cells | with inputs | partially reproduced |
| link-dendritic-cells | with inputs | reproduced |
| link-sr-cells | with inputs | partially reproduced |
| link-grab-rois | with inputs | not reproduced |
| link-aim-mice | with labels | not reproduced |
| fig1f-dspn-rheobase-on-lower | dandiset | reproduced |
| fig1e-dspn-fi-on-higher | dandiset | reproduced |
| fig1f-dspn-sch-no-change | dandiset | reproduced |
| fig1i-dspn-dendritic-on-higher | dandiset | reproduced |
| fig1i-dspn-dendritic-sch-lower | dandiset | not reproduced |
| fig2h-dspn-sr-amplitude-on-larger | dandiset | blocked: other (too few cells) |
| fig2i-dspn-sr-frequency-unchanged | dandiset | blocked: other (too few cells) |
| fig2bc-dspn-spines | dandiset | blocked: methods underspecified |
| fig3d-ispn-rheobase-on-higher | dandiset | reproduced |
| fig3c-ispn-fi-on-lower | dandiset | reproduced |
| fig3d-ispn-sul-no-change | dandiset | reproduced |
| fig3f-ispn-dendritic-on-lower | dandiset | reproduced |
| fig3f-ispn-dendritic-sul-no-change | dandiset | not reproduced |
| fig4g-ispn-sr-amplitude-on-smaller | dandiset | blocked: other (too few cells) |
| fig4h-ispn-sr-frequency-unchanged | dandiset | blocked: other (too few cells) |
| fig4bc-ispn-spines | dandiset | blocked: methods underspecified |
| fig4k-confocal-spines | dandiset | blocked: methods underspecified |
| fig5f-ach-off-elevated | dandiset | reproduced |
| fig5f-ach-off-like-6ohda | dandiset | reproduced |
| fig5f-ach-6ohda-elevated | dandiset | blocked: other (too few mice) |
| fig5f-ach-d2-control | dandiset | blocked: other (too few mice) |
| fig5f-ach-da-suppresses | dandiset | blocked: other (too few mice) |
| fig6c-m1r-antag-somatic-unchanged | dandiset | reproduced |
| fig6d-m1r-antag-dendritic-lower | dandiset | reproduced |
| fig6f-m1r-antag-spines | dandiset | blocked: methods underspecified |
| fig7c-cdgi-ko-somatic-oscillation | dandiset | reproduced |
| fig7f-cdgi-ko-dendritic-unchanged | dandiset | reproduced |
| fig7h-cdgi-ko-spines | dandiset | blocked: methods underspecified |
| fig8bc-cdgi-aims | with labels | blocked: other (too few mice) |
| fig9d-m1r-crispr-somatic-unchanged | dandiset | reproduced |
| fig9f-m1r-crispr-spines | dandiset | blocked: methods underspecified |
| fig10b-m1r-aims | with labels | blocked: other (too few mice) |
| fig1-dspn-lesion-and-off-somatic | elsewhere | reproduced |
| fig1-dspn-lesion-dendritic-unchanged | elsewhere | reproduced |
| fig3-ispn-lesion-and-off-somatic | elsewhere | reproduced |
| fig3-ispn-lesion-dendritic-up | elsewhere | reproduced |
| fig7e-oxom-cdgi | elsewhere | reproduced |
| fig8a-cdgi-rotations | elsewhere | reproduced |
| fig10a-m1r-rotations | elsewhere | reproduced |
| fig10ef-open-field | elsewhere | reproduced |
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. Spike counts, rheobase, the dendritic index and the GRAB-ACh area follow the paper's scripts, but the dendritic background is the script's constant for every session (the files carry none), sites are paired by number, and the basal-ratio rule for keeping dendritic pairs is a choice made here (0.5 to 2; it drops no pair). The stricter 0.8 to 1.25 would change two outcomes in opposite directions: fig3f-ispn-dendritic-on-lower would fail (P = 0.077) and fig3f-ispn-dendritic-sul-no-change would pass (P = 0.066). Three site series that hold six trials are split into two sites, the half with the smaller area taken as distal. The GRAB-ACh stimulus time comes from the trial table (3 s in six files where the script assumes 10 s). Sr2+-oEPSC events were found with a threshold detector written here, which finds fewer, larger events than the paper's TaroTools detection with visual verification. Mann-Whitney P values are computed with scipy (exact for small samples without ties, otherwise asymptotic), where Prism was used; the exact P is printed beside it where there are no ties.
What was found about the dandiset.
- The somatic and dendritic patch-clamp data of Figures 1, 3, 6, 7 and 9 are there for every group
except the unlesioned control and 6-OHDA groups, with the paper's cell counts; most cells' spike
counts match the paper's table exactly. The groups must be read from
condition_subfolder: files named for SCH 23390 or sulpiride hold baseline cells and cells recorded in both conditions. - The Sr2+-oEPSC data hold one cell per mouse (14 of 33), the GRAB-ACh data one region per mouse (11 of 34), and the AIM data 9 mice, two of which are not in the paper; too few for the paper's tests. The AIM totals of the Figure 8 mice differ from the paper's.
- The AIM genotype labels contradict each other between the subject metadata, the session names, the table's genotype column and the video files.
- Spine density, rotation counts and open-field data are not in the dandiset as measurements; the spine and rotation raw data are (image stacks and videos), but the counts need manual software.
- Three dendritic site series hold two sites under one name (six trials), one site has a single trial, and two cells lack sites the paper's values need: dSPN on+SCH 20190411 cell 1 has only its second pair (0.584 here against 0.228 in the paper, which decides fig1i-dspn-dendritic-sch-lower) and iSPN on+sul 20190508 cell 3 only a distal site (with the paper's 0.217 for it, the sulpiride test gives the paper's P = 0.0545, which decides fig3f-ispn-dendritic-sul-no-change under the main rule). That these missing sites explain the differences is an inference from the recovered P values; the paper does not list each cell's sites.
- The spine files hold image stacks only, with no processing or analysis entries.
- The dendritic stimulus series are all zero, the Sr2+-oEPSC light-pulse series are zero in many sweeps and are not referenced by the recordings table, and the GRAB-ACh "zero-laser" background images are brighter than the signal.