We introduce aggregate MIcrobiome COVerage (micov), a bioinformatic tool that efficiently computes precise, optionally-aggregated, genomic coverage positions across numerous metagenomes and arbitrary sample types. Micov offers three key advantages over conventional tools: rapid sample type-specific cumulative coverage calculations, identification of mobile or polymorphic genetic elements, and detection of strain heterogeneity through coverage variations.
The primary input mapping structure for micov is per-sample SAM/BAM or BED (3-column). These data are then consolidated into Parquet files to utilize pushdown filters.
micov's compute runs on DuckDB with the
miint extension. miint is a DuckDB
extension, not a Python package, so it is not installed by pip or
conda. micov fetches it from https://ftp.microbio.me/pub/miint the first
time it opens a connection, which requires outbound network access on first
use; afterwards it is cached in ~/.duckdb/extensions/ and no network is
needed.
miint is currently published unsigned, so micov enables DuckDB's
allow_unsigned_extensions on every connection. This is a development
posture and will be tightened once signed builds are available.
DuckDB is pinned to 1.5.4 or 1.5.5. Extensions are built per DuckDB
version, and the repository above carries trees up to v1.5.5; a newer DuckDB
has no miint build to load.
Supported platforms are Linux (x86_64, aarch64) and macOS on Apple silicon. No miint build is published for Windows or for Intel macOS, so micov does not run there.
To install without network access, or to use a local miint build, put the extension somewhere on disk and point micov at it:
export MICOV_MIINT_EXTENSION_PATH=/path/to/miint.duckdb_extensionAlternatively, pre-seed the cache on a machine that does have network access
and copy ~/.duckdb/extensions/ across.
We recommend creating a separate conda environment, and installing into that.
$ pip install micovTo install the most up-to-date version of micov
$ git clone https://github.com/biocore/micov.git
$ cd micov
$ conda create -n micov -c conda-forge python=3.12
$ conda install -q --yes -n micov -c conda-forge --file ci/conda_requirements.txt
$ conda activate micov
$ pip install -e ".[test]"The [test] extra installs pytest, which make test needs. Omit it for a
plain runtime install.
See below for examples of running micov on SAM files.
First, activate the Conda environment where micov is installed:
conda activate micovNext, process SAM files into coverage data. micov accepts headerless
SAM/BAM.
If your input files contain headers, remove them using samtools before running micov:
samtools view -S input.sam > output.samSimilarly, if your input files are in BAM format, convert them to SAM format using samtools:
samtools view input.bam > output.sammicov compress writes two Parquet files per sample,
{output}.coverage.parquet and {output}.covered_positions.parquet.
--lengths is required. It supplies the coverage denominators, and it also
serves as the reference map: headerless SAM carries no header for htslib to
resolve reference names against. An example length file is at
./example/metadata/length.tsv; see step 3 for how to build one.
mkdir -p "./example/parquet"
for file in ./example/samfiles/*.sam.xz; do
sample_id=$(basename "$file" .sam.xz)
echo "Processing $file..."
micov compress \
--data "$file" \
--lengths ./example/metadata/length.tsv \
--output "./example/parquet/${sample_id}"
donemicov also reads from a pipe, which is the better option for large inputs
since nothing is staged on disk. Give it --sample-id, as there is no filename
to take one from:
xzcat foo.sam.xz | micov compress \
--lengths length.tsv --sample-id foo --output fooReads whose reference is absent from --lengths cannot be attributed to a
genome and are dropped; micov reports how many. Note it cannot distinguish
those from reads that simply did not align, because htslib reports both the
same way — so if that count is surprising, check that --lengths covers every
reference the data was aligned against.
micov cov-to-parquet builds the same Parquet representation from BED3
.cov/.cov.gz files. micov compress no longer writes .cov, so this is
for existing coverage files — including aggregating one sample across
several runs, which compress does not do:
micov cov-to-parquet \
--pattern "run*/sample1.cov.gz" \
--output combined/sample1 \
--lengths length.tsvIt requires a length mapping file (length.tsv), which
maps genome IDs to their corresponding genome lengths. An example length file
can be found in ./example/metadata/length.tsv. If this file is not available,
it can for example be generated using seqkit:
seqkit fx2tab --length --name --header-line foo.fasta > length.tsvNow, consolidate the coverage files. On read, micov will interpret the non-extension
portion of a filename as the sample ID. For example, given foo/bar/baz.cov.gz, the
sample ID will be baz.
micov cov-to-parquet \
--pattern "example/coverages/*.cov.gz" \
--output example/parquet/example \
--lengths example/metadata/length.tsvThis command was named nonqiita-to-parquet before micov dropped its Qiita
support. The old name still works but is hidden from --help; prefer
cov-to-parquet.
A series of plots can be constructed guided by metadata. Specifically, micov produces the following:
- Non-cumulative coverage curves for each genome in the feature metadata.
- Cumulative coverage curves for each genome in the feature metadata. These accumulation data are supported by K-S tests written to the output directory.
- Scaled and unscaled position plots for each genome in the feature metadata.
Categorical metadata can be used to group samples; sample-metadata is
required. The genomes to examine can optionally be constrained using
features-to-keep. Specific start and stop regions of genomes can also be
specified within the features-to-keep but limited to a single region per
genome currently.
Both files must have a header line. The first column of a sample metadata
file is the sample ID, under the header sample_id or sample_name; the first
column of a feature metadata file (and of a --target-names file) is the
genome ID, under the header genome_id. micov stops with an error naming the
file otherwise. A file without a header -- a taxonomy lineages.txt, say --
would have its first row taken as column names, and that genome or sample
silently dropped.
The --output parameter specified a prefix for the output files.
Optionally, Monte Carlo curves can be produced for the cumulative plots by
specifying --monte. There are two Monte Carlo options: unfocused and
focused. The unfocused option will select samples at random with any
coverage data, while the focused option will randomly select samples with
nonzero coverage of the current genome. Both options select independent of
sample metadata, and will select the max number of samples observed in a sample
group.
Additionally, users can specify --percentile to display plots with the x-axis
representing percentile of samples instead of absolute sample counts.
By default, the position plots lay sample groups out smallest first, and
groups of equal size in text order, where 270 comes before 30 and 5.
Specify --sort-by-metadata-value to lay them out by metadata value instead,
with numbers in numeric order: 5, 30, 270.
Pairwise Kolmogorov-Smirnov (KS) tests between all sample groups' cumulative coverage curves are automatically conducted and results saved in cumulative.ks.csv. The KS test quantifies whether two sample groups differ in the distribution of their cumulative genome coverages, with the KS statistic measuring the maximal difference between the two cumulative distributions, and the KS p-value assessing the statistical significance of the difference.
The file is comma-separated, one row per pair of curves, with columns label_A, label_B, ks-statistic, ks-pvalue and ks-pvalue-bonferroni. ks-pvalue is uncorrected. ks-pvalue-bonferroni is min(1, p × m), where the family m is the number of group-vs-group comparisons in that file -- that is, for that genome. Comparisons against a --monte curve are a null-model check rather than a hypothesis: they are not counted in m and their corrected value is left empty, so adding --monte never changes a group pair's corrected p-value. Correcting across genomes, or by another method, is left to the analyst.
mkdir -p "./example/plots/per_sample_groups"
micov per-sample \
--parquet-coverage "./example/parquet/example" \
--sample-metadata "./example/metadata/sample_metadata.txt" \
--sample-metadata-column "dog" \
--features-to-keep "./example/metadata/feature_metadata.txt" \
--output "./example/plots/per_sample_groups/example" \
--plotThe binning command allows you to divide genome positions into fix-sized bins and compute summary statistics across samples, based on sample metadata. This is useful for identifying regions of interest (e.g. high variability across samples).
mkdir -p "./example/binning"
micov binning \
--parquet-coverage ./example/parquet/example \
--sample-metadata ./example/metadata/sample_metadata.txt \
--features-to-keep ./example/metadata/feature_metadata.txt \
--metadata-variable "dog" \
--outdir ./example/binning \
--rankEach bin is ranked based on the standard deviation of sample hits across groups assoicated with the chosen metadata category, with bins exhibiting higher variability ranked at the top.
The rankings are saved in the output stats_by_variance_of_sample_hits.tsv whereas binning statistics (start and end positions of each bin, number of sample hits per bin, number of read hits per bin.etc) are saved in stats_bins.tsv.
Per-genome coverage percentages are a column of {output}.coverage.parquet:
$ duckdb -c "SELECT genome_id, percent_covered FROM 'foo.coverage.parquet'"Multiple coverage files for the same sample are aggregated with
cov-to-parquet, which takes a glob (see step 3). micov compress takes
SAM/BAM only.