Как объединить похожие строки, показывающие наиболее распространенные символы - PullRequest
6 голосов
/ 14 мая 2019

В кадре данных у меня есть список строк, которые похожи друг на друга, но разделены разницей в%.Я хотел бы объединить эти общие строки в одну строку с наиболее распространенным символом в каждом месте.

Фрейм данных выглядит так:

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

Я пытаюсь добавить еще один столбецс наиболее вероятной строкой из столбца шаблона

pattern  Freq     score rank  true_string
DT%E 37568 1138.4242    1  DTGE
%TGE 37666 1018.0000    2  DTGE
D%GE 37641 1017.3243    3  DTGE
DTG% 37665  965.7692    4  DTGE
%VGNE 34234  684.6800    5  SVGNE
SVGN% 34281  634.8333    6  SVGNE
SV%NE 34248  634.2222    7  SVGNE
SVG%E 34265  623.0000    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

Ответы [ 2 ]

3 голосов
/ 14 мая 2019

Это сложный, но интересный вопрос.

Вот кое-что, что должно дать вам некоторые идеи (и воспроизвести ваш ожидаемый результат); обратите внимание, однако, что это несколько эмпирический подход, который делает следующие предположения:

  1. Всегда есть >=2 паттернов, принадлежащих одному и тому же true_string; это необходимо для работы (иерархического) метода кластеризации (см. ниже). Если у вас есть <2 шаблоны, определяющие true_string, это не сработает, что имеет смысл, потому что у вас будут одинаковые частоты для двух символов в одной и той же позиции.

  2. Все pattern имеют одинаковую длину ; т.е. мы рассматриваем только замены одного символа, но не вставки / удаления.

1024 * подход * Мы используем библиотеку stringdist для вычисления сходства строк. stringdistmatrix предлагает различные метрики расстояния (Левенштейн, Хэмминг, ..., подробности см. ?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.

В более технических терминах высота дендрограммы связана с расстоянием между группами с использованием полного сцепления (то есть измерения расстояния на основе самых разнородных пар). Поскольку расстояние между группами в свою очередь основано на q-граммовых расстояниях между pattern с, можно связать высоту обратно с q-граммным расстоянием между двумя pattern с, то есть абсолютной разностью между N- грамм-векторы обоих pattern с.

2 голосов
/ 14 мая 2019

Я проверил ответ Маврикия, но когда добавил новую строку.

Новый Ввод

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
Осматривайте каждый персонаж по одному.
 TMP = substr(x_1_tmp$pattern,i,i)
[1] "D" "D" "D" "%" "D" "D"
Если 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  %
unique(pattern[i] except % ) > 1 мы проверяем другую строку в группе длин символов.И мы слили символ (кроме% column) в другой символ.
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    
Наконец, мы можем предсказать, что % является
   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

Мой ответ не выглядит фантастическим, но он работает ..

Я рекомендую вам просто следоватькод один за другим

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...