# Scratch wound-healing assay statistics: migration speed, percent closure and experiments as n

> Measure 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.

Canonical: https://www.licklider.ai/guides/scratch-wound-healing-assay/
Section: Guides › Normalised data and fold change
Author: Licklider · Published 2026-11-12 · Last reviewed 2026-11-12 (UTC)

> **Quick answer.** In a scratch (wound-healing) assay, image each wound at several times and measure **migration speed** (half the rate at which the width shrinks, one edge) from the slope of wound width over time, using only the time points before the edges meet, rather than only **percent closure** at one time: percent closure depends on how wide the scratch started (in our example, r = −0.82 with starting width, against 0.03 for speed), so it is biased whenever starting widths differ between conditions. Average fields within each well and wells within each condition and experiment, then compare conditions with **independent experiments as n** (one log ratio per experiment, paired). In our simulation, speed detected a 20% slowing in 81.5% of studies against 68.0% for 24-hour percent closure; using wells as n gave 14.8% false positives when the conditions varied between experiments. Suppress proliferation (low serum, or a non-toxic dose of mitomycin C given to every condition) so that closure reflects migration.

**Terms used below.** *Scratch (wound-healing) assay*: a gap made in a cell monolayer, imaged as cells move in to close it. *Wound width (or area)*: the size of the gap in each image. *Migration speed*: how fast each wound edge moves, here half the rate at which the width shrinks, in µm per hour. *Percent closure*: (starting width − width at time t) ÷ starting width × 100. *Field*: one imaged position along the scratch. *Independent experiment*: cells seeded, scratched and imaged on a separate occasion. *Technical replicate*: another well or field from the same experiment. *Paired*: drug and control compared within the same experiment. *Log ratio*: log(drug ÷ control); averaging log ratios gives a ratio of geometric means; every speed must be above 0. With wound area instead of width, edge speed is −(slope of area over time) ÷ (2 × wound length), assuming the imaged length of the wound stays the same. *Percentage points*: the difference between two percentages. *Welch's t-test*: compares two groups without assuming equal spreads (used here for the wells-as-n comparison). *One-sample t-test*: tests whether the mean of the per-experiment log ratios differs from 0. *r*: the correlation coefficient, from −1 to 1. *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 migration speed? | Half the slope of width over time per field (time points before closure only), averaged per well and condition; one log ratio per experiment, one-sample t-test | Speed ratio with 95% CI, n experiments |
| Does it change closure at a fixed time? | Percent closure per field, averaged per experiment; difference per experiment, one-sample t-test; biased if starting widths differ between conditions | Difference in percentage points with 95% CI; also report starting widths |
| Several treatments or doses | Per-experiment summaries for each condition, then a model with experiment as a block and a correction across comparisons | Ratios with CIs |
| Proliferation as well as migration | Run a proliferation control (low serum or mitomycin C) or count cells in parallel ([cell doubling time](/guides/cell-doubling-time/)) | Both measures |

Liang and colleagues describe the assay as "capturing the images at the beginning and at regular intervals during cell migration to close the scratch, and comparing the images to quantify the migration rate of the cells" ([Liang et al. 2007](https://doi.org/10.1038/nprot.2007.30)). Jonkman and colleagues recommend measuring "the gap area as a function of time. From the slope of this plot a simple calculation yields the cell migration rate", and note that the migration rate "is more broadly comparable across datasets with varying wound areas" ([Jonkman et al. 2014](https://doi.org/10.4161/cam.36224)). Suarez-Arnedo and colleagues found that a hand-made scratch "is highly variable with respect to the width, and this variability affects the cell migration rate and the reproducibility of the assay", and provide an ImageJ plugin to measure "area, wound area fraction, average wound width, and width deviation" ([Suarez-Arnedo et al. 2020](https://doi.org/10.1371/journal.pone.0232565)). Grada and colleagues note that "using low serum concentrations in cell medium (serum starvation) is the most common method to suppress cell proliferation in wound healing assays" ([Grada et al. 2017](https://doi.org/10.1016/j.jid.2016.11.020)). On replication, Vaux and colleagues warn that "replicates are not independent tests of the hypothesis" ([Vaux et al. 2012](https://doi.org/10.1038/embor.2012.36)).

## Worked example: a drug with no effect on migration

Constructed data: wound width in 3 fields per well, 3 wells per condition (control and drug), imaged at 0, 6, 12 and 24 hours, in **5 independent experiments**. By design, **the drug does not change migration speed** (6 µm/h per edge); hand-made scratches start at different widths (a standard deviation of 80 µm), experiments differ in speed, and each measurement has some error. The random seed was the first one tried and was not chosen for its result. Stored code and data: [wound.csv](/guides/scratch-wound-healing-assay/wound.csv), [model.py](/guides/scratch-wound-healing-assay/model.py), [make_data.py](/guides/scratch-wound-healing-assay/make_data.py), [analysis.py](/guides/scratch-wound-healing-assay/analysis.py), [analysis.R](/guides/scratch-wound-healing-assay/analysis.R) and [the analysis output](/guides/scratch-wound-healing-assay/analysis_output.txt); analysis.py and analysis.R print the same output.

Starting widths ranged from 255 to 743 µm (mean 485 µm). Across fields, 24-hour percent closure was strongly related to starting width (r = −0.82): narrow scratches closed a larger share in the same time, as expected when edges move at a fixed speed. Migration speed was not related to starting width (r = 0.03). One narrow field had closed by 24 h; that time point was left out of the speed fit.

| Analysis | Result |
| --- | --- |
| Migration speed, wells as n (15 vs 15) | drug ÷ control 0.94, p = 0.067 |
| **Migration speed, experiments as n (5)** | **0.93 (95% CI 0.85 to 1.02), p = 0.11**; control 6.5 µm/h per edge |
| 24-hour percent closure, experiments as n | drug − control −8.6 percentage points (95% CI −19.1 to 1.8), p = 0.084; control closure 68.6% |

Both measures correctly show no clear effect, but the percent-closure interval is wide, because the starting widths add their own variation to closure. In 10,000 simulated studies of this design ([simulation.py](/guides/scratch-wound-healing-assay/simulation.py), [the simulation output](/guides/scratch-wound-healing-assay/simulation_output.txt)):

| Scenario | Speed, wells as n, p < 0.05 | Speed, experiments as n | 24-h percent closure, experiments as n |
| --- | --- | --- | --- |
| Drug has no effect | 5.6% | 5.7% | 5.2% (false positives) |
| No effect, conditions vary more between experiments | **14.8%** | 4.9% | 4.8% (false positives) |
| Drug slows migration by 20% | 92.6% | **81.5%** (power) | 68.0% (power) |

With experiments as n, false positives stayed near 5%, and speed detected a 20% slowing more often than percent closure. Wells as n looked fine when experiments behaved alike, but when the conditions varied between experiments it gave false positives in 14.8% of studies; its extra "power" comes from the same overconfidence.

The code expects one row per field and time with columns experiment, condition (control or drug), well, field, hours and width_um (from ImageJ or similar, after setting the image scale so that widths are in µm, not pixels; use the plugin's average wound width); the stored scripts assume images at 0 and 24 h for percent closure; 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 newer and SciPy; the R code uses base R only. Keep the hours column as real times, and drop fields with fewer than 2 time points before closure.

```python
# Condensed: analysis.py also gives percent closure and the wells-as-n comparison
import numpy as np, pandas as pd
from scipy import stats

d = pd.read_csv("wound.csv")
o = d[d.width_um > 20]                                                                     # time points before closure
speed = o.groupby(["experiment", "condition", "well", "field"]).apply(
    lambda x: -np.polyfit(x.hours, x.width_um, 1)[0] / 2, include_groups=False)          # µm/h per edge, per field
e = speed.groupby(["experiment", "condition", "well"]).mean().groupby(["experiment", "condition"]).mean().unstack()   # wells, then experiments
r = np.log(e["drug"] / e["control"]); t = stats.ttest_1samp(r, 0)
print(np.exp(r.mean()), np.exp(t.confidence_interval()), t.pvalue)                        # 0.93, 0.85 to 1.02, p = 0.11
```

```r
d <- read.csv("wound.csv"); d <- d[d$width_um > 20, ]                                    # time points before closure
f <- aggregate(cbind(hours, width_um) ~ experiment + condition + well + field, data = d,
               FUN = identity, simplify = FALSE)
f$speed <- mapply(function(h, w) -coef(lm(w ~ h))[[2]] / 2, f$hours, f$width_um)          # µm/h per edge, per field
w <- aggregate(speed ~ experiment + condition + well, data = f, FUN = mean)               # wells first
e <- tapply(w$speed, list(w$experiment, w$condition), mean)                               # then experiments
r <- log(e[, "drug"] / e[, "control"]); tt <- t.test(r)
exp(c(ratio = mean(r), tt$conf.int)); tt$p.value                                          # 0.93, 0.85 to 1.02, p = 0.11
```

## Before you analyse

1. **Make scratches as uniform as possible** (an insert or a guide rather than a free-hand tip) and record the starting width of every field; if starting widths differ between conditions, percent closure is biased.
2. **Image at several times** and fit speed from the linear part of closure (before the edges meet); fix the time points in advance.
3. **Suppress or measure proliferation**, for example with low serum or a mitomycin C dose shown not to reduce viability, applied to every condition, so that closure reflects migration; check that the treatment does not kill cells.
4. **Measure blind to condition**, with the same software settings for every image (for example the ImageJ wound-healing plugin).
5. **Count independent experiments as n**, with every condition in every experiment; wells and fields are technical replicates ([what counts as n](/guides/what-counts-as-n/)). With 3 experiments the t-test has 2 degrees of freedom and a very wide interval; plan 5 or more.

## Check what an AI assistant ran

1. **The assistant compared percent closure at one time without checking starting widths.** Ask for migration speed from several time points, and for the starting widths per condition.
2. **The assistant used every field or well as n.** Ask for one value per condition per experiment and a comparison across experiments.
3. **The assistant included time points after the wound had closed.** Ask for speed from the linear part only.
4. **The assistant ignored proliferation.** Ask how proliferation was controlled or measured.

## Report it

> Scratches were made with [a 200 µL tip / culture inserts] in confluent monolayers in [1]% serum, and imaged at [0, 6, 12 and 24] h ([3] fields per well, [3] wells per condition). Wound width was measured blind to condition, and migration speed per edge was half the slope of width over time. Speeds were averaged per condition within each of [5] independent experiments and compared as log ratios with a one-sample t-test across experiments (R [4.3.3]).

> The drug did not clearly change migration speed (drug ÷ control [0.93], 95% CI [0.85] to [1.02]; p = [0.11]; control [6.5] µm/h per edge).

Replace the bracketed values with your own.

## References

- Liang CC, Park AY, Guan JL (2007). In vitro scratch assay: a convenient and inexpensive method for analysis of cell migration in vitro. *Nature Protocols* 2:329–333. [doi:10.1038/nprot.2007.30](https://doi.org/10.1038/nprot.2007.30)
- Jonkman JEN, Cathcart JA, Xu F, et al. (2014). An introduction to the wound healing assay using live-cell microscopy. *Cell Adhesion & Migration* 8:440–451. [doi:10.4161/cam.36224](https://doi.org/10.4161/cam.36224)
- Suarez-Arnedo A, Torres Figueroa F, Clavijo C, et al. (2020). An image J plugin for the high throughput image analysis of in vitro scratch wound healing assays. *PLOS ONE* 15:e0232565. [doi:10.1371/journal.pone.0232565](https://doi.org/10.1371/journal.pone.0232565)
- Grada A, Otero-Vinas M, Prieto-Castrillo F, Obagi Z, Falanga V (2017). Research techniques made simple: analysis of collective cell migration using the wound healing assay. *Journal of Investigative Dermatology* 137:e11–e16. [doi:10.1016/j.jid.2016.11.020](https://doi.org/10.1016/j.jid.2016.11.020)
- Vaux DL, Fidler F, Cumming G (2012). Replicates and repeats—what is the difference and is it significant? *EMBO Reports* 13:291–296. [doi:10.1038/embor.2012.36](https://doi.org/10.1038/embor.2012.36)

## Related

- [What counts as n?](https://www.licklider.ai/guides/what-counts-as-n/)
- [Cell doubling time](https://www.licklider.ai/guides/cell-doubling-time/)
- [Statistics on fold change and normalised data](https://www.licklider.ai/guides/statistics-on-fold-change/)
- [Tumour growth curves](https://www.licklider.ai/guides/tumour-growth-curves/)
- [Transwell migration and invasion](https://www.licklider.ai/guides/transwell-migration-invasion/)

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