Normalised data and fold change · Choose a method
Cell viability as percent of control: replicate wells, independent experiments and normalisation
Analyse 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.
On this page
Quick answer. For a single-dose viability assay (MTT, MTS, resazurin, CellTiter-Glo), the unit is the independent experiment (a different day, passage and plate), not the well. In each experiment, subtract the blank, average the replicate wells of each condition, and express treated as a ratio of vehicle; then test across experiments, either the log ratio against 0 (reported as a ratio with 95% CI) or the percent of vehicle against 100%. In a simulation with 4 experiments of 6 wells per condition and no real effect, treating wells as n gave false positives in 38.8% of studies; experiments as n gave about 5%. Report the result as the change in assay signal, because these assays measure metabolism or ATP, not cell number.
Terms used below. Blank: wells with medium and reagent but no cells, giving the background signal. Vehicle: cells given the solvent without the compound, the control. Replicate (technical) wells: wells of the same condition on one plate; they measure the same experiment several times. Independent experiment: a repeat on a different day with a fresh passage and plate; the number of these is n. Percent of vehicle (percent of control): treated signal ÷ vehicle signal × 100 within one experiment. Log ratio: natural log(treated ÷ vehicle); 0 means no change, and its average back-transforms to a ratio. Edge effect: outer wells of a plate drift because they evaporate faster. One-sample t-test: a test of whether the mean of a set of values (here one per experiment) differs from a fixed value such as 0 or 100%. Paired t-test: a test of within-experiment differences. Linear range: the range of cell numbers over which the signal rises in proportion. IC50: the concentration that halves the response. Welch t-test: a two-group t-test that does not assume equal spread. 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 compound change viability at one dose? | Per experiment: blank-subtract, mean of wells, log(treated ÷ vehicle); one-sample t-test of the log ratios against 0 across experiments | Ratio (or percent of vehicle) with 95% CI, n experiments (wells per condition) |
| The same, on the percent scale | Percent of vehicle per experiment, one-sample t-test against 100%, experiments as n; similar to the log ratio for modest effects, but the log ratio treats increases and decreases alike | Mean percent with 95% CI, n experiments |
| Several compounds or doses against one vehicle | Log ratio per compound and experiment; one-sample tests against 0 with a correction | Ratios with adjusted CIs |
| Two compounds compared with each other | Paired t-test of their log ratios across experiments (each experiment holds both) | Ratio of ratios with 95% CI |
| A full dose range | Fit a dose-response curve per experiment (IC50 and dose-response) | IC50 with 95% CI |
| Is it cell death or slower metabolism? | A second readout (cell count, imaging, membrane integrity) in the same experiments | Both results side by side |
Lazic and colleagues note that “in vitro experiments are usually repeated on multiple days, and the number of wells, aliquots, or culture dishes within a day are treated as subsamples”, and that “the multiple wells on each day are then treated as subsamples and do not contribute to N” (Lazic et al. 2018). Lew warns about the control group after normalisation: with data expressed as percent of control, “the control group had zero variability”, and he recommends “Do not express the data as percent of control before analysis using ANOVA” (Lew 2007); testing the ratios themselves, as below, avoids comparing against a control column with no spread. Mansoury and colleagues write that “Corner and edge wells have been observed to be more prone to evaporation compared to interior wells” (Mansoury et al. 2021). Chan and colleagues found that metabolism-based readouts “can yield significantly misleading results when using ATP or tetrazolium-reduction assays as a proxy for cell number” (Chan et al. 2013).
Worked example: a compound with no real effect
Constructed data: 4 independent experiments, each a 96-well plate with 3 blank, 6 vehicle and 6 treated wells (absorbance, as in an MTT assay). The signal scale differs between experiments (standard deviation 0.30 on the log scale), wells scatter around their experiment’s mean (6%), the treated/vehicle ratio varies a little between experiments (8%), and blanks read about 0.05. By design, the compound does not change viability. The data’s random seed was the first one tried and was not chosen for its result. Stored code and data: viability.csv, model.py, make_data.py, analysis.py, analysis.R and the analysis output; analysis.py and analysis.R print the same output.
The treated wells averaged 105.4%, 99.6%, 110.1% and 105.0% of vehicle in the four experiments, while the vehicle signal itself ranged from 0.78 to 1.18 between experiments.
| Analysis of constructed data with no real effect, treated vs vehicle | Result |
|---|---|
| A. (Not valid: wells share experiments) Wells as n (24 treated wells), one-sample t-test of percent of vehicle against 100% | mean 105.0%, p = 0.00053 (invalid) |
| B. (Not valid: wells share experiments) Wells as n (24 vs 24), Welch t-test on percent of vehicle | difference 5.0 percentage points, p = 0.0036 (invalid) |
| C. Experiments as n (4), percent of vehicle against 100% | mean 105.0% (95% CI 98.2 to 111.8), p = 0.10 |
| D. Experiments as n (4), log(treated ÷ vehicle) against 0 | ratio 1.049 (95% CI 0.983 to 1.120), p = 0.099 |
| E. Experiments as n (4), paired t-test on blank-subtracted signal | difference 0.054 absorbance units, p = 0.13 |
In these constructed data with no real effect, counting 24 wells as 24 replicates turned a chance 5% difference that varied between experiments into a “highly significant” (p = 0.00053) apparent increase in assay signal. With experiments as n, the data are compatible with anything from a 2% decrease to a 12% increase (log ratio: 104.9% of vehicle, 95% CI 98.3% to 112.0%; the percent scale in C gives almost the same, 105.0%, 98.2% to 111.8%). Across 5,000 simulated studies per row (simulation.py, the simulation output):
| Scenario | A. Wells as n, vs 100% (invalid) | B. Wells as n, vs vehicle wells (invalid) | C. Percent vs 100% | D. Log ratio | E. Paired raw signal |
|---|---|---|---|---|---|
| No effect, 3 experiments (false positives) | 39.2% | 32.4% | 5.3% | 5.2% | 4.6% |
| No effect, 4 experiments (false positives) | 38.8% | 32.3% | 4.8% | 4.9% | 4.3% |
| Viability 85% of vehicle, 3 experiments (power; inflated for A and B) | 98.7% | 98.2% | 45.9% | 41.9% | 35.1% |
| Viability 85% of vehicle, 4 experiments (power; inflated for A and B) | 99.7% | 99.6% | 73.2% | 69.9% | 62.9% |
| Viability 85% of vehicle, 6 experiments (power; inflated for A and B) | 100.0% | 100.0% | 95.3% | 94.9% | 92.1% |
These percentages describe this simulated design (one plate per experiment, 6 wells per condition, the variation in model.py), not every viability assay. Wells as n found a “difference” in 30–39% of studies with no effect (3 to 6 experiments; the 6-experiment rows are in the simulation output), because wells on one plate share the day’s cells and reagent and are read together. The percent and log-ratio tests with experiments as n kept false positives near 5% and had similar power; the paired test on raw signal was weaker, because the signal scale changed between experiments. A 15% loss of viability was missed in about 30% of studies with 4 experiments and in more than half with 3.
The code expects one row per well with columns experiment, condition (labels exactly blank, vehicle and treated), well (an identifier only) and absorbance (or luminescence or fluorescence). Rename your columns and labels to match, save the table as viability.csv (or change the file name in the code), and run the code in that folder (python analysis.py or Rscript analysis.R). If a blank-subtracted mean is 0 or below (complete kill, or a blank that reads high), the ratio cannot be logged: check the plate, and report percent of vehicle and the raw signals. Python needs NumPy, pandas and SciPy 1.10 or later (pip install numpy pandas scipy); the R code uses base R only.
import numpy as np, pandas as pd
from scipy import stats
d = pd.read_csv("viability.csv")
d["signal"] = d.absorbance - d.experiment.map(d[d.condition == "blank"].groupby("experiment").absorbance.mean())
e = d[d.condition != "blank"].groupby(["experiment", "condition"]).signal.mean().unstack() # one row per experiment
assert (e > 0).all().all(), "a blank-subtracted mean is 0 or below: check the plate before taking logs"
lr = np.log(e.treated / e.vehicle); t = stats.ttest_1samp(lr, 0); ci = t.confidence_interval()
print(f"treated / vehicle {np.exp(lr.mean()):.3f} (95% CI {np.exp(ci.low):.3f} to {np.exp(ci.high):.3f}), p = {t.pvalue:.2g}")
# treated / vehicle 1.049 (95% CI 0.983 to 1.120), p = 0.099, that is 104.9% of vehicle (98.3% to 112.0%)
d <- read.csv("viability.csv")
blank <- tapply(d$absorbance[d$condition == "blank"], d$experiment[d$condition == "blank"], mean)
d$signal <- d$absorbance - blank[d$experiment]
e <- with(d[d$condition != "blank", ], tapply(signal, list(experiment, condition), mean)) # one row per experiment
stopifnot(all(e > 0)) # a blank-subtracted mean of 0 or below cannot be logged
t1 <- t.test(log(e[, "treated"] / e[, "vehicle"]))
round(exp(c(t1$estimate, t1$conf.int)), 3); t1$p.value # treated / vehicle 1.049, 95% CI 0.983 to 1.120, p = 0.099 (104.9% of vehicle)
Before you analyse
- Plan independent experiments, each on a different day with a fresh passage and plate, and decide their number in advance (at least 3, and more for small effects: in the simulation, 6 experiments detected a 15% loss in 95% of studies and 3 in fewer than half; with 2, a t-test has one degree of freedom); more wells or plates from one day do not add replicates.
- Keep blank wells on every plate and subtract the mean of that plate’s blank wells before forming ratios.
- Lay out plates to avoid edge effects: fill the outer wells with medium or PBS and leave them out of the analysis (they are not blanks), or spread conditions across positions instead of placing all treated wells on one edge.
- Check that the signal is in the assay’s linear range for the cell number seeded, and that the solvent concentration is the same in every condition.
- Keep every experiment that met the planned quality checks, and show the per-experiment values (SuperPlots).
Check what an AI assistant ran
- The assistant used all wells as n. Ask for one value per experiment.
- The assistant set the vehicle to 100% and ran ANOVA with the vehicle as a group. Ask for the test on ratios across experiments.
- The assistant described the result as a change in cell number or proliferation. Ask whether a cell count or a second readout supports it.
- The assistant pooled several plates from one day as separate experiments. Ask which plates came from independent days.
Report it
Viability was measured with [MTT] in [4] independent experiments (different days and passages), each with [6] wells per condition and [3] blank wells. Blank-subtracted signals were averaged per condition within each experiment, and log(treated ÷ vehicle) was compared with 0 by a one-sample t-test across experiments (R [4.3.3]); the assay measures [metabolic activity], not cell number.
In the constructed example, which has no effect built in, the treated signal was not clearly different from vehicle (ratio [1.049], 95% CI [0.983] to [1.120]; p = [0.099]; n = [4] experiments).
Replace the bracketed values with your own.
References
- Lazic SE, Clarke-Williams CJ, Munafò MR (2018). What exactly is ‘N’ in cell culture and animal experiments? PLoS Biology 16:e2005282. doi:10.1371/journal.pbio.2005282
- Lew M (2007). Good statistical practice in pharmacology. Problem 2. British Journal of Pharmacology 152:299–303. doi:10.1038/sj.bjp.0707372
- Mansoury M, Hamed M, Karmustaji R, Al Hannan F, Safrany ST (2021). The edge effect: a global problem. The trouble with culturing cells in 96-well plates. Biochemistry and Biophysics Reports 26:100987. doi:10.1016/j.bbrep.2021.100987
- Chan GKY, Kleinheinz TL, Peterson D, Moffat JG (2013). A simple high-content cell cycle assay reveals frequent discrepancies between cell number and ATP and MTS proliferation assays. PLoS ONE 8:e63583. doi:10.1371/journal.pone.0063583
How this page was made
- Written and published by Licklider. Recommendations cite peer-reviewed methods papers 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, base R), and the two languages print identical output; the simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.