# Dendritic spine density statistics: spines per 10 µm, segments within neurons within animals, and count models

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

Canonical: https://www.licklider.ai/guides/dendritic-spine-density/
Section: Guides › Nested measurements
Author: Licklider · Published 2026-11-12 · Last reviewed 2026-11-12 (UTC)

> **Quick answer.** Spines are counted on dendritic segments of neurons from each animal, so segments and neurons are subsamples of the **animal** (the unit that received the genotype or treatment). For each animal, divide the total spines by the total dendrite length measured (spines per 10 µm), and compare animals with a Welch t-test (a Welch ANOVA for three or more groups). A Poisson model that treats every segment as independent is the worst choice; a count mixed model with animal and neuron random effects respects the nesting. With few animals, that mixed model's p-values are too small. In a simulation of 6 vs 6 mice with 6 neurons each, 2 segments per neuron and no real difference, segments as n gave false positives in 25.8% of studies, neurons as n 18.3%, the all-segments Poisson model 46.2%, and mice as n 4.4%. In the same simulation, spreading the same 36 neurons per group over 12 mice × 3 neurons instead of 6 × 6 raised the chance of detecting a 20% drop from 61.0% to 83.0%.

**Terms used below.** *Dendritic spine*: a small protrusion on a dendrite that carries most excitatory synapses. *Segment*: a stretch of dendrite (here 10–30 µm of a secondary branch, one that branches off a primary dendrite leaving the cell body) on which spines are counted. *Spine density*: spines per unit length, here per 10 µm. *Experimental unit*: the thing randomised to a group, here the mouse. *Poisson model*: a regression for counts that assumes their variance equals their mean; an *offset* of log(length) turns counts into rates per µm. *Rate ratio*: knockout density ÷ wild-type density from a count model. *Negative-binomial model*: a count model that allows more scatter than Poisson. *Mixed model*: a regression with random effects (an adjustment for each animal and each neuron), so segments are not treated as independent. *Wald test*: the test mixed-model software uses for p-values by default. *Welch ANOVA*: one-way ANOVA for three or more groups without assuming equal spread. *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 genotype or treatment change spine density? | Total spines ÷ total length per animal (spines per 10 µm), Welch t-test across animals | Difference with 95% CI, n animals (neurons, segments, µm) |
| Uneven numbers of neurons or segments per animal | Total spines ÷ total length per animal still gives one density per animal. Alternative: Poisson or negative-binomial mixed model with log(length) offset and random effects for animal and neuron; with few animals the mixed model's Wald p-values are too small (here p = 0.037 with no real effect). Report it beside the animal-level result | Difference or rate ratio with 95% CI |
| Spine types (thin, stubby, mushroom) | Density of each type per animal, analysed the same way, with a Holm correction across all types tested ([which correction](/guides/which-multiple-comparison-correction/)) | Differences per type |
| Proximal vs distal or apical vs basal | Density per animal for each compartment; paired t-test on each animal's difference between compartments, or a mixed model with animal as a random effect | Differences with 95% CI |
| Three or more groups | Animal densities in a [Welch ANOVA](/guides/welch-anova/), then comparisons chosen before seeing the data | Differences with adjusted CIs |

Benavides-Piccione and colleagues determined spine density "by counting the number of spines per 10 µm of dendritic length", and "the analysis of all spine variables was performed under blinded conditions" ([Benavides-Piccione et al. 2025](https://doi.org/10.1002/cne.70060)). Wilson and colleagues note that "an additional neuron from the same animal does not provide the same amount of additional information as another neuron from a different animal" ([Wilson et al. 2017](https://doi.org/10.1016/j.jneumeth.2017.01.003)), and Lazic describes pseudoreplication as observations that "are not statistically independent, but treated as if they are", using synapse counts on many cells per rat as an example ([Lazic 2010](https://doi.org/10.1186/1471-2202-11-5)).

## Worked example: a knockout with no real effect

Constructed data: spines counted on **2 secondary dendritic segments (10–30 µm) of 6 neurons in each of 6 wild-type and 6 knockout mice** (144 segments, 3,453 spines). Density is about 12 spines per 10 µm and varies between mice (standard deviation 0.12 on the log scale), between neurons of a mouse (0.15) and between segments (0.10); each count scatters around its segment's density. By design, **the knockout does not change spine density**. The data's random seed was the first one tried and was not chosen for its result. Stored code and data: [spines.csv](/guides/dendritic-spine-density/spines.csv), [model.py](/guides/dendritic-spine-density/model.py), [make_data.py](/guides/dendritic-spine-density/make_data.py), [analysis.py](/guides/dendritic-spine-density/analysis.py), [analysis.R](/guides/dendritic-spine-density/analysis.R) and [the analysis output](/guides/dendritic-spine-density/analysis_output.txt); analysis.py and analysis.R print the same output.

Knockout mice ranged from 11.2 to 15.9 spines per 10 µm (1,864 spines on 1,370 µm) and wild-type mice from 9.2 to 14.5 (1,589 spines on 1,397 µm). By chance the knockout mice in this draw had a higher spine density.

| Analysis of constructed data with no real effect, knockout vs wild-type | Result |
| --- | --- |
| A. (Not valid: segments share neurons and mice) Segments as n (72 vs 72), Welch t-test | ratio 1.22, p = 0.00027 |
| B. (Not valid: neurons share mice) Neurons as n (36 vs 36), Welch t-test | ratio 1.21, p = 0.0033 |
| **C. Mice as n (6 vs 6), spines per 10 µm, Welch t-test** | **difference 2.32 (95% CI −0.31 to 4.94), p = 0.078** |
| D. (Not valid: mice ignored) Poisson model of all segments as independent counts, log(length) offset | rate ratio 1.20, p = 1.5 × 10⁻⁷ |
| E. (Nesting respected, but Wald p too small with 6 mice per group) Poisson mixed model with mouse, neuron and segment random effects ([mixed_model.R](/guides/dendritic-spine-density/mixed_model.R), [output](/guides/dendritic-spine-density/mixed_model_output.txt); R only) | rate ratio 1.21 (95% CI 1.01 to 1.44), p = 0.037 |

Four of the five analyses reached p < 0.05 on data with no real effect: the three invalid analyses (A, B and D), and the mixed model, which respects the nesting but whose Wald test was too confident with 6 mice per group. Only the analysis with mice as n did not. Its p = 0.078 is not evidence of no effect either: in the simulation below, this design detected a real 20% drop in only 61.0% of studies, and the confidence interval shows what the data allow. Across 5,000 simulated studies of this design ([simulation.py](/guides/dendritic-spine-density/simulation.py), [the simulation output](/guides/dendritic-spine-density/simulation_output.txt)):

| Scenario | A. Segments as n (invalid) | B. Neurons as n (invalid) | C. Mice as n | D. Poisson, all segments (invalid) |
| --- | --- | --- | --- | --- |
| No effect, 6 mice × 6 neurons (false positives) | **25.8%** | **18.3%** | 4.4% | **46.2%** |
| 20% fewer spines, 6 mice × 6 neurons (power) | 90.7% | 87.3% | 61.0% | 95.7% |
| No effect, 12 mice × 3 neurons (false positives) | 17.2% | 10.4% | 4.5% | 36.6% |
| 20% fewer spines, 12 mice × 3 neurons (power) | 95.1% | 91.7% | **83.0%** | 98.0% |

These percentages describe this simulated design (2 segments per neuron, the variation between mice, neurons and segments set in model.py), not every spine study. The all-segments Poisson model was the most wrong, because it treats every spine count as independent and assumes no variation between neurons or mice. Mice as n kept false positives near 5%; for the same 36 neurons (72 segments) per genotype, spreading them over 12 mice instead of 6 raised power from 61.0% to 83.0%. The mixed model (E) was not included in the simulation; report the animal-level analysis (C) as the result and the mixed model, if you fit it, as a secondary analysis.

The code expects one row per segment with columns `genotype` (labels exactly `wild-type` and `knockout`), `mouse` (unique across genotypes), `neuron` (numbered within each mouse or across the experiment; either works), `segment`, `length_um` and `spines`. If you only have a mean density per neuron, go back to the spine count and length of each segment or neuron: averaging neuron densities weights a short dendrite as much as a long one. Check that every row has a length above 0 and a spine count. Rename your columns and labels to match, save the table as spines.csv (or change the file name in the code), and run the code in that folder (`python analysis.py` or `Rscript analysis.R`). Python needs NumPy, pandas and SciPy 1.11 or later (`pip install numpy pandas scipy`); the R code uses base R; the optional mixed model needs lme4 (`install.packages("lme4")`).

```python
import numpy as np, pandas as pd
from scipy import stats

d = pd.read_csv("spines.csv")
m = d.groupby(["genotype", "mouse"])[["spines", "length_um"]].sum()       # totals per mouse
m["per10"] = 10 * m.spines / m.length_um                                  # spines per 10 um
k, w = m.loc["knockout", "per10"], m.loc["wild-type", "per10"]
t = stats.ttest_ind(k, w, equal_var=False); ci = t.confidence_interval()
print(f"difference {k.mean() - w.mean():.2f} per 10 um (95% CI {ci.low:.2f} to {ci.high:.2f}), p = {t.pvalue:.2g}")
# difference 2.32 per 10 um (95% CI -0.31 to 4.94), p = 0.078
```

```r
d <- read.csv("spines.csv")
m <- aggregate(cbind(spines, length_um) ~ genotype + mouse, data = d, FUN = sum)   # totals per mouse
m$per10 <- 10 * m$spines / m$length_um                                           # spines per 10 um
t.test(per10 ~ genotype, data = m)       # Welch; knockout - wild-type 2.32, 95% CI -0.31 to 4.94, p = 0.078
```

## Before you analyse

1. **Define the segments before imaging**: branch order (for example secondary dendrites), distance from the soma, length range and number per neuron, the same for every animal.
2. **Choose neurons and segments by a rule set in advance, and count blind to genotype**, with images coded by someone else.
3. **Record length with every count** and analyse densities (or counts with a length offset), never raw counts.
4. **Plan animals, not just neurons**: in our simulation, spreading the same 36 neurons per group over twice the mice raised the power to detect a 20% drop from 61% to 83%.
5. **Use the animal as n**; if you fit a mixed model, include animal and neuron random effects and check it against the animal-level result ([nested data](/guides/nested-data-mixed-model/)).
6. **Plot the per-animal densities**: with 6 animals per group one animal can drive the result, and a Welch t-test does not fix strong skew (consider a log scale).
7. **Check cages and litters**: if treatment was given per cage, or mice in a group share a cage or litter, they are not independent; use the cage or litter as the unit, or model it as a random effect ([what counts as n](/guides/what-counts-as-n/)).

## Check what an AI assistant ran

1. **The assistant ran a t-test on all segments or neurons.** Ask for one density per animal.
2. **The assistant fitted a Poisson regression to all segments.** Ask how animals and neurons were handled.
3. **The assistant compared raw spine counts.** Ask for densities per length.
4. **The assistant classified spine types and tested each one without saying how many types there were.** Ask for a Holm correction across all types tested.

## Report it

> Spines were counted blind to genotype on [2] secondary dendritic segments ([10–30] µm) of [6] neurons per mouse in [6] wild-type and [6] knockout mice. For each mouse, the total spine count was divided by the total length measured (spines per 10 µm), and genotypes were compared with a Welch t-test (R [4.3.3]).

> In the constructed example, which has no effect built in, spine density did not clearly differ between genotypes (knockout − wild-type [2.32] spines per 10 µm, 95% CI [−0.31] to [4.94]; p = [0.078]; n = [6] mice per genotype, [36] neurons and [72] segments per genotype).

Replace the bracketed values with your own.

## References

- Benavides-Piccione R, Fernaud-Espinosa I, Kastanauskaite A, DeFelipe J (2025). Principles for dendritic spine size and density in human and mouse cortical pyramidal neurons. *Journal of Comparative Neurology* 533:e70060. [doi:10.1002/cne.70060](https://doi.org/10.1002/cne.70060)
- Wilson MD, Sethi S, Lein PJ, Keil KP (2017). Valid statistical approaches for analyzing Sholl data: mixed effects versus simple linear models. *Journal of Neuroscience Methods* 279:33–43. [doi:10.1016/j.jneumeth.2017.01.003](https://doi.org/10.1016/j.jneumeth.2017.01.003)
- Lazic SE (2010). The problem of pseudoreplication in neuroscientific studies: is it affecting your analysis? *BMC Neuroscience* 11:5. [doi:10.1186/1471-2202-11-5](https://doi.org/10.1186/1471-2202-11-5)

## Related

- [Sholl analysis](https://www.licklider.ai/guides/sholl-analysis/)
- [Nested data: means or mixed model](https://www.licklider.ai/guides/nested-data-mixed-model/)
- [Statistical tests for count data](https://www.licklider.ai/guides/statistical-tests-for-count-data/)
- [What counts as n](https://www.licklider.ai/guides/what-counts-as-n/)

## 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 optional mixed model uses R with lme4 1.1-35.1; the simulation was run in Python.
- Separate review passes checked the statistics, the citations and readability before publication.
