Microbiome Data Basics

This tutorial introduces the structure and interpretation of marker-gene amplicon data using linked count, metadata, and taxonomy tables. It covers sequencing depth, observed zeros, compositionality, filtering, alpha diversity, dissimilarity, and ordination. Other assays may require different features and preprocessing.

Chapter 1

Interpreting observed counts

Each entry in a marker-gene count table is the number of retained reads assigned to one feature in one sample. The table reflects finite sampling and processing; it is not a census of microbial cells in the specimen.

Marker-gene terminology

Marker gene
A short DNA region used as a signpost for describing which microbes may be present.
Amplification
Making many copies of the chosen marker so the sequencing instrument can measure it.
Amplicon
The copied DNA fragment produced during amplification; this tutorial analyzes such fragments.
Taxonomy
Nested names used to classify organisms, from broad groups to narrower groups.
Gene-copy number
The number of copies of the marker gene in a genome; this can differ among microbes.
Phylogenetic tree
A branching diagram representing inferred evolutionary relatedness among features.
Physical sample

A collected specimen contains microbial material.

Measurement pipeline

Extraction, amplification, sequencing, and processing select what is observed.

Count table

Reads are assigned to features for each sample.

Sample
One collected specimen represented by one row in our teaching table.
Read
One sequencing observation retained after processing.
Feature
A unit produced by the pipeline, such as an ASV, OTU, taxon, gene, or pathway.
Count
The number of retained reads assigned to one feature in one sample.
ASV
An amplicon sequence variant: a sequence-level feature, not automatically a species.

A read count is not a cell count

If ASV1 has count 40 in sample S1, we observed 40 reads assigned to ASV1. We cannot conclude that the specimen contained exactly 40 cells. Sampling, extraction, gene-copy number, amplification, sequencing, and processing all stand between cells and the final table.

Chapter 2

The linked data objects

Many marker-gene microbiome projects use three linked tables. Sample IDs connect counts to sample metadata; feature IDs connect counts to taxonomy. Some projects also include representative sequences and a phylogenetic tree.

Three linked microbiome data tables Sample metadata connects to count-table rows through sample identifiers, while taxonomy connects to count-table columns through feature identifiers. Sample metadata group, batch, body site Count table rows = samples columns = features cells = observed reads Taxonomy phylum, genus, ... Sequences / tree optional; linked by feature ID sample_id feature_id
Never join these tables by their current row order alone. Match the explicit IDs and then verify the result.

1. Sample-by-feature count table

Synthetic count table used throughout this tutorial
sample_idASV1ASV2 ASV3ASV4ASV5ASV6
S140100050
S2802000100
S352515005
S41050300010
B1000600

Each cell is an observed count. The constructed table makes S2 exactly twice the counts of S1 and S4 exactly twice the counts of S3. B1 is a low-count negative control.

2. Sample metadata

Variables describing each sample
sample_idgroupbody_sitebatchsample_type
S1ControlGut1Biological
S2ControlGut1Biological
S3TreatmentGut1Biological
S4TreatmentGut2Biological
B1BlankNot applicable1Negative control

Metadata describe samples: treatment, time, subject, site, batch, and similar variables. Batch is not a nuisance you may ignore; it can be confused with the scientific group if the study is poorly balanced.

3. Taxonomy table

A taxonomy table attaches hierarchical labels to feature IDs. Classification can stop at different levels or be uncertain. A phylum is a broader group than a genus. Keep the original ASV IDs; a missing genus label does not mean the feature count is zero.

Illustrative annotations for the six synthetic features
feature_idphylumgenus
ASV1BacteroidotaBacteroides
ASV2BacillotaFaecalibacterium
ASV3BacillotaBlautia
ASV4PseudomonadotaPseudomonas
ASV5VerrucomicrobiotaAkkermansia
ASV6ActinomycetotaBifidobacterium

Taxonomic labels are illustrative; no biological or functional inference is intended.

Chapter 3

Validate IDs and calculate sequencing depth

Validate table structure and identifier alignment before analysis.

Import the three teaching tables

Download the files from the sidebar, preserve their names, and place them together in your project’s data folder.

RImport and validate
# Run once if tidyverse is not already installed:
install.packages("tidyverse")

# Run at the start of each new session:
library(tidyverse)

counts <- read_csv(
  "data/microbiome_counts.csv",
  show_col_types = FALSE
)
metadata <- read_csv(
  "data/microbiome_metadata.csv",
  show_col_types = FALSE
)
taxonomy <- read_csv(
  "data/microbiome_taxonomy.csv",
  show_col_types = FALSE
)

count_matrix <- as.matrix(counts[, -1])
rownames(count_matrix) <- counts$sample_id

stopifnot(anyDuplicated(counts$sample_id) == 0)
stopifnot(anyDuplicated(metadata$sample_id) == 0)
stopifnot(anyDuplicated(taxonomy$feature_id) == 0)
stopifnot(setequal(counts$sample_id, metadata$sample_id))
stopifnot(setequal(colnames(count_matrix), taxonomy$feature_id))
stopifnot(!anyNA(count_matrix))
stopifnot(all(count_matrix >= 0))
stopifnot(all(count_matrix == floor(count_matrix)))

stopifnot() stops execution when an assumption is false. setequal() checks that the same IDs appear regardless of order. Before joining values, reorder explicitly and confirm identical() order.

In counts[, -1], the empty space means “all rows” and -1 removes the first column, which contains IDs rather than counts. as.matrix() then creates a numeric matrix; rownames() attaches the sample IDs to its rows.

Library size is a row sum

Library size of sample i = sum of its feature counts
RObserved sequencing depth
library_size <- rowSums(count_matrix)
library_size
S1 S2 S3 S4 B1 55 110 50 100 6

B1 is a negative control with six retained reads; exclude it from biological summaries and evaluate it during contamination assessment.

Different library sizes can represent the same observed proportions Sample S1 has 55 reads and S2 has 110 reads, but both bars have the same proportions across ASV1, ASV2, and ASV5. S1 S2 55 reads 110 reads ASV1 72.7% ASV2 18.2% ASV5 9.1%
S2 has twice as many observed reads as S1, but the two observed compositions are identical. More reads do not automatically mean more microbial cells in the original specimen.

Terminology

Avoid “S2 has twice the microbial abundance.” Say “S2 has twice the library size” or “twice the observed sequencing depth.” Absolute microbial load requires additional measurement or design information.

Chapter 4

Why an observed zero is difficult

A zero in the count table says only that no read was observed for that sample-feature combination. Different underlying processes can produce the same visible value.

Structural and sampling zeros can look identical A truly absent feature and a present but undetected feature both lead to a zero in the observed count table, while a captured feature leads to a positive count. Underlying feature state not directly observed Truly absent possible structural zero Present at low level not captured in finite reads Present and captured Observed 0 Observed > 0
The mechanism cannot be inferred from a single observed zero. Repeated sampling, controls, study design, and an appropriate model may provide additional evidence.
Structural zero
The feature is genuinely absent from the target community under the scientific definition.
Sampling zero
The feature is present at a low level but was not captured by finite sampling or measurement.
NA
The value is unknown or unavailable. It is missing data, not an observed count of zero.

Zeros in the synthetic dataset

  • ASV3 is zero in S1 and S2 but observed in S3 and S4. The table alone does not reveal why.
  • ASV4 occurs only in negative control B1. That is a contamination warning to investigate, not automatic proof.
  • ASV6 is observed in treatment samples only. This small descriptive dataset cannot establish a treatment effect.

Pseudocounts do not identify zero mechanisms

Some later methods compare logarithms of feature ratios, but the logarithm of zero is undefined. Adding a small number can make that calculation computable. It does not reveal why the original observation was zero or turn non-detection into biological presence. Preserve the original counts and document the zero-handling rule.

Chapter 5

Relative abundance and compositionality

Relative abundance divides each feature count by its sample’s library size. It answers “What share of the observed reads belongs to this feature?” Every sample is rescaled to a total of 1, or 100%.

Relative abundance of feature j in sample i = countij / library sizei
RCounts to proportions
relative_matrix <- sweep(
  count_matrix,
  MARGIN = 1,
  STATS = rowSums(count_matrix),
  FUN = "/"
)

round(relative_matrix, 3)
rowSums(relative_matrix)

sweep() divides every row by its own row sum. Confirm that each nonempty row now sums to 1. Handle zero-library samples explicitly before division; otherwise they produce undefined values.

Selected relative-abundance profiles
sampleASV1ASV2 ASV3ASV5ASV6
S172.7%18.2%0%9.1%0%
S272.7%18.2%0%9.1%0%
S310%50%30%0%10%
S410%50%30%0%10%

The compositional constraint

Because proportions sum to one, increasing one component’s share necessarily reduces at least one other share. Relative abundance therefore describes ratios among observed components, not absolute microbial load.

  • Relative abundance is useful for descriptive plots.
  • It does not recover absolute microbial load.
  • “Twice the proportion” does not necessarily mean “twice as many cells.”
  • Counts, proportions, and log-ratios are different representations and can answer different questions.
  • Always retain the original count table.
Chapter 6

Prevalence, abundance, and filtering

Prevalence is the proportion of samples in which a feature was detected. Abundance describes how much was observed. A feature can be widespread but low in abundance, or abundant in only a few samples.

Prevalence of feature j = number of samples with countij > 0 / number of samples
RUse biological samples for this summary
counts_long <- counts |>
  pivot_longer(
    -sample_id,
    names_to = "feature_id",
    values_to = "count"
  )

biological_ids <- metadata |>
  filter(sample_type == "Biological") |>
  pull(sample_id)

feature_summary <- counts_long |>
  filter(sample_id %in% biological_ids) |>
  group_by(feature_id) |>
  summarise(
    detected = sum(count > 0),
    prevalence = mean(count > 0),
    total_reads = sum(count),
    .groups = "drop"
  )

feature_summary
Detection across four biological samples
featuredetectedprevalencetotal reads
ASV14 of 4100%135
ASV24 of 4100%105
ASV32 of 450%45
ASV40 of 40%0
ASV52 of 450%15
ASV62 of 450%15

Filtering changes the scientific object

  • Rare features are not automatically errors or irrelevant.
  • Filtering changes richness, distances, and possibly conclusions.
  • Define filtering rules independently of desired visual separation.
  • Document the threshold and rationale; when possible, check sensitivity to another reasonable rule.
  • Sample filtering for low depth and feature filtering for low prevalence are different decisions.
  • Use negative controls and suitable methods to assess contamination; an abundance cutoff is not enough.
Chapter 7

Alpha diversity: within one sample

Alpha diversity summarizes within-sample feature variety. No single index represents all aspects of diversity; different indices emphasize different properties of the observed community.

Observed richness

Observed richness = number of features with count > 0

Richness records which features were detected, but ignores how reads are distributed. It is sensitive to sequencing effort, detection, and filtering.

Shannon diversity

H = −Σ pj log(pj), summed over positive proportions

Shannon diversity reflects both richness and evenness. With the same richness, a sample whose reads are spread more evenly across features usually has a larger value than one dominated by a single feature. R’s log() uses the natural logarithm, so that is the convention used below.

RObserved diversity summaries
bio_matrix <- count_matrix[
  rownames(count_matrix) %in% biological_ids,
  ,
  drop = FALSE
]

bio_relative <- sweep(
  bio_matrix,
  1,
  rowSums(bio_matrix),
  "/"
)

alpha_summary <- tibble(
  sample_id = rownames(bio_matrix),
  library_size = rowSums(bio_matrix),
  observed_richness = rowSums(bio_matrix > 0),
  shannon = apply(
    bio_relative,
    1,
    function(p) -sum(p[p > 0] * log(p[p > 0]))
  )
)

alpha_summary
Alpha-diversity summaries for the synthetic biological samples
samplelibrary sizerichnessShannon
S15530.760
S211030.760
S35041.168
S410041.168

Interpretation boundary

These values summarize the observed table. A higher diversity value is not universally “better,” does not identify which features differ, and does not prove a biological mechanism. Always state the index, preprocessing, sample type, and comparison.

Unequal library sizes

Observed richness is especially sensitive to library size. Real analyses may use rarefaction, coverage-based comparisons, or statistical models for different purposes. There is no universal correction: choose a method that matches the question and report it.

Chapter 8

Beta diversity, dissimilarity, and ordination

Beta-diversity analyses quantify differences among sample profiles. The primary object is a distance or dissimilarity matrix containing one value for each sample pair.

Terminology varies across fields, so do not report only “beta diversity.” Name the representation, exact distance or dissimilarity, feature set, and downstream method.

Bray–Curtis dissimilarity

BC(x, y) = Σ |xj − yj| / Σ (xj + yj)
  • 0 means the two input profiles are identical.
  • Values nearer 1 indicate greater dissimilarity under this definition.
  • The answer depends on the values you put into the formula and which features you retained.
RCompare two vectors
bray_curtis <- function(x, y) {
  sum(abs(x - y)) / sum(x + y)
}

bray_curtis(
  count_matrix["S1", ],
  count_matrix["S2", ]
)

bray_curtis(
  relative_matrix["S1", ],
  relative_matrix["S2", ]
)
[1] 0.3333333 [1] 0

Raw counts make S1 and S2 look different because S2 has twice the total count. Their relative profiles are identical, so relative-abundance Bray–Curtis is 0. Preprocessing is part of the comparison’s definition, not an invisible preliminary step.

Bray–Curtis dissimilarities computed from the relative-abundance table
sampleS1S2S3S4B1
S1000.7180.7181
S2000.7180.7181
S30.7180.718001
S40.7180.718001
B111110

In this matrix, S1 versus S3 is 0.718, whereas S1 versus S2 is 0. PCoA begins with a complete dissimilarity matrix such as this.

Different distances emphasize different information
MeasureMain input ideaQuestion emphasized
JaccardPresence / absenceWere the same features detected?
Bray–CurtisNonnegative abundancesHow different are observed profiles?
AitchisonLog-ratio structure; zeros need an explicit strategyHow do relative relationships among parts differ?
UniFracFeature profiles + treeHow different are samples considering phylogeny?

Principal coordinates analysis

Principal coordinates analysis (PCoA) places samples on a few axes so the displayed distances approximate the original pairwise distances. A two-dimensional display usually loses information, so report how much variation the displayed axes represent.

Schematic principal coordinates plot Control samples appear near each other on the left, treatment samples near each other on the right, and the negative control is separated above them. This is a schematic, not a computed result. PCoA axis 1 PCoA axis 2 S1 S2 S3 S4 B1 Control Treatment Negative control schematic only
Nearby points have similar profiles under the selected representation and distance. Separation is descriptive, not automatically a hypothesis test or evidence that treatment caused the difference.

Ordination reporting checklist

  1. What representation and feature filtering were used?
  2. Which distance measure was used?
  3. How much information do the displayed axes represent?
  4. Could subject, batch, depth, time, or another variable explain the pattern?
  5. Which formal model, uncertainty measure, and study design support the claim?
Chapter 9

Microbiome analysis workflow

Begin with the study question, measurement process, and structure of each data object before selecting analytical or visualization methods.

  1. Write the scientific question in one sentence.
  2. Identify the count table’s row and column orientation.
  3. Match sample and feature IDs across objects.
  4. Inspect metadata, controls, batch, missingness, and possible confounders.
  5. Calculate library sizes and examine their distribution.
  6. Summarize prevalence and abundance without silently discarding features.
  7. Choose and document filtering based on the question and design.
  8. Choose a representation and distance appropriate to the target quantity.
  9. Calculate within-sample or between-sample summaries.
  10. Interpret results together with study design and uncertainty.

Build a long table for plotting

RCounts + sample metadata + taxonomy
count_feature_ids <- setdiff(names(counts), "sample_id")

unmatched_samples <- anti_join(
  counts |> distinct(sample_id),
  metadata |> distinct(sample_id),
  by = "sample_id"
)
unmatched_features <- anti_join(
  tibble(feature_id = count_feature_ids),
  taxonomy |> distinct(feature_id),
  by = "feature_id"
)

stopifnot(nrow(unmatched_samples) == 0)
stopifnot(nrow(unmatched_features) == 0)

plot_data <- counts |>
  pivot_longer(
    -sample_id,
    names_to = "feature_id",
    values_to = "count"
  ) |>
  left_join(metadata, by = "sample_id") |>
  left_join(taxonomy, by = "feature_id") |>
  group_by(sample_id) |>
  mutate(
    library_size = sum(count),
    relative_abundance = if_else(
      library_size > 0,
      count / library_size,
      NA_real_
    )
  ) |>
  ungroup()

stopifnot(
  nrow(plot_data) == nrow(counts) * (ncol(counts) - 1)
)

The two anti_join() checks detect count-table IDs with no matching metadata or taxonomy record. The final row-count check detects unexpected row multiplication; ncol(counts) - 1 is the number of feature columns after excluding sample_id. Never treat a successful join as proof that labels are correct.

Plot observed composition

RDescriptive stacked bars
plot_data |>
  filter(sample_type == "Biological") |>
  ggplot(
    aes(
      x = sample_id,
      y = relative_abundance,
      fill = feature_id
    )
  ) +
  geom_col(width = 0.78) +
  facet_grid(~ group, scales = "free_x", space = "free_x") +
  scale_y_continuous(
    labels = function(x) paste0(x * 100, "%")
  ) +
  labs(
    title = "Observed composition by sample",
    subtitle = "Synthetic tutorial data",
    x = NULL,
    y = "Relative abundance",
    fill = "Feature"
  ) +
  theme_minimal(base_size = 12)

Stacked bars reveal dominant features and sample composition. They are weak for comparing thin segments across many samples. For larger data, show only a declared set of features, combine the remainder as “Other,” and preserve the full table for analysis.

Reporting observed composition

Prefer: “In this synthetic dataset, treatment samples assign a larger observed share of reads to ASV3 than control samples.” Avoid: “Treatment increased ASV3.” The second sentence makes a causal and absolute claim that this tiny descriptive example cannot support.

Chapter 10

Exercises and reference

Exercise 1: Describe S1 versus S2 in one precise sentence

S2 has twice the observed sequencing depth of S1, while the two samples have identical observed relative compositions across the six features.

Exercise 2: Interpret ASV4 without overclaiming

ASV4 is detected only in negative control B1. This pattern warrants investigation for contamination or another technical source, but this table alone does not prove the mechanism.

Exercise 3: Distinguish 0 from NA

A count of 0 means no read was observed for that sample-feature cell. NA means the value or annotation is unavailable. For example, an unassigned genus label should be NA, not count 0.

Exercise 4: Why is raw-count Bray–Curtis nonzero for S1 versus S2?

S2’s counts are exactly twice S1’s, so the raw vectors differ in magnitude. Once each row is converted to proportions, the profiles are identical and the dissimilarity is 0.

Synthesis exercise: Write three sentences about the dataset
  1. One sentence about sequencing depth.
  2. One sentence about Control and Treatment composition.
  3. One sentence about zeros.

One valid answer: “S2 and S4 have twice the library sizes of S1 and S3, respectively. In this synthetic dataset, treatment samples devote a larger observed proportion to ASV3 than control samples. The zero cells cannot be classified as structural or sampling zeros from the table alone.”

Glossary

Abundance
How much of a feature was observed under a stated representation.
Alpha diversity
Diversity summarized within one sample.
Beta diversity
Variation or dissimilarity among samples; always state the definition used.
Composition
Parts represented relative to a constrained whole.
Feature
The unit represented by a count-table row or column, such as an ASV or pathway.
Library size
Total retained reads observed for one sample.
Metadata
Variables describing samples, subjects, collection, or processing.
Ordination
A lower-dimensional display of multivariate relationships.
Prevalence
Proportion of samples in which a feature was detected.
Relative abundance
A feature count divided by the sample’s total count.
Sampling zero
A possibly present feature was not observed in finite sampling.
Structural zero
A feature is genuinely absent from the target community.
Taxonomy
Hierarchical labels assigned to microbial features.

Learning outcomes

  • connect count, metadata, and taxonomy tables by their identifiers;
  • distinguish library size from absolute microbial load;
  • explain why one observed zero does not identify its generating process;
  • state what relative abundance preserves and discards;
  • distinguish prevalence, alpha diversity, and between-sample dissimilarity; and
  • interpret ordination as a descriptive approximation rather than a causal conclusion.

References and further reading