Function to look for different patterns in specific positions in a string in R

Viewed 92

I am trying to create a function in R that searches in strings a specific pattern in a specific position, if the letter is present in the established position, I want to count it.

example of dataset:

library(dplyr)

mutations <- tibble(
  "position" = c(9,10),
  "AA" = c("G","G"))

strings <- c("EVQLVESGGGLAKPG", 
             "VQLVESGGGLAKPGGS",
             "EVQLVESGGALAKPGGSLRLSCAAS")

So, in this case, I want to look for the position 9 and 10, if there is a letter "G" I want to count it.

Expected dataframe or tibble output:

| string | mut_counts |
|________|____________|
|   1    |     2      |
|________|____________|
|   2    |     1      |
|________|____________|
|   3    |     1      |
|________|____________|

In this example, all strings have a "G" at position 9, so they would all get 1, and only one of the three sequences have a "G" at position 10, so this sequence will have 2.

I am trying to use str_locate_all() from the stringr package to be able to locate the positions and then compare with my dataframe to count but I am failing to get what I wanted.

library(stringr)

.class_mutations <- function(sequences, mutations){
  .count_pattern <- function(x){
    df <- sum(as.integer(locating_all_patterns[[x]][,"start"] == mutations$position[mut]))
  }
  
  for(mut in nrow(mutations)){
    locating_all_patterns <- str_locate_all(pattern = mutations$AA[mut], sequences)
    counting_patterns <- lapply(locating_all_patterns, .count_pattern)
  }
    
  return(counting_patterns)
}

.class_mutations(strings, mutations)

I am getting this error Error in locating_all_patterns[[x]] : no such index at level 1, besides, if you have a better/faster way to do this, I would also appreciate it. I have to take into account that this is going to applied in thousands of strings, so I should avoid slow functions.

Thank you

2 Answers

base R

rowSums(outer(strings, seq_len(nrow(mutations)),
        function(st, i) {
          substr(st, mutations$position[i], mutations$position[i]) == mutations$AA[i]
        }))
# [1] 2 1 1

Walk-through:

  • outer effectively just produces two vectors, an expansion of the cartesian product of the two arguments. If we insert a browser() as the first line of the inner anon-func, we'd see

    data.frame(st, i)
    #                          st i
    # 1           EVQLVESGGGLAKPG 1
    # 2          VQLVESGGGLAKPGGS 1
    # 3 EVQLVESGGALAKPGGSLRLSCAAS 1
    # 4           EVQLVESGGGLAKPG 2
    # 5          VQLVESGGGLAKPGGS 2
    # 6 EVQLVESGGALAKPGGSLRLSCAAS 2
    

    (Shown as a frame only for a columnar presentation. Both st and i are simple vectors.)

    From here, knowing that substr is vectorized across all arguments, then a single call to substr will find the ith character in each of the strings.

  • The result of the substr is a vector of letters. Continuing the same browser() session from above,

    substr(st, mutations$position[i], mutations$position[i])
    # [1] "G" "G" "G" "G" "L" "A"
    mutations$AA[i]
    # [1] "G" "G" "G" "G" "G" "G"
    substr(st, mutations$position[i], mutations$position[i]) == mutations$AA[i]
    # [1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE
    

    The mutations$AA[i] shows us what we're looking for. A nice thing of the vectorized method here is that mutations$AA[i] will always be the same length and in the expected order of letters retrieved by substr(.).

  • The outer itself returns a matrix, with length(X) rows and length(Y) columns (X and Y are the first and second args to outer, respective).

    outer(strings, seq_len(nrow(mutations)),
            function(st, i) {
              substr(st, mutations$position[i], mutations$position[i]) == mutations$AA[i]
            })
    #      [,1]  [,2]
    # [1,] TRUE  TRUE
    # [2,] TRUE FALSE
    # [3,] TRUE FALSE
    

    The number of correct mutations found in each string is just a sum of each row. (Ergo rowSums.)


If you're concerned due to a large amount of mutations and strings, you can replace the outer and iterate over each row of mutations instead:

rowSums(sapply(seq_len(nrow(mutations)), function(i) substr(strings, mutations$position[i], mutations$position[i]) == mutations$AA[i]))
# [1] 2 1 1

This calls substr once for each mutations row, so if the outer-explosion is too much, this might reduce the memory footprint.

For a base R option, we can make sure of the string functions. This approach compares the length of each substring before and after replacing the target character:

nchar(substr(strings, 9, 10)) -
nchar(gsub("G", "", substr(strings, 9, 10), fixed=TRUE))

[1] 2 1 1

Data:

strings <- c("EVQLVESGGGLAKPG", 
             "VQLVESGGGLAKPGGS",
             "EVQLVESGGALAKPGGSLRLSCAAS")
Related