| Title: | Within-Host Pathogen Variant QC, Diversity, and Bottleneck |
|---|---|
| Description: | Provides a SummarizedExperiment-based container and workflow functions for analysing intra-host single nucleotide variants (iSNV) from pathogen deep sequencing data. Supports replicate-aware quality control with full provenance tracking, multi-caller import harmonisation, within-host diversity quantification, and standardised export to transmission bottleneck estimation tools. Designed as infrastructure that bridges upstream variant callers (iVar, LoFreq, Freebayes) with downstream analysis packages through a common data object with uncertainty-preserving QC. |
| Authors: | Cuiwei Gao [aut, cre] |
| Maintainer: | Cuiwei Gao <[email protected]> |
| License: | Artistic-2.0 |
| Version: | 0.99.0 |
| Built: | 2026-09-18 19:51:35 UTC |
| Source: | https://github.com/BiocStaging/WithinHostExperiment |
Provides a
RangedSummarizedExperiment-class-based
container and workflow functions for analysing intra-host single
nucleotide variants (iSNV) from pathogen deep sequencing data.
The package fills an infrastructure gap between upstream variant callers (iVar, LoFreq, Freebayes) and downstream transmission bottleneck workflows. Its core contribution is replicate-aware, uncertainty-preserving quality control – variants are flagged rather than deleted, preserving full provenance for reproducible analysis. Standardised export functions provide interoperability with tools such as ViralBottleneck.
readWithinHost (from VCF),
readWithinHostTable (from TSV/CSV)
ISNVFilter (configure thresholds),
flagISNV (apply standard QC),
flagReplicateDiscordance (replicate-aware QC),
flagPrimerSites (amplicon primer masking),
qcSummary (QC report),
passedISNV (extract clean subset)
calcDiversity,
shannonISNV,
piISNV,
wattersonISNV,
compareDiversity
transmissionPairs,
exportPairFrequencies,
asViralBottleneckInput,
calcSharedVariants,
quickBottleneck
annotateFromGFF (positional gene annotation
from GFF3 files),
annotateCodonChange (CDS-aware codon
translation and amino acid change prediction from GFF3 +
reference FASTA)
trackFrequency,
detectEmergingVariants,
plotFrequencyTrajectory
flagTemporalInconsistency (longitudinal
consistency filtering – flag transient iSNVs that appear
at only one timepoint)
detectCrossContamination (identify index-
hopping signatures between co-sequenced samples)
WithinHostSFS-class (S4 class),
buildSFS and buildSFSList
(construct from WHE),
correctSFSBias (ascertainment bias correction),
neutralityFromSFS (Tajima's D + Fu's Fs +
Fay & Wu's H from a single SFS),
compareSFS (chi-squared SFS comparison),
plotSFS (publication-quality SFS plot)
This package operates at the level of individual single
nucleotide variants (iSNVs). It does not perform haplotype
phasing or quasispecies reconstruction, which require
read-level (BAM) analysis that is outside the scope of
RangedSummarizedExperiment-based containers. For
haplotype-aware within-host analysis, consider
CliqueSNV (Knyazev et al. 2021 Nucleic Acids Res 49:e102),
ShoRAH (Zagordi et al. 2011 BMC Bioinformatics
12:119), or similar tools. Their output can be imported
into WithinHostExperiment for downstream diversity
and QC analysis.
Distinguishing genuine within-host evolution from co-infection (superinfection) requires phylogenetic analysis of full haplotypes. The multi-caller consensus and replicate-concordance modules reduce artefacts but do not resolve this distinction.
estimateSelectionCoefficient assumes a
simple deterministic model. For stochastic (drift-aware)
estimates, consider dedicated population-genetics
frameworks.
Flag, don't delete. QC functions mark variants in
the qcPass assay rather than removing rows, preserving
uncertainty and enabling re-analysis with different thresholds.
Replicate-aware. Technical replicate concordance is a first-class QC criterion, not a post-hoc check.
Interoperable, don't reinvent. The package provides standardised export functions compatible with existing downstream tools rather than reimplementing their methods.
Bioconductor-native. Built on
RangedSummarizedExperiment, interoperable via
VRanges coercion.
Maintainer: Cuiwei Gao [email protected]
Roder AE et al. (2023). Optimized quantification of intra-host viral diversity in SARS-CoV-2 and influenza virus sequence data. mBio 14:e01046-23. doi:10.1128/mbio.01046-23
Bendall EE et al. (2023). Rapid transmission and tight bottlenecks constrain the evolution of highly transmissible SARS-CoV-2 variants. Nat. Commun. 14:272. doi:10.1038/s41467-023-36001-5
Sobel Leonard A et al. (2017). Transmission Bottleneck Size Estimation from Pathogen Deep-Sequencing Data. J Virol 91:e00171-17. doi:10.1128/JVI.00171-17
Mostefai F et al. (2024). Refining SARS-CoV-2 intra-host variation by leveraging large-scale sequencing data. NAR Genomics and Bioinformatics 6:lqae145. doi:10.1093/nargab/lqae145
Zheng B, Johnson PCD, Hughes J (2025). ViralBottleneck: an R package for estimating viral transmission bottlenecks from deep sequencing data using multiple methods. Virus Evolution 11:veaf071. doi:10.1093/ve/veaf071
Useful links:
Report bugs at https://github.com/CuiweiG/WithinHostExperiment/issues
# List exported functions ls("package:WithinHostExperiment")# List exported functions ls("package:WithinHostExperiment")
Returns an ISNVFilter with default thresholds tuned
for the error profile and read characteristics of the specified
sequencing technology.
adaptiveFilter(technology = c("illumina", "ont", "pacbio_hifi", "generic"))adaptiveFilter(technology = c("illumina", "ont", "pacbio_hifi", "generic"))
technology |
Character scalar. One of |
Each technology has distinct error modes that motivate different default thresholds:
minFreq = 0.03, minDepth = 100)Illumina short reads have low per-base error rates (~0.1–0.5%) but systematic context-dependent errors. A 3% frequency threshold safely exceeds the noise floor while retaining true low-frequency variants. Grubaugh et al. (2019) measured iSNVs above 3% accurately from at least 1,000 RNA copies sequenced to at least 400x, so 100x is a permissive floor; low-frequency false positives inflate diversity estimates markedly (McCrone & Lauring 2016).
minFreq = 0.10,
minDepth = 60, maxStrandBias = Inf)ONT reads have higher per-base error rates (~1–5% with R10
chemistry), requiring a more conservative frequency threshold
of 10% to avoid false positives; Bull et al. (2020) found that
ONT sequencing does not accurately detect variants at low
read-count frequencies, and reported over 99% sensitivity and
precision for single-nucleotide variants above about 60-fold
depth, which sets the depth threshold. Strand bias filtering is
disabled (Inf) rather than given an untested threshold;
supply maxStrandBias explicitly when the data support one.
minFreq = 0.05, minDepth = 50)HiFi consensus reads reach about 99.8% read accuracy via circular consensus sequencing (Wenger et al. 2019), allowing a frequency threshold below ONT but above Illumina. Depth of 50x reflects the lower throughput but high per-read accuracy.
Falls back to the standard ISNVFilter defaults
(minFreq = 0.03, minDepth = 100), suitable for Illumina
or when the technology is unknown.
An ISNVFilter object.
McCrone JT, Lauring AS (2016). Measurements of intrahost viral diversity are extremely sensitive to systematic errors in variant calling. J Virol 90:6884-6895. doi:10.1128/JVI.00667-16
Grubaugh ND et al. (2019). An amplicon-based sequencing framework for accurately measuring intrahost virus diversity using PrimalSeq and iVar. Genome Biology 20:8. doi:10.1186/s13059-018-1618-7
Bull RA et al. (2020). Analytical validity of nanopore sequencing for rapid SARS-CoV-2 genome analysis. Nature Communications 11:6272. doi:10.1038/s41467-020-20075-6
Wenger AM et al. (2019). Accurate circular consensus long-read sequencing improves variant detection and assembly of a human genome. Nature Biotechnology 37:1155–1162. doi:10.1038/s41587-019-0217-9
ISNVFilter for the underlying constructor,
flagISNV for applying filters.
# Illumina defaults adaptiveFilter("illumina") # ONT with strand bias disabled filt <- adaptiveFilter("ont") slot(filt, "maxStrandBias") # Inf # PacBio HiFi adaptiveFilter("pacbio_hifi") # Generic (same as ISNVFilter()) adaptiveFilter("generic")# Illumina defaults adaptiveFilter("illumina") # ONT with strand bias disabled filt <- adaptiveFilter("ont") slot(filt, "maxStrandBias") # Inf # PacBio HiFi adaptiveFilter("pacbio_hifi") # Generic (same as ISNVFilter()) adaptiveFilter("generic")
For each SNV in a WithinHostExperiment, determines
the affected codon and amino acid change using CDS coordinates
from a GFF3 file and the reference genome sequence from a FASTA
file. Adds GFF_FEATURE, REF_CODON, ALT_CODON,
REF_AA, ALT_AA, and AA_CLASS (Synonymous /
Nonsynonymous / Nonsense) columns to mcols(rowRanges).
annotateCodonChange(whe, gff, refFasta, nameCol = "gene")annotateCodonChange(whe, gff, refFasta, nameCol = "gene")
whe |
A |
gff |
Character scalar. Path to a GFF3 file containing CDS features. |
refFasta |
Character scalar. Path to the reference genome FASTA file. |
nameCol |
Character scalar. GFF3 attribute for the gene name
(default: |
This is a pure-R implementation using the standard genetic code (NCBI translation table 1). No external annotation tools are required. For complex genomes with overlapping reading frames, ribosomal frameshift sites, or non-standard codon usage, use SnpEff or ANNOVAR upstream.
The algorithm:
Parse CDS features from GFF3 (requires column 8 = phase).
For each variant overlapping a CDS, compute the codon position within the CDS (accounting for strand and phase).
Extract the reference codon from the FASTA, substitute the variant base, translate both codons.
Classify: Synonymous (same AA), Nonsynonymous (different AA), or Nonsense (stop codon gained/lost).
Only single-nucleotide substitutions are annotated. Indels and
multi-nucleotide variants are marked as NA.
A WithinHostExperiment with additional
columns in mcols(rowRanges): GFF_FEATURE,
REF_CODON, ALT_CODON, REF_AA,
ALT_AA, AA_CLASS.
## Requires a reference FASTA and GFF3 with CDS entries ref <- system.file("extdata", "test_ref.fa", package = "WithinHostExperiment") if (file.exists(ref)) { gff_file <- tempfile(fileext = ".gff3") writeLines(c("##gff-version 3", "test_segment\t.\tCDS\t1\t999\t.\t+\t0\tgene=testGene"), gff_file) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- annotateCodonChange(whe, gff_file, ref) mc <- S4Vectors::mcols(SummarizedExperiment::rowRanges(whe)) head(mc[, c("GFF_FEATURE", "REF_AA", "ALT_AA", "AA_CLASS")]) }## Requires a reference FASTA and GFF3 with CDS entries ref <- system.file("extdata", "test_ref.fa", package = "WithinHostExperiment") if (file.exists(ref)) { gff_file <- tempfile(fileext = ".gff3") writeLines(c("##gff-version 3", "test_segment\t.\tCDS\t1\t999\t.\t+\t0\tgene=testGene"), gff_file) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- annotateCodonChange(whe, gff_file, ref) mc <- S4Vectors::mcols(SummarizedExperiment::rowRanges(whe)) head(mc[, c("GFF_FEATURE", "REF_AA", "ALT_AA", "AA_CLASS")]) }
Reads a GFF3 file and adds gene/feature annotations to
mcols(rowRanges) of a WithinHostExperiment.
This automates the annotation step required by
dndsWithinHost, which expects REF_AA and
ALT_AA columns.
annotateFromGFF(whe, gff, featureType = "gene", nameCol = "Name")annotateFromGFF(whe, gff, featureType = "gene", nameCol = "Name")
whe |
A |
gff |
Character scalar. Path to a GFF3 file. |
featureType |
Character scalar. Which GFF feature type to
extract (default: |
nameCol |
Character scalar. GFF3 attribute to use as the
feature name (default: |
The function overlaps variant positions with GFF features of the
specified type (default: "gene") and adds a
GFF_FEATURE column containing the gene name or ID.
For coding-region annotation (amino acid changes), a reference
FASTA is additionally required; this function provides the
positional overlap only.
The GFF3 file is parsed with base R (no external dependency).
Only lines matching featureType in column 3 are used.
Attribute parsing follows the GFF3 specification
(key=value pairs separated by semicolons).
For full codon-aware annotation (predicting amino acid changes),
use dedicated tools such as SnpEff or ANNOVAR upstream, then
import the annotated VCF via readWithinHost.
A WithinHostExperiment with a
GFF_FEATURE column added to mcols(rowRanges).
## Create a minimal GFF3 for demonstration gff_file <- tempfile(fileext = ".gff3") writeLines(c( "##gff-version 3", "test_segment\t.\tgene\t1\t500\t.\t+\t.\tName=geneA", "test_segment\t.\tgene\t501\t1000\t.\t+\t.\tName=geneB" ), gff_file) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- annotateFromGFF(whe, gff_file) S4Vectors::mcols(SummarizedExperiment::rowRanges(whe))$GFF_FEATURE## Create a minimal GFF3 for demonstration gff_file <- tempfile(fileext = ".gff3") writeLines(c( "##gff-version 3", "test_segment\t.\tgene\t1\t500\t.\t+\t.\tName=geneA", "test_segment\t.\tgene\t501\t1000\t.\t+\t.\tName=geneB" ), gff_file) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- annotateFromGFF(whe, gff_file) S4Vectors::mcols(SummarizedExperiment::rowRanges(whe))$GFF_FEATURE
Converts a WithinHostExperiment to the input format
expected by the ViralBottleneck package.
asViralBottleneckInput( whe, pairId = NULL, threshold = 0.03, usePassedOnly = TRUE )asViralBottleneckInput( whe, pairId = NULL, threshold = 0.03, usePassedOnly = TRUE )
whe |
|
pairId |
Character (optional). Specific pair. |
threshold |
Numeric. Variant calling threshold (default: 0.03). |
usePassedOnly |
Logical (default: TRUE). |
For comprehensive bottleneck analysis with multiple methods (presence-absence, KL divergence, binomial, beta-binomial approximate/exact, and Wright-Fisher), use the ViralBottleneck package (Zheng et al. 2025, Virus Evolution 11:veaf071). This function prepares a simplified input suitable for the beta-binomial methods.
A data.frame with columns: pos, donor_freq,
recipient_freq – a simplified format for beta-binomial MLE.
For the full ViralBottleneck input format (per-base read
counts), prepare data externally using the read count
assays (altCount, refCount).
vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") asViralBottleneckInput(whe, pairId = "p1")vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") asViralBottleneckInput(whe, pairId = "p1")
If a truth WithinHostExperiment is provided, computes
true positives, false positives, false negatives, precision,
recall, and F1 for each caller. If no truth is provided,
computes pairwise agreement using
calcCallerConcordance.
benchmarkCallers(whe_list, truth = NULL)benchmarkCallers(whe_list, truth = NULL)
whe_list |
A named list of |
truth |
An optional |
A DataFrame. When truth is provided:
Caller name.
Total sites called by this caller.
True positives.
False positives.
False negatives.
TP / (TP + FP).
TP / (TP + FN).
Harmonic mean of precision and recall.
When truth is NULL, the result of
calcCallerConcordance is returned.
gr1 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr1)$ref <- c("A", "C", "G") S4Vectors::mcols(gr1)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr1, colData = S4Vectors::DataFrame(sample_id = "S1")) benchmarkCallers(list(caller1 = whe1), truth = whe1)gr1 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr1)$ref <- c("A", "C", "G") S4Vectors::mcols(gr1)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr1, colData = S4Vectors::DataFrame(sample_id = "S1")) benchmarkCallers(list(caller1 = whe1), truth = whe1)
Constructs a WithinHostSFS object from the allele
frequencies in a WithinHostExperiment. One SFS is
built per sample.
buildSFS( whe, sampleIdx = 1L, nBins = 20L, fold = TRUE, usePassedOnly = TRUE, threshold = 0.03, genomeLength = NULL )buildSFS( whe, sampleIdx = 1L, nBins = 20L, fold = TRUE, usePassedOnly = TRUE, threshold = 0.03, genomeLength = NULL )
whe |
|
sampleIdx |
Integer or character. Which sample (column) to use. Default: 1. |
nBins |
Integer. Number of frequency bins (default: 20). |
fold |
Logical. If |
usePassedOnly |
Logical. If |
threshold |
Numeric. Detection threshold (default: 0.03). The first bin starts at this value, not 0. |
genomeLength |
Integer (optional). Reference genome length. |
A WithinHostSFS object.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) sfsvcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) sfs
Convenience wrapper that calls buildSFS for each
sample and returns a named list of WithinHostSFS
objects.
buildSFSList( whe, nBins = 20L, fold = TRUE, usePassedOnly = TRUE, threshold = 0.03, genomeLength = NULL )buildSFSList( whe, nBins = 20L, fold = TRUE, usePassedOnly = TRUE, threshold = 0.03, genomeLength = NULL )
whe |
|
nBins |
Integer. Number of frequency bins (default: 20). |
fold |
Logical. If |
usePassedOnly |
Logical. If |
threshold |
Numeric. Detection threshold (default: 0.03). The first bin starts at this value, not 0. |
genomeLength |
Integer (optional). Reference genome length. |
A named list of WithinHostSFS objects.
data(example_whe) sfs_list <- buildSFSList(example_whe, genomeLength = 1000L) length(sfs_list)data(example_whe) sfs_list <- buildSFSList(example_whe, genomeLength = 1000L) length(sfs_list)
For a list of WithinHostExperiment objects (one per
variant caller), computes pairwise concordance statistics
including shared sites, caller-unique sites, and Jaccard index.
calcCallerConcordance(whe_list)calcCallerConcordance(whe_list)
whe_list |
A named list of |
A DataFrame with columns:
First caller name.
Second caller name.
Number of sites detected by both callers.
Sites unique to caller1.
Sites unique to caller2.
Jaccard similarity index (shared / union).
Roder AE et al. (2023). Optimized quantification of intra-host viral diversity in SARS-CoV-2 and influenza virus sequence data. mBio 14:e01046-23. doi:10.1128/mbio.01046-23
gr1 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr1)$ref <- c("A", "C", "G") S4Vectors::mcols(gr1)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr1, colData = S4Vectors::DataFrame(sample_id = "S1")) gr2 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 400), width = 1)) S4Vectors::mcols(gr2)$ref <- c("A", "C", "T") S4Vectors::mcols(gr2)$alt <- c("T", "G", "C") whe2 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.12, 0.22, 0.15), ncol = 1)), rowRanges = gr2, colData = S4Vectors::DataFrame(sample_id = "S1")) calcCallerConcordance(list(ivar = whe1, lofreq = whe2))gr1 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr1)$ref <- c("A", "C", "G") S4Vectors::mcols(gr1)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr1, colData = S4Vectors::DataFrame(sample_id = "S1")) gr2 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 400), width = 1)) S4Vectors::mcols(gr2)$ref <- c("A", "C", "T") S4Vectors::mcols(gr2)$alt <- c("T", "G", "C") whe2 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.12, 0.22, 0.15), ncol = 1)), rowRanges = gr2, colData = S4Vectors::DataFrame(sample_id = "S1")) calcCallerConcordance(list(ivar = whe1, lofreq = whe2))
Generic for computing diversity metrics from within-host
variant data. Methods should return a DataFrame with
one row per sample.
Calculates diversity metrics per sample from a
WithinHostExperiment. Implements standard
population genetics indices adapted for deep sequencing
iSNV data, following Lauring (2020).
calcDiversity(x, ...) ## S4 method for signature 'WithinHostExperiment' calcDiversity( x, indices = c("shannon", "pi", "watterson", "richness", "simpson", "chao1"), genomeLength = NULL, usePassedOnly = TRUE, ... )calcDiversity(x, ...) ## S4 method for signature 'WithinHostExperiment' calcDiversity( x, indices = c("shannon", "pi", "watterson", "richness", "simpson", "chao1"), genomeLength = NULL, usePassedOnly = TRUE, ... )
x |
|
... |
Additional arguments (currently unused). |
indices |
Character vector. Which indices to compute:
|
genomeLength |
Integer (optional). Required for |
usePassedOnly |
Logical. If TRUE (default), only use variants
where |
A DataFrame of diversity metrics.
A DataFrame with one row per sample and columns
for each requested index.
Lauring AS (2020). Within-Host Viral Diversity: A Window into Viral Evolution. Annu Rev Virol 7:63-81. doi:10.1146/annurev-virology-010320-061642
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") calcDiversity(whe, genomeLength = 1000L)vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") calcDiversity(whe, genomeLength = 1000L)
Generic for computing neutrality tests from within-host
variant data. Methods should return a DataFrame with
one row per sample.
Calculates neutrality test statistics per sample from a
WithinHostExperiment. Implements Tajima's D
(Tajima 1989) and Fu's Fs (Fu 1997), adapted for deep
sequencing iSNV data.
calcNeutralityTests(x, ...) ## S4 method for signature 'WithinHostExperiment' calcNeutralityTests( x, genomeLength = NULL, indices = c("tajimaD", "fusFs"), usePassedOnly = TRUE, ... )calcNeutralityTests(x, ...) ## S4 method for signature 'WithinHostExperiment' calcNeutralityTests( x, genomeLength = NULL, indices = c("tajimaD", "fusFs"), usePassedOnly = TRUE, ... )
x |
|
... |
Additional arguments (currently unused). |
genomeLength |
Integer scalar. Reference genome length in bp. Required. |
indices |
Character vector. Which tests to compute:
|
usePassedOnly |
Logical. If TRUE (default), only use
variants where |
A DataFrame of neutrality statistics.
A DataFrame with one row per sample and columns
sample_id plus one column per requested test.
Tajima F (1989). Statistical method for testing the neutral mutation hypothesis by DNA polymorphism. Genetics 123:585-595.
Fu YX (1997). Statistical tests of neutrality of mutations against population growth, hitchhiking and background selection. Genetics 147:915-925.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") calcNeutralityTests(whe, genomeLength = 1000L)vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") calcNeutralityTests(whe, genomeLength = 1000L)
Estimates total species (variant) richness using the Chao1 estimator (Chao 1984):
where is the observed number of variants,
is the number of singletons (variants with
altCount == 1), and is the number of
doubletons (variants with altCount == 2). When
, the bias-corrected form is used:
.
chao1ISNV(counts, detected)chao1ISNV(counts, detected)
counts |
Integer vector. Alternative allele read counts
( |
detected |
Integer scalar. Number of observed variants
( |
Numeric scalar. Chao1 richness estimate.
Chao A (1984). Nonparametric estimation of the number of classes in a population. Scand J Statist 11:265-270.
chao1ISNV(c(1, 1, 2, 5, 10), detected = 5)chao1ISNV(c(1, 1, 2, 5, 10), detected = 5)
Coerce WithinHostExperiment to data.frame or VRanges
A data.frame or VRanges
object containing the variant data from the
WithinHostExperiment object.
data(example_whe) df <- as(example_whe, "data.frame") head(df) vr <- as(example_whe, "VRanges") vrdata(example_whe) df <- as(example_whe, "data.frame") head(df) vr <- as(example_whe, "VRanges") vr
Compare diversity between sample groups
compareDiversity(whe, groupBy, index = "shannon", genomeLength = NULL)compareDiversity(whe, groupBy, index = "shannon", genomeLength = NULL)
whe |
|
groupBy |
Character. Column name in |
index |
Character. Which index to compare (default: "shannon"). |
genomeLength |
Integer (optional). Required if index is "pi" or "watterson". |
A DataFrame with test results.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") ## Single-group, returns diversity table: compareDiversity(whe, groupBy = "sample_id", index = "shannon")vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") ## Single-group, returns diversity table: compareDiversity(whe, groupBy = "sample_id", index = "shannon")
Computes summary statistics and a chi-squared test for
homogeneity between two WithinHostSFS objects
(e.g., before vs. after QC, or two timepoints).
compareSFS(sfs1, sfs2)compareSFS(sfs1, sfs2)
sfs1 |
A |
sfs2 |
A |
The test compares raw variant counts, so apply it to
uncorrected spectra: the counts of a bias-corrected spectrum are
rescaled and no longer multinomial. Bins where both spectra are
empty are dropped, and when only two bins remain
chisq.test applies Yates' continuity
correction. The chi-squared approximation is unreliable
when an expected count falls below 5; instead of a warning, that
condition is reported in chisq_approx_ok, and the p-value
should then be read with caution.
A named list with components:
Chi-squared statistic.
Chi-squared p-value.
Degrees of freedom.
Smallest expected count under homogeneity,
or NA when fewer than two bins remain and no test is
run.
TRUE when every expected count
is at least 5, the condition under which
chisq.test does not warn; NA
alongside the other NA diagnostics when no test is
run.
Total variants in sfs1.
Total variants in sfs2.
Proportion in each bin, sfs1.
Proportion in each bin, sfs2.
data(example_whe) whe1 <- example_whe sfs1 <- buildSFS(whe1, genomeLength = 1000L) sfs2 <- buildSFS(whe1, sampleIdx = 2L, genomeLength = 1000L) compareSFS(sfs1, sfs2)data(example_whe) whe1 <- example_whe sfs1 <- buildSFS(whe1, genomeLength = 1000L) sfs2 <- buildSFS(whe1, sampleIdx = 2L, genomeLength = 1000L) compareSFS(sfs1, sfs2)
Takes a named list of WithinHostExperiment objects
(one per variant caller) and identifies variant sites detected
by multiple callers. Sites are matched by genomic position
(seqnames + start) and alternative allele. Only sites detected
by at least min_callers with a frequency spread no
greater than freq_tolerance are retained.
consensusISNV(whe_list, min_callers = 2L, freq_tolerance = 0.05)consensusISNV(whe_list, min_callers = 2L, freq_tolerance = 0.05)
whe_list |
A named list of |
min_callers |
Integer scalar. Minimum number of callers that must detect a site (default: 2L). |
freq_tolerance |
Numeric scalar. Maximum allowed range (max - min) of allele frequencies across callers for a site to pass (default: 0.05). |
When technical replicates are unavailable, using a multi-caller consensus is recommended for reducing false positive iSNV calls (Roder et al. 2023).
A WithinHostExperiment containing only
consensus sites. mcols(rowRanges) includes:
Number of callers detecting the site.
Comma-separated caller names.
Range of allele frequencies across callers.
The altFreq assay contains the mean frequency across
callers for each consensus site. It is the only assay of the
result: read counts, depths and qcPass are not carried
over, so run flagISNV on the individual callers'
objects rather than on the consensus.
Roder AE et al. (2023). Optimized quantification of intra-host viral diversity in SARS-CoV-2 and influenza virus sequence data. mBio 14:e01046-23. doi:10.1128/mbio.01046-23
gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.10, 0.20, 0.30), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) gr2 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 400), width = 1)) S4Vectors::mcols(gr2)$ref <- c("A", "C", "T") S4Vectors::mcols(gr2)$alt <- c("T", "G", "C") whe2 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.12, 0.18, 0.15), ncol = 1)), rowRanges = gr2, colData = S4Vectors::DataFrame(sample_id = "S1")) consensus <- consensusISNV(list(caller1 = whe1, caller2 = whe2)) consensusgr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") whe1 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.10, 0.20, 0.30), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) gr2 <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 400), width = 1)) S4Vectors::mcols(gr2)$ref <- c("A", "C", "T") S4Vectors::mcols(gr2)$alt <- c("T", "G", "C") whe2 <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.12, 0.18, 0.15), ncol = 1)), rowRanges = gr2, colData = S4Vectors::DataFrame(sample_id = "S1")) consensus <- consensusISNV(list(caller1 = whe1, caller2 = whe2)) consensus
Deep sequencing imposes a detection threshold (e.g., 3%): sites with true frequency below this threshold are unobservable. This inflates the apparent fraction of moderate-frequency variants relative to rare variants.
correctSFSBias(sfs, method = c("truncation", "binomial"))correctSFSBias(sfs, method = c("truncation", "binomial"))
sfs |
A |
method |
Character. Correction method:
|
This function applies a correction based on the expected number of unobserved sites under a neutral beta distribution model: given the observed SFS and depth-dependent detectability, it estimates the missing mass in the lowest frequency bins.
The "truncation" method assumes a uniform density of
variant frequencies and multiplies every bin by
, the reciprocal of
the observable fraction of the frequency range. It therefore
rescales the spectrum without changing its shape. The
"binomial" method uses the stored depth to compute
where ,
and reweights each bin by the inverse of its detectability.
Detectability is floored at 0.01, so a bin is inflated at most
one hundredfold.
A corrected WithinHostSFS with
corrected = TRUE. Counts are no longer raw variant
counts, and nSites changes accordingly, which also
changes the number of segregating sites used by
neutralityFromSFS. Use uncorrected spectra for
compareSFS.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) sfs_corr <- correctSFSBias(sfs, method = "binomial") sfs_corrvcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) sfs_corr <- correctSFSBias(sfs, method = "binomial") sfs_corr
Identifies iSNV sites where a high-frequency variant in one sample appears at suspiciously low frequency in another sample from the same sequencing run, consistent with index hopping or sample cross-contamination.
detectCrossContamination( whe, runCol = NULL, sourceMinFreq = 0.2, sinkMaxFreq = 0.05, sinkMinFreq = 0.005, usePassedOnly = TRUE )detectCrossContamination( whe, runCol = NULL, sourceMinFreq = 0.2, sinkMaxFreq = 0.05, sinkMinFreq = 0.005, usePassedOnly = TRUE )
whe |
A |
runCol |
Character scalar (optional). Column name in
|
sourceMinFreq |
Numeric scalar. Minimum frequency in the "source" sample for a variant to be considered a potential contaminant origin (default: 0.20). |
sinkMaxFreq |
Numeric scalar. Maximum frequency in the "sink" sample for a variant to be flagged as potential contamination (default: 0.05). |
sinkMinFreq |
Numeric scalar. Minimum frequency in the "sink" sample (default: 0.005). Variants below this are likely noise, not contamination. |
usePassedOnly |
Logical. If |
Index hopping occurs when adapters are exchanged between library molecules during pooled sequencing, causing reads from one sample to be misassigned to another. The signature: a variant at high frequency (e.g., 80%) in sample A appears at very low frequency (e.g., 0.5-2%) in sample B sequenced on the same flow cell, at a ratio consistent with the expected hopping rate (typically 0.1-2% for Illumina platforms).
This function does not require external metadata beyond what is
in colData. For finer control, use the output
DataFrame to implement study-specific contamination
criteria.
A DataFrame with columns:
Sample where the variant is at high frequency.
Sample where the variant appears at suspiciously low frequency.
Chromosome/segment.
Genomic position.
Reference allele.
Alternative allele.
Frequency in the source sample.
Frequency in the sink sample.
sink_freq / source_freq – lower values are more suspicious.
Sequencing run (if runCol provided).
Bendall EE et al. (2022). SARS-CoV-2 genomic diversity in households highlights the challenges of sequence-based transmission inference. mSphere 7:e00400-22. doi:10.1128/msphere.00400-22
Costello M et al. (2018). Characterization and remediation of sample index swaps by non-redundant dual indexing on massively parallel sequencing platforms. BMC Genomics 19:332. doi:10.1186/s12864-018-4703-0
gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") ## Sample A has pos 100 at 80%; sample B has same pos at 1% freq <- matrix(c(0.80, 0.05, 0.30, # sample A 0.01, 0.40, 0.00), # sample B nrow = 3, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("A", "B"), run = c("run1", "run1"))) detectCrossContamination(whe, runCol = "run")gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") ## Sample A has pos 100 at 80%; sample B has same pos at 1% freq <- matrix(c(0.80, 0.05, 0.30, # sample A 0.01, 0.40, 0.00), # sample B nrow = 3, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("A", "B"), run = c("run1", "run1"))) detectCrossContamination(whe, runCol = "run")
Identifies variants whose allele frequency crosses a threshold
between consecutive time points within each host. A variant is
considered emerging if it was below freqThreshold at one
time point and increased by at least minIncrease at the
next.
detectEmergingVariants( whe, hostCol = "host_id", timeCol = "timepoint", freqThreshold = 0.05, minIncrease = 0.05, usePassedOnly = TRUE )detectEmergingVariants( whe, hostCol = "host_id", timeCol = "timepoint", freqThreshold = 0.05, minIncrease = 0.05, usePassedOnly = TRUE )
whe |
A |
hostCol |
Character scalar. Column name in |
timeCol |
Character scalar. Column name in |
freqThreshold |
Numeric scalar. Frequency threshold for emergence (default: 0.05). |
minIncrease |
Numeric scalar. Minimum frequency increase between consecutive time points (default: 0.05). |
usePassedOnly |
Logical. If TRUE (default), only use QC-passed variants. |
A DataFrame with columns:
Host identifier.
Variant identifier (chrom:pos:alt).
Earlier time point.
Later time point.
Frequency at the earlier time point.
Frequency at the later time point.
Change in frequency.
gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.02, 0.10, 0.12, 0.15), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) detectEmergingVariants(whe)gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.02, 0.10, 0.12, 0.15), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) detectEmergingVariants(whe)
Estimates the ratio of nonsynonymous to synonymous divergence among
the iSNVs of each sample with the counting method of Nei and Gojobori
(1986). The numbers of synonymous () and nonsynonymous
() sites are counted over the complete codons of every CDS in
gff, using the reference sequence in refFasta and the
standard genetic code. With synonymous and
nonsynonymous iSNVs present in a sample,
each proportion is corrected for multiple hits with Jukes and Cantor
(1969), , and
is their ratio. Under neutrality the expected ratio is
1; values below 1 suggest purifying selection and values above 1
diversifying selection, but within-host ratios computed from few
iSNVs are highly variable and should be read with their counts.
dndsWithinHost( whe, gff, refFasta, gffFeatureCol = "GFF_FEATURE", refAACol = "REF_AA", altAACol = "ALT_AA", usePassedOnly = TRUE, nameCol = "gene" )dndsWithinHost( whe, gff, refFasta, gffFeatureCol = "GFF_FEATURE", refAACol = "REF_AA", altAACol = "ALT_AA", usePassedOnly = TRUE, nameCol = "gene" )
whe |
A |
gff |
Character scalar. Path to the GFF3 file with CDS features used for the annotation. |
refFasta |
Character scalar. Path to the reference genome FASTA used for the annotation. |
gffFeatureCol |
Character scalar. Column name in
|
refAACol |
Character scalar. Column name in
|
altAACol |
Character scalar. Column name in
|
usePassedOnly |
Logical. If |
nameCol |
Character scalar. GFF3 attribute holding the gene name
(default: |
Each iSNV present in a sample (non-missing, non-zero frequency, and
passing QC when usePassedOnly = TRUE) is counted once,
irrespective of its frequency. Amino acid classes come from
annotateCodonChange, which should be run with the same
gff and refFasta. Variants outside a CDS, or without an
amino acid annotation, are not counted. Nonsense changes count as
nonsynonymous. Codons shared by overlapping CDS features of the same
gene are counted once.
A DataFrame with one row per sample and gene carrying
at least one counted iSNV (one row with gene = NA when a
sample has none):
Sample identifier.
Synonymous and nonsynonymous iSNVs in the sample.
Synonymous and nonsynonymous sites over all CDS features.
Proportions and .
Jukes-Cantor corrected (NA when
is zero or a proportion reaches 0.75).
The same quantities for each gene.
Nei M, Gojobori T (1986). Simple methods for estimating the numbers of synonymous and nonsynonymous nucleotide substitutions. Mol Biol Evol 3:418-426. doi:10.1093/oxfordjournals.molbev.a040410
Jukes TH, Cantor CR (1969). Evolution of protein molecules. In Munro HN (ed.), Mammalian Protein Metabolism, pp. 21-132. Academic Press, New York.
ref <- tempfile(fileext = ".fa") writeLines(c(">seg1", "ATGCTGAAAGGGTAA"), ref) gff <- tempfile(fileext = ".gff3") writeLines(c("##gff-version 3", "seg1\t.\tCDS\t1\t15\t.\t+\t0\tgene=geneA"), gff) gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(1, 6), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "G") S4Vectors::mcols(gr)$alt <- c("G", "A") whe <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) whe <- annotateCodonChange(whe, gff, ref) dndsWithinHost(whe, gff = gff, refFasta = ref)ref <- tempfile(fileext = ".fa") writeLines(c(">seg1", "ATGCTGAAAGGGTAA"), ref) gff <- tempfile(fileext = ".gff3") writeLines(c("##gff-version 3", "seg1\t.\tCDS\t1\t15\t.\t+\t0\tgene=geneA"), gff) gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(1, 6), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "G") S4Vectors::mcols(gr)$alt <- c("G", "A") whe <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) whe <- annotateCodonChange(whe, gff, ref) dndsWithinHost(whe, gff = gff, refFasta = ref)
Given a vector of allele frequencies observed over equally-spaced
time points, estimates the selection coefficient using
the change in logit-transformed frequency per unit time:
where .
estimateSelectionCoefficient(freqs, times = NULL, generation_time = 1)estimateSelectionCoefficient(freqs, times = NULL, generation_time = 1)
freqs |
Numeric vector. Allele frequencies at successive time points (values must be in (0, 1)). |
times |
Numeric vector (optional). Time values for each
observation. If |
generation_time |
Numeric scalar. Time per generation (default: 1). Used to scale the selection coefficient. |
This assumes a simple deterministic selection model. Each
consecutive pair of time points yields one estimate of .
Numeric vector of selection coefficient estimates, one
per consecutive time interval. Length is
length(freqs) - 1.
Feder AF et al. (2016). More effective drugs lead to harder selective sweeps in the evolution of drug resistance in HIV-1. eLife 5:e10670. doi:10.7554/eLife.10670
freqs <- c(0.05, 0.10, 0.25, 0.50) estimateSelectionCoefficient(freqs) estimateSelectionCoefficient(freqs, times = c(0, 3, 7, 14))freqs <- c(0.05, 0.10, 0.25, 0.50) estimateSelectionCoefficient(freqs) estimateSelectionCoefficient(freqs, times = c(0, 3, 7, 14))
Estimates transmission bottleneck size using one of three methods:
exact_bbBeta-binomial likelihood over the number
of transmitted variant virions, which models finite recipient
read depth, in the spirit of Sobel Leonard et al. (2017).
Requires altCount and totalDepth assays. Two
deviations from the published method are deliberate: when all
or none of the transmitted virions carry the variant, the
recipient read counts are modelled with a fixed sequencing
error probability of threshold / 2, and the recipient
detection threshold is not integrated out.
wright_fisherApproximate score in which one
generation of binomial drift is combined with a beta kernel of
concentration max(Nb, 2) around the drifted frequency.
It mixes a density for detected variants with probabilities for
lost variants, so it is a heuristic rather than a likelihood;
prefer exact_bb or presence_absence for
inference.
presence_absencePresence-absence likelihood using
.
exactBottleneck( whe, pairId, maxNb = 500L, method = c("exact_bb", "wright_fisher", "presence_absence"), nboot = 0L, threshold = 0.03 )exactBottleneck( whe, pairId, maxNb = 500L, method = c("exact_bb", "wright_fisher", "presence_absence"), nboot = 0L, threshold = 0.03 )
whe |
A |
pairId |
Character. Which pair to estimate. |
maxNb |
Integer. Maximum bottleneck size to evaluate (default: 500). |
method |
Character. One of |
nboot |
Integer. Number of bootstrap replicates for confidence intervals (default: 0, uses a likelihood-ratio interval). Bootstrapping resamples variant sites and uses the random number generator, so set a seed for reproducible intervals. |
threshold |
Numeric. Variant calling threshold (default: 0.03). |
A named list with components:
NbNumeric. Maximum likelihood estimate of
bottleneck size over the grid 1:maxNb. A warning is
issued when the estimate reaches maxNb, because the
value is then censored by the grid.
ciNumeric vector of length 2. 95% confidence
interval (likelihood-ratio or bootstrap percentile), also
bounded by maxNb.
loglikNumeric. Log-likelihood at the MLE.
methodCharacter. Method used.
n_variantsInteger. Number of donor variants used.
pair_idCharacter. Pair identifier.
Sobel Leonard A et al. (2017). Transmission Bottleneck Size Estimation from Pathogen Deep-Sequencing Data. J Virol 91:e00171-17. doi:10.1128/JVI.00171-17
vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") exactBottleneck(whe, pairId = "p1", maxNb = 50L, method = "presence_absence")vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") exactBottleneck(whe, pairId = "p1", maxNb = 50L, method = "presence_absence")
A small synthetic WithinHostExperiment with 3 samples
(donor, recipient, independent) containing iSNV data at positions 33 to
945 of a synthetic genome segment. The donor and recipient form a
transmission pair (pair_id = "pair_1").
data(example_whe)data(example_whe)
A WithinHostExperiment with 28 variant sites
and 3 samples. Assays: altFreq, totalDepth, refCount, altCount,
qcPass.
Synthetic. Built by inst/scripts/save_example_data.R from
test_donor.vcf, test_recipient.vcf and
test_independent.vcf in inst/extdata, read with
readWithinHost.
data(example_whe) example_whe transmissionPairs(example_whe)data(example_whe) example_whe transmissionPairs(example_whe)
For each transmission pair, creates a table of variant frequencies in the donor and recipient.
exportPairFrequencies(whe, pairId = NULL, usePassedOnly = TRUE)exportPairFrequencies(whe, pairId = NULL, usePassedOnly = TRUE)
whe |
A |
pairId |
Character (optional). Specific pair to export. If NULL, exports all pairs. |
usePassedOnly |
Logical. Use only QC-passed variants (default: TRUE). |
A DataFrame with columns: chrom, position, ref, alt,
donor_freq, recipient_freq, shared (logical).
vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") exportPairFrequencies(whe)vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") exportPairFrequencies(whe)
Fits a linear model (lm) relating a diversity
metric to one or more predictors. When group_col is supplied
it is included as a fixed effect (a full mixed-effects model would
require the nlme dependency).
fitDiversityModel(div_df, formula, group_col = NULL)fitDiversityModel(div_df, formula, group_col = NULL)
div_df |
A |
formula |
A formula or character string describing the model
(e.g.\ |
group_col |
Character scalar (optional). Column name in
|
A list with components:
The fitted lm object.
A data.frame of coefficient estimates,
standard errors, t-values, and p-values.
Numeric vector of model residuals.
df <- data.frame( shannon = c(1.2, 1.5, 0.8, 1.1, 1.9, 0.7), timepoint = c(1, 2, 3, 1, 2, 3), group = c("A", "A", "A", "B", "B", "B") ) res <- fitDiversityModel(df, shannon ~ timepoint) res_grouped <- fitDiversityModel(df, shannon ~ timepoint, group_col = "group")df <- data.frame( shannon = c(1.2, 1.5, 0.8, 1.1, 1.9, 0.7), timepoint = c(1, 2, 3, 1, 2, 3), group = c("A", "A", "A", "B", "B", "B") ) res <- fitDiversityModel(df, shannon ~ timepoint) res_grouped <- fitDiversityModel(df, shannon ~ timepoint, group_col = "group")
Generic for applying quality control filters to within-host
variant data. The default method operates on
WithinHostExperiment objects. Other packages
can define methods for alternative containers.
Applies quality control filters to a WithinHostExperiment.
Instead of removing variants, this function sets the corresponding
entries in the qcPass assay to FALSE. Each filtering
step is recorded in the qcLog for full reproducibility.
flagISNV(x, filter = ISNVFilter(), ...) ## S4 method for signature 'WithinHostExperiment' flagISNV(x, filter = ISNVFilter(), ...)flagISNV(x, filter = ISNVFilter(), ...) ## S4 method for signature 'WithinHostExperiment' flagISNV(x, filter = ISNVFilter(), ...)
x |
A |
filter |
An |
... |
Must be empty. Any further argument is an error, so that a misspelled argument name cannot be silently ignored. |
Filtering steps are applied in order:
Minimum read depth (minDepth)
Minimum alternative allele frequency (minFreq)
Maximum alternative allele frequency (maxFreq)
Minimum alternative allele read count (minAltReads)
Strand bias (maxStrandBias) – only if
fwdAltCount and revAltCount assays exist
The minBaseQual, minMapQual and replicateConc
slots of the filter are recorded for provenance but not applied
here; a non-default value raises a warning. Use
flagReplicateDiscordance for replicate concordance.
The filtered object (same class as input).
A WithinHostExperiment with updated
qcPass assay and qcLog.
ISNVFilter for filter configuration,
passedISNV for extracting passed variants,
qcSummary for QC report,
flagReplicateDiscordance for replicate QC.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter(minDepth = 500L)) qcLog(whe) passedISNV(whe)vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter(minDepth = 500L)) qcLog(whe) passedISNV(whe)
For amplicon-based sequencing data, marks variants that fall within
primer binding sites in the qcPass assay.
flagPrimerSites(whe, primers, action = c("flag", "remove"))flagPrimerSites(whe, primers, action = c("flag", "remove"))
whe |
|
primers |
|
action |
Character. |
library(GenomicRanges) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") primers <- GRanges("test_segment", IRanges::IRanges( start = c(40, 880), width = c(25, 30))) whe <- flagPrimerSites(whe, primers) qcLog(whe)library(GenomicRanges) vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") primers <- GRanges("test_segment", IRanges::IRanges( start = c(40, 880), width = c(25, 30))) whe <- flagPrimerSites(whe, primers) qcLog(whe)
Identifies variant sites where technical replicates disagree and
flags them in the qcPass assay.
flagReplicateDiscordance(whe, freqTolerance = 0.05, requireBothDetected = TRUE)flagReplicateDiscordance(whe, freqTolerance = 0.05, requireBothDetected = TRUE)
whe |
A |
freqTolerance |
Numeric scalar. Maximum allowable difference
in |
requireBothDetected |
Logical scalar. If |
For each replicate group, the function compares altFreq
between replicates and flags sites where the difference exceeds
freqTolerance. Sites are flagged in ALL samples of the
replicate group.
Roder et al. (2023) reported that the choice of variant caller and
the use of replicate sequencing had the largest effects on
single-nucleotide variant discovery. An
alternative approach is replicate intersection (requiring
detection in both replicates); use requireBothDetected =
TRUE with freqTolerance = Inf to achieve this.
A WithinHostExperiment with updated
qcPass and qcLog.
Roder AE et al. (2023). Optimized quantification of intra-host viral diversity in SARS-CoV-2 and influenza virus sequence data. mBio 14:e01046-23. doi:10.1128/mbio.01046-23
flagISNV for standard QC filtering.
rep1 <- system.file("extdata", "test_donor_rep1.vcf", package = "WithinHostExperiment") rep2 <- system.file("extdata", "test_donor_rep2.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(rep1, rep2), colData = S4Vectors::DataFrame( sample_id = c("rep1", "rep2"), replicate_group = c("donor_reps", "donor_reps"), replicate_id = c("1", "2")), caller = "ivar") whe <- flagReplicateDiscordance(whe, freqTolerance = 0.05) qcLog(whe)rep1 <- system.file("extdata", "test_donor_rep1.vcf", package = "WithinHostExperiment") rep2 <- system.file("extdata", "test_donor_rep2.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(rep1, rep2), colData = S4Vectors::DataFrame( sample_id = c("rep1", "rep2"), replicate_group = c("donor_reps", "donor_reps"), replicate_id = c("1", "2")), caller = "ivar") whe <- flagReplicateDiscordance(whe, freqTolerance = 0.05) qcLog(whe)
Applies Strand Coverage Imbalance filtering to a
WithinHostExperiment. Sites whose absolute SCI
score exceeds maxSCI are flagged in the qcPass
assay.
flagStrandBiasSCI(whe, maxSCI = 1.5)flagStrandBiasSCI(whe, maxSCI = 1.5)
whe |
A |
maxSCI |
Numeric scalar. Maximum allowable absolute SCI
score (default: 1.5). Sites with |
Unlike the simple fold-difference strand bias in
flagISNV, this function uses the SCI score, which
normalises by the global strand coverage ratio (see
sciStrandBias). This is preferred for amplicon-based sequencing
where forward and reverse strand coverage is inherently
unbalanced.
A WithinHostExperiment with updated
qcPass and qcLog.
Mostefai F et al. (2024). Refining SARS-CoV-2 intra-host variation by leveraging large-scale sequencing data. NAR Genomics and Bioinformatics 6:lqae145. doi:10.1093/nargab/lqae145
sciStrandBias for the underlying
computation, flagISNV for standard QC.
library(SummarizedExperiment) library(GenomicRanges) gr <- GRanges("seg", IRanges::IRanges(start = 1:3, width = 1)) assays <- list( altFreq = matrix(c(0.1, 0.05, 0.2), ncol = 1), totalDepth = matrix(c(1000L, 1000L, 1000L), ncol = 1), qcPass = matrix(TRUE, nrow = 3, ncol = 1), fwdAltCount = matrix(c(50L, 2L, 30L), ncol = 1), revAltCount = matrix(c(45L, 40L, 28L), ncol = 1), fwdTotalCount = matrix(c(500L, 500L, 500L), ncol = 1), revTotalCount = matrix(c(480L, 480L, 480L), ncol = 1) ) whe <- WithinHostExperiment( rowRanges = gr, assays = assays, colData = S4Vectors::DataFrame(sample_id = "s1")) whe <- flagStrandBiasSCI(whe, maxSCI = 1.5) qcLog(whe)library(SummarizedExperiment) library(GenomicRanges) gr <- GRanges("seg", IRanges::IRanges(start = 1:3, width = 1)) assays <- list( altFreq = matrix(c(0.1, 0.05, 0.2), ncol = 1), totalDepth = matrix(c(1000L, 1000L, 1000L), ncol = 1), qcPass = matrix(TRUE, nrow = 3, ncol = 1), fwdAltCount = matrix(c(50L, 2L, 30L), ncol = 1), revAltCount = matrix(c(45L, 40L, 28L), ncol = 1), fwdTotalCount = matrix(c(500L, 500L, 500L), ncol = 1), revTotalCount = matrix(c(480L, 480L, 480L), ncol = 1) ) whe <- WithinHostExperiment( rowRanges = gr, assays = assays, colData = S4Vectors::DataFrame(sample_id = "s1")) whe <- flagStrandBiasSCI(whe, maxSCI = 1.5) qcLog(whe)
For longitudinal samples from the same host, identifies iSNVs that appear at only one timepoint (transient) versus those detected at multiple consecutive timepoints (persistent). Transient iSNVs are more likely to be sequencing artefacts; persistent ones are more likely genuine within-host variants.
flagTemporalInconsistency( whe, hostCol = "host_id", timeCol = "timepoint", minTimepoints = 2L, minConsecutive = NULL, usePassedOnly = TRUE )flagTemporalInconsistency( whe, hostCol = "host_id", timeCol = "timepoint", minTimepoints = 2L, minConsecutive = NULL, usePassedOnly = TRUE )
whe |
A |
hostCol |
Character scalar. Column name in |
timeCol |
Character scalar. Column name in |
minTimepoints |
Integer scalar. Minimum number of timepoints at which an iSNV must be detected to be considered persistent (default: 2). |
minConsecutive |
Integer scalar. Minimum number of
consecutive timepoints required. If |
usePassedOnly |
Logical. If |
This check applies only where a host was sampled more than once. The biological rationale: genuine within-host variants arise from viral replication and should persist (or change smoothly) across consecutive timepoints. An iSNV that appears once at 5% and is absent at all other timepoints is more likely a sequencing or library preparation artefact.
The function records the filtering step in qcLog with
step name "temporal_consistency".
A WithinHostExperiment with updated
qcPass assay and a new column temporal_class in
mcols(rowRanges). Sites are classified within each host;
a transient site is flagged only in the samples of the hosts
where it is transient. The site-level temporal_class is
"persistent" if the site is persistent in at least one
host, "transient" if it is transient in every host where
it was detected, and NA where it was never detected.
Farjo M et al. (2024). Within-host evolutionary dynamics and tissue compartmentalization during acute SARS-CoV-2 infection. J. Virol. 98:e01618-23. doi:10.1128/jvi.01618-23
gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") freq <- matrix(c(0.1, NA, 0.2, # var1: 2 timepoints 0.05, NA, NA, # var2: 1 timepoint (transient) 0.15, 0.2, 0.1), # var3: 3 timepoints nrow = 3, ncol = 3) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("t1", "t2", "t3"), host_id = c("H1", "H1", "H1"), timepoint = c(1, 2, 3))) whe <- flagTemporalInconsistency(whe, minTimepoints = 2L) S4Vectors::mcols(SummarizedExperiment::rowRanges(whe))$temporal_classgr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C", "G") S4Vectors::mcols(gr)$alt <- c("T", "G", "A") freq <- matrix(c(0.1, NA, 0.2, # var1: 2 timepoints 0.05, NA, NA, # var2: 1 timepoint (transient) 0.15, 0.2, 0.1), # var3: 3 timepoints nrow = 3, ncol = 3) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("t1", "t2", "t3"), host_id = c("H1", "H1", "H1"), timepoint = c(1, 2, 3))) whe <- flagTemporalInconsistency(whe, minTimepoints = 2L) S4Vectors::mcols(SummarizedExperiment::rowRanges(whe))$temporal_class
Computes Fu's Fs (Fu 1997), a test of neutrality based on the probability of observing at least the observed number of alleles given the estimate of theta from nucleotide diversity. Under the infinite-sites model:
where is the probability of observing a number of
alleles greater than or equal to the observed value, given
. A large negative Fs indicates an
excess of alleles (rare variants), consistent with population
expansion or genetic hitchhiking.
fusFs(S, n, theta_pi)fusFs(S, n, theta_pi)
S |
Integer scalar. Number of segregating sites (alleles minus 1). |
n |
Numeric scalar. Sample size. As in |
theta_pi |
Numeric scalar. Theta estimated from
nucleotide diversity ( |
The probability is approximated using Ewens' sampling formula for the expected number of alleles.
Numeric scalar. Fu's Fs, or NA with a warning when
: the statistic uses as the number of
haplotypes, which cannot exceed the number of sequences sampled.
Fu YX (1997). Statistical tests of neutrality of mutations against population growth, hitchhiking and background selection. Genetics 147:915-925.
fusFs(S = 10, n = 100, theta_pi = 5)fusFs(S = 10, n = 100, theta_pi = 5)
Constructs a quality control filter configuration for use with
flagISNV.
ISNVFilter( minDepth = 100L, minFreq = 0.03, maxFreq = 0.5, minAltReads = 10L, maxStrandBias = 10, minBaseQual = 20L, minMapQual = 20L, replicateConc = FALSE )ISNVFilter( minDepth = 100L, minFreq = 0.03, maxFreq = 0.5, minAltReads = 10L, maxStrandBias = 10, minBaseQual = 20L, minMapQual = 20L, replicateConc = FALSE )
minDepth |
Integer. Minimum read depth (default: 100). |
minFreq |
Numeric. Minimum alt allele frequency (default: 0.03). |
maxFreq |
Numeric. Maximum alt allele frequency (default: 0.50). |
minAltReads |
Integer. Minimum alt allele reads (default: 10). |
maxStrandBias |
Numeric. Max strand bias fold-diff (default: 10). |
minBaseQual |
Integer. Minimum base quality (default: 20).
Recorded for provenance; |
minMapQual |
Integer. Minimum mapping quality (default: 20).
Recorded for provenance; |
replicateConc |
Logical. Require replicate concordance
(default: FALSE). Recorded for provenance; apply it with
|
An ISNVFilter object.
ISNVFilter() ISNVFilter(minDepth = 500L, minFreq = 0.05)ISNVFilter() ISNVFilter(minDepth = 500L, minFreq = 0.05)
An S4 class holding parameters for filtering intra-host single
nucleotide variants. Used by flagISNV.
## S4 method for signature 'ISNVFilter' show(object)## S4 method for signature 'ISNVFilter' show(object)
object |
An |
Default values are based on commonly used thresholds in the within-host pathogen diversity literature:
minDepth = 100Minimum coverage for variant detection. This is a permissive floor: Grubaugh et al. (2019, Genome Biology) measured iSNVs above 3% accurately from at least 1,000 RNA copies sequenced to at least 400x.
minFreq = 0.033% minor allele frequency threshold, the lower limit of accurate iSNV measurement in Grubaugh et al. (2019); Popa et al. (2020, Science Translational Medicine) used 2%.
maxFreq = 0.50Variants above 50% are considered consensus-level changes, not intra-host variants.
minAltReads = 10Minimum supporting reads to reduce false positives from sequencing errors.
maxStrandBias = 10Maximum fold-difference between forward and reverse strand alt allele counts. Excessive strand bias suggests systematic error.
An object of class ISNVFilter. Objects are created with
the constructor ISNVFilter; the show method prints
the filter thresholds and returns NULL invisibly.
minDepthInteger scalar. Minimum read depth at iSNV site (default: 100).
minFreqNumeric scalar. Minimum alternative allele frequency (default: 0.03).
maxFreqNumeric scalar. Maximum alternative allele frequency; variants above this are excluded as near-fixed (default: 0.50).
minAltReadsInteger scalar. Minimum reads supporting the alternative allele (default: 10).
maxStrandBiasNumeric scalar. Maximum strand bias as fold-difference between strands (default: 10.0).
minBaseQualInteger scalar. Minimum mean base quality
(default: 20). Recorded for provenance only:
flagISNV does not apply it, because the object
model has no per-site quality assay. Filter on quality in the
variant caller instead.
minMapQualInteger scalar. Minimum mean mapping quality
(default: 20). Recorded for provenance only, as for
minBaseQual.
replicateConcLogical scalar. Whether replicate concordance
is required (default: FALSE). Recorded for provenance only;
apply it with flagReplicateDiscordance.
Roder AE et al. (2023). Optimized quantification of intra-host viral diversity in SARS-CoV-2 and influenza virus sequence data. mBio 14:e01046-23. doi:10.1128/mbio.01046-23
McCrone JT, Lauring AS (2016). Measurements of intrahost viral diversity are extremely sensitive to systematic errors in variant calling. J Virol 90:6884-6895. doi:10.1128/JVI.00667-16
Grubaugh ND et al. (2019). An amplicon-based sequencing framework for accurately measuring intrahost virus diversity using PrimalSeq and iVar. Genome Biology 20:8. doi:10.1186/s13059-018-1618-7
Mostefai F et al. (2024). Refining SARS-CoV-2 intra-host variation by leveraging large-scale sequencing data. NAR Genomics and Bioinformatics 6:lqae145. doi:10.1093/nargab/lqae145
ISNVFilter for the constructor,
flagISNV for applying filters.
# Default filter ISNVFilter() # Stringent filter ISNVFilter(minDepth = 500L, minFreq = 0.05)# Default filter ISNVFilter() # Stringent filter ISNVFilter(minDepth = 500L, minFreq = 0.05)
Thin wrapper around p.adjust for convenient
multiple testing correction of p-value vectors.
multitestCorrection(pvalues, method = "BH")multitestCorrection(pvalues, method = "BH")
pvalues |
Numeric vector of raw p-values. |
method |
Character scalar. Correction method passed to
|
Numeric vector of adjusted p-values (same length as
pvalues).
raw_p <- c(0.001, 0.04, 0.03, 0.5) multitestCorrection(raw_p) multitestCorrection(raw_p, method = "bonferroni")raw_p <- c(0.001, 0.04, 0.03, 0.5) multitestCorrection(raw_p) multitestCorrection(raw_p, method = "bonferroni")
Computes Tajima's D, Fu's Fs, and Fay and Wu's H from a single
WithinHostSFS object. This is the recommended
interface for neutrality testing: build the SFS once, then
derive all statistics from it.
neutralityFromSFS(sfs, n = NULL)neutralityFromSFS(sfs, n = NULL)
sfs |
A |
n |
Integer. Effective sample size for neutrality tests.
For deep sequencing data, do not use raw depth.
Default: |
Fay and Wu's H measures an excess of high-frequency derived variants, which can indicate positive selection or genetic hitchhiking (Fay and Wu 2000). It is defined as:
where
and is the count of sites with derived
alleles. A negative H indicates an excess of high-frequency
variants.
A named list with components:
Number of segregating sites.
Effective sample size used.
Nucleotide diversity (per site).
Watterson's theta.
Fay and Wu's theta_H.
Tajima's D.
Fu's Fs.
Fay and Wu's H.
Whether the input SFS was bias-corrected.
Fay JC, Wu CI (2000). Hitchhiking under positive Darwinian selection. Genetics 155:1405-1413.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L, fold = FALSE) neutralityFromSFS(sfs)vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L, fold = FALSE) neutralityFromSFS(sfs)
Non-parametric permutation test comparing a summary statistic between two groups. The observed test statistic is compared to a null distribution obtained by randomly permuting group labels.
permutationTest(values, groups, n_perm = 999L, statistic = "mean_diff")permutationTest(values, groups, n_perm = 999L, statistic = "mean_diff")
values |
Numeric vector. The measured values (e.g.\ a diversity index). |
groups |
Factor or character vector (same length as
|
n_perm |
Integer scalar. Number of permutations (default: 999). Permutation uses the random number generator, so set a seed for a reproducible p-value. |
statistic |
Character scalar. The test statistic to compute:
|
A list with components:
Numeric scalar. The observed test statistic.
Numeric scalar. Two-sided permutation p-value.
Integer. Number of permutations performed.
set.seed(42) vals <- c(rnorm(10, 2), rnorm(10, 3)) grps <- rep(c("A", "B"), each = 10) permutationTest(vals, grps, n_perm = 499)set.seed(42) vals <- c(rnorm(10, 2), rnorm(10, 3)) grps <- rep(c("A", "B"), each = 10) permutationTest(vals, grps, n_perm = 499)
Estimates nucleotide diversity following Nei & Li (1979), adapted for deep sequencing allele frequencies:
where is the alternative allele frequency at site
, is the genome length, and is the
number of reads sampled at a site, for which mean read depth
stands in. The correction removes the
finite-sample bias of estimating a frequency from
draws (Nei 1987, Eq. 10.5). This is a different use of
"sample size" from the one in tajimaD, where
reads must not stand in for independent lineages. When
meanDepth is not provided, the uncorrected estimator
is returned (equivalent to infinite sample size).
piISNV(freq, genomeLength, meanDepth = NULL)piISNV(freq, genomeLength, meanDepth = NULL)
freq |
Numeric vector. Alternative allele frequencies. |
genomeLength |
Integer scalar. Reference genome length in bp. |
meanDepth |
Numeric scalar (optional). Mean read depth for
finite-sample correction. If |
Numeric scalar. Nucleotide diversity.
Nei M, Li WH (1979). Mathematical model for studying genetic variation in terms of restriction endonucleases. Proc Natl Acad Sci USA 76:5269-5273. doi:10.1073/pnas.76.10.5269
Nei M (1987). Molecular Evolutionary Genetics. Columbia University Press, New York. Chapter 10.
piISNV(c(0.1, 0.3, 0.5), genomeLength = 13588) piISNV(c(0.1, 0.3, 0.5), genomeLength = 13588, meanDepth = 1000)piISNV(c(0.1, 0.3, 0.5), genomeLength = 13588) piISNV(c(0.1, 0.3, 0.5), genomeLength = 13588, meanDepth = 1000)
Creates a publication-quality histogram or density plot of alternative allele frequencies, optionally faceted by sample or metadata group.
plotFrequencySpectrum( whe, type = c("histogram", "density"), facetBy = NULL, usePassedOnly = TRUE, threshold = NULL, freqRange = NULL )plotFrequencySpectrum( whe, type = c("histogram", "density"), facetBy = NULL, usePassedOnly = TRUE, threshold = NULL, freqRange = NULL )
whe |
|
type |
Character. |
facetBy |
Character (optional). Column name in
|
usePassedOnly |
Logical. Only show QC-passed variants (default: TRUE). |
threshold |
Numeric scalar (optional). Variant calling
threshold to display as reference line. Default |
freqRange |
Numeric vector of length 2 (optional). Frequency
range for the x-axis as proportions, e.g. |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "d"), caller = "ivar") plotFrequencySpectrum(whe, threshold = 0.03) }if (requireNamespace("ggplot2", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "d"), caller = "ivar") plotFrequencySpectrum(whe, threshold = 0.03) }
Creates a ggplot2 spaghetti plot of allele frequency over time for each variant within a specified host. Each line represents one variant, coloured with the Okabe-Ito palette (Wong 2011); with more than eight variants the colours repeat.
plotFrequencyTrajectory( whe, hostId, hostCol = "host_id", timeCol = "timepoint", usePassedOnly = TRUE )plotFrequencyTrajectory( whe, hostId, hostCol = "host_id", timeCol = "timepoint", usePassedOnly = TRUE )
whe |
A |
hostId |
Character scalar. Host identifier to plot. |
hostCol |
Character scalar. Column name in |
timeCol |
Character scalar. Column name in |
usePassedOnly |
Logical. If TRUE (default), only use QC-passed variants. |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.05, 0.10, 0.15, 0.20), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) plotFrequencyTrajectory(whe, hostId = "H1") }if (requireNamespace("ggplot2", quietly = TRUE)) { gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.05, 0.10, 0.15, 0.20), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) plotFrequencyTrajectory(whe, hostId = "H1") }
Creates a publication-quality scatter plot comparing variant frequencies between a donor and recipient, highlighting shared and unique variants with distinct shapes and colours.
plotPairScatter(whe, pairId, threshold = 0.03)plotPairScatter(whe, pairId, threshold = 0.03)
whe |
A |
pairId |
Character. Which pair to plot. |
threshold |
Numeric. Variant calling threshold shown as reference lines (default: 0.03). |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") plotPairScatter(whe, pairId = "p1") }if (requireNamespace("ggplot2", quietly = TRUE)) { vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") plotPairScatter(whe, pairId = "p1") }
Creates a publication-quality four-panel dashboard showing frequency distribution, read depth, QC filtering waterfall, and per-sample pass/fail summary.
plotQCDashboard(whe)plotQCDashboard(whe)
whe |
A patchwork composite ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("patchwork", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "d"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter()) plotQCDashboard(whe) }if (requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("patchwork", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "d"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter()) plotQCDashboard(whe) }
Creates a publication-quality bar plot of a
WithinHostSFS object.
plotSFS(sfs, normalize = FALSE)plotSFS(sfs, normalize = FALSE)
sfs |
A |
normalize |
Logical. If |
A ggplot object.
if (requireNamespace("ggplot2", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) plotSFS(sfs) }if (requireNamespace("ggplot2", quietly = TRUE)) { vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") sfs <- buildSFS(whe, genomeLength = 1000L) plotSFS(sfs) }
Returns the quality control log recording all filtering steps
applied to a WithinHostExperiment.
Returns a subset of the WithinHostExperiment containing
only variant sites where at least one sample passed QC.
Identifies donor-recipient pairs based on role and
pair_id columns in colData.
qcLog(x) qcFilters(x) passedISNV(x) transmissionPairs(x) qcLog(x) <- value qcFilters(x) <- value ## S4 method for signature 'WithinHostExperiment' qcLog(x) ## S4 method for signature 'WithinHostExperiment' qcFilters(x) ## S4 replacement method for signature 'WithinHostExperiment' qcLog(x) <- value ## S4 replacement method for signature 'WithinHostExperiment' qcFilters(x) <- value ## S4 method for signature 'WithinHostExperiment' passedISNV(x) ## S4 method for signature 'WithinHostExperiment' transmissionPairs(x)qcLog(x) qcFilters(x) passedISNV(x) transmissionPairs(x) qcLog(x) <- value qcFilters(x) <- value ## S4 method for signature 'WithinHostExperiment' qcLog(x) ## S4 method for signature 'WithinHostExperiment' qcFilters(x) ## S4 replacement method for signature 'WithinHostExperiment' qcLog(x) <- value ## S4 replacement method for signature 'WithinHostExperiment' qcFilters(x) <- value ## S4 method for signature 'WithinHostExperiment' passedISNV(x) ## S4 method for signature 'WithinHostExperiment' transmissionPairs(x)
x |
A |
value |
A list replacement value. |
A DataFrame with columns: step, parameter, value,
n_flagged, n_passed, timestamp.
A list of ISNVFilter objects.
A WithinHostExperiment (subset of rows).
A DataFrame with columns: donor (sample_id),
recipient (sample_id), pair_id.
The modified WithinHostExperiment.
The modified WithinHostExperiment.
gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(1:3, width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(runif(3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) qcLog(whe) gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(1:3, width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(runif(3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) passedISNV(whe)gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(1:3, width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(runif(3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) qcLog(whe) gr <- GenomicRanges::GRanges("seg", IRanges::IRanges(1:3, width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(runif(3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) passedISNV(whe)
Produces per-sample summary statistics from a
WithinHostExperiment.
qcSummary(whe)qcSummary(whe)
whe |
A DataFrame with columns: sample_id, total_sites,
passed_sites, flagged_sites, pass_rate, median_depth, median_freq.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter(minDepth = 500L)) qcSummary(whe)vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe <- flagISNV(whe, ISNVFilter(minDepth = 500L)) qcSummary(whe)
Estimates transmission bottleneck size using the approximate
beta-binomial method of Sobel Leonard et al. (2017), which
does not model sequencing depth explicitly. For the exact
beta-binomial method (which accounts for finite read depth)
or other approaches (presence-absence, KL divergence,
Wright-Fisher), use the ViralBottleneck package
(Zheng et al. 2025, Virus Evolution 11:veaf071)
via asViralBottleneckInput.
quickBottleneck(whe, pairId, maxNb = 200L, threshold = 0.03)quickBottleneck(whe, pairId, maxNb = 200L, threshold = 0.03)
whe |
A |
pairId |
Character. Which pair to estimate. |
maxNb |
Integer. Maximum bottleneck size to evaluate (default: 200). |
threshold |
Numeric. Variant calling threshold (default: 0.03). |
A named list: Nb (MLE), ci (95% CI), loglik, n_variants, pair_id.
Sobel Leonard A et al. (2017). Transmission Bottleneck Size Estimation from Pathogen Deep-Sequencing Data. J Virol 91:e00171-17. doi:10.1128/JVI.00171-17
vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") quickBottleneck(whe, pairId = "p1", maxNb = 50L)vcf1 <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") vcf2 <- system.file("extdata", "test_recipient.vcf", package = "WithinHostExperiment") whe <- readWithinHost(c(vcf1, vcf2), colData = S4Vectors::DataFrame( sample_id = c("donor", "recipient"), role = c("donor", "recipient"), pair_id = c("p1", "p1")), caller = "ivar") quickBottleneck(whe, pairId = "p1", maxNb = 50L)
Reads one or more VCF files produced by variant callers (LoFreq,
Freebayes, or generic) and constructs a
WithinHostExperiment.
readWithinHost(vcfFiles, colData, caller = "auto", genome = NULL)readWithinHost(vcfFiles, colData, caller = "auto", genome = NULL)
vcfFiles |
Character vector. Paths to VCF files (one per sample). |
colData |
|
caller |
Character scalar. Which field layout the VCF uses:
|
genome |
Character scalar (optional). Path to reference FASTA (stored in metadata, not currently used for annotation). |
Each VCF file corresponds to one sample. The colData
argument provides sample metadata and must contain a
sample_id column whose length matches the number of files.
caller = "ivar" refers to a VCF carrying iVar's per-sample
fields (REF_DP, ALT_DP, ALT_FREQ,
ALT_QUAL). ivar variants itself writes a TSV, not a
VCF, so such files come from a converter, for instance
ivar_variants_to_vcf.py in the nf-core/viralrecon pipeline.
An iVar TSV is read directly by
readWithinHostTable(format = "ivar").
Records with several alternative alleles are split into one site per
allele. Per-allele values (INFO AF; FORMAT AO,
AF, ALT_DP, ALT_FREQ and ALT_QUAL) are
taken for that allele, and per-site values such as total depth and
reference counts are shared.
When technical replicates are not available, Roder et al. (2023,
mBio 14:e01046-23) recommend using a combination of
multiple variant callers with stringent cutoffs to reduce false
positives. To implement multi-caller consensus, import VCFs from
each caller separately and intersect the resulting objects using
standard GRanges overlap operations.
vcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whevcf <- system.file("extdata", "test_donor.vcf", package = "WithinHostExperiment") whe <- readWithinHost(vcf, colData = S4Vectors::DataFrame(sample_id = "donor"), caller = "ivar") whe
Reads pre-computed iSNV data from TSV or CSV files and constructs
a WithinHostExperiment.
readWithinHostTable(files, format = c("ivar", "generic"), colData = NULL)readWithinHostTable(files, format = c("ivar", "generic"), colData = NULL)
files |
Character vector. Paths to files (one per sample). |
format |
Character. |
colData |
|
tsv <- system.file("extdata", "test_donor_ivar.tsv", package = "WithinHostExperiment") whe <- readWithinHostTable(tsv, format = "ivar") whetsv <- system.file("extdata", "test_donor_ivar.tsv", package = "WithinHostExperiment") whe <- readWithinHostTable(tsv, format = "ivar") whe
Computes a strand bias score that normalises the per-site forward/reverse alternative-allele ratio by the global strand coverage ratio, making it suitable for amplicon data where strand coverage is inherently unequal. Mostefai et al. (2024) showed that strand bias filters which ignore this imbalance can discard genuine variants; their own metric is a per-strand binomial likelihood.
sciStrandBias(fwd_alt, rev_alt, fwd_total, rev_total)sciStrandBias(fwd_alt, rev_alt, fwd_total, rev_total)
fwd_alt |
Numeric vector. Forward-strand alternative allele read counts. |
rev_alt |
Numeric vector. Reverse-strand alternative allele read counts. |
fwd_total |
Numeric vector. Forward-strand total read counts. |
rev_total |
Numeric vector. Reverse-strand total read counts. |
The SCI is defined as:
where and are
the sums of forward and reverse total counts across all sites.
A pseudocount of 1 is added to strand counts to avoid division
by zero.
A numeric vector of SCI scores (log2 scale). Values near zero indicate no strand bias; large absolute values indicate strand-specific enrichment or depletion.
Mostefai F et al. (2024). Refining SARS-CoV-2 intra-host variation by leveraging large-scale sequencing data. NAR Genomics and Bioinformatics 6:lqae145. doi:10.1093/nargab/lqae145
sci <- sciStrandBias( fwd_alt = c(50, 2, 30), rev_alt = c(45, 40, 28), fwd_total = c(500, 500, 500), rev_total = c(480, 480, 480) ) scisci <- sciStrandBias( fwd_alt = c(50, 2, 30), rev_alt = c(45, 40, 28), fwd_total = c(500, 500, 500), rev_total = c(480, 480, 480) ) sci
Calculates per-site biallelic Shannon entropy, summed across all iSNV sites:
where is the alternative allele frequency at site
. This formulation treats each iSNV as an independent
biallelic locus, following McCrone & Lauring (2016) and Popa
et al. (2020). Higher values indicate greater within-host
diversity.
shannonISNV(freq)shannonISNV(freq)
freq |
Numeric vector. Alternative allele frequencies (values in (0, 1); 0, 1, and NA are excluded). |
Numeric scalar. Shannon entropy.
Lauring AS (2020). Within-Host Viral Diversity: A Window into Viral Evolution. Annu Rev Virol 7:63-81. doi:10.1146/annurev-virology-010320-061642
shannonISNV(c(0.05, 0.15, 0.30, 0.45)) shannonISNV(0.5) # log(2)shannonISNV(c(0.05, 0.15, 0.30, 0.45)) shannonISNV(0.5) # log(2)
Calculates Simpson diversity summed over biallelic iSNV sites:
where is the alternative allele frequency at site
. Each site contributes the probability that two alleles
drawn at random at that site differ (at most 0.5), so the index is
the expected number of differing sites. Higher values indicate
greater diversity.
simpsonISNV(freq)simpsonISNV(freq)
freq |
Numeric vector. Alternative allele frequencies (values in (0, 1); 0, 1, and NA are excluded). |
Numeric scalar. Simpson diversity index.
simpsonISNV(c(0.05, 0.15, 0.30, 0.45)) simpsonISNV(0.5) # maximum per-site diversity = 0.5simpsonISNV(c(0.05, 0.15, 0.30, 0.45)) simpsonISNV(0.5) # maximum per-site diversity = 0.5
Computes Tajima's D (Tajima 1989) from summary statistics of
a within-host pathogen population. The statistic measures the
difference between nucleotide diversity () and
Watterson's estimator (),
normalised by its expected standard deviation under neutrality:
A significantly negative D indicates an excess of rare variants (e.g., population expansion or purifying selection), while a positive D suggests balancing selection or population contraction.
tajimaD(S, n, pi_hat, genomeLength)tajimaD(S, n, pi_hat, genomeLength)
S |
Integer scalar. Number of segregating sites. |
n |
Numeric scalar. Sample size. For deep sequencing data,
do not use raw read depth directly – the classical
Tajima's D assumes independent lineages, whereas sequencing
reads oversample a much smaller viral population.
Use an effective sample size (e.g., |
pi_hat |
Numeric scalar. Nucleotide diversity estimate (per-site). |
genomeLength |
Integer scalar. Reference genome length in bp. |
Deep sequencing caveat: The classical Tajima (1989)
formula was designed for independent haplotype samples (e.g.,
Sanger-sequenced clones or individual genomes). In deep
sequencing, reads are not independent lineages; they are
technical replicates of a viral population whose effective
size is much smaller than the read depth. Using raw read
depth (e.g., n = 2000) inflates the harmonic number
, making artificially
small and biasing D toward positive values.
We recommend capping n at a biologically meaningful
effective sample size. Lauring (2020 Annu Rev Virol)
suggests n = 100 as a conservative proxy. The high-level
calcNeutralityTests method applies this cap
automatically (n = min(depth, 100)).
Interpretation warning: Deep-sequencing D values are not directly comparable to classical population-genetic D computed from independently sampled haplotypes. We recommend reporting D as a descriptive statistic of the site frequency spectrum (e.g., "the majority of samples show negative D, consistent with an excess of rare variants"), rather than as a formal neutrality test with p-value interpretation. For rigorous neutrality testing on deep sequencing data, consider simulation-based approaches that account for the read-sampling process.
Numeric scalar. Tajima's D.
Tajima F (1989). Statistical method for testing the neutral mutation hypothesis by DNA polymorphism. Genetics 123:585-595.
Lauring AS (2020). Within-Host Viral Diversity: A Window into Viral Evolution. Annu Rev Virol 7:63-81. doi:10.1146/annurev-virology-010320-061642
tajimaD(S = 10, n = 1000, pi_hat = 0.001, genomeLength = 10000)tajimaD(S = 10, n = 1000, pi_hat = 0.001, genomeLength = 10000)
Extracts per-variant allele frequency trajectories across
longitudinal time points within each host. Returns a long-format
DataFrame suitable for downstream analysis or plotting.
trackFrequency( whe, hostCol = "host_id", timeCol = "timepoint", usePassedOnly = TRUE )trackFrequency( whe, hostCol = "host_id", timeCol = "timepoint", usePassedOnly = TRUE )
whe |
A |
hostCol |
Character scalar. Column name in |
timeCol |
Character scalar. Column name in |
usePassedOnly |
Logical. If TRUE (default), only use
variants where |
A DataFrame with columns:
Host identifier.
Unique variant identifier (chrom:pos:alt).
Time point value.
Alternative allele frequency at that time point.
Logical. Whether the variant passed QC
at that time point (TRUE if no QC assay present).
gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.05, 0.10, 0.15, 0.20), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) trackFrequency(whe)gr <- GenomicRanges::GRanges("seg1", IRanges::IRanges(c(100, 200), width = 1)) S4Vectors::mcols(gr)$ref <- c("A", "C") S4Vectors::mcols(gr)$alt <- c("T", "G") freq <- matrix(c(0.05, 0.10, 0.15, 0.20), nrow = 2, ncol = 2) whe <- WithinHostExperiment( assays = list(altFreq = freq), rowRanges = gr, colData = S4Vectors::DataFrame( sample_id = c("S1_t1", "S1_t2"), host_id = c("H1", "H1"), timepoint = c(1, 2))) trackFrequency(whe)
Compares observed diversity metrics against wide plausibility
ranges for common pathogens, as a sanity check on units and
orders of magnitude rather than a reference interval. The bounds
are the package's own heuristics, not estimates from a specific
study, and are deliberately generous: is compared on a
per-site scale and Shannon entropy on the scale returned by
calcDiversity. A value inside the range is not
evidence that an estimate is correct.
validateDiversity( div_df, pathogen = c("sars_cov_2", "influenza", "hiv", "tb", "generic") )validateDiversity( div_df, pathogen = c("sars_cov_2", "influenza", "hiv", "tb", "generic") )
div_df |
A |
pathogen |
Character scalar. Pathogen whose plausibility
range is used.
One of |
A DataFrame with columns:
Diversity metric name.
Observed value.
Lower bound of the plausibility range.
Upper bound of the plausibility range.
Logical; whether the observed value falls within the range.
div <- S4Vectors::DataFrame(pi = 1e-4, shannon = 5.0) validateDiversity(div, pathogen = "sars_cov_2")div <- S4Vectors::DataFrame(pi = 1e-4, shannon = 5.0) validateDiversity(div, pathogen = "sars_cov_2")
Estimates Watterson's theta (Watterson 1975):
where is the number of segregating sites,
is the harmonic number,
and is the genome length, with the number of
sampled viral genomes.
wattersonISNV(nSites, genomeLength, meanDepth)wattersonISNV(nSites, genomeLength, meanDepth)
nSites |
Integer scalar. Number of segregating (iSNV) sites. |
genomeLength |
Integer scalar. Genome length in bp. |
meanDepth |
Numeric scalar. Sample size |
As tajimaD sets out, raw read depth should
not be passed as for deep sequencing data:
reads are technical replicates of a much smaller viral
population, and a depth-sized inflates and
makes correspondingly small. Pass an effective
sample size, or a depth capped at one, as
calcNeutralityTests does.
Numeric scalar. Watterson's theta estimate.
Watterson GA (1975). On the number of segregating sites in genetical models without recombination. Theor Popul Biol 7:256-276. doi:10.1016/0040-5809(75)90020-9
## n is an effective sample size, not the raw depth wattersonISNV(15, genomeLength = 13588, meanDepth = 100)## n is an effective sample size, not the raw depth wattersonISNV(15, genomeLength = 13588, meanDepth = 100)
Methods for the combineRows and
combineCols generics.
combineRows combines objects vertically (adding variant
sites); all objects must have the same samples (columns).
combineCols combines objects horizontally (adding
samples); all objects must have the same variant sites (rows).
QC logs and filters from all objects are merged.
## S4 method for signature 'WithinHostExperiment' combineRows(x, ...) ## S4 method for signature 'WithinHostExperiment' combineCols(x, ..., use.names = TRUE)## S4 method for signature 'WithinHostExperiment' combineRows(x, ...) ## S4 method for signature 'WithinHostExperiment' combineCols(x, ..., use.names = TRUE)
x |
|
... |
Further |
use.names |
Accepted for compatibility with the generic. Sites are combined in the order given, not matched by name. |
data(example_whe) whe1 <- example_whe[1:10, ] whe2 <- example_whe[11:nrow(example_whe), ] merged <- combineRows(whe1, whe2) nrow(merged) == nrow(example_whe) whe_d <- example_whe[, 1] whe_r <- example_whe[, 2] merged <- combineCols(whe_d, whe_r) ncol(merged)data(example_whe) whe1 <- example_whe[1:10, ] whe2 <- example_whe[11:nrow(example_whe), ] merged <- combineRows(whe1, whe2) nrow(merged) == nrow(example_whe) whe_d <- example_whe[, 1] whe_r <- example_whe[, 2] merged <- combineCols(whe_d, whe_r) ncol(merged)
Constructs a WithinHostExperiment from assay matrices,
genomic ranges, and sample metadata.
WithinHostExperiment(assays, rowRanges, colData, metadata = list())WithinHostExperiment(assays, rowRanges, colData, metadata = list())
assays |
Named list of matrices. Must include at least
|
rowRanges |
|
colData |
|
metadata |
Named list (optional). Additional metadata such as
|
A WithinHostExperiment object.
library(GenomicRanges) gr <- GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) whelibrary(GenomicRanges) gr <- GRanges("seg1", IRanges::IRanges(c(100, 200, 300), width = 1)) whe <- WithinHostExperiment( assays = list(altFreq = matrix(c(0.1, 0.2, 0.3), ncol = 1)), rowRanges = gr, colData = S4Vectors::DataFrame(sample_id = "S1")) whe
Extends RangedSummarizedExperiment-class
to hold intra-host single nucleotide variant (iSNV) data with
integrated quality control audit logging.
Subsetting preserves the qcLog and qcFilters slots.
These slots are experiment-level metadata (not row/column-dependent)
and are carried forward unchanged.
## S4 method for signature 'WithinHostExperiment' show(object) ## S4 method for signature 'WithinHostExperiment,ANY,ANY,ANY' x[i, j, ..., drop = TRUE]## S4 method for signature 'WithinHostExperiment' show(object) ## S4 method for signature 'WithinHostExperiment,ANY,ANY,ANY' x[i, j, ..., drop = TRUE]
object |
A |
x |
|
i |
Row indices (variant sites). |
j |
Column indices (samples). |
... |
Additional arguments passed to the parent method. |
drop |
Logical (ignored for |
qcLogDataFrame. Audit trail of QC filtering steps.
qcFilterslist. History of ISNVFilter objects
that have been applied.
altCountInteger matrix. Alternative allele read counts.
refCountInteger matrix. Reference allele read counts.
totalDepthInteger matrix. Total read depth.
altFreqNumeric matrix. Alt allele frequencies (0-1).
qcPassLogical matrix. TRUE = passed QC, FALSE = flagged.
Initialized to all TRUE; updated by flagISNV.
sample_idCharacter. Unique sample identifier (required).
host_idCharacter. Host/patient identifier (optional).
replicate_groupCharacter. Technical replicate group identifier (optional).
replicate_idCharacter. Replicate number within group (optional).
roleCharacter. Sample role: "donor", "recipient", or "independent" (optional).
pair_idCharacter. Transmission pair identifier (optional).
readWithinHost and readWithinHostTable
for constructing from files.
flagISNV for quality control.
passedISNV for extracting QC-passed variants.
transmissionPairs for extracting donor-recipient pairs.
data(example_whe) example_whedata(example_whe) example_whe
An S4 class representing the site frequency spectrum (SFS) of within-host variants from deep sequencing data. The SFS bins variant allele frequencies into discrete categories and stores metadata needed for population-genetic inference.
## S4 method for signature 'WithinHostSFS' show(object)## S4 method for signature 'WithinHostSFS' show(object)
object |
A |
An object of class WithinHostSFS, normally created by
buildSFS; the show method prints a summary of the
spectrum and returns NULL invisibly.
countsInteger vector. Number of iSNVs in each frequency
bin. Length equals length(breaks) - 1.
breaksNumeric vector. Bin boundaries. Bins are right-closed, and the lowest break is included in the first bin. Default: 20 equal bins from the detection threshold to the maximum frequency.
foldedLogical scalar. If TRUE, the SFS is folded
(minor allele frequency); if FALSE, unfolded
(derived allele frequency).
sampleIdCharacter scalar. Sample identifier.
genomeLengthInteger scalar. Reference genome length.
nSitesInteger scalar. Total number of variant sites used to build the SFS.
meanDepthNumeric scalar. Typical sequencing depth;
buildSFS stores the median depth across sites.
thresholdNumeric scalar. Detection threshold applied (e.g., 0.03). Sites below this are unobservable.
correctedLogical scalar. Whether ascertainment bias correction has been applied.
buildSFS for constructing from a
WithinHostExperiment, neutralityFromSFS
for computing all neutrality tests from the SFS.