---
title: "Computing diversity indices in R with vegan"
description: "Compute species richness, Shannon and Simpson diversity from a site-by-species community matrix in R with vegan, with reusable code and a small worked example."
date: "2026-05-01 10:00"
date-modified: "2026-09-29"
categories: [R, biodiversity, ecology tutorial]
image: thumbnail.png
image-alt: "Horizontal bar chart of Shannon diversity across four sample plots, from Plot_D at 1.6 down to Plot_C at 0.41, in the site green palette."
---
*Updated 29 September 2026: a paragraph of checks for your own data was added: empty sampling units, single-species units, where Pielou's J is undefined, and cover data, which the count-based rarefaction tools do not accept.*
*Updated 25 September 2026: the post said Shannon values usually fall between about 1.5 and 3.5, a range with no source and no general truth. A new section, Reading a Shannon value, converts the index to an effective number of species and to evenness on the post's own plots, and says why values from unequal sampling effort do not compare.*
Alpha diversity, meaning how diverse a single site or sample is, is usually the first number an ecologist wants out of a species-abundance table. This post covers the three you'll reach for most often: **species richness**, the **Shannon index**, and the **Simpson index**, all from the `vegan` package.
We'll go from a raw community matrix to a tidy results table and a finished plot.
## A community matrix
`vegan` expects a **community matrix**: one row per site (or sample), one column per species, cells holding abundances (counts or cover). Here's a small worked example, with counts of five plant species across four meadow plots:
```{r}
#| label: community-matrix
#| message: false
library(vegan)
library(dplyr)
library(ggplot2)
paper <- "#f5f4ee"; ink <- "#16241d"; forest <- "#275139"
gold <- "#cda23f"; abandoned <- "#b5534e"; faint <- "#5d6b61"; line <- "#dad9ca"
theme_te <- theme_minimal(base_size = 12) +
theme(panel.grid.minor = element_blank(),
panel.grid.major = element_line(colour = line, linewidth = 0.3),
plot.background = element_rect(fill = paper, colour = NA),
panel.background = element_rect(fill = paper, colour = NA),
axis.title = element_text(colour = ink),
axis.text = element_text(colour = faint),
plot.title = element_text(colour = ink, face = "bold"))
community <- data.frame(
row.names = c("Plot_A", "Plot_B", "Plot_C", "Plot_D"),
Festuca = c(10, 0, 25, 4),
Trifolium = c( 8, 12, 2, 5),
Plantago = c( 5, 3, 1, 6),
Achillea = c( 2, 9, 0, 5),
Lotus = c( 0, 15, 0, 4)
)
community
```
Each row is a plot; each column a species. `Plot_C` is dominated by *Festuca*, while `Plot_D` spreads its individuals evenly across all five species.
## The three indices
`vegan` gives you each of these in a single call:
```{r}
#| label: compute-indices
richness <- specnumber(community) # number of species present
shannon <- diversity(community, index = "shannon")
simpson <- diversity(community, index = "simpson")
richness
shannon
simpson
```
A few things worth knowing:
- **`specnumber()`** simply counts the non-zero species. It's the most basic measure, and blind to abundance.
- **Shannon (H')** rises with both richness *and* evenness. Its maximum is not fixed: with S species it can reach at most ln S, so the range it can take grows with richness (the next section shows how to read a value).
- **Simpson** is returned here as 1 - D: the probability that two individuals drawn at random belong to *different* species. It runs from 0 to 1 and is less sensitive to rare species than Shannon.
## Reading a Shannon value
To read a Shannon value, convert it. exp(H') is the number of equally common species that would give the same value, and H' divided by ln S, the most H' can reach with S species, says how close the community is to perfectly even. The raw number has no scale of its own: it measures the uncertainty about which species the next individual drawn will belong to, and its size depends on the base of the logarithm, on how many species there are and on how hard the site was sampled. The chunk below computes each reading for the four plots:
```{r shannon-reading}
shannon_bits <- diversity(community, index = "shannon", base = 2)
shannon_ceiling <- log(richness) # H' if every species were equally common
pielou_j <- shannon / shannon_ceiling
effective_species <- exp(shannon) # Hill number of order q = 1
plot_totals <- rowSums(community) # individuals counted per plot
shannon_reading <- data.frame(
n = plot_totals,
S = richness,
H_nats = round(shannon, 2),
H_bits = round(shannon_bits, 2),
ln_S = round(shannon_ceiling, 2),
J = round(pielou_j, 2),
exp_H = round(effective_species, 2)
)
shannon_reading
example_h <- c(2.1, 2.4) # two published-looking values
```
**Units.** `diversity()` uses the natural log, so its H' is in nats. Software or papers that use log base 2 report bits, which is every value multiplied by 1/ln 2 = `r sprintf("%.3f", 1 / log(2))`: `Plot_D` reads `r sprintf("%.2f", shannon["Plot_D"])` in nats and `r sprintf("%.2f", shannon_bits["Plot_D"])` in bits. Base 10, which some papers use, multiplies by 1/ln 10 = `r sprintf("%.3f", 1 / log(10))`. Check the base before you set your value beside a published one.
**The ceiling.** With S species, H' reaches its maximum of ln S only when all of them are equally common. `Plot_D` sits at `r sprintf("%.2f", shannon["Plot_D"])` against a ceiling of ln `r richness["Plot_D"]` = `r sprintf("%.2f", shannon_ceiling["Plot_D"])`; `Plot_C` reaches `r sprintf("%.2f", shannon["Plot_C"])` against ln `r richness["Plot_C"]` = `r sprintf("%.2f", shannon_ceiling["Plot_C"])`. The ratio H'/ln S is Pielou's evenness J, `r sprintf("%.2f", pielou_j["Plot_D"])` and `r sprintf("%.2f", pielou_j["Plot_C"])` here. Because the ceiling moves with richness there is no general "normal" range for H': fifty equally common species give ln 50 = `r sprintf("%.2f", log(50))`, five give `r sprintf("%.2f", log(5))`, and both communities are perfectly even.
**Effective number of species.** exp(H') turns the index into the number of equally common species that would give the same value, the Hill number of order 1 (Jost 2006). Like H' itself, exp(H') computed from a sample is biased low, and [Estimating diversity with Hill numbers](../estimating-hill-number-diversity/) shows how to correct it. `Plot_D` is worth `r sprintf("%.2f", effective_species["Plot_D"])` equally common species out of `r richness["Plot_D"]` present, `Plot_C` only `r sprintf("%.2f", effective_species["Plot_C"])` out of `r richness["Plot_C"]`. This is the scale for saying how much more diverse one site is than another. Values of `r sprintf("%.1f", example_h[1])` and `r sprintf("%.1f", example_h[2])` nats look close, but they are `r sprintf("%.1f", exp(example_h[1]))` and `r sprintf("%.1f", exp(example_h[2]))` effective species, `r sprintf("%.0f", 100 * (exp(example_h[2]) / exp(example_h[1]) - 1))` per cent more at the second site.
**Effort.** Even in the same units, `r sprintf("%.1f", example_h[1])` at one site and `r sprintf("%.1f", example_h[2])` at another is not yet a comparison. The H' of a sample is biased low, because rare species are missed, and on average it climbs as more individuals are counted; if the second site was sampled harder, part or all of the gap can be effort. [When not to use Shannon](../when-not-to-use-shannon/) shows the climb on a single community, and [Pielou's evenness under unequal sampling effort](../pielou-evenness-and-sampling-effort/) shows J ranking two communities by effort rather than by evenness. Even these four plots hold between `r min(plot_totals)` and `r max(plot_totals)` individuals. Equalise effort first, by rarefying to a common count or to a common coverage ([Coverage-based rarefaction and extrapolation](../coverage-based-rarefaction/)), and compare the values after that.
## A tidy results table
Let's pull everything into one data frame, rounded for reading:
```{r}
#| label: results-table
diversity_summary <- tibble(
plot = rownames(community),
richness = richness,
shannon = round(shannon, 2),
simpson = round(simpson, 2)
) |>
arrange(desc(shannon))
diversity_summary
```
`Plot_D` comes out most diverse. It carries all five species at similar abundances, so its evenness is high. `Plot_A` and `Plot_B` land in the middle and almost tie (`r sprintf("%.2f", shannon["Plot_A"])` vs `r sprintf("%.2f", shannon["Plot_B"])`, from `r plot_totals["Plot_A"]` and `r plot_totals["Plot_B"]` individuals, so read the tie as provisional), despite very different species makeups. `Plot_C`, dominated by a single grass, scores lowest even though three species are present. That gap between *richness* and *Shannon* is the evenness effect at work.
## A plot
Finally, the bit you'll actually paste into a report: Shannon diversity per plot.
```{r}
#| label: fig-shannon
#| fig-cap: "Shannon diversity (H') across the four meadow plots."
#| fig-alt: "Bar chart of Shannon diversity for four meadow plots, the green bars labelled with their values and ordered from lowest to highest."
#| fig-width: 6
#| fig-height: 4
ggplot(diversity_summary, aes(x = reorder(plot, shannon), y = shannon)) +
geom_col(fill = "#2f6b3e", width = 0.7) +
geom_text(aes(label = shannon), hjust = -0.2, size = 3.5) +
coord_flip() +
labs(x = NULL, y = "Shannon diversity (H')") +
ylim(0, max(diversity_summary$shannon) * 1.15) +
theme_te
```
## Where to go next
This is the foundation. Realistic next steps:
- swap the toy matrix for your own data, read in with `read.csv()` into the same site-by-species shape;
- compare **Pielou's evenness** (`shannon / log(richness)`, the `J` column above) only at equal effort, and at equal coverage as well when richness differs: it moves with sampling effort, as [Pielou's evenness under unequal sampling effort](../pielou-evenness-and-sampling-effort/) shows;
- once you have many sites, move from alpha to **beta diversity** and ordination (`vegdist()`, `metaMDS()`).
Three checks belong between `read.csv()` and `diversity()` when the data are your own. First, find empty sampling units with `rowSums(community) == 0`: `diversity()` still returns a number for a row of zeros (a Shannon index of 0), and that number looks like data but describes nothing, so drop or report such units separately. Second, a unit with a single species has H' = 0 and ln S = 0, so its Pielou's J is 0/0 and comes back as `NaN`; evenness is undefined there, not zero. Third, cover values are fine for the indices themselves, which only use proportions, but not for the count-based tools: `rarefy()` accepts only integer counts, and rarefying to a common number of individuals has no meaning for percentage cover, so the effort advice above applies to count data.
Ordination is the next post. If you hit a snag adapting this to your own data, [let me know](../../contact.qmd).
## References
Jost L 2006 Oikos 113(2):363-375 (10.1111/j.2006.0030-1299.14714.x)
Shannon CE 1948 Bell System Technical Journal 27(3):379-423 (10.1002/j.1538-7305.1948.tb01338.x)
Simpson EH 1949 Nature 163(4148):688 (10.1038/163688a0)
## Related tutorials
- [When not to use Shannon](../when-not-to-use-shannon/)
- [Rarefaction and accumulation curves](../rarefaction-accumulation-curves/)
- [Coverage-based rarefaction and extrapolation](../coverage-based-rarefaction/)
- [Pielou's evenness under unequal sampling effort](../pielou-evenness-and-sampling-effort/)