# tocID <- "BIN-ALI-Similarity.R" # # Purpose: A Bioinformatics Course: # R code accompanying the BIN-ALI-Similarity unit. # # Version: 1.2 # # Date: 2017-10 - 2020-09 # Author: Boris Steipe (boris.steipe@utoronto.ca) # # Versions: # 1.2 2020 Updates # 1.1 Change from require() to requireNamespace(), # use ::() idiom throughout # 1.0 Refactored for 2017; add aaindex, ternary plot. # 0.1 First code copied from 2016 material. # # # TODO: # Update ggtern:: ternary plot to use aacol dots under text # # # == DO NOT SIMPLY source() THIS FILE! ======================================= # # If there are portions you don't understand, use R's help system, Google for an # answer, or ask your instructor. Don't continue if you don't understand what's # going on. That's not how it works ... # # ============================================================================== #TOC> ========================================================================== #TOC> #TOC> Section Title Line #TOC> ---------------------------------------------- #TOC> 1 Amino Acid Properties 41 #TOC> 2 Mutation Data matrix 158 #TOC> 3 Background score 199 #TOC> #TOC> ========================================================================== # = 1 Amino Acid Properties =============================================== # A large collection of amino acid property tables is available via the seqinr # package: if (! requireNamespace("seqinr", quietly=TRUE)) { install.packages("seqinr") } # Package information: # library(help = seqinr) # basic information # browseVignettes("seqinr") # available vignettes # data(package = "seqinr") # available datasets # A true Labor of Love has gone into the compilation of the seqinr "aaindex" # data: ?aaindex data(aaindex, package = "seqinr") # load the aaindex list from the package length(aaindex) # Here are all the index descriptions for (i in 1:length(aaindex)) { cat(paste(i, ": ", aaindex[[i]]$D, "\n", sep="")) } # It's a bit cumbersome to search through the descriptions ... here is a # function to make this easier: searchAAindex <- function(patt) { # Searches the aaindex descriptions for regular expression "patt" # and prints index number and description. hits <- which(sapply(aaindex, function(x) length(grep(patt, x$D)) > 0)) for (i in seq_along(hits)) { cat(sprintf("%3d\t%s\n", hits[i], aaindex[[ hits[i] ]]$D)) } } searchAAindex("free energy") # Search for "free energy" searchAAindex("(size)|(volume)") # Search for "size" or "volume": # Let's examine ... # ... a hydrophobicity index (Y <- aaindex[[528]][c("D", "I")]) # ... a volume index (V <- aaindex[[150]][c("D", "I")]) # ... and one of our own: side-chain pK values as reported by # Pace et al. (2009) JBC 284:13285-13289, with non-ionizable pKs set # to 7.4 (physiological pH) K <- list(I = c( 7.4, # Ala 12.3, # Arg 7.4, # Asn 3.9, # Asp 8.6, # Cys 7.4, # Gln 4.3, # Glu 7.4, # Gly 6.5, # His 7.4, # Ile 7.4, # Leu 10.4, # Lys 7.4, # Met 7.4, # Phe 7.4, # Pro 7.4, # Ser 7.4, # Thr 7.4, # Trp 9.8, # Tyr 7.4)) # Val names(K$I) <- c("Ala","Arg","Asn","Asp","Cys","Gln","Glu","Gly","His","Ile", "Leu","Lys","Met","Phe","Pro","Ser","Thr","Trp","Tyr","Val") # Given these biophysical indices, how similar are the amino acids? We have three-dimensions of measures here. Scatterplots can only display two dimensions ... # pull the names from Y$I, convert them to single letter code, and reorder the # AACOLS palette accordingly ... aac <- AACOLS[toupper(seqinr::a(names(Y$I)))] plot(Y$I, V$I, xlab = "hydrophobicity", ylab = "volume", pch = 21, cex = 6, col = aac, bg = aac) text(Y$I, V$I, names(Y$I), cex = 0.8) plot(Y$I, K$I, xlab = "hydrophobicity", ylab = "pK", pch = 21, cex = 6, col = aac, bg = aac) text(Y$I, K$I, names(Y$I), cex = 0.8) # ... but how do we plot 3D data? Plotting into a 3D cube is possible, but such # plots are in general unintuitive and hard to interpret. One alternative is a # so-called "ternary plot": if (! requireNamespace("ggtern", quietly=TRUE)) { install.packages("ggtern") } # Package information: # library(help = ggtern) # basic information # browseVignettes("ggtern") # available vignettes # data(package = "ggtern") # available datasets # collect into data frame, normalize to (0.05, 0.95) myDat <- data.frame("phi" = 0.9*(((Y$I-min(Y$I))/(max(Y$I)-min(Y$I))))+0.05, "vol" = 0.9*(((V$I-min(V$I))/(max(V$I)-min(V$I))))+0.05, "pK" = 0.9*(((K$I-min(K$I))/(max(K$I)-min(K$I))))+0.05, stringsAsFactors = FALSE) rownames(myDat) <- names(Y$I) ggtern::ggtern(data = myDat, ggplot2::aes(x = vol, y = phi, z = pK, label = rownames(myDat))) + ggplot2::geom_text() # This results in a mapping of amino acids relative to each other that is # similar to the Venn diagram you have seen in the notes. # ... or we could use principal components analysis, to pull out the # best projection of the three feature dimensions into two. (Done here without delving # into the theory ...) prc <- prcomp(myDat) plot(prc$x[,1], prc$x[,2], xlab="", ylab="", xaxt="n", yaxt="n", pch=19, cex=6, col=aad, cex.main=0.7, main="Principal Component Analysis of Amino Acid Features") text(prc$x[,1], prc$x[,2], names(Y$I), cex = 0.8, col="#00000088") # This matches the intuition rather well in that "similar" amino acids are close # on the plot. But we can't interpret the distances in terms of just one of the # parameters. Whatever - nature has a different way to define similarity: # mutations to similar amino acids are less likely to break the protein. # = 2 Mutation Data matrix ================================================ # A mutation data matrix encodes all amino acid pairscores in a matrix. # The Biostrings package contains the most common mutation data matrices. if (! requireNamespace("BiocManager", quietly=TRUE)) { install.packages("BiocManager") } if (! requireNamespace("Biostrings", quietly=TRUE)) { BiocManager::install("Biostrings") } # Package information: # library(help=Biostrings) # basic information # browseVignettes("Biostrings") # available vignettes # data(package = "Biostrings") # available datasets # Let's load the BLOSUM62 mutation data matrix from the package data(BLOSUM62) # ... and see what it contains. (You've seen this matrix before.) BLOSUM62 # We can simply access values via the row/column names. # Identical amino acids have high scores ... BLOSUM62["H", "H"] # Score for a pair of two histidines BLOSUM62["S", "S"] # Score for a pair of two serines # Similar amino acids have low positive scores ... BLOSUM62["L", "I"] # Score for a leucine / lysine pair BLOSUM62["F", "Y"] # etc. # Dissimilar amino acids have negative scores ... BLOSUM62["L", "K"] # Score for a leucine / lysine pair BLOSUM62["Q", "P"] # etc. BLOSUM62["R", "W"] # the matrix is symmetric! BLOSUM62["W", "R"] # = 3 Background score ==================================================== # The mutation data matrix is designed to give high scores to homologous # sequences, low scores to non-homologous sequences. What score on average # should we expect for a random sequence? # If we sample amino acid pairs at random, we will get a score that is the # average of the individual pairscores in the matrix. Omitting the ambiguity # codes and the gap character: sum(BLOSUM62[1:20, 1:20])/400 # But that score could be higher for real sequences, for which the amino acid # distribution is not random. For example membrane proteins have a large number # of hydrophobic residues - an alignment of unrelated proteins might produce # positive scores. And there are other proteins with biased amino acid # compositions, in particular poteins that interact with multiple other # proteins. Let's test how this impacts the background score by comparing a # sequence with shuffled sequences. These have the same composition, but are # obvioulsy not homologous. The data directory contains the FASTA file for the # PDB ID 3FG7 - a villin headpiece structure with a large amount of # low-complexity amino acid sequence ... aa3FG7 <- Biostrings::readAAStringSet("./data/3FG7.fa")[[1]] # ... and the FASTA file for the E. coli OmpG outer membrane porin (PDB: 2F1C) # with an exceptionally high percentage of hydrophobic residues. aa2F1C <- Biostrings::readAAStringSet("./data/2F1C.fa")[[1]] # Here is a function that takes two sequences and # returns their average pairscore. averagePairScore <- function(a, b, MDM = BLOSUM62) { # Returns average pairscore of two sequences. # Parameters: # a, b chr amino acid sequence string # MDM mutation data matrix. Default is BLOSUM62 # Value: num average pairscore. a <- unlist(strsplit(a, "")) b <- unlist(strsplit(b, "")) v <- 0 for (i in seq_along(a)) { v <- v + MDM[ a[i], b[i] ] } return(v / length(a)) } orig3FG7 <- toString(aa3FG7) orig2F1C <- toString(aa2F1C) N <- 1000 scores3FG7 <- numeric(N) scores2F1C <- numeric(N) for (i in 1:N) { scores3FG7[i] <- averagePairScore(orig3FG7, toString(sample(aa3FG7))) scores2F1C[i] <- averagePairScore(orig2F1C, toString(sample(aa2F1C))) } # Plot the distributions hist(scores3FG7, col="#5599EE33", breaks = seq(-1.5, 0, by=0.1), main = "Pairscores for randomly shuffled sequences", xlab = "Average pairscore from BLOSUM 62") hist(scores2F1C, col="#55EE9933", breaks = seq(-1.5, 0, by=0.1), add = TRUE) abline(v = sum(BLOSUM62[1:20, 1:20])/400, col = "firebrick", lwd = 2) legend('topright', c("3FG7 (villin)", "2F1C (OmpG)"), fill = c("#5599EE33", "#55EE9933"), bty = 'n', inset = 0.1) # This is an important result: even though we have shuffled significantly biased # sequences, and the average scores trend above the average of the mutation data # matrix, the average scores still remain comfortably below zero. This means # that we can't (in general) improve a high-scoring alignment by simply # extending it with randomly matched residues. We will only improve the score if # the similarity of newly added residues is larger than what we expect to get by # random chance! # [END]