如何合并显示最常见字符的相似字符串

时间:2019-05-13 22:37:49

标签: r string bioinformatics

在一个数据帧中,我有一个字符串列表,这些字符串彼此相似,但相差%。我想将这些通用字符串组合成一个在每个位置具有最通用字符的字符串。

数据框如下所示:

\n

我正在尝试从模式列中添加另一列中最有可能的字符串

pattern  Freq     score rank
DT%E 37568 1138.4242    1
%TGE 37666 1018.0000    2
D%GE 37641 1017.3243    3
DTG% 37665  965.7692    4
%VGNE 34234  684.6800    5
SVGN% 34281  634.8333    6
SV%NE 34248  634.2222    7
SVG%E 34265  623.0000    8
%LGNE 41098  595.6232    9
SL%NE 41086  595.4493   10
SLGN% 41200  564.3836   11
SPT%AYNE 35082  539.7231   12
SP%AAYNE 35094  531.7273   13
SPTA%YNE 35061  531.2273   14
SPTAA%NE 35225  518.0147   15
SPTAAYN% 35144  516.8235   16
%PTAAYNE 35111  516.3382   17
S%TAAYNE 35100  516.1765   18
SPTAAY%E 35130  509.1304   19
SLG%E 41467  450.7283   20

2 个答案:

答案 0 :(得分:3)

这是一个棘手但有趣的问题。

这里应该给您一些想法(并复制您的预期输出);但是请注意,这在某种程度上是一种经验方法,它做出以下假设:

  1. 总是有>=2个模式属于同一true_string ;这对于(分层)聚类方法有效(见下文)是必要的。如果您有<2个模式定义了true_string,那么它将不起作用,这是有道理的,因为在同一位置出现两个字符的频率相同。

  2. 所有pattern的长度都相同;也就是说,我们只考虑单个字符的替换,而不考虑插入/删除。

方法

我们利用库stringdist来计算字符串相似度。 stringdistmatrix提供了各种距离度量标准(Levenshtein,Hamming等,有关详细信息,请参见?stringdist::stringdistmatrix)。在这种情况下,我们使用method = "qgram",因为它会导致与您的预期输出一致的分组(因此出现了较早的“经验”警告)。我不知道这对您的真实数据的概括程度如何,因此请务必记住,您可能需要尝试使用不同的method来找到“适合”您的距离相似性指标期望。

计算出字符串距离矩阵后,我们然后使用分层聚类对字符串进行聚类;我们基于在grp的垂直距离处切割树添加v = 2标签,然后使用自定义get_consensus_string函数来推断每个grp的共识字符串;如开头所述,该函数假定一个grp中的所有字符串都具有相同的长度,并针对该字符串中的每个位置选择出现频率最高的字符。

代码

首先,自定义get_consensus_string函数

library(tidyverse)
get_consensus_string <- function(x) {
    map_dfc(x, str_split, "") %>%
        rowid_to_column("pos") %>%
        gather(k, v, -pos) %>%
        group_by(pos, v) %>%
        add_count() %>%
        group_by(pos) %>%
        filter(n == max(n)) %>%
        arrange(pos, desc(v)) %>%
        dplyr::slice(1) %>%
        pull(v) %>%
        paste0(collapse = "")
}

我们现在可以基于来自grp的字符串相似性距离矩阵的分层聚类结果添加stringdist::stringdistmatrix标签;我凭经验在v = 2的垂直距离处切割了这棵树(这是可能需要调整的参数)。一旦有了grp标签,我们就会添加共识字符串。

library(stringdist)
df %>%
    mutate(grp = cutree(hclust(stringdistmatrix(df$pattern, method = "qgram")), h = 2)) %>%
    group_by(grp) %>%
    mutate(true_string = get_consensus_string(pattern)) %>%
    ungroup()
## A tibble: 20 x 6
#   pattern   Freq score  rank   grp true_string
#   <fct>    <int> <dbl> <int> <int> <chr>
# 1 DT%E     37568 1138.     1     1 DTGE
# 2 %TGE     37666 1018      2     1 DTGE
# 3 D%GE     37641 1017.     3     1 DTGE
# 4 DTG%     37665  966.     4     1 DTGE
# 5 %VGNE    34234  685.     5     2 SVGNE
# 6 SVGN%    34281  635.     6     2 SVGNE
# 7 SV%NE    34248  634.     7     2 SVGNE
# 8 SVG%E    34265  623      8     2 SVGNE
# 9 %LGNE    41098  596.     9     3 SLGNE
#10 SL%NE    41086  595.    10     3 SLGNE
#11 SLGN%    41200  564.    11     3 SLGNE
#12 SPT%AYNE 35082  540.    12     4 SPTAAYNE
#13 SP%AAYNE 35094  532.    13     4 SPTAAYNE
#14 SPTA%YNE 35061  531.    14     4 SPTAAYNE
#15 SPTAA%NE 35225  518.    15     4 SPTAAYNE
#16 SPTAAYN% 35144  517.    16     4 SPTAAYNE
#17 %PTAAYNE 35111  516.    17     4 SPTAAYNE
#18 S%TAAYNE 35100  516.    18     4 SPTAAYNE
#19 SPTAAY%E 35130  509.    19     4 SPTAAYNE
#20 SLG%E    41467  451.    20     3 SLGNE

您可以看到最终代码非常干净,并且可以再现预期的输出。


一些其他注释/评论

可能需要讨论两个问题:(1)如何选择合适的距离度量标准;(2)在哪里砍树。

关于第一个问题,一种经验方法是尝试对pattern s进行分层聚类后尝试不同的度量并可视化树状图。

例如,对于method = "qgram",您可以这样做

mat <- as.matrix(stringdistmatrix(df$pattern, method = "qgram"))
rownames(mat) <- df$pattern
colnames(mat) <- df$pattern
plot(hclust(as.dist(mat)))

enter image description here

一旦您对聚类结果感到满意,我们就可以继续。

关于砍伐树木,一种实用/务实的方法是检查树状图并找到我们砍伐树木的合适高度(本例中为v = 2);或者,如果您知道唯一的true_string的数量,则可以用cutree指定k中的组数。

用更专业的术语来说,树状图的高度与使用完全链接的组之间的距离相关联(即根据最不相似的对测量距离)。由于组之间的距离又是基于pattern之间的q-gram距离,因此可以将高度与两个pattern之间的q-gram-距离相关,即绝对两个pattern的N元语法向量之间的差异。

答案 1 :(得分:2)

我检查了莫里斯的答案,但是当我添加新行时。

新输入

D%GT    12434   12421   22      DXGT
DX%T    31242   2221.2  21      DXGT

使用的数据

pattern Freq    score   rank        true_string
DT%E    37568   1138.4242   1       DTGE
D%GT    12434   12421   22      DXGT
DX%T    31242   2221.2  21      DXGT
%TGE    37666   1018    2       DTGE
D%GE    37641   1017.3243   3       DTGE
DTG%    37665   965.7692    4       DTGE
%VGNE   34234   684.68  5       SVGNE
SVGN%   34281   634.8333    6       SVGNE
SV%NE   34248   634.2222    7       SVGNE
SVG%E   34265   623 8       SVGNE
%LGNE   41098   595.6232    9       SLGNE
SL%NE   41086   595.4493    10      SLGNE
SLGN%   41200   564.3836    11      SLGNE
SPT%AYNE    35082   539.7231    12      SPTAAYNE
SP%AAYNE    35094   531.7273    13      SPTAAYNE
SPTA%YNE    35061   531.2273    14      SPTAAYNE
SPTAA%NE    35225   518.0147    15      SPTAAYNE
SPTAAYN%    35144   516.8235    16      SPTAAYNE
%PTAAYNE    35111   516.3382    17      SPTAAYNE
S%TAAYNE    35100   516.1765    18      SPTAAYNE
SPTAAY%E    35130   509.1304    19      SPTAAYNE
SLG%E   41467   450.7283    20      SLGNE

莫里兹的答案

df %>%
    mutate(grp = cutree(hclust(stringdistmatrix(df$pattern, method = "qgram")), h = 2)) %>%
    group_by(grp) %>%
    mutate(true_string = get_consensus_string(pattern)) %>%
    ungroup()
> Result 
  pattern   Freq  score  rank   grp true_string
 1 DT%E     37568  1138.     1     1 DT%T       
 2 D%GT     12434 12421     22     1 DT%T       
 3 DX%T     31242  2221.    21     1 DT%T       
 4 %TGE     37666  1018      2     2 %TGE       
 5 D%GE     37641  1017.     3     2 %TGE       
 6 DTG%     37665   966.     4     1 DT%T       
 7 %VGNE    34234   685.     5     3 SVGNE      
 8 SVGN%    34281   635.     6     3 SVGNE      
 9 SV%NE    34248   634.     7     3 SVGNE      
10 SVG%E    34265   623      8     3 SVGNE      
11 %LGNE    41098   596.     9     4 SLGNE      
12 SL%NE    41086   595.    10     4 SLGNE      
13 SLGN%    41200   564.    11     4 SLGNE      
14 SPT%AYNE 35082   540.    12     5 SPTAAYNE   
15 SP%AAYNE 35094   532.    13     5 SPTAAYNE   
16 SPTA%YNE 35061   531.    14     5 SPTAAYNE   
17 SPTAA%NE 35225   518.    15     5 SPTAAYNE   
18 SPTAAYN% 35144   517.    16     5 SPTAAYNE   
19 %PTAAYNE 35111   516.    17     5 SPTAAYNE   
20 S%TAAYNE 35100   516.    18     5 SPTAAYNE   
21 SPTAAY%E 35130   509.    19     5 SPTAAYNE   
22 SLG%E    41467   451.    20     4 SLGNE   

从以上结果来看,它不起作用。

我的答案

library(dplyr)
library(data.table)

df <- fread(data)

string_pred <- function(x){

  x = x %>% mutate(CL=nchar(pattern)) 

  x_1 = x%>% select(pattern,CL)
  Chr.length = unique(x_1$CL)
  final_result = NULL
  for ( len in 1:length(Chr.length)){ 
    x_1_tmp = x %>% filter(CL==Chr.length[len])

    RESULT = NULL
    for(i in 1:Chr.length[len]){

      TMP = substr(x_1_tmp$pattern,i,i)
      TMP_GUESS = unique(TMP[!grepl("%",TMP)])
      if(length(TMP_GUESS)==1){
        TMP[grepl("%",TMP)] <- TMP_GUESS  
      } else {
        TMP= TMP
      }
      NAME = sprintf('P%s',i)

      RESULT = cbind(RESULT, NAME=TMP) %>% as.data.table()
      names(RESULT)[i] = eval(parse(text='NAME'))
    }
    material = RESULT %>% rowwise() %>% .[apply(.,1,function(x){'%' %in% x}) ,]
    if (nrow(material)==0){
      x_1_tmp =x_1_tmp %>%  mutate( pred = apply(RESULT,1,function(x)paste(as.character(x),collapse = ''))) %>% as.data.table()
    } else {
      mat.loc = RESULT %>% rowwise() %>%apply(.,1,function(x){'%' %in% x}) %>% which(unlist(.)==TRUE)

      for (i in 1:nrow(material)){
        ori.loc = mat.loc[i]
        loc = names(material[i,])[material[i,]=='%']
        tmp = material[i,] %>% dplyr::select(-loc)
        RESULT[ori.loc,] = RESULT %>% rowwise()  %>% inner_join(., tmp) %>% .[apply(.,1,function(x){!('%' %in% x)}) ,] %>% unique()

      }
      x_1_tmp = x_1_tmp %>%mutate( pred = apply(RESULT,1,function(x)paste(as.character(x),collapse = ''))) %>% as.data.table()
    }
    final_result = rbind(final_result, x_1_tmp)
  }
  return(final_result)
}

我的答辩结果

> string_pred(df)

     pattern  Freq      score rank CL     pred
 1:     DT%E 37568  1138.4242    1  4     DTGE
 2:     D%GT 12434 12421.0000   22  4     DXGT
 3:     DX%T 31242  2221.2000   21  4     DXGT
 4:     %TGE 37666  1018.0000    2  4     DTGE
 5:     D%GE 37641  1017.3243    3  4     DTGE
 6:     DTG% 37665   965.7692    4  4     DTGE
 7:    %VGNE 34234   684.6800    5  5    SVGNE
 8:    SVGN% 34281   634.8333    6  5    SVGNE
 9:    SV%NE 34248   634.2222    7  5    SVGNE
10:    SVG%E 34265   623.0000    8  5    SVGNE
11:    %LGNE 41098   595.6232    9  5    SLGNE
12:    SL%NE 41086   595.4493   10  5    SLGNE
13:    SLGN% 41200   564.3836   11  5    SLGNE
14:    SLG%E 41467   450.7283   20  5    SLGNE
15: SPT%AYNE 35082   539.7231   12  8 SPTAAYNE
16: SP%AAYNE 35094   531.7273   13  8 SPTAAYNE
17: SPTA%YNE 35061   531.2273   14  8 SPTAAYNE
18: SPTAA%NE 35225   518.0147   15  8 SPTAAYNE
19: SPTAAYN% 35144   516.8235   16  8 SPTAAYNE
20: %PTAAYNE 35111   516.3382   17  8 SPTAAYNE
21: S%TAAYNE 35100   516.1765   18  8 SPTAAYNE
22: SPTAAY%E 35130   509.1304   19  8 SPTAAYNE

方法

  1. 按每个图案的字符长度分隔
  pattern  Freq      score rank CL
1    DT%E 37568  1138.4242    1  4
2    D%GT 12434 12421.0000   22  4
3    DX%T 31242  2221.2000   21  4
4    %TGE 37666  1018.0000    2  4
5    D%GE 37641  1017.3243    3  4
6    DTG% 37665   965.7692    4  4
  1. 逐个检查每个字符。
 TMP = substr(x_1_tmp$pattern,i,i)
[1] "D" "D" "D" "%" "D" "D"
  1. 如果unique(pattern[i] except % ) == 1->我们将%分配为unique(pattern[i] except % )
   P1 P2 P3 P4
1:  D  T  G  E
2:  D  %  G  T
3:  D  X  G  T
4:  D  T  G  E
5:  D  %  G  E
6:  D  T  G  %
  1. unique(pattern[i] except % ) > 1我们检查字符长度组中的另一行。然后我们将字符(%列除外)合并为其他字符。
RESULT[ori.loc,] = RESULT %>% rowwise()  %>% 
                      inner_join(., tmp) %>%
                       .[apply(.,1,function(x){!('%' %in% x)}) ,] %>% unique()
>print
Joining, by = c("P1", "P3", "P4")
Source: local data frame [1 x 4]
Groups: <by row>

# A tibble: 1 x 4
  P1    P2    P3    P4   
  <chr> <chr> <chr> <chr>
1 D     X     G     T    
  1. 最后我们可以预测%是什么
   pattern  Freq      score rank CL pred
1:    DT%E 37568  1138.4242    1  4 DTGE
2:    D%GT 12434 12421.0000   22  4 DXGT
3:    DX%T 31242  2221.2000   21  4 DXGT
4:    %TGE 37666  1018.0000    2  4 DTGE
5:    D%GE 37641  1017.3243    3  4 DTGE
6:    DTG% 37665   965.7692    4  4 DTGE

我的回答看起来并不花哨,但确实有效。

我建议您一一遵循代码