BIOL-410 · Lesson 3 of 12 · 26 min

Read processing, k-mers and coverage

Raw reads are never analyzed as they come off the instrument. They are checked for quality, trimmed of adapters and poor-quality ends, screened for contamination, and summarized in ways that reveal properties of the genome before any assembly or mapping is done. This lesson covers those first steps and two ideas that run through the rest of the course: coverage, the number of reads covering each position, and k-mers, the short words of fixed length into which reads can be broken. Counting k-mers, it turns out, lets you estimate a genome's size, heterozygosity and repeat content from raw reads alone.

Quality control

Quality control tools such as FastQC summarize a set of reads with a standard series of plots, and experienced analysts read them like a clinician reads a chart. The per-position quality plot shows how base quality changes along the read; a gentle decline toward the 3' end is normal for Illumina data, but a sudden drop across all reads suggests an instrument problem. The GC content distribution should be roughly normal around the genome's expected value; a second peak suggests contamination with another organism. Adapter content rises toward read ends when inserts are shorter than the read length, so the sequencer reads through the insert into the adapter. Overrepresented sequences may be adapter dimers, rRNA in an RNA-seq library, or a highly expressed gene. Duplication levels reflect library complexity: low-input libraries yield many duplicates.

None of these plots is a pass or fail test in isolation. An RNA-seq library is expected to show high duplication, because highly expressed genes produce many identical fragments, while the same level in a whole-genome library would indicate a problem. The skill lies in knowing what is expected for each experiment. Tools such as MultiQC gather reports from many samples into one view, so that an outlier sample stands out at a glance.

Trimming and filtering

Adapter trimming searches each read for the beginning of the adapter sequence, allowing a few mismatches, and removes it and everything after it. For paired-end reads, the overlap between the two reads can locate the adapter even when it is short. Quality trimming removes low-quality bases, usually from the 3' end. A simple and widely used approach is a sliding window: scan from the 5' end and cut where the average quality in a window of, say, four bases first falls below a threshold. A more principled approach, due to Richard Mott and used in early base-calling software, subtracts a threshold from each base's quality and keeps the segment with the largest running sum, which is the maximum-scoring subarray problem.

Two quality-trimming algorithms compared on invented quality strings
# Mott-style quality trimming: keep the segment maximizing the sum of (Q - threshold).
def mott_trim(qual, threshold=20):
    best, best_start, best_end = 0, 0, 0
    run, start = 0, 0
    for i, q in enumerate(qual):
        run += q - threshold
        if run <= 0:
            run, start = 0, i + 1
        elif run > best:
            best, best_start, best_end = run, start, i + 1
    return best_start, best_end

def sliding_trim(qual, window=4, threshold=20):
    for i in range(len(qual) - window + 1):
        if sum(qual[i:i + window]) / window < threshold:
            return 0, i
    return 0, len(qual)

reads = {
    'good tail':  [38] * 40 + [30, 28, 25, 22, 20, 18],
    'bad tail':   [38] * 30 + [12, 10, 8, 2, 2, 2, 2, 2, 2, 2],
    'bad start':  [8, 9, 12] + [37] * 40,
    'dip middle': [38] * 20 + [10, 8, 12] + [38] * 20,
}
for name, q in reads.items():
    m = mott_trim(q)
    s = sliding_trim(q)
    print(f"{name:11s} length {len(q):2d}  Mott keeps {m[0]:2d}-{m[1]:2d}  sliding window keeps {s[0]:2d}-{s[1]:2d}")
Output
good tail   length 46  Mott keeps  0-44  sliding window keeps  0-46
bad tail    length 40  Mott keeps  0-30  sliding window keeps  0-29
bad start   length 43  Mott keeps  3-43  sliding window keeps  0- 0
dip middle  length 43  Mott keeps  0-43  sliding window keeps  0-19

The comparison shows why algorithm choice matters. For a brief dip in the middle of an otherwise excellent read, the sliding window cuts the read at the dip and discards the good second half, while the Mott method keeps the whole read because the good bases outweigh the poor ones. For a read that begins with a few poor bases, the sliding window as written, scanning from the start, cuts at position zero and discards the entire read, whereas the Mott method simply removes the poor bases, because it finds the best segment anywhere in the read. After trimming, reads shorter than a minimum length, often 30 to 50 bases, are discarded, and for paired reads both mates are usually discarded together or kept as unpaired reads.

Modern aligners tolerate errors and can soft-clip, or ignore, poorly matching read ends during mapping (lesson 6), so aggressive quality trimming is less necessary than it once was and can even bias results. Adapter trimming, however, remains important, especially for short inserts such as small RNA libraries or ancient DNA. Contamination screening compares a sample of reads against databases of likely contaminants, such as human DNA in a bacterial culture, PhiX control DNA added to Illumina runs, or common laboratory organisms.

Coverage and the Lander–Waterman model

If \(N\) reads of length \(L\) are drawn from random positions in a genome of size \(G\), the average coverage, or depth, is \(c = NL/G\). In 1988 Eric Lander and Michael Waterman modeled the placement of reads as a Poisson process. The number of reads covering a given base then follows approximately a Poisson distribution with mean \(c\), so the probability that a base is not covered at all is \(e^{-c}\). At 5-fold coverage about 0.7 percent of bases are missed; at 10-fold, 0.005 percent. The expected number of gaps between contiguous covered regions, if a minimum overlap of \(T\) bases between adjacent reads is required for assembly, is approximately

\[c = \frac{NL}{G}, \qquad P(\text{base uncovered}) = e^{-c}, \qquad \mathrm{E}[\text{gaps}] \approx N e^{-c(1 - T/L)}\]

The model is idealized. Real coverage is overdispersed: regions of extreme GC content, low mappability or difficult structure are covered less evenly than the Poisson model predicts, so real projects need more depth than the formula suggests. Even so, it explains why coverage targets exist. For germline variant calling in a diploid genome, about 30-fold average coverage is standard, because a heterozygous site needs enough reads from both alleles to be distinguished from errors (lesson 7). For de novo assembly with long reads, 20 to 40-fold is typical; for detecting low-frequency mutations in a tumor, several hundred-fold or more.

Coverage of randomly placed reads compared with the Lander–Waterman prediction
# Simulate random read placement and compare observed coverage with the Poisson model.
import math, random
random.seed(410)
G, L = 200_000, 150
for c_target in (1, 3, 5, 10):
    N = round(c_target * G / L)
    depth = [0] * (G + 1)
    for _ in range(N):
        s = random.randrange(G - L + 1)
        depth[s] += 1
        depth[s + L] -= 1
    run, uncovered, total = 0, 0, 0
    for i in range(G):
        run += depth[i]
        total += run
        uncovered += run == 0
    c = N * L / G
    print(f"target {c_target:2d}x: mean depth {total / G:5.2f}, uncovered {uncovered / G:.4%}, "
          f"Poisson predicts {math.exp(-c):.4%}")
Output
target  1x: mean depth  1.00, uncovered 35.7875%, Poisson predicts 36.7971%
target  3x: mean depth  3.00, uncovered 5.4560%, Poisson predicts 4.9787%
target  5x: mean depth  5.00, uncovered 0.6575%, Poisson predicts 0.6736%
target 10x: mean depth 10.00, uncovered 0.0095%, Poisson predicts 0.0045%

The simulated uncovered fraction is close to the Poisson prediction at low coverage. (The slight shortfall of coverage near the genome's ends, where fewer reads can start, is an edge effect of a linear simulation.) Real data would show more uncovered bases than the model, because real reads are not placed uniformly.

k-mers and the k-mer spectrum

A k-mer is a substring of length \(k\). A read of length \(L\) contains \(L - k + 1\) overlapping k-mers. With four bases there are \(4^k\) possible k-mers: 1,048,576 for \(k = 10\), and over \(4 \times 10^{12}\) for \(k = 21\). Because DNA is double-stranded and reads come from both strands, analyses usually count canonical k-mers, taking for each k-mer the lexicographically smaller of it and its reverse complement. The choice of \(k\) is a trade-off. If \(k\) is too small, many k-mers occur by chance at multiple places in the genome; once \(4^k\) greatly exceeds the genome size, most k-mers are unique. If \(k\) is too large, more k-mers contain a sequencing error, and fewer error-free k-mers are available. Values of 21 to 31 are common for genomes of bacteria to mammals.

Counting how many times each distinct k-mer occurs across all reads, and then counting how many distinct k-mers have each multiplicity, gives the k-mer spectrum, a histogram of remarkable diagnostic value. Sequencing errors create k-mers that occur only once or twice, forming a tall peak at the far left. Genuine k-mers from single-copy sequence form a peak near the k-mer coverage, which is lower than read coverage by a factor of \((L - k + 1)/L\). In a diploid genome, k-mers covering a heterozygous site occur on only one haplotype and form a second peak at half that depth; its height relative to the main peak reflects heterozygosity. Repeats contribute k-mers at two, three or more times the main peak.

A histogram with k-mer multiplicity on the horizontal axis from 1 to 100 and number of distinct k-mers on the vertical axis. A very tall peak at multiplicity 1 to 3 is labeled errors. A smaller peak at about 25 is labeled heterozygous k-mers, half of the main peak position. A tall peak at about 50 is labeled homozygous single-copy k-mers. A long low tail extending to the right at about 100 and beyond is labeled repeats.
A k-mer spectrum from a diploid genome, showing error, heterozygous, homozygous and repeat components (simulated shape)

The spectrum yields a simple estimate of genome size. If the main peak lies at k-mer depth \(d\), and the total number of k-mers in the reads, excluding the error peak, is \(K\), then the genome size is approximately \(G \approx K/d\): each position in the genome is represented about \(d\) times. Tools such as GenomeScope fit a mixture model to the spectrum and estimate genome size, heterozygosity, repeat content and error rate together, all before assembly. This is invaluable for planning: a plant thought to have a 1 Gb genome that turns out to have a 3 Gb, highly heterozygous genome needs a very different sequencing budget.

Counting canonical k-mers and estimating genome size from the spectrum peak
# Estimate genome size from the k-mer spectrum of simulated reads with sequencing errors.
import random
from collections import Counter
random.seed(7)
G, L, cov, err, k = 60_000, 100, 25, 0.01, 21
genome = ''.join(random.choice('ACGT') for _ in range(G))
comp = str.maketrans('ACGT', 'TGCA')
def canonical(s):
    r = s.translate(comp)[::-1]
    return min(s, r)
counts = Counter()
n_reads = cov * G // L
for _ in range(n_reads):
    s = random.randrange(G - L + 1)
    read = list(genome[s:s + L])
    for i in range(L):
        if random.random() < err:
            read[i] = random.choice([b for b in 'ACGT' if b != read[i]])
    read = ''.join(read)
    if random.random() < 0.5:
        read = read.translate(comp)[::-1]
    for i in range(L - k + 1):
        counts[canonical(read[i:i + k])] += 1
spectrum = Counter(counts.values())
low_cut = 5
peak = max((m for m in spectrum if m >= low_cut), key=lambda m: spectrum[m])
solid = sum(m * n for m, n in spectrum.items() if m >= low_cut)
print('distinct k-mers seen once:', spectrum[1])
print('peak k-mer depth:', peak, ' expected about', round(cov * (L - k + 1) / L * (1 - err) ** k, 1))
print('estimated genome size:', round(solid / peak), ' true size:', G)
Output
distinct k-mers seen once: 218072
peak k-mer depth: 15  expected about 16.2
estimated genome size: 64763  true size: 60000

The peak lies where expected once errors are allowed for: read coverage of 25 becomes k-mer coverage of 20, and the roughly one in five 21-mers that contain an error are lost from the peak, leaving about 16. The size estimate is within about 8 percent of the truth; it runs slightly high because some error-containing k-mers recur often enough to pass the cut-off. Tens of thousands of distinct k-mers occur only once, almost all created by errors; counting tools discard them, and assemblers and error-correction programs use the same principle, treating k-mers well above the error threshold as solid and correcting read bases that create weak k-mers. Real k-mer counters such as Jellyfish and KMC handle billions of k-mers with efficient hashing and disk-based partitioning, and probabilistic structures such as Bloom filters store k-mer membership in a fraction of the memory.

Sketches and minimizers

Comparing whole read sets or genomes k-mer by k-mer is expensive, so many tools work with a sketch, a small random-like sample of k-mers chosen consistently. MinHash, the idea behind the Mash tool, keeps the \(s\) k-mers with the smallest hash values; the fraction shared between two sketches estimates the Jaccard similarity of the full k-mer sets, which in turn estimates the average nucleotide identity of two genomes. With sketches of a thousand k-mers, millions of bacterial genomes can be compared in minutes, which is how public databases are screened for close relatives of a new isolate. Minimizers, the smallest k-mer in each window of consecutive k-mers, give a related way to choose representative k-mers along a sequence; they underlie the minimap2 aligner (lesson 6) and many long-read assemblers.

Read processing steps before assembly or mapping
StepPurposeTypical toolsWhat can go wrong
Quality reportSpot instrument, library or contamination problemsFastQC, MultiQCMisreading expected features (RNA-seq duplication) as failures
Adapter and quality trimmingRemove adapter read-through and poor basesCutadapt, fastp, TrimmomaticOver-trimming bias; losing mate pairing
Contamination screenDetect reads from other organismsKraken2, FastQ ScreenDatabase gaps cause false absence
k-mer counting and profilingEstimate size, heterozygosity, repeats, errorsJellyfish, KMC, GenomeScopeWrong k or polyploidy misfits the model
Sketch comparisonFind close relatives, check identityMash, sourmashSketches too small for close comparisons

Worked problem

Planning a genome project from a k-mer spectrum

An initial Illumina run on an invented wild grass gives 60 Gb of reads (150 bp). The 21-mer spectrum has an error peak, a peak at depth 18 and a taller peak at depth 36. The total number of k-mers outside the error peak is about 3.1 x 10^10. What can you infer?

Show solution
  1. Two peaks with one at half the depth of the other indicate a diploid (or diploid-like) genome with substantial heterozygosity; the peak at 36 is homozygous k-mers.
  2. Genome size is estimated as K divided by the homozygous peak depth: 3.1 x 10^10 / 36 ≈ 8.6 x 10^8, about 860 Mb.
  3. Read coverage is about 60 Gb / 0.86 Gb ≈ 70-fold; the k-mer depth of 36 is lower because each 150 bp read contains 130 21-mers and errors remove some.
  4. A tall heterozygous peak warns that a short-read assembly would fragment where haplotypes differ; long reads and a haplotype-aware assembler would be appropriate.
  5. Check the right-hand tail for repeat content before deciding how much long-read coverage to buy.

Answer: A heterozygous diploid genome of roughly 0.86 Gb; plan haplotype-resolved long-read assembly.

Common mistakes

  • MistakeTreating every FastQC warning as a failed sample.

    InsteadInterpret metrics in context; high duplication is expected in RNA-seq, for example.

  • MistakeTrimming aggressively by default.

    InsteadModern aligners soft-clip poor ends; heavy quality trimming can bias coverage and shorten useful reads.

  • MistakeAssuming coverage is Poisson in real data.

    InsteadReal coverage is overdispersed by GC and mappability effects; plan for more depth than the model suggests.

  • MistakeConfusing k-mer depth with read depth.

    Insteadk-mer depth is lower by the factor (L − k + 1)/L and further by errors.

  • MistakeChoosing a very small k for genome profiling.

    InsteadWhen 4^k is not much larger than the genome size, k-mers repeat by chance and the spectrum blurs.

Summary and key terms

  • Quality control reports are interpreted in the context of the experiment; MultiQC compares many samples at once.
  • Adapter trimming remains essential; quality trimming by sliding window or the Mott maximum-segment method should be moderate.
  • Coverage c = NL/G; under the Lander–Waterman Poisson model the uncovered fraction is e^−c, but real coverage is overdispersed.
  • k-mers are substrings of length k; canonical k-mers merge a k-mer with its reverse complement.
  • The k-mer spectrum reveals errors, heterozygosity, single-copy sequence and repeats, and estimates genome size as total k-mers divided by peak depth.
  • MinHash sketches and minimizers summarize sequences for fast comparison and mapping.
trimming
Removing adapter sequence and low-quality bases from reads.
coverage
The number of reads overlapping a position, or its average over a genome.
Lander–Waterman model
A Poisson model of random read placement predicting coverage and gaps.
k-mer
A substring of length k.
canonical k-mer
The lexicographically smaller of a k-mer and its reverse complement.
k-mer spectrum
A histogram of how many distinct k-mers occur at each multiplicity.
heterozygosity
The proportion of sites at which the two haplotypes of a diploid differ.
Bloom filter
A probabilistic data structure that tests set membership compactly with some false positives.
MinHash sketch
A subset of k-mers with the smallest hash values, used to estimate set similarity.
minimizer
The smallest k-mer, by a chosen order, within a window of consecutive k-mers.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. A 5 Mb genome is sequenced with 2 million 150 bp reads. What is the mean coverage?

    Show answer

    2 x 10^6 x 150 / 5 x 10^6 = 60-fold.

  2. At 3-fold coverage, what fraction of bases is expected to be uncovered under the Poisson model?

    Show answer

    e^−3 ≈ 0.05, about 5 percent.

  3. How many 31-mers does a 150 bp read contain?

    Show answer

    120.

  4. What is the canonical form of the 5-mer GGATC?

    Show answer

    Its reverse complement is GATCC; the smaller is GATCC.

  5. Why does a diploid k-mer spectrum often show a peak at half the main depth?

    Show answer

    k-mers overlapping heterozygous sites occur on only one of the two haplotypes.

  6. A spectrum peak lies at 40 and the solid k-mer total is 4.8 x 10^9. Estimate genome size.

    Show answer

    1.2 x 10^8, about 120 Mb.

  7. Why are k-mers that occur once usually discarded?

    Show answer

    They are mostly created by sequencing errors.

  8. What does MinHash estimate between two genomes?

    Show answer

    The Jaccard similarity of their k-mer sets, from which average nucleotide identity can be estimated.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz