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.

In [1]:
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.

In [2]:
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).

In [3]:
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.

In [4]:
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.

In [5]:
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).

In [6]:
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.

In [7]:
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.

In [8]:
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.

In [9]:
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.

In [10]:
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.

In [11]:
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.

In [12]:
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
In [13]:
# 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
No description has been provided for this image

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.

In [14]:
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.

In [15]:
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.

In [16]:
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.

In [17]:
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.

In [18]:
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.

In [19]:
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.

In [20]:
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.

In [21]:
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.

In [22]:
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.

In [23]:
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.

In [24]:
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.

In [25]:
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.

In [26]:
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.

In [27]:
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.

In [28]:
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.

In [29]:
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.

In [30]:
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.

In [31]:
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.

In [32]:
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.

In [33]:
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.

In [34]:
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.

In [35]:
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.

In [36]:
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.

In [37]:
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.

In [38]:
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.

In [39]:
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.

In [40]:
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.