A Snakemake pipeline for building phylogenetic trees of influenza virus sequences using UShER. The pipeline processes influenza sequences by segment and subtype, creating separate trees for HA and NA segments by subtype (e.g., H1, H3, N1, N2) while combining all subtypes for other segments (PB2, PB1, PA, NP, MP, NS).
flu-usher/
├── Snakefile # Main pipeline file
├── config.yaml # Configuration file
├── data/ # Input data directories
│ ├── H1N1/ # Example: H1N1 sequences (all segments)
│ ├── H3N2/ # Example: H3N2 sequences (all segments)
│ ├── H5N1/ # Example: H5N1 sequences (all segments)
│ └── H7N9/ # Example: H7N9 sequences (all segments)
│ ├── sequences.fasta # One or more FASTA files containing sequences
│ └── metadata.xls # One or more metadata files from GISAID
├── logs/ # Log files (created by the pipeline)
├── results/ # Output results (organized by segment/subtype)
│ ├── HA/ # HA segment results by subtype
│ │ ├── H1/ # H1 subtype tree and files
│ │ ├── H3/ # H3 subtype tree and files
│ │ ├── H5/ # H5 subtype tree and files
│ │ ├── H7/ # H7 subtype tree and files
│ │ └── H9/ # H9 subtype tree and files
│ ├── NA/ # NA segment results by subtype
│ │ ├── N1/ # N1 subtype tree and files
│ │ ├── N2/ # N2 subtype tree and files
│ │ └── N9/ # N9 subtype tree and files
│ └── PB2/ # Other segment results (PB1, PA, NP, MP, NS)
│ └── all/ # All subtypes combined
├── scripts/
│ ├── parse_gisaid_data.py # Parse GISAID data by segment
│ ├── download_ref_seq.py # Download reference sequences
│ ├── curate_and_extract_coding_seqs.py # Curate alignments and extract coding sequences
│ ├── randomize_alignment.py # Randomize alignment order
│ ├── trim_dag.py # Trim suboptimal trees from DAG
│ ├── convert_DAG_protobuf_to_newick_samples.py # Sample tree from DAG
│ ├── create_root_samples_file.py # Create samples file for root extraction
│ ├── extract_root_sequence.py # Infer root sequence from tree
│ ├── simplified_host_classifier.py # Classify hosts into groups (used by augment_metadata.py)
│ ├── augment_metadata.py # Add host, geographic, and temporal group columns to metadata
│ ├── create_samples_file.py # Create samples file for subtree extraction (generic column filter)
│ ├── prepare_host_annotation.py # Build 2-col CSV (isolate_id, host_group) for PastML
│ └── prepare_subtype_annotation.py # Build 2-col CSV (isolate_id, subtype) for PastML, normalizing "A / H5N1" → "H5N1"
└── notebooks/ # Jupyter notebooks for development and analysis
-
Set up your environment
Create and activate the conda environment:
conda env create -f environment.yml conda activate flu-usherClone larch, then build and install it from source into the environment, where
$CONDA_PREFIXis the path to the active conda environment (e.g.,/home/hhaddox/miniforge3/envs/flu-usher/):git clone https://github.com/matsengrp/larch.git cd larch git submodule update --init --recursive mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX .. make -j16 make installOther dependencies are managed per-step via conda environments in
envs/, which Snakemake invokes automatically. -
Configure the pipeline
Edit
config.yamlto:- Specify input directories containing your GISAID data
- List HA subtypes to analyze (e.g., H1, H3, H5, H7, H9)
- List NA subtypes to analyze (e.g., N1, N2, N9)
- Set reference accession numbers for each segment-subtype combination
- Adjust filtering thresholds for sequence curation (max_frac_gaps, max_frac_ambig)
- Set number of randomizations for tree building (n_randomizations, default: 10)
- Set desired number of threads
- Specify geographic groups to extract (geographic_groups_to_extract)
- Optionally specify rerooting nodes for final trees (reroot)
-
Prepare your GISAID data
The pipeline expects two types of files with input data downloaded from GISAID:
- One or more FASTA files containing nucleotide sequences
- FASTA sequence ID format:
EPI|SEGMENT|NAME|EPI_ISL|SUBTYPE
- FASTA sequence ID format:
- One or more XLS files with metadata for the above sequences
The pipeline will automatically:
- Aggregate all FASTA and metadata files from each input directory
- Parse sequences by segment
- Group HA and NA segments by subtype (defined by the
subtypecolumn in the metadata file) - Combine all subtypes for internal segments (PB2, PB1, PA, NP, MP, NS)
- One or more FASTA files containing nucleotide sequences
-
Run the pipeline
snakemake --cores <number_of_cores> --use-conda -
Output
The pipeline organizes outputs by segment and subtype:
For HA and NA segments (e.g.,
results/HA/H5/orresults/NA/N1/):raw_sequences.fasta.xz: Parsed sequences for this segment-subtypereference/: Reference sequence and Nextclade dataset filesmsa.fasta.xz: Multiple sequence alignment from Nextclademsa.tsv.xz: Nextclade alignment metadatacurated_msa.fasta.xz: Quality-filtered alignment (e.g., gaps < 3%, ambiguities < 0%)unaligned_coding_seqs/: Directory containing per-gene unaligned coding sequences extracted from curated sequencescurated_reference.fasta/txt/gff/gtf: Reference files matching the curated alignmentcurated_root.fasta: Root sequence (reference or inferred from rerooting)randomized_{n}/: Directory for each randomized alignment (n = 0, 1, 2, ...)msa.fasta.xz: Randomized alignmentmsa.vcf: Variant call format filepreopt_tree.pb.gz: Initial parsimony treeopt_tree.pb.gz: Optimized treedag.pb: DAG representation of the tree
larch_merged_dag.pb: Merged DAG from all randomizationstrimmed_dag.pb: Trimmed DAG with suboptimal trees removedsampled_tree.nh: Newick format tree sampled from trimmed DAGsampled_tree.pb.gz: MAT protobuf of sampled treefinal_tree.pb.gz: Final tree (rerooted if specified in config)final_tree.jsonl.gz: Interactive Taxonium visualization filegeographic_trees/: Geographic subtree visualizations{geo_group}_samples.txt: Sample list for each geographic group{geo_group}_tree.pb.gz: Extracted subtree for each geographic group{geo_group}_tree.jsonl.gz: Taxonium visualization for each geographic group
host_ancestral/: Per-node host inference (PastML / DOWNPASS)combined_ancestral_states.tab: Tab-separated file of inferredhost_groupfor every node (leaves + internals); thenodecolumn joins to internal node IDs infinal_tree.pb.gz. Ambiguous internals may appear on multiple rows (one per equally-parsimonious state).host_tree.html: Interactive PastML visualization of the ancestral reconstruction.named.tree_final_tree.nwk: PastML's labeled-tree output (identical names tofinal_tree.nwkhere).
subtype_ancestral/: Per-node subtype inference (PastML / DOWNPASS)combined_ancestral_states.tab: Tab-separated file of inferredsubtype(H*N*form, normalized from the raw GISAIDsubtypecolumn) for every node (leaves + internals); same join + ambiguity semantics ashost_ancestral/. On HA per-subtype trees the H letter is fixed across all tips and only the inferred N partner varies (analogously for NA trees); on internal-segment trees neither letter is constrained, so the fullH*N*can vary along the tree.subtype_tree.html: Interactive PastML visualization of the ancestral reconstruction.
For the other segments (e.g.,
results/PB2/all/orresults/NP/all/):- Same outputs as above, but combining all influenza subtypes
Global outputs:
results/combined_metadata.csv: Aggregated metadata from all input filesresults/combined_metadata_augmented.csv: Metadata with host_group, geographic_group, and temporal_group columns addedresults/host_annotation.csv: 2-column (isolate_id, host_group) annotation table for PastMLresults/subtype_annotation.csv: 2-column (isolate_id, subtype) annotation table for PastML (subtype normalized toH*N*from the rawsubtypecolumn)results/input_data_md5sums.txt: Provenance manifest with md5 hashes for every input FASTA / XLS file under the configuredinput_dirs, one file per line in standardmd5sumformat (<hash> <path>)results/notebooks/: Executed analysis notebooksanalyze_metadata.html: Metadata analysis reportanalyze_alignments.html: Alignment statistics report
-
Parse GISAID data (
parse_gisaid_data.py):- Aggregates sequences from multiple input directories
- Splits by segment and subtype
- Creates unified metadata file
-
Download reference (
download_ref_seq.py):- Fetches appropriate reference sequence for each segment-subtype
- Creates Nextclade dataset configuration
-
Align sequences (Nextclade):
- Performs codon-aware alignment to reference
- Includes reference in the output alignment
- Generates alignment metadata TSV
-
Curate alignment and extract coding sequences (
curate_and_extract_coding_seqs.py):- Filters sequences by quality (gaps, ambiguities, terminal gaps)
- Extracts only coding regions based on GFF annotations
- Sanitizes sequence IDs for UShER compatibility
- Filters duplicate sequences
- Validates CDS for all genes (sequences must pass ALL validations)
- Extracts per-gene unaligned coding sequences from curated alignment
- Preserves original nucleotides removed during alignment
-
Randomize alignment (
randomize_alignment.py):- Creates multiple randomized versions of the alignment (keeping reference at top)
- Each randomization uses a different seed for variation in tree building
-
Create VCF (faToVcf):
- Converts each randomized FASTA alignment to variant format
- Includes reference and handles ambiguous nucleotides
-
Build initial tree (usher-sampled):
- Creates parsimony-based phylogenetic tree for each randomization
- Uses empty starting tree and builds incrementally
-
Optimize tree (matOptimize):
- Refines tree topology to minimize parsimony score for each randomization
- Multiple optimization rounds to improve tree quality
-
Convert to DAG (larch-usher):
- Converts each optimized tree to a DAG (Directed Acyclic Graph) representation
- Allows for representing multiple equally parsimonious tree topologies
-
Merge DAGs (larch-dagutil):
- Combines DAGs from all randomizations into a single merged DAG
- Trims redundant structures during merge
-
Trim DAG (
trim_dag.py):- Removes suboptimal trees from the merged DAG
- Retains only the most parsimonious tree topologies
-
Sample tree from DAG (
convert_DAG_protobuf_to_newick_samples.py):- Samples a representative tree from the trimmed DAG
- Outputs in Newick format
-
Create MAT protobuf (matOptimize):
- Converts the sampled Newick tree to MAT protobuf format
- Required for downstream matUtils operations
-
Reroot tree (matUtils extract):
- Reroots the tree at a specified node if configured
- Otherwise creates a symlink to the sampled tree
-
Create root sequence (
extract_root_sequence.pyor symlink):- If rerooted: Infers root sequence from tree mutations
- If not rerooted: Uses reference sequence as root
-
Augment metadata (
augment_metadata.py):- Adds host_group column (human, avian, swine, bovine, other)
- Adds geographic_group column (north_america, europe, asia, other)
- Adds temporal_group column (early, late, unknown based on global median date)
-
Extract geographic subtrees (matUtils extract):
- Creates separate subtrees for each configured geographic region (filter on the augmented
geographic_groupcolumn) - Each subtree includes the root sequence plus matching samples
- Creates separate subtrees for each configured geographic region (filter on the augmented
-
Infer per-node host states (PastML,
prepare_host_annotation.py):- Builds a 2-column annotation table from
combined_metadata_augmented.csvmappingisolate_id → host_group - Runs PastML with the DOWNPASS parsimony method on
final_tree.nwkto reconstructhost_groupfor every internal node - Inputs:
final_tree.nwk,combined_metadata_augmented.csv - Outputs:
host_ancestral/combined_ancestral_states.tab(per-node host_group; node IDs matchfinal_tree.pb.gz),host_ancestral/host_tree.html(interactive viz),host_ancestral/named.tree_final_tree.nwk - Ambiguous internals appear on multiple rows of
combined_ancestral_states.tab(one per equally-parsimonious state)
- Builds a 2-column annotation table from
-
Infer per-node subtype states (PastML,
prepare_subtype_annotation.py):- Builds a 2-column annotation table from
combined_metadata_augmented.csvmappingisolate_id → subtype, normalizing the raw GISAIDsubtype("A / H5N1") toH*N*form ("H5N1") inside the prep script (unknownfor non-matches) - Runs PastML with DOWNPASS on
final_tree.nwkto reconstructsubtypefor every internal node - Inputs:
final_tree.nwk,combined_metadata_augmented.csv - Outputs:
subtype_ancestral/combined_ancestral_states.tab,subtype_ancestral/subtype_tree.html - On HA per-subtype trees the H letter is fixed across all tips and only the inferred N partner varies (analogously for NA trees); on internal-segment trees (
PB2/all,PB1/all,PA/all,NP/all,MP/all,NS/all) neither letter is constrained, so the fullH*N*can vary along the tree and ancestral subtype tracks reassortment events
- Builds a 2-column annotation table from
-
Create visualizations (usher_to_taxonium):
- Converts final tree and all subtrees to Taxonium format
- Incorporates metadata (including host, geographic, and temporal groups) for interactive exploration
-
Execute analysis notebooks (jupyter nbconvert):
- Runs analysis notebooks after all pipeline outputs are complete
- Generates HTML reports in
results/notebooks/ - Includes metadata analysis and alignment statistics
-
Record input data md5 sums (md5sum):
- Walks every
.fastaand.xlsfile under the configuredinput_dirsand writes their md5 hashes toresults/input_data_md5sums.txtas a provenance manifest - Files are listed as explicit Snakemake inputs, so the manifest is regenerated whenever any input data file changes
- Walks every
The main conda environment (environment.yml) provides Snakemake and the build dependencies needed to compile larch. Per-step dependencies are managed via separate conda environments in envs/, which Snakemake invokes automatically using the --use-conda flag.