I ended up using an iterative approach (inspired by this post), which took 1.5 hours - so a massive speed up (and that was just the sequential version).
Note: fd
is analogous to find
, but way nicer to use.
First, merge subsets of n VCFs
# size of the subsets
n=200
# create file of filenames for each subset
fd -e vcf . path/to/vcfs/ | split -l $n --additional-suffix '.fofn' - subset_vcfs
-e vcf
tells fd
to find all files with a .vcf
extension, you can obviously change this to .bcf
/.vcf.gz
etc. if necessary. split
splits the list of all VCFs files into subsets of size -l
(200) and creates the file of filenames with the prefix subset_vcfs
. It will add a random suffix to this - e.g. subset_vcfsav
. Plus the additional suffix .fofn
. See the split
manual for all options.
Now iterate through these subsets and merge the VCFs listed in them.
Subset merge option 1 - sequential
This just merges the VCFs one at a time
for sub in *.fofn;
do
echo "Merging subset ${sub}..."
out_vcf="merge.${sub}.bcf"
bcftools merge --file-list "$sub" -o "$out_vcf"
bcftools index "$out_vcf"
done
Subset merge option 2 - parallel
You can easily parallelise the previous step using fd
on the subset files. See this post for more details. Or basic bash for
loop parallelisation.
Create a script (merge_subset.sh
) of what we want to run on each subset
#!/usr/bin/env bash
sub="$1"
echo "Merging subset ${sub}..."
out_vcf="merge.${sub}.bcf"
bcftools merge --file-list "$sub" -o "$out_vcf"
bcftools index "$out_vcf"
Now we run this script with each subset file of filenames using fd
# number of processes to use. remove this option to use all available
jobs=4
fd -j $jobs -e fofn -x bash merge_subset.sh '{}'
Final merge
Now we merge all of these VCFs into our final VCF
ls merge.*.bcf > merged.fofn
bcftools merge -o merged.bcf --file-list merged.fofn