Correlation, regression and survival · Choose a method

Lifespan assay statistics for worms and flies: log-rank tests, plates and vials, censoring and replicate trials

Worms on one plate or flies in one vial are not independent, so a pooled log-rank test can give false positives. Use plates or trials as units, or a Cox model with plate effects. Python and R code.

By Licklider · Published · Last reviewed · How this page was made · Markdown

On this page
  1. Which analysis for your question
  2. Worked example: a treatment with no real effect
  3. Before you analyse
  4. Check what an AI assistant ran
  5. Report it
  6. References

Quick answer. In a worm or fly lifespan assay, animals on one plate (or in one vial) share food, temperature and handling, and animals in one trial share a start date, so they are not independent. Show Kaplan–Meier curves and medians for each trial, and test the treatment with an analysis whose units are plates or trials. In R, use a Cox model with a random effect for each plate, stratified by trial (its z-test is optimistic with few plates), with plate medians on the log scale (trial as a block) as a check; in Python, use the plate medians, and add the log ratio of trial medians, which is valid but weak with 3 trials. These approximate analyses assume the treatment effect is similar in every trial; show the per-trial results so readers can judge. Censor animals that crawl off, burrow, bag or are lost on the day they were last seen alive; this assumes the treatment does not change how often animals are lost. In a simulation of 3 trials with 3 plates of 35 worms per condition, built so that plates and trials account for about 7% and 11% of the variation (close to the published estimates), a log-rank test on all worms pooled gave false positives in 46.2% of studies where the treatment did nothing; plate medians gave 8.2% (slightly above the nominal 5%) and trial medians 4.3%; the Cox model with plate effects was not simulated.

Terms used below. Lifespan assay: following a cohort of animals from a start age until death, scoring survivors daily or every few days. Kaplan–Meier (KM) curve: the proportion still alive over time, allowing for censored animals; the median lifespan is where it crosses 50%. Censoring: recording an animal lost for a reason other than ageing (crawling off the plate, burrowing, bagging, injury) as alive until the last day it was seen, so that it contributes those days; this is unbiased only if the reason for loss is unrelated to the treatment and to how long the animal would have lived. Day: here day 1 is the first day of adulthood. Bagging: matricidal hatching, larvae hatching inside the mother. Log-rank test: compares survival curves, treating every animal as independent. Cox model: a regression for survival; the hazard ratio compares death rates (below 1 means longer life). Random effect (frailty): an extra term that lets each plate have its own baseline risk. Stratified by trial: comparing conditions within each trial only. Trial as a block: each trial gets its own baseline in the model, so conditions are compared within trials. Liberal / optimistic test: one whose p-values are too small, giving more than 5% false positives. Gompertz distribution: a standard ageing model in which the death rate rises exponentially with age. FUdR: fluorodeoxyuridine, used to stop worms producing progeny. Trial: an independent repeat with its own cohort and start date. 95% confidence interval (CI): the range of values compatible with the data. Power: the chance of detecting an effect that is really there. False positive: a “significant” result when there is no real effect.

Which analysis for your question

Question Analysis Report
Does a treatment change lifespan? KM curves and medians per trial; Cox model with a plate random effect stratified by trial (R, coxme; not checked in our simulation, and its z-test is optimistic with few plates), or plate medians (log) with trial as a block (slightly liberal when the effect varies between trials) Hazard ratio or median ratio with 95% CI; animals, plates and trials
One trial, several plates Plate medians on the log scale compared between conditions (Welch t-test), or the Cox model with plate effects; the result applies to that one cohort, so repeat the trial Ratio with 95% CI, n plates; state one trial
Plate identity not recorded Plates cannot be modelled; report KM curves and medians per trial, and the log ratio of trial medians Medians per trial; say plates were not recorded
Only a few trials, many animals Log ratio of trial medians, one-sample t-test (weak with 3 trials), alongside the per-trial curves Ratio with 95% CI
How consistent are the trials? The treatment effect in each trial, shown separately Median and n per trial and condition
Flies in vials The same, with the vial in place of the plate As above
How many animals? Petrascheck and Miller: about 150 animals per group detect a true 12% increase in at least 4 of 5 experiments when animals are independent; with plate effects, plan plates and trials too (in our simulation 315 worms per group on 9 plates gave only 58.5% power with plate medians; we did not simulate a design reaching 80%, so adapt simulation.py to your plate and trial numbers) Planned and achieved n

Petrascheck and Miller note that lifespan is “commonly tested in lifespan experiments by comparing Kaplan-Meier survival estimates and log-rank tests”, warn that “an n of 100 is not large enough” to reliably detect increases of 8 to 16%, that detecting “a true 12% increase in lifespan at a significance level α = 0.05 in at least 4 out of 5 experiments (>80%) requires 150 animals for each cohort”, and that “pooling real-life data is highly problematic due to the variability between experiments” (Petrascheck and Miller 2017). In the Caenorhabditis Intervention Testing Program, each trial started “with ≥105 adults distributed over three technical replicate plates”; differences among trials and among plates within trials made up “roughly 15% of the total variation in individual lifespan”, and the authors conclude that “it is critical to assess the lifespans of discrete cohorts in repeat studies” (Lucanic et al. 2017). On censoring, Sutphin and Kaeberlein note that worms on solid media “will occasionally ‘flee’” (Sutphin and Kaeberlein 2009), and Amrit and colleagues list lost, burrowed, bagged and protruding-vulva worms as animals to censor (Amrit et al. 2014); record them as censored on the last day seen alive rather than deleting them.

Worked example: a treatment with no real effect

Constructed data: 3 independent trials, each with 3 plates of 35 worms per condition (630 worms), scored daily. Lifespans follow a Gompertz distribution with a median near 18 days. Trials differ (both conditions shift together), the treatment’s apparent effect varies a little between trials, and plates differ; in variance terms, plates and trials account for about 7% and 11% of the variation in individual lifespan, close to the 6% (plates) and 9% (trials) reported by Lucanic and colleagues. About 10% of worms are censored at a random day. By design, the treatment does not change lifespan. The data’s random seed was the first one tried and was not chosen for its result. Stored code and data: lifespan.csv, model.py, make_data.py, analysis_functions.py, analysis.py, analysis.R and the analysis output; analysis.py and analysis.R print the same output.

Trial Median lifespan, control (days) Median lifespan, treated (days) Worms censored
1 20 19 22
2 19 17 21
3 20 22 18
Analysis, treated vs control Result
A. (Not valid: plates and trials ignored) Log-rank test, all worms pooled (315 vs 315) χ²(1) = 0.22, p = 0.64
B. (Not valid: plates ignored) Log-rank test stratified by trial χ²(1) = 0.04, p = 0.85
C. Plate medians as n (9 vs 9), log scale, trial as a block ratio 1.02 (95% CI 0.92 to 1.13), p = 0.74
D. Trial medians, log ratio per trial (n = 3) ratio 0.98 (95% CI 0.75 to 1.27), p = 0.75
E. Cox model, plate random effect, stratified by trial (mixed_model.R, its output; R only) hazard ratio 1.04 (95% CI 0.73 to 1.47), p = 0.84

With this data set every analysis came out non-significant; the simulation shows how often the pooled tests give a false positive. The interval from the Cox model with plate effects (0.73 to 1.47) is much wider than the pooled log-rank test implies, because it counts plates rather than worms as the source of repeatable differences. Across 2,000 simulated studies of this design (simulation.py, the simulation output):

Scenario A. Log-rank, pooled (invalid) B. Log-rank by trial (invalid) C. Plate medians, trial block D. Trial medians, log ratio per trial
No effect (false positives) 46.2% 46.6% 8.2% 4.3%
12% longer lifespan (power; for A and B a rejection rate, not power) 91.2% 93.0% 58.5% 20.9%

These percentages describe this simulated design (3 trials × 3 plates × 35 worms, the variation in model.py), not every lifespan study; with smaller plate-to-plate differences the pooled tests are less wrong, and with larger ones more. The log-rank test counts 315 worms per condition as independent, but worms on one plate move together, so it found a “difference” in almost half of the null studies; stratifying by trial does not help because the test still ignores plates. Plate medians with trial as a block were close to valid (8.2%, slightly high because the treatment effect also varies between trials), and trial medians were valid but, with 3 trials, weak. The Cox model with plate effects (E) uses every worm and accounts for plates; it was not included in the simulation, and with few plates its z-test can be somewhat optimistic.

The code expects one row per animal with columns trial, condition (control or treated), plate (unique across trials and conditions), day (of death or censoring, day 1 = first day of adulthood) and died (1 = died, 0 = censored, not TRUE/FALSE), with condition labels exactly control and treated. If fewer than half the animals on a plate die before the end of scoring, its median is undefined: Python stops with an error and R drops that plate, so check every plate reached 50% mortality, and keep scoring until it does or report the plate’s survival at the last day instead. Rename your columns and labels to match, and run the code in the folder that holds the CSV file. Python needs NumPy, pandas 2.2 or later and SciPy plus analysis_functions.py; the R code uses the survival package that ships with R, and the Cox model with plate effects needs install.packages("coxme"). In Python, report the plate-median ratio in place of the hazard ratio, and plot Kaplan–Meier curves with a survival package such as lifelines.

import numpy as np, pandas as pd
from scipy import stats
from analysis_functions import km_median                       # Kaplan-Meier median (stored with the guide)

d = pd.read_csv("lifespan.csv")
pl = d.groupby(["trial", "condition", "plate"]).apply(lambda x: km_median(x.day.to_numpy(), x.died.to_numpy()),
                                                      include_groups=False).rename("med").reset_index()
X = np.column_stack([np.ones(len(pl)), pl.condition == "treated", pd.get_dummies(pl.trial, drop_first=True)]).astype(float)
y = np.log(pl.med.to_numpy()); b, *_ = np.linalg.lstsq(X, y, rcond=None); res = y - X @ b; df = len(y) - X.shape[1]
se = np.sqrt(res @ res / df * np.linalg.inv(X.T @ X)[1, 1]); q = stats.t.ppf(0.975, df)
print(f"treated / control {np.exp(b[1]):.2f} (95% CI {np.exp(b[1] - q * se):.2f} to {np.exp(b[1] + q * se):.2f}), p = {2 * stats.t.sf(abs(b[1] / se), df):.2g}")
# treated / control 1.02 (95% CI 0.92 to 1.13), p = 0.74
library(survival)
d <- read.csv("lifespan.csv")
med <- function(x) unname(summary(survfit(Surv(day, died) ~ 1, data = x))$table["median"])
pl <- do.call(rbind, lapply(split(d, d$plate), function(x) data.frame(trial = x$trial[1], condition = x$condition[1], med = med(x))))
summary(lm(log(med) ~ condition + factor(trial), data = pl))$coefficients["conditiontreated", ]   # log ratio 0.02, p = 0.74
library(coxme)                                                   # analysis E: Cox model with plate effects
fit <- coxme(Surv(day, died) ~ condition + strata(trial) + (1 | plate), data = d); fit   # HR = exp(coef) 1.04, p = 0.84
par(mfrow = c(1, 3))                                             # Kaplan-Meier curves, one panel per trial
for (t in 1:3) plot(survfit(Surv(day, died) ~ condition, data = d[d$trial == t, ]), lty = 1:2, main = paste("Trial", t), xlab = "Day of adulthood")

Before you analyse

  1. Plan animals, plates and trials for the effect you care about: about 150 independent animals per group detect a 12% change in 4 of 5 experiments, but with plate effects that is not enough on its own (315 worms on 9 plates per group gave 58.5% power here); use at least 3 independent trials with their own cohorts.
  2. Spread each condition over several plates or vials and record which animal was on which, so plates can be part of the analysis.
  3. Write the censoring rules down in advance (crawled off, burrowed, bagged, injured, lost) and apply them blind to condition; report the number censored per condition, because a treatment that changes bagging or burrowing makes censoring biased.
  4. Score blind and on a fixed schedule, and keep temperature, food and FUdR or other additives the same across conditions.
  5. Show the curves for every trial, and if you pool trials, do it in a model that keeps trials and plates rather than by adding up animals.

Check what an AI assistant ran

  1. The assistant pooled all animals from all trials into one log-rank test. Ask for per-trial curves and an analysis with plates or trials as units.
  2. The assistant stratified the log-rank test by trial and stopped there. Ask how plates were handled.
  3. The assistant dropped censored animals, or counted them as deaths. Ask for censoring at the day they were lost, and whether the treatment could have changed the reasons for loss.
  4. The assistant reported a 10% increase from 100 animals as definitive. Ask for the interval and the planned sample size.

Report it

Lifespan was scored daily in [3] independent trials, each with [3] plates of [35] worms per condition, blind to condition; worms that crawled off, burrowed, bagged or were lost were censored. Kaplan–Meier curves and medians are shown for each trial. The treatment was compared with a mixed-effects Cox model with a random effect for each plate, stratified by trial (R [4.3.3], survival and coxme), and with plate medians on the log scale with trial as a block.

In the constructed example, which has no effect built in, the treatment did not clearly change lifespan (hazard ratio [1.04], 95% CI [0.73] to [1.47]; p = [0.84]; [630] worms on [18] plates in [3] trials); per-trial medians were [20, 19, 20] days for control and [19, 17, 22] for treated.

Replace the bracketed values with your own.

References

  • Petrascheck M, Miller DL (2017). Computational analysis of lifespan experiment reproducibility. Frontiers in Genetics 8:92. doi:10.3389/fgene.2017.00092
  • Lucanic M, Plummer WT, Chen E, et al. (2017). Impact of genetic background and experimental reproducibility on identifying chemical compounds with robust longevity effects. Nature Communications 8:14256. doi:10.1038/ncomms14256
  • Sutphin GL, Kaeberlein M (2009). Measuring Caenorhabditis elegans life span on solid media. Journal of Visualized Experiments (27):1152. doi:10.3791/1152
  • Amrit FRG, Ratnappan R, Keith SA, Ghazi A (2014). The C. elegans lifespan assay toolkit. Methods 68:465–475. doi:10.1016/j.ymeth.2014.04.002
  • Therneau TM. coxme: mixed effects Cox models. R package. CRAN

How this page was made

  • Written and published by Licklider. Recommendations cite peer-reviewed methods papers and software documentation listed under References; cited passages were checked against the source text.
  • The worked example uses constructed data. Its numbers were produced by stored code (Python 3.13 with SciPy 1.18.1; R 4.3.3 with survival 3.5-8), and the two languages print identical output; the optional mixed-effects Cox model uses R with coxme 2.2-22; the simulation was run in Python.
  • Separate review passes checked the statistics, the citations and readability before publication.