# SuperPlots: 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.

Canonical: https://www.licklider.ai/guides/superplots/
Section: Guides › Writing and reporting
Author: Licklider · Published 2026-11-10 · Last reviewed 2026-11-10 (UTC)

> **Quick answer.** Plot every cell, coloured and shaped by the independent experiment it came from; draw each experiment's mean on top; then run the test on those **experiment means**, not on the cells. When every experiment contains both conditions, compare the means **paired by experiment** (a paired t-test with n = number of experiments); if the conditions were run in separate experiments, use Welch's t-test on the means instead. With only two experiments, describe the two per-experiment differences and run a third experiment before testing: a paired test with n = 2 needs t = 12.71 for p < 0.05. This is the SuperPlot approach of Lord et al. (2020). In the example below, 200 cells per condition give p = 4 × 10⁻⁸, while the four experiments give p = 0.19.

> **Terms used below.** *Independent experiment* (biological replicate): a repeat on a separate day, culture or passage; the number of them is n. *Experiment mean*: the average of all cells of one condition in one experiment. *Paired t-test*: compares the two conditions within each experiment, using one difference per experiment. *Welch's t-test*: a two-group test that does not assume equal spreads. *Degrees of freedom (df)*: n − 1 for a paired test on n experiments. *95% CI*: the range of effects compatible with the data. *Mixed model*: a regression that knows which cells came from which experiment; a *random intercept* gives each experiment its own baseline, and a *condition-by-experiment term* lets the drug effect differ between experiments. *Singular fit*: the model could not estimate one of its variance terms.

## What a SuperPlot shows

<img src="/guides/superplots/superplot.svg" alt="SuperPlot of cell speed for control and drug. About 400 small points show individual cells, coloured and shaped by four experiments. Four large outlined markers per condition show each experiment's mean, joined by a line from control to drug. Three experiments rise by less than 1 µm/min; experiment E3 rises by about 3.4. Black bars mark the mean of the four experiment means, 10.4 for control and 11.6 for drug. Title: paired t-test on 4 experiment means, p = 0.19." width="420" height="420" style="max-width:100%;height:auto;border-radius:6px" />

One figure answers two questions a reader asks of cell data: how variable are the cells, and did the effect repeat in every experiment? Lord et al. (2020) built the SuperPlot so that "each cell-level datapoint divulges which experiment it came from", which shows "whether each experimental round gave similar results or if one run biases the conclusion".

| Layer | Shows | In the example |
| --- | --- | --- |
| Small points, coloured by experiment | Cell-to-cell variability | 50 cells per condition in each of 4 experiments |
| Large markers, same colours | One mean per experiment and condition | 8 values: the data the test uses |
| Lines joining each experiment's two means | Whether the effect repeats | Three small rises, one large (E3) |
| Black bar | Mean of the experiment means | 10.41 (control), 11.65 (drug) µm/min |

It replaces the bar graph: in a review of 703 physiology articles, Weissgerber et al. (2015) found that most presented continuous data in bar and line graphs, although "many different data distributions can lead to the same bar or line graph".

Use colour **and** marker shape for the experiments, so the figure still works in greyscale and for colour-blind readers (the example uses the Okabe–Ito palette).

## Why the test uses experiment means

Cells from one experiment share a culture, a day, a reagent batch and an imaging session. They are not independent replicates of the treatment, so counting them as n makes the test answer the wrong question. Lord et al. (2020): "P values and standard error of the mean should be calculated using biological replicates—independent measurements of a population of interest, typically from independent samples or separate experiments."

The example data are constructed: four experiments, 50 cells per condition in each, cell speed in µm/min. The drug adds 0.3–0.7 µm/min on three days and 3.4 on one.

| Analysis | n | Difference (drug − control) | 95% CI | p |
| --- | --- | --- | --- | --- |
| Welch t-test on all cells (wrong unit) | 200 vs 200 cells | 1.24 | 0.80 to 1.67 | 4.3 × 10⁻⁸ |
| Paired t-test on experiment means | 4 experiments | 1.24 | −1.08 to 3.55 | 0.19 |

The averages are identical here because every experiment contributes the same 50 cells, but the cell-level interval uses the wrong unit and is far too narrow: it treats one unusual experiment (E3) as 50 independent confirmations, and in the simulation below such a test raised a false alarm 18.0% of the time with no true average effect. The experiment-level test sees four experiments, three of which barely moved.

How often does the cell-level test raise a false alarm? We simulated the same design with **no average drug effect**, 20,000 times ([simulation code](/guides/superplots/simulation.py), [output](/guides/superplots/simulation_output.json)):

| Situation (true average effect = 0) | Cell-level Welch t-test | Paired t-test on experiment means |
| --- | --- | --- |
| Drug effect varies between experiments (SD 0.5 µm/min) | 18.0% significant | 4.9% significant |
| Only the baseline varies between experiments | 3.7% significant | 5.3% significant |

The first row matters because a response can differ a little from day to day. Aarts et al. (2015) found the same pattern in simulations of clustered designs, with false-positive rates "up to ~20-~50 %" when variation in the experimental effect across clusters is ignored.

**Do this:** compute one mean per experiment and condition, and run the test on those means.

## Pair the means by experiment

When each experiment contains both conditions, experiments differ in their baseline, and a paired test removes it; an unpaired test leaves it in the noise. To show this, a second constructed dataset ([superplot_data_consistent.csv](/guides/superplots/superplot_data_consistent.csv)) has a deliberate baseline shift — E2 runs about 3 µm/min faster than E3 in both conditions — and a drug effect that adds a similar amount in every experiment (0.73 to 1.38 µm/min). Pairing helps this much only when both conditions are in the same experiments and the effect adds a roughly fixed amount:

| Analysis of the four experiment means | Difference | 95% CI | p |
| --- | --- | --- | --- |
| Paired t-test, t(3) = 6.42 | 1.03 | 0.52 to 1.54 | 0.0077 |
| Unpaired Welch t-test | 1.03 | −1.29 to 3.35 | 0.32 |

The paired t-test assumes the experiments are independent and that the drug − control differences are roughly symmetric. With three to six experiments you cannot check that from the data, so judge it from the design: means of many cells are close to normally distributed, and differences on the original scale suit effects that add a fixed amount. If the drug multiplies speed by a factor (the effect is larger on fast days), run the paired test on the logarithms of the means and report the ratio. If control and drug were run in separate experiments, use Welch's t-test on the means instead ([paired vs unpaired](/guides/paired-vs-unpaired-t-test/)).

**Do this:** decide the pairing from the design before you look at the data, and state it in the legend.

## Make it in R and Python

Both snippets read a long-format file with one row per cell and columns `experiment` (E1–E4), `condition` (`control` or `drug`), `cell` and `speed`: download [superplot_data.csv](/guides/superplots/superplot_data.csv). Replace `control` and `drug` in the code with the exact labels in your file. If the experiment label has been lost, recover it from file names, acquisition dates or microscope metadata before you analyse; cells that cannot be assigned to an experiment cannot be used for the test.

```r
library(ggplot2)
d <- read.csv("superplot_data.csv")
reps <- aggregate(speed ~ experiment + condition, data = d, FUN = mean)   # one mean per experiment
w <- reshape(reps, idvar = "experiment", timevar = "condition", direction = "wide")
t.test(w$speed.drug, w$speed.control, paired = TRUE)                     # n = experiments

ggplot(d, aes(condition, speed, colour = experiment, shape = experiment)) +
  geom_point(position = position_jitter(width = 0.25, height = 0, seed = 1), alpha = 0.45) +
  geom_line(data = reps, aes(group = experiment)) +
  geom_point(data = reps, size = 4.5, stroke = 1, colour = "black", aes(fill = experiment)) +
  stat_summary(data = reps, aes(condition, speed), fun = mean, geom = "crossbar",
               width = 0.5, inherit.aes = FALSE) +
  scale_shape_manual(values = c(21, 22, 24, 23)) +
  theme_classic()
```

```python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import stats

d = pd.read_csv("superplot_data.csv")
means = d.groupby(["experiment", "condition"])["speed"].mean().unstack()  # one mean per experiment
res = stats.ttest_rel(means["drug"], means["control"])                     # n = experiments
print(res, res.confidence_interval())

colours = dict(zip(means.index, ["#E69F00", "#56B4E9", "#009E73", "#CC79A7"]))
markers = dict(zip(means.index, ["o", "s", "^", "D"]))
x = {"control": 0, "drug": 1}
rng = np.random.default_rng(1)
fig, ax = plt.subplots(figsize=(4.2, 4.2))
for (e, c), g in d.groupby(["experiment", "condition"]):
    ax.scatter(x[c] + rng.uniform(-0.28, 0.28, len(g)), g["speed"], s=9,
               color=colours[e], marker=markers[e], alpha=0.45, linewidths=0)
for e in means.index:
    ax.plot([0, 1], means.loc[e, ["control", "drug"]], color=colours[e])
    ax.scatter([0, 1], means.loc[e, ["control", "drug"]], s=110, color=colours[e],
               marker=markers[e], edgecolors="black", zorder=3, label=e)
for c in x:
    ax.hlines(means[c].mean(), x[c] - 0.32, x[c] + 0.32, color="black", lw=2, zorder=4)
ax.set_xticks([0, 1], ["Control", "Drug"])
ax.set_ylabel("Cell speed (µm/min)")
ax.legend(title="Experiment", frameon=False)
fig.savefig("superplot.svg")
```

Both give t(3) = 1.70, p = 0.19, mean difference 1.24 µm/min (95% CI −1.08 to 3.55) on the example data (complete scripts: [page_snippet.R](/guides/superplots/page_snippet.R), [page_snippet.py](/guides/superplots/page_snippet.py); checked with R 4.3.3, ggplot2 4.0.3, Python 3.13, SciPy 1.18.1 and matplotlib 3.11). In SciPy 1.18.1, `ttest_rel` is the paired test, while `ttest_ind` is unpaired and assumes equal variances unless you pass `equal_var=False`. With three or more conditions in every experiment, compare each condition with control by paired t-tests on the experiment means and adjust with Holm's method ([which correction to use](/guides/which-multiple-comparison-correction/)).

**Do this:** keep the experiment label in your data file from the start; it is the column every later step needs.

## Write the legend, Methods and Results

Lord et al. (2020): "it is critical to list the number of independent experiments in the figure or caption, as well as how the means and statistical tests were calculated." The Nature Portfolio reporting summary asks for "the exact sample size (n) for each experimental group/condition, given as a discrete number and unit of measurement" and "a statement on whether measurements were taken from distinct samples or whether the same sample was measured repeatedly".

**Figure legend template**

> Cell migration speed after drug or vehicle. Small symbols, individual cells (50 per condition per experiment); large symbols, the mean of each independent experiment (n = 4 experiments, performed on separate days), coloured and shaped by experiment; lines join the two conditions of one experiment; black bars, mean of the experiment means. P value from a two-sided paired t-test on the experiment means (n = 4) [if the conditions were run in separate experiments: Welch's t-test on the experiment means].

**Methods sentence**

> For each independent experiment (n = 4, separate days and cell passages), we averaged the speeds of all tracked cells per condition and compared drug with vehicle by a two-sided paired t-test on the experiment means (R 4.3.3; use Welch's t-test on the means if the conditions were run in separate experiments), so that the experiment, not the cell, was the unit of analysis.

**Results sentence**

> Mean cell speed was 1.24 µm/min higher with the drug (95% CI −1.08 to 3.55; paired t-test on experiment means, t(3) = 1.70, p = 0.19, n = 4 experiments); the increase was 0.3–0.7 µm/min in three experiments and 3.4 µm/min in one.

Report the interval with the p-value: with four experiments the CI is wide, and that width is the honest summary of what the data show ([how to report a t-test](/guides/how-to-report-a-t-test/)). If your result looks like the example — a positive estimate, a CI that includes zero — write the estimate and interval as above and say that the experiments cannot yet distinguish a small effect from none; do not switch to the cell-level test to get a smaller p.

**Do this:** copy the three templates and replace the numbers, the number of experiments and the test with your own.

## With two or three experiments

A paired t-test on experiment means has n − 1 degrees of freedom, so the bar for p < 0.05 falls quickly as experiments are added:

| Experiments | Degrees of freedom | t needed for two-sided p < 0.05 |
| --- | --- | --- |
| 2 | 1 | 12.71 |
| 3 | 2 | 4.30 |
| 4 | 3 | 3.18 |
| 5 | 4 | 2.78 |
| 6 | 5 | 2.57 |

With two experiments, show the SuperPlot and the two per-experiment differences, describe them, and run a third experiment before you make an inferential claim. Lord et al. (2020) note that "increasing the number of biological replicates usually has a larger influence on the statistical power than measuring many more cells in each sample". To plan how many experiments you need, use the [sample size calculator](/tools/sample-size-calculator/) in paired mode with the SD of the per-experiment differences (in the example the four differences are 0.68, 0.34, 3.41 and 0.52 µm/min, SD 1.45) ([how many biological replicates](/guides/how-many-biological-replicates/)).

**Do this:** plan the number of independent experiments, not the number of cells, before you start.

## When the simple mean is not enough

The experiment-mean test gives every experiment the same weight. Lord et al. (2020) advise keeping "the number of observations per sample similar, because each sample gets the same weighting". If the cell counts differ a lot between experiments (say 15 in one, 300 in another), or there is a second level of nesting (cells within coverslips within experiments), a mixed model can weight the data properly — but only with a condition-by-experiment term, `(1 | experiment/condition)` or `(1 + condition | experiment)`; in our simulation a random intercept for experiment alone gave 21.6% false positives, and with four experiments the correct model failed to estimate its variance terms in 31.2% of datasets.

Because both conditions appear in every experiment, the model must let **the drug effect vary between experiments**, not only the baseline. A random intercept for experiment alone treats every cell as independent evidence about the effect. We fitted both models to 1,000 simulated datasets of the design above with no true average effect, the drug effect varying between experiments (SD 0.5 µm/min); seeded R code with lme4 and lmerTest ([code](/guides/superplots/mixed_model_simulation.R), [output](/guides/superplots/mixed_model_simulation_output.txt)):

| Analysis (true average effect = 0) | False positives at p < 0.05 | Singular fits |
| --- | --- | --- |
| `speed ~ condition + (1 \| experiment)` | 21.6% | 1.3% |
| `speed ~ condition + (1 \| experiment/condition)` | 6.8% | 31.2% |
| Paired t-test on experiment means | 5.9% (1,000 runs; 4.9% in the 20,000-run simulation above) | — |

With four experiments, the correctly specified model is no better than the paired test on means and fails to estimate its variance terms in about a third of datasets (a "singular fit"). Aarts et al. (2015) recommend multilevel analysis whenever observations from the same cluster appear in different conditions, and for this design such a model matches the data only with the condition-by-experiment term ([nested data](/guides/nested-data-mixed-model/)).

**Do this:** with similar cell counts and three to six experiments, use the paired test on experiment means. If you need a mixed model, include the condition-by-experiment term — `(1 | experiment/condition)` or `(1 + condition | experiment)` — and report any singular fit.

## No-code option

SuperPlotsOfData is a free, open-source web app for making SuperPlots from a spreadsheet (Goedhart 2021; [huygens.science.uva.nl/SuperPlotsOfData](https://huygens.science.uva.nl/SuperPlotsOfData/)). Upload long-format data with a column for the experiment, and check that its statistics use the experiment means; then report the test as above.

## Check what an AI assistant ran

AI-generated plotting code can run the test on every row of the data file, which in cell data means every cell. Check three lines:

1. **What goes into the test?** The vectors passed to `t.test()` or `ttest_rel()` should have one value per experiment (4 in the example), not one per cell (200). Print their lengths.
2. **Paired or not?** `paired = TRUE` in R or `ttest_rel` in SciPy when each experiment contains both conditions; SciPy's `ttest_ind` is unpaired and assumes equal variances unless you pass `equal_var=False`.
3. **What do the error bars show?** Lord et al. (2020) explain that error bars computed from all cells are "artificially small (due to √n in the denominator)". Compute any interval from the experiment means, or show none and give the CI of the difference in the text.

**Do this:** paste the test line of the code into your notes with the n it printed, and match it to the n in your legend.

## References

- Lord SJ, Velle KB, Mullins RD, Fritz-Laylin LK (2020). SuperPlots: communicating reproducibility and variability in cell biology. *Journal of Cell Biology* 219(6):e202001064. [doi:10.1083/jcb.202001064](https://doi.org/10.1083/jcb.202001064)
- Aarts E, Dolan CV, Verhage M, van der Sluis S (2015). Multilevel analysis quantifies variation in the experimental effect while optimizing power and preventing false positives. *BMC Neuroscience* 16:94. [doi:10.1186/s12868-015-0228-5](https://doi.org/10.1186/s12868-015-0228-5)
- Goedhart J (2021). SuperPlotsOfData—a web app for the transparent display and quantitative comparison of continuous data from different conditions. *Molecular Biology of the Cell* 32(6):470–474. [doi:10.1091/mbc.E20-09-0583](https://doi.org/10.1091/mbc.E20-09-0583)
- Weissgerber TL, Milic NM, Winham SJ, Garovic VD (2015). Beyond bar and line graphs: time for a new data presentation paradigm. *PLOS Biology* 13(4):e1002128. [doi:10.1371/journal.pbio.1002128](https://doi.org/10.1371/journal.pbio.1002128)
- Nature Portfolio. Reporting Summary (life sciences), form version of August 2023. [nature.com/documents/nr-reporting-summary-flat.pdf](https://www.nature.com/documents/nr-reporting-summary-flat.pdf)

## Related

- [Colocalisation analysis](https://www.licklider.ai/guides/colocalisation-analysis/)
- [Paired vs unpaired t-test](https://www.licklider.ai/guides/paired-vs-unpaired-t-test/)
- [What counts as n: biological vs technical replicates](https://www.licklider.ai/guides/what-counts-as-n/)
- [Nested data: cells within animals or experiments](https://www.licklider.ai/guides/nested-data-mixed-model/)
- [How to report a t-test](https://www.licklider.ai/guides/how-to-report-a-t-test/)
- [SD vs SEM error bars](https://www.licklider.ai/guides/sd-vs-sem-error-bars/)
- [Organoid size analysis](https://www.licklider.ai/guides/organoid-size-analysis/)

## How this page was made

- Written and published by Licklider. The method follows Lord et al. (2020) and the other peer-reviewed papers and reporting forms listed under References; quoted passages were checked against the source text.
- The worked example, figure and simulation use constructed data and stored code (Python 3.13 with SciPy 1.18.1 and matplotlib 3.11; R 4.3.3 with ggplot2 4.0.3); Python and R give the same statistics.
- Separate review passes checked the statistics, the citations and readability before publication.
