Lesson 8 of 13 · 12 min
Building the Feature/Abundance Table
A feature table is the sample-by-feature count matrix that every downstream analysis reads from: rows are features (ASVs, OTUs, or taxa) and columns are samples, with each cell holding the reads assigned to that feature in that sample. It never travels alone; it is joined to a sample metadata sheet keyed by sample ID and to a taxonomy table keyed by feature ID. Those two joins are what turn a raw count grid into something you can ask biological questions of.
#feature-id S1 S2 S3 S4
ASV_0001 1204 0 58 932
ASV_0002 0 342 410 0
ASV_0003 77 12 0 145# Sanity-check the table before you trust it: depth, sparsity, per-sample counts.
biom summarize-table \
--input-fp feature-table.biom \
--output-fp table-summary.txt
head -n 20 table-summary.txtNum samples: 4
Num observations: 3
Total count: 3180
Table density (fraction of non-zero entries): 0.667
Counts/sample summary:
Min: 354.0
Max: 1281.0
Median: 772.500
Mean: 795.000
Std. dev.: 392.788
Sample Metadata Categories: None provided
Observation Metadata Categories: None provided
Counts/sample detail:
S2: 354.0
S3: 468.0
S4: 1077.0
S1: 1281.0
Filter, then collapse
# Drop noise: features with fewer than 10 reads total, and features seen
# in fewer than 2 samples (a prevalence filter). Do this before normalizing.
qiime feature-table filter-features \
--i-table table.qza \
--p-min-frequency 10 \
--p-min-samples 2 \
--o-filtered-table table-filt.qza
# Aggregate ASVs up to genus (level 6 in a 7-rank kingdom-to-species taxonomy).
qiime taxa collapse \
--i-table table-filt.qza \
--i-taxonomy taxonomy.qza \
--p-level 6 \
--o-collapsed-table genus-table.qzaRead counts are compositional: the library size (total reads per sample) is set by the sequencer, not the biology, so an absolute count is meaningless on its own and only ratios between features carry information. Because every sample's parts are tied to that arbitrary total, an apparent rise in one feature forces others down, inducing spurious negative correlations and making raw counts unsafe to compare across samples. Filtering and collapsing change which parts you count, but the data stays compositional, so the next decision is how to put samples on a common footing.
Choosing a normalization
Rarefaction subsamples every sample down to a common depth, equalizing library size, and remains the standard input for alpha- and beta-diversity metrics, but it discards reads and adds sampling noise, so avoid it for differential abundance. Total-sum scaling (TSS), or relative abundance, divides each count by its sample total to give proportions that are easy to read and plot, yet the result is still compositional and breaks correlation and many standard tests. The centered log-ratio (CLR) takes the log of each count over the sample's geometric mean, moving the data into a real coordinate space where Euclidean distance and ordinary statistics behave, which is why tools like ALDEx2 and ANCOM-BC operate in log-ratio space.
# QIIME exports tables as features x samples; transpose to samples x features
# so that each normalization is computed per sample (across features).
counts <- t(as.matrix(read.delim("genus-table.tsv", row.names = 1, check.names = FALSE)))
# TSS: relative abundance, each row sums to 1.
tss <- sweep(counts, 1, rowSums(counts), "/")
# CLR: log-ratio to the per-sample geometric mean.
# Add a pseudocount first because log(0) is undefined.
pc <- counts + 0.5
gm <- exp(rowMeans(log(pc))) # geometric mean of each sample
clr <- log(sweep(pc, 1, gm, "/")) # log(x_i / geometric_mean)
round(clr[, 1:3], 2) g__Bacteroides g__Prevotella g__Faecalibacterium
S1 2.14 -1.87 0.42
S2 -0.63 3.01 1.55
S3 1.02 0.88 -2.31
S4 1.77 -0.44 0.10
Try it yourself: Starting from genus-table.qza, rarefy it to an even depth with qiime feature-table rarefy --p-sampling-depth 1000, and separately compute the CLR matrix in R. Build a genus-by-sample bar chart from the TSS proportions and an Aitchison PCA with prcomp() on the CLR matrix, then note how the CLR ordination separates samples that the proportion bars make look nearly identical.