Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R match between two comma-separated strings

I am trying to find an elegant way to find matches between the two following character columns in a data frame. The complicated part is that either string can contain a comma-separated list, and if a member of one list is a match for any member of the other list, then that whole entry would be considered a match. I'm not sure how well I've explained this, so here's sample data and output:

Alt1:

  • AT
  • A
  • G
  • CGTCC,AT
  • CGC

Alt2:

  • AA
  • A
  • GG
  • AT,GGT
  • CG

Expected Match per row:

  • Row 1 = none
  • Row 2 = A
  • Row 3 = none
  • Row 4 = AT
  • Row 5 = none

Non-working solutions:

First attempt: merge entire data frames by desired columns, then match up the alt columns shown above:

match1 = data.frame(merge(vcf.df, ref.df, by=c("chr", "start", "end",  "ref")))
matches = unique(match1[unlist(sapply(match1$Alt1 grep, match1$Alt2, fixed=TRUE)),])

Second method, using findoverlaps feature from VariantAnnoatation/Granges:

findoverlaps(ranges(vcf1), ranges(vcf2)) 

Any suggestions would be greatly appreciated! Thank you!

Solution Thanks to @Marat Talipov's answer below, the following solution works to compare two comma-separated strings:

> ##read in edited kaviar vcf and human ref
> ref <-     readVcfAsVRanges("ref.vcf.gz", humie_ref)
Warning message:
In .vcf_usertag(map, tag, ...) :
  ScanVcfParam ‘geno’ fields not present: ‘AD’

> ##rename chromosomes to match with vcf files
> ref <- renameSeqlevels(ref, c("1"="chr1"))

> ##################################
> ## Gather VCF files to process  ## 
> ##################################
> ##data frame *.vcf.gz files in directory path
> vcf_path <- data.frame(path=list.files(vcf_dir, pattern="*.vcf.gz$",  full=TRUE))

> ##read in everything but sample data for speediness
> vcf_param = ScanVcfParam(samples=NA)
> vcf <- readVcfAsVRanges("test.vcf.gz", humie_ref, param=vcf_param)

> #################
> ## Match SNP's ##
> #################
> ##create data frames of info to match on
> vcf.df = data.frame(chr =as.character(seqnames(vcf)), start = start(vcf),     end = end(vcf), ref = as.character(ref(vcf)), 
+                     alt=alt(vcf), stringsAsFactors=FALSE)
> ref.df = data.frame(chr =as.character(seqnames(ref)), start =     start(ref), end = end(ref), 
+                     ref = as.character(ref(ref)), alt=alt(ref),     stringsAsFactors=FALSE)
> 
> ##merge based on all positional fields except vcf
> col_match = data.frame(merge(vcf.df, ref.df, by=c("chr", "start", "end", "ref")))

> library(stringi)
> ##split each alt column by comma and bind together
> M1 <- stri_list2matrix(sapply(col_match$alt.x,strsplit,','))
> M2 <- stri_list2matrix(sapply(col_match$alt.y,strsplit,','))
> M <- rbind(M1,M2)

> ##compare results
> result <- apply(M,2,function(z) unique(na.omit(z[duplicated(z)])))

> ##add results column to col_match df for checking/subsetting
> col_match$match = result
> head(col_match)
   chr    start      end ref alt.x alt.y match
1 chr1 39998059 39998059   A     G     G     G
2 chr1 39998059 39998059   A     G     G     G
3 chr1 39998084 39998084   C     A     A     A
4 chr1 39998084 39998084   C     A     A     A
5 chr1 39998085 39998085   G     A     A     A
6 chr1 39998085 39998085   G     A     A     A
like image 377
SummerEla Avatar asked Sep 03 '25 17:09

SummerEla


2 Answers

In the case that input lists are of equal length and you'd like to compare list elements in the pairwise manner, you could use this solution:

library(stringi)

M1 <- stri_list2matrix(sapply(Alt1,strsplit,','))
M2 <- stri_list2matrix(sapply(Alt2,strsplit,','))
M <- rbind(M1,M2)

result <- apply(M,2,function(z) unique(na.omit(z[duplicated(z)])))

Sample input:

Alt1 <- list('AT','A','G','CGTCC,AT','CGC','GG,CC')
Alt2 <- list('AA','A','GG','AT,GGT','CG','GG,CC')

Output:

# [[1]]
# character(0)
# 
# [[2]]
# [1] "A"
# 
# [[3]]
# character(0)
# 
# [[4]]
# [1] "AT"
# 
# [[5]]
# character(0)
# 
# [[6]]
# [1] "GG" "CC"
like image 78
Marat Talipov Avatar answered Sep 06 '25 12:09

Marat Talipov


Sticking with the stringi package, you could do something like this, using the Alt1 and Alt2 data from Marat's answer.

library(stringi)

f <- function(x, y) {
    ssf <- stri_split_fixed(c(x, y), ",", simplify = TRUE)
    if(any(sd <- stri_duplicated(ssf))) ssf[sd] else NA_character_
}

Map(f, Alt1, Alt2)
# [[1]]
# [1] NA
# 
# [[2]]
# [1] "A"
# 
# [[3]]
# [1] NA
# 
# [[4]]
# [1] "AT"
# 
# [[5]]
# [1] NA
# 
# [[6]]
# [1] "GG" "CC"

Or in base R, we can use scan() to separate the strings with commas.

g <- function(x, y, sep = ",") {
    s <- scan(text = c(x, y), what = "", sep = sep, quiet = TRUE)
    s[duplicated(s)]
}
Map(g, Alt1, Alt2)
like image 42
Rich Scriven Avatar answered Sep 06 '25 12:09

Rich Scriven