Affimer Proteins: Next Generation Sequencing Data Analysis (Part 1) | by Rob Harrand | Towards Data Science

In this blog series, I’ll describe a specific data analysis problem using the R programming language. Namely, the analysis of biological data from a Next Generation Sequencing machine, relating to a specific type of molecule called an Affimer protein. A reasonable working knowledge of R is beneficial to understanding the code, but the overall process can be understood without this.
In part 1, we’ll start with an introduction to the context of the analysis task, and then we’ll begin to explore some example data and find specific patterns in DNA sequences. In part 2 we’ll then take the analysis further, looking at amino acid translation and ‘frame-shifts’. Finally in part 3, we’ll see how to use molecular barcodes, and then how to whittle down the choice of the best Affimer protein candidates.
Before we can talk about Affimer molecules, we need to understand the basics of antibodies. Antibodies are Y-shaped molecules that form a vital part of the immune system. They’re used to detect and bind to molecules that trigger immune responses, such as bacteria or viruses. The unique molecular sections targeted are known as antigens (or ‘antibody generators’), and can be thoughts of as the ‘keys’ to the corresponding ‘lock’ of an antibody.
One part on an antibody (the ‘Y-part’) is highly variable between antibodies, but extremely specific to each antigen. This not only makes them useful for fighting infections, but also useful in the development of diagnostic tests, as they can be used to detect a molecule of interest. For example, home pregnancy tests use antibodies (immobilised in a line on a test strip) to detect and attach to human chorionic gonadotropin (hCG).
For this any many other reasons, antibodies are used globally in many areas of medicine, from diagnostics to therapeutics. Typically, antibodies for specific applications are produced from a single cell line, creating what are called monoclonal antibodies.
However, for all their useful properties, there are downsides to such antibodies, including potentially long timescales to identify suitable binders (i.e. particular antibodies that bind to some target of interest), issues related to their relatively large size (such as ‘steric hindrance’, or the slowing down of chemical reactions due to the bulky nature of the molecules), and the inevitable use of animals.
See below for a diagram of an antibody, with the antigen binding site highlighted,
Affimer molecules are proteins that are designed to perform similar functions to monoclonal antibodies, but with several advantages. First, they are much smaller, making them more stable (in terms of temperature and pH) than their much larger antibody counterparts, as-well-as less prone to steric hindrance. Second, they’re much quicker to create in terms of finding a binder to a specific target. Third, their batch-to-batch consistency is high. And fourth, no animals are involved as they are generated using an in-vitro process.
Below is a diagram showing the relative sizes of an antibody and an Affimer protein. The binding areas are shown in red,
Affimer molecules are not individually designed for a given application. Instead, vast libraries of Affimer binders are created, from which suitable molecules are selected. We’ll describe the process in more detail below, but before we do, it’s interesting to delve a little deeper into how nature solves the same problem, i.e. how to find a suitable binder to a new, previously unknown target? The process is actually not that dissimilar to the Affimer molecule approach.
If this was an engineering problem, we’d analyse the target and work out the best possible binder from theoretical principles. Nature has no such capacity. Instead, the approach taken is to start with a brute-force, randomised approach using billions of different antibodies. Above we saw that each antibody has a highly variable region. There are several mechanisms that have evolved to generate the variation in this region (whilst leaving the rest of the framework unchanged, or ‘conserved’ between antibodies). First, B cells (a type of white blood cell that creates antibodies) uses a process called somatic recombination (or more specifically, V(D)J recombination) to generate the initial variability. This processes basically involves constructing an antibody by randomly selecting different possible sub-sections (a bit like repeatedly picking random cards from a deck to generate many different combinations).
Once one of these antibodies with a random, variable region binds to an antigen (due to its variable region just happening to be compatible), a second mechanism sets to work, to further improve the affinity (or the strength of the attraction). This is called somatic hypermutation and involves genetic point mutations (mutations in single nucleotides) caused by errors made during the rapid rate of B-cell proliferation. These mutations take a DNA sequence that encodes a suitable binder and modifies it slightly in many different ways. Some of the consequent variable regions will have a lower affinity, and some may lose their binding ability to the corresponding antigen all together. But some may, again by chance, increase their binding affinity. The corresponding B-cells that produce these antibodies will, as the cells continue to multiply, out-compete those with lower binding strengths, leading to the optimum binders being generated overall, in a process called affinity maturation.
The result is a large number of antibodies that have a variable region very well suited to binding to a particular antigen.
Affimer binders are created in a similar way, in that randomisation is used over specific design. This begins with a process called phage display. This is a process that uses a certain type of virus called a bacteriophage, or phage for short. Genes are inserted into these phages, causing them to produce (or ‘display’) the particular protein that corresponds to the injected gene on its surface. By allowing mutations in this gene, random variants of some initial genetic sequence are created, resulting in many different versions (hence the term ‘library’). Affimer protein libraries consist of around 10^10 randomised sequences.
The next step is to pass a target molecule (i.e. the molecule of interest, for which a binder is required) over this library and to see which of the 10^10 binders are compatible. After a simple wash-step removes the rest, the phages with the bound Affimer molecules can then be removed, amplified (by infecting bacteria and causing them to produce many more copies), and the process repeated. This way, the binder or binders that just so happen to bind (remember, the library consists of randomised mutations) can be isolated, identified and characterised.
It’s this characterisation where the data begins to be generated. The first step in this process is to take the binders that have resulted from these repeated steps and to determine their exact DNA sequences. This is a crucial step, because there is now an Affimer binder has the right characteristics for a given application. This connection between the phenotype and associated genotype is what allows a certain molecule to be uniquely identified, ring-fenced (by, say, a particular company or University) and manufactured at scale.
In terms of physical structure, Affimer molecules are made up of a ‘scaffold’ (the main body of the molecule) and two peptide loops (short sequences of amino-acids). It is the variability of the loops that allow Affimer binders to be screened and selected against target molecules. In terms of data analysis, it is the amino-acid sequence of these loops that is key. Note that the two loop regions are called ‘loop 2’ and ‘loop 4’ respectively. The diagram below shows an Affimer molecule with these variable loop regions highlighted,
In this section, we’ll dive into the data analysis relating to Affimer binders.
To start, we’ll look at some useful R libraries and their corresponding functions for simple DNA manipulation. First, make sure you have the correct R libraries installed,
if (!requireNamespace(“BiocManager”, quietly = TRUE))install.packages(“BiocManager”) BiocManager::install(“Biostrings”)BiocManager::install(“ShortRead”) #Note, if after ‘updating all’ from the above you receive an installation error, you may have to manually delete the ‘rlang’ folder and reinstall
Next, load the installed libraries,
library(“Biostrings”) #Package for sequence analysislibrary(“ShortRead”) #For loading fastq fileslibrary(“tidyverse”) #For general data manipulation and plottinglibrary(“gridExtra”) #For plotting multiple plotslibrary(“stringr”) #For various string functions
Let’s start with an example (and completely made-up) DNA sequence, which we’ll set to a variable called ‘seq’,
seq = “TACGGAAAGCTAGAAAGTTTTGCTACTACATCAAAGTCCGCAAATACATGCACCTAAAAGTTGTTAACGGCCCATTCATA”
The Biostrings module provides a function called matchPattern that searches for a sequence in a larger sequence. Below is an example, where we’re searching for the sub-sequence ‘AAGTTTT’ in our ‘seq’ variable,
matchPattern(pattern = 'AAGTTTT',              subject = seq)## Views on a 80-letter BString subject## subject: TACGGAAAGCTAGAAAGTTTTGCTACTACATCAA...CATGCACCTAAAAGTTGTTAACGGCCCATTCATA## views:## start end width## [1] 15 21 7 [AAGTTTT]
You can see that the smaller sequence was found, starting at position 15 and ending at position 21. This function allows mismatches and indels (insertion or deletion of bases). For example, let’s add an ‘A’ to the middle of the sub-sequence and run it as before,
matchPattern(pattern = ‘AAGTATTT’,              subject = seq)## Views on a 80-letter BString subject## subject: TACGGAAAGCTAGAAAGTTTTGCTACTACATCAA...CATGCACCTAAAAGTTGTTAACGGCCCATTCATA## views: NONE
Now, because of the inserted DNA base, nothing was found. However, we can set the appropriate parameter to allow indels, and allow up to 1 mismatch,
matchPattern(pattern = ‘AAGTATTT’,              subject = seq,              with.indels = T,              max.mismatch = 1)## Views on a 80-letter BString subject## subject: TACGGAAAGCTAGAAAGTTTTGCTACTACATCAA...CATGCACCTAAAAGTTGTTAACGGCCCATTCATA## views:## start end width## [1] 15 21 7 [AAGTTTT]
Now the function has found the original match. Note that the length is still 7, as it has recognised the inserted ‘A’ as an insertion, and not part of the actual search sequence.
Now let’s apply this along with some other useful functions to a loaded (demo) dataset. We’ll use the readFastq function to directly load a Next Generation Sequencing (NGS) Fastq file. Note that we actually have two files, one per read direction. This is a consequence of how the NGS machine works, by reading each section of DNA from the start, forwards, and from the end, backwards. We’ll look into how NGS machines work later.
We’ll also use sread to extract the DNA sequence, and finally reverseComplement on the 2nd DNA sequence.
First, we use the function readFastq to read the fastq file. This file format is used extensively in the field of genomics, and is a simple text file. The files contain not only the sequence, but also ‘quality scores’ (known as Phred quality scores). These are generated by the NGS machine, and give an indication of how well the individual bases have been read. See the table below,
The scores are actually encoded in ASCII characters, where the number of each ASCII character represents the quality score (specifically, the Phred quality scores from 0 to 93 are encoded in ASCII characters 33 to 126). We’ll have a look at the quality scores, but first we need to read the data,
#Forward readread_f = readFastq(path, pattern=”999250_S99_L001_R1_001.fastq”)#Reverse readread_r = readFastq(path, pattern=”999250_S99_L001_R2_001.fastq”) 
Next, let’s extract just the sequences from the files using sread,
dna_1 = sread(read_f)dna_2 = sread(read_r)
And then get the reverse compliment of the reverse read (because it’s in reverse!),
dna_2 = reverseComplement(dna_2) #Get the reverse compliment
Let’s take a look at the quality of the first sequence,
quality(read_f[1])## class: FastqQuality## quality:## BStringSet object of length 1:## width seq## [1] 153 ^6@e?U1d@sy0i@`XHe~@BzFn`Ak=F7TQuT...Vq[q@VJ`TzVMGzoawbloN]P17k^zjA\d9
Here we can see the quality scores in ASCII format. We can go further than this, by using the FastqQuality and quality functions to get the quality scores from the files, and then plot them with the boxplot function. As a rule of thumb, anything over a Phred quality score of 20 is acceptable, so we’ll add that threshold as a red line,
qual <- FastqQuality(quality(quality(read_f))) #get quality scores readM <- as(qual, “matrix”) #convert scores to matrix boxplot(as.data.frame((readM)), #Plot the data        outline = FALSE,         main=”Per Cycle Read Quality”,         xlab=”Cycle”,         ylab=”Phred Quality”,         ylim=c(0,100)) abline(h=20, lwd = 3, col = ‘red’) #Add the horizontal line
We can see that the quality of this read is good, with all Phred scores well above the cut-off.
In this first post we’ve looked at what Affimer molecules are and have started to look at basic data analysis in R relating to DNA sequences. In part 2 we’ll go deeper into the data, see how DNA sequences are translated into amino acid sequences, search for specific Affimer molecule loops, and look at something called ‘reading frames’.