Overview
krepp is a k-mer-based tool for searching and finding query sequences in large reference genome collections, estimating the distance between reads and reference genomes, and placing them on backbone phylogenies and/or taxonomies. It uses locality-sensitive hashing (LSH), a phylogeny-guided k-mer coloring algorithm, and a maximum-likelihood framework to achieve these.
What we will cover
This tutorial walks through the core krepp workflow using a controlled mock microbial community consisting of 20 marine genomes simulated at 100,000 Illumina reads. We will work with two small reference sets to avoid heavy computation during the tutorial. We will index one toy reference set with controlled novelty and limited relatedness to queries and directly download another, diverse but heavily down-sampled microbial index.
The tutorial consists of five main sections:
- Installation of the tools and downloading the tutorial datasets.
- Indexing reference genomes and using one of the available pre-built indexes.
- Estimating read-to-genome distances and interpretation of distances.
- Placing reads on a reference phylogeny (or a taxonomy) and visualising the result with
gappa. - Exercises with a more realistic reference index and conclusions.
Resources
- Slides for the first part of this tutorial.
- The main paper (Genome Biology) with methodological details and extensive benchmarking.
- Another tutorial (in IMSI, 2025) for an earlier version which remains mostly valid.
- Slides (from RECOMB 2025) focusing on method and results.
Setup
Hardware requirements
Participants should have access to a machine with at least 8 GB of RAM and 8 GB of free disk space. You can either run the tutorial on your personal laptop, or use a remote server that you have access to. Some commands we will run may benefit from a multi-threaded setup, but this is only optional.
Software requirements
We will be using krepp version v0.8.2.
| Tool | Version | Purpose |
|---|---|---|
| conda / micromamba | any recent | environment management |
| krepp | 0.8.2 | distance estimation and placement |
| gappa | 0.9.0 | phylogenetic placement visualization and processing |
| wget / curl | any recent | downloading datasets |
We recommend using micromamba, refer to this link for installation.
Create an environment and install krepp
Set up an environment and install krepp (version 0.8.2) via Bioconda, then verify the installation:
micromamba create -n krepp-tutorial -c conda-forge -c bioconda krepp=0.8.2 -ymicromamba activate krepp-tutorialkrepp --help
Expected output:
krepp version: v0.8.2krepp: a tool for k-mer-based search, distance estimation & phylogenetic placement.Usage: krepp [OPTIONS] SUBCOMMANDOptions:--help--verbose,--no-verbose{false} Increased verbosity and progress report.--seed UINT Random seed for the LSH and other parts that require randomness. [0]--num-threads UINT Number of threads to use in OpenMP-based parallelism. [1]Subcommands:index Build an index from k-mers of reference genomes.place Place queries on a tree with respect to an index.dist Estimate distances of queries to genomes in an index.inspect Display statistics and information for a given index.sketch Create a sketch from k-mers in a single FASTA/FASTQ file.seek Seek query sequences in a sketch and estimate distances.
Install helper tools into the same environment, and verify:
micromamba install -c conda-forge -c bioconda gappa=0.9.0 wget -ygappa --help
Expected output:
.... .... '' '||. .||' || || '|.|' ...' .... ... ... ... ... .... .|'|. | || '' .|| ||' || ||' || '' .|| .|' || |'' .|' || || | || | .|' || .|'|. || '.... '|..'|'. ||...' ||...' '|..'|. '||' ||:. '....' || || '''' '''' v0.9.0 (c) 2017-2025 by Lucas Czech and Pierre BarberaUsage: gappa [OPTIONS] SUBCOMMANDOptions:--help FLAG Print this help message and exit.--version FLAG Print the gappa version and exit.Subcommands:analyze Commands for analyzing and comparing placement data, that is, finding differences and patterns.edit Commands for editing and manipulating files like jplace, fasta or newick.examine Commands for examining, visualizing, and tabulating information in placement data.prepare Commands for preparing and preprocessing of phylogenetic and placement data.simulate Commands for random generation of phylogenetic and placement data.tools Auxiliary commands of gappa.gappa - a toolkit for analyzing and visualizing phylogenetic (placement) data
Download and prepare the tutorial data
git clone https://github.com/bo1929/ismb2026-krepp-tutorial-materials.gitcd ismb2026-krepp-tutorial-materialsbash scripts/setup.sh
Note
setup.sh will download a small (~1.5 GB) microbial index.
Verify the inputs are ready
ls data/
Expected output:
input_map.tsvindex-WoLv1-tiny/profile.tsvprofile_species_accessions.tsvquery_genomes/query_info.tsvquery_taxonomy.tsvquery_mixture.fq.gzreference_genomes/reference_info.tsvreference_taxonomy.tsvreference_tree.nwk
| Path | Description |
|---|---|
index-WoLv1-tiny/ |
A lightweight Web of Life index for ~10,000 microbial genomes |
reference_genomes/ |
A toy reference set, consisting of 31 reference genome assemblies (FASTA, gzip-compressed) |
reference_info.tsv |
Metadata describing reference labels, taxonomic groups and lineages, and accessions |
reference_tree.nwk |
A simple baseline phylogeny for the toy reference set, built from Mash distances |
input_map.tsv |
Two-column TSV mapping reference labels to genome file paths (31 lines) |
query_genomes/ |
Genome assemblies for the 20 query organisms (FASTA, gzip-compressed) |
query_info.tsv |
Metadata (taxon and accession) for 20 query taxa selected with controlled novelty |
query_mixture.fq.gz |
100,000 Illumina reads simulated from the query genomes (FASTQ, gzip-compressed) |
profile.tsv |
Taxonomic profile of the mock community across taxa (77 lines) |
Dataset
Query mixture of reads
- A controlled (mock) metagenomic sample: 100,000 simulated Illumina reads from 20 query genomes.
- The mixture is based on an abundance profile mimicking a marine environment.
- Queries in this mixture differ in terms of abundances, taxonomies, and their novelty level.
We will be comparing our results to the known ground truth profile given in data/profile.tsv!
Abundances of queries and taxonomic profiles
- Spanning two kingdoms, Bacteria (83%) and Archaea (17%), and seven classes:
- Gammaproteobacteria (~66%)
- Thermoprotei (~11%)
- Alphaproteobacteria (~8%)
- Thermococci (~6%)
- Epsilonproteobacteria (~5%)
- Acidimicrobiia (~2%)
- Deinococci (~2%)
Reference genomes and novelty levels
To see how a certain taxon can be identified depending on its representation in a reference database, we control its novelty level by measuring the lowest shared taxon of the closest reference genome.
As it will be discussed in the next section in more detail, we will be using two different sets of reference genomes:
- a 31-genome toy reference set for controlled novelty,
- a small but diverse microbial index (Web of Life) spanning many taxa.
For the Web of Life, we do not control the novelty but we do for the toy reference set. Some queries match a reference at species rank (same taxon, different individual genomes). Others share only genus, family, or kingdom with their best matching reference, in terms of the ANI between them, akin to the phylogenetic distance measured in branch lengths.
If a read comes from a genome X of species A under genus B, and if the closest reference Y belongs to the same genus B but not the same species A, then the novelty level would be genus level.
By novelty-level (the lowest shared taxonomic rank with the closest reference):
- species: 11 queries (33% of reads)
- genus: 6 queries (53% of reads)
- family: 1 query (2% of reads)
- kingdom: 2 queries (13% of reads)

Figure 1. Dataset overview. Left: What portion of the query reads relates to the toy reference set by novelty, then kingdom and class level abundances.
Right: All 20 query species with relative abundances (coloured by class).
As novelty increases (species to kingdom), it becomes more difficult to identify a read and incorporate it in the analysis.
Reference genome set
- References cover two kingdoms (Bacteria and Archaea), five phyla (Proteobacteria, Actinobacteria, Deinococcus-Thermus, Crenarchaeota, and Euryarchaeota), and seven classes (Gammaproteobacteria, Alphaproteobacteria, Epsilonproteobacteria, Acidimicrobiia, Deinococci, Thermoprotei, and Thermococci).
- Below we show a phylogeny consisting of both reference genomes and query genomes.
- Novelty of each query can also be understood as the branch length to the closest reference in this phylogeny.
- We will use a similar phylogeny after excluding the queries for phylogenetic placement of the reads in the query mixture.

Figure 2. A toy phylogeny built using Mash distances and neighbor-joining, queries are colored in red. Although this may not be the most reliable method for obtaining a microbial phylogeny (as Mash distances do not extend well to highly divergent genomes, which we have in our case), it is good enough for our purposes. The same species may appear at tips both as query and reference, which correspond to queries that are least novel (another genome from the exact same species is present in the reference).
Goals
- Abundant and low-novelty query reads should be identified correctly with low distances.
- High novelty reads should be mapped correctly to corresponding references but with potentially high distances.
- Rare and low-abundance queries should be detected with correct abundances.
- Phylogenetic placement should associate queries with correct branches, reflecting the overall composition at a higher resolution than a taxonomy.
Summary table
| Query species | Kingdom | Class | Abundance | Novelty level |
|---|---|---|---|---|
| Nitrosococcus halophilus | Bacteria | Gammaproteobacteria | 17.5% | genus |
| Spiribacter curvatus | Bacteria | Gammaproteobacteria | 15.7% | genus |
| Hyperthermus butylicus | Archaea | Thermoprotei | 11.1% | kingdom |
| Candidatus Endolissoclinum faulkneri | Bacteria | Alphaproteobacteria | 6.0% | genus |
| Nitrosococcus oceani | Bacteria | Gammaproteobacteria | 5.7% | species |
| Palaeococcus pacificus | Archaea | Thermococci | 5.7% | genus |
| Sulfurovum sp. NBC37-1 | Bacteria | Epsilonproteobacteria | 5.4% | genus |
| Spiribacter salinus | Bacteria | Gammaproteobacteria | 5.1% | species |
| Vibrio anguillarum | Bacteria | Gammaproteobacteria | 4.6% | species |
| Alteromonas mediterranea | Bacteria | Gammaproteobacteria | 4.0% | species |
| Psychrobacter sp. JCM 18900 | Bacteria | Gammaproteobacteria | 2.3% | genus |
| Ilumatobacter coccineus | Bacteria | Acidimicrobiia | 2.3% | species |
| Aliivibrio salmonicida | Bacteria | Gammaproteobacteria | 2.2% | species |
| Erythrobacter litoralis | Bacteria | Alphaproteobacteria | 2.2% | species |
| Aliivibrio wodanis | Bacteria | Gammaproteobacteria | 2.2% | species |
| Marinomonas posidonica | Bacteria | Gammaproteobacteria | 1.7% | species |
| Thioflavicoccus mobilis | Bacteria | Gammaproteobacteria | 1.6% | family |
| Thalassolituus oleivorans | Bacteria | Gammaproteobacteria | 1.6% | species |
| Alteromonas macleodii | Bacteria | Gammaproteobacteria | 1.6% | species |
| Marinithermus hydrothermalis | Bacteria | Deinococci | 1.5% | kingdom |
Indexing reference genomes
Whether you have a collection of reference genomes or prefer a public database, krepp expects them to be indexed using locality-sensitive hashing. An index is simply a directory storing certain data structures in binary format, together with a human-readable metadata file.
The krepp command-line interface offers a subcommand called krepp index that builds an index from a collection of reference genomes.
Ready-to-use indexes
We also offer a catalogue of ready-to-use indexes constructed from different public databases, available for download. In either case, you build or download it once, then perform queries and reuse it for your analyses.
Goals in this part of the tutorial
- Build a small index from a selection of 31 microbial genomes detailed in the previous section
- Download an index built from the Web of Life (WoL) database (>10,000 genomes with a reference phylogeny)
Indexing genomes from scratch
Input files
Given a set of reference genomes, krepp builds an index via krepp index -o /path/to/index -i /path/to/input.tsv.
There are only two required options:
-o(--index-dir), which is the output directory path in which the index will be stored,-i(--input-file) a two-column TSV file mapping reference IDs to paths/URLs for FASTA/Q files (gzip compatible).
Each reference (whether a complete assembly or a genome skim) is a file identified by a unique ID.
The input map links these IDs to their file paths using a two-column, tab-separated format: REF-ID<TAB>/path/to/ref.
The paths can be either absolute or relative to the current working directory.
For the toy dataset, the input map can be seen below:
head -5 data/input_map.tsv
Pyrodictium_occultum data/reference_genomes/GCF_000007085.1.fna.gzThermus_thermophilus data/reference_genomes/GCF_000006785.2.fna.gzAllochromatium_vinosum data/reference_genomes/GCF_900119735.1.fna.gzCandidatus_Endolissoclinum_sp data/reference_genomes/GCA_965228445.1.fna.gzPsychrobacter_cryohalolentis data/reference_genomes/GCF_000013905.1.fna.gz
The IDs are particularly important for phylogenetic placement: the leaves of the phylogeny provided for placement (i.e., a Newick file) must match the IDs given in this file. For distance estimation, there is no such requirement and arbitrary IDs can be used.
Building the index
Run from inside data so paths in input_map.tsv resolve.
cd datakrepp index \ -i input_map.tsv \ -o toy-index \ --num-threads 4cd ..
Expected output:
krepp version: v0.8.2Invocation: krepp index -i input_map.tsv -o toy-index --num-threads 4Tue May 26 05:20:01 2026Reading the tree and initializing the index...No tree has given as a guide, the color index could be suboptimal.Building the index...Internal node: 60 size: 13408544 progress: 61/61Finished indexing, elapsed: 19.8957 secSkipped saving a backbone for the index!Done converting & saving, elapsed: 0.069542 secTue May 26 05:20:21 2026
You will, of course, have different dates and exact elapsed times.
Multi-threaded indexing
The above command took ~27 seconds with a single thread. Using multiple threads can give significant speed-ups for large reference sets, but this is optional. Note that memory use increases with more threads, so you may want to reduce the thread count if you run out of memory during indexing. It does not affect memory use at query time, once indexing is finished.
Inspecting and verifying an index
We can verify an index by running
krepp inspect -i data/toy-index | head -n 16
Expected output:
Backbone tree: NA======= Partial index: 0 =======krepp version: v0.8.2date: 2026-05-26 05:27:50seed: 0k: 29w: 35h: 13m: 4frac: trueppos_v: [28, 26, 25, 22, 16, 14, 12, 11, 7, 5, 4, 3, 1]npos_v: [0, 2, 6, 8, 9, 10, 13, 15, 17, 18, 19, 20, 21, 23, 24, 27]nrows: 33554432total_num_kmers: 13408544sdust-t: 0sdust-w: 0
This command also reports the parameters used during indexing for future reference. Alternatively, to display similar information about the index, we can check the metadata file.
cat data/toy-index/metadata-*.txt
(Optional) Indexing options and parameters
| Flag | Default | Meaning |
|---|---|---|
-k (--kmer-len) |
29 | Length of k-mers [29] |
-w (--win-len) |
35 | Length of minimizer window, -w must be greater than or equal to -k [k+6] |
-h (--num-positions) |
13 | Number of positions for the LSH [k-16] |
Note
Defaults are calibrated with short reads and microbial genomes in mind, and should perform well for most cases.
If you only want to test different parameters and options without overwriting the existing index, use a different -o directory.
Advanced: batching for ultra-large datasets and FracMinHash
Peak memory usage during index construction can also be controlled by partitioning the index into smaller pieces.
This is done by a FracMinHash-based batching, controlled by options -m and -r.
krepp partitions the index into -m (more or less) equally sized pieces; these partitions can be built independently but queried together.
The -r option determines which partition is constructed: if --no-frac is given, only the -rth partition is built; otherwise all partitions from 0th to -rth are built and saved (see krepp index --help for details).
You don't need to construct all partitions; krepp will search in whatever is available, and these partitions can be distributed independently.
The default is -m 4 -r 1 --frac, so 50% (0th and 1st out of 4 partitions) of the minimized k-mers will be indexed.
The only requirement is keeping the -m value (and of course -i and -t) fixed across all partitions.
For instance, one can index 3% of the reference k-mers and construct a lightweight index by running:
krepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 2 --no-frackrepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 1 --no-frackrepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 0 --no-frac
or, alternatively
krepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 2 --frac
This is faster but uses more memory during index construction, despite resulting in an index of the same size. If this doesn't work well for your task, you can keep adding batches partially and make index larger:
krepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 3 --no-frackrepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r 4 --no-frac
or, if you have enough memory, you can do this in parallel:
seq 3 99 | xargs -I{} -P4 \ krepp index -o /path/to/index -i /path/to/input.tsv -t /path/to/tree.nwk -m 100 -r {} --no-frac
Advanced: using a guide tree during indexing for better compression
If you have a phylogeny available (in Newick format), run krepp index with the -t option and the path to the Newick tree (inside data/):
krepp index \ -i input_map.tsv \ -o toy-index \ -t reference_tree.nwk \ --num-threads 4
An index constructed with a backbone can be used for phylogenetic placement without providing a tree later, and distance estimates are unaffected.
You can always specify a backbone with the krepp place subcommand, using the -t (--nwk-file) option.
Note that the tip labels of the phylogeny must match the reference IDs given in -i /path/to/input.tsv for krepp index.
Installing pre-built indexes
We will use the Web of Life index (tiny version <2 GB) which includes a microbial phylogeny (>10,000 leaves).
You do not need to run the command below if you already followed the steps in the Setup section. Note that this index is intentionally made extremely lightweight for this tutorial and may underperform for complex samples compared to larger indexes.
wget --no-check-certificate https://ter-trees.ucsd.edu/data/krepp/index-WoLv1-tiny.tarmkdir -p datatar -xf index-WoLv1-tiny.tar -C data/
The downloaded directory contains the index. This particular index is small enough to fit in your laptop's memory.
Run cat data/index-WoLv1-tiny/metadata-*.txt to see the configuration and verify the download.
Expected output:
krepp version: v0.8.2date: 2026-05-29 04:53:32seed: 0k: 27w: 33h: 12m: 36frac: falseppos_v: [26, 25, 24, 22, 21, 17, 14, 8, 7, 5, 3, 2]npos_v: [0, 1, 4, 6, 9, 10, 11, 12, 13, 15, 16, 18, 19, 20, 23]nrows: 466034total_num_kmers: 184716530sdust-t: 0sdust-w: 0
See this page for the full list of available databases.
How to choose an index?
The available indexes are mostly microbial and the majority come with a reference phylogeny (for placement). Some major instances are listed below; they vary in size to fit the memory available to you:
-
Web of Life (WoL): lightweight and a reliable phylogeny (67 GB, 41 GB, 2 GB ← we downloaded this one)
-
RefSeq-microbial: denser sampling and an ultra-large uDance phylogeny (180 GB)
-
GTDB: the latest release, an up-to-date reference (230 GB, 100 GB)
For any reference database, the rule of thumb is to use the largest index that fits your available memory. The indexes vary in size but are based on the same genome set. The level of compression and the extent of k-mer downsampling differ. Depending on the query data and the task, a small index might perform as accurately as a large one. The downloads can be either through our servers or an AWS S3 bucket.
Distance Estimation: krepp dist
krepp distcomputes the Hamming distance between individual sequences (e.g., short reads in our case) in a given FASTA/Q file and sufficiently similar (i.e., <25% similarity) reference genomes in the index.- These approximate distances are akin to alignment identity: If you were to align this read (e.g., using Bowtie2), the proportion of mismatches is expected to match krepp's distance estimates.
- Individual estimates for reads may have some error, especially at high distances (>15%), but they are accurate on average, and can recover genome-wide similarities.
- These distances are the main information krepp relies on for various tasks such as phylogenetic placement, taxonomic classification, and for providing sample-wise (i.e., for the entire file) summaries.
Per-read distances (default)
By default, krepp reports distances between individual sequences and all relevant references:
krepp dist -i data/toy-index -q data/query_mixture.fq.gz --num-threads 4 > results/distances_default.tsv
The output is in a tab-separated format, consisting of three columns:
SEQ_ID: sequence/read identifier in the inputREFERENCE_NAME: name of the matching reference in the indexDIST: estimated maximum likelihood distance
head results/distances_default.tsv
Expected output:
# software: krepp version: v0.8.2 invocation :krepp dist -i data/toy-index -q data/query_mixture.fq.gz --num-threads 4 -o results/distances_default.tsvSEQ_ID REFERENCE_NAME DISTNC_007484.1-37612 Nitrosococcus_oceani 0.00009NC_007484.1-37612 Nitrosococcus_wardiae 0.20504NC_007484.1-37612 Nitrosococcus_watsonii 0.07074NC_013960.1-34342 Nitrosococcus_oceani 0.18942NC_013960.1-34342 Nitrosococcus_wardiae 0.11605NC_008818.1-42619 NA NaNNC_021291.1-872 Spiribacter_roseus 0.20522NC_009663.1-23622 NA NaN
Some observations:
- The exact order of the rows may change across reads, but the mappings should be identical.
- Notice that a sequence identifier may appear across multiple consecutive rows; this means that the corresponding read is mapped to multiple references, each with its own distance.
- The number of unique IDs in the first column is equal to the number of reads in the given query file.
- Some rows could have NA, indicating that there is no reference that could be matched for this particular read.
Other options for distance estimation
Filtering reference hits
By default, krepp reports all reference hits, regardless of their distances.
If you are only interested in references that are sufficiently close compared to the best match, use --filter flag:
krepp dist -i data/toy-index -q data/query_mixture.fq.gz --num-threads 4 \ --filter -o results/distances_filtered.tsv
For each read, this only retains mappings that are not too far away from the reference hit with the minimum distance.
An example showing the impact:
- The total number of mappings goes down from 80,983 to 59,074 (comparing
grep -v NaN results/distances_*.tsv | wc -l). NC_022664.1-89288has 11 hits without--filter, the closest one at 0.06523.NC_022664.1-89288 Nitrosococcus_oceani 0.09644NC_022664.1-89288 Nitrosococcus_wardiae 0.07829NC_022664.1-89288 Alteromonas_stellipolaris 0.08502NC_022664.1-89288 Spiribacter_salinus 0.07165NC_022664.1-89288 Allochromatium_vinosum 0.06582NC_022664.1-89288 Marinomonas_posidonica 0.11331NC_022664.1-89288 Spiribacter_roseus 0.06523NC_022664.1-89288 Alteromonas_mediterranea 0.09313NC_022664.1-89288 Nitrosococcus_watsonii 0.07694NC_022664.1-89288 Marinomonas_mediterranea 0.12875NC_022664.1-89288 Alteromonas_macleodii 0.09642- Using
--filterreduces it to 9 mappings, keeping the ones that are statistically indistinguishable.NC_022664.1-89288 Nitrosococcus_watsonii 0.07693NC_022664.1-89288 Alteromonas_stellipolaris 0.08504NC_022664.1-89288 Nitrosococcus_wardiae 0.07828NC_022664.1-89288 Alteromonas_macleodii 0.09642NC_022664.1-89288 Spiribacter_roseus 0.06520NC_022664.1-89288 Spiribacter_salinus 0.07164NC_022664.1-89288 Alteromonas_mediterranea 0.09310NC_022664.1-89288 Nitrosococcus_oceani 0.09647NC_022664.1-89288 Allochromatium_vinosum 0.06581
- Another option is to keep only the one with the best match (lowest distance) using
--no-multi(default:--multi). - Alternatively, one can directly cap at a maximum distance threshold (e.g., 5%) using
--dist-max 0.05.
Run krepp dist --help to list all options and see their descriptions.
Interpretation of results

Figure 3. Distribution of per-read krepp distances, grouped by the novelty level of the source query genome. Species-level queries (the index contains another genome of the exact same species) have distances centred near zero (mean 0.034) with a high mapping rate (81%). Queries with genus-level novelty are spread much more widely, with mean and median both well above 0.1, as expected. Family and kingdom-level queries have much lower mapping rates, reflecting the absence of any close relative in the reference set, and are biased towards reads from more conserved regions. Mapped reads with distances saturating near 0.20 also demonstrate the limits of sensitivity (at least in this setting with only a few references and a toy index).

Figure 4. Genome-wide distance (measured by Mash) vs. average per-read distance measured by krepp for each query organism's closest reference genome. Left: top-5 species-level queries by mapping rate. Pairs cluster tightly near the diagonal at distances below 2%, showing close agreement between krepp per-read estimates and whole-genome Mash distances. Right: top-5 genus-level queries, for which krepp means track Mash distances across a wider range (0.10–0.27). Nitrosococcus halophilus hits N. wardiae at exactly 0.0953 on both axes. However, at very high genome-wide distances, krepp starts to underestimate due to the large portion of reads that are not mapped, and also due to mapping bias towards reads that come from more conserved regions. Bottom: mapping rate (fraction of reads with at least 1 hit) for the same 10 organisms, ranging from 99.9% (Alteromonas macleodii) down to 12.6% (Palaeococcus pacificus).
Please note that this is only a toy example where we utilized a very small number of arbitrarily chosen reference genomes. We also downsampled considerably using minimizers and FracMinHash to obtain a small index for the sake of the tutorial. Actual mapping rates and distance accuracy may change in a more realistic setting with more resources and time, most likely in a positive direction!
Counting operational genomic units
- Instead of retaining the per-read distance estimation, one could summarize the entire query file (i.e., a sample) in operational genomic units (OGUs).
- Although the interpretation is different from taxonomic abundance profiles, an OGU table provides a useful high-level view on the entire composition.
- Each read is counted as 1, and its contribution to each genomic unit (i.e., a single reference) is disproportional to the number of equally good matches.
- With
--summarize, krepp internally sets--filter. Then, for each read, it counts the number of reference hits (call itn), each getting an equal share (1/n). The reported result is an OGU table.
krepp dist -i data/toy-index -q data/query_mixture.fq.gz --summarize --num-threads 4 \ > results/distances_summary.tsv && head results/distances_summary.tsv
Expected output:
REFERENCE_NAMEis the genomic unit.WEIGHTED_COUNTdenotes the total count of read contributions across the sample.SEQUENCE_ABUNDANCEis the normalized proportion of counts, summing up to 1.# software: krepp version: v0.8.2 invocation :krepp dist -i data/toy-index -q data/query_mixture.fq.gz --summarize --num-threads 4REFERENCE_NAME WEIGHTED_COUNT SEQUENCE_ABUNDANCEAlteromonas_mediterranea 3129.79686 0.06055Nitrosococcus_oceani 6606.76386 0.12782Spiribacter_roseus 6890.63616 0.13331Moorella_thermoacetica 20.82979 0.00040Thermus_thermophilus 17.32136 0.00034Spiribacter_salinus 5273.02897 0.10202Pyrodictium_occultum 21.49657 0.00042Marinomonas_mediterranea 169.35874 0.00328
OGU abundances may not translate into taxonomic abundances
When a read comes from genome A and the index holds two closely related genomes B and C (e.g., three strains of the same species), the counts may be deflated. This may occur for densely sampled species (e.g., E. coli). In a different scenario, some references may end up having inflated counts. However, this can provide a valuable signal in downstream analysis (i.e., machine learning, prediction, or sample comparison via UniFrac).
Phylogenetic Placement: krepp place
krepp placeuses distance estimates to find placements on a given phylogeny that best explain the distances for each query.- For each query sequence (e.g., a read), after finding reference hits and estimating ML distances, krepp looks for large clades in the phylogeny that are indistinguishable from the minimum-distance leaf (i.e., closest reference genome).
- In this mode, a reference phylogeny must be provided via the
-toption; its tip labels must match the reference IDs in the input file (-i) used during indexing. - If a guide tree was provided during indexing, krepp uses that tree for placement unless it is overridden via
-t.
Incorporating the relationships between reference genomes into the analysis, phylogenetic placement provides a more interpretable and complete view of the queries.
Placing reads on a phylogeny
By default, krepp reports multiple placements for each query, weighted by likelihood ratios, and filters out placements with only a few k-mer matches (i.e., low evidence).
For a given backbone phylogeny in Newick format (data/reference_tree.nwk), run:
krepp place -i data/toy-index -q data/query_mixture.fq.gz \ -t data/reference_tree.nwk --num-threads 4 -o results/placements_default.jplace
which reports placement in a .jplace file, which is a JSON-based format used to encode phylogenetic placements as a set of records containing branch, distance, and uncertainty information per read, together with the reference tree.
head results/placements_default.jplace
Expected output:
- Each sequence has its identifier, and a set of records pinpointing a placement.
- The
edge_numindicates the branch each sequence is placed on, with weightlike_weight_ratioreflecting the confidence. - The
.jplacefile stores a decorated tree, labeling each edge of the input tree given via-twith{edge_num}.{"version" : 3,"fields" : ["edge_num", "pendant_length", "distal_length", "likelihood", "like_weight_ratio", "distance"],"placements" : [ {"n" : ["NC_015559.1-12009"], "p" : [[22, 0.02798, 0.06314, -17.19396, 1.00000, 0.08581]]}, {"n" : ["NC_015559.1-3949"], "p" : [[22, 0.09093, 0.06314, -17.99522, 1.00000, 0.13928]]}, {"n" : ["NC_022664.1-43244"], "p" : [ [14, 0.13809, 0.03122, -16.87034, 0.44321, 0.15156], [6, 0.12885, 0.01378, -27.72109, 0.55679, 0.12989]] }, - For more details, refer to the paper describing the format: "Matsen FA, Hoffman NG, Gallagher A, Stamatakis A (2012) A Format for Phylogenetic Placements. PLOS ONE 7(2): e31009. https://doi.org/10.1371/journal.pone.0031009".
Visualization and interpretation of placements
You may rightfully hesitate to get your hands dirty and write your own scripts for analyzing .jplace files. Luckily, gappa (a tool we downloaded during the setup step) offers solutions for a good collection of tasks and post-processing needs, one of which is visualizing placements as a heat-tree (similar to a heat-map) from the .jplace outputs, coloring branches by the total placement mass they accumulate:
gappa examine heat-tree \ --jplace-path results/placements_default.jplace --mass-norm relative \ --write-svg-tree --out-dir results/ --file-prefix heat_ --allow-file-overwriting \ --svg-tree-shape rectangular --svg-tree-ladderize --svg-tree-stroke-width 3

Figure 5. Heat-tree generated by the above command, showing per-branch placement mass (proportion of placements). Notice that a high mass has been placed within the Nitrosococcus clade, reflecting the high abundance queries Nitrosococcus halophilus (17.5%) and Nitrosococcus oceani (5.7%). Similar observations can be made for Spiribacter roseus and Spiribacter salinus. Overall, phylogenetic placements are in agreement with the input query abundances.
Some observations:
- Queries with a reference from the same species place predominantly on the terminal branch of the closest reference, with LWR near 1.0 and short placement distances (~0.002–0.02). The read is close enough to the reference genome that no other branch is competitive.
- Queries with genus-level novelty spread their mass across multiple terminal branches within the genus clade and onto the internal branch connecting them. For example, Nitrosococcus halophilus reads contribute mass across N. wardiae, N. oceani, N. watsonii, and the internal edge joining them. krepp correctly localises the read within the genus but cannot resolve it to a single species as it is not present.
- More novel queries tend to place either on deep internal branches or incorrectly on distant terminal branches, usually with low LWR spread across many edges. The placement distances are high (>0.15), and no single branch dominates. This correctly signals that the query diverges above the genus level and that the reference set lacks a close relative.
Other placement options and formats
- For a flat and more readable view, krepp can also report placements in a table format, using
--tabular:krepp place -i data/toy-index -q data/query_mixture.fq.gz \ -t data/reference_tree.nwk --tabular --num-threads 4 > results/placements_tabular.tsvhead results/placements_tabular.tsv
Expected output:
SEQ_ID: query sequence identifierDISTAL_NODE: label of the internal node under the placement branchEDGE_NUM: edge number in the decorated tree given in the header of the reportLWR: likelihood weight ratioDIST: placement distance# software: krepp version: v0.8.2 invocation :krepp place -i data/toy-index -q data/query_mixture.fq.gz -t data/reference_tree.nwk --tabular --num-threads 4# (((Thalassolituus_oleivorans:0.04096{0},Sulfurovum_mangrovi:0.36463{1}):0.10560{2},(((Ilumatobacter_sp_AH-269-C13:0.12736{3},Ilumatobacter_coccineus:0.18559{4}):0.18819{5},(Spiribacter_roseus:0.02756{6},(Spiribacter_salinus:0.25239{7},(Candidatus_Endolissoclinum_sp:0.14462{8},(Erythrobacter_aurantius:0.14409{9},Erythrobacter_litoralis:0.07738{10}):0.06494{11}):0.10243{12}):0.03647{13}):0.06245{14}):0.15524{15},(Bacteroides_fragilis:0.50000{16},Palaeococcus_ferrophilus:0.50000{17}):0.06083{18}):0.10569{19}):0.03098{20},((Marinomonas_mediterranea:0.11388{21},(Marinomonas_posidonica:0.12629{22},((Staphylococcus_aureus:0.10455{23},(Moorella_thermoacetica:0.25831{24},Fusobacterium_nucleatum:0.14728{25}):0.07209{26}):0.12974{27},(Bacillus_subtilis:0.14246{28},(Thermus_thermophilus:0.15808{29},(Pyrodictium_occultum:0.19841{30},Mycobacterium_tuberculosis:0.20717{31}):0.04471{32}):0.02593{33}):0.12215{34}):0.07715{35}):0.04304{36}):0.09730{37},((Alteromonas_stellipolaris:0.15275{38},((Nitrosococcus_wardiae:0.08166{39},(Nitrosococcus_watsonii:0.09813{40},Nitrosococcus_oceani:-0.00904{41}):0.11275{42}):0.10015{43},(Vibrio_anguillarum:0.08961{44},(Aliivibrio_salmonicida:0.07676{45},Aliivibrio_wodanis:0.07034{46}):0.09247{47}):0.04989{48}):0.01565{49}):0.03886{50},(Alteromonas_mediterranea:0.06015{51},(Alteromonas_macleodii:0.05276{52},(Psychrobacter_cryohalolentis:0.14239{53},Allochromatium_vinosum:0.23019{54}):0.15003{55}):0.01706{56}):0.02411{57}):0.05419{58}):0.02465{59}){60};SEQ_ID DISTAL_NODE EDGE_NUM LWR DISTNC_015559.1-12009 Marinomonas_posidonica 22 1.00000 0.08581NC_015559.1-3949 Marinomonas_posidonica 22 1.00000 0.13928NC_022664.1-43244 Spiribacter_roseus 6 0.55679 0.12989NC_022664.1-43244 NA 14 0.44321 0.15156NC_011312.1-6343 Aliivibrio_salmonicida 45 1.00000 0.00009NC_013960.1-152360 Nitrosococcus_wardiae 39 1.00000 0.11037NZ_CP186025.1-22373 Vibrio_anguillarum 44 1.00000 0.00875- Each read may appear on multiple rows (one per placed branch), with an LWR summing to 1.0 across rows.
- If the distal node is not labeled,
DISTAL_NODE = NAis output even for placed reads, with a non-NAEDGE_NUM. - An LWR of 1.0 on a terminal node means the read maps to that reference without any ambiguity.
- To aggregate placements across all reads into per-branch abundances, use the
--summarizeoption:krepp place -i data/toy-index -q data/query_mixture.fq.gz \ -t data/reference_tree.nwk --summarize --num-threads 4 > results/placements_summarized.tsv
Placing on a taxonomy
- In addition to a phylogenetic tree, krepp can also place sequences on a taxonomy (which is a multi-furcating tree without branch lengths) using the same branch length agnostic placement algorithm.
- For taxonomic placement, krepp requires a tab-separated lineage file (
-l) mapping each reference name to a semicolon-separated GTDB-style path (e.g.,XYZ k__Bacteria;p__Proteobacteria;...;s__Escherichia coli, see an example here). - The
-t(tree) and-l(lineage) flags are mutually exclusive: use-lwith the path to the lineage file for a taxonomy-based placement and-twith the path to the Newick file for phylogenetic placement.
Example lineages:
A tab-separated file linking reference IDs (same as the ones in input map given via -i) to their taxonomic lineages:
XYZ k__Bacteria; p__Chloroflexota; c__; o__; f__; g__Roseiflexus; s__ABC k__Bacteria; p__Chloroflexota; c__; o__; f__; g__Chloroflexus; s__Chloroflexus aurantiacus123 k__Bacteria; p__Chloroflexota; c__; o__; f__; g__Thermomicrobium; s__Thermomicrobium roseumA00 k__Bacteria; p__Chloroflexota; c__; o__; f__; g__Dehalococcoides; s__Dehalococcoides mccartyiA11 k__Bacteria; p__Chloroflexota; c__; o__; f__; g__Dehalococcoides; s__Dehalococcoides mccartyi
Note that multiple genomes may have the same lineage (i.e., they are same species) and missing labels and empty ranks are also compatible.
krepp place -i data/toy-index -q data/query_mixture.fq.gz -l data/reference_taxonomy_lineages.tsv \ --tabular -o results/taxplacements_tabular.tsvhead results/taxplacements_tabular.tsv | grep -v "#"
Expected output:
SEQ_ID DISTAL_NODE EDGE_NUM LWR DISTNC_015559.1-12009 Marinomonas_posidonica 37 1.00000 0.08579NC_015559.1-3949 Marinomonas_posidonica 37 1.00000 0.13930NC_022664.1-43244 Spiribacter 57 0.47172 0.14497NC_022664.1-43244 Spiribacter_roseus 53 0.52828 0.12986NC_011312.1-6343 Aliivibrio_salmonicida 13 1.00000 0.00001NC_013960.1-152360 Nitrosococcus_wardiae 47 1.00000 0.11041NZ_CP186025.1-22373 Vibrio_anguillarum 18 1.00000 0.00872
Similar to phylogenetic placement, you can use the --summarize option to aggregate placement across all reads and report weighted count values and normalized sequence abundances.
Please note that sequence abundances are not equivalent to taxonomic profiles as the normalization is done across all ranks not per rank (i.e., species level profile).
krepp place -i data/toy-index -q data/query_mixture.fq.gz \ -l data/reference_taxonomy_lineages.tsv -o results/taxplacements_summarized.tsvhead results/taxplacements_summarized.tsvgrep "^Nitrosococcus" results/taxplacements_summarized.tsv
Expected output:
Nitrosococcus_watsonii 49 301.26923 0.00784Nitrosococcus_oceani 45 5874.38590 0.15279Nitrosococcus_wardiae 47 8900.17328 0.23149Nitrosococcus 51 531.75661 0.01383
Columns: DISTAL_NODE, EDGE_NUM, WEIGHTED_COUNT, and SEQUENCE_ABUNDANCE.
Conclusions
We covered the core krepp workflow: indexing, distance estimation, phylogenetic placement, and taxonomic placement using a small toy reference set.
Distance estimation (krepp dist) computes per-read Hamming distances to reference genomes, akin to the ratio of mismatches one would obtain through alignment, but sensitive to more divergent sequences.
The default command produces a tab-separated table of read-to-reference mappings and maximum likelihood distances.
Filtering (--filter) retains only statistically close hits; the --summarize flag aggregates results into operational genomic unit counts, which can be used for downstream analysis and sample comparison.
Phylogenetic placement (krepp place -t) finds placements of query reads on a given reference phylogeny, producing a .jplace file by default, a format that can be further processed by tools such as gappa, iTOL, and QIIME 2.
Taxonomic placement (krepp place -l) uses a lineage file to assign reads to named clades.
Both support tabular (--tabular) and summarised (--summarize) outputs, similar to distance estimation.
Exercises with Web of Life
For further exercises, you can use the same type of commands with a slightly larger index we downloaded earlier. This is the tiny (i.e., heavily minimized and downsampled) version of the Web of Life index (>10,000 microbial genomes, see this page for the full versions) on the same query mixture:

Figure 6. Per-query mapping rate and mean distance to the best-matching reference, using the WoL index. With denser sampling and a more diverse representation, mapping rates are uniformly high and best-reference distances are consistently low, meaning that all these queries have relatively close matches in this index compared to the toy index we built.

Figure 7. Placement heat-tree on the WoL reference phylogeny (>10,000 leaves). Since the toy sample we generated has low complexity, only a few clades/branches are highlighted. Real and complex samples could result in more widely spread placements.