Statistics guides for lab researchers
Choose the right method for your data, report the result correctly, and answer the reviewer — with cited evidence and examples you can rerun.
Design, replicates and sample size
Decide what counts as one observation and how many you need.
- Choose a methodWhat counts as n? Biological replicates, technical replicates and the experimental unitYour n is the number of units independently given a treatment — mice, cages, litters or independent experiments — not the cells or wells measured inside one treated unit. Find your unit and handle the extra measurements.
- Choose a methodChange from baseline or ANCOVA? Analysing before-and-after data with a control groupWith randomised groups measured before and after, analyse the final value with the baseline as a covariate (ANCOVA): it is unbiased and has the most power. Change scores are a weaker alternative; avoid percent change. Python and R code.
- Choose a methodHow many biological replicates do you need?Work out n per group from the smallest difference worth detecting and the variability between animals or cultures. Tables, pilot-study pitfalls, the resource equation and RNA-seq numbers.
- Choose a methodUnequal group sizes: can you still run a t-test or ANOVA?Yes, for independent groups and a continuous outcome: use Welch's t-test or Welch's ANOVA, which do not assume equal spread. Do not drop animals to balance the groups. It covers what imbalance costs in power and unbalanced two-factor designs.
- Choose a methodHow to randomise and blind an animal experiment: a practical method with codeGenerate the allocation with a computer, within blocks such as cages or litters, before the experiment; hide group identity behind codes until the analysis is fixed. Picking animals by hand biases results. Python and R code to make the allocation.
- Choose a methodSex as a biological variable: how to analyse male and female animals, and when you can claim a sex differenceAnalyse male and female animals together in one model with treatment and sex as factors. A result significant in one sex but not the other is not evidence of a sex difference; test the interaction. Python and R code.
- Choose a methodAdding more animals after seeing p = 0.07: why it inflates false positives, and how to plan interim looksAdding animals because p was just above 0.05 raises the false-positive rate. Fix the sample size in advance, or plan a group sequential design with adjusted thresholds. If you already added animals, report both looks. Python and R code.
- Choose a methodSample size for more than two groups: power the comparison you will actually reportFor three or more groups, base the sample size on the comparisons you will report, such as each dose against control with an adjusted α, not the overall ANOVA. The resource equation suits exploratory studies. Python and R code.
- Choose a methodNovel object recognition and preference indices: testing against chance and comparing groupsTest a discrimination index against chance only to show that a group remembers; to show that groups differ, compare the groups directly. Set the exploration exclusion rule in advance and report total exploration. Python and R code.
- Choose a methodLitter effects in rodent studies: pups within litters, litter as the unit and litter sizeWhen dams receive the treatment, analyse litters, not pups: litter means or a mixed model with planned covariates (litter size only if treatment cannot change it). In simulation, pups as n gave 44% false positives. Python and R code.
Choosing a test
Start from your design and data, not from a test name.
- Choose a methodShould you test for normality before a t-test or ANOVA?No — not as a gate for choosing the test. Choose the test from your design and measurement in advance, then look at the residuals. Why, with simulations and a worked example in R and Python.
- Choose a methodWhen can you exclude an outlier? A rule, a check and what to writeExclude a value only for a documented reason that does not depend on the value itself, decided before you look at the group comparison. Otherwise keep it, use a robust analysis and report both versions.
- Choose a methodOne-tailed vs two-tailed test: which to use, and what switching costsUse a two-sided test unless you recorded the direction before the data and would treat an opposite result as no effect. Worked example, simulation, sample sizes and templates.
Comparing two groups
Welch, Student, paired and rank-based comparisons of two groups.
- Start here · Choose a methodWelch's t-test vs Student's t-test: which should you use?Use Welch's t-test by default to compare two independent groups. Here is why, how to check which test your software or AI assistant ran, and how to report it.
- Choose a methodPaired vs unpaired t-test: which one fits your experiment?Use a paired t-test when each value in one group has a partner in the other — the same animal measured twice, or matched samples. Here is how to tell, and what choosing wrongly does.
- Choose a methodMann–Whitney U test vs t-test: which should you use?The t-test compares means; the Mann–Whitney test asks whether one group tends to have larger values. Choose by the question you need answered, not by a normality test.
- Choose a methodCan you run a t-test with n = 3?Yes, a t-test works with three per group if each is an independent replicate — but it detects only large effects. Here is what n = 3 can tell you and how to use it well.
- Report a resultHow to report a t-test: templates for Welch, Student and paired testsCopy-ready sentences for reporting Welch's, Student's and paired t-tests in a paper, with what each number means and the details reviewers check first.
- Choose a methodHistopathology score statistics: ordinal scores, blinded scoring, fields within animals and which testCombine field or section scores into one score per animal, score blind, and compare animals with a Mann–Whitney test (or a t-test on mean field scores). Fields as n gave 41% false positives in simulation. Python and R code.
Comparing three or more groups
ANOVA and its alternatives, including two-way designs.
- Interpret a resultANOVA is significant but no post hoc test is: what it means and what to doA significant F-test with no significant pairwise comparison is not an error. The two tests ask different questions. Here is how to decide what to report, with a worked example in R and Python.
- Choose a methodWelch's ANOVA vs one-way ANOVA: which to use for three or more groupsUse Welch's ANOVA by default to compare the means of three or more independent groups, then Games–Howell for pairs. Why, with a worked example in R and Python, a simulation and the software defaults that differ.
- Choose a methodTwo-way ANOVA vs separate t-tests: does the effect differ between groups?Significant in wild type, not in knockout? That is not evidence the treatment works differently. Test the interaction in a two-way ANOVA, with a worked example in R and Python.
- Choose a methodANOVA assumptions not met: what to do for each oneWhich ANOVA assumption failed decides the fix: fix the unit, use Welch's ANOVA, analyse logs, fit a count or proportion model, or use a robust or rank test. Checks and code in R and Python.
- Choose a methodKruskal–Wallis vs one-way ANOVA: which to use, and what each tells youKruskal–Wallis asks whether some groups rank higher than others; ANOVA compares means. Choose by your question and scale, follow up with Dunn's test, and know where each breaks.
- Choose a methodTest for trend across doses: when one trend test beats comparing each dose with controlIf you expect the response to change steadily with dose, test the trend once with a linear contrast or a regression on dose, decided in advance. Comparing each dose with control has less power. Python and R code.
Multiple comparisons
Which correction to use and how to explain it.
- Choose a methodWhich multiple comparison correction should I use?Pick the correction from the question you are asking: Tukey for all pairs, Dunnett for each group against one control, Holm for a set of separate tests, Benjamini–Hochberg for screens. Worked example in R and Python.
- Choose a methodElevated plus maze and open field statistics: many anxiety measures, locomotion and one primary outcomeChoose one primary anxiety outcome (or a composite) in advance, check locomotion, and correct for the rest. In simulations of 10 vs 10 mice with no effect, reporting any of five measures gave 14% false positives. Python and R code.
Nested measurements
Cells, slices, fields and replicates within animals or cultures, and mixed models.
- Choose a methodNested data (cells within mice): average per animal or fit a mixed model?Several cells, wells or slices per animal? Compare groups with one mean per animal, or with a mixed model that has a random intercept for each animal. Worked example in R and Python.
- Choose a methodColocalisation analysis: Pearson or Manders coefficient, and why n is not the number of cellsPearson's coefficient measures whether two signals rise and fall together; Manders' coefficients measure how much of each signal overlaps the other. Compare conditions using one value per independent experiment, not per cell. Python and R code.
- Choose a methodSingle-cell RNA-seq differential expression between conditions: pseudobulk, not cells as replicatesTo compare conditions in single-cell RNA-seq, sum counts per sample within each cell type and test samples, not cells; a cell-level Wilcoxon test finds dozens of false positives. Plan enough animals. Python and R code.
- Choose a methodCalcium imaging analysis: ΔF/F, neurons within animals and selecting responsive cellsCompare groups with the animal as n (or a mixed model), not hundreds of neurons; select responsive cells on separate trials from those you measure; and fix the ΔF/F baseline in advance. Python and R code.
- Choose a methodiPSC experiments: donors, clones and differentiations, and what counts as nIn patient-versus-control iPSC studies the donor is the unit: average clones and differentiations per donor, or, with more donors, model them as nested random effects. Gene-corrected (isogenic) lines give paired comparisons with more power. Python and R code.
- Choose a methodPatch-clamp statistics: cells, animals, pooled events and paired drug applicationsCompare genotypes with the animal as n, not cells or pooled events: a Kolmogorov–Smirnov test on pooled mEPSCs finds differences that do not exist. Analyse drug applications in the same cell as paired. Python and R code.
- Choose a methodSholl analysis statistics: intersections per radius, neurons within animals and testing every radiusCompare Sholl profiles with one value per animal (total intersections averaged over its neurons) or a mixed model. Testing every radius with neurons as n gave false positives in 69% of simulated studies. Python and R code.
- Choose a methodOrganoid size statistics: organoids, wells and batches as n, log-scale sizes and size thresholdsAverage log organoid size per well and per differentiation batch, then compare conditions per batch. In simulation, organoids as n gave 23% false positives, and dropping small organoids made a 20% shrinkage look like 15%. Python and R code.
- Choose a methodImmunofluorescence intensity statistics: CTCF, background, cells within images and experiments as nSubtract background per cell (CTCF), image with identical settings, average cells per image and images per experiment, and compare conditions per experiment. Cells as n gave 22.6% false positives in a simulated design. Python and R code.
- Choose a methodDendritic spine density statistics: spines per 10 µm, segments within neurons within animals, and count modelsCompare spine density per animal: total spines ÷ total dendrite length for each animal's neurons, animals as n. In a simulation with no real effect, segments as n gave 26% false positives. Python and R code.
- Choose a methodLTP field recording statistics: fEPSP slope as percent of baseline, slices within animals and the time courseExpress fEPSP slope as percent of baseline, average over a pre-set late window for each animal, and compare animals. In simulation, testing every minute with slices as n found a false effect in 28% of studies. Python and R code.
Repeated measurements over time
Time courses, growth curves and repeated scores on the same animals or cultures.
- Choose a methodRepeated measures ANOVA vs mixed model: which to use for measurements over timeRepeated measures ANOVA and a mixed model agree on complete data; when a value is missing for a reason unrelated to the value itself, use the mixed model. R code for both, and a Python per-animal summary route.
- Choose a methodGlucose tolerance test statistics: total AUC, incremental AUC or the time course?Decide the question first. For overall glucose handling, compare one area under the curve per mouse with Welch's t-test; if fasting glucose may differ, plan to add the incremental AUC. Skip per-time-point t-tests. R and Python code.
- Choose a methodTumour growth curves: how to compare treatment groups when mice reach the humane endpointCompare tumour growth rates on the log scale using every measurement up to culling, with a mixed model or per-mouse slopes. Comparing only the mice still measured on the last day underestimates the effect. Python and R code.
- Choose a methodClinical scores over time (EAE, arthritis): how to compare groups without a test on every daySummarise each animal's daily clinical scores into measures chosen in advance, such as cumulative score, maximum score, incidence and day of onset, and compare those. A test on every day inflates false positives. Python and R code.
- Choose a methodCell proliferation and doubling time: fitting the growth phase and comparing cell linesEstimate doubling time from the slope of log cell number over the exponential phase, before cells approach confluence. Compare cell lines on growth rates from independent experiments, not on wells at one time point. Python and R code.
- Choose a methodFear conditioning freezing statistics: per-tone t-tests, extinction curves and what counts as nAnalyse percent freezing with one value per mouse: a mean or an extinction slope per mouse, or a repeated-measures model with a sphericity correction. A t-test at every tone, or tones as n, gives false positives. Python and R code.
Counts, proportions and tables
Chi-square, Fisher and models for counts and percentages.
- Choose a methodChi-square vs Fisher's exact test: which to use for a 2 × 2 tableFor counts from independent animals, use the chi-square test without Yates' correction when every expected count is at least 5, and an exact test otherwise. Worked example in R and Python, with the defaults that differ.
- Choose a methodComparing percentages between groups: % positive cells, % survival per litter, % correct trialsPositive cells out of cells counted, per animal? Compare animals, not pooled cells: Welch's t-test on each animal's percentage, or a quasi-binomial model.
- Choose a methodStatistical tests for count data: Poisson, quasi-Poisson or negative binomial?For counts per animal (lesions, colonies, foci), fit a count model with an offset for area or time; with small groups, take the p-value from quasi-Poisson. R and Python.
- Choose a methodMendelian ratios in mouse crosses: genotype counts, goodness of fit and partial lethalityTest pup genotype counts from a heterozygous cross against 1:2:1, report knockout viability with a confidence interval, and plan enough pups: in simulation, losing a quarter of knockouts went undetected in most studies of 160 pups.
Normalised data and fold change
Ratios to control, fold change, ΔΔCt and assay readouts expressed relative to a control.
- Choose a methodStatistics on fold change and normalised data (% of control, ratio to control)Analyse fold changes on the log scale, paired by experiment when each blot or plate has its own control, instead of setting controls to 1 and testing against them. Worked Western blot example in R and Python.
- Choose a methodqPCR statistics: test ΔCt values, not 2^−ΔΔCt fold changesRun the t-test on ΔCt values, one per biological sample, then report the 2^−ΔΔCt fold change with a back-transformed confidence interval. Worked example, simulation and code in R and Python.
- Choose a methodNormalising to body weight: organ-to-body-weight ratio or ANCOVA?A ratio removes body size only when the organ grows in proportion to the body. Otherwise use body weight as a covariate (ANCOVA on the log scale). Worked example where the ratio finds a false effect, in R and Python.
- Choose a methodSeahorse respiration data: normalising OCR, mito stress test parameters and plates as replicatesNormalise OCR to cell number (or protein or DNA) per well, subtract non-mitochondrial respiration, and compare genotypes with independent plates as n, on the log scale. Wells on one plate are technical replicates. Python and R code.
- Choose a methodScratch wound-healing assay statistics: migration speed, percent closure and experiments as nMeasure migration speed from the slope of wound width over time rather than percent closure at one time, which depends on the starting width; average fields and wells, and compare conditions with independent experiments as n. Python and R code.
- Choose a methodTranswell migration and invasion assay statistics: fields, inserts, percent of control and what counts as nAverage fields within inserts and inserts within experiments, then compare conditions as a log ratio per experiment. Fields as n, or a Student's t-test against a control fixed at 100%, give false positives. Python and R code.
- Choose a methodChIP-qPCR statistics: percent input, fold enrichment over IgG and what counts as nCalculate percent input using the input dilution, compare conditions as a log ratio per independent chromatin preparation, and use IgG as a background check rather than the denominator. Python and R code with a simulation.
- Choose a methodDual-luciferase reporter assay statistics: firefly/Renilla ratios, transfections as n and a control reporter that movesNormalise firefly to Renilla per well, average wells per transfection, and compare conditions as a log2 ratio per transfection. Wells as n gave 19% false positives in simulation; check that Renilla is unaffected. Python and R code.
- Choose a methodCell viability as percent of control: replicate wells, independent experiments and normalisationAnalyse MTT or CellTiter-Glo viability with independent experiments as n: blank-subtract, average wells, express treated as percent or log ratio of vehicle. In simulation, wells as n gave 39% false positives. Python and R code.
Standard curves, detection limits and omics
ELISA curves, values below the detection limit, flow cytometry, CFU, sequencing and proteomics data.
- Choose a methodELISA standard curve: fitting a 4PL curve, reading off concentrations, and values outside the rangeFit an ELISA standard curve with a four-parameter logistic (4PL) model, weighted for the larger scatter at high signal, read concentrations only inside the range of the standards that pass, and average replicate wells first. Python and R code.
- Choose a methodValues below the detection limit: why LOD/2 is a shortcut, and how to analyse non-detectsA value below the limit of detection is censored, known only to be lower than the limit. Count them, then use a censored-data model, or a rank test when all share one limit, instead of LOD/2. Python and R code.
- Choose a methodComparing flow cytometry MFI between groups: median fluorescence, log scale, staining day and the mouse as nSummarise each mouse by its median (or geometric mean) fluorescence, compare groups on the log scale with the mouse as n, and block by staining day when MFI shifts between staining days. Events are not replicates. Python and R code.
- Choose a methodCFU counts and bacterial burden: log10 scale, dilution series, limit of detection and mice as nConvert colonies to CFU per organ, analyse log10 burden with mice as n, and record mice with no colonies on the least dilute plate as below the detection limit. Python and R code with a simulation.
- Choose a methodMicrobiome relative abundance in mouse studies: compositional data, cage effects and the false discovery rateCompare taxa between groups on a log-ratio (CLR) scale, control the false discovery rate across taxa, and treat the cage, not the mouse, as the unit when groups are caged separately. Python and R code.
- Choose a methodReading RNA-seq differential expression results: padj, fold-change cutoffs, NA values and batchUse the adjusted p-value (padj), not the raw p-value; a fold-change cutoff applied afterwards describes genes but does not test a 2-fold claim; test against a threshold when the size of the change matters. Python and R code.
- Choose a methodMissing values in label-free proteomics: missing not at random, imputation and filteringMany missing intensities in label-free proteomics are low-abundance values, not random gaps. Filter proteins by valid values per group and test observed values, or use a method that models missingness; constant imputation changes the results. Python and R code.
Correlation, regression and survival
Relationships between variables and time-to-event data.
- Choose a methodLog-rank test vs Cox regression: which to use for survival dataUse the log-rank test to compare whole survival curves between groups and Cox regression for a hazard ratio or covariates. Check proportional hazards and code humane endpoints as events.
- Choose a methodPearson vs Spearman correlation: which to use, and how to report itChoose Pearson or Spearman from the scatterplot and your design, report the coefficient with a 95% confidence interval, and handle several measurements per animal. Worked examples in R and Python.
- Choose a methodHow to compare IC50 values statistically: pIC50, independent experiments and the extra-sum-of-squares F testFit one curve per compound in each independent experiment, convert each IC50 to pIC50, and compare the pIC50s with a t-test; report the IC50 ratio with its 95% confidence interval. Pooling wells across experiments overstates certainty. R and Python code.
- Choose a methodCorrelation or linear regression? The slope, R², and comparing slopes between groupsUse correlation to describe how closely two measurements move together; use regression when you need the slope, such as change per week or per dose. To compare groups, test the difference in slopes. R and Python code.
- Choose a methodROC curve and AUC for a biomarker: what the AUC means, its confidence interval, and choosing a cut-offReport the AUC with its 95% confidence interval and the numbers of diseased and healthy animals. A cut-off chosen on the same data looks too good. Compare two markers on the same animals with DeLong's test. Python and R code.
- Choose a methodLatency with a cut-off time: how to analyse trials that end before the animal respondsA latency recorded at the cut-off is censored, not a real value. Averaging it biases means and group differences. Compare groups with survival methods, report how many trials reached the cut-off, and keep trial-level data. Python and R code.
- Choose a methodAgreement between raters for blinded scoring: Cohen's kappa, weighted kappa and the ICCFor two raters' categories use Cohen's kappa, for ordinal grades a weighted kappa, for measurements the intraclass correlation (ICC) for absolute agreement. Correlation is not agreement. Report the estimate with its confidence interval. Python and R code.
- Choose a methodCircadian rhythm analysis with the cosinor method: mesor, amplitude, peak time and comparing groupsFit a 24-hour cosine to time-of-day data to estimate mesor, amplitude and peak time with confidence intervals, test for a rhythm, and compare groups in one model instead of a t-test at every time point. Python and R code.
- Choose a methodMichaelis–Menten kinetics: fitting Km and Vmax by nonlinear regression and comparing Km between enzymesEstimate Km and Vmax by nonlinear regression of rate on substrate, not from a Lineweaver–Burk line, and compare Km between enzymes or conditions with one estimate per independent experiment. Python and R code.
- Choose a methodDrug combination synergy: Bliss independence, HSA and Loewe, and how to test synergy across experimentsName the reference model before testing, compute the log Bliss ratio in each independent experiment, and test it across experiments; one plate's wells and the highest single agent (HSA) rule both overstate synergy. Python and R code.
- Choose a methodSaturation binding: Kd and Bmax by nonlinear regression, nonspecific binding and comparing conditionsFit specific binding (total minus nonspecific) with a one-site model by nonlinear regression in each experiment, check ligand depletion, and compare Kd as pKd and Bmax as a ratio across independent experiments. Python and R code.
- Choose a methodClonogenic survival assay statistics: plating efficiency, linear-quadratic fits and comparing survival curvesNormalise colony counts by each condition's plating efficiency, fit the linear-quadratic model per experiment, and compare SF2 or the dose enhancement ratio across independent experiments; pooling dishes in a Poisson model gives false positives. Python and R code.
- Choose a methodLifespan assay statistics for worms and flies: log-rank tests, plates and vials, censoring and replicate trialsWorms 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.
- Choose a methodMyelin g-ratio statistics: axons within animals, axon diameter and comparing myelinationCompare g-ratios with one value per animal, not hundreds of axons. Adjust for axon size from each animal's own fibre-versus-axon diameter line, because smaller axons alone can change the mean g-ratio. Python and R code.
Interpreting results
What p-values, intervals and effect sizes do and do not tell you.
- Interpret a resultp = 0.06 or “not significant”: what it means and what to writeA p-value above 0.05 is not evidence of no effect. Read the confidence interval, write the estimate with it, and use an equivalence test if you need to claim no meaningful difference.
- Interpret a resultHow to interpret a 95% confidence interval (and what to write)A 95% confidence interval is the range of effect sizes compatible with your data under the model. Read it by asking which effects it makes implausible, then write the estimate with its interval.
- Interpret a resultCohen's d vs Hedges' g: which to report, and how much they differHedges' g is Cohen's d multiplied by a small-sample correction J. With lab-sized groups in a single study, report g; a table of J by group size, one shared confidence interval, and Python and R code.
Writing and reporting
Methods sections, results sentences and figures.
- Report a resultSuperPlots: show cells and biological replicates in one figure (R and Python code)Plot every cell coloured by experiment, overlay each experiment's mean, and test the experiment means — with a worked example, figure legend, Methods sentence and R and Python code.
- Report a resultHow to report ANOVA results: templates for Welch's, classical and two-way ANOVACopy-ready sentences for reporting Welch's and classical one-way ANOVA, post hoc comparisons and two-way interactions, with where each number sits in R and Python output.
- Report a resultHow to write the statistical methods section: a fill-in template for animal and cell studiesA fill-in statistical analysis paragraph in the order reviewers check it, mapped to ARRIVE 2.0, SAMPL and the Nature reporting summary, with a filled-in mouse example and code for software versions.
- Report a resultSD vs SEM error bars: which to show, and what to write in the legendShow the data points, then pick the bar by purpose: SD for spread, a 95% CI for the precision of a mean. Why SEM bars mislead at small n, overlap rules, legend templates and Python/R code.
- Report a resultHow to report a mixed model: what to write in the Methods and Results, with a templateReport a mixed model's formula, random effects, estimation method, degrees-of-freedom method and software; then the fixed effect with its 95% CI, the random-effect SDs and the numbers at each level. Python and R code.
Responding to reviewers
What a statistical comment is checking and how to answer it.
- Respond to a reviewerReviewer asks you to correct for multiple comparisons: how to respondWork out which of three situations you are in, then use the matching reply, Methods sentence and code. Includes a worked example showing how five p-values change under Holm's correction.
- Respond to a reviewerReviewer asks you to justify your sample size: how to respondWork out which of three situations you are in, then use the matching reply and Methods sentence. Includes a sensitivity analysis you can run now and why observed power will not help.
- Respond to a reviewerReviewer asks for effect sizes: what to report and how to replyAdd the effect in your own units with its 95% confidence interval first, and a standardised effect only where units mean little. Per-design table, R and Python code, and reply templates.
- Respond to a reviewerReviewer asks why you did not use a non-parametric test: how to respondDecide which of three situations you are in — a measurement analysed with Welch's test, ordinal or outlier-driven data, or three animals per group — then use the matching reply, Methods sentence and code.
- Respond to a reviewerReviewer says your n is cells, not animals: how to respondDecide whether the reviewer is right about your design, re-run the test with the animal or experiment as the unit, and reply with a copy-ready paragraph — including what to write if the result is no longer significant.
Using AI for statistics
Checking what an AI assistant chose and ran.
- Interpret a resultHow to check an AI assistant's statistical analysis: a 15-minute checklistSeven checks to run before an AI-produced analysis goes into your paper: the unit, the test, the software defaults that change answers, a re-run, a recomputed number, the references and the claim.
- Report a resultHow to disclose your use of AI in a statistical analysisName the tool and date, say what it did and what you checked, and put it where your journal asks. Current ICMJE, Nature Portfolio and Elsevier wording, fill-in templates and a log to keep.