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.

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

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

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

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

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

In [6]:
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"])))
In [7]:
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
No description has been provided for this image
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).

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

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

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

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

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

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.

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

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

In [16]:
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
No description has been provided for this image
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).

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

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

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

In [20]:
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
In [21]:
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
No description has been provided for this image
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.

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

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

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

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

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

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

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

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

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

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

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

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

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