Reproducing Hart et al. (2024) from dandiset 000773¶
Paper. Hart T, Lopes LE, Frank DD, Kronauer DJC (2024). Pheromone representation in the ant antennal lobe changes with age. Current Biology 34, 3233-3240. doi:10.1016/j.cub.2024.05.031. The claims file was written from the accepted author manuscript in PubMed Central (PMC11265976, NIHMS1997610), main text, legends and STAR Methods, with the supplement (Figures S1 to S4, Table S1).
Dandiset. DANDI:000773, version 0.261004.1511, the only published version. The paper cites the dandiset without a version, so the most recent published version is pinned; the draft has the same 15,246 assets and size, and was last modified when this version was published (2026-10-04).
What the dandiset holds. The two-photon calcium imaging of Figures 2C to 4 and S2 to S4: 21 GCaMP6s ants (14 at 48% and 7 at 3% odor concentration), each with 22 trials (seven odors three times, and one paraffin oil trial) of 33 planes; one NWB file per plane and trial. It does not hold the behaviour of Figure 1 and S1, the confocal stacks of Figure 2A and 2B, or the 90-day-old ants of Figure 3C and 3D (DANDI 000467, read here for reading (a) of fig3d-pg-60-vs-90-days-same).
Outside data. The paper's supplement Data S1 (behavioural counts, MMC2.csv) and Data S2
(per-glomerulus fold-change traces, MMC3.xlsx), from the PubMed Central Open Access copy
PMC11265976.1, pinned by MD5. Data S1 is the data of the five behaviour claims (data: elsewhere).
Data S2 is used for three things: to name the ants and trials of the dandiset in the paper's
terms, to find where each focal glomerulus is in each ant (the paper drew these ROIs by hand and
gives no coordinates; see the link claim data-s2-glomerulus-link), and as the 90-day values of
reading (b) of fig3d-pg-60-vs-90-days-same. Every AUC, map and count tested here is computed
from the dandiset's pixels.
| Claim | Data | Tested here |
|---|---|---|
| fig1c-old-faster-alarm, fig1c-vehicle-single-curve, fig1b-alarm-stimuli-similar, figs1-heptanone-separate-curves, figs1-heptanol-blend-single-curve | Data S1 (elsewhere) | yes |
| fig2b-gcamp-baseline-unchanged | Brain Image Library (elsewhere) | no, blocked: the download server could not be reached (three attempts) |
| fig2d-whole-al-alarm-no-age-effect, figs3b-whole-al-general-decline, figs3b-whole-al-other-no-age-effect | dandiset | yes |
| fig2e-heptanone-breadth-falls, fig2-alarm-sparse, figs3c-general-breadth-no-age-effect | dandiset | run with the claims file's automated ROIs; all three blocked as methods underspecified |
| data-s2-glomerulus-link (link claim added here) | dandiset and Data S2 | yes |
| figs2a, fig3b, fig3d-pg-stronger-old, figs4a, fig4 (two claims), figs4d, pg-opposite-modulation | dandiset, ROI positions from Data S2 | yes |
| fig3d-pg-60-vs-90-days-same | dandiset, DANDI 000467 and Data S2 (with inputs) | both readings run; blocked as methods underspecified |
The analysis streams about 460 GB (frames 1-5 and 9-14 of every plane file, the middle plane of each 48% trial, then single rows of the planes around each focal glomerulus, and the same for the eight 90-day ants of DANDI 000467) and takes about 40 minutes (36 to 44 in the runs so far).
Revision after the first verification. The focal glomeruli are now also placed without the trials that are then tested (leave one odor out), which is the main measure, and every placement is compared with its chance level; reading (b) of the glomerulus fold change leaves out pixels at background level; the whole-AL measure's offset is shown; and the 90-day placements are tested against chance.
Revision after the second verification. The landing page of the Brain Image Library dataset is read and its notices printed; reading (a) of fig3d-pg-60-vs-90-days-same also compares the 60-day values at the held-out placements with the 90-day values; and the text is corrected where the round 2 verifier found it unsupported by the outputs.
import hashlib
import io
import time
import warnings
from concurrent.futures import ThreadPoolExecutor
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats
import antal
pd.set_option("display.width", 220)
pd.set_option("display.max_columns", 40)
pd.set_option("display.max_rows", 300)
pd.set_option("display.float_format", "{:.4g}".format)
warnings.filterwarnings("ignore", category=RuntimeWarning)
started = time.time()
ANTS = None # all ants
assets = antal.list_assets()
trials = antal.trial_table(assets)
if ANTS is not None:
trials = trials[trials.subject.isin(ANTS)].reset_index(drop=True)
print(f"dandiset {antal.DANDISET_ID} version {antal.DANDISET_VERSION}: {len(assets)} assets, "
f"{assets['size'].sum() / 1e12:.2f} TB; {len(trials)} trials of {trials.subject.nunique()} ants")
# one file per ant for its metadata
first = trials.groupby("subject").blobs.first()
with ThreadPoolExecutor(8) as ex:
meta = dict(zip(first.index, ex.map(lambda b: antal.nwb_metadata(b[16]), first)))
meta = pd.DataFrame(meta).T
meta["age_days"] = meta.age.str.extract(r"P(\d+)D")[0].astype(int)
meta["cohort"] = np.where(meta.age_days < 40, "young", "old")
conc = trials[trials.odor != "paraffin-oil"].groupby("subject").conc.first()
meta["conc"] = conc
trials["cohort"] = trials.subject.map(meta.cohort)
dandiset 000773 version 0.261004.1511: 15246 assets, 1.28 TB; 462 trials of 21 ants
Data check¶
print("Ants by concentration and cohort (ages from the NWB subject metadata, days post eclosion):")
print(meta.groupby(["conc", "cohort"]).age_days.agg(["count", "min", "max"]).to_string())
print("\nPer ant: age, trials per odor, planes per trial")
tab = trials.pivot_table(index="subject", columns="odor", values="t", aggfunc="count").fillna(0).astype(int)
tab.insert(0, "age_days", meta.age_days)
tab.insert(1, "conc", meta.conc)
tab["planes"] = trials.groupby("subject").n_planes.unique().map(lambda v: ",".join(map(str, v)))
print(tab.to_string())
print("\nOdor names in the file names:", assets.odor.value_counts().to_dict())
print("Plane depths (um):", sorted(assets.z.unique()))
print("Every trial has the 33 planes 0 to 160 um:",
bool(trials.z.map(lambda z: z == list(range(0, 165, 5))).all()))
print("Asset sizes (bytes):", assets["size"].unique())
Ants by concentration and cohort (ages from the NWB subject metadata, days post eclosion):
count min max
conc cohort
0.03 old 4 61 61
young 3 15 15
0.48 old 7 60 61
young 7 16 20
Per ant: age, trials per odor, planes per trial
odor age_days conc 3-hexanone 4-methyl-3-heptanol 4-methyl-3-heptanone ethanol ethylpyrazine isopropanol paraffin-oil propionic-acid planes
subject
ant1-m1-d11-y2023 61 0.03 3 3 3 3 3 3 1 3 33
ant1-m1-d17-y2023 15 0.03 3 3 3 3 3 3 1 3 33
ant1-m1-d20-y2023 18 0.48 3 3 3 3 3 3 1 3 33
ant1-m2-d24-y2023 20 0.48 3 3 3 3 3 3 1 3 33
ant1-m4-d6-y2023 61 0.48 3 3 3 3 3 3 1 3 33
ant2-m1-d11-y2023 61 0.03 3 3 3 3 3 3 1 3 33
ant2-m1-d17-y2023 15 0.03 3 3 3 3 3 3 1 3 33
ant2-m1-d20-y2023 18 0.48 3 3 3 3 3 3 1 3 33
ant2-m2-d24-y2023 20 0.48 3 3 3 3 3 3 1 3 33
ant3-m1-d10-y2023 60 0.48 3 3 3 3 3 3 1 3 33
ant3-m1-d17-y2023 15 0.03 3 3 3 3 3 3 1 3 33
ant3-m2-d24-y2023 20 0.48 3 3 3 3 3 3 1 3 33
ant4-m1-d10-y2023 60 0.48 3 3 3 3 3 3 1 3 33
ant4-m1-d11-y2023 61 0.03 3 3 3 3 3 3 1 3 33
ant4-m1-d18-y2023 16 0.48 3 3 3 3 3 3 1 3 33
ant4-m1-d20-y2023 18 0.48 3 3 3 3 3 3 1 3 33
ant4-m4-d6-y2023 61 0.48 3 3 3 3 3 3 1 3 33
ant5-m1-d10-y2023 60 0.48 3 3 3 3 3 3 1 3 33
ant5-m1-d11-y2023 61 0.03 3 3 3 3 3 3 1 3 33
ant5-m4-d6-y2023 61 0.48 3 3 3 3 3 3 1 3 33
ant6-m1-d10-y2023 60 0.48 3 3 3 3 3 3 1 3 33
Odor names in the file names: {'3-hexanone': 2079, '4-methyl-3-heptanol': 2079, '4-methyl-3-heptanone': 2079, 'ethanol': 2079, 'isopropanol': 2079, 'propionic-acid': 2079, 'ethylpyrazine': 2046, 'paraffin-oil': 693, 'ethylyprazine': 33}
Plane depths (um): [np.int64(0), np.int64(5), np.int64(10), np.int64(15), np.int64(20), np.int64(25), np.int64(30), np.int64(35), np.int64(40), np.int64(45), np.int64(50), np.int64(55), np.int64(60), np.int64(65), np.int64(70), np.int64(75), np.int64(80), np.int64(85), np.int64(90), np.int64(95), np.int64(100), np.int64(105), np.int64(110), np.int64(115), np.int64(120), np.int64(125), np.int64(130), np.int64(135), np.int64(140), np.int64(145), np.int64(150), np.int64(155), np.int64(160)]
Every trial has the 33 planes 0 to 160 um: True
Asset sizes (bytes): [84086336]
Ages agree with the Methods (young 15 to 20, old 60 to 61 days post eclosion) and the counts with the Figure S3B legend (48%: 7 young and 7 old; 3%: 3 young and 4 old). One trial of one ant spells ethylpyrazine "ethylyprazine" in its 33 file names; it is read as ethylpyrazine (its position in the odor sequence is that of ethylpyrazine; see the next table).
odd = assets[assets.odor == "ethylyprazine"].drop_duplicates(["subject", "t"])
for _, r in odd.iterrows():
seq = trials[trials.subject == r.subject].sort_values("t")[["t", "odor"]]
print(r.subject, "trial", r.t, "is spelled 'ethylyprazine'; that ant's sequence:")
print(seq.T.to_string(header=False))
print()
print("Metadata of one file per ant (first trial, plane 80 um):")
print(meta[["age", "species", "unit", "shape", "dtype", "rate_hz", "imaging_rate", "grid_spacing_um",
"offset", "chunks", "compression"]].to_string())
print("\nSeries description:", meta.series_description.unique())
off = antal.check_offsets(trials.sample(6, random_state=0).blobs.map(lambda b: b[3]).tolist())
print("\nData offset and range reads checked against h5py on 6 files:")
print(off.to_string(index=False))
ant1-m2-d24-y2023 trial 135 is spelled 'ethylyprazine'; that ant's sequence:
t 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
odor paraffin-oil isopropanol 3-hexanone ethanol 4-methyl-3-heptanol 4-methyl-3-heptanone ethylpyrazine propionic-acid isopropanol 3-hexanone ethanol 4-methyl-3-heptanol 4-methyl-3-heptanone ethylpyrazine propionic-acid isopropanol 3-hexanone ethanol 4-methyl-3-heptanol 4-methyl-3-heptanone ethylpyrazine propionic-acid
Metadata of one file per ant (first trial, plane 80 um):
age species unit shape dtype rate_hz imaging_rate grid_spacing_um offset chunks compression
ant1-m1-d11-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant1-m1-d17-y2023 P15D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant1-m1-d20-y2023 P18D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant1-m2-d24-y2023 P20D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant1-m4-d6-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant2-m1-d11-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant2-m1-d17-y2023 P15D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant2-m1-d20-y2023 P18D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant2-m2-d24-y2023 P20D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant3-m1-d10-y2023 P60D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant3-m1-d17-y2023 P15D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant3-m2-d24-y2023 P20D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant4-m1-d10-y2023 P60D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant4-m1-d11-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant4-m1-d18-y2023 P16D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant4-m1-d20-y2023 P18D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant4-m4-d6-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant5-m1-d10-y2023 P60D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant5-m1-d11-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant5-m4-d6-y2023 P61D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
ant6-m1-d10-y2023 P60D http://purl.obolibrary.org/obo/NCBITaxon_2015173 raw fluorescence (40, 512, 512) float64 1.21 0.83 [0.2889, 0.2889, 5.0] 3464 None None
Series description: ['two photon recording, 40 cycles total, recording time 48.0s, odor stimulus triggered at 3s with duration 5s']
Data offset and range reads checked against h5py on 6 files:
blob offset range_equals_h5py
05540d8c-a138-40dd-b2c0-38f93abd2d8f 3464 True
6856799e-a24e-47d3-a9ad-b0f93524f6fa 3464 True
bfacec2b-a17c-4dc8-8fe0-8d8413fa048f 3464 True
8630c51b-5b6a-4cbb-8c57-53faaaaeb245 3464 True
b51043c3-54a7-4213-91e6-a814217375b6 3464 True
0794a458-159e-421f-99c6-f265199b2d56 3464 True
Each file holds 40 frames (volumes) of 512 x 512 raw fluorescence as float64, uncompressed and
contiguous, at the same byte offset, so frames are read by range; the range reads equal h5py's.
The pixel size, 0.289 um, matches 148 um over 512 pixels (Methods). The timing metadata disagree
with each other: the series description gives 40 cycles in 48.0 s (1.2 s per volume, as 33 planes
at 27.5 frames per second take), the series rate is 1.21 Hz (0.83 s per volume, the Methods' value),
and imaging_rate is 0.83, a period stored as a rate. All windows here are in frames, so this
changes no measure; Data S2 puts frames 1.2 s apart. The odor onset is not stored in the files,
only in the description ("odor stimulus triggered at 3s with duration 5s").
s1_raw = antal.fetch(antal.DATA_S1, antal.DATA_S1_MD5)
s2_raw = antal.fetch(antal.DATA_S2, antal.DATA_S2_MD5)
print("Data S1", antal.DATA_S1, "MD5", hashlib.md5(s1_raw).hexdigest())
print("Data S2", antal.DATA_S2, "MD5", hashlib.md5(s2_raw).hexdigest())
s1 = pd.read_csv(io.BytesIO(s1_raw))
s2 = antal.load_data_s2(s2_raw)
print("\nData S1: trials per stimulus:", s1.groupby("stimulus").file_name.nunique().to_dict(),
"; time points:", sorted(s1.time.unique()))
s2a = s2.groupby(["subject", "age", "conc"]).agg(glomeruli=("glomerulus", "nunique"),
trials=("trial", "nunique")).reset_index()
s2a["in_dandiset"] = s2a.subject.isin(meta.index)
s2a["dandiset_age_days"] = s2a.subject.map(meta.age_days)
s2a["dandiset_conc"] = s2a.subject.map(meta.conc)
print("\nData S2 ants (names normalised to the dandiset's subject names):")
print(s2a.to_string(index=False))
Data S1 https://pmc-oa-opendata.s3.amazonaws.com/PMC11265976.1/NIHMS1997610-supplement-MMC2.csv MD5 22496279f60217c8781b673e82fb8ac7 Data S2 https://pmc-oa-opendata.s3.amazonaws.com/PMC11265976.1/NIHMS1997610-supplement-MMC3.xlsx MD5 54ff6698bf2b80098376d60ed3af467d
Data S1: trials per stimulus: {'4m3heptanol': 7, '4m3heptanone': 6, 'blend': 7, 'pentane': 6} ; time points: [np.int64(-60), np.int64(-50), np.int64(-40), np.int64(-30), np.int64(-20), np.int64(-10), np.int64(0), np.int64(10), np.int64(20), np.int64(30), np.int64(40), np.int64(50), np.int64(60)]
Data S2 ants (names normalised to the dandiset's subject names):
subject age conc glomeruli trials in_dandiset dandiset_age_days dandiset_conc
ant1-m1-d20-y2023 2weeks 0.48 6 27 True 18 0.48
ant1-m2-d24-y2023 2weeks 0.48 6 27 True 20 0.48
ant1-m4-d21-y2022 3months 0.12 1 12 False NaN NaN
ant1-m4-d21-y2022 3months 0.48 2 18 False NaN NaN
ant1-m4-d26-y2022 3months 0.03 1 12 False NaN NaN
ant1-m4-d26-y2022 3months 0.48 2 18 False NaN NaN
ant1-m4-d27-y2022 3months 0.0075 1 12 False NaN NaN
ant1-m4-d27-y2022 3months 0.12 1 12 False NaN NaN
ant1-m4-d6-y2023 2months 0.48 6 27 True 61 0.48
ant2-m1-d20-y2023 2weeks 0.48 6 27 True 18 0.48
ant2-m2-d24-y2023 2weeks 0.48 6 26 True 20 0.48
ant2-m4-d20-y2022 3months 0.03 1 12 False NaN NaN
ant2-m4-d20-y2022 3months 0.12 1 12 False NaN NaN
ant2-m4-d21-y2022 3months 0.12 1 12 False NaN NaN
ant2-m4-d21-y2022 3months 0.48 2 18 False NaN NaN
ant2-m4-d26-y2022 3months 0.03 1 12 False NaN NaN
ant2-m4-d26-y2022 3months 0.48 2 18 False NaN NaN
ant2-m4-d27-y2022 3months 0.0075 1 12 False NaN NaN
ant2-m4-d27-y2022 3months 0.12 1 12 False NaN NaN
ant3-m1-d10-y2023 2months 0.48 4 27 True 60 0.48
ant3-m2-d24-y2023 2weeks 0.48 6 27 True 20 0.48
ant3-m4-d21-y2022 3months 0.12 1 12 False NaN NaN
ant3-m4-d21-y2022 3months 0.48 2 18 False NaN NaN
ant3-m4-d26-y2022 3months 0.03 1 12 False NaN NaN
ant3-m4-d26-y2022 3months 0.48 2 18 False NaN NaN
ant3-m4-d27-y2022 3months 0.0075 1 12 False NaN NaN
ant3-m4-d27-y2022 3months 0.12 1 12 False NaN NaN
ant4-m1-d10-y2023 2months 0.48 6 27 True 60 0.48
ant4-m1-d18-y2023 2weeks 0.48 6 27 True 16 0.48
ant4-m1-d20-y2023 2weeks 0.48 6 27 True 18 0.48
ant4-m4-d20-y2022 3months 0.03 1 12 False NaN NaN
ant4-m4-d20-y2022 3months 0.12 1 12 False NaN NaN
ant4-m4-d26-y2022 3months 0.03 1 12 False NaN NaN
ant4-m4-d26-y2022 3months 0.48 2 18 False NaN NaN
ant4-m4-d6-y2023 2months 0.48 6 27 True 61 0.48
ant5-m1-d10-y2023 2months 0.48 5 27 True 60 0.48
ant5-m4-d26-y2022 3months 0.03 1 12 False NaN NaN
ant5-m4-d26-y2022 3months 0.48 2 18 False NaN NaN
ant5-m4-d6-y2023 2months 0.48 6 27 True 61 0.48
ant6-m1-d10-y2023 2months 0.48 6 27 True 60 0.48
Data S2 names its ants by number and imaging date (for example a4-1-18-2023, also written a4-1-18-23); the dandiset's subjects carry the same number and date (ant4-m1-d18-y2023). The 14 ants of Data S2 at 48% from 2023 are the 14 dandiset ants at 48%, and Data S2's age labels (2 weeks, 2 months) agree with the dandiset's ages. The 3% ants are not in Data S2. The 2022 ants of Data S2 are the 90-day-old ants of the earlier study.
The confocal stacks of Figure 2A and 2B (fig2b-gcamp-baseline-unchanged) are in the Brain Image Library (doi:10.35077/g.1153). The next cell reads the dataset's landing page (the brains it lists under download.brainimagelibrary.org, and its notices), then tries to reach that download server three times, 30 s apart, with a 60 s timeout each.
import html
import re
import requests
try:
page = requests.get("https://doi.org/10.35077/g.1153", timeout=60)
text = html.unescape(re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", page.text)))
brains = sorted(set(re.findall(r"download\.brainimagelibrary\.org/\S+/(Two_\w+_Old/\w+_Brain_\d+)", text)))
print("Landing page", page.url, page.status_code)
print(f"Brains listed: {len(brains)} ({sum('Young' in b for b in brains)} young, {sum('Old_Brain' in b for b in brains)} old)")
for notice in ("This repository is under review[^.]*\\.", "BIL data will be unavailable[^.]*\\."):
print("Notice:", *re.findall(notice, text)[:1])
except Exception as e:
print("Landing page not reachable,", type(e).__name__)
bil = "https://download.brainimagelibrary.org/3d/38/3d38636a9ef063d3/Two_Week_Old/Young_Brain_1/"
for attempt in range(1, 4):
try:
r = requests.get(bil, timeout=60)
print(f"Brain Image Library download server, attempt {attempt}:", r.status_code, len(r.content), "bytes")
break
except Exception as e:
print(f"Brain Image Library download server, attempt {attempt} (60 s timeout): not reachable,", type(e).__name__)
time.sleep(30)
Landing page https://api.brainimagelibrary.org/web/view?bildid=g.1153 200 Brains listed: 15 (7 young, 8 old) Notice: This repository is under review for potential modification in compliance with Administration directives. Notice: BIL data will be unavailable between October 5 and October 16.
Brain Image Library download server, attempt 1 (60 s timeout): not reachable, ReadTimeout
Brain Image Library download server, attempt 2 (60 s timeout): not reachable, ReadTimeout
Brain Image Library download server, attempt 3 (60 s timeout): not reachable, ReadTimeout
The behaviour claims (Data S1)¶
The five behaviour claims use Data S1, which the dandiset does not hold. Per trial and time point
from -10 to 60 s, the proportion of young ants outside the nest over the colony's young (and the
same for old), as the paper's alarm_behavior_calculations.py computes it (claims file); Data S1
carries these proportions. The logistic of Table S1 (and the quadratic for the vehicle) is fit by
least squares to all per-trial points; the extra sum-of-squares F test compares one shared curve
with one curve per group. Starting values (YM 0.9, Y0 0.5, K 0.05) are a standard choice.
b = s1[(s1.time >= -10) & (s1.time <= 60)]
P0 = [0.9, 0.5, 0.05]
alarm = b[b.stimulus != "pentane"]
def xy(df, col):
return df.time.to_numpy(float), df[col].to_numpy(float)
def show(name, r, names=("YM", "Y0", "K")):
print(f"{name}: F({r['df'][0]}, {r['df'][1]}) = {r['F']:.3f}, p = {r['p']:.2g}")
for g, p in r["separate"].items():
print(f" {g:>14}: " + ", ".join(f"{n} {v:.4g}" for n, v in zip(names, p)))
print(f" {'shared':>14}: " + ", ".join(f"{n} {v:.4g}" for n, v in zip(names, r["shared"])))
r_age = antal.extra_ss_test(antal.logistic, {"young": xy(alarm, "prop_outside_2wo"),
"old": xy(alarm, "prop_outside_2mo")}, P0)
show("Pooled alarm stimuli, young against old", r_age)
tg = np.linspace(0, 60, 61)
yo, ol = (antal.logistic(tg, *r_age["separate"][k]) for k in ("young", "old"))
print(f"Old curve above young at every second from 0 to 60 s: {bool((ol > yo).all())} "
f"(smallest old minus young {np.min(ol - yo):.3f})")
exp = {"YM young": (0.858, 0.081), "YM old": (0.914, 0.033), "Y0 young": (0.571, 0.046),
"Y0 old": (0.663, 0.040), "K young": (0.070, 0.036), "K old": (0.099, 0.029)}
got = {"YM young": r_age["separate"]["young"][0], "YM old": r_age["separate"]["old"][0],
"Y0 young": r_age["separate"]["young"][1], "Y0 old": r_age["separate"]["old"][1],
"K young": r_age["separate"]["young"][2], "K old": r_age["separate"]["old"][2]}
print(pd.DataFrame({"expected": {k: v[0] for k, v in exp.items()}, "tolerance": {k: v[1] for k, v in exp.items()},
"measured": got}).assign(within=lambda d: (d.measured - d.expected).abs() <= d.tolerance).round(4).to_string())
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))
for a, df, title in ((ax[0], alarm, "pooled alarm stimuli"), (ax[1], b[b.stimulus == "pentane"], "vehicle")):
for col, lab in (("prop_outside_2wo", "young"), ("prop_outside_2mo", "old")):
m = df.groupby("time")[col].agg(["mean", "sem"])
a.errorbar(m.index, m["mean"], m["sem"], marker="o", label=lab, capsize=2)
a.set_title(title); a.set_xlabel("time (s)"); a.set_ylabel("proportion outside the nest"); a.set_ylim(0, 1)
ax[0].plot(tg, yo, "C0-"); ax[0].plot(tg, ol, "C1-"); ax[0].legend()
plt.tight_layout(); plt.show()
print("All plotted proportions lie in [0, 1]:", bool(b[["prop_outside_2wo", "prop_outside_2mo"]].stack().between(0, 1).all()))
Pooled alarm stimuli, young against old: F(3, 314) = 8.006, p = 3.7e-05
young: YM 0.8579, Y0 0.5714, K 0.06954
old: YM 0.9135, Y0 0.6631, K 0.09862
shared: YM 0.8815, Y0 0.6172, K 0.08518
Old curve above young at every second from 0 to 60 s: True (smallest old minus young 0.061)
expected tolerance measured within
YM young 0.858 0.081 0.8579 True
YM old 0.914 0.033 0.9135 True
Y0 young 0.571 0.046 0.5714 True
Y0 old 0.663 0.04 0.6631 True
K young 0.07 0.036 0.0695 True
K old 0.099 0.029 0.0986 True
All plotted proportions lie in [0, 1]: True
Result: reproduced. Separate curves for young and old ants fit better than one shared curve, F(3, 314) = 8.006, p = 3.7e-05 (Table S1: F(3, 314) = 8.006, p < 0.0001). All six parameters are within tolerance (YM 0.858 and 0.914, Y0 0.571 and 0.663, K 0.070 and 0.099 for young and old). Old ants are faster under both readings: K is higher for old ants (0.0986 against 0.0695), and the old curve lies above the young one at every second from 0 to 60 s (by at least 0.061).
veh = b[b.stimulus == "pentane"]
r_veh = antal.extra_ss_test(antal.quadratic, {"young": xy(veh, "prop_outside_2wo"),
"old": xy(veh, "prop_outside_2mo")}, P0)
show("Vehicle (pentane), young against old, quadratic", r_veh, names=("B0", "B1", "B2"))
Vehicle (pentane), young against old, quadratic: F(3, 90) = 2.617, p = 0.056
young: B0 0.3575, B1 -0.001075, B2 3.436e-05
old: B0 0.5016, B1 -0.001581, B2 4.284e-06
shared: B0 0.4296, B1 -0.001328, B2 1.932e-05
Result: reproduced. For the vehicle, the quadratic fit gives F(3, 90) = 2.617, p = 0.056, so the single curve is not rejected at 0.05, as in Table S1 (F(3, 90) = 2.617, p = 0.056).
r_stim = antal.extra_ss_test(antal.logistic, {s: xy(alarm[alarm.stimulus == s], "prop_outside_total")
for s in ("4m3heptanone", "4m3heptanol", "blend")}, P0)
show("Three alarm stimuli, all ants", r_stim)
Three alarm stimuli, all ants: F(6, 151) = 2.144, p = 0.052
4m3heptanone: YM 0.8755, Y0 0.5925, K 0.09477
4m3heptanol: YM 0.9359, Y0 0.6599, K 0.05678
blend: YM 0.8446, Y0 0.5866, K 0.1026
shared: YM 0.8809, Y0 0.6142, K 0.08385
Result: reproduced. One curve for the three alarm stimuli is not rejected: F(6, 151) = 2.144, p = 0.052 (Table S1: F(6, 151) = 2.144, p = 0.052).
r_one = {}
for s in ("4m3heptanone", "4m3heptanol", "blend"):
d = alarm[alarm.stimulus == s]
r_one[s] = antal.extra_ss_test(antal.logistic, {"young": xy(d, "prop_outside_2wo"),
"old": xy(d, "prop_outside_2mo")}, P0)
show(f"{s}, young against old", r_one[s])
yo, ol = (antal.logistic(tg, *r_one[s]["separate"][k]) for k in ("young", "old"))
print(f" old above young at every second 0-60 s: {bool((ol > yo).all())}; "
f"K old above K young: {r_one[s]['separate']['old'][2] > r_one[s]['separate']['young'][2]}")
4m3heptanone, young against old: F(3, 90) = 6.377, p = 0.00057
young: YM 0.8298, Y0 0.5336, K 0.09494
old: YM 0.9207, Y0 0.662, K 0.09729
shared: YM 0.8757, Y0 0.5976, K 0.0948
old above young at every second 0-60 s: True; K old above K young: True
4m3heptanol, young against old: F(3, 106) = 1.979, p = 0.12
young: YM 0.9953, Y0 0.6319, K 0.03576
old: YM 0.9361, Y0 0.6923, K 0.08447
shared: YM 0.9371, Y0 0.6603, K 0.05955
old above young at every second 0-60 s: True; K old above K young: True
blend, young against old: F(3, 106) = 2.529, p = 0.061
young: YM 0.8036, Y0 0.5454, K 0.08945
old: YM 0.8872, Y0 0.6339, K 0.1139
shared: YM 0.8433, Y0 0.5905, K 0.1037
old above young at every second 0-60 s: True; K old above K young: True
Result: figs1-heptanone-separate-curves reproduced; figs1-heptanol-blend-single-curve reproduced. For 4-methyl-3-heptanone separate curves are preferred, F(3, 90) = 6.377, p = 0.00057, and the old curve lies above the young one at every second from 0 to 60 s (reading b; K is also slightly higher for old ants, 0.0973 against 0.0949). For 4-methyl-3-heptanol (F(3, 106) = 1.979, p = 0.12) and the blend (F(3, 106) = 2.529, p = 0.061) single curves are not rejected; in both the old curve lies above the young one, the similar trend the legend describes. All F values equal Table S1's to the third decimal.
Streaming the imaging data¶
For every trial (including the paraffin oil trials, used as a control), frames 1-5 and 9-14 of all 33 planes are read; each frame is aligned to the plane's frames 1-5 by phase correlation; the peak fold change map is made as in the Methods (minimum filter of radius 2, maximum projection through the planes, clipped to [-1, 4]). For the 48% ants, the per-plane peak maps are also kept in 4 x 4 pixel blocks, to find the focal glomeruli.
t0 = time.time()
keep = [(s, t) for s, t, o, c in zip(trials.subject, trials.t, trials.odor, trials.conc) if c == 0.48 and o != "paraffin-oil"]
maps = antal.all_volume_maps({(s, t): b for s, t, b in zip(trials.subject, trials.t, trials.blobs)},
keep_keys=keep, processes=4, progress=100)
print(f"{len(maps)} trials in {(time.time() - t0) / 60:.1f} min")
trials["key"] = list(zip(trials.subject, trials.t))
100 of 462 trials
200 of 462 trials
300 of 462 trials
400 of 462 trials
462 trials in 14.8 min
sh = pd.DataFrame([{"subject": k[0], "t": k[1], "median_shift": np.median([np.median(p) for p in v["shifts"]]),
"planes_over_2px": int(sum(np.median(p) > 2 for p in v["shifts"])),
"frames_not_aligned": int(sum(np.sum(np.array(p) > antal.MAX_SHIFT) for p in v["shifts"]))}
for k, v in maps.items()])
print("Motion: per trial, the median over planes of the median frame shift (pixels):")
print(sh.median_shift.describe().round(2).to_string())
print("Trials with a median shift above 2 px:", int((sh.median_shift > 2).sum()), "of", len(sh))
print("Frames whose shift estimate exceeded", antal.MAX_SHIFT, "px and were left in place:",
int(sh.frames_not_aligned.sum()), "of", len(sh) * 33 * 11)
print("\nPer ant, median shift over trials:")
print(sh.groupby("subject").median_shift.median().round(1).to_string())
h = pd.Series({k: hashlib.md5(v["raw_max"].tobytes()).hexdigest() for k, v in maps.items()})
print("\nTrials whose raw maximum projection is identical to another trial's:", int(h.duplicated(keep=False).sum()))
Motion: per trial, the median over planes of the median frame shift (pixels): count 462 mean 1.27 std 0.94 min 0 25% 1 50% 1 75% 2 max 6 Trials with a median shift above 2 px: 28 of 462 Frames whose shift estimate exceeded 20 px and were left in place: 2742 of 167706 Per ant, median shift over trials: subject ant1-m1-d11-y2023 1 ant1-m1-d17-y2023 1 ant1-m1-d20-y2023 1 ant1-m2-d24-y2023 1 ant1-m4-d6-y2023 1 ant2-m1-d11-y2023 1 ant2-m1-d17-y2023 1 ant2-m1-d20-y2023 1 ant2-m2-d24-y2023 1 ant3-m1-d10-y2023 1.5 ant3-m1-d17-y2023 1 ant3-m2-d24-y2023 1 ant4-m1-d10-y2023 1 ant4-m1-d11-y2023 1 ant4-m1-d18-y2023 1 ant4-m1-d20-y2023 1 ant4-m4-d6-y2023 1 ant5-m1-d10-y2023 1 ant5-m1-d11-y2023 1 ant5-m4-d6-y2023 1 ant6-m1-d10-y2023 1
Trials whose raw maximum projection is identical to another trial's: 0
The whole-AL outline¶
The Methods define "a single ROI around the boundary of the AL for each ant, based on a max Z-projection of raw fluorescence", drawn by hand. Here it is taken from the mean over the ant's trials of the raw maximum projection (frames 1-5) by Otsu's threshold, a standard choice (claims file); the claims are also run with the outline shrunk and grown by 5 pixels.
raw_ant = {s: np.mean([maps[k]["raw_max"] for k in trials[trials.subject == s].key], 0) for s in meta.index if (trials.subject == s).any()}
masks = {s: {g: antal.al_mask(r, grow=g) for g in (-5, 0, 5)} for s, r in raw_ant.items()}
# the background level of each ant (median of the raw maximum projection outside the AL outline),
# the F0 floor of reading (b) of the glomerulus fold change (antal.roi_trace)
FLOOR = {s: float(np.median(r[~masks[s][0]])) for s, r in raw_ant.items()}
area = pd.DataFrame({s: {f"grow {g}": m[g].sum() for g in m} for s, m in masks.items()}).T
area["area_um2 (grow 0)"] = (area["grow 0"] * 0.2889 ** 2).round(0)
print("AL outline area per ant (pixels):")
print(area.to_string())
print("\nBackground level outside the AL (raw units), per ant:", {s: round(v, 1) for s, v in FLOOR.items()})
print("Raw maximum projection inside the AL, median over ants of the per-ant 1st percentile:",
round(float(np.median([np.percentile(raw_ant[s][masks[s][0]], 1) for s in raw_ant])), 1))
ants = list(raw_ant)
fig, axs = plt.subplots(3, 7, figsize=(21, 9))
for a, s in zip(axs.flat, ants):
a.imshow(np.log1p(raw_ant[s]), cmap="gray"); a.contour(masks[s][0], [0.5], colors="r", linewidths=0.8)
a.set_title(f"{s} ({meta.cohort[s]}, {meta.conc[s]:.0%})", fontsize=8); a.axis("off")
plt.tight_layout(); plt.show()
AL outline area per ant (pixels):
grow -5 grow 0 grow 5 area_um2 (grow 0)
ant1-m1-d11-y2023 65707 70840 76121 5913
ant1-m1-d17-y2023 60184 65067 70098 5431
ant1-m1-d20-y2023 61252 66630 72159 5561
ant1-m2-d24-y2023 50370 54849 59476 4578
ant1-m4-d6-y2023 75499 81399 87425 6794
ant2-m1-d11-y2023 64539 69915 75439 5835
ant2-m1-d17-y2023 57209 61970 66817 5172
ant2-m1-d20-y2023 50979 55355 59879 4620
ant2-m2-d24-y2023 73702 79669 85790 6649
ant3-m1-d10-y2023 57611 62315 67168 5201
ant3-m1-d17-y2023 58493 63430 68515 5294
ant3-m2-d24-y2023 61720 66830 72088 5578
ant4-m1-d10-y2023 62710 67919 73277 5669
ant4-m1-d11-y2023 58114 62969 67972 5256
ant4-m1-d18-y2023 65398 70593 75935 5892
ant4-m1-d20-y2023 63994 69073 74300 5765
ant4-m4-d6-y2023 59832 64940 70196 5420
ant5-m1-d10-y2023 71206 76577 82097 6391
ant5-m1-d11-y2023 82352 88472 94362 7384
ant5-m4-d6-y2023 67029 72441 78001 6046
ant6-m1-d10-y2023 63761 68853 74045 5747
Background level outside the AL (raw units), per ant: {'ant1-m1-d11-y2023': 155.1, 'ant1-m1-d17-y2023': 104.4, 'ant1-m1-d20-y2023': 72.9, 'ant1-m2-d24-y2023': 82.6, 'ant1-m4-d6-y2023': 91.8, 'ant2-m1-d11-y2023': 104.7, 'ant2-m1-d17-y2023': 93.5, 'ant2-m1-d20-y2023': 101.6, 'ant2-m2-d24-y2023': 89.7, 'ant3-m1-d10-y2023': 144.5, 'ant3-m1-d17-y2023': 108.6, 'ant3-m2-d24-y2023': 102.4, 'ant4-m1-d10-y2023': 111.0, 'ant4-m1-d11-y2023': 144.8, 'ant4-m1-d18-y2023': 97.4, 'ant4-m1-d20-y2023': 92.4, 'ant4-m4-d6-y2023': 90.0, 'ant5-m1-d10-y2023': 137.0, 'ant5-m1-d11-y2023': 118.2, 'ant5-m4-d6-y2023': 110.1, 'ant6-m1-d10-y2023': 150.5}
Raw maximum projection inside the AL, median over ants of the per-ant 1st percentile: 470.1
Stimulus timing¶
The files carry no stimulus times. As a check that the odor arrives in the frames the Methods assume (after frames 1-5, peak in frames 9-14), the whole-AL mean of the peak map is compared between odor trials and the paraffin oil trial of each ant, and (below, after the glomerulus traces) the frame-by-frame time course is shown.
def al_mean(key, grow=0, field="peak"):
return float(maps[key][field][masks[key[0]][grow]].mean())
trials["al_mean"] = [al_mean(k) for k in trials.key]
trials["al_mean_g-5"] = [al_mean(k, -5) for k in trials.key]
trials["al_mean_g+5"] = [al_mean(k, 5) for k in trials.key]
trials["al_mean_no_z0"] = [al_mean(k, 0, "peak_no_z0") for k in trials.key]
trials["al_mean_no_min"] = [al_mean(k, 0, "peak_no_min") for k in trials.key]
# the offset of each ant: the whole-AL mean of its paraffin oil trial, subtracted as a variant
par_offset = trials[trials.odor == "paraffin-oil"].set_index("subject").al_mean
trials["al_mean_minus_paraffin"] = trials.al_mean - trials.subject.map(par_offset)
print("Whole-AL mean peak fold change, by odor (all ants, median over trials):")
print(trials.groupby(["conc", "odor"]).al_mean.describe()[["count", "50%", "min", "max"]].round(3).to_string())
print("\nThe offset: paraffin oil trials, whole-AL mean with the minimum filter (the Methods' map) and without it:")
po = trials[trials.odor == "paraffin-oil"]
print(po[["al_mean", "al_mean_no_min"]].describe().loc[["count", "min", "50%", "max"]].round(3).to_string())
print("Per ant paraffin oil offset:", par_offset.round(3).to_dict())
Whole-AL mean peak fold change, by odor (all ants, median over trials):
count 50% min max
conc odor
0.03 3-hexanone 21 -0.101 -0.273 0.971
4-methyl-3-heptanol 21 -0.222 -0.486 0.114
4-methyl-3-heptanone 21 -0.215 -0.306 0.232
ethanol 21 -0.241 -0.372 0.93
ethylpyrazine 21 -0.217 -0.345 0.219
isopropanol 21 -0.186 -0.362 0.094
propionic-acid 21 -0.272 -0.38 1.357
0.48 3-hexanone 42 0.413 -0.412 3.398
4-methyl-3-heptanol 42 -0.217 -0.481 0.06
4-methyl-3-heptanone 42 -0.204 -0.425 0.301
ethanol 42 -0.199 -0.504 1.564
ethylpyrazine 42 -0.183 -0.44 1.002
isopropanol 42 -0.011 -0.477 3.011
propionic-acid 42 -0.24 -0.461 0.064
1 paraffin-oil 21 -0.232 -0.453 0.075
The offset: paraffin oil trials, whole-AL mean with the minimum filter (the Methods' map) and without it:
al_mean al_mean_no_min
count 21 21
min -0.453 3.265
50% -0.232 3.797
max 0.075 3.985
Per ant paraffin oil offset: {'ant1-m1-d11-y2023': 0.075, 'ant1-m1-d17-y2023': -0.201, 'ant1-m1-d20-y2023': -0.218, 'ant1-m2-d24-y2023': -0.109, 'ant1-m4-d6-y2023': -0.203, 'ant2-m1-d11-y2023': -0.232, 'ant2-m1-d17-y2023': -0.168, 'ant2-m1-d20-y2023': -0.249, 'ant2-m2-d24-y2023': -0.168, 'ant3-m1-d10-y2023': -0.209, 'ant3-m1-d17-y2023': -0.298, 'ant3-m2-d24-y2023': -0.252, 'ant4-m1-d10-y2023': -0.261, 'ant4-m1-d11-y2023': -0.188, 'ant4-m1-d18-y2023': -0.243, 'ant4-m1-d20-y2023': -0.282, 'ant4-m4-d6-y2023': -0.276, 'ant5-m1-d10-y2023': -0.321, 'ant5-m1-d11-y2023': -0.248, 'ant5-m4-d6-y2023': -0.134, 'ant6-m1-d10-y2023': -0.453}
fig2d-whole-al-alarm-no-age-effect¶
The mean calcium response across the entire AL to each alarm pheromone does not differ between young and old ants, at 3% or at 48%.
Per trial, the mean of the peak map inside the AL outline; per ant, the mean of its three trials of the odor; Welch's t test of young against old for each pheromone and concentration.
odor_trials = trials[trials.odor != "paraffin-oil"]
def per_ant(col="al_mean", df=None):
df = odor_trials if df is None else df
return df.groupby(["conc", "odor", "subject", "cohort"])[col].mean().reset_index()
def welch_table(pa, col, odors, concs=(0.03, 0.48)):
rows = []
for c in concs:
for o in odors:
d = pa[(pa.conc == c) & (pa.odor == o)]
y, ol = d[d.cohort == "young"][col], d[d.cohort == "old"][col]
if len(y) < 2 or len(ol) < 2:
continue
t, p = stats.ttest_ind(y, ol, equal_var=False)
rows.append({"conc": c, "odor": o, "n_young": len(y), "n_old": len(ol), "mean_young": y.mean(),
"mean_old": ol.mean(), "t": t, "p": p})
return pd.DataFrame(rows)
res2d = {}
for col in ("al_mean", "al_mean_g-5", "al_mean_g+5", "al_mean_no_z0"):
res2d[col] = welch_table(per_ant(col), col, antal.ALARM)
print(f"--- {col} ---")
print(res2d[col].to_string(index=False))
pa = per_ant()
print("\nPer ant values (al_mean):")
print(pa[pa.odor.isin(antal.ALARM)].pivot_table(index=["conc", "cohort", "subject"], columns="odor", values="al_mean").round(3).to_string())
--- al_mean ---
conc odor n_young n_old mean_young mean_old t p
0.03 4-methyl-3-heptanone 3 4 -0.1764 -0.1568 -0.2903 0.784
0.03 4-methyl-3-heptanol 3 4 -0.1479 -0.2565 2.465 0.06499
0.48 4-methyl-3-heptanone 7 7 -0.1655 -0.2156 0.8855 0.3934
0.48 4-methyl-3-heptanol 7 7 -0.1804 -0.2506 1.619 0.1398
--- al_mean_g-5 ---
conc odor n_young n_old mean_young mean_old t p
0.03 4-methyl-3-heptanone 3 4 -0.1547 -0.1353 -0.2837 0.7886
0.03 4-methyl-3-heptanol 3 4 -0.1252 -0.2385 2.529 0.06132
0.48 4-methyl-3-heptanone 7 7 -0.1434 -0.1959 0.9175 0.377
0.48 4-methyl-3-heptanol 7 7 -0.1588 -0.2329 1.691 0.1242
--- al_mean_g+5 ---
conc odor n_young n_old mean_young mean_old t p
0.03 4-methyl-3-heptanone 3 4 -0.2013 -0.1814 -0.3027 0.775
0.03 4-methyl-3-heptanol 3 4 -0.1735 -0.2766 2.434 0.0672
0.48 4-methyl-3-heptanone 7 7 -0.1914 -0.2389 0.8561 0.4088
0.48 4-methyl-3-heptanol 7 7 -0.205 -0.272 1.573 0.1505
--- al_mean_no_z0 ---
conc odor n_young n_old mean_young mean_old t p
0.03 4-methyl-3-heptanone 3 4 -0.1765 -0.1668 -0.1572 0.8813
0.03 4-methyl-3-heptanol 3 4 -0.1488 -0.2584 2.462 0.06654
0.48 4-methyl-3-heptanone 7 7 -0.1657 -0.2224 1.046 0.3164
0.48 4-methyl-3-heptanol 7 7 -0.181 -0.2514 1.626 0.1381
Per ant values (al_mean):
odor 4-methyl-3-heptanol 4-methyl-3-heptanone
conc cohort subject
0.03 old ant1-m1-d11-y2023 -0.157 -0.139
ant2-m1-d11-y2023 -0.23 -0.012
ant4-m1-d11-y2023 -0.327 -0.188
ant5-m1-d11-y2023 -0.312 -0.288
young ant1-m1-d17-y2023 -0.184 -0.244
ant2-m1-d17-y2023 -0.116 -0.125
ant3-m1-d17-y2023 -0.144 -0.16
0.48 old ant1-m4-d6-y2023 -0.144 -0.223
ant3-m1-d10-y2023 -0.264 -0.171
ant4-m1-d10-y2023 -0.243 -0.285
ant4-m4-d6-y2023 -0.203 -0.131
ant5-m1-d10-y2023 -0.186 -0.068
ant5-m4-d6-y2023 -0.254 -0.23
ant6-m1-d10-y2023 -0.46 -0.402
young ant1-m1-d20-y2023 -0.218 -0.18
ant1-m2-d24-y2023 -0.179 -0.1
ant2-m1-d20-y2023 -0.267 -0.291
ant2-m2-d24-y2023 -0.203 0.017
ant3-m2-d24-y2023 -0.114 -0.167
ant4-m1-d18-y2023 -0.14 -0.267
ant4-m1-d20-y2023 -0.142 -0.17
Result: reproduced. None of the four Welch tests finds an age difference in the whole-AL mean peak fold change: at 3%, p = 0.78 (4-methyl-3-heptanone) and 0.065 (4-methyl-3-heptanol); at 48%, p = 0.39 and 0.14. The outline shrunk or grown by 5 pixels, and the projection without the 0 um plane, give the same (smallest p 0.061). The cohort means are all negative (-0.15 to -0.26): the minimum filter makes the map of a non-responding AL negative, as the paraffin oil trials show (median -0.23).
figs3b-whole-al-general-decline and figs3b-whole-al-other-no-age-effect¶
At 48%, 3-hexanone and isopropanol evoke broad AL responses in young but not old ants; mean peak responses across the AL fall more than 9-fold (3-hexanone) and more than 57-fold (isopropanol).
The age decline of whole-AL responses is specific to 3-hexanone and isopropanol at 48%.
The measure has a negative offset (paraffin oil trials near -0.2, above). Two variants show what it
does to the ratios of cohort means: each ant's paraffin oil value subtracted from its odor values
(al_mean_minus_paraffin), and the map made without the minimum filter (al_mean_no_min).
res3b = {}
for col in ("al_mean", "al_mean_g-5", "al_mean_g+5", "al_mean_no_z0", "al_mean_minus_paraffin", "al_mean_no_min"):
r = welch_table(per_ant(col), col, antal.GENERAL)
r["ratio_young_over_old"] = r.mean_young / r.mean_old
res3b[col] = r
print(f"--- {col} ---")
print(r.to_string(index=False))
print("\nPer ant values at 48% (al_mean):")
print(pa[(pa.conc == 0.48) & pa.odor.isin(antal.GENERAL)].pivot_table(index=["cohort", "subject"], columns="odor", values="al_mean").round(3).to_string())
fig, ax = plt.subplots(1, 2, figsize=(12, 3.5))
for a, c in zip(ax, (0.03, 0.48)):
d = pa[pa.conc == c]
for i, o in enumerate(antal.ODORS):
for j, co in enumerate(("young", "old")):
v = d[(d.odor == o) & (d.cohort == co)].al_mean
a.scatter(np.full(len(v), i + (j - 0.5) * 0.3), v, color=f"C{j}", s=12, label=co if i == 0 else None)
a.set_xticks(range(7)); a.set_xticklabels([o[:12] for o in antal.ODORS], rotation=30, fontsize=7)
a.set_title(f"{c:.0%}"); a.set_ylabel("whole-AL mean peak fold change"); a.legend(fontsize=7)
plt.tight_layout(); plt.show()
print("Range of plotted values:", round(pa.al_mean.min(), 3), "to", round(pa.al_mean.max(), 3))
--- al_mean --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 0.2246 -0.1495 1.951 0.1685 -1.503 0.03 isopropanol 3 4 -0.1626 -0.21 0.7089 0.511 0.7742 0.03 ethylpyrazine 3 4 -0.1447 -0.2387 0.9323 0.4417 0.606 0.03 ethanol 3 4 -0.1173 -0.1858 0.3659 0.744 0.6311 0.03 propionic-acid 3 4 -0.3065 -0.06721 -1.799 0.1691 4.561 0.48 3-hexanone 7 7 1.613 0.03934 7.392 1.999e-05 40.99 0.48 isopropanol 7 7 0.8602 -0.1514 3.702 0.008514 -5.683 0.48 ethylpyrazine 7 7 -0.04757 -0.1975 2.018 0.07035 0.2409 0.48 ethanol 7 7 -0.07243 -0.2331 1.295 0.2342 0.3108 0.48 propionic-acid 7 7 -0.2719 -0.237 -0.7455 0.4773 1.147 --- al_mean_g-5 --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 0.2599 -0.1285 1.941 0.1706 -2.022 0.03 isopropanol 3 4 -0.1391 -0.1895 0.7259 0.5012 0.7342 0.03 ethylpyrazine 3 4 -0.1217 -0.2182 0.938 0.4392 0.5579 0.03 ethanol 3 4 -0.09837 -0.1649 0.3562 0.7503 0.5964 0.03 propionic-acid 3 4 -0.2859 -0.04207 -1.76 0.1761 6.797 0.48 3-hexanone 7 7 1.69 0.07046 7.471 1.731e-05 23.98 0.48 isopropanol 7 7 0.9166 -0.126 3.675 0.008876 -7.276 0.48 ethylpyrazine 7 7 -0.01937 -0.1744 2.029 0.0691 0.1111 0.48 ethanol 7 7 -0.04676 -0.2115 1.29 0.2359 0.2211 0.48 propionic-acid 7 7 -0.2514 -0.2147 -0.7555 0.4719 1.171 --- al_mean_g+5 --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 0.1871 -0.174 1.963 0.1664 -1.075 0.03 isopropanol 3 4 -0.1897 -0.2324 0.6653 0.5366 0.8161 0.03 ethylpyrazine 3 4 -0.1715 -0.261 0.9122 0.4504 0.6571 0.03 ethanol 3 4 -0.1412 -0.2093 0.3674 0.7432 0.6745 0.03 propionic-acid 3 4 -0.3296 -0.09439 -1.849 0.1608 3.493 0.48 3-hexanone 7 7 1.53 0.005097 7.306 2.317e-05 300.2 0.48 isopropanol 7 7 0.7989 -0.1796 3.726 0.008199 -4.447 0.48 ethylpyrazine 7 7 -0.07981 -0.2237 2.005 0.07189 0.3568 0.48 ethanol 7 7 -0.1014 -0.2583 1.304 0.2314 0.3928 0.48 propionic-acid 7 7 -0.2949 -0.2622 -0.7283 0.4869 1.125
--- al_mean_no_z0 --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 0.2243 -0.1517 1.96 0.1671 -1.479 0.03 isopropanol 3 4 -0.163 -0.2116 0.7221 0.5034 0.7704 0.03 ethylpyrazine 3 4 -0.1448 -0.2398 0.9401 0.438 0.6039 0.03 ethanol 3 4 -0.1201 -0.1895 0.3743 0.7381 0.6338 0.03 propionic-acid 3 4 -0.3067 -0.09361 -1.861 0.1588 3.276 0.48 3-hexanone 7 7 1.613 0.03777 7.396 1.986e-05 42.69 0.48 isopropanol 7 7 0.8601 -0.1519 3.703 0.008504 -5.663 0.48 ethylpyrazine 7 7 -0.05094 -0.2018 2.096 0.06177 0.2524 0.48 ethanol 7 7 -0.0728 -0.2344 1.303 0.2317 0.3106 0.48 propionic-acid 7 7 -0.2723 -0.2378 -0.7391 0.481 1.145 --- al_mean_minus_paraffin --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 0.447 -0.001592 2.934 0.07673 -280.8 0.03 isopropanol 3 4 0.05975 -0.06213 1.897 0.1432 -0.9617 0.03 ethylpyrazine 3 4 0.07769 -0.0908 1.486 0.209 -0.8556 0.03 ethanol 3 4 0.1051 -0.03793 0.8155 0.474 -2.77 0.03 propionic-acid 3 4 -0.08416 0.08069 -0.9992 0.3832 -1.043 0.48 3-hexanone 7 7 1.83 0.3045 7.035 5.282e-05 6.01 0.48 isopropanol 7 7 1.078 0.1138 3.477 0.01202 9.47 0.48 ethylpyrazine 7 7 0.1697 0.06766 1.23 0.2509 2.508 0.48 ethanol 7 7 0.1449 0.03206 0.8517 0.4241 4.519 0.48 propionic-acid 7 7 -0.05457 0.02811 -1.683 0.1236 -1.941 --- al_mean_no_min --- conc odor n_young n_old mean_young mean_old t p ratio_young_over_old 0.03 3-hexanone 3 4 3.899 3.83 1.356 0.2379 1.018 0.03 isopropanol 3 4 3.796 3.803 -0.1228 0.9081 0.9981 0.03 ethylpyrazine 3 4 3.779 3.81 -0.6686 0.5485 0.9918 0.03 ethanol 3 4 3.821 3.809 0.2531 0.8164 1.003 0.03 propionic-acid 3 4 3.763 3.819 -0.7813 0.4721 0.9853 0.48 3-hexanone 7 7 3.965 3.865 1.807 0.1169 1.026 0.48 isopropanol 7 7 3.904 3.821 1.341 0.2227 1.022 0.48 ethylpyrazine 7 7 3.701 3.785 -1.027 0.3316 0.9779 0.48 ethanol 7 7 3.691 3.779 -0.9404 0.3658 0.9767 0.48 propionic-acid 7 7 3.571 3.788 -2.281 0.04199 0.9429 Per ant values at 48% (al_mean):
odor 3-hexanone ethanol ethylpyrazine isopropanol propionic-acid
cohort subject
old ant1-m4-d6-y2023 0.63 -0.166 -0.186 0.076 -0.193
ant3-m1-d10-y2023 0.134 -0.11 -0.187 -0.105 -0.174
ant4-m1-d10-y2023 -0.044 -0.176 -0.131 -0.218 -0.277
ant4-m4-d6-y2023 0.073 -0.217 -0.168 -0.095 -0.065
ant5-m1-d10-y2023 0.006 -0.254 -0.089 -0.085 -0.226
ant5-m4-d6-y2023 -0.175 -0.265 -0.198 -0.202 -0.29
ant6-m1-d10-y2023 -0.349 -0.443 -0.425 -0.43 -0.433
young ant1-m1-d20-y2023 1.262 -0.23 -0.156 0.359 -0.295
ant1-m2-d24-y2023 1.179 -0.18 -0.178 0.093 -0.243
ant2-m1-d20-y2023 1.916 -0.235 -0.165 0.317 -0.333
ant2-m2-d24-y2023 1.369 -0.161 0.012 1.773 -0.25
ant3-m2-d24-y2023 2.482 -0.166 0.199 1.806 -0.209
ant4-m1-d18-y2023 1.775 -0.162 -0.187 0.585 -0.328
ant4-m1-d20-y2023 1.305 0.628 0.143 1.088 -0.244
Range of plotted values: -0.46 to 2.482
Result: figs3b-whole-al-general-decline partially reproduced; figs3b-whole-al-other-no-age-effect reproduced. At 48% the whole-AL response falls with age for 3-hexanone (young 1.613, old 0.039, t = 7.39, p = 2.0e-05) and isopropanol (young 0.860, old -0.151, p = 0.0085). The ratio of cohort means is 41.0 for 3-hexanone, above the 8.1 the claim needs, but for isopropanol the old mean is negative, so the ratio (-5.7) cannot express a fold decline and does not reach 51.3; with the outline grown or shrunk the 3-hexanone ratio is 300 and 24. The measure has an offset: the paraffin oil trials give a median of -0.23 (per ant -0.45 to 0.075). With each ant's paraffin oil value subtracted, both declines remain (p = 5.3e-05 and 0.012) and both old means are positive, but the ratios are 6.0 and 9.5, below 8.1 and 51.3, so the ratios are not reached either way. Without the minimum filter the map is saturated near the clip value of 4 even for paraffin oil (median 3.80), since the projection then takes the largest per-pixel noise of 33 planes; that variant measures no response and is shown only to locate the offset in the minimum filter. The eight other tests (five odorants at 3%; ethylpyrazine, ethanol and propionic acid at 48%) give p from 0.070 to 0.74, and the smallest p over the outline, projection and paraffin-subtracted variants is 0.062 (ethylpyrazine at 48%), so the decline is specific to 3-hexanone and isopropanol at 48%. The paper's Figure S3B scale could not be compared with this offset: its values are not given in the text.
fig2e-heptanone-breadth-falls, fig2-alarm-sparse, figs3c-general-breadth-no-age-effect¶
At 48%, 4-methyl-3-heptanone activates a few more glomeruli in young than in old ants (medians of 4 against 2 responding ROIs), while the count for 4-methyl-3-heptanol does not differ with age.
The ROIs were drawn by hand. The claims file's procedure is used: regions of a trial's peak map at
= 0.2 that recur in at least two trials of any odor, split at glomerulus size (in
antal.candidate_rois: local maxima of each map, merged across trials when closer than half a glomerulus, kept when found in two or more trials, as disks of a glomerulus's diameter). The paper gives no glomerulus size, so three diameters are run: 25, 35 and 50 pixels (7, 10 and 14 um). An ROI responds to an odor when its mean peak fold change, averaged over that odor's trials, is >= 0.2 (Methods). Motion-artifact trials: (a) all trials kept; (b) the trials Data S2 leaves out for that ant (Data S2 holds no ROI counts, so its trial list is the only record of exclusions). Data S2 holds every trial of every 48% ant for at least one glomerulus, so a trial missing for every glomerulus excludes nothing; reading (b) therefore leaves out a trial that Data S2 lacks for one of the ant's glomeruli while it holds that odor's other repeats there. Wilcoxon rank-sum tests (R's default, normal approximation with continuity correction when there are ties).
s2_any = s2[s2.conc == 0.48].groupby("subject").apply(lambda d: set(zip(d.odor_ds, d.rep)), include_groups=False)
missing_every = {s: sorted(set(zip(g.odor, g.rep)) - s2_any.get(s, set()))
for s, g in odor_trials[odor_trials.conc == 0.48].groupby("subject")}
print("Trials of 48% ants that Data S2 lacks for every glomerulus:", sum(len(v) for v in missing_every.values()))
# a repeat that Data S2 lacks for a glomerulus while it holds that odor's other repeats (a glomerulus
# without any trial of an odor is a selection of glomeruli, not of trials)
s2_48 = s2[s2.conc == 0.48].drop_duplicates(["subject", "glomerulus", "odor_ds", "rep"])
excluded = {s: sorted({(o, r) for (_, o), x in s2_48[s2_48.subject == s].groupby(["glomerulus", "odor_ds"])
for r in (1, 2, 3) if r not in set(x.rep)})
for s in odor_trials[odor_trials.conc == 0.48].subject.unique()}
print("Trials of 48% ants that Data S2 lacks for a glomerulus while holding the odor's other repeats (reading b excludes them):")
for s, v in excluded.items():
print(f" {s}: {v}")
counts = []
for s, g in odor_trials[odor_trials.conc == 0.48].groupby("subject"):
for reading in ("a", "b"):
gg = g if reading == "a" else g[[(o, r) not in excluded[s] for o, r in zip(g.odor, g.rep)]]
mp = [maps[k]["peak"] for k in gg.key]
for d in (25, 35, 50):
rois = antal.candidate_rois(mp, masks[s][0], d)
c = antal.count_responding(mp, list(gg.odor), rois)
counts.append({"subject": s, "cohort": meta.cohort[s], "reading": reading, "diameter": d,
"n_rois": len(rois), **c})
counts = pd.DataFrame(counts)
def wilcox(c, odors):
rows = []
for (rd, d), x in c.groupby(["reading", "diameter"]):
for o in odors:
y, ol = x[x.cohort == "young"][o], x[x.cohort == "old"][o]
u, p = stats.mannwhitneyu(y, ol, alternative="two-sided", use_continuity=True)
rows.append({"reading": rd, "diameter_px": d, "odor": o, "median_young": y.median(),
"median_old": ol.median(), "W": u, "p": p})
return pd.DataFrame(rows)
print("\nCounts of responding ROIs per ant, reading (a), diameter 35 px:")
print(counts[(counts.reading == "a") & (counts.diameter == 35)].drop(columns=["reading", "diameter"]).sort_values("cohort").to_string(index=False))
w2e = wilcox(counts, antal.ALARM)
print("\nAlarm pheromones, young against old:")
print(w2e.to_string(index=False))
Trials of 48% ants that Data S2 lacks for every glomerulus: 0
Trials of 48% ants that Data S2 lacks for a glomerulus while holding the odor's other repeats (reading b excludes them):
ant1-m1-d20-y2023: []
ant1-m2-d24-y2023: [('ethanol', 3)]
ant1-m4-d6-y2023: []
ant2-m1-d20-y2023: []
ant2-m2-d24-y2023: [('4-methyl-3-heptanone', 3)]
ant3-m1-d10-y2023: []
ant3-m2-d24-y2023: []
ant4-m1-d10-y2023: []
ant4-m1-d18-y2023: []
ant4-m1-d20-y2023: []
ant4-m4-d6-y2023: [('3-hexanone', 1)]
ant5-m1-d10-y2023: []
ant5-m4-d6-y2023: []
ant6-m1-d10-y2023: []
Counts of responding ROIs per ant, reading (a), diameter 35 px:
subject cohort n_rois 3-hexanone 4-methyl-3-heptanol 4-methyl-3-heptanone ethanol ethylpyrazine isopropanol propionic-acid
ant1-m4-d6-y2023 old 96 52 2 0 0 3 32 1
ant3-m1-d10-y2023 old 71 29 0 0 0 2 0 2
ant4-m1-d10-y2023 old 59 15 2 1 0 3 1 2
ant4-m4-d6-y2023 old 74 28 0 1 0 4 1 5
ant5-m1-d10-y2023 old 82 22 4 17 0 11 8 0
ant5-m4-d6-y2023 old 44 4 0 0 0 3 0 0
ant6-m1-d10-y2023 old 16 0 0 0 0 0 0 0
ant1-m1-d20-y2023 young 75 72 0 1 0 2 48 0
ant1-m2-d24-y2023 young 62 50 0 7 2 1 20 0
ant2-m1-d20-y2023 young 45 45 0 0 0 4 38 0
ant2-m2-d24-y2023 young 102 80 2 12 0 28 93 2
ant3-m2-d24-y2023 young 92 92 0 1 0 58 91 4
ant4-m1-d18-y2023 young 76 76 4 2 2 8 72 0
ant4-m1-d20-y2023 young 82 79 1 0 72 31 73 0
Alarm pheromones, young against old:
reading diameter_px odor median_young median_old W p
a 25 4-methyl-3-heptanone 4 2 34.5 0.2186
a 25 4-methyl-3-heptanol 0 2 23.5 0.9455
a 35 4-methyl-3-heptanone 1 0 32 0.3461
a 35 4-methyl-3-heptanol 0 0 23.5 0.9432
a 50 4-methyl-3-heptanone 0 0 27.5 0.7093
a 50 4-methyl-3-heptanol 0 0 32.5 0.2284
b 25 4-methyl-3-heptanone 4 2 35.5 0.1725
b 25 4-methyl-3-heptanol 0 2 23.5 0.9455
b 35 4-methyl-3-heptanone 1 0 33 0.2816
b 35 4-methyl-3-heptanol 0 0 23.5 0.9432
b 50 4-methyl-3-heptanone 0 0 28.5 0.6017
b 50 4-methyl-3-heptanol 0 0 32.5 0.2284
Result: blocked (methods underspecified). The counts depend on the glomerulus size, which the paper does not give, and the paper's hand-drawn ROIs and per-ant counts are not published (Data S2 holds no counts, so the claims file's comparison with them cannot be made). At 25 pixels the medians are 4 (young) and 2 (old) for 4-methyl-3-heptanone, the paper's values, but the Wilcoxon test gives p = 0.22 (0.17 under reading b); at 35 pixels 1 and 0 (p = 0.35, 0.28 under b); at 50 pixels 0 and 0 (p = 0.71, 0.60 under b). No reading finds the significant difference the paper reports. For 4-methyl-3-heptanol no reading finds a difference (p 0.23 to 0.95). Data S2 lacks no trial of a 48% ant for every glomerulus; reading (b) leaves out the three trials it lacks for one glomerulus while holding that odor's other repeats (one trial in each of three ants).
mx = counts.groupby(["reading", "diameter"])[antal.ALARM].max()
print("Largest count of responding ROIs over the 14 ants at 48%, per alarm pheromone:")
print(mx.to_string())
print("\nEvery ant at most 25 ROIs (5% of 500) under every reading:", bool((mx <= 25).all().all()),
"; at most 50 ROIs (10%):", bool((mx <= 50).all().all()))
print("ROIs found per ant (all odors), range:", counts.n_rois.min(), "to", counts.n_rois.max())
Largest count of responding ROIs over the 14 ants at 48%, per alarm pheromone:
4-methyl-3-heptanone 4-methyl-3-heptanol
reading diameter
a 25 32 11
35 17 4
50 7 2
b 25 39 11
35 23 4
50 9 2
Every ant at most 25 ROIs (5% of 500) under every reading: False ; at most 50 ROIs (10%): True
ROIs found per ant (all odors), range: 12 to 173
Result: blocked (methods underspecified), as it depends on fig2e-heptanone-breadth-falls. The counts are small fractions of 500 under every glomerulus size: the largest count of any ant for 4-methyl-3-heptanone is 32 (25 px), 17 (35 px) and 7 (50 px) ROIs under reading (a) and 39, 23 and 9 under reading (b); for 4-methyl-3-heptanol 11, 4 and 2 under both. The 10% reading holds under every size; the 5% reading fails at 25 px under both readings (32 and 39 ROIs).
w3c = wilcox(counts, ["ethylpyrazine", "ethanol", "propionic-acid"])
print(w3c.to_string(index=False))
print("\nThe automated ROIs: ROIs found per ant by diameter (min, median, max):")
print(counts[counts.reading == "a"].groupby("diameter").n_rois.agg(["min", "median", "max"]).to_string())
print("Largest count of responding ROIs per odorant, reading (a):")
print(counts[counts.reading == "a"].groupby("diameter")[antal.GENERAL].max().to_string())
reading diameter_px odor median_young median_old W p
a 25 ethylpyrazine 16 4 38.5 0.08317
a 25 ethanol 0 0 33 0.1996
a 25 propionic-acid 0 3 18.5 0.4358
a 35 ethylpyrazine 8 3 35 0.1984
a 35 ethanol 0 0 35 0.07479
a 35 propionic-acid 0 1 18.5 0.4339
a 50 ethylpyrazine 2 2 32.5 0.3242
a 50 ethanol 0 0 31.5 0.173
a 50 propionic-acid 0 0 23.5 0.9358
b 25 ethylpyrazine 16 5 38 0.0945
b 25 ethanol 0 0 33 0.2004
b 25 propionic-acid 0 3 17.5 0.3551
b 35 ethylpyrazine 8 3 35 0.1984
b 35 ethanol 0 0 35 0.07479
b 35 propionic-acid 0 1 19 0.4763
b 50 ethylpyrazine 2 2 32.5 0.3242
b 50 ethanol 0 0 31.5 0.173
b 50 propionic-acid 0 0 23.5 0.9358
The automated ROIs: ROIs found per ant by diameter (min, median, max):
min median max
diameter
25 17 122.5 173
35 16 74.5 102
50 12 37 55
Largest count of responding ROIs per odorant, reading (a):
3-hexanone isopropanol ethylpyrazine ethanol propionic-acid
diameter
25 155 160 99 128 11
35 92 93 58 72 5
50 53 53 33 40 2
Result: blocked (methods underspecified). By the letter of the criterion no odorant shows an age difference in the count of responding ROIs at any glomerulus size (smallest p 0.075, ethanol at 35 px; ethylpyrazine p 0.083 to 0.32, propionic acid 0.36 to 0.94). But the counts come from the same automated ROIs on which fig2e-heptanone-breadth-falls is blocked: that procedure does not find the positive age effect the paper reports for 4-methyl-3-heptanone, and its counts are implausible as glomerulus counts (17 to 173 ROIs per ant at 25 px, and up to 128 ROIs responding to ethanol, 72 in one young ant at 35 px, above the general odorants' sparse responses of Figure S3C). A null result from a procedure that misses the paper's effect does not test the claim, so this claim is blocked for the same reason as fig2e.
data-s2-glomerulus-link: finding the focal glomeruli¶
The paper drew one ROI per focal glomerulus (PGb, PGa, G1 to G4) by hand in each ant and gives no positions; the dandiset holds none. Data S2 holds, for each ant and glomerulus, the paper's fold-change trace of every trial it used. Each glomerulus is placed where the dandiset's per-trial peak fold change (frames 9-14, per plane, in 4 x 4 pixel blocks, averaged over the ROI disk and maximised over the plane and its neighbours) best correlates (Pearson r over the trials Data S2 holds for that glomerulus) with Data S2's per-trial peak. Only the pattern across trials is matched, not the amplitude. The AUCs are then read from the dandiset at that place: an ROI disk of radius 8 pixels (2.3 um, a standard choice; 5 and 12 are shown) on the maximum projection of the plane and its two neighbours, all 40 frames, aligned with the shifts measured on the middle plane of the trial, fold change from frames 1-5 and the sum over frames 6-40 (Methods), under readings (a) and (b) of the claims file.
Data S2 holds two quantifications of the same ants: names with a two-digit year (a4-1-18-23) carry the PGb and PGa alarm traces of Figure 3C and 3D, names with a four-digit year everything else. One place is found per ant and glomerulus from all of its Data S2 trials; the link is reported for each set apart. (Locating the Figure 3C and 3D set on its own six trials was tried while preparing, not run here; with six trials the correlation is near 1 almost anywhere a response is, so it is not used.)
Two checks of the placement. (1) Chance level: the best r over the same voxels when Data S2's values are shuffled, 200 times per placement, across trials (the verifier's suggestion) and, stricter, by odor (each odor's three values moved together to another odor), so that a placement is above chance when its r exceeds the 95th percentile of the shuffled maxima (p < 0.05). (2) Held-out placement: the place is chosen with the trials whose values are then measured and compared, so the link, and every test on the located ROIs, is optimistic. Each glomerulus is therefore also placed once per odor from Data S2's trials of the other odors (leave one odor out), and the AUCs of that odor's trials are read there; and, for the alarm pheromones, once from the general odorant trials alone. Placing from the six alarm trials alone, to test the general odorants, is not run: with six trials the best r is near 1 by chance (the null below shows it for the one glomerulus that Data S2 holds on six trials).
Link criterion (written before the AUCs were compared): an ant, glomerulus and Data S2 set agrees when the per-trial AUC from the dandiset (reading a, radius 8) correlates with Data S2's at r >= 0.7 across its trials. The link holds when at least 80% of these agree, holds in part from 50%, and fails below.
pk = antal.s2_peaks(s2[(s2.conc == 0.48) & s2.subject.isin(meta.index)])
loc_rows, jobs = [], []
tr48 = odor_trials[odor_trials.conc == 0.48].copy()
t0 = time.time()
ctrl = trials[(trials.odor == "paraffin-oil") & (trials.subject.map(meta.conc) == 0.48)]
shift_trials = pd.concat([tr48, ctrl])
SH = dict(zip(shift_trials.key, antal.all_trial_shifts([b[antal.SHIFT_PLANE] for b in shift_trials.blobs])))
print(f"motion over 40 frames measured for {len(SH)} trials in {(time.time() - t0) / 60:.1f} min")
SM = {k: antal.smooth_planes(v["planes_peak"]) for k, v in maps.items() if "planes_peak" in v}
rng_null = np.random.default_rng(773)
held_rows, hjobs = [], []
for (s, g), x in pk.groupby(["subject", "glomerulus"]):
t2 = tr48[tr48.subject == s].set_index(["odor", "rep"])
keys = [t2.loc[(o, r), "key"] for o, r in zip(x.odor_ds, x.rep)]
m = antal._block(masks[s][0].astype(float)) > 0.5
loc = antal.locate(SM, keys, x.s2_peak.to_numpy(), m, smooth=False)
n_trial = antal.locate_null(SM, keys, x.s2_peak.to_numpy(), m, n=200, rng=rng_null, smooth=False)
n_odor = antal.locate_null(SM, keys, x.s2_peak.to_numpy(), m, n=200, rng=rng_null, smooth=False, odors=list(x.odor_ds))
loc_rows.append({"subject": s, "cohort": meta.cohort[s], "glomerulus": g, "n_trials": len(x), **loc,
"null95_trial": np.quantile(n_trial, 0.95), "p_trial": (1 + np.sum(n_trial >= loc["r"])) / 201,
"null95_odor": np.quantile(n_odor, 0.95), "p_odor": (1 + np.sum(n_odor >= loc["r"])) / 201})
for (o, r), row in pd.concat([t2.reset_index(), trials[(trials.subject == s) & (trials.odor == "paraffin-oil")]]).set_index(["odor", "rep"]).iterrows():
jobs.append(((s, g, o, r), row.blobs, loc["z_index"], loc["y"], loc["x"], 8, (5, 12), SH[row.key], FLOOR[s]))
# held-out placements: leave one odor out, and for the alarm pheromones the general odorants only
splits = [(o, (x.odor_ds != o).to_numpy()) for o in x.odor_ds.unique()]
if x.odor_ds.isin(antal.ALARM).any() and x.odor_ds.isin(antal.GENERAL).any():
splits.append(("general only", ~x.odor_ds.isin(antal.ALARM).to_numpy()))
for held, use in splits:
xx = x[use]
hk = [t2.loc[(o, r), "key"] for o, r in zip(xx.odor_ds, xx.rep)]
hl = antal.locate(SM, hk, xx.s2_peak.to_numpy(), m, smooth=False)
held_rows.append({"subject": s, "glomerulus": g, "held_out": held, "n_place": len(hk), **hl})
meas = antal.ALARM if held == "general only" else [held]
for (o, r), row in t2[t2.index.get_level_values(0).isin(meas)].iterrows():
hjobs.append(((s, g, held, o, r), row.blobs, hl["z_index"], hl["y"], hl["x"], 8, (5, 12), SH[row.key], FLOOR[s]))
locs = pd.DataFrame(loc_rows)
held_locs = pd.DataFrame(held_rows)
print(f"{len(locs)} placements with their chance level and {len(held_locs)} held-out placements in {(time.time() - t0) / 60:.1f} min")
t0 = time.time()
traces = antal.all_roi_traces(jobs)
htraces = antal.all_roi_traces(hjobs)
print(f"{len(traces)} glomerulus traces at the placements and {len(htraces)} at the held-out placements in {(time.time() - t0) / 60:.1f} min")
print("\nPositions found (plane index 0-32, y, x in pixels), the correlation of the per-trial peaks, and its chance level")
print("(95th percentile of the best r with Data S2's values shuffled across trials, or by odor, and the permutation p):")
print(locs.round(3).to_string(index=False))
locs["above_chance"] = locs.p_trial < 0.05
ABOVE = set(zip(locs[locs.above_chance].subject, locs[locs.above_chance].glomerulus))
ABOVE_ODOR = set(zip(locs[locs.p_odor < 0.05].subject, locs[locs.p_odor < 0.05].glomerulus))
print("\nPlacements above chance (p < 0.05), trial shuffle:", locs.groupby("cohort").above_chance.agg(["sum", "count"]).to_dict("index"))
print("Placements above chance (p < 0.05), odor shuffle:", locs.assign(a=locs.p_odor < 0.05).groupby("cohort").a.agg(["sum", "count"]).to_dict("index"))
print("Held-out placements at the same voxel as the placement from all trials:",
int(held_locs.merge(locs, on=["subject", "glomerulus"], suffixes=("", "_all")).pipe(
lambda d: ((d.z_index == d.z_index_all) & (d.y == d.y_all) & (d.x == d.x_all)).sum())), "of", len(held_locs))
motion over 40 frames measured for 308 trials in 1.5 min
81 placements with their chance level and 639 held-out placements in 3.1 min
1782 glomerulus traces at the placements and 2154 at the held-out placements in 7.5 min
Positions found (plane index 0-32, y, x in pixels), the correlation of the per-trial peaks, and its chance level
(95th percentile of the best r with Data S2's values shuffled across trials, or by odor, and the permutation p):
subject cohort glomerulus n_trials z_index y x r slope null95_trial p_trial null95_odor p_odor
ant1-m1-d20-y2023 young G1 21 10 122 202 0.946 1.246 0.774 0.005 0.851 0.005
ant1-m1-d20-y2023 young G2 21 13 358 222 0.958 0.8 0.837 0.005 0.927 0.015
ant1-m1-d20-y2023 young G3 21 15 166 306 0.99 0.529 0.93 0.005 0.978 0.005
ant1-m1-d20-y2023 young G4 21 7 302 350 0.957 0.69 0.859 0.005 0.925 0.035
ant1-m1-d20-y2023 young PGa 21 19 262 378 0.974 1.228 0.834 0.005 0.957 0.025
ant1-m1-d20-y2023 young PGb 21 15 306 334 0.983 1.227 0.838 0.005 0.925 0.005
ant1-m2-d24-y2023 young G1 21 10 106 238 0.951 1.113 0.823 0.005 0.887 0.005
ant1-m2-d24-y2023 young G2 21 12 306 306 0.973 0.914 0.851 0.005 0.88 0.005
ant1-m2-d24-y2023 young G3 21 25 290 222 0.971 1.163 0.841 0.005 0.918 0.005
ant1-m2-d24-y2023 young G4 20 17 362 266 0.98 1.133 0.858 0.005 0.942 0.005
ant1-m2-d24-y2023 young PGa 21 16 158 314 0.984 1.029 0.912 0.005 0.977 0.01
ant1-m2-d24-y2023 young PGb 21 25 302 226 0.984 1.203 0.882 0.005 0.948 0.005
ant1-m4-d6-y2023 old G1 21 12 158 214 0.903 1.225 0.881 0.015 0.896 0.05
ant1-m4-d6-y2023 old G2 21 12 406 234 0.931 1.308 0.871 0.01 0.888 0.03
ant1-m4-d6-y2023 old G3 21 12 450 194 0.975 1.119 0.893 0.005 0.928 0.015
ant1-m4-d6-y2023 old G4 21 13 298 346 0.965 0.639 0.852 0.005 0.919 0.005
ant1-m4-d6-y2023 old PGa 21 13 302 338 0.937 0.614 0.854 0.005 0.91 0.01
ant1-m4-d6-y2023 old PGb 21 15 318 326 0.935 0.989 0.85 0.005 0.886 0.005
ant2-m1-d20-y2023 young G1 21 5 194 218 0.966 1.061 0.813 0.005 0.911 0.005
ant2-m1-d20-y2023 young G2 21 18 214 274 0.988 1.022 0.848 0.005 0.942 0.005
ant2-m1-d20-y2023 young G3 21 16 218 274 0.989 0.544 0.835 0.005 0.966 0.005
ant2-m1-d20-y2023 young G4 21 21 226 274 0.968 0.946 0.826 0.005 0.955 0.045
ant2-m1-d20-y2023 young PGa 21 22 190 290 0.993 1.515 0.809 0.005 0.942 0.005
ant2-m1-d20-y2023 young PGb 21 24 226 298 0.992 1.383 0.833 0.005 0.951 0.005
ant2-m2-d24-y2023 young G1 20 9 138 222 0.947 1.597 0.858 0.005 0.883 0.005
ant2-m2-d24-y2023 young G2 20 19 242 298 0.97 1.101 0.829 0.005 0.873 0.005
ant2-m2-d24-y2023 young G3 20 9 354 182 0.983 0.889 0.855 0.005 0.914 0.03
ant2-m2-d24-y2023 young G4 20 14 358 238 0.975 1.053 0.832 0.005 0.864 0.005
ant2-m2-d24-y2023 young PGa 21 11 186 362 0.978 0.355 0.87 0.005 0.922 0.025
ant2-m2-d24-y2023 young PGb 21 16 266 338 0.983 1.306 0.85 0.005 0.842 0.005
ant3-m1-d10-y2023 old G1 21 6 158 186 0.776 2.131 0.82 0.234 0.815 0.164
ant3-m1-d10-y2023 old G2 21 23 114 302 0.888 1.265 0.843 0.015 0.854 0.03
ant3-m1-d10-y2023 old PGa 21 19 226 390 0.846 1.197 0.848 0.06 0.886 0.214
ant3-m1-d10-y2023 old PGb 21 16 258 358 0.924 1.04 0.865 0.01 0.89 0.005
ant3-m2-d24-y2023 young G1 21 11 118 298 0.975 1.063 0.802 0.005 0.886 0.01
ant3-m2-d24-y2023 young G2 21 27 114 254 0.992 0.53 0.774 0.005 0.956 0.025
ant3-m2-d24-y2023 young G3 21 32 330 158 0.933 0.395 0.746 0.01 0.87 0.015
ant3-m2-d24-y2023 young G4 21 19 170 334 0.995 1.018 0.814 0.005 0.972 0.01
ant3-m2-d24-y2023 young PGa 21 15 334 154 0.993 0.753 0.758 0.005 0.913 0.015
ant3-m2-d24-y2023 young PGb 21 17 294 302 0.96 0.978 0.77 0.005 0.905 0.005
ant4-m1-d10-y2023 old G1 21 11 118 222 0.862 1.053 0.865 0.06 0.876 0.129
ant4-m1-d10-y2023 old G2 21 23 94 366 0.817 2.093 0.841 0.194 0.863 0.154
ant4-m1-d10-y2023 old G3 6 8 250 230 0.998 0.433 0.999 0.373 0.998 0.488
ant4-m1-d10-y2023 old G4 15 12 134 366 0.888 0.531 0.917 0.204 0.942 0.433
ant4-m1-d10-y2023 old PGa 21 15 258 390 0.9 1.021 0.85 0.005 0.914 0.114
ant4-m1-d10-y2023 old PGb 21 16 282 358 0.869 0.757 0.848 0.02 0.873 0.08
ant4-m1-d18-y2023 young G1 21 12 122 226 0.99 1.028 0.769 0.005 0.872 0.005
ant4-m1-d18-y2023 young G2 21 12 134 218 0.958 0.569 0.828 0.005 0.946 0.02
ant4-m1-d18-y2023 young G3 21 16 254 330 0.95 0.571 0.804 0.005 0.917 0.03
ant4-m1-d18-y2023 young G4 21 17 414 334 0.967 0.637 0.879 0.005 0.953 0.025
ant4-m1-d18-y2023 young PGa 21 16 362 318 0.992 0.927 0.837 0.005 0.965 0.005
ant4-m1-d18-y2023 young PGb 21 18 254 366 0.987 1.107 0.814 0.005 0.903 0.005
ant4-m1-d20-y2023 young G1 21 7 186 206 0.959 1.49 0.846 0.005 0.898 0.005
ant4-m1-d20-y2023 young G2 21 17 350 302 0.962 1.172 0.783 0.005 0.837 0.005
ant4-m1-d20-y2023 young G3 21 22 314 254 0.989 0.854 0.807 0.005 0.868 0.005
ant4-m1-d20-y2023 young G4 21 32 246 166 0.983 0.736 0.808 0.005 0.866 0.005
ant4-m1-d20-y2023 young PGa 21 18 306 374 0.975 0.885 0.836 0.005 0.828 0.005
ant4-m1-d20-y2023 young PGb 21 23 314 318 0.983 1.27 0.832 0.005 0.828 0.005
ant4-m4-d6-y2023 old G1 21 25 50 262 0.825 0.534 0.803 0.035 0.839 0.085
ant4-m4-d6-y2023 old G2 20 12 310 234 0.874 1.372 0.813 0.005 0.885 0.08
ant4-m4-d6-y2023 old G3 20 28 362 246 0.833 0.804 0.85 0.095 0.854 0.095
ant4-m4-d6-y2023 old G4 20 16 374 214 0.94 1.249 0.84 0.005 0.884 0.005
ant4-m4-d6-y2023 old PGa 21 21 286 338 0.93 1.385 0.84 0.005 0.891 0.005
ant4-m4-d6-y2023 old PGb 21 20 274 294 0.924 1.001 0.818 0.005 0.848 0.005
ant5-m1-d10-y2023 old G1 21 6 102 210 0.862 2.909 0.835 0.015 0.857 0.035
ant5-m1-d10-y2023 old G2 21 20 278 366 0.877 2.339 0.872 0.05 0.885 0.085
ant5-m1-d10-y2023 old G3 21 23 154 242 0.786 0.617 0.845 0.259 0.84 0.244
ant5-m1-d10-y2023 old G4 21 15 354 302 0.906 0.647 0.859 0.01 0.892 0.03
ant5-m1-d10-y2023 old PGb 21 5 82 318 0.775 0.199 0.81 0.174 0.84 0.303
ant5-m4-d6-y2023 old G1 21 9 126 226 0.933 1.556 0.861 0.005 0.894 0.015
ant5-m4-d6-y2023 old G2 21 31 194 330 0.87 0.856 0.852 0.025 0.876 0.085
ant5-m4-d6-y2023 old G3 21 4 106 250 0.806 0.451 0.907 0.617 0.933 0.493
ant5-m4-d6-y2023 old G4 21 14 446 278 0.965 1.744 0.848 0.005 0.859 0.005
ant5-m4-d6-y2023 old PGa 21 17 298 370 0.924 1.259 0.85 0.005 0.891 0.01
ant5-m4-d6-y2023 old PGb 21 16 302 314 0.972 1.199 0.855 0.005 0.872 0.01
ant6-m1-d10-y2023 old G1 21 21 162 214 0.754 1.654 0.851 0.657 0.83 0.507
ant6-m1-d10-y2023 old G2 21 19 238 362 0.829 1.594 0.875 0.294 0.889 0.353
ant6-m1-d10-y2023 old G3 21 7 170 250 0.718 1.35 0.851 0.881 0.848 0.861
ant6-m1-d10-y2023 old G4 21 20 118 294 0.868 0.745 0.875 0.06 0.929 0.284
ant6-m1-d10-y2023 old PGa 21 17 214 402 0.904 1.652 0.858 0.015 0.883 0.005
ant6-m1-d10-y2023 old PGb 21 19 214 390 0.836 0.894 0.848 0.09 0.846 0.09
Placements above chance (p < 0.05), trial shuffle: {'old': {'sum': 24, 'count': 39}, 'young': {'sum': 42, 'count': 42}}
Placements above chance (p < 0.05), odor shuffle: {'old': {'sum': 18, 'count': 39}, 'young': {'sum': 42, 'count': 42}}
Held-out placements at the same voxel as the placement from all trials: 175 of 639
def trace_rows(tr, placement):
rows = []
for k, v in tr.items():
s, g, o, r = (k[0], k[1], k[-2], k[-1])
if len(k) == 5 and k[2] != "general only" and k[2] != o:
continue
pl = placement if len(k) == 4 else ("general only" if k[2] == "general only" else "leave one odor out")
for rad, (A, B) in v.items():
rows.append({"placement": pl, "subject": s, "glomerulus": g, "odor": o, "rep": r, "radius": rad,
"auc_a": A[antal.AUC].sum(), "auc_b": B[antal.AUC].sum(),
"pk_a": A[8:14].sum(), "late_a": A[34:40].sum(), "pk_b": B[8:14].sum(), "late_b": B[34:40].sum()})
return rows
auc = pd.DataFrame(trace_rows(traces, "in sample") + trace_rows(htraces, None))
auc["cohort"] = auc.subject.map(meta.cohort)
print("Trials with no pixel above the F0 floor under reading (b):", int(auc.auc_b.isna().sum()), "of", len(auc))
s2x = s2[(s2.conc == 0.48) & s2.subject.isin(meta.index)]
s2auc = s2x[s2x.frame >= 6].groupby(["subject", "glomerulus", "set", "odor_ds", "rep"]).dff.sum().rename("auc_s2").reset_index()
s2auc = s2auc.rename(columns={"odor_ds": "odor"})
cmp_ = auc[(auc.radius == 8) & (auc.placement == "in sample")].merge(s2auc, on=["subject", "glomerulus", "odor", "rep"])
s2auc = s2auc.drop(columns="set")
trace_r = []
for (s, g, o, r), v in traces.items():
x = s2x[(s2x.subject == s) & (s2x.glomerulus == g) & (s2x.odor_ds == o) & (s2x.rep == r)].sort_values("frame")
if len(x) == 40:
trace_r.append({"subject": s, "glomerulus": g, "set": x.set.iloc[0], "r_trace": np.corrcoef(x.dff, v[8][0])[0, 1]})
trace_r = pd.DataFrame(trace_r)
link = cmp_.groupby(["subject", "cohort", "glomerulus", "set"]).apply(
lambda d: pd.Series({"n": len(d), "r_auc_a": np.corrcoef(d.auc_s2, d.auc_a)[0, 1],
"r_auc_b": np.corrcoef(d.auc_s2, d.auc_b)[0, 1],
"median_abs_diff_a": (d.auc_a - d.auc_s2).abs().median()}), include_groups=False).reset_index()
link = link.merge(trace_r.groupby(["subject", "glomerulus", "set"]).r_trace.median().reset_index(), on=["subject", "glomerulus", "set"])
link = link.merge(locs[["subject", "glomerulus", "r", "p_trial"]].rename(columns={"r": "r_locate", "p_trial": "p_locate"}), on=["subject", "glomerulus"])
link["agrees"] = link.r_auc_a >= 0.7
print(link.round(3).to_string(index=False))
frac = link.agrees.mean()
print(f"\nAnt, glomerulus and Data S2 set triples that agree (r >= 0.7): {link.agrees.sum()} of {len(link)} ({frac:.0%})")
print("By glomerulus:", link.groupby("glomerulus").agrees.agg(["sum", "count"]).to_dict("index"))
print("By cohort:", link.groupby("cohort").agrees.agg(["sum", "count"]).to_dict("index"))
print("By Data S2 set:", link.groupby("set").agrees.agg(["sum", "count"]).to_dict("index"))
allr = np.corrcoef(cmp_.auc_s2, cmp_.auc_a)[0, 1]
print(f"Over all {len(cmp_)} trials: r = {allr:.3f}; median dandiset minus Data S2 AUC "
f"{np.median(cmp_.auc_a - cmp_.auc_s2):.2f} (Data S2 AUC median {cmp_.auc_s2.median():.2f}, IQR "
f"{cmp_.auc_s2.quantile(.25):.2f} to {cmp_.auc_s2.quantile(.75):.2f})")
print("Link:", "holds" if frac >= 0.8 else "holds in part" if frac >= 0.5 else "fails")
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
ax[0].scatter(cmp_.auc_s2, cmp_.auc_a, s=5); ax[0].set_xlabel("Data S2 AUC"); ax[0].set_ylabel("dandiset AUC (a, r=8)")
lim = [min(cmp_.auc_s2.min(), cmp_.auc_a.min()), max(cmp_.auc_s2.max(), cmp_.auc_a.max())]
ax[0].plot(lim, lim, "k:", lw=0.8)
ax[1].hist(link.r_auc_a, bins=20); ax[1].axvline(0.7, color="k", ls=":"); ax[1].set_xlabel("r per ant and glomerulus")
plt.tight_layout(); plt.show()
print("Axes cover every point; ranges", [round(v, 1) for v in lim])
# held-out check: the same criterion on AUCs read where the glomerulus was placed without that odor
cmp_ho = auc[(auc.radius == 8) & (auc.placement == "leave one odor out")].merge(
cmp_[["subject", "glomerulus", "odor", "rep", "set", "auc_s2"]], on=["subject", "glomerulus", "odor", "rep"])
link_ho = cmp_ho.groupby(["subject", "cohort", "glomerulus", "set"]).apply(
lambda d: pd.Series({"n": len(d), "r_auc_a": np.corrcoef(d.auc_s2, d.auc_a)[0, 1]}), include_groups=False).reset_index()
link_ho["agrees"] = link_ho.r_auc_a >= 0.7
print(f"\nHeld out (leave one odor out): {link_ho.agrees.sum()} of {len(link_ho)} triples agree ({link_ho.agrees.mean():.0%}); "
f"by cohort {link_ho.groupby('cohort').agrees.agg(['sum', 'count']).to_dict('index')}; "
f"by set {link_ho.groupby('set').agrees.agg(['sum', 'count']).to_dict('index')}; "
f"over all {len(cmp_ho)} trials r = {np.corrcoef(cmp_ho.auc_s2, cmp_ho.auc_a)[0, 1]:.3f}")
cmp_gen = auc[(auc.radius == 8) & (auc.placement == "general only")].merge(
cmp_[["subject", "glomerulus", "odor", "rep", "set", "auc_s2"]], on=["subject", "glomerulus", "odor", "rep"])
link_gen = cmp_gen.groupby(["subject", "cohort", "glomerulus", "set"]).apply(
lambda d: pd.Series({"n": len(d), "r_auc_a": np.corrcoef(d.auc_s2, d.auc_a)[0, 1]}), include_groups=False).reset_index()
link_gen["agrees"] = link_gen.r_auc_a >= 0.7
print(f"Alarm trials, placed from the general odorants only: {link_gen.agrees.sum()} of {len(link_gen)} agree; "
f"by cohort {link_gen.groupby('cohort').agrees.agg(['sum', 'count']).to_dict('index')}")
for name, lk in (("in sample (criterion)", link), ("leave one odor out", link_ho)):
lk = lk.assign(above=[(s, g) in ABOVE for s, g in zip(lk.subject, lk.glomerulus)])
print(f"{name}: triples that agree among placements above chance {lk[lk.above].agrees.sum()} of {lk.above.sum()}, "
f"below chance {lk[~lk.above].agrees.sum()} of {(~lk.above).sum()}")
Trials with no pixel above the F0 floor under reading (b): 3 of 11808
subject cohort glomerulus set n r_auc_a r_auc_b median_abs_diff_a r_trace r_locate p_locate agrees
ant1-m1-d20-y2023 young G1 main 21 0.812 0.834 2.076 0.626 0.946 0.005 True
ant1-m1-d20-y2023 young G2 main 21 0.717 0.753 10.14 0.6 0.958 0.005 True
ant1-m1-d20-y2023 young G3 main 21 0.855 0.918 10.16 -0.096 0.99 0.005 True
ant1-m1-d20-y2023 young G4 main 21 -0.138 -0.123 13.55 0.014 0.957 0.005 False
ant1-m1-d20-y2023 young PGa fig3cd 6 0.884 0.81 3.958 0.719 0.974 0.005 True
ant1-m1-d20-y2023 young PGa main 15 0.95 0.947 2.856 0.688 0.974 0.005 True
ant1-m1-d20-y2023 young PGb fig3cd 6 0.8 0.837 4.036 0.915 0.983 0.005 True
ant1-m1-d20-y2023 young PGb main 15 0.925 0.938 4.242 0.828 0.983 0.005 True
ant1-m2-d24-y2023 young G1 main 21 0.924 0.905 2.808 0.766 0.951 0.005 True
ant1-m2-d24-y2023 young G2 main 21 0.805 0.836 5.489 0.484 0.973 0.005 True
ant1-m2-d24-y2023 young G3 main 21 0.926 0.925 3.841 0.474 0.971 0.005 True
ant1-m2-d24-y2023 young G4 main 20 0.967 0.928 2.498 0.726 0.98 0.005 True
ant1-m2-d24-y2023 young PGa fig3cd 6 0.649 0.612 1.663 0.105 0.984 0.005 False
ant1-m2-d24-y2023 young PGa main 15 0.807 0.845 3.418 0.027 0.984 0.005 True
ant1-m2-d24-y2023 young PGb fig3cd 6 0.341 0.257 4.353 0.464 0.984 0.005 False
ant1-m2-d24-y2023 young PGb main 15 0.968 0.972 1.334 0.454 0.984 0.005 True
ant1-m4-d6-y2023 old G1 main 21 0.711 0.702 7.192 0.393 0.903 0.015 True
ant1-m4-d6-y2023 old G2 main 21 0.761 0.769 4.209 0.425 0.931 0.01 True
ant1-m4-d6-y2023 old G3 main 21 0.818 0.833 5.853 0.538 0.975 0.005 True
ant1-m4-d6-y2023 old G4 main 21 0.854 0.85 4.804 0.599 0.965 0.005 True
ant1-m4-d6-y2023 old PGa fig3cd 6 0.85 0.854 10.63 0.75 0.937 0.005 True
ant1-m4-d6-y2023 old PGa main 15 0.913 0.903 3.657 0.747 0.937 0.005 True
ant1-m4-d6-y2023 old PGb fig3cd 6 0.606 0.654 11.54 0.849 0.935 0.005 False
ant1-m4-d6-y2023 old PGb main 15 0.925 0.925 3.374 0.739 0.935 0.005 True
ant2-m1-d20-y2023 young G1 main 21 0.661 0.709 2.311 0.754 0.966 0.005 False
ant2-m1-d20-y2023 young G2 main 21 0.94 0.951 5.003 0.65 0.988 0.005 True
ant2-m1-d20-y2023 young G3 main 21 0.714 0.793 7.922 0.293 0.989 0.005 True
ant2-m1-d20-y2023 young G4 main 21 0.877 0.874 4.72 0.431 0.968 0.005 True
ant2-m1-d20-y2023 young PGa fig3cd 6 0.325 0.357 3.016 0.281 0.993 0.005 False
ant2-m1-d20-y2023 young PGa main 15 0.945 0.951 1.627 0.682 0.993 0.005 True
ant2-m1-d20-y2023 young PGb fig3cd 6 0.691 0.675 3.303 0.76 0.992 0.005 False
ant2-m1-d20-y2023 young PGb main 15 0.985 0.985 2.733 0.819 0.992 0.005 True
ant2-m2-d24-y2023 young G1 main 20 0.911 0.93 2.796 0.498 0.947 0.005 True
ant2-m2-d24-y2023 young G2 main 20 0.865 0.917 6.41 0.378 0.97 0.005 True
ant2-m2-d24-y2023 young G3 main 20 0.943 0.953 7.782 0.206 0.983 0.005 True
ant2-m2-d24-y2023 young G4 main 20 0.867 0.91 7.919 0.863 0.975 0.005 True
ant2-m2-d24-y2023 young PGa fig3cd 6 -0.241 -0.277 10.59 -0.18 0.978 0.005 False
ant2-m2-d24-y2023 young PGa main 15 0.762 0.803 6.476 0.236 0.978 0.005 True
ant2-m2-d24-y2023 young PGb fig3cd 6 0.821 0.859 6.662 0.484 0.983 0.005 True
ant2-m2-d24-y2023 young PGb main 15 0.856 0.867 6.87 0.757 0.983 0.005 True
ant3-m1-d10-y2023 old G1 main 21 -0.118 -0.031 3.003 0.005 0.776 0.234 False
ant3-m1-d10-y2023 old G2 main 21 0.42 0.436 4.003 0.093 0.888 0.015 False
ant3-m1-d10-y2023 old PGa fig3cd 6 0.878 0.82 3.98 0.449 0.846 0.06 True
ant3-m1-d10-y2023 old PGa main 15 0.796 0.772 2.418 0.626 0.846 0.06 True
ant3-m1-d10-y2023 old PGb fig3cd 6 0.233 0.34 9.083 0.209 0.924 0.01 False
ant3-m1-d10-y2023 old PGb main 15 0.604 0.577 5.229 0.329 0.924 0.01 False
ant3-m2-d24-y2023 young G1 main 21 0.73 0.788 5.484 0.636 0.975 0.005 True
ant3-m2-d24-y2023 young G2 main 21 0.962 0.966 7.242 0.366 0.992 0.005 True
ant3-m2-d24-y2023 young G3 main 21 0.854 0.835 7.972 0.238 0.933 0.01 True
ant3-m2-d24-y2023 young G4 main 21 0.954 0.967 4.313 0.573 0.995 0.005 True
ant3-m2-d24-y2023 young PGa fig3cd 6 0.693 0.827 6.979 0.195 0.993 0.005 False
ant3-m2-d24-y2023 young PGa main 15 0.957 0.964 10.71 0.498 0.993 0.005 True
ant3-m2-d24-y2023 young PGb fig3cd 6 0.886 0.934 5.229 0.939 0.96 0.005 True
ant3-m2-d24-y2023 young PGb main 15 0.924 0.926 2.828 0.962 0.96 0.005 True
ant4-m1-d10-y2023 old G1 main 21 0.744 0.59 5.63 0.279 0.862 0.06 True
ant4-m1-d10-y2023 old G2 main 21 -0.032 -0.297 3.355 0.067 0.817 0.194 False
ant4-m1-d10-y2023 old G3 main 6 -0.686 -0.417 8.493 -0.023 0.998 0.373 False
ant4-m1-d10-y2023 old G4 main 15 0.621 0.669 2.702 0.119 0.888 0.204 False
ant4-m1-d10-y2023 old PGa fig3cd 6 0.921 0.935 2.988 0.67 0.9 0.005 True
ant4-m1-d10-y2023 old PGa main 15 0.64 0.659 3.725 0.446 0.9 0.005 False
ant4-m1-d10-y2023 old PGb fig3cd 6 0.637 0.799 17.43 0.833 0.869 0.02 False
ant4-m1-d10-y2023 old PGb main 15 0.861 0.835 6.846 0.529 0.869 0.02 True
ant4-m1-d18-y2023 young G1 main 21 0.956 0.965 1.649 0.977 0.99 0.005 True
ant4-m1-d18-y2023 young G2 main 21 0.877 0.894 9.653 0.469 0.958 0.005 True
ant4-m1-d18-y2023 young G3 main 21 0.51 0.688 10.37 0.303 0.95 0.005 False
ant4-m1-d18-y2023 young G4 main 21 0.954 0.946 7.848 0.877 0.967 0.005 True
ant4-m1-d18-y2023 young PGa fig3cd 6 0.608 0.556 3.205 0.632 0.992 0.005 False
ant4-m1-d18-y2023 young PGa main 15 0.93 0.938 3.563 0.595 0.992 0.005 True
ant4-m1-d18-y2023 young PGb fig3cd 6 0.792 0.881 6.449 0.834 0.987 0.005 True
ant4-m1-d18-y2023 young PGb main 15 0.912 0.907 6.574 0.841 0.987 0.005 True
ant4-m1-d20-y2023 young G1 main 21 0.945 0.939 3.586 0.631 0.959 0.005 True
ant4-m1-d20-y2023 young G2 main 21 0.968 0.973 3.407 0.882 0.962 0.005 True
ant4-m1-d20-y2023 young G3 main 21 0.982 0.985 4.853 0.598 0.989 0.005 True
ant4-m1-d20-y2023 young G4 main 21 0.969 0.878 4.386 0.609 0.983 0.005 True
ant4-m1-d20-y2023 young PGa fig3cd 6 0.842 0.843 2.883 0.566 0.975 0.005 True
ant4-m1-d20-y2023 young PGa main 15 0.965 0.969 4.757 0.758 0.975 0.005 True
ant4-m1-d20-y2023 young PGb fig3cd 6 0.851 0.885 4.169 0.698 0.983 0.005 True
ant4-m1-d20-y2023 young PGb main 15 0.989 0.989 3.443 0.887 0.983 0.005 True
ant4-m4-d6-y2023 old G1 main 21 0.626 0.243 2.359 0.509 0.825 0.035 False
ant4-m4-d6-y2023 old G2 main 20 0.322 0.362 4.195 0.756 0.874 0.005 False
ant4-m4-d6-y2023 old G3 main 20 0.537 -0.056 3.346 0.229 0.833 0.095 False
ant4-m4-d6-y2023 old G4 main 20 0.853 0.829 2.337 0.651 0.94 0.005 True
ant4-m4-d6-y2023 old PGa fig3cd 6 0.827 0.769 0.982 0.639 0.93 0.005 True
ant4-m4-d6-y2023 old PGa main 15 0.824 0.827 1.662 0.73 0.93 0.005 True
ant4-m4-d6-y2023 old PGb fig3cd 6 0.39 0.515 13.07 0.732 0.924 0.005 False
ant4-m4-d6-y2023 old PGb main 15 0.95 0.962 4.197 0.703 0.924 0.005 True
ant5-m1-d10-y2023 old G1 main 21 0.053 -0.15 4.515 0.044 0.862 0.015 False
ant5-m1-d10-y2023 old G2 main 21 0.394 0.604 4.061 0.317 0.877 0.05 False
ant5-m1-d10-y2023 old G3 main 21 -0.071 -0.205 5.331 0.155 0.786 0.259 False
ant5-m1-d10-y2023 old G4 main 21 0.343 0.415 5.504 0.21 0.906 0.01 False
ant5-m1-d10-y2023 old PGb fig3cd 6 -0.522 0.179 42.41 -0.02 0.775 0.174 False
ant5-m1-d10-y2023 old PGb main 15 -0.08 -0.237 12.33 0.006 0.775 0.174 False
ant5-m4-d6-y2023 old G1 main 21 0.886 0.895 2.96 0.798 0.933 0.005 True
ant5-m4-d6-y2023 old G2 main 21 0.679 0.331 3.645 0.444 0.87 0.025 False
ant5-m4-d6-y2023 old G3 main 21 0.466 0.557 2.004 0.335 0.806 0.617 False
ant5-m4-d6-y2023 old G4 main 21 0.962 0.956 0.983 0.929 0.965 0.005 True
ant5-m4-d6-y2023 old PGa fig3cd 6 0.877 0.911 1.684 0.73 0.924 0.005 True
ant5-m4-d6-y2023 old PGa main 15 0.69 0.611 1.325 0.835 0.924 0.005 False
ant5-m4-d6-y2023 old PGb fig3cd 6 0.766 0.776 9.532 0.95 0.972 0.005 True
ant5-m4-d6-y2023 old PGb main 15 0.954 0.955 2.225 0.911 0.972 0.005 True
ant6-m1-d10-y2023 old G1 main 21 0.035 0.313 5.709 0.045 0.754 0.657 False
ant6-m1-d10-y2023 old G2 main 21 0.879 0.846 2.642 0.156 0.829 0.294 True
ant6-m1-d10-y2023 old G3 main 21 0.399 0.358 1.621 0.157 0.718 0.881 False
ant6-m1-d10-y2023 old G4 main 21 0.167 0.046 1.991 -0.005 0.868 0.06 False
ant6-m1-d10-y2023 old PGa fig3cd 6 0.913 0.934 4.388 0.572 0.904 0.015 True
ant6-m1-d10-y2023 old PGa main 15 0.904 0.89 2.778 0.123 0.904 0.015 True
ant6-m1-d10-y2023 old PGb fig3cd 6 -0.007 0.026 8.795 0.814 0.836 0.09 False
ant6-m1-d10-y2023 old PGb main 15 0.868 0.926 3.03 0.441 0.836 0.09 True
Ant, glomerulus and Data S2 set triples that agree (r >= 0.7): 71 of 108 (66%)
By glomerulus: {'G1': {'sum': 9, 'count': 14}, 'G2': {'sum': 9, 'count': 14}, 'G3': {'sum': 7, 'count': 13}, 'G4': {'sum': 9, 'count': 13}, 'PGa': {'sum': 19, 'count': 26}, 'PGb': {'sum': 18, 'count': 28}}
By cohort: {'old': {'sum': 25, 'count': 52}, 'young': {'sum': 46, 'count': 56}}
By Data S2 set: {'fig3cd': {'sum': 14, 'count': 27}, 'main': {'sum': 57, 'count': 81}}
Over all 1672 trials: r = 0.793; median dandiset minus Data S2 AUC -1.96 (Data S2 AUC median 0.45, IQR -3.57 to 9.45)
Link: holds in part
Axes cover every point; ranges [np.float64(-22.8), np.float64(166.3)]
Held out (leave one odor out): 44 of 108 triples agree (41%); by cohort {'old': {'sum': 9, 'count': 52}, 'young': {'sum': 35, 'count': 56}}; by set {'fig3cd': {'sum': 4, 'count': 27}, 'main': {'sum': 40, 'count': 81}}; over all 1672 trials r = 0.731
Alarm trials, placed from the general odorants only: 19 of 79 agree; by cohort {'old': {'sum': 4, 'count': 37}, 'young': {'sum': 15, 'count': 42}}
in sample (criterion): triples that agree among placements above chance 66 of 90, below chance 5 of 18
leave one odor out: triples that agree among placements above chance 42 of 90, below chance 2 of 18
Result: the link holds in part (partially reproduced). The 14 ants of Data S2 at 48% are the 14 dandiset ants at 48%, with matching ages. At the place found for each glomerulus, 71 of 108 ant, glomerulus and set triples (66%) have per-trial AUCs that correlate with Data S2's at r >= 0.7, below the 80% the criterion asks. Young ants agree far more often (46 of 56) than old ants (25 of 52), and the main set (57 of 81) more often than the Figure 3C and 3D set of PGb and PGa alarm traces (14 of 27). Over all 1672 trials r = 0.793.
The two checks show that this 66% is optimistic. Every young placement is above chance (42 of 42), but only 24 of 39 old placements are under the trial shuffle and 18 of 39 under the odor shuffle; the triples agree at 66 of 90 placements above chance and 5 of 18 below. Read where the glomerulus was placed without the odor's own trials, 44 of 108 triples agree (41%; young 35 of 56, old 9 of 52; Figure 3C and 3D set 4 of 27), over all trials r = 0.731; the alarm trials read where the glomerulus was placed from the general odorants alone agree in 19 of 79 (old 4 of 37). The criterion, written for the placement from all trials, gives a partial link; on held-out trials the link would fail, and in old ants it fails either way.
# stimulus timing from the glomerulus traces: mean fold change per frame, odor trials with a strong
# response (Data S2 AUC in the top quarter) against the paraffin oil trials
strong = set(map(tuple, cmp_[cmp_.auc_s2 >= cmp_.auc_s2.quantile(0.75)][["subject", "glomerulus", "odor", "rep"]].to_numpy()))
tc_odor = np.mean([v[8][0] for k, v in traces.items() if k in strong], 0)
tc_par = np.mean([v[8][0] for k, v in traces.items() if k[2] == "paraffin-oil"], 0)
tc = pd.DataFrame({"frame": np.arange(1, 41), "strong odor trials": tc_odor, "paraffin oil": tc_par})
print(f"{len(strong)} strong odor trials, {sum(k[2] == 'paraffin-oil' for k in traces)} paraffin oil traces")
print(tc.round(3).T.to_string(header=False))
first_up = int(np.argmax(tc_odor > tc_odor[:5].mean() + 3 * tc_odor[:5].std())) + 1
print("First frame above the frames 1-5 mean + 3 SD (strong odor trials):", first_up,
"; peak frame:", int(np.argmax(tc_odor)) + 1)
print("Mean of frames 9-14: strong odor trials", round(tc_odor[8:14].mean(), 3), "; paraffin oil", round(tc_par[8:14].mean(), 3))
per_trial = pd.DataFrame([{"kind": "paraffin oil" if k[2] == "paraffin-oil" else "odor, Data S2 AUC top quarter" if k in strong else "odor, other",
"peak_minus_base": v[8][0][8:14].mean() - v[8][0][:5].mean(),
"frames_6_8_minus_base": v[8][0][5:8].mean() - v[8][0][:5].mean()} for k, v in traces.items()])
print("\nPer trace, frames 9-14 (and 6-8) minus frames 1-5, by kind of trial:")
print(per_trial.groupby("kind").agg(["count", "median"]).round(3).to_string())
418 strong odor trials, 81 paraffin oil traces
frame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
strong odor trials -0.028 -0.035 -0.012 -0.025 0.1 0.307 0.464 0.634 0.745 0.82 0.894 0.932 0.908 0.914 0.87 0.825 0.821 0.759 0.736 0.683 0.645 0.585 0.561 0.518 0.481 0.436 0.419 0.391 0.35 0.323 0.287 0.272 0.236 0.206 0.184 0.161 0.167 0.134 0.12 0.111
paraffin oil 0.006 -0.013 0.021 -0.008 -0.006 -0.04 -0.037 -0.078 -0.098 -0.07 -0.088 -0.096 -0.076 -0.12 -0.106 -0.103 -0.136 -0.12 -0.11 -0.126 -0.118 -0.123 -0.125 -0.128 -0.135 -0.133 -0.139 -0.092 -0.104 -0.121 -0.106 -0.108 -0.107 -0.128 -0.116 -0.111 -0.112 -0.124 -0.127 -0.096
First frame above the frames 1-5 mean + 3 SD (strong odor trials): 6 ; peak frame: 12
Mean of frames 9-14: strong odor trials 0.869 ; paraffin oil -0.091
Per trace, frames 9-14 (and 6-8) minus frames 1-5, by kind of trial:
peak_minus_base frames_6_8_minus_base
count median count median
kind
odor, Data S2 AUC top quarter 418 0.55 418 0.299
odor, other 1283 -0.028 1283 -0.009
paraffin oil 81 -0.085 81 -0.047
The focal glomerulus claims¶
Per ant, glomerulus and odor, the mean AUC over the trials Data S2 holds for it (the paper's own selection of trials; all three trials as a variant), at 48%. Only ants that have the glomerulus in Data S2 enter (the Figure S2 and S4 n). The main measure is read at the held-out placement (leave one odor out: each odor's AUCs are read where the glomerulus was placed from the other odors' trials), so that the values tested are not those the place was chosen to match; this is an assumption (the paper drew the ROIs by hand). Reading (a), radius 8. The variants: reading (b), radii 5 and 12, all three trials, placements above chance only, the placement from all trials (in sample, the main measure of round 1), and for the alarm pheromones the placement from the general odorants only. As a reference only, the same test is run on Data S2's own traces.
MAIN = ("leave one odor out", "a", 8, "s2", False) # placement, reading, radius, trials, above chance only
VARIANTS = [MAIN, ("leave one odor out", "b", 8, "s2", False), ("leave one odor out", "a", 5, "s2", False),
("leave one odor out", "a", 12, "s2", False), ("leave one odor out", "a", 8, "all", False),
("leave one odor out", "a", 8, "s2", True), ("in sample", "a", 8, "s2", False),
("in sample", "b", 8, "s2", False), ("in sample", "a", 8, "s2", True)]
ALARM_VARIANTS = VARIANTS + [("general only", "a", 8, "s2", False), ("general only", "b", 8, "s2", False)]
def vname(v):
pl, rd, rad, tf, ab = v
return f"{pl}, {rd}, r={rad}" + (", all trials" if tf == "all" else "") + (", above chance" if ab else "")
def variants_for(odors):
return ALARM_VARIANTS if set(odors) <= set(antal.ALARM) else VARIANTS
def per_ant_auc(odors, v=MAIN, col="auc"):
pl, rd, rad, tf, ab = v
a = auc[(auc.placement == pl) & (auc.radius == rad) & auc.odor.isin(odors)]
if tf == "s2":
a = a.merge(s2auc[["subject", "glomerulus", "odor", "rep"]], on=["subject", "glomerulus", "odor", "rep"])
else:
a = a[a.set_index(["subject", "glomerulus"]).index.isin(s2auc.set_index(["subject", "glomerulus"]).index.unique())]
if ab:
a = a[[(s, g) in ABOVE for s, g in zip(a.subject, a.glomerulus)]]
return a.groupby(["subject", "cohort", "glomerulus", "odor"])[f"{col}_{rd}"].mean().rename("v").reset_index()
def s2_per_ant(odors, s2df=None):
d = s2auc if s2df is None else s2df
d = d[d.odor.isin(odors)].copy()
d["cohort"] = np.where(d.subject.map(meta.age_days) < 40, "young", "old")
return d.groupby(["subject", "cohort", "glomerulus", "odor"]).auc_s2.mean().rename("v").reset_index()
def welch_glom(pa_, pairs):
rows = []
for g, o in pairs:
d = pa_[(pa_.glomerulus == g) & (pa_.odor == o)]
y, ol = d[d.cohort == "young"].v, d[d.cohort == "old"].v
t, p = stats.ttest_ind(y, ol, equal_var=False) if len(y) > 1 and len(ol) > 1 else (np.nan, np.nan)
rows.append({"glomerulus": g, "odor": o, "n_young": len(y), "n_old": len(ol), "mean_young": y.mean(),
"mean_old": ol.mean(), "t": t, "p": p})
return pd.DataFrame(rows)
def run_all(pairs, odors):
out = {vname(v): welch_glom(per_ant_auc(odors, v), pairs) for v in variants_for(odors)}
out["Data S2 (reference)"] = welch_glom(s2_per_ant(odors), pairs)
return out
def show_all(out):
t = pd.concat([v.assign(variant=k) for k, v in out.items()])
t = t.sort_values(["glomerulus", "odor"], kind="stable")
with pd.option_context("display.float_format", "{:.4g}".format):
print(t[["glomerulus", "odor", "variant", "n_young", "n_old", "mean_young", "mean_old", "t", "p"]].to_string(index=False))
GLOMS = ["PGb", "PGa", "G1", "G2", "G3", "G4"]
MAIN_NAME = vname(MAIN)
figs2a-mean-six-glomeruli-no-age-effect¶
The alarm pheromone response averaged over the six focal glomeruli does not differ between young and old ants.
def mean_over_gloms(pa_):
return pa_.groupby(["subject", "cohort", "odor"]).v.mean().reset_index().assign(glomerulus="mean of six")
res_s2a = {vname(v): welch_glom(mean_over_gloms(per_ant_auc(antal.ALARM, v)), [("mean of six", o) for o in antal.ALARM])
for v in ALARM_VARIANTS}
res_s2a["Data S2 (reference)"] = welch_glom(mean_over_gloms(s2_per_ant(antal.ALARM)), [("mean of six", o) for o in antal.ALARM])
show_all(res_s2a)
glomerulus odor variant n_young n_old mean_young mean_old t p mean of six 4-methyl-3-heptanol leave one odor out, a, r=8 7 7 -2.506 -0.8205 -1.257 0.2329 mean of six 4-methyl-3-heptanol leave one odor out, b, r=8 7 7 -1.545 -1.708 0.0998 0.9222 mean of six 4-methyl-3-heptanol leave one odor out, a, r=5 7 7 -2.383 -0.6453 -1.166 0.2665 mean of six 4-methyl-3-heptanol leave one odor out, a, r=12 7 7 -2.485 -0.7052 -1.375 0.1951 mean of six 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 7 -2.506 -0.8205 -1.257 0.2329 mean of six 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 7 -2.506 0.8568 -1.274 0.2404 mean of six 4-methyl-3-heptanol in sample, a, r=8 7 7 -0.591 1.653 -1.203 0.253 mean of six 4-methyl-3-heptanol in sample, b, r=8 7 7 0.389 2.125 -0.7409 0.473 mean of six 4-methyl-3-heptanol in sample, a, r=8, above chance 7 7 -0.591 5.407 -1.615 0.1483 mean of six 4-methyl-3-heptanol general only, a, r=8 7 7 -2.715 -1.136 -1.724 0.1104 mean of six 4-methyl-3-heptanol general only, b, r=8 7 7 -1.538 -3.501 0.905 0.3896 mean of six 4-methyl-3-heptanol Data S2 (reference) 7 7 3.794 8.451 -2.153 0.05267 mean of six 4-methyl-3-heptanone leave one odor out, a, r=8 7 7 -0.3803 0.2511 -0.5663 0.5817 mean of six 4-methyl-3-heptanone leave one odor out, b, r=8 7 7 0.2083 -0.5379 0.4463 0.6643 mean of six 4-methyl-3-heptanone leave one odor out, a, r=5 7 7 -0.5307 0.7503 -1.133 0.2795 mean of six 4-methyl-3-heptanone leave one odor out, a, r=12 7 7 -0.7528 -0.1771 -0.5587 0.5867 mean of six 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 7 -0.4009 0.2511 -0.5869 0.5682 mean of six 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 7 -0.3803 0.7855 -0.6312 0.544 mean of six 4-methyl-3-heptanone in sample, a, r=8 7 7 1.703 1.251 0.243 0.8122 mean of six 4-methyl-3-heptanone in sample, b, r=8 7 7 3.146 1.194 0.901 0.3868 mean of six 4-methyl-3-heptanone in sample, a, r=8, above chance 7 7 1.703 1.402 0.1494 0.884 mean of six 4-methyl-3-heptanone general only, a, r=8 7 7 -0.01668 -0.8231 0.508 0.6238 mean of six 4-methyl-3-heptanone general only, b, r=8 7 7 1.267 -3.453 1.913 0.07996 mean of six 4-methyl-3-heptanone Data S2 (reference) 7 7 8.083 5.06 1.602 0.142
Result: reproduced (resting on the located ROIs). The mean AUC over the six glomeruli does not differ with age: at the held-out placement (reading a, radius 8) p = 0.58 (4-methyl-3-heptanone) and 0.23 (4-methyl-3-heptanol); over every variant, including the placement from all trials of round 1 (p = 0.81 and 0.25) and placements above chance only, p is 0.0799 or more (general odorants only, reading b, 4-methyl-3-heptanone). Data S2's own values give p = 0.14 and 0.053. This null result is weak evidence: it comes from the same located ROIs that miss the paper's positive old-ant effects (fig3b, fig3d), and a null from a procedure that misses the paper's effects is a weak test, the reason figs3c-general-breadth-no-age-effect is blocked. It is kept as reproduced because the six glomeruli are placed from Data S2 per ant (above chance in every young ant), not found by the automated rule of figs3c, and no variant reaches p < 0.05.
fig3b-g2-heptanone-weaker-old¶
In glomeruli G1 to G4 alarm pheromone responses tend to weaken with age; the decline is significant only for 4-methyl-3-heptanone in G2.
res_3b = run_all([(g, o) for g in ["G1", "G2", "G3", "G4"] for o in antal.ALARM], antal.ALARM)
show_all(res_3b)
for k, v in res_3b.items():
g2 = v[(v.glomerulus == "G2") & (v.odor == "4-methyl-3-heptanone")].iloc[0]
others = v[~((v.glomerulus == "G2") & (v.odor == "4-methyl-3-heptanone"))]
print(f"{k:>24}: G2 heptanone p = {g2.p:.3g}, young higher {g2.mean_young > g2.mean_old}; "
f"other seven with p < 0.05: {int((others.p < 0.05).sum())}; young above old in {int((v.mean_young > v.mean_old).sum())} of 8")
glomerulus odor variant n_young n_old mean_young mean_old t p
G1 4-methyl-3-heptanol leave one odor out, a, r=8 7 7 -4.425 -2.436 -0.9577 0.3574
G1 4-methyl-3-heptanol leave one odor out, b, r=8 7 7 -2.936 -5.906 0.7188 0.4921
G1 4-methyl-3-heptanol leave one odor out, a, r=5 7 7 -4.563 -2.453 -0.9954 0.3394
G1 4-methyl-3-heptanol leave one odor out, a, r=12 7 7 -4.378 -2.182 -0.9779 0.3477
G1 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 7 -4.425 -2.436 -0.9577 0.3574
G1 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 4 -4.425 -4.02 -0.2163 0.8336
G1 4-methyl-3-heptanol in sample, a, r=8 7 7 -4.547 -2.653 -0.8399 0.4174
G1 4-methyl-3-heptanol in sample, b, r=8 7 7 -2.926 -6.256 0.88 0.4023
G1 4-methyl-3-heptanol in sample, a, r=8, above chance 7 4 -4.547 -3.246 -0.5546 0.5958
G1 4-methyl-3-heptanol general only, a, r=8 7 7 -2.421 -2.774 0.1639 0.8733
G1 4-methyl-3-heptanol general only, b, r=8 7 7 -0.08414 -8.448 2.014 0.06858
G1 4-methyl-3-heptanol Data S2 (reference) 7 7 -2.484 -2.034 -0.3229 0.7569
G1 4-methyl-3-heptanone leave one odor out, a, r=8 7 7 8.709 -1.967 2.613 0.03338
G1 4-methyl-3-heptanone leave one odor out, b, r=8 7 7 11.08 -4.658 2.643 0.02155
G1 4-methyl-3-heptanone leave one odor out, a, r=5 7 7 9.257 -1.825 2.606 0.03254
G1 4-methyl-3-heptanone leave one odor out, a, r=12 7 7 6.871 -1.709 2.611 0.03534
G1 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 7 7.3 -1.967 2.644 0.02999
G1 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 4 8.709 -0.8487 2.172 0.05951
G1 4-methyl-3-heptanone in sample, a, r=8 7 7 9.632 4.891 0.6035 0.56
G1 4-methyl-3-heptanone in sample, b, r=8 7 7 11.5 2.649 1.005 0.3363
G1 4-methyl-3-heptanone in sample, a, r=8, above chance 7 4 9.632 4.394 0.6295 0.5447
G1 4-methyl-3-heptanone general only, a, r=8 7 7 12.99 -1.44 1.961 0.09484
G1 4-methyl-3-heptanone general only, b, r=8 7 7 15.76 -7.065 2.638 0.0265
G1 4-methyl-3-heptanone Data S2 (reference) 7 7 11.31 4.107 1.139 0.2859
G2 4-methyl-3-heptanol leave one odor out, a, r=8 7 7 -1.509 -2.695 0.7617 0.4609
G2 4-methyl-3-heptanol leave one odor out, b, r=8 7 7 1.294 -2.963 1.515 0.1617
G2 4-methyl-3-heptanol leave one odor out, a, r=5 7 7 -0.7607 -2.635 1.054 0.3128
G2 4-methyl-3-heptanol leave one odor out, a, r=12 7 7 -1.951 -2.582 0.4926 0.6313
G2 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 7 -1.509 -2.695 0.7617 0.4609
G2 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 5 -1.509 -4.023 1.762 0.1085
G2 4-methyl-3-heptanol in sample, a, r=8 7 7 -1.532 -4.185 1.926 0.07889
G2 4-methyl-3-heptanol in sample, b, r=8 7 7 0.6476 -4.4 2.364 0.03578
G2 4-methyl-3-heptanol in sample, a, r=8, above chance 7 5 -1.532 -5.37 3.139 0.01168
G2 4-methyl-3-heptanol general only, a, r=8 7 7 -2.47 -2.654 0.114 0.9113
G2 4-methyl-3-heptanol general only, b, r=8 7 7 -1.886 -6.253 1.159 0.2784
G2 4-methyl-3-heptanol Data S2 (reference) 7 7 -3.362 -1.454 -1.237 0.2467
G2 4-methyl-3-heptanone leave one odor out, a, r=8 7 7 -1.98 -2.339 0.1789 0.8611
G2 4-methyl-3-heptanone leave one odor out, b, r=8 7 7 0.07537 -3.078 1.014 0.3336
G2 4-methyl-3-heptanone leave one odor out, a, r=5 7 7 -2.48 -1.95 -0.2462 0.8097
G2 4-methyl-3-heptanone leave one odor out, a, r=12 7 7 -1.404 -2.813 0.7037 0.4962
G2 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 7 -1.707 -2.339 0.304 0.7666
G2 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 5 -1.98 -2.919 0.4014 0.6975
G2 4-methyl-3-heptanone in sample, a, r=8 7 7 5.172 -0.2909 1.229 0.2435
G2 4-methyl-3-heptanone in sample, b, r=8 7 7 7.922 -1.562 1.796 0.09767
G2 4-methyl-3-heptanone in sample, a, r=8, above chance 7 5 5.172 -3.352 2.331 0.04957
G2 4-methyl-3-heptanone general only, a, r=8 7 7 -2.033 -1.531 -0.325 0.7513
G2 4-methyl-3-heptanone general only, b, r=8 7 7 -1.539 -4.126 0.7368 0.4776
G2 4-methyl-3-heptanone Data S2 (reference) 7 7 20.01 2.831 3.344 0.006862
G3 4-methyl-3-heptanol leave one odor out, a, r=8 7 6 -0.1407 -1.57 0.5463 0.5969
G3 4-methyl-3-heptanol leave one odor out, b, r=8 7 6 0.7612 -7.356 1.746 0.1227
G3 4-methyl-3-heptanol leave one odor out, a, r=5 7 6 -0.3949 -1.287 0.329 0.7489
G3 4-methyl-3-heptanol leave one odor out, a, r=12 7 6 0.1747 -1.814 0.7908 0.4468
G3 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 6 -0.1407 -1.57 0.5463 0.5969
G3 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 1 -0.1407 3.456 NaN NaN
G3 4-methyl-3-heptanol in sample, a, r=8 7 6 -2.18 -1.364 -0.3734 0.716
G3 4-methyl-3-heptanol in sample, b, r=8 7 6 -2.444 -1.014 -0.3297 0.7478
G3 4-methyl-3-heptanol in sample, a, r=8, above chance 7 1 -2.18 5.538 NaN NaN
G3 4-methyl-3-heptanol general only, a, r=8 7 5 -2.637 -2.374 -0.2216 0.8304
G3 4-methyl-3-heptanol general only, b, r=8 7 5 -0.7648 -7.372 1.59 0.1818
G3 4-methyl-3-heptanol Data S2 (reference) 7 6 5.761 6.736 -0.2533 0.8059
G3 4-methyl-3-heptanone leave one odor out, a, r=8 7 6 -1.797 -1.536 -0.09417 0.9273
G3 4-methyl-3-heptanone leave one odor out, b, r=8 7 6 -0.9529 -6.994 1.144 0.2842
G3 4-methyl-3-heptanone leave one odor out, a, r=5 7 6 -2.311 -2.077 -0.08472 0.9346
G3 4-methyl-3-heptanone leave one odor out, a, r=12 7 6 -1.644 -1.766 0.04508 0.9652
G3 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 6 -1.408 -1.536 0.04489 0.9653
G3 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 1 -1.797 -3.548 NaN NaN
G3 4-methyl-3-heptanone in sample, a, r=8 7 6 -0.2688 -0.3116 0.02812 0.9782
G3 4-methyl-3-heptanone in sample, b, r=8 7 6 -0.6826 -0.5266 -0.03529 0.9725
G3 4-methyl-3-heptanone in sample, a, r=8, above chance 7 1 -0.2688 2.313 NaN NaN
G3 4-methyl-3-heptanone general only, a, r=8 7 5 -2.951 -3.441 0.2682 0.7953
G3 4-methyl-3-heptanone general only, b, r=8 7 5 -1.393 -9.019 1.851 0.1139
G3 4-methyl-3-heptanone Data S2 (reference) 7 6 16.83 4.667 2.139 0.05863
G4 4-methyl-3-heptanol leave one odor out, a, r=8 7 5 -3.817 -0.1241 -1.652 0.132
G4 4-methyl-3-heptanol leave one odor out, b, r=8 7 5 -3.968 1.777 -2.218 0.05176
G4 4-methyl-3-heptanol leave one odor out, a, r=5 7 5 -3.981 -0.137 -1.795 0.1048
G4 4-methyl-3-heptanol leave one odor out, a, r=12 7 5 -3.638 -0.1168 -1.588 0.1485
G4 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 5 -3.817 -0.1241 -1.652 0.132
G4 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 4 -3.817 -0.3196 -1.348 0.2257
G4 4-methyl-3-heptanol in sample, a, r=8 7 5 6.684 1.603 0.9058 0.3971
G4 4-methyl-3-heptanol in sample, b, r=8 7 5 5.536 3.5 0.2989 0.7744
G4 4-methyl-3-heptanol in sample, a, r=8, above chance 7 4 6.684 2.173 0.7991 0.4516
G4 4-methyl-3-heptanol general only, a, r=8 7 5 -2.2 -1.606 -0.3246 0.753
G4 4-methyl-3-heptanol general only, b, r=8 7 5 -3.583 0.4686 -1.461 0.1793
G4 4-methyl-3-heptanol Data S2 (reference) 7 5 13.3 7.605 1.002 0.3429
G4 4-methyl-3-heptanone leave one odor out, a, r=8 7 5 -3.691 -4.295 0.3034 0.7678
G4 4-methyl-3-heptanone leave one odor out, b, r=8 7 5 -8.391 -2.734 -1.599 0.1481
G4 4-methyl-3-heptanone leave one odor out, a, r=5 7 5 -3.776 -4.217 0.2249 0.8266
G4 4-methyl-3-heptanone leave one odor out, a, r=12 7 5 -3.94 -4.033 0.046 0.9642
G4 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 5 -3.067 -4.295 0.5894 0.5688
G4 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 4 -3.691 -5.009 0.6487 0.5331
G4 4-methyl-3-heptanone in sample, a, r=8 7 5 -4.821 -3.815 -0.4953 0.6318
G4 4-methyl-3-heptanone in sample, b, r=8 7 5 -4.307 -1.99 -0.7067 0.4969
G4 4-methyl-3-heptanone in sample, a, r=8, above chance 7 4 -4.821 -4.409 -0.1844 0.8592
G4 4-methyl-3-heptanone general only, a, r=8 7 5 -3.716 -3.812 0.06034 0.9536
G4 4-methyl-3-heptanone general only, b, r=8 7 5 -4.755 -1.899 -1.08 0.3056
G4 4-methyl-3-heptanone Data S2 (reference) 7 5 -4.553 -5.487 0.5715 0.5845
leave one odor out, a, r=8: G2 heptanone p = 0.861, young higher True; other seven with p < 0.05: 1; young above old in 5 of 8
leave one odor out, b, r=8: G2 heptanone p = 0.334, young higher True; other seven with p < 0.05: 1; young above old in 6 of 8
leave one odor out, a, r=5: G2 heptanone p = 0.81, young higher False; other seven with p < 0.05: 1; young above old in 4 of 8
leave one odor out, a, r=12: G2 heptanone p = 0.496, young higher True; other seven with p < 0.05: 1; young above old in 6 of 8
leave one odor out, a, r=8, all trials: G2 heptanone p = 0.767, young higher True; other seven with p < 0.05: 1; young above old in 6 of 8
leave one odor out, a, r=8, above chance: G2 heptanone p = 0.698, young higher True; other seven with p < 0.05: 0; young above old in 5 of 8
in sample, a, r=8: G2 heptanone p = 0.243, young higher True; other seven with p < 0.05: 0; young above old in 5 of 8
in sample, b, r=8: G2 heptanone p = 0.0977, young higher True; other seven with p < 0.05: 1; young above old in 5 of 8
in sample, a, r=8, above chance: G2 heptanone p = 0.0496, young higher True; other seven with p < 0.05: 1; young above old in 4 of 8
general only, a, r=8: G2 heptanone p = 0.751, young higher False; other seven with p < 0.05: 0; young above old in 5 of 8
general only, b, r=8: G2 heptanone p = 0.478, young higher True; other seven with p < 0.05: 1; young above old in 6 of 8
Data S2 (reference): G2 heptanone p = 0.00686, young higher True; other seven with p < 0.05: 0; young above old in 5 of 8
Result: not reproduced. At the held-out placement (reading a, radius 8) the G2 response to 4-methyl-3-heptanone does not differ with age (young -1.98, old -2.34, p = 0.86), and G1 with 4-methyl-3-heptanone does (8.71 against -1.97, p = 0.033). Over the variants the G2 p ranges from 0.0496 to 0.86; only the placement from all trials restricted to placements above chance (5 old ants) gives G2 p < 0.05, with one other pair also significant, which would be a partial reproduction. Young means are above old ones in 5 of 8 pairs (held out). Data S2's own values give G2 p = 0.0069 and no other pair below 0.05, as the paper.
fig3d-pg-stronger-old¶
PGb and PGa respond significantly more strongly to alarm pheromones in old than in young ants; PGa does not respond to 4-methyl-3-heptanone.
res_3d = run_all([(g, o) for g in ["PGb", "PGa"] for o in antal.ALARM], antal.ALARM)
show_all(res_3d)
def sig_old(v, g, o):
r = v[(v.glomerulus == g) & (v.odor == o)].iloc[0]
return bool(r.p < 0.05 and r.mean_old > r.mean_young)
for k, v in res_3d.items():
ra = sig_old(v, "PGb", antal.ALARM[0]) and sig_old(v, "PGb", antal.ALARM[1]) and sig_old(v, "PGa", antal.ALARM[1])
rb = (sig_old(v, "PGb", antal.ALARM[0]) or sig_old(v, "PGb", antal.ALARM[1])) and sig_old(v, "PGa", antal.ALARM[1])
print(f"{k:>24}: reading (a) holds {ra}; reading (b) holds {rb}")
pa_d = per_ant_auc(antal.ALARM).rename(columns={"v": "dandiset, held out"})
pa_i = per_ant_auc(antal.ALARM, ("in sample", "a", 8, "s2", False)).rename(columns={"v": "dandiset, in sample"})
pa_s = s2_per_ant(antal.ALARM).rename(columns={"v": "Data S2"})
x = pa_d.merge(pa_i, on=["subject", "cohort", "glomerulus", "odor"]).merge(pa_s, on=["subject", "cohort", "glomerulus", "odor"])
x["above_chance"] = [(s, g) in ABOVE for s, g in zip(x.subject, x.glomerulus)]
print("\nPer ant mean AUC (a, r=8), PGb and PGa, dandiset at the held-out and in-sample placements against Data S2:")
print(x[x.glomerulus.isin(["PGb", "PGa"])].sort_values(["glomerulus", "odor", "cohort"]).round(2).to_string(index=False))
glomerulus odor variant n_young n_old mean_young mean_old t p
PGa 4-methyl-3-heptanol leave one odor out, a, r=8 7 6 -4.205 0.07576 -1.288 0.2379
PGa 4-methyl-3-heptanol leave one odor out, b, r=8 7 6 -5.155 2.308 -1.66 0.1256
PGa 4-methyl-3-heptanol leave one odor out, a, r=5 7 6 -4.639 0.7749 -1.313 0.2312
PGa 4-methyl-3-heptanol leave one odor out, a, r=12 7 6 -3.757 -0.09724 -1.319 0.223
PGa 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 6 -4.205 0.07576 -1.288 0.2379
PGa 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 5 -4.205 0.629 -1.243 0.2664
PGa 4-methyl-3-heptanol in sample, a, r=8 7 6 -1.35 11.62 -2.511 0.04383
PGa 4-methyl-3-heptanol in sample, b, r=8 7 6 0.4185 14.43 -2.398 0.05278
PGa 4-methyl-3-heptanol in sample, a, r=8, above chance 7 5 -1.35 12.54 -2.278 0.07452
PGa 4-methyl-3-heptanol general only, a, r=8 7 6 -3.908 -0.1848 -1.071 0.3277
PGa 4-methyl-3-heptanol general only, b, r=8 7 6 -2.163 2.156 -1.103 0.3151
PGa 4-methyl-3-heptanol Data S2 (reference) 7 6 5.043 19.74 -3.064 0.01643
PGa 4-methyl-3-heptanone leave one odor out, a, r=8 7 6 -3.318 1.383 -2.01 0.08293
PGa 4-methyl-3-heptanone leave one odor out, b, r=8 7 6 -2.113 3.203 -2.047 0.07984
PGa 4-methyl-3-heptanone leave one odor out, a, r=5 7 6 -3.891 1.746 -2.258 0.05678
PGa 4-methyl-3-heptanone leave one odor out, a, r=12 7 6 -3.298 0.9729 -1.675 0.1385
PGa 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 6 -3.318 1.383 -2.01 0.08293
PGa 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 5 -3.318 1.185 -1.63 0.1612
PGa 4-methyl-3-heptanone in sample, a, r=8 7 6 -4.051 0.1196 -1.91 0.09299
PGa 4-methyl-3-heptanone in sample, b, r=8 7 6 -2.377 1.259 -1.534 0.1618
PGa 4-methyl-3-heptanone in sample, a, r=8, above chance 7 5 -4.051 -0.305 -1.489 0.1893
PGa 4-methyl-3-heptanone general only, a, r=8 7 6 -1.563 -0.8726 -0.5057 0.625
PGa 4-methyl-3-heptanone general only, b, r=8 7 6 -0.0757 0.4663 -0.4299 0.6772
PGa 4-methyl-3-heptanone Data S2 (reference) 7 6 -2.856 -0.8641 -0.8729 0.4092
PGb 4-methyl-3-heptanol leave one odor out, a, r=8 7 7 -0.9367 3.001 -0.8235 0.4288
PGb 4-methyl-3-heptanol leave one odor out, b, r=8 7 7 0.7307 3.58 -0.537 0.6036
PGb 4-methyl-3-heptanol leave one odor out, a, r=5 7 7 0.04293 3.222 -0.6303 0.5406
PGb 4-methyl-3-heptanol leave one odor out, a, r=12 7 7 -1.361 3.602 -1.118 0.2912
PGb 4-methyl-3-heptanol leave one odor out, a, r=8, all trials 7 7 -0.9367 3.001 -0.8235 0.4288
PGb 4-methyl-3-heptanol leave one odor out, a, r=8, above chance 7 5 -0.9367 5.176 -1.029 0.3437
PGb 4-methyl-3-heptanol in sample, a, r=8 7 7 -0.6211 6.363 -1.5 0.1625
PGb 4-methyl-3-heptanol in sample, b, r=8 7 7 1.101 8.936 -1.814 0.09622
PGb 4-methyl-3-heptanol in sample, a, r=8, above chance 7 5 -0.6211 8.382 -1.7 0.1354
PGb 4-methyl-3-heptanol general only, a, r=8 7 7 -2.655 3.002 -2.207 0.05152
PGb 4-methyl-3-heptanol general only, b, r=8 7 7 -0.7473 2.076 -0.6038 0.5642
PGb 4-methyl-3-heptanol Data S2 (reference) 7 7 4.503 21.9 -3.61 0.003644
PGb 4-methyl-3-heptanone leave one odor out, a, r=8 7 7 -0.2058 8.455 -1.615 0.1415
PGb 4-methyl-3-heptanone leave one odor out, b, r=8 7 7 1.547 9.362 -1.399 0.195
PGb 4-methyl-3-heptanone leave one odor out, a, r=5 7 7 0.01586 10.47 -1.704 0.1253
PGb 4-methyl-3-heptanone leave one odor out, a, r=12 7 7 -1.1 6.879 -1.735 0.1172
PGb 4-methyl-3-heptanone leave one odor out, a, r=8, all trials 7 7 -0.2058 8.455 -1.615 0.1415
PGb 4-methyl-3-heptanone leave one odor out, a, r=8, above chance 7 5 -0.2058 4.644 -1.293 0.2296
PGb 4-methyl-3-heptanone in sample, a, r=8 7 7 4.555 5.155 -0.1739 0.865
PGb 4-methyl-3-heptanone in sample, b, r=8 7 7 6.823 6.352 0.1446 0.8874
PGb 4-methyl-3-heptanone in sample, a, r=8, above chance 7 5 4.555 6.059 -0.3854 0.7108
PGb 4-methyl-3-heptanone general only, a, r=8 7 7 -2.827 3.79 -2.544 0.02605
PGb 4-methyl-3-heptanone general only, b, r=8 7 7 -0.3915 1.741 -0.4472 0.6665
PGb 4-methyl-3-heptanone Data S2 (reference) 7 7 7.762 20.66 -2.463 0.0358
leave one odor out, a, r=8: reading (a) holds False; reading (b) holds False
leave one odor out, b, r=8: reading (a) holds False; reading (b) holds False
leave one odor out, a, r=5: reading (a) holds False; reading (b) holds False
leave one odor out, a, r=12: reading (a) holds False; reading (b) holds False
leave one odor out, a, r=8, all trials: reading (a) holds False; reading (b) holds False
leave one odor out, a, r=8, above chance: reading (a) holds False; reading (b) holds False
in sample, a, r=8: reading (a) holds False; reading (b) holds False
in sample, b, r=8: reading (a) holds False; reading (b) holds False
in sample, a, r=8, above chance: reading (a) holds False; reading (b) holds False
general only, a, r=8: reading (a) holds False; reading (b) holds False
general only, b, r=8: reading (a) holds False; reading (b) holds False
Data S2 (reference): reading (a) holds True; reading (b) holds True
Per ant mean AUC (a, r=8), PGb and PGa, dandiset at the held-out and in-sample placements against Data S2:
subject cohort glomerulus odor dandiset, held out dandiset, in sample Data S2 above_chance
ant1-m4-d6-y2023 old PGa 4-methyl-3-heptanol -1.44 1.57 24.2 True
ant3-m1-d10-y2023 old PGa 4-methyl-3-heptanol -2.69 7.01 16.47 False
ant4-m1-d10-y2023 old PGa 4-methyl-3-heptanol -0.18 33.79 38.21 True
ant4-m4-d6-y2023 old PGa 4-methyl-3-heptanol -4.33 10.1 16.76 True
ant5-m4-d6-y2023 old PGa 4-methyl-3-heptanol -5.49 2.62 8.03 True
ant6-m1-d10-y2023 old PGa 4-methyl-3-heptanol 14.59 14.62 14.75 True
ant1-m1-d20-y2023 young PGa 4-methyl-3-heptanol -5.67 2.73 9.73 True
ant1-m2-d24-y2023 young PGa 4-methyl-3-heptanol 0.87 -0.74 -0.52 True
ant2-m1-d20-y2023 young PGa 4-methyl-3-heptanol -1.69 -1.82 2.51 True
ant2-m2-d24-y2023 young PGa 4-methyl-3-heptanol -8.72 -9.08 -1.09 True
ant3-m2-d24-y2023 young PGa 4-methyl-3-heptanol -4.37 -4.87 10.58 True
ant4-m1-d18-y2023 young PGa 4-methyl-3-heptanol -8.74 -0.81 1.09 True
ant4-m1-d20-y2023 young PGa 4-methyl-3-heptanol -1.12 5.14 13.01 True
ant1-m4-d6-y2023 old PGa 4-methyl-3-heptanone -3.91 -4.76 -6.75 True
ant3-m1-d10-y2023 old PGa 4-methyl-3-heptanone 2.37 2.24 1.44 False
ant4-m1-d10-y2023 old PGa 4-methyl-3-heptanone 6.83 6.83 7.1 True
ant4-m4-d6-y2023 old PGa 4-methyl-3-heptanone -0.48 -2.65 -1.97 True
ant5-m4-d6-y2023 old PGa 4-methyl-3-heptanone -4.21 -4.21 -4.98 True
ant6-m1-d10-y2023 old PGa 4-methyl-3-heptanone 7.7 3.26 -0.02 True
ant1-m1-d20-y2023 young PGa 4-methyl-3-heptanone -4.42 -4.42 -4.81 True
ant1-m2-d24-y2023 young PGa 4-methyl-3-heptanone -5.89 -1.88 -2.45 True
ant2-m1-d20-y2023 young PGa 4-methyl-3-heptanone -1.51 -1.36 -3.5 True
ant2-m2-d24-y2023 young PGa 4-methyl-3-heptanone -4.56 -7.59 2.92 True
ant3-m2-d24-y2023 young PGa 4-methyl-3-heptanone -5.92 -7.78 -4.6 True
ant4-m1-d18-y2023 young PGa 4-methyl-3-heptanone 1.46 -4.19 -5.37 True
ant4-m1-d20-y2023 young PGa 4-methyl-3-heptanone -2.37 -1.14 -2.18 True
ant1-m4-d6-y2023 old PGb 4-methyl-3-heptanol 4.13 4.01 28.2 True
ant3-m1-d10-y2023 old PGb 4-methyl-3-heptanol -4.31 -0.62 7.66 True
ant4-m1-d10-y2023 old PGb 4-methyl-3-heptanol -2.7 9.75 22.8 True
ant4-m4-d6-y2023 old PGb 4-methyl-3-heptanol 3.28 3.28 17.38 True
ant5-m1-d10-y2023 old PGb 4-methyl-3-heptanol -5.65 -6.53 33.61 False
ant5-m4-d6-y2023 old PGb 4-methyl-3-heptanol 25.49 25.49 26.03 True
ant6-m1-d10-y2023 old PGb 4-methyl-3-heptanol 0.78 9.16 17.65 False
ant1-m1-d20-y2023 young PGb 4-methyl-3-heptanol -0.28 -0.28 4.35 True
ant1-m2-d24-y2023 young PGb 4-methyl-3-heptanol -0.27 -0.27 6.66 True
ant2-m1-d20-y2023 young PGb 4-methyl-3-heptanol -0.76 1.9 6.06 True
ant2-m2-d24-y2023 young PGb 4-methyl-3-heptanol -9.56 -9.56 -4.87 True
ant3-m2-d24-y2023 young PGb 4-methyl-3-heptanol -6.62 -6.62 -5.19 True
ant4-m1-d18-y2023 young PGb 4-methyl-3-heptanol 12.45 12.45 23 True
ant4-m1-d20-y2023 young PGb 4-methyl-3-heptanol -1.51 -1.96 1.51 True
ant1-m4-d6-y2023 old PGb 4-methyl-3-heptanone -0.81 -0.81 4.73 True
ant3-m1-d10-y2023 old PGb 4-methyl-3-heptanone 0.86 0.54 17.17 True
ant4-m1-d10-y2023 old PGb 4-methyl-3-heptanone 4.04 11.72 35.84 True
ant4-m4-d6-y2023 old PGb 4-methyl-3-heptanone 3.46 3.18 16.44 True
ant5-m1-d10-y2023 old PGb 4-methyl-3-heptanone 34.52 -3.32 36.77 False
ant5-m4-d6-y2023 old PGb 4-methyl-3-heptanone 15.67 15.67 9.75 True
ant6-m1-d10-y2023 old PGb 4-methyl-3-heptanone 1.45 9.1 23.95 False
ant1-m1-d20-y2023 young PGb 4-methyl-3-heptanone 7.43 8.06 11.56 True
ant1-m2-d24-y2023 young PGb 4-methyl-3-heptanone 6.22 6.22 4.97 True
ant2-m1-d20-y2023 young PGb 4-methyl-3-heptanone 2.42 2.42 2.3 True
ant2-m2-d24-y2023 young PGb 4-methyl-3-heptanone -9.75 -0.32 8.85 True
ant3-m2-d24-y2023 young PGb 4-methyl-3-heptanone -5.73 14.18 19.55 True
ant4-m1-d18-y2023 young PGb 4-methyl-3-heptanone -3.26 -3.26 0.29 True
ant4-m1-d20-y2023 young PGb 4-methyl-3-heptanone 1.23 4.58 6.83 True
Result: not reproduced. At the held-out placement (reading a, radius 8) old ants' PGb and PGa alarm AUCs are higher on average but no difference is significant: PGb with 4-methyl-3-heptanone -0.21 against 8.46 (p = 0.14), PGb with 4-methyl-3-heptanol p = 0.43, PGa with 4-methyl-3-heptanol -4.21 against 0.08 (p = 0.24). No variant meets either reading. The one pair significant at the placement from all trials, PGa with 4-methyl-3-heptanol (11.6 against -1.35, p = 0.044), is not significant at any held-out placement (p 0.13 to 0.33), so it came from choosing the place on the same trials. With the placement from the general odorants alone, PGb with 4-methyl-3-heptanone is higher in old ants (p = 0.026) but PGa is not (p = 0.33). PGa's 4-methyl-3-heptanone AUC is -3.32 (young) and 1.38 (old), no response. Data S2's own values meet both readings (PGb p = 0.036 and 0.0036, PGa 0.016). At the held-out placement the old ants' PGb AUCs are below Data S2's in 6 of 7 ants for 4-methyl-3-heptanone and 7 of 7 for 4-methyl-3-heptanol (per-ant table).
fig3d-pg-60-vs-90-days-same¶
PGb and PGa responses to alarm pheromones do not differ between 60-day-old ants and the 90-day-old ants of the earlier study.
Reading (b) of the claims file: the 90-day per-ant values are those of Data S2 (its 2022 ants at 48%, AUC frames 6-40 from its traces, mean over trials); the 60-day values are the dandiset's, so this reading compares values measured here with values measured by the paper. Reading (a) follows.
s2_90 = s2[(s2.age == "3months") & (s2.conc == 0.48) & s2.glomerulus.isin(["PGb", "PGa"]) & s2.odor_ds.isin(antal.ALARM)]
a90 = (s2_90[s2_90.frame >= 6].groupby(["subject", "glomerulus", "odor_ds", "trial"]).dff.sum()
.groupby(["subject", "glomerulus", "odor_ds"]).mean().rename("v").reset_index().rename(columns={"odor_ds": "odor"}))
print("90-day ants in Data S2 at 48% per glomerulus and odor:")
print(a90.groupby(["glomerulus", "odor"]).subject.nunique().to_string())
rows = []
for name, pa60 in [(vname(v), per_ant_auc(antal.ALARM, v)) for v in ALARM_VARIANTS] + [("Data S2 60-day (reference)", s2_per_ant(antal.ALARM))]:
for g, o in [("PGb", antal.ALARM[0]), ("PGb", antal.ALARM[1]), ("PGa", antal.ALARM[1])]:
x60 = pa60[(pa60.cohort == "old") & (pa60.glomerulus == g) & (pa60.odor == o)].v
x90 = a90[(a90.glomerulus == g) & (a90.odor == o)].v
t, p = stats.ttest_ind(x60, x90, equal_var=False)
rows.append({"60-day values": name, "glomerulus": g, "odor": o, "n60": len(x60), "n90": len(x90),
"mean60": x60.mean(), "mean90": x90.mean(), "t": t, "p": p})
r6090 = pd.DataFrame(rows)
print(r6090.to_string(index=False))
90-day ants in Data S2 at 48% per glomerulus and odor:
glomerulus odor
PGa 4-methyl-3-heptanol 8
4-methyl-3-heptanone 8
PGb 4-methyl-3-heptanol 8
4-methyl-3-heptanone 8
60-day values glomerulus odor n60 n90 mean60 mean90 t p
leave one odor out, a, r=8 PGb 4-methyl-3-heptanone 7 8 8.455 24.99 -2.964 0.01434
leave one odor out, a, r=8 PGb 4-methyl-3-heptanol 7 8 3.001 20.28 -2.612 0.02205
leave one odor out, a, r=8 PGa 4-methyl-3-heptanol 6 8 0.07576 17.79 -2.87 0.01577
leave one odor out, b, r=8 PGb 4-methyl-3-heptanone 7 8 9.362 24.99 -2.743 0.02126
leave one odor out, b, r=8 PGb 4-methyl-3-heptanol 7 8 3.58 20.28 -2.384 0.0331
leave one odor out, b, r=8 PGa 4-methyl-3-heptanol 6 8 2.308 17.79 -2.459 0.03173
leave one odor out, a, r=5 PGb 4-methyl-3-heptanone 7 8 10.47 24.99 -2.313 0.04604
leave one odor out, a, r=5 PGb 4-methyl-3-heptanol 7 8 3.222 20.28 -2.613 0.0222
leave one odor out, a, r=5 PGa 4-methyl-3-heptanol 6 8 0.7749 17.79 -2.585 0.02439
leave one odor out, a, r=12 PGb 4-methyl-3-heptanone 7 8 6.879 24.99 -3.629 0.003982
leave one odor out, a, r=12 PGb 4-methyl-3-heptanol 7 8 3.602 20.28 -2.55 0.02495
leave one odor out, a, r=12 PGa 4-methyl-3-heptanol 6 8 -0.09724 17.79 -3.031 0.01333
leave one odor out, a, r=8, all trials PGb 4-methyl-3-heptanone 7 8 8.455 24.99 -2.964 0.01434
leave one odor out, a, r=8, all trials PGb 4-methyl-3-heptanol 7 8 3.001 20.28 -2.612 0.02205
leave one odor out, a, r=8, all trials PGa 4-methyl-3-heptanol 6 8 0.07576 17.79 -2.87 0.01577
leave one odor out, a, r=8, above chance PGb 4-methyl-3-heptanone 5 8 4.644 24.99 -5.017 0.0005117
leave one odor out, a, r=8, above chance PGb 4-methyl-3-heptanol 5 8 5.176 20.28 -2.014 0.07135
leave one odor out, a, r=8, above chance PGa 4-methyl-3-heptanol 5 8 0.629 17.79 -2.643 0.02307
in sample, a, r=8 PGb 4-methyl-3-heptanone 7 8 5.155 24.99 -5.078 0.000212
in sample, a, r=8 PGb 4-methyl-3-heptanol 7 8 6.363 20.28 -2.137 0.05325
in sample, a, r=8 PGa 4-methyl-3-heptanol 6 8 11.62 17.79 -0.851 0.4115
in sample, b, r=8 PGb 4-methyl-3-heptanone 7 8 6.352 24.99 -5.165 0.000198
in sample, b, r=8 PGb 4-methyl-3-heptanol 7 8 8.936 20.28 -1.809 0.0962
in sample, b, r=8 PGa 4-methyl-3-heptanol 6 8 14.43 17.79 -0.4336 0.6726
in sample, a, r=8, above chance PGb 4-methyl-3-heptanone 5 8 6.059 24.99 -4.391 0.001591
in sample, a, r=8, above chance PGb 4-methyl-3-heptanol 5 8 8.382 20.28 -1.703 0.1171
in sample, a, r=8, above chance PGa 4-methyl-3-heptanol 5 8 12.54 17.79 -0.6609 0.5241
general only, a, r=8 PGb 4-methyl-3-heptanone 7 8 3.79 24.99 -6.141 4.904e-05
general only, a, r=8 PGb 4-methyl-3-heptanol 7 8 3.002 20.28 -3.033 0.01372
general only, a, r=8 PGa 4-methyl-3-heptanol 6 8 -0.1848 17.79 -2.828 0.01622
general only, b, r=8 PGb 4-methyl-3-heptanone 7 8 1.741 24.99 -4.441 0.001114
general only, b, r=8 PGb 4-methyl-3-heptanol 7 8 2.076 20.28 -2.643 0.0204
general only, b, r=8 PGa 4-methyl-3-heptanol 6 8 2.156 17.79 -2.37 0.03597
Data S2 60-day (reference) PGb 4-methyl-3-heptanone 7 8 20.66 24.99 -0.7961 0.4442
Data S2 60-day (reference) PGb 4-methyl-3-heptanol 7 8 21.9 20.28 0.2633 0.797
Data S2 60-day (reference) PGa 4-methyl-3-heptanol 6 8 19.74 17.79 0.2843 0.7811
Reading (a): the 90-day AUCs recomputed from DANDI 000467 (version 0.261004.1655, the latest published), the earlier study's dandiset, which has the same file layout. The eight 90-day ants of Data S2 at 48% are located in it as above (PGb from its 12 Data S2 trials with four odors, PGa from its 6 alarm trials), and their AUCs are measured as for the 60-day ants.
OLD_ID, OLD_VERSION = antal.OLD_DANDISET
a467 = antal.list_assets(OLD_ID, OLD_VERSION)
t467 = antal.trial_table(a467)
s2_90all = s2[(s2.age == "3months") & (s2.conc == 0.48)]
subs90 = sorted(s2_90all.subject.unique())
t90 = t467[t467.subject.isin(subs90) & (t467.conc == 0.48)].copy()
print("Trials of these ants at 48% by number of frames (the 120-frame trials are left out):",
t90.groupby(["odor", "frames"]).t.count().to_dict())
t90 = t90[t90.frames == 40]
t90["key"] = list(zip(t90.subject, t90.t))
print(f"DANDI {OLD_ID} version {OLD_VERSION}: {len(a467)} assets, {t467.subject.nunique()} ants; "
f"the {len(subs90)} Data S2 90-day ants at 48% are all in it: {set(subs90) <= set(t467.subject)}")
print(t90.groupby(["subject", "odor"]).t.count().unstack(fill_value=0).to_string())
with ThreadPoolExecutor(8) as ex:
m90 = dict(zip(subs90, ex.map(lambda s: antal.nwb_metadata(t90[t90.subject == s].blobs.iloc[0][16]), subs90)))
print("Ages (NWB):", {s: m["age"] for s, m in m90.items()}, "; offsets:", {m["offset"] for m in m90.values()},
"; shapes:", {m["shape"] for m in m90.values()})
print(antal.check_offsets(t90.sample(2, random_state=0).blobs.map(lambda b: b[3]).tolist()).to_string(index=False))
t0 = time.time()
maps90 = antal.all_volume_maps(dict(zip(t90.key, t90.blobs)), keep_keys=list(t90.key), processes=4)
SH90 = dict(zip(t90.key, antal.all_trial_shifts([b[antal.SHIFT_PLANE] for b in t90.blobs])))
pk90 = antal.s2_peaks(s2_90all)
chk = s2_90all.dropna(subset=["t_s2"]).drop_duplicates(["subject", "odor_ds", "rep", "t_s2"])
chk = chk.merge(t467[t467.conc == 0.48][["subject", "odor", "rep", "t"]], left_on=["subject", "odor_ds", "rep"], right_on=["subject", "odor", "rep"], how="left")
print(f"Data S2 rows naming the trial number: {len(chk)}; the dandiset's trial of that odor and repeat has that number: {int((chk.t == chk.t_s2).sum())}")
loc90, jobs90 = [], []
rng90 = np.random.default_rng(467)
FLOOR90 = {}
for (s, g), x in pk90.groupby(["subject", "glomerulus"]):
tt_ = t90[t90.subject == s].set_index(["odor", "rep"])
raw = np.mean([maps90[k]["raw_max"] for k in tt_.key], 0)
al90 = antal.al_mask(raw)
FLOOR90[s] = float(np.median(raw[~al90]))
m = antal._block(al90.astype(float)) > 0.5
x = x[[(o, r) in tt_.index for o, r in zip(x.odor_ds, x.rep)]]
keys = [tt_.loc[(o, r), "key"] for o, r in zip(x.odor_ds, x.rep)]
pl90 = {k: maps90[k]["planes_peak"] for k in tt_.key}
loc = antal.locate(pl90, keys, x.s2_peak.to_numpy(), m)
nul = antal.locate_null(pl90, keys, x.s2_peak.to_numpy(), m, n=200, rng=rng90)
loc90.append({"subject": s, "glomerulus": g, "n_trials": len(x), **loc, "null95_trial": np.quantile(nul, 0.95),
"p_trial": (1 + np.sum(nul >= loc["r"])) / 201})
for (o, r), row in tt_.iterrows():
jobs90.append(((s, g, o, r), row.blobs, loc["z_index"], loc["y"], loc["x"], 8, (5, 12), SH90[row.key], FLOOR90[s]))
tr90 = antal.all_roi_traces(jobs90)
print(f"{len(maps90)} trials and {len(tr90)} glomerulus traces in {(time.time() - t0) / 60:.1f} min")
auc90 = pd.DataFrame([{"subject": s, "glomerulus": g, "odor": o, "rep": r, "radius": rad,
"auc_a": A[antal.AUC].sum(), "auc_b": B[antal.AUC].sum()}
for (s, g, o, r), v in tr90.items() for rad, (A, B) in v.items()])
s2auc90 = (s2_90all[s2_90all.frame >= 6].groupby(["subject", "glomerulus", "odor_ds", "rep"]).dff.sum()
.rename("auc_s2").reset_index().rename(columns={"odor_ds": "odor"}))
c90 = auc90[auc90.radius == 8].merge(s2auc90, on=["subject", "glomerulus", "odor", "rep"])
l90 = c90.groupby(["subject", "glomerulus"]).apply(lambda d: pd.Series({"n": len(d), "r_auc_a": np.corrcoef(d.auc_s2, d.auc_a)[0, 1]}),
include_groups=False).reset_index()
l90 = l90.merge(pd.DataFrame(loc90)[["subject", "glomerulus", "z_index", "y", "x", "r", "n_trials", "null95_trial", "p_trial"]], on=["subject", "glomerulus"])
print("\n90-day ants: positions found and agreement of per-trial AUCs with Data S2 (r >= 0.7):")
print(l90.to_string(index=False))
print("Agree:", int((l90.r_auc_a >= 0.7).sum()), "of", len(l90), "; placements above chance (p < 0.05):",
l90.assign(a=l90.p_trial < 0.05).groupby("glomerulus").a.agg(["sum", "count"]).to_dict("index"))
rows = []
# the 60-day values at the placement from all trials (in sample) and, added after the round 2
# verifier, at the held-out placements of the main measure; the 90-day values are always in sample
for pl60, rd, rad in [("in sample", rd, rad) for rd, rad in (("a", 8), ("b", 8), ("a", 5), ("a", 12), ("b", 5), ("b", 12))] \
+ [(pl, rd, 8) for pl in ("leave one odor out", "general only") for rd in ("a", "b")]:
pa60 = per_ant_auc(antal.ALARM, (pl60, rd, rad, "s2", False))
a90r = (auc90[auc90.radius == rad].merge(s2auc90[["subject", "glomerulus", "odor", "rep"]], on=["subject", "glomerulus", "odor", "rep"])
.groupby(["subject", "glomerulus", "odor"])[f"auc_{rd}"].mean().rename("v").reset_index())
for g, o in [("PGb", antal.ALARM[0]), ("PGb", antal.ALARM[1]), ("PGa", antal.ALARM[1])]:
x60 = pa60[(pa60.cohort == "old") & (pa60.glomerulus == g) & (pa60.odor == o)].v
x90 = a90r[(a90r.glomerulus == g) & (a90r.odor == o)].v
t, p = stats.ttest_ind(x60, x90, equal_var=False)
rows.append({"60-day placement": pl60, "variant": f"{rd}, r={rad}", "glomerulus": g, "odor": o, "n60": len(x60), "n90": len(x90),
"mean60": x60.mean(), "mean90 (000467)": x90.mean(), "t": t, "p": p})
r6090a = pd.DataFrame(rows)
print("\nReading (a): 60-day (dandiset 000773) against 90-day (DANDI 000467), both measured here:")
print(r6090a.to_string(index=False))
Trials of these ants at 48% by number of frames (the 120-frame trials are left out): {('4-methyl-3-heptanol', 40): 24, ('4-methyl-3-heptanone', 40): 24, ('4-methyl-3-hexanol', 40): 24, ('6-methyl-5-hepten-2-one', 40): 24, ('6-methyl-5-hepten-2-one', 120): 5}
DANDI 000467 version 0.261004.1655: 14685 assets, 22 ants; the 8 Data S2 90-day ants at 48% are all in it: True
odor 4-methyl-3-heptanol 4-methyl-3-heptanone 4-methyl-3-hexanol 6-methyl-5-hepten-2-one
subject
ant1-m4-d21-y2022 3 3 3 3
ant1-m4-d26-y2022 3 3 3 3
ant2-m4-d21-y2022 3 3 3 3
ant2-m4-d26-y2022 3 3 3 3
ant3-m4-d21-y2022 3 3 3 3
ant3-m4-d26-y2022 3 3 3 3
ant4-m4-d26-y2022 3 3 3 3
ant5-m4-d26-y2022 3 3 3 3
Ages (NWB): {'ant1-m4-d21-y2022': 'P92D/P99D', 'ant1-m4-d26-y2022': 'P97D/P104D', 'ant2-m4-d21-y2022': 'P92D/P99D', 'ant2-m4-d26-y2022': 'P97D/P104D', 'ant3-m4-d21-y2022': 'P92D/P99D', 'ant3-m4-d26-y2022': 'P97D/P104D', 'ant4-m4-d26-y2022': 'P97D/P104D', 'ant5-m4-d26-y2022': 'P97D/P104D'} ; offsets: {3464} ; shapes: {(40, 512, 512)}
blob offset range_equals_h5py ca1331f3-9c35-483f-8aa6-cb20d1c408df 3464 True f014ac91-f325-4c59-9959-ef9e751ae43d 3464 True
Data S2 rows naming the trial number: 96; the dandiset's trial of that odor and repeat has that number: 95
96 trials and 192 glomerulus traces in 4.2 min
90-day ants: positions found and agreement of per-trial AUCs with Data S2 (r >= 0.7):
subject glomerulus n r_auc_a z_index y x r n_trials null95_trial p_trial
ant1-m4-d21-y2022 PGa 6 -0.3319 7 174 218 0.9987 6 0.9992 0.1443
ant1-m4-d21-y2022 PGb 12 0.8919 19 222 286 0.9948 12 0.9423 0.004975
ant1-m4-d26-y2022 PGa 6 0.9393 19 258 366 0.9983 6 0.9993 0.3134
ant1-m4-d26-y2022 PGb 12 0.9039 16 266 330 0.9975 12 0.9553 0.004975
ant2-m4-d21-y2022 PGa 6 0.9786 17 398 298 0.9974 6 0.9992 0.2587
ant2-m4-d21-y2022 PGb 12 0.9108 20 254 326 0.9854 12 0.9431 0.00995
ant2-m4-d26-y2022 PGa 6 0.7834 0 258 310 0.9986 6 0.9993 0.194
ant2-m4-d26-y2022 PGb 12 0.962 17 226 322 0.9938 12 0.9563 0.004975
ant3-m4-d21-y2022 PGa 6 0.7431 20 190 342 0.9997 6 0.9992 0.01493
ant3-m4-d21-y2022 PGb 12 0.9318 19 238 306 0.9927 12 0.9478 0.004975
ant3-m4-d26-y2022 PGa 6 0.8031 22 362 242 0.999 6 0.9993 0.1194
ant3-m4-d26-y2022 PGb 12 0.9361 20 270 310 0.9862 12 0.9511 0.004975
ant4-m4-d26-y2022 PGa 6 0.3952 10 426 282 0.9989 6 0.9993 0.1443
ant4-m4-d26-y2022 PGb 12 0.9059 15 302 318 0.9933 12 0.95 0.004975
ant5-m4-d26-y2022 PGa 6 0.2134 17 114 342 0.9962 6 0.9993 0.5224
ant5-m4-d26-y2022 PGb 12 0.5575 23 142 398 0.9411 12 0.9434 0.06965
Agree: 12 of 16 ; placements above chance (p < 0.05): {'PGa': {'sum': 1, 'count': 8}, 'PGb': {'sum': 7, 'count': 8}}
Reading (a): 60-day (dandiset 000773) against 90-day (DANDI 000467), both measured here:
60-day placement variant glomerulus odor n60 n90 mean60 mean90 (000467) t p
in sample a, r=8 PGb 4-methyl-3-heptanone 7 8 5.155 11.59 -1.529 0.1505
in sample a, r=8 PGb 4-methyl-3-heptanol 7 8 6.363 6.021 0.07571 0.9411
in sample a, r=8 PGa 4-methyl-3-heptanol 6 8 11.62 6.53 0.8654 0.4086
in sample b, r=8 PGb 4-methyl-3-heptanone 7 8 6.352 14.14 -2.043 0.06313
in sample b, r=8 PGb 4-methyl-3-heptanol 7 8 8.936 9.059 -0.02826 0.9779
in sample b, r=8 PGa 4-methyl-3-heptanol 6 8 14.43 4.508 1.257 0.2334
in sample a, r=5 PGb 4-methyl-3-heptanone 7 8 5.122 12.79 -1.527 0.151
in sample a, r=5 PGb 4-methyl-3-heptanol 7 8 6.022 6.956 -0.202 0.8438
in sample a, r=5 PGa 4-methyl-3-heptanol 6 8 11.29 5.854 1.019 0.3348
in sample a, r=12 PGb 4-methyl-3-heptanone 7 8 5.381 10.89 -1.399 0.1856
in sample a, r=12 PGb 4-methyl-3-heptanol 7 8 6.608 6.621 -0.002832 0.9978
in sample a, r=12 PGa 4-methyl-3-heptanol 6 8 11.19 6 0.9368 0.3721
in sample b, r=5 PGb 4-methyl-3-heptanone 7 8 5.686 14.85 -2.073 0.05882
in sample b, r=5 PGb 4-methyl-3-heptanol 7 8 8.498 9.647 -0.2578 0.8011
in sample b, r=5 PGa 4-methyl-3-heptanol 6 8 13.83 7.416 NaN NaN
in sample b, r=12 PGb 4-methyl-3-heptanone 7 8 7.035 13.78 -1.911 0.07998
in sample b, r=12 PGb 4-methyl-3-heptanol 7 8 9.296 9.61 -0.07112 0.9444
in sample b, r=12 PGa 4-methyl-3-heptanol 6 8 14.19 4.653 1.2 0.2535
leave one odor out a, r=8 PGb 4-methyl-3-heptanone 7 8 8.455 11.59 -0.5405 0.5998
leave one odor out a, r=8 PGb 4-methyl-3-heptanol 7 8 3.001 6.021 -0.6464 0.5325
leave one odor out a, r=8 PGa 4-methyl-3-heptanol 6 8 0.07576 6.53 -1.442 0.1751
leave one odor out b, r=8 PGb 4-methyl-3-heptanone 7 8 9.362 14.14 -0.8195 0.4311
leave one odor out b, r=8 PGb 4-methyl-3-heptanol 7 8 3.58 9.059 -1.022 0.3311
leave one odor out b, r=8 PGa 4-methyl-3-heptanol 6 8 2.308 4.508 -0.3386 0.7414
general only a, r=8 PGb 4-methyl-3-heptanone 7 8 3.79 11.59 -2.058 0.06342
general only a, r=8 PGb 4-methyl-3-heptanol 7 8 3.002 6.021 -0.931 0.3688
general only a, r=8 PGa 4-methyl-3-heptanol 6 8 -0.1848 6.53 -1.42 0.1818
general only b, r=8 PGb 4-methyl-3-heptanone 7 8 1.741 14.14 -2.306 0.04144
general only b, r=8 PGb 4-methyl-3-heptanol 7 8 2.076 9.059 -1.341 0.2092
general only b, r=8 PGa 4-methyl-3-heptanol 6 8 2.156 4.508 -0.3464 0.7353
Result: blocked (methods underspecified). The two readings give different outcomes and neither can be run as a fair test here. Reading (a), both cohorts measured here at the placement from all trials, finds no difference (PGb with 4-methyl-3-heptanone 5.15 against 11.59, p = 0.15; PGb with 4-methyl-3-heptanol p = 0.94; PGa p = 0.41). With the 60-day values read at the held-out placement of the main measure (90-day values still from the placement from all trials), it also finds none (8.46 against 11.59, p = 0.60; p = 0.53; PGa 0.08 against 6.53, p = 0.18; reading b p = 0.33 to 0.74); at the placement from the general odorants alone only reading (b) of PGb with 4-methyl-3-heptanone differs (1.74 against 14.14, p = 0.041). But the 90-day PGa positions are placed from six trials, and only 1 of 8 is above chance (r 0.996 to 0.9997 against 95th percentiles of 0.999); the PGb positions (12 trials) are above chance in 7 of 8. A held-out placement cannot be made for the 90-day PGa, whose Data S2 trials are the six alarm trials. Reading (b) compares the 60-day AUCs measured here with Data S2's 90-day values; it finds differences (held-out placement p = 0.014, 0.022 and 0.016; placement from all trials p = 0.0002, 0.053, 0.41), but the AUCs measured here are several times smaller than Data S2's for the same ants (60-day PGb with 4-methyl-3-heptanone 5.15 against Data S2's 20.66 for the same ants; 90-day 11.59 in DANDI 000467 against 24.99), so the difference reflects the two ways of measuring, not age. With Data S2's values for both ages, the only comparison within one pipeline (it uses no dandiset data), none differs (p = 0.44, 0.80 and 0.78). In 000467, 12 of 16 ant and glomerulus pairs agree with Data S2 at r >= 0.7, and one of 96 Data S2 trial numbers does not match the dandiset's trial of that odor and repeat.
figs4a-general-odorants-in-focal-glomeruli¶
3-hexanone excites all six focal glomeruli in young ants and PGb, PGa and G4 in old ants; isopropanol excites all but G1 in young ants only; propionic acid excites G4 at both ages; no other general odorant excites a focal glomerulus.
One-sided one-sample t test that the mean AUC exceeds zero, per cohort, glomerulus and odorant (60 cells), at p < 0.05 uncorrected.
EXPECTED_S4A = {("young", g, "3-hexanone") for g in GLOMS} | {("young", g, "isopropanol") for g in GLOMS if g != "G1"} \
| {("young", "G4", "propionic-acid"), ("old", "PGb", "3-hexanone"), ("old", "PGa", "3-hexanone"),
("old", "G4", "3-hexanone"), ("old", "G4", "propionic-acid")}
def one_sample(pa_):
rows = []
for co in ("young", "old"):
for g in GLOMS:
for o in antal.GENERAL:
v = pa_[(pa_.cohort == co) & (pa_.glomerulus == g) & (pa_.odor == o)].v
t, p = stats.ttest_1samp(v, 0, alternative="greater") if len(v) > 1 else (np.nan, np.nan)
rows.append({"cohort": co, "glomerulus": g, "odor": o, "n": len(v), "mean": v.mean(), "p": p,
"significant": bool(p < 0.05), "expected": (co, g, o) in EXPECTED_S4A})
return pd.DataFrame(rows)
res_s4a = {vname(v): one_sample(per_ant_auc(antal.GENERAL, v)) for v in VARIANTS}
res_s4a["Data S2 (reference)"] = one_sample(s2_per_ant(antal.GENERAL))
main = res_s4a[MAIN_NAME]
print(main.to_string(index=False))
def s4a_summary(v):
hx = v[v.odor.isin(["3-hexanone", "isopropanol"])]
return {"matches": int((v.significant == v.expected).sum()), "missing": int((~v.significant & v.expected).sum()),
"extra": int((v.significant & ~v.expected).sum()),
"hexanone_isopropanol_pattern_holds": bool((hx.significant == hx.expected).all())}
print()
print(pd.DataFrame({k: s4a_summary(v) for k, v in res_s4a.items()}).T.to_string())
for k in (MAIN_NAME, vname(VARIANTS[1]), "in sample, a, r=8"):
v = res_s4a[k]
print(f"\n{k}: mismatches", v[v.significant != v.expected][["cohort", "glomerulus", "odor", "mean", "p", "expected"]].to_string(index=False))
cohort glomerulus odor n mean p significant expected
young PGb 3-hexanone 7 29.02 0.0002824 True True
young PGb isopropanol 7 12.86 0.007599 True True
young PGb ethylpyrazine 7 0.9497 0.3815 False False
young PGb ethanol 7 2.427 0.2843 False False
young PGb propionic-acid 7 -4.167 0.9043 False False
young PGa 3-hexanone 7 27.23 0.002654 True True
young PGa isopropanol 7 24.47 0.006094 True True
young PGa ethylpyrazine 7 0.4151 0.4095 False False
young PGa ethanol 7 -2.05 0.981 False False
young PGa propionic-acid 7 -4.703 0.9906 False False
young G1 3-hexanone 7 7.561 0.05805 False True
young G1 isopropanol 7 2.891 0.05378 False False
young G1 ethylpyrazine 7 -1.849 0.8888 False False
young G1 ethanol 7 1.162 0.3077 False False
young G1 propionic-acid 7 -6.717 0.9892 False False
young G2 3-hexanone 7 32.27 0.01304 True True
young G2 isopropanol 7 12.94 0.009949 True True
young G2 ethylpyrazine 7 0.3075 0.4583 False False
young G2 ethanol 7 1.719 0.3151 False False
young G2 propionic-acid 7 -5.143 0.937 False False
young G3 3-hexanone 7 20.51 0.008572 True True
young G3 isopropanol 7 22.69 0.01373 True True
young G3 ethylpyrazine 7 1.273 0.3125 False False
young G3 ethanol 7 1.356 0.3285 False False
young G3 propionic-acid 7 -4.938 0.9518 False False
young G4 3-hexanone 7 41.62 0.007115 True True
young G4 isopropanol 7 15.81 0.0124 True True
young G4 ethylpyrazine 7 -1.541 0.882 False False
young G4 ethanol 7 2.751 0.2974 False False
young G4 propionic-acid 7 0.9168 0.4408 False True
old PGb 3-hexanone 7 12.78 0.01305 True True
old PGb isopropanol 7 -1.666 0.848 False False
old PGb ethylpyrazine 7 4.499 0.1236 False False
old PGb ethanol 7 -2.31 0.9418 False False
old PGb propionic-acid 7 -0.3171 0.5444 False False
old PGa 3-hexanone 6 5.999 0.04161 True True
old PGa isopropanol 6 -1.034 0.7888 False False
old PGa ethylpyrazine 6 0.6829 0.3045 False False
old PGa ethanol 6 -2.398 0.9761 False False
old PGa propionic-acid 6 -5.039 0.9999 False False
old G1 3-hexanone 7 0.1172 0.4567 False False
old G1 isopropanol 7 -0.211 0.5465 False False
old G1 ethylpyrazine 7 -3.32 0.9954 False False
old G1 ethanol 7 -2.463 0.9861 False False
old G1 propionic-acid 7 -2.777 0.9096 False False
old G2 3-hexanone 7 4.762 0.1727 False False
old G2 isopropanol 7 -0.04233 0.5105 False False
old G2 ethylpyrazine 7 4.012 0.06966 False False
old G2 ethanol 7 -3.454 0.9938 False False
old G2 propionic-acid 7 -3.346 0.9639 False False
old G3 3-hexanone 5 3.819 0.1835 False False
old G3 isopropanol 5 -1.496 0.9437 False False
old G3 ethylpyrazine 5 -0.5663 0.6273 False False
old G3 ethanol 5 -1.495 0.8384 False False
old G3 propionic-acid 5 -0.827 0.743 False False
old G4 3-hexanone 6 3.677 0.1529 False True
old G4 isopropanol 6 0.01511 0.4955 False False
old G4 ethylpyrazine 6 -2.767 0.9484 False False
old G4 ethanol 6 -2.856 0.9988 False False
old G4 propionic-acid 6 0.7514 0.3877 False True
matches missing extra hexanone_isopropanol_pattern_holds
leave one odor out, a, r=8 56 4 0 False
leave one odor out, b, r=8 55 4 1 False
leave one odor out, a, r=5 56 4 0 False
leave one odor out, a, r=12 56 4 0 False
leave one odor out, a, r=8, all trials 56 4 0 False
leave one odor out, a, r=8, above chance 55 5 0 False
in sample, a, r=8 57 3 0 False
in sample, b, r=8 56 3 1 False
in sample, a, r=8, above chance 58 2 0 True
Data S2 (reference) 60 0 0 True
leave one odor out, a, r=8: mismatches cohort glomerulus odor mean p expected
young G1 3-hexanone 7.561 0.05805 True
young G4 propionic-acid 0.9168 0.4408 True
old G4 3-hexanone 3.677 0.1529 True
old G4 propionic-acid 0.7514 0.3877 True
leave one odor out, b, r=8: mismatches cohort glomerulus odor mean p expected
young G1 3-hexanone 7.578 0.07708 True
young G1 isopropanol 4.972 0.02099 False
young G4 propionic-acid 0.4952 0.4703 True
old G4 3-hexanone 2.794 0.2959 True
old G4 propionic-acid 3.168 0.1702 True
in sample, a, r=8: mismatches cohort glomerulus odor mean p expected
young G4 propionic-acid 2.891 0.3073 True
old G4 3-hexanone 2.813 0.06338 True
old G4 propionic-acid 0.6724 0.4025 True
Result: not reproduced (resting on the located ROIs and ROI size). At the held-out placement (reading a, radius 8) 56 of the 60 cells agree with the figure and no unexpected cell is significant. The four that differ are young G1 with 3-hexanone (p = 0.058), old G4 with 3-hexanone (p = 0.15) and G4 with propionic acid at both ages (p = 0.44 and 0.39). The misses for 3-hexanone break the text's pattern, so the claim is not reproduced. The pattern holds under one of the nine variants, the placement from all trials restricted to placements above chance (58 of 60), which would be a partial reproduction; at the placement from all trials it fails on old G4 with 3-hexanone (p = 0.063), as in round 1. Data S2's own values give all 60.
fig4-general-odorants-decline¶
Responses to 3-hexanone and isopropanol decline significantly with age in five of the six focal glomeruli (3-hexanone in all but PGb, isopropanol in all but G1).
res_4 = run_all([(g, o) for o in ["3-hexanone", "isopropanol"] for g in GLOMS], ["3-hexanone", "isopropanol"])
show_all(res_4)
EXP4 = {(g, "3-hexanone") for g in GLOMS if g != "PGb"} | {(g, "isopropanol") for g in GLOMS if g != "G1"}
def s4_summary(v):
dec = (v.p < 0.05) & (v.mean_young > v.mean_old)
e = np.array([(g, o) in EXP4 for g, o in zip(v.glomerulus, v.odor)])
per_odor = {o: int(dec[(v.odor == o).to_numpy() & e].sum()) for o in ["3-hexanone", "isopropanol"]}
return {"expected declines found (of 10)": int(dec[e].sum()), "3-hexanone (of 5)": per_odor["3-hexanone"],
"isopropanol (of 5)": per_odor["isopropanol"],
"PGb 3-hexanone p": float(v[(v.glomerulus == "PGb") & (v.odor == "3-hexanone")].p.iloc[0]),
"G1 isopropanol p": float(v[(v.glomerulus == "G1") & (v.odor == "isopropanol")].p.iloc[0])}
print()
print(pd.DataFrame({k: s4_summary(v) for k, v in res_4.items()}).T.round(4).to_string())
glomerulus odor variant n_young n_old mean_young mean_old t p
G1 3-hexanone leave one odor out, a, r=8 7 7 7.561 0.1172 1.753 0.1247
G1 3-hexanone leave one odor out, b, r=8 7 7 7.578 0.2955 1.55 0.1701
G1 3-hexanone leave one odor out, a, r=5 7 7 7.691 -0.4587 1.735 0.1298
G1 3-hexanone leave one odor out, a, r=12 7 7 7.488 -0.0369 2.002 0.08835
G1 3-hexanone leave one odor out, a, r=8, all trials 7 7 7.561 0.1172 1.753 0.1247
G1 3-hexanone leave one odor out, a, r=8, above chance 7 4 7.561 0.8584 1.489 0.1749
G1 3-hexanone in sample, a, r=8 7 7 9.82 -1.449 2.594 0.03353
G1 3-hexanone in sample, b, r=8 7 7 12.69 -4.114 3.29 0.008306
G1 3-hexanone in sample, a, r=8, above chance 7 4 9.82 -0.6539 2.205 0.05556
G1 3-hexanone Data S2 (reference) 7 7 12.59 -1.735 2.871 0.0269
G1 isopropanol leave one odor out, a, r=8 7 7 2.891 -0.211 1.342 0.2048
G1 isopropanol leave one odor out, b, r=8 7 7 4.972 -4.959 2.272 0.05001
G1 isopropanol leave one odor out, a, r=5 7 7 3.193 -1.095 1.913 0.08001
G1 isopropanol leave one odor out, a, r=12 7 7 2.922 -0.5449 1.455 0.1715
G1 isopropanol leave one odor out, a, r=8, all trials 7 7 2.891 -0.211 1.342 0.2048
G1 isopropanol leave one odor out, a, r=8, above chance 7 4 2.891 -0.6072 2.085 0.07041
G1 isopropanol in sample, a, r=8 7 7 3.149 -0.1729 1.678 0.1326
G1 isopropanol in sample, b, r=8 7 7 5.267 -5.056 2.402 0.03575
G1 isopropanol in sample, a, r=8, above chance 7 4 3.149 -0.006895 1.605 0.1496
G1 isopropanol Data S2 (reference) 7 7 6.281 1.168 1.386 0.2105
G2 3-hexanone leave one odor out, a, r=8 7 7 32.27 4.762 2.305 0.04977
G2 3-hexanone leave one odor out, b, r=8 7 7 33.86 3.515 2.042 0.07479
G2 3-hexanone leave one odor out, a, r=5 7 7 33.83 4.665 2.378 0.04506
G2 3-hexanone leave one odor out, a, r=12 7 7 28.71 4.714 2.241 0.05319
G2 3-hexanone leave one odor out, a, r=8, all trials 7 7 32.27 6.085 2.2 0.05892
G2 3-hexanone leave one odor out, a, r=8, above chance 7 5 32.27 6.917 1.996 0.07655
G2 3-hexanone in sample, a, r=8 7 7 23.32 4.751 2.892 0.01353
G2 3-hexanone in sample, b, r=8 7 7 27.35 6.225 2.799 0.01701
G2 3-hexanone in sample, a, r=8, above chance 7 5 23.32 7.313 2.106 0.06805
G2 3-hexanone Data S2 (reference) 7 7 40.36 3.47 4.811 0.001245
G2 isopropanol leave one odor out, a, r=8 7 7 12.94 -0.04233 2.956 0.01918
G2 isopropanol leave one odor out, b, r=8 7 7 17.23 -7.259 3.622 0.003718
G2 isopropanol leave one odor out, a, r=5 7 7 11.93 0.5424 2.523 0.03195
G2 isopropanol leave one odor out, a, r=12 7 7 13.79 -1.048 3.049 0.02029
G2 isopropanol leave one odor out, a, r=8, all trials 7 7 12.94 -0.04233 2.956 0.01918
G2 isopropanol leave one odor out, a, r=8, above chance 7 5 12.94 -1.821 3.502 0.01097
G2 isopropanol in sample, a, r=8 7 7 9.236 0.06622 2.336 0.04637
G2 isopropanol in sample, b, r=8 7 7 12.81 -1.084 2.746 0.01866
G2 isopropanol in sample, a, r=8, above chance 7 5 9.236 -0.3905 2.261 0.04846
G2 isopropanol Data S2 (reference) 7 7 22.57 1.634 4.428 0.002563
G3 3-hexanone leave one odor out, a, r=8 7 5 20.51 3.819 2.28 0.04773
G3 3-hexanone leave one odor out, b, r=8 7 5 27.11 -1.893 2.994 0.01638
G3 3-hexanone leave one odor out, a, r=5 7 5 20.75 4.114 2.184 0.05563
G3 3-hexanone leave one odor out, a, r=12 7 5 21.44 3.676 2.566 0.03055
G3 3-hexanone leave one odor out, a, r=8, all trials 7 5 20.51 3.491 2.324 0.04436
G3 3-hexanone leave one odor out, a, r=8, above chance 7 1 20.51 18.24 NaN NaN
G3 3-hexanone in sample, a, r=8 7 5 21.88 3.495 3.149 0.01153
G3 3-hexanone in sample, b, r=8 7 5 24.34 2.679 2.803 0.02046
G3 3-hexanone in sample, a, r=8, above chance 7 1 21.88 19.93 NaN NaN
G3 3-hexanone Data S2 (reference) 7 5 56.54 4.783 5.818 0.0001775
G3 isopropanol leave one odor out, a, r=8 7 5 22.69 -1.496 3.074 0.02135
G3 isopropanol leave one odor out, b, r=8 7 5 26.05 -5.199 3.051 0.01349
G3 isopropanol leave one odor out, a, r=5 7 5 24.01 -2.327 3.2 0.01786
G3 isopropanol leave one odor out, a, r=12 7 5 22.05 -1.158 3.114 0.02021
G3 isopropanol leave one odor out, a, r=8, all trials 7 5 22.69 -1.496 3.074 0.02135
G3 isopropanol leave one odor out, a, r=8, above chance 7 1 22.69 0.4034 NaN NaN
G3 isopropanol in sample, a, r=8 7 5 21.72 -1.288 2.107 0.07632
G3 isopropanol in sample, b, r=8 7 5 22.44 -1.995 1.87 0.104
G3 isopropanol in sample, a, r=8, above chance 7 1 21.72 7.021 NaN NaN
G3 isopropanol Data S2 (reference) 7 5 32.04 1.466 3.619 0.008021
G4 3-hexanone leave one odor out, a, r=8 7 6 41.62 3.677 3.01 0.02026
G4 3-hexanone leave one odor out, b, r=8 7 6 41.76 2.794 2.679 0.02975
G4 3-hexanone leave one odor out, a, r=5 7 6 40.94 3.842 3.104 0.01723
G4 3-hexanone leave one odor out, a, r=12 7 6 41.66 3.308 2.931 0.02344
G4 3-hexanone leave one odor out, a, r=8, all trials 7 6 41.62 3.399 3.035 0.01965
G4 3-hexanone leave one odor out, a, r=8, above chance 7 4 41.62 6.21 2.737 0.02759
G4 3-hexanone in sample, a, r=8 7 6 29.97 2.813 3.337 0.01409
G4 3-hexanone in sample, b, r=8 7 6 29.91 5.525 2.589 0.0392
G4 3-hexanone in sample, a, r=8, above chance 7 4 29.97 4.378 3.12 0.01809
G4 3-hexanone Data S2 (reference) 7 6 35.3 7.73 4.383 0.001634
G4 isopropanol leave one odor out, a, r=8 7 6 15.81 0.01511 2.89 0.02454
G4 isopropanol leave one odor out, b, r=8 7 6 14.31 -0.9964 2.058 0.07549
G4 isopropanol leave one odor out, a, r=5 7 6 17.14 0.5056 3.153 0.0131
G4 isopropanol leave one odor out, a, r=12 7 6 16.43 -0.004647 2.663 0.03453
G4 isopropanol leave one odor out, a, r=8, all trials 7 6 15.81 0.01511 2.89 0.02454
G4 isopropanol leave one odor out, a, r=8, above chance 7 4 15.81 -0.6793 2.979 0.02069
G4 isopropanol in sample, a, r=8 7 6 13.61 -1.053 3.181 0.01686
G4 isopropanol in sample, b, r=8 7 6 14.12 1.456 2.011 0.08852
G4 isopropanol in sample, a, r=8, above chance 7 4 13.61 -1.701 3.259 0.0139
G4 isopropanol Data S2 (reference) 7 6 17.73 1.198 3.475 0.008646
PGa 3-hexanone leave one odor out, a, r=8 7 6 27.23 5.999 3.048 0.01557
PGa 3-hexanone leave one odor out, b, r=8 7 6 27.07 8.292 2.47 0.03879
PGa 3-hexanone leave one odor out, a, r=5 7 6 26.58 6.562 2.941 0.01929
PGa 3-hexanone leave one odor out, a, r=12 7 6 27.62 4.95 3.151 0.01458
PGa 3-hexanone leave one odor out, a, r=8, all trials 7 6 27.23 5.999 3.048 0.01557
PGa 3-hexanone leave one odor out, a, r=8, above chance 7 5 27.23 5.628 2.991 0.0156
PGa 3-hexanone in sample, a, r=8 7 6 37.03 7.485 2.39 0.05143
PGa 3-hexanone in sample, b, r=8 7 6 41.78 10.3 2.327 0.05551
PGa 3-hexanone in sample, a, r=8, above chance 7 5 37.03 7.135 2.401 0.04965
PGa 3-hexanone Data S2 (reference) 7 6 39.3 8.389 3.258 0.01414
PGa isopropanol leave one odor out, a, r=8 7 6 24.47 -1.034 3.639 0.009829
PGa isopropanol leave one odor out, b, r=8 7 6 29.26 0.9784 3.68 0.009296
PGa isopropanol leave one odor out, a, r=5 7 6 26.68 -1.134 3.942 0.006703
PGa isopropanol leave one odor out, a, r=12 7 6 23.69 -1.297 3.717 0.00899
PGa isopropanol leave one odor out, a, r=8, all trials 7 6 24.47 -1.034 3.639 0.009829
PGa isopropanol leave one odor out, a, r=8, above chance 7 5 24.47 -0.4591 3.549 0.01085
PGa isopropanol in sample, a, r=8 7 6 15.61 -0.8747 2.321 0.05618
PGa isopropanol in sample, b, r=8 7 6 18.58 1.008 2.483 0.04389
PGa isopropanol in sample, a, r=8, above chance 7 5 15.61 -0.09126 2.209 0.0658
PGa isopropanol Data S2 (reference) 7 6 23.43 -1.442 3.523 0.01123
PGb 3-hexanone leave one odor out, a, r=8 7 7 29.02 12.78 2.632 0.02188
PGb 3-hexanone leave one odor out, b, r=8 7 7 32.54 16.04 2.399 0.03458
PGb 3-hexanone leave one odor out, a, r=5 7 7 27.67 13.4 2.469 0.02971
PGb 3-hexanone leave one odor out, a, r=12 7 7 29.03 13.02 2.851 0.0146
PGb 3-hexanone leave one odor out, a, r=8, all trials 7 7 29.02 12.78 2.632 0.02188
PGb 3-hexanone leave one odor out, a, r=8, above chance 7 5 29.02 13.89 2.322 0.04492
PGb 3-hexanone in sample, a, r=8 7 7 30.6 12.89 3.051 0.01008
PGb 3-hexanone in sample, b, r=8 7 7 35.63 16.19 3.118 0.009164
PGb 3-hexanone in sample, a, r=8, above chance 7 5 30.6 14.05 2.789 0.02
PGb 3-hexanone Data S2 (reference) 7 7 31.28 25.12 1.259 0.232
PGb isopropanol leave one odor out, a, r=8 7 7 12.86 -1.666 3.541 0.007995
PGb isopropanol leave one odor out, b, r=8 7 7 15.33 0.3751 2.615 0.03346
PGb isopropanol leave one odor out, a, r=5 7 7 12.78 -1.197 3.878 0.003345
PGb isopropanol leave one odor out, a, r=12 7 7 14.38 -1.521 2.933 0.02331
PGb isopropanol leave one odor out, a, r=8, all trials 7 7 12.86 -1.666 3.541 0.007995
PGb isopropanol leave one odor out, a, r=8, above chance 7 5 12.86 -2.883 3.771 0.005375
PGb isopropanol in sample, a, r=8 7 7 16.22 -2.166 2.875 0.02599
PGb isopropanol in sample, b, r=8 7 7 20.8 -0.1172 2.919 0.02422
PGb isopropanol in sample, a, r=8, above chance 7 5 16.22 -2.858 2.939 0.02239
PGb isopropanol Data S2 (reference) 7 7 16.76 3.733 2.271 0.04583
expected declines found (of 10) 3-hexanone (of 5) isopropanol (of 5) PGb 3-hexanone p G1 isopropanol p
leave one odor out, a, r=8 9 4 5 0.0219 0.2048
leave one odor out, b, r=8 7 3 4 0.0346 0.05
leave one odor out, a, r=5 8 3 5 0.0297 0.08
leave one odor out, a, r=12 8 3 5 0.0146 0.1715
leave one odor out, a, r=8, all trials 8 3 5 0.0219 0.2048
leave one odor out, a, r=8, above chance 6 2 4 0.0449 0.0704
in sample, a, r=8 7 4 3 0.0101 0.1326
in sample, b, r=8 7 4 3 0.0092 0.0358
in sample, a, r=8, above chance 5 2 3 0.02 0.1496
Data S2 (reference) 10 5 5 0.232 0.2105
Result: partially reproduced (resting on the held-out placement and the ROI size). At the held-out placement (reading a, radius 8) 9 of the 10 declines are significant: 3-hexanone in PGa, G2, G3 and G4 (p = 0.016, 0.0498, 0.048, 0.020) but not G1 (p = 0.12), and isopropanol in all five (PGb 0.008, PGa 0.0098, G2 0.019, G3 0.021, G4 0.025), so at least four of five glomeruli decline for each odorant. 3-hexanone in PGb, not significant in the paper, declines here (29.0 against 12.8, p = 0.022); isopropanol in G1 does not (p = 0.20), as in the paper. The partial level rests on two p-values just under 0.05 and is not met by any of the eight other variants (5 to 8 of 10; 3-hexanone or isopropanol 3 or fewer of 5), including the placement from all trials (7 of 10, isopropanol 3 of 5). Data S2's own values give all 10, with PGb 3-hexanone p = 0.23. The located ROIs bias this claim toward passing: on held-out trials only 9 of 52 old-ant glomerulus traces follow Data S2's, so old-ant values are read partly from places that do not respond like the glomerulus, which pulls old means down. Claims that need old ants to be lower (this one) are pushed toward passing, and those that need them higher (fig3d, the late window of figs4d) toward failing. The PGb decline with 3-hexanone is the visible case: the old mean is 12.8 here against Data S2's 25.1 for the same ants (p = 0.022 here, 0.23 on Data S2). With all trials in place of Data S2's trials, G2 with 3-hexanone gives p = 0.059.
fig4-propionic-acid-g2-g3¶
In young ants propionic acid gives a very small negative deflection in G2 and G3 that declines in amplitude with age; its responses in G4 tend to fall with age, not significantly.
res_pa = run_all([(g, "propionic-acid") for g in ["G2", "G3", "G4"]], ["propionic-acid"])
show_all(res_pa)
for k, v in res_pa.items():
g2, g3, g4 = (v[v.glomerulus == g].iloc[0] for g in ("G2", "G3", "G4"))
ok = (g2.mean_young < 0 and g3.mean_young < 0 and g2.p < 0.05 and g3.p < 0.05 and abs(g2.mean_old) < abs(g2.mean_young)
and abs(g3.mean_old) < abs(g3.mean_young) and g4.p >= 0.05 and g4.mean_old < g4.mean_young)
print(f"{k:>24}: criterion holds {ok}")
glomerulus odor variant n_young n_old mean_young mean_old t p
G2 propionic-acid leave one odor out, a, r=8 7 7 -5.143 -3.346 -0.548 0.5968
G2 propionic-acid leave one odor out, b, r=8 7 7 -5.787 -5.178 -0.1675 0.8704
G2 propionic-acid leave one odor out, a, r=5 7 7 -4.451 -3.588 -0.2532 0.8054
G2 propionic-acid leave one odor out, a, r=12 7 7 -5.706 -3.792 -0.6159 0.5538
G2 propionic-acid leave one odor out, a, r=8, all trials 7 7 -5.143 -3.346 -0.548 0.5968
G2 propionic-acid leave one odor out, a, r=8, above chance 7 5 -5.143 -4.699 -0.1313 0.8983
G2 propionic-acid in sample, a, r=8 7 7 -7.575 -3.817 -1.145 0.2836
G2 propionic-acid in sample, b, r=8 7 7 -5.838 -4.872 -0.2789 0.7867
G2 propionic-acid in sample, a, r=8, above chance 7 5 -7.575 -4.879 -0.8162 0.437
G2 propionic-acid Data S2 (reference) 7 7 -9.869 -3.733 -3.459 0.004968
G3 propionic-acid leave one odor out, a, r=8 7 5 -4.938 -0.827 -1.489 0.1737
G3 propionic-acid leave one odor out, b, r=8 7 5 -4.678 -6.733 0.3408 0.7445
G3 propionic-acid leave one odor out, a, r=5 7 5 -4.378 -1.045 -1.081 0.3052
G3 propionic-acid leave one odor out, a, r=12 7 5 -5.401 -0.9342 -1.622 0.1488
G3 propionic-acid leave one odor out, a, r=8, all trials 7 5 -4.938 -0.827 -1.489 0.1737
G3 propionic-acid leave one odor out, a, r=8, above chance 7 1 -4.938 2.697 NaN NaN
G3 propionic-acid in sample, a, r=8 7 5 -5.324 -0.9763 -1.707 0.1245
G3 propionic-acid in sample, b, r=8 7 5 -5.387 -1.462 -0.9679 0.3569
G3 propionic-acid in sample, a, r=8, above chance 7 1 -5.324 2.697 NaN NaN
G3 propionic-acid Data S2 (reference) 7 5 -5.531 -1.832 -2.54 0.03014
G4 propionic-acid leave one odor out, a, r=8 7 6 0.9168 0.7514 0.02582 0.98
G4 propionic-acid leave one odor out, b, r=8 7 6 0.4952 3.168 -0.3796 0.7136
G4 propionic-acid leave one odor out, a, r=5 7 6 1.704 1.136 0.0802 0.938
G4 propionic-acid leave one odor out, a, r=12 7 6 0.4578 0.3958 0.01097 0.9915
G4 propionic-acid leave one odor out, a, r=8, all trials 7 6 0.9168 0.7514 0.02582 0.98
G4 propionic-acid leave one odor out, a, r=8, above chance 7 4 0.9168 -1.512 0.4018 0.7006
G4 propionic-acid in sample, a, r=8 7 6 2.891 0.6724 0.3682 0.7218
G4 propionic-acid in sample, b, r=8 7 6 3.586 3.17 0.05726 0.9557
G4 propionic-acid in sample, a, r=8, above chance 7 4 2.891 -1.63 0.7959 0.4522
G4 propionic-acid Data S2 (reference) 7 6 10.8 4.019 1.349 0.2186
leave one odor out, a, r=8: criterion holds False
leave one odor out, b, r=8: criterion holds False
leave one odor out, a, r=5: criterion holds False
leave one odor out, a, r=12: criterion holds False
leave one odor out, a, r=8, all trials: criterion holds False
leave one odor out, a, r=8, above chance: criterion holds False
in sample, a, r=8: criterion holds False
in sample, b, r=8: criterion holds False
in sample, a, r=8, above chance: criterion holds False
Data S2 (reference): criterion holds True
Result: not reproduced. At the held-out placement (reading a, radius 8) the propionic acid AUC of young ants is negative in G2 (-5.1) and G3 (-4.9) and nearer zero in old ants (-3.3 and -0.83), the stated direction, but neither difference is significant (p = 0.60 and 0.17); no variant meets the criterion. G4 does not differ (0.92 against 0.75, p = 0.98). Data S2's own values give G2 p = 0.0050 and G3 p = 0.030.
figs4d-pgb-hexanone-dynamics¶
In PGb, 3-hexanone responses of old ants have a lower peak but decline more slowly than those of young ants; the AUC over frames 9 to 14 is larger in young ants and over frames 35 to 40 larger in old ants.
rows = []
for v in VARIANTS:
for win in ("pk", "late"):
pa_ = per_ant_auc(["3-hexanone"], v, col=win)
rows.append({"variant": vname(v), "window": "frames 9-14" if win == "pk" else "frames 35-40",
**welch_glom(pa_, [("PGb", "3-hexanone")]).iloc[0].drop(["glomerulus", "odor"]).to_dict()})
s2p = s2x[(s2x.glomerulus == "PGb") & (s2x.odor_ds == "3-hexanone")]
for win, (f0, f1) in (("frames 9-14", (9, 14)), ("frames 35-40", (35, 40))):
d = s2p[(s2p.frame >= f0) & (s2p.frame <= f1)].groupby(["subject", "trial"]).dff.sum().groupby("subject").mean().rename("v").reset_index()
d["cohort"] = np.where(d.subject.map(meta.age_days) < 40, "young", "old"); d["glomerulus"] = "PGb"; d["odor"] = "3-hexanone"
rows.append({"variant": "Data S2 (reference)", "window": win, **welch_glom(d, [("PGb", "3-hexanone")]).iloc[0].drop(["glomerulus", "odor"]).to_dict()})
r4d = pd.DataFrame(rows)
print(r4d.to_string(index=False))
mt = {co: np.mean([traces[k][8][0] for k in traces if k[1] == "PGb" and k[2] == "3-hexanone" and meta.cohort[k[0]] == co], 0) for co in ("young", "old")}
plt.figure(figsize=(6, 3)); [plt.plot(np.arange(1, 41), v, label=k) for k, v in mt.items()]
plt.axvspan(9, 14, color="0.9"); plt.axvspan(35, 40, color="0.9"); plt.xlabel("frame"); plt.ylabel("PGb fold change (a)"); plt.legend(); plt.show()
print("Plotted range:", round(min(v.min() for v in mt.values()), 3), "to", round(max(v.max() for v in mt.values()), 3))
variant window n_young n_old mean_young mean_old t p
leave one odor out, a, r=8 frames 9-14 7 7 9.465 3.47 3.884 0.002594
leave one odor out, a, r=8 frames 35-40 7 7 0.6776 0.9765 -0.4456 0.667
leave one odor out, b, r=8 frames 9-14 7 7 10.22 4.121 3.487 0.006064
leave one odor out, b, r=8 frames 35-40 7 7 1.097 1.434 -0.4653 0.6512
leave one odor out, a, r=5 frames 9-14 7 7 9.342 3.636 3.995 0.002132
leave one odor out, a, r=5 frames 35-40 7 7 0.4989 1.022 -0.8867 0.4023
leave one odor out, a, r=12 frames 9-14 7 7 9.432 3.431 4.408 0.0009062
leave one odor out, a, r=12 frames 35-40 7 7 0.7302 1.058 -0.5015 0.6275
leave one odor out, a, r=8, all trials frames 9-14 7 7 9.465 3.47 3.884 0.002594
leave one odor out, a, r=8, all trials frames 35-40 7 7 0.6776 0.9765 -0.4456 0.667
leave one odor out, a, r=8, above chance frames 9-14 7 5 9.465 3.741 3.716 0.004115
leave one odor out, a, r=8, above chance frames 35-40 7 5 0.6776 0.8999 -0.2831 0.788
in sample, a, r=8 frames 9-14 7 7 10.29 3.496 4.712 0.0005799
in sample, a, r=8 frames 35-40 7 7 0.8112 0.9747 -0.2531 0.805
in sample, b, r=8 frames 9-14 7 7 11.42 4.137 4.717 0.0006696
in sample, b, r=8 frames 35-40 7 7 1.4 1.433 -0.04824 0.9623
in sample, a, r=8, above chance frames 9-14 7 5 10.29 3.779 4.546 0.001071
in sample, a, r=8, above chance frames 35-40 7 5 0.8112 0.8974 -0.1235 0.9052
Data S2 (reference) frames 9-14 7 7 10.23 5.834 3.938 0.001981
Data S2 (reference) frames 35-40 7 7 0.8206 2.753 -3.202 0.009849
Plotted range: -0.137 to 1.874
Result: partially reproduced. At the held-out placement (reading a, radius 8) the PGb response to 3-hexanone over frames 9 to 14 is larger in young ants (9.5 against 3.5, p = 0.0026; every variant p <= 0.0061), but over frames 35 to 40 old ants are not above young ones (0.68 against 0.98, p = 0.67; variants p 0.40 to 0.96). Data S2's own values give both (p = 0.0020 and 0.0098), so the slower decline in old ants is not found in the dandiset's AUCs at the located ROIs.
pg-opposite-modulation¶
In PGb and PGa, responses to alarm pheromones rise with age while responses to general odorants fall or stay absent; no general odorant response rises with age in any focal glomerulus.
res_all30 = run_all([(g, o) for g in GLOMS for o in antal.GENERAL], antal.GENERAL)
for k in res_all30:
v = res_all30[k]
up = v[(v.p < 0.05) & (v.mean_old > v.mean_young)]
pg_down = v[v.glomerulus.isin(["PGb", "PGa"]) & (v.p < 0.05) & (v.mean_young > v.mean_old)]
print(f"{k:>24}: general odorant tests with old significantly higher: {len(up)} of {len(v)}"
+ (f" ({', '.join(up.glomerulus + ' ' + up.odor)})" if len(up) else "")
+ f"; PGb/PGa general odorants declining: {len(pg_down)}")
leave one odor out, a, r=8: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
leave one odor out, b, r=8: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
leave one odor out, a, r=5: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
leave one odor out, a, r=12: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
leave one odor out, a, r=8, all trials: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
leave one odor out, a, r=8, above chance: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 4
in sample, a, r=8: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 2
in sample, b, r=8: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 3
in sample, a, r=8, above chance: general odorant tests with old significantly higher: 0 of 30; PGb/PGa general odorants declining: 3
Data S2 (reference): general odorant tests with old significantly higher: 2 of 30 (G2 propionic-acid, G3 propionic-acid); PGb/PGa general odorants declining: 3
Result: not reproduced. The criterion needs fig3d-pg-stronger-old, which is not reproduced, so the claim is not. The general odorant parts hold: no general odorant response rises with age in any of the six glomeruli (0 of 30 tests under every variant), and PGb and PGa responses to 3-hexanone and isopropanol fall with age (4 of 4 significant at the held-out placement). On Data S2's own values 2 of the 30 tests have old significantly "higher" (G2 and G3 with propionic acid, the negative deflection nearer zero that fig4-propionic-acid-g2-g3 expects), so this criterion and that claim's cannot both hold; the question is recorded for a reader.
print(f"Notebook ran in {(time.time() - started) / 60:.1f} min")
Notebook ran in 35.6 min
Summary¶
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.
| Claim | Data | Outcome |
|---|---|---|
| fig1c-old-faster-alarm | Data S1 | reproduced |
| fig1c-vehicle-single-curve | Data S1 | reproduced |
| fig1b-alarm-stimuli-similar | Data S1 | reproduced |
| figs1-heptanone-separate-curves | Data S1 | reproduced |
| figs1-heptanol-blend-single-curve | Data S1 | reproduced |
| fig2b-gcamp-baseline-unchanged | Brain Image Library | blocked: the download server was not reached |
| fig2d-whole-al-alarm-no-age-effect | dandiset | reproduced |
| figs3b-whole-al-general-decline | dandiset | partially reproduced (ratios) |
| figs3b-whole-al-other-no-age-effect | dandiset | reproduced |
| fig2e-heptanone-breadth-falls | dandiset | blocked: methods underspecified (ROI drawing) |
| fig2-alarm-sparse | dandiset | blocked: methods underspecified (depends on fig2e) |
| figs3c-general-breadth-no-age-effect | dandiset | blocked: methods underspecified (the same automated ROIs) |
| data-s2-glomerulus-link | dandiset and Data S2 | partially reproduced (66% agree; 41% on held-out trials) |
| figs2a-mean-six-glomeruli-no-age-effect | located ROIs | reproduced, resting on assumptions |
| fig3b-g2-heptanone-weaker-old | located ROIs | not reproduced |
| fig3d-pg-stronger-old | located ROIs | not reproduced |
| fig3d-pg-60-vs-90-days-same | located ROIs, DANDI 000467, Data S2 | blocked: methods underspecified (the readings disagree; 90-day PGa placements at chance; reading b mixes two pipelines) |
| figs4a-general-odorants-in-focal-glomeruli | located ROIs | not reproduced |
| fig4-general-odorants-decline | located ROIs | partially reproduced, resting on assumptions (not under the other variants) |
| fig4-propionic-acid-g2-g3 | located ROIs | not reproduced |
| figs4d-pgb-hexanone-dynamics | located ROIs | partially reproduced |
| pg-opposite-modulation | located ROIs | not reproduced |
How the analysis differs from the paper's. Frames are aligned by whole-pixel phase correlation, not the ImageJ Image Stabilizer; the glomerulus traces use one set of shifts per trial, from the middle plane. The AL outline is found by a threshold, not drawn. Responding ROIs are found by the claims file's automated rule, at three glomerulus sizes, not drawn. The focal glomeruli are placed where the dandiset's per-trial responses best follow Data S2's, with a disk ROI of 8 pixels radius; the paper drew them by hand. The main measure reads each odor's AUCs where the glomerulus was placed from the other odors' trials, so that the tested values did not choose the place; the placement from all trials is shown as a variant. Fold change follows reading (a) (ROI mean, then fold change), with reading (b) shown, leaving out pixels whose baseline is at background level. Each ant's value is the mean over the trials Data S2 holds for that glomerulus (all trials are shown as a variant).
Pattern of the results. Everything the paper's own Data S1 and Data S2 values support was recomputed above as a reference: the behaviour claims match Table S1 exactly, and Data S2's traces give every focal glomerulus claim as the paper states it, except pg-opposite-modulation, whose criterion fails on Data S2 (2 of 30 tests, G2 and G3 with propionic acid; asked of a reader). The whole-AL measures, which here use a threshold outline in place of the paper's hand-drawn one, agree with the paper in direction and significance; the ratios of Figure S3B are not reached, and the measure has a negative offset of about -0.2. Every young-ant glomerulus placement is above chance, but 15 of 39 old-ant placements are not, and on held-out trials only 9 of 52 old-ant triples agree with Data S2. The focal glomerulus claims that rest on old ants' alarm responses (Figure 3B and 3D) fail, and the AUCs measured here for old ants' PGb are well below Data S2's in both this dandiset and DANDI 000467; so these outcomes say more about the located ROIs than about the paper. Of the ten declines of general odorant responses the paper reports, 5 to 9 are significant here, depending on the placement and ROI. Where old-ant ROIs miss the glomerulus, old values are read too low, which favours the claims that old responses are lower (fig4) and works against those that they are higher (fig3d, figs4d).
What was found about the dataset. It holds the calcium imaging of all 21 ants the paper describes, 22 trials each (with the paraffin oil control), and its ants and ages match Data S2. It holds no ROI positions, glomerulus identities, stimulus times (only a description string) or responding-ROI counts, so the focal glomerulus and ROI count analyses cannot be repeated from it alone. Its timing metadata disagree (1.21 Hz series rate and a stored imaging rate of 0.83 against 1.2 s per volume in the description). One trial misspells ethylpyrazine in its file names. Raw fluorescence is stored as float64, eight bytes per value, in 1.28 TB. The behaviour data are only in the paper's supplement, the confocal stacks in the Brain Image Library, and the 90-day ants in DANDI 000467.