Visualising Assembly Graphs. Visualising Assembly Graphs for... | by Vijini Mallawaarachchi | Towards Data Science

I have been working with assembly graphs of metagenomes for a while and I have come across many fascinating things. In this article, I will share with you some of my observations related to metagenomic assembly graphs and binning contigs obtained from those graphs. Assuming that you have a basic understanding about genome assembly (if not you can read my previous article Genome Assembly — The Holy Grail of Genome Analysis), let’s get started.
An assembly graph is used to represent the final assembly of a genome (or metagenomes). In simple terms, the assembler builds this assembly graph based on reads and their overlap information. Finally, the assembler resolves paths across the assembly graph and outputs non-branching paths as contigs.
Given below is part of the visualisation of an assembly graph obtained from the tool named Bandage.
The most common file format used to represent assembly graphs is the GFA (Graph Fragment Assembly) format. A GFA file consists of a set of sequences and tab-delimited pairs of sequences with their overlaps. You can read more about this format from http://gfa-spec.github.io/GFA-spec/GFA1.html.
As shown in Figure 2, sequences are denoted starting from “S” and overlaps (or links) between sequences are denoted starting from “L”. The plus (+) and minus (-) signs denote whether the original sequence or its reverse complement is considered in the overlap. The value denoted with the letter “M” in a link refers to the overlap length. In this sample file, the overlap length is 55 base pairs.
Since the assembly graph we are talking about is already a “graph”, we can model sequences to be vertices and overlaps/links to be edges.
Vertices → Sequences
Edges → Overlaps between sequences
Now let us visualise a sample assembly graph using python-igraph. You can read more about visualising graph data using python-igraph from my previous article. For the ease of explanation, I will not consider the plus and minus signs in the link information. In simple terms, I will be visualising an undirected graph.
We will consider a dataset consisting of reads from two bacterial species; E. faecalis and S. aureus. We will refer to this as the ES dataset. I have already assembled this dataset using metaSPAdes assembler to obtain contigs. This contig dataset known as ES+metaSPAdes, can be found from the link here as contigs.fasta. The assembly graph file can be found as assembly_graph_with_scaffolds.gfa.
Note: metaSPAdes represents each contig as a set of segments and the assembly graph file contains details about these segments and links between these segments. Hence, when obtaining the links between the contigs, you will have to scan the prefix and suffix for each contig in the contigs.paths file and the assembly_graph_with_scaffolds.gfa file to determine the prefix or suffix of the overlapping contig.
The visualisation of the ES+metaSPAdes dataset will look as follows.
We can align the contigs to reference genomes to determine to which genome each contig belong to. For this, we can use BWA-MEM. Given below is a sample command to run BWA-MEM.
bwa mem <path to reference genome> <path to contig file> > <output path>alignment.sam
For each contig, the reference genome resulting in the longest alignment length can be considered as the contig’s source.
After determining the ground truth of the contigs, we can label the vertices (contigs) and visualise this data as shown in Figure 4.
As illustrated in Figure 5, we can see that the contigs from the two species tend to form two separate regions in the assembly graph.
Moreover, it is highly likely that contigs belonging to the same species tend to have overlaps among each other, apart from the boundary cases. We can make use of these data during binning analysis.
Here are some code snippets used to produce the images I have presented in this article.
import refrom igraph import *from collections import defaultdictpaths = {}segment_contigs = {}node_count = 0# Get contig paths from contigs.pathswith open(<path to metaSPAdes contigs.paths file>) as file:    name = file.readline()    path = file.readline()        while name != "" and path != "":                    while ";" in path:            path = path[:-2]+","+file.readline()                start = 'NODE_'        end = '_length_'        contig_num = str(int(re.search('%s(.*)%s' % (start, end), name).group(1))-1)                segments = path.rstrip().split(",")                if contig_num not in paths:            node_count += 1            paths[contig_num] = [segments[0], segments[-1]]                for segment in segments:            if segment not in segment_contigs:                segment_contigs[segment] = set([contig_num])            else:                segment_contigs[segment].add(contig_num)                name = file.readline()        path = file.readline()links = []links_map = defaultdict(set)# Get contig paths from contigs.pathswith open(<path to metaSPAdes GFA file>) as file:    line = file.readline()        while line != "":                # Identify lines with link information        if "L" in line:            strings = line.split("\t")            f1, f2 = strings[1]+strings[2], strings[3]+strings[4]            links_map[f1].add(f2)            links_map[f2].add(f1)            links.append(strings[1]+strings[2]+" "+strings[3]+strings[4])        line = file.readline()# Create graphg = Graph()# Add verticesg.add_vertices(node_count)for i in range(len(g.vs)):    g.vs[i]["id"]= i    g.vs[i]["label"]= str(i+1)for i in range(len(paths)):    segments = paths[str(i)]        start = segments[0]    start_rev = ""    if start.endswith("+"):        start_rev = start[:-1]+"-"    else:        start_rev = start[:-1]+"+"            end = segments[1]    end_rev = ""    if end.endswith("+"):        end_rev = end[:-1]+"-"    else:        end_rev = end[:-1]+"+"        new_links = []        if start in links_map:        new_links.extend(list(links_map[start]))    if start_rev in links_map:        new_links.extend(list(links_map[start_rev]))    if end in links_map:        new_links.extend(list(links_map[end]))    if end_rev in links_map:        new_links.extend(list(links_map[end_rev]))        for new_link in new_links:        if new_link in segment_contigs:            for contig in segment_contigs[new_link]:                if i!=int(contig):                    g.add_edge(i,int(contig))            g.simplify(multiple=True, loops=False, combine_edges=None)
out_fig_name = "assembly_graph.png"visual_style = {}# Set bbox and marginvisual_style["bbox"] = (1500,1500)visual_style["margin"] = 30# Set vertex coloursvisual_style["vertex_color"] = 'white'# Set vertex sizevisual_style["vertex_size"] = 35# Set vertex lable sizevisual_style["vertex_label_size"] = 15# Don't curve the edgesvisual_style["edge_curved"] = False# Set the layoutmy_layout = g.layout_fruchterman_reingold()visual_style["layout"] = my_layout# Plot the graphplot(g, out_fig_name, **visual_style)
node_colours = []for i in range(node_count):    if i in efaecalis_list:        node_colours.append("red")    elif i in saureus_list:        node_colours.append("green")    else:        node_colours.append("white")out_fig_name = "coloured_assembly_graph.png"g.vs["color"] = node_coloursvisual_style = {}# Set bbox and marginvisual_style["bbox"] = (1500,1500)visual_style["margin"] = 30# Set vertex sizevisual_style["vertex_size"] = 35# Set vertex lable sizevisual_style["vertex_label_size"] = 15# Don't curve the edgesvisual_style["edge_curved"] = False# Set the layoutvisual_style["layout"] = my_layout# Plot the graphplot(g, out_fig_name, **visual_style)
My lab and I have developed a tool named GraphBin to refine binned contigs by making use of assembly graphs and the connectivity information between contigs. You can find the GitHub repo from here.
github.com
GraphBin is published in the OUP Bioinformatics journal. You can have a look at the publication for more information about the tool from DOI: 10.1093/bioinformatics/btaa180.
dx.doi.org
You can also read more on genome assembly and metagenomics from my previous articles listed below.
towardsdatascience.com
towardsdatascience.com
Hope you found my findings interesting. I would love to hear your thoughts.
Thank you for reading.