# Histopathology score statistics: ordinal scores, blinded scoring, fields within animals and which test

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

Canonical: https://www.licklider.ai/guides/histopathology-scores/
Section: Guides › Comparing two groups
Author: Licklider · Published 2026-11-12 · Last reviewed 2026-11-12 (UTC)

> **Quick answer.** Semi-quantitative histopathology scores (for example 0–4 inflammation grades) are **ordinal**: the steps are ordered but not necessarily equal. Score blind to group with criteria set in advance, combine the fields or sections of each animal into **one score per animal** (the median of an odd number of fields, or the observer's whole-tissue grade), and compare animals (or cages, if whole cages were treated) with a **Mann–Whitney test** (the default; Kruskal–Wallis and Dunn's test with a Holm correction for more than two groups). Choose the analysis before seeing the data. If the two groups' scores differ clearly in spread, the Brunner–Munzel test is safer than Mann–Whitney ([Mann–Whitney vs t-test](/guides/mann-whitney-vs-t-test/)). When many fields (say 5 or more) are scored per animal, a Welch t-test on each animal's mean field score is a reasonable alternative and behaved well in our simulation, although pathology guidance prefers rank tests. Fields are subsamples of the animal, not extra n. In a simulation of 8 vs 8 mice with 9 scored fields each and no real effect, a Mann–Whitney test on all **fields** gave false positives in 41.1% of studies; a Mann–Whitney test on mouse medians gave 4.1% and a t-test on mouse means 4.9%.

**Terms used below.** *Semi-quantitative score*: an ordered grade (such as 0 none, 1 minimal, 2 mild, 3 moderate, 4 severe) given by an observer using defined criteria. *Ordinal*: ordered categories whose spacing is unknown. *Field*: one microscope field (or region) scored separately. *Masking (blinding)*: scoring without knowing which group a slide belongs to. *Median*: the middle value; for an odd number of fields it is one of the grades (with an even number it can fall between grades). *IQR*: the interquartile range, the middle half of the values. *Mann–Whitney test*: a rank test comparing two groups; ties are common with scores. *Welch t-test*: a two-group t-test that does not assume equal spread. *Proportional-odds model*: a regression for ordinal outcomes that estimates one *odds ratio* for being in a higher grade. *Kruskal–Wallis test*: a rank test for three or more groups; *Dunn's test* follows it with pairwise comparisons on the same ranks. *Kappa*: a measure of agreement between observers beyond chance. *SEM*: standard error of the mean. *Asymptotic test*: one that uses a large-sample approximation; tied grades usually rule out the exact version. *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 a histopathology score? | One score per animal (median of an odd number of fields, or the observer's whole-tissue grade); Mann–Whitney test (exact when groups are small and there are no ties) | Median and IQR per group, n animals, p |
| Many fields (5 or more) scored per animal, and you want a mean difference with a CI | Mean of the field scores per animal; Welch t-test on those means, alongside the Mann–Whitney test | Mean of animal means with 95% CI, n animals |
| More than two groups | Kruskal–Wallis test on animal scores, then Dunn's test with a Holm correction | Medians per group, adjusted p |
| You want an effect size for an ordinal score | Proportional-odds model of animal scores (odds ratio for a higher grade); its test was a little liberal (6.5%) with 8 animals per group | Odds ratio (with a profile-likelihood CI from `confint`) |
| Several lesions or organs | Each score analysed separately, with a correction across them, or one pre-specified primary score | Results per score |
| Is the scoring reliable? | A second blinded observer on a subset; quadratic-weighted kappa ([agreement](/guides/inter-rater-agreement/)) | Kappa with 95% CI |

Gibson-Corley and colleagues advise that "Parametric analyses (e.g. Student's T-test) should not be used to analyze ordinal data, but rather nonparametric analyses (e.g. Mann Whitney test) should be considered"; that "the most common approach to ordinal scoring is to assign a summary score for each animal"; and, when several fields are averaged, that "for ordinal data, the median is the most appropriate measure for central tendency", adding that "this statistical axiom is not without some controversy". On blinding they write that "lack of masking can lead to unintentional observational bias that can often exaggerate treatment effects", and on reliability that agreement "is often reported by a kappa value" ([Gibson-Corley et al. 2013](https://doi.org/10.1177/0300985813485099)). Meyerholz and Beck review the same principles for tissue stains ([Meyerholz and Beck 2018](https://doi.org/10.1038/s41374-018-0057-0)), and the ordinal R package implements "cumulative link (mixed) models also known as ordered regression models, proportional odds models" ([ordinal package](https://cran.r-project.org/package=ordinal)).

## Worked example: a treatment with no real effect

Constructed data: a blinded observer scored inflammation 0–4 in **9 fields of one section per mouse**, in **8 control and 8 treated mice** (144 field scores). Each mouse has an underlying inflammation level, fields vary around it, and each field's level is cut into the five grades. By design, **the treatment does not change inflammation**. The data's random seed was the first one tried and was not chosen for its result. Stored code and data: [scores.csv](/guides/histopathology-scores/scores.csv), [model.py](/guides/histopathology-scores/model.py), [make_data.py](/guides/histopathology-scores/make_data.py), [analysis.py](/guides/histopathology-scores/analysis.py), [analysis.R](/guides/histopathology-scores/analysis.R) and [the analysis output](/guides/histopathology-scores/analysis_output.txt); analysis.py and analysis.R print the same output.

| Mouse median score | 0 | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- | --- |
| Control (8 mice) | 0 | 1 | 4 | 2 | 1 |
| Treated (8 mice) | 0 | 1 | 3 | 4 | 0 |

| Analysis, treated vs control | Result |
| --- | --- |
| A. (Not valid: fields from one mouse are not independent) Fields as n (72 vs 72), Mann–Whitney test | p = 0.26 |
| B. (Not recommended: single grades treated as numbers) Mouse median scores, Welch t-test | mean 2.38 vs 2.38, p = 1 |
| **C. Mouse median scores (8 vs 8), Mann–Whitney test** | **p = 0.91** |
| D. Mean of the 9 field scores per mouse, Welch t-test | 2.19 vs 2.38, p = 0.67 |
| E. Proportional-odds model of mouse median scores | odds ratio 1.18 for a higher grade, p = 0.86 |

With this data set even the field-level test came out non-significant, and the medians (2.5 vs 2) and means (2.19 vs 2.38) even point in opposite directions, as noise can; the simulation shows how often the field-level test misleads. Across 5,000 simulated studies of this design ([simulation.py](/guides/histopathology-scores/simulation.py), [the simulation output](/guides/histopathology-scores/simulation_output.txt)):

| Scenario | A. Fields as n (invalid) | B. Medians, t-test | C. Medians, Mann–Whitney | D. Mean of fields, t-test | E. Proportional odds |
| --- | --- | --- | --- | --- | --- |
| No effect (false positives) | **41.1%** | 4.7% | 4.1% | 4.9% | 6.5% |
| Treatment lowers inflammation by 1 SD of the between-mouse variation (power; for A a rejection rate, not power) | 86.1% | 39.1% | 35.4% | 42.1% | 44.9% |

These percentages describe this simulated design (8 mice per group, 9 fields, the variation in model.py), not every histology study. Testing 72 field scores per group as if they were independent found a "difference" in 41.1% of null studies, because fields from one mouse share its inflammation. Every analysis with one value per mouse kept false positives near 5%. The Mann–Whitney test on mouse medians was the most cautious and slightly the least powerful; the t-test on the mean of 9 field scores per mouse kept 4.9% false positives and gained a little power, because the mean of many grades is close to a continuous measure. The proportional-odds model gave an interpretable odds ratio but its likelihood-ratio test was a little liberal (6.5%) with 8 mice per group.

The code expects one row per field with columns `group` (labels exactly `control` and `treated`), `mouse`, `field` and `score` (whole numbers); use an odd number of fields per animal if you summarise by the median, so that it is a grade. If whole cages were treated, add a `cage` column and take the median of mice per cage. The code uses the normal approximation for the Mann–Whitney test; with fewer than about 6 animals per group use an exact or permutation version (for example `coin::wilcox_test(..., distribution = "exact")` in R, which handles ties). Rename your columns and labels to match, and run the code in the folder that holds the CSV file. Python needs NumPy, pandas and SciPy; the R code uses base R and MASS (shipped with R).

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

d = pd.read_csv("scores.csv")
med = d.groupby(["group", "mouse"]).score.median()                   # one score per mouse
mean = d.groupby(["group", "mouse"]).score.mean()
u = stats.mannwhitneyu(med["treated"], med["control"], alternative="two-sided", method="asymptotic")
t = stats.ttest_ind(mean["treated"], mean["control"], equal_var=False)
print(f"medians {med['treated'].median():g} vs {med['control'].median():g}, Mann-Whitney p = {u.pvalue:.2g}; mean of fields t-test p = {t.pvalue:.2g}")
# medians 2.5 vs 2, Mann-Whitney p = 0.91; mean of fields t-test p = 0.67
```

```r
d <- read.csv("scores.csv")
m <- aggregate(score ~ group + mouse, data = d, FUN = median)                   # one score per mouse
wilcox.test(score ~ group, data = m, exact = FALSE)$p.value                      # Mann-Whitney: 0.91
library(MASS); m$y <- factor(m$score, ordered = TRUE)
fit <- polr(y ~ group, data = m); exp(coef(fit))                                # odds ratio for a higher grade: 1.18
exp(confint(fit))                                                              # profile-likelihood 95% CI
```

## Before you analyse

1. **Write the scoring criteria with examples for every grade before scoring**, and keep them fixed.
2. **Score blind to group**, with slides coded by someone else, and in random order.
3. **Define the fields or sections in advance** (number, location, how they are chosen) and score the same number for every animal.
4. **Reduce to one score per animal before testing**, and use the animal (or cage, if whole cages were treated) as n.
5. **Have a second observer score a subset** and report agreement; if only one observer scored, say so and report the scoring criteria and blinding in full.

## Check what an AI assistant ran

1. **The assistant tested all fields or sections as separate observations.** Ask for one score per animal.
2. **The assistant ran a t-test on single 0–4 grades per animal and reported means with SEM.** Ask for one score per animal and a Mann–Whitney test, or, if 5 or more fields were scored per animal and it was planned, a t-test on mean field scores alongside it.
3. **The assistant did not say whether scoring was blind.** Ask how slides were coded.
4. **The assistant tested several lesion scores and reported the significant one.** Ask which score was primary and for a correction.

## Report it

> Inflammation was scored 0–4 using predefined criteria in [9] fields per section by an observer blinded to group, and the median of the field scores was taken for each mouse; a second blinded observer scored [25%] of slides (weighted kappa [value]). Mouse scores were compared between groups with a Mann–Whitney test (R [4.3.3]).

> In the constructed example, which has no effect built in, inflammation scores did not clearly differ between groups (treated scores did not tend to be higher or lower; median of mouse scores [2.5] vs [2]; Mann–Whitney p = [0.91]; n = [8] mice per group).

Replace the bracketed values with your own.

## References

- Gibson-Corley KN, Olivier AK, Meyerholz DK (2013). Principles for valid histopathologic scoring in research. *Veterinary Pathology* 50:1007–1015. [doi:10.1177/0300985813485099](https://doi.org/10.1177/0300985813485099)
- Meyerholz DK, Beck AP (2018). Principles and approaches for reproducible scoring of tissue stains in research. *Laboratory Investigation* 98:844–855. [doi:10.1038/s41374-018-0057-0](https://doi.org/10.1038/s41374-018-0057-0)
- Christensen RHB. ordinal: regression models for ordinal data. R package. [CRAN](https://cran.r-project.org/package=ordinal)

## Related

- [Mann-Whitney vs t-test](https://www.licklider.ai/guides/mann-whitney-vs-t-test/)
- [Agreement between raters](https://www.licklider.ai/guides/inter-rater-agreement/)
- [Clinical scores over time](https://www.licklider.ai/guides/clinical-scores-over-time/)
- [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 with MASS), 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.
