Chartrand et al. (2023): morphology claims from the paper's feature table¶

Paper. Chartrand et al. (2023), Science 382, eadf0805, doi:10.1126/science.adf0805; see reproduce_chartrand_2023.ipynb for the text read and the table of all claims.

Data. These claims rest on the reconstructed morphologies, which are not in DANDI:000630 (the dandiset holds the electrophysiology only). The paper deposited the reconstructions in the Brain Image Library (doi:10.35077/g.606) and the morphology features computed from them in its code archive (Zenodo doi:10.5281/zenodo.8310226; read here from github.com/AllenInstitute/patchseq_human_L1 at commit 7777857c1b89b19735e45fd9c9bd7493cc1d805c, file data/RawFeatureWide_human+derivatives.csv). The claims are data: elsewhere and are counted apart from the claims on the dandiset. The analysis uses the paper's per-cell morphology features as deposited; it does not recompute them from the reconstructions. The dandiset is read only to list which cells it holds.

Claim Status
fig2d-subclass-morphology tested
fig4b-mc4r-axon tested
fig5b-tnfaip8l3-dendrites tested
figs6a-tumor-dendrite-branching tested, then blocked: the medical condition is missing for most cells
In [1]:
import numpy as np
import pandas as pd

import l1ephys as L

pd.set_option("display.width", 200)
labels = L.load_labels()
morph = L.read_table("RawFeatureWide_human+derivatives.csv", index_col="specimen_id").drop(columns=["Unnamed: 0"])
morph["axon_emd_with_basal_dendrite"] *= 5   # l1_load.py: units to microns
MORPH = list(morph.columns)
h = labels[labels.in_strict_set].join(morph)
h["has_morph_table"] = h.index.isin(morph.index)
h["homology_type"] = pd.Categorical(h["t-type"].map(L.HOMOLOGY), categories=L.SUBCLASSES + ["other"])
h["l1_type"] = h.homology_type.notna()
h["core_l1_type"] = h.homology_type.isin(L.SUBCLASSES)
dand = L.list_assets()
dand_ids = pd.to_numeric(dand.path.str.extract(r"ses-(\d+)")[0])
h["in_dandiset"] = h.ephys_roi_id.isin(dand_ids)
print(f"{len(morph)} cells in the morphology table, {len(MORPH)} features; strict L1 set {len(h)} cells, "
      f"{int(h.has_morph_table.sum())} with morphology")
print("Cells with morphology by site:", h[h.has_morph_table].collaborator.value_counts().to_dict(),
      "| of which in dandiset 000630:", int((h.has_morph_table & h.in_dandiset).sum()))
print("By subclass:", h[h.has_morph_table].homology_type.value_counts(sort=False).to_dict())
print("Feature ranges (5 examples):")
print(h.loc[h.has_morph_table, ["axon_extent_x", "axon_max_path_distance", "basal_dendrite_num_branches",
                                "basal_dendrite_extent_x", "basal_dendrite_soma_percentile_y"]].describe().round(2).to_string())
/tmp/ipykernel_3127/497383901.py:12: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`
  h["has_morph_table"] = h.index.isin(morph.index)
86 cells in the morphology table, 51 features; strict L1 set 272 cells, 78 with morphology
Cells with morphology by site: {'AIBS': 53, 'Gabor': 16, 'Huib': 9} | of which in dandiset 000630: 50
By subclass: {'LAMP5': 29, 'MC4R': 12, 'PAX6': 19, 'L1 VIP': 4, 'other': 7}
Feature ranges (5 examples):
       axon_extent_x  axon_max_path_distance  basal_dendrite_num_branches  basal_dendrite_extent_x  basal_dendrite_soma_percentile_y
count          78.00                   78.00                        78.00                    78.00                             78.00
mean          623.23                 1006.66                        32.68                   183.23                              0.57
std           361.66                  413.95                        13.97                   136.58                              0.26
min           168.93                   26.52                         9.00                    32.66                              0.01
25%           415.98                  753.43                        25.00                   102.43                              0.40
50%           611.78                  967.99                        32.00                   140.94                              0.58
75%           767.62                 1210.98                        38.00                   192.04                              0.80
max          2752.96                 2257.62                        89.00                   818.76                              0.99
/tmp/ipykernel_3127/497383901.py:18: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`
  h["in_dandiset"] = h.ephys_roi_id.isin(dand_ids)

The morphology table holds the reconstructed cells of all three sites, linked to the cell table by specimen id. Of the 78 cells with morphology in the strict L1 set, 53 are from Seattle and 50 of these are cells of the dandiset, but the morphologies themselves are not in it.

fig2d-subclass-morphology¶

Human L1 subclasses differ morphologically in vertical orientation of axons and dendrites, axon extent and shape, and dendrite branching (Kruskal-Wallis, FDR p < 1e-3).

As l1_subclass_distinctness.ipynb cells 19 to 21: strict L1 cells with morphology in the four core subclasses; Kruskal-Wallis per morphology feature, Benjamini-Hochberg; the features Fig. 2D plots (cell 21) are the fraction of dendrite below the soma, the axon horizontal extent and the number of dendrite branches. The claims file gives no criterion; the claim is judged on these three having FDR p < 1e-3, with the other features named in the statement reported.

In [2]:
sub = h[h.has_morph_table & h.core_l1_type].copy()
sub["homology_type"] = sub.homology_type.cat.remove_unused_categories()
res, pairs = L.run_kw_dunn(sub, MORPH, "homology_type", posthoc="dunn")
print(f"{len(sub)} cells", sub.homology_type.value_counts(sort=False).to_dict(), f"| {len(res)} features, "
      f"{int((res.pval_fdr < 1e-3).sum())} with FDR p < 1e-3")
fig2d = ["basal_dendrite_soma_percentile_y", "axon_extent_x", "basal_dendrite_num_branches"]
print(res.loc[fig2d, ["pval", "pval_fdr", "epsilon2", "n"]].to_string())
others = [f for f in ["axon_soma_percentile_y", "axon_bias_y", "axon_extent_y", "axon_extent_x_over_y",
                      "basal_dendrite_bias_y", "axon_max_path_distance"] if f in res.index]
print("Other orientation, extent and shape features:"); print(res.loc[others, ["pval_fdr", "epsilon2"]].to_string())
print("Medians:"); print(sub.groupby("homology_type", observed=True)[fig2d].median().round(2).to_string())
64 cells {'LAMP5': 29, 'MC4R': 12, 'PAX6': 19, 'L1 VIP': 4} | 51 features, 4 with FDR p < 1e-3
                                      pval  pval_fdr  epsilon2   n
feature                                                           
basal_dendrite_soma_percentile_y  0.000008  0.000203  0.418712  64
axon_extent_x                     0.000024  0.000405  0.382557  64
basal_dendrite_num_branches       0.000605  0.003430  0.275033  64
Other orientation, extent and shape features:
                        pval_fdr  epsilon2
feature                                   
axon_soma_percentile_y  0.008276  0.236277
axon_bias_y             0.091546  0.121413
axon_extent_y           0.047302  0.155453
axon_extent_x_over_y    0.008276  0.235750
basal_dendrite_bias_y   0.002760  0.290706
axon_max_path_distance  0.003197  0.281326
Medians:
               basal_dendrite_soma_percentile_y  axon_extent_x  basal_dendrite_num_branches
homology_type                                                                              
LAMP5                                      0.73         587.00                         40.0
MC4R                                       0.20         260.26                         30.5
PAX6                                       0.71         683.42                         27.0
L1 VIP                                     0.38         632.40                         24.5

Result: partially reproduced. Across the four core subclasses (64 reconstructed cells) the fraction of dendrite below the soma (FDR p = 2.0e-4) and the axon horizontal extent (4.1e-4) differ below 1e-3. The number of dendrite branches differs with FDR p = 0.0034, below 0.05 but not below the claim's 1e-3, and the axon's vertical orientation features have FDR p 0.008 to 0.09.

fig4b-mc4r-axon¶

MC4R cells are quantitatively distinct from other L1 types in maximum axonal path distance and axon branch frequency.

As l1_rosehip.ipynb cells 4, 11 and 17: strict L1 cells, MC4R against the rest; branch frequency is the number of axon branches per 100 um of axon; exact Mann-Whitney over all morphology features and the branch frequency; Benjamini-Hochberg. Judged on FDR p < 1e-4 (Fig. 4B legend) for both.

In [3]:
d = h[h.has_morph_table].copy()
d["axon_branch_freq"] = d["axon_num_branches"] / d["axon_total_length"] * 100
d["in_type"] = np.where(d.homology_type == "MC4R", "MC4R", "rest")
res = L.run_twosamp(d, MORPH + ["axon_branch_freq"], "in_type")
med = d.groupby("in_type")[["axon_max_path_distance", "axon_branch_freq"]].median()
print(f"MC4R {int((d.in_type == 'MC4R').sum())} cells against {int((d.in_type == 'rest').sum())}")
print(res.loc[["axon_max_path_distance", "axon_branch_freq"], ["pval_mw", "pval_mw_fdr", "mw_r", "nobs"]].to_string())
print("Medians:"); print(med.round(2).to_string())
MC4R 12 cells against 66
                             pval_mw  pval_mw_fdr      mw_r  nobs
feature                                                          
axon_max_path_distance  3.313772e-04     0.002872  0.626263    78
axon_branch_freq        2.839605e-08     0.000001  0.876263    78
Medians:
         axon_max_path_distance  axon_branch_freq
in_type                                          
MC4R                     662.30              3.01
rest                     991.93              1.36

Result: partially reproduced. MC4R cells (12 against 66) have a higher axon branch frequency (3.01 against 1.36 per 100 um, FDR p = 1.2e-6, below 1e-4) and a shorter maximum axonal path (662 against 992 um), but the path difference has FDR p = 0.0029, not below 1e-4.

fig5b-tnfaip8l3-dendrites¶

PAX6 TNFAIP8L3 cells have more dendritic branches and a horizontal dendritic extent over 550 um wide.

As l1_tnfaip.ipynb cells 16 and 17: strict L1 cells, PAX6 TNFAIP8L3 against the rest; exact Mann-Whitney over the morphology features; Benjamini-Hochberg; judged on FDR p < 0.05 (Fig. 5B legend) with TNFAIP8L3 higher for both features, and each TNFAIP8L3 cell's horizontal extent above 550 um.

In [4]:
d = h[h.has_morph_table].copy()
d["in_type"] = np.where(d["t-type"] == "PAX6 TNFAIP8L3", "TNFAIP8L3", "rest")
res = L.run_twosamp(d, MORPH, "in_type")
f2 = ["basal_dendrite_num_branches", "basal_dendrite_extent_x"]
print(f"TNFAIP8L3 {int((d.in_type == 'TNFAIP8L3').sum())} cells against {int((d.in_type == 'rest').sum())}")
print(res.loc[f2, ["pval_mw", "pval_mw_fdr", "mw_r", "nobs"]].to_string())
print("TNFAIP8L3 cells:"); print(d.loc[d.in_type == "TNFAIP8L3", f2 + ["collaborator"]].round(1).to_string())
print("Rest, medians:", d.loc[d.in_type == "rest", f2].median().round(1).to_dict(), "| rest maximum extent:",
      round(d.loc[d.in_type == "rest", "basal_dendrite_extent_x"].max(), 1))
TNFAIP8L3 2 cells against 76
                              pval_mw  pval_mw_fdr      mw_r  nobs
feature                                                           
basal_dendrite_num_branches  0.013320     0.084915  0.907895    78
basal_dendrite_extent_x      0.000666     0.016983  1.000000    78
TNFAIP8L3 cells:
           basal_dendrite_num_branches  basal_dendrite_extent_x collaborator
spec_id                                                                     
840142358                         50.0                    583.2         AIBS
828758585                         68.0                    818.8        Gabor
Rest, medians: {'basal_dendrite_num_branches': 32.0, 'basal_dendrite_extent_x': 139.9} | rest maximum extent: 571.0

Result: partially reproduced. The two PAX6 TNFAIP8L3 cells have horizontal dendritic extents of 583 and 819 um, both over 550 um, against a median of 140 um in the other 76 cells (FDR p = 0.017). They have more dendrite branches (50 and 68 against a median of 32), but with FDR p = 0.085, not below 0.05.

figs6a-tumor-dendrite-branching¶

Cells from tumor patients have higher dendritic branching than cells from epilepsy patients, not explained by brain area or subclass.

As l1_metadata_effects.ipynb cells 65, 66, 72 and 73: strict L1 cells with an L1 t-type and morphology, epileptic against non-epileptic donors (code's epileptic(), on the public table's medical_conditions in place of the code's LIMS records); exact Mann-Whitney over the morphology features; Benjamini-Hochberg. Judged on the number of dendrite branches having FDR p < 0.05 and being higher in non-epileptic (tumor) donors, overall and within MTG LAMP5 cells (cell 72, the check that area and subclass do not explain it).

In [5]:
def epileptic(x):
    if pd.isna(x):
        return x
    return "epileptic" if "epilep" in x.lower() else "non-epileptic"

d = h[h.has_morph_table & h.l1_type].copy()
d["epileptic"] = d.medical_conditions.map(epileptic)
print("Medical condition in the public table, cells with morphology:", d.medical_conditions.value_counts(dropna=False).to_dict())
for name, data in [("all", d), ("MTG LAMP5", d[(d.structure == "MTG") & (d.homology_type == "LAMP5")])]:
    data = data.dropna(subset=["epileptic"])
    if data.epileptic.nunique() < 2:
        print(f"{name}: only one group with a known condition ({data.epileptic.value_counts().to_dict()}); not testable")
        continue
    res = L.run_twosamp(data, MORPH, "epileptic")
    med = data.groupby("epileptic").basal_dendrite_num_branches.median()
    print(f"{name}: {data.epileptic.value_counts().to_dict()}; dendrite branches median {med.round(1).to_dict()}, "
          f"p {res.loc['basal_dendrite_num_branches', 'pval_mw']:.3g}, FDR p {res.loc['basal_dendrite_num_branches', 'pval_mw_fdr']:.3g}; "
          f"smallest FDR p {res.pval_mw_fdr.min():.3g} ({res.pval_mw_fdr.idxmin()})")
Medical condition in the public table, cells with morphology: {nan: 51, 'Epilepsy': 10, 'Tumor status - primary': 4, 'Tumor': 3, 'Epileptiform Events': 3}
all: {'epileptic': 13, 'non-epileptic': 7}; dendrite branches median {'epileptic': 26.0, 'non-epileptic': 37.0}, p 0.351, FDR p 0.942; smallest FDR p 0.568 (axon_exit_theta)
MTG LAMP5: only one group with a known condition ({'epileptic': 7}); not testable

Result: blocked, metadata insufficient. The public cell table gives a medical condition for only 20 of the 71 reconstructed cells with an L1 t-type (13 epileptic, 7 not; 51 have none), where the paper's code read the conditions for all donors from the Allen Institute's donor database. On these 20 cells the number of dendrite branches is higher in non-epileptic donors (median 37 against 26) without significance (FDR p = 0.94), and the check within MTG LAMP5 cells cannot be run (only epileptic donors are labelled). The test cannot be repeated on the paper's cells.

Summary¶

Claim Outcome
fig2d-subclass-morphology partially reproduced
fig4b-mc4r-axon partially reproduced
fig5b-tnfaip8l3-dendrites partially reproduced
figs6a-tumor-dendrite-branching blocked, metadata insufficient

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.

The analysis uses the paper's deposited morphology features, not the reconstructions. The strict L1 set holds 78 reconstructed cells in the feature table, against the 71 of Table S2; which cells the paper counted is not settled here.