чайников - язык r регулярные выражения




Создание нового регулярного выражения на основе возвращенных результатов и правил предыдущего регулярного выражения | Индексирование регулярного выражения и просмотр соответствия регулярного выражения подстроке (3)

Я особенно смотрю на R, Perl и shell. Но любой другой язык программирования тоже подойдет.

ВОПРОС

Есть ли способ визуальной или программной проверки и индексации совпадающей строки на основе регулярного выражения? Это предназначено для обращения к первому регулярному выражению и его результатам внутри второго регулярного выражения, чтобы иметь возможность изменять часть сопоставляемой строки и писать новые правила для этой конкретной части.

https://regex101.com действительно визуализирует соответствие определенной строки регулярному выражению. Но это далеко от совершенства и не эффективно для моего огромного набора данных.

ПРОБЛЕМА

У меня есть около 12000 совпадающих строк (последовательностей ДНК) для моего первого регулярного выражения, и я хочу обработать эти строки и, основываясь на некоторых строгих правилах, найти другие строки во втором файле, которые хорошо сочетаются с этими 12000 соответствиями на основе этих строгих правил.

Упрощенный пример

Это мое первое регулярное выражение (упрощенная, более короткая версия моего исходного регулярного выражения), которое проходит через мой первый текстовый файл .

[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)

Давайте предположим, что он находит следующие три подстроки в моем большом текстовом файле:

1. AAACCCGTGTAATAACAGACGTACTGTGTA
2. TTTTTTTGCGACCGAGAAACGGTTCTGTGTA
3. TAACAAGGACCCTGTGTA

Теперь у меня есть второй файл, который включает в себя очень большую строку. Из этого второго файла меня интересует только извлечение тех подстрок, которые соответствуют новому (второму) регулярному выражению, которое само зависит от моего первого регулярного выражения в нескольких разделах . Следовательно, это второе регулярное выражение должно учитывать подстроки, совпадающие в первом файле, и смотреть, как они соответствуют первому регулярному выражению!

Позвольте мне для простоты проиндексировать мое первое регулярное выражение для лучшей иллюстрации следующим образом:

first.regex.p1 = [ACGT]{1,12000}
first.regex.p2 = (AAC)
first.regex.p3 = [AG]{2,5}
first.regex.p4 = [ACGT]{2,5}
first.regex.p5 = (CTGTGTA)

Теперь мое второе (новое) регулярное выражение, которое будет искать второй текстовый файл и будет зависеть от результатов первого регулярного выражения (и того, как подстроки, возвращенные из первого файла, соответствуют первому регулярному выражению), будет определено следующим образом:

second.regex = (CTAAA)[AC]{5,100}(TTTGGG){**rule1**} (CTT)[AG]{10,5000}{**rule2**}

Здесь rule1 и rule2 зависят от совпадений, поступающих от первого регулярного выражения в первом файле. Следовательно,

rule1 = look at the matched strings from file1 and complement the pattern of first.regex.p3 that is found in the matched substring from file1 (the complement should of course have the same length)
rule2 = look at the matched strings from file1 and complement the pattern of first.regex.p4 that is found in the matched substring from file1 (the complement should of course have the same length)

Вы можете видеть, что второе регулярное выражение имеет разделы, которые принадлежат ему (то есть они не зависят от любого другого файла / регулярного выражения), но оно также имеет разделы, которые зависят от результатов первого файла и правил первого регулярного выражения и того, как каждый Подстрока в первом файле соответствует первому регулярному выражению!

Опять же, для простоты, я использую третью подходящую подстроку из file1 (потому что она короче двух других), чтобы показать вам, как выглядит возможное совпадение из второго файла и как оно удовлетворяет второму регулярному выражению:

Это то, что мы использовали в нашем первом регулярном выражении для первого файла:

3. TAACAAGGACCCTGTGTA

Итак, в этом матче мы видим, что:

T has matched first.regex.p1
AAC has matched first.regex.p2
AAGGA has matched first.regex.p3
CC first.regex.p4
CTGTGTA has matched first.regex.p5

Теперь во втором регулярном выражении для второго файла мы видим, что при поиске подстроки, соответствующей второму регулярному выражению, мы зависим от результатов, поступающих из первого файла (которые соответствуют первому регулярному выражению). В частности, нам нужно взглянуть на совпавшие подстроки и дополнить части, которые соответствуют first.regex.p3 и first.regex.p4 ( rule1 и rule2 из second.regex).

complement means:
A will be substituted by T
T -> A
G -> C
C -> G

Так что, если у вас есть TAAA, дополнением будет ATTT.

Поэтому, возвращаясь к этому примеру:

  1. TAACAAGGACCCTGTGTA

Нам нужно дополнить следующее, чтобы удовлетворить требования второго регулярного выражения:

AAGGA has matched first.regex.p3
CC first.regex.p4

И дополнения являются:

TTCCT (based on rule1)
GG (based on rule2)

Итак, пример подстроки, которая соответствует second.regex:

CTAAAACACCTTTGGG TTCCT CTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG GG

Это только один пример! Но в моем случае у меня 12000 подходящих подстрок! Я не могу понять, как даже подойти к этой проблеме. Я пытался написать чистое регулярное выражение, но мне не удалось реализовать что-либо, что должным образом следует этой логике. Возможно, мне даже не следует использовать регулярное выражение?

Можно ли сделать это полностью с помощью регулярных выражений? Или я должен смотреть на другой подход? Можно ли проиндексировать регулярное выражение и во второй ссылке на регулярное выражение вернуться к первому регулярному выражению и заставить регулярное выражение рассматривать соответствующие подстроки как возвращенные первым регулярным выражением?


Использование stringr в R

Извлечь совпадения с regex_1: "[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)"

reg_1_matches = stringr::str_extract_all(sequences, "[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)")
reg_1_matches = unlist(reg_1_matches)

давайте предположим, что совпадения были:

 reg_1_matches = c("TTTTTTTGCGACCGAGAAACGGTTCTGTGTA", "TAACAAGGACCCTGTGTA")

Используйте stringr :: str_match с группами захвата (...)

df_ps = stringr::str_match(reg_1_matches, "[ACGT]{1,12000}AAC([AG]{2,5})([ACGT]{2,5})CTGTGTA")

p3 = df_ps[,2]
p4 = df_ps[,3]

комплемент

rule_1 = chartr(old= "ACGT", "TGCA", p3)
rule_2 = chartr(old= "ACGT", "TGCA", p4)

Построить регулярное выражение_2

  paste("(CTAAA)[AC]{5,100}(TTTGGG)", rule_1, "(CTT)[AG]{10,5000}", rule_2, sep="") 

все за один раз

reg_1_matches =  stringr::str_extract_all(sequences, "[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)")
df_ps = stringr::str_match(reg_1_matches, "[ACGT]{1,12000}AAC([AG]{2,5})([ACGT]{2,5})CTGTGTA")
p3 = df_ps[,2]
p4 = df_ps[,3]
rule_1 = chartr(old= "ACGT", "TGCA", p3)
rule_2 = chartr(old= "ACGT", "TGCA", p4)
paste("(CTAAA)[AC]{5,100}(TTTGGG)", rule_1, "(CTT)[AG]{10,5000}", rule_2, sep="") 

Это можно сделать программно на Perl или любом другом языке.

Поскольку вам нужен ввод из двух разных файлов, вы не можете сделать это в чистом регулярном выражении, так как регулярное выражение не может читать файлы. Вы даже не можете сделать это в одном шаблоне, так как никакой движок регулярных выражений не запоминает то, что вы использовали ранее для другой входной строки. Это должно быть сделано в программе, окружающей ваши совпадения, которая очень хорошо должна быть регулярным выражением, поскольку именно для этого предназначено регулярное выражение.

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

Вместо файла 1 я буду использовать раздел DATA . Он содержит все три примера входных строк. Вместо файла 2 я использую ваш пример вывода для третьей входной строки.

Основная идея заключается в том, чтобы разделить оба шаблона на подэлементы. Для первого мы можем просто использовать массив шаблонов. Для второго мы создаем анонимные функции, которые мы будем вызывать с результатами сопоставления из первого шаблона, чтобы создать второй полный шаблон. Большинство из них просто возвращают фиксированную строку, но два фактически принимают значение из аргументов для построения дополнений.

use strict;
use warnings;

sub complement {
    my $string = shift;
    $string =~ tr/ATGC/TACG/; # this is a transliteration, faster than s///
    return $string;
}

# first regex, split into sub-patterns
my @first = ( 
    qr([ACGT]{1,12000}), 
    qr(AAC), 
    qr([AG]{2,5}), 
    qr([ACGT]{2,5}), 
    qr(CTGTGTA), 
);

# second regex, split into sub-patterns as callbacks
my @second = (
    sub { return qr(CTAAA) },
    sub { return qr([AC]{5,100}) },
    sub { return qr(TTTGGG) },
    sub {
        my (@matches) = @_;

        # complement the pattern of first.regex.p3
        return complement( $matches[3] );
    },
    sub { return qr(CTT) },
    sub { return qr([AG]{10,5000}) },
    sub {
        my (@matches) = @_;

        # complement the pattern of first.regex.p4
        return complement( $matches[4] );
    },
);

my $file2 = "CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG";

while ( my $file1 = <DATA> ) {

    # this pattern will match the full thing in $1, and each sub-section in $2, $3, ...
    # @matches will contain (full, $2, $3, $4, $5, $6)
    my @matches = ( $file1 =~ m/(($first[0])($first[1])($first[2])($first[3])($first[4]))/g );

    # iterate the list of anonymous functions and call each of them,
    # passing in the match results of the first match
    my $pattern2 = join q{}, map { '(' . $_->(@matches) . ')' } @second;

    my @matches2 = ( $file2 =~ m/($pattern2)/ );
}

__DATA__
AAACCCGTGTAATAACAGACGTACTGTGTA
TTTTTTTGCGACCGAGAAACGGTTCTGTGTA
TAACAAGGACCCTGTGTA

Это сгенерированные вторые шаблоны для ваших трех входных подстрок.

((?^:CTAAA))((?^:[AC]{5,100}))((?^:TTTGGG))(TCT)((?^:CTT))((?^:[AG]{10,5000}))(GCAT)
((?^:CTAAA))((?^:[AC]{5,100}))((?^:TTTGGG))(CC)((?^:CTT))((?^:[AG]{10,5000}))(AA)
((?^:CTAAA))((?^:[AC]{5,100}))((?^:TTTGGG))(TTCCT)((?^:CTT))((?^:[AG]{10,5000}))(GG)

Если вы не знакомы с этим, это то, что происходит, если вы print шаблон, который был создан с помощью оператора регулярных выражений в кавычках qr// .

Шаблон соответствует вашему примеру вывода для третьего случая. Получившийся @matches2 выглядит следующим образом при выгрузке с использованием Data::Printer .

[
    [0] "CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG",
    [1] "CTAAA",
    [2] "ACACC",
    [3] "TTTGGG",
    [4] "TTCCT",
    [5] "CTT",
    [6] "AAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG",
    [7] "GG"
]

Я ничего не могу сказать о скорости этой реализации, но я верю, что она будет достаточно быстрой.

Если вы хотите найти другие комбинации шаблонов, все, что вам нужно сделать, это заменить sub { ... } в этих двух массивах. Если для первого совпадения их число отличается от пяти, вы также создадите этот шаблон программно. Я не делал этого выше, чтобы все было проще. Вот как это будет выглядеть.

my @matches = ( $file1 =~ join q{}, map { "($_)" } @first);

Если вы хотите узнать больше об этом виде стратегии, я предлагаю вам прочитать превосходный Perl Марка Джейсона Доминуса высшего уровня , который можно бесплатно скачать в формате PDF здесь .


awk решение. Требования не так уж и сложны: простой скрипт может помочь. Есть только одно осложнение: каждое регулярное выражение, являющееся результатом вашего первого совпадения, должно сопоставляться со всеми строками второго файла. Здесь мы используем xargs для решения этой проблемы.

Теперь, независимо от того, какой язык вы выберете, похоже, что количество найденных совпадений будет очень большим, поэтому сначала нужно сделать несколько замечаний о регулярных выражениях.

Регулярное выражение для первого файла будет медленным, потому что в

[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)

количество возможностей для первой части [AGCT]{1,12000} огромно. На самом деле он говорит только о выборе любого элемента из A, C, G, T и тому от 1 до 12000 раз. Тогда сравните с остальными. Не могли бы мы сделать

AAC([AG]{2,5})([ACGT]{2,5})CTGTGTA$

вместо? Прирост скорости значителен.

Аналогичное замечание можно сделать для регулярного выражения для второго файла. Если вы замените

 (CTAAA)[AC]{5,100}(TTTGGG){**rule1**}(CTT)[AG]{10,5000}{**rule2**}

с

(CTAAA)[AC]{5,100}(TTTGGG){**rule1**}(CTT)[AG]*{**rule2**}$

Вы почувствуете некоторое улучшение.

Поскольку я начал этот ответ с низкого уровня сложности требований, давайте посмотрим код:

$ cat tst.awk
match($0, /AAC([AG]{2,5})([ACGT]{2,5})CTGTGTA$/, a) {
   r = sprintf("(CTAAA)[AC]{5,100}(TTTGGG)(%s)(CTT)[AG]*(%s)$", 
               translate(a[1]), 
               translate(a[2]));
   print r
}

function translate(word) {
   cmd = "echo '" word "' | tr 'ACGT' 'TGCA'";
   res = ((cmd | getline line) > 0 ? line : "");
   close(cmd);
   return res
}

Что это сделает, так это создаст регулярное выражение для вашего второго файла. (Я добавил дополнительную группировку для демонстрационных целей). Теперь давайте посмотрим на второй скрипт:

$ cat tst2.awk
match($0, regex, a){ printf("Found matches %s and %s\n", a[3], a[5]) }

Для этого нужно получить regex и сопоставить его с каждой строкой, прочитанной из второго входного файла. Нам нужно предоставить этому сценарию значение для regex , например так:

$ awk -f tst.awk input1.txt | xargs -I {} -n 1 awk -v regex={} -f tst2.awk input2.txt

Опция -v awk позволяет нам определить регулярное выражение, которое передается в этот вызов первым скриптом.

$ cat input1.txt
TAACAAGGACCCTGTGTA

$ cat input2.txt
CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG

и результат:

$ awk -f tst.awk input1.txt | xargs -I {} -n 1 awk -v regex={} -f tst2.awk input2.txt
Found matches TTCCT and GG

В заключение: вы должны использовать регулярные выражения для решения вашей проблемы? Да, но вы должны быть не слишком амбициозными, чтобы соответствовать целой строке за один раз. Квантификаторы, такие как {1,12000}, будут замедлять вас, на каком бы языке вы ни выбрали.





bioinformatics