separar - manejo de expresiones regulares
Creación de una nueva expresión regular basada en los resultados devueltos y las reglas de una expresión regular anterior | Indexando una expresión regular y viendo cómo la expresión regular ha coincidido con una subcadena (4)
Estoy particularmente mirando a R, Perl y shell. Pero cualquier otro lenguaje de programación también estaría bien.
PREGUNTA
¿Hay alguna forma de inspeccionar e indexar visual o programáticamente una cadena coincidente en función de la expresión regular? Esto está destinado a hacer referencia a la primera expresión regular y sus resultados dentro de una segunda expresión regular, para poder modificar una parte de la cadena coincidente y escribir nuevas reglas para esa parte en particular.
https://regex101.com visualiza cómo una determinada cadena coincide con la expresión regular. Pero está lejos de ser perfecto y no es eficiente para mi gran conjunto de datos.
PROBLEMA
Tengo alrededor de 12000 cadenas emparejadas (secuencias de ADN) para mi primera expresión regular, y quiero procesar estas cadenas y, en base a algunas reglas estrictas, encuentro otras cadenas en un segundo archivo que van bien junto con esas 12000 coincidencias basadas en esas reglas estrictas.
EJEMPLO SIMPLIFICADO
Este es mi primer regex (una versión simplificada y más corta de mi regex original) que se ejecuta en mi primer archivo de texto .
[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)
Supongamos que encuentra las siguientes tres sub-cadenas en mi archivo de texto grande:
1. AAACCCGTGTAATAACAGACGTACTGTGTA
2. TTTTTTTGCGACCGAGAAACGGTTCTGTGTA
3. TAACAAGGACCCTGTGTA
Ahora tengo un segundo archivo que incluye una cadena muy grande. A partir de este segundo archivo, solo me interesa extraer aquellas sub-cadenas que coincidan con una nueva (segunda) expresión regular que, a su vez, depende de mi primera expresión regular en algunas secciones . Por lo tanto, esta segunda expresión regular debe tener en cuenta las subcadenas coincidentes en el primer archivo y ver cómo se han combinado con la primera expresión regular.
Permítame, en aras de la simplicidad, indexar mi primera expresión regular para una mejor ilustración de esta manera:
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)
Ahora mi segunda (nueva) expresión regular que buscará el segundo archivo de texto y dependerá de los resultados de la primera expresión regular (y de cómo las subcadenas devueltas desde el primer archivo coincidieron con la primera expresión regular) se definirá de la siguiente manera:
second.regex = (CTAAA)[AC]{5,100}(TTTGGG){**rule1**} (CTT)[AG]{10,5000}{**rule2**}
Aquí, la regla 1 y la regla 2 dependen de las coincidencias que provienen de la primera expresión regular en el primer archivo. Por lo tanto;
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)
Puede ver que la segunda expresión regular tiene secciones que pertenecen a sí misma (es decir, son independientes de cualquier otro archivo / expresión regular), pero también tiene secciones que dependen de los resultados del primer archivo y las reglas de la primera expresión regular y cómo cada una ¡la subcadena en el primer archivo ha coincidido con la primera expresión regular!
Ahora, nuevamente, por simplicidad, uso la tercera subcadena coincidente del archivo1 (porque es más corta que las otras dos) para mostrarle cómo se ve una posible coincidencia con el segundo archivo y cómo satisface la segunda expresión regular:
Esto es lo que tuvimos desde nuestro primer regex ejecutado hasta el primer archivo:
3. TAACAAGGACCCTGTGTA
Así que en este partido, vemos que:
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
Ahora, en nuestra segunda regex para el segundo archivo, vemos que cuando buscamos una subcadena que coincida con la segunda regex, dependemos de los resultados del primer archivo (que coinciden con la primera regex). Particularmente, debemos observar las subcadenas coincidentes y complementar las partes que coincidieron con first.regex.p3 y first.regex.p4 ( rule1 and rule2 from second.regex).
complement means:
A will be substituted by T
T -> A
G -> C
C -> G
Así que si tienes TAAA, el complemento será ATTT.
Por lo tanto, volviendo a este ejemplo:
- TAACAAGGACCCTGTGTA
Necesitamos complementar lo siguiente para satisfacer los requisitos de la segunda expresión regular:
AAGGA has matched first.regex.p3
CC first.regex.p4
Y los complementos son:
TTCCT (based on rule1)
GG (based on rule2)
Así que un ejemplo de una subcadena que coincide con second.regex es este:
CTAAAACACCTTTGGG TTCCT CTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG GG
¡Este es sólo un ejemplo! Pero en mi caso tengo 12000 subcadenas emparejadas !! No puedo averiguar cómo abordar este problema. He intentado escribir expresiones regex puras, pero no he implementado nada que siga correctamente esta lógica ... Tal vez no debería usar Regex.
¿Es posible hacer esto completamente con expresiones regulares? ¿O debería mirar otro enfoque? ¿Es posible indexar una expresión regular y en la segunda referencia de expresiones regulares a la primera expresión regular y obligar a la expresión regular a considerar las subcadenas coincidentes como devueltas por la primera expresión regular?
Esta pregunta realmente nos recuerda el viejo dicho acerca de las expresiones regulares , aunque en este caso los idiomas con los que te relacionas son regulares, por lo que RE es una buena opción para esto.
Desafortunadamente, a mi Perl le falta algo, pero fundamentalmente esto suena como un problema de Regex en lugar de un problema de R o de Perl, así que haré todo lo posible para responderlo sobre esa base.
El motor de expresiones regulares de Perl admite grupos de captura. Las subcadenas que coinciden con las subexpresiones entre corchetes en su expresión regular pueden estar disponibles después de la comparación:
use feature qw(say);
$foo = ''foo'';
''aaa'' =~ /(a)(a+)/;
say($1); # => ''a''
say($2); # => ''aa''
say("Matched!") if ''aaaa'' =~ /${2}/;
Lo que sugeriría hacer es poner en paréntesis su expresión regular correctamente, separar los grupos de captura después de hacer coincidir y luego unirlos en una nueva expresión regular, digamos ...
use feature qw(say);
''ACGTAACAGAGATCTGTGTA'' =~ /([ACGT]{1,12000})(AAC)([AG]{2,5})([ACGT]{2,5})(CTGTGTA)/ ; # Note that I''ve added a lot of (s and )s here so that the results get sorted into nice groups
say($1); # => ''ACGT''
say($2); # => ''AAC''
say($3); # => ''AGAG''
say($4); # => ''AT''
say($5); # => ''CTGTGTA''
$complemented_3 = complement($3); # You can probably implement these yourself...
$complemented_4 = complement($4);
$new_regex = /${complemented_3}[ACGT]+${complemented_4}/;
Si las secciones tienen un significado real, también recomendaría buscar grupos de captura con nombre y dar a los resultados nombres decentes en lugar de $1, $2, $3...
Esto se puede hacer mediante programación en Perl, o en cualquier otro idioma.
Ya que necesita la entrada de dos archivos diferentes, no puede hacer esto en expresiones regulares puras, ya que las expresiones regulares no pueden leer archivos. Ni siquiera puede hacerlo en un patrón, ya que ningún motor de expresiones regulares recuerda lo que hizo antes en una cadena de entrada diferente. Se debe hacer en el programa que rodea a tus partidos, que debería ser regex, ya que es para lo que se hace regex.
Puedes construir el segundo patrón paso a paso. He implementado una versión más avanzada en Perl que se puede adaptar fácilmente para adaptarse a otras combinaciones de patrones, sin cambiar el código real que hace el trabajo.
En lugar del archivo 1, DATA
sección de DATA
. Contiene las tres cadenas de entrada de ejemplo. En lugar del archivo 2, uso la salida de ejemplo para la tercera cadena de entrada.
La idea principal detrás de esto es dividir ambos patrones en sub-patrones. Para el primero, simplemente podemos usar una serie de patrones. Para el segundo, creamos funciones anónimas a las que llamaremos con los resultados de coincidencia del primer patrón para construir el segundo patrón completo. La mayoría de ellos simplemente devuelven una cadena fija, pero dos toman realmente un valor de los argumentos para construir los complementos.
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
Estos son los segundos patrones generados para sus tres subcadenas de entrada.
((?^: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)
Si no está familiarizado con esto, es lo que sucede si print
un patrón que se construyó con el operador de qr//
regulares qr//
.
El patrón coincide con su salida de ejemplo para el tercer caso. El resultado de @matches2
ve así cuando se descarga usando Data::Printer .
[
[0] "CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG",
[1] "CTAAA",
[2] "ACACC",
[3] "TTTGGG",
[4] "TTCCT",
[5] "CTT",
[6] "AAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAG",
[7] "GG"
]
No puedo decir nada sobre la velocidad de esta implementación, pero creo que será razonablemente rápido.
Si quería encontrar otras combinaciones de patrones, todo lo que tenía que hacer era reemplazar las entradas sub { ... }
en esos dos arreglos. Si hay un número diferente de cinco de ellos para la primera coincidencia, también construirá ese patrón mediante programación. No he hecho eso arriba para simplificar las cosas. Así es como se vería.
my @matches = ( $file1 =~ join q{}, map { "($_)" } @first);
Si desea obtener más información sobre este tipo de estrategia, le sugiero que lea el excelente Perl de Orden Superior de Mark Jason Dominus, que está disponible de forma gratuita aquí en formato PDF .
Usando stringr en R
Extraiga coincidencias con 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)
Supongamos que los partidos fueron:
reg_1_matches = c("TTTTTTTGCGACCGAGAAACGGTTCTGTGTA", "TAACAAGGACCCTGTGTA")
Utilice stringr :: str_match con grupos de captura (...)
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]
Complemento
rule_1 = chartr(old= "ACGT", "TGCA", p3)
rule_2 = chartr(old= "ACGT", "TGCA", p4)
Construir regex_2
paste("(CTAAA)[AC]{5,100}(TTTGGG)", rule_1, "(CTT)[AG]{10,5000}", rule_2, sep="")
todo de una vez
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="")
solución awk . Los requisitos no son tan complicados: un simple script puede hacer el truco. Solo hay una complicación: cada expresión regular que sea el resultado de su primera coincidencia debe coincidir con todas las líneas del segundo archivo. Aquí es donde usamos xargs para resolver eso.
Ahora, sea cual sea el idioma que elija, parece que la cantidad de coincidencias que se van a realizar será extensa, por lo que primero deben hacerse algunos comentarios sobre las expresiones regulares.
La expresión regular para el primer archivo será lenta, porque en
[ACGT]{1,12000}(AAC)[AG]{2,5}[ACGT]{2,5}(CTGTGTA)
El número de posibilidades para la primera parte [AGCT]{1,12000}
es enorme. En realidad, solo dice elegir cualquier elemento de A, C, G, T y hasta entre 1 y 12000 veces. Luego empareja el resto. No podríamos hacer un
AAC([AG]{2,5})([ACGT]{2,5})CTGTGTA$
¿en lugar? La ganancia de velocidad es considerable.
Se puede hacer un comentario similar a la expresión regular para el segundo archivo. Si reemplazas
(CTAAA)[AC]{5,100}(TTTGGG){**rule1**}(CTT)[AG]{10,5000}{**rule2**}
con
(CTAAA)[AC]{5,100}(TTTGGG){**rule1**}(CTT)[AG]*{**rule2**}$
Experimentarás alguna mejora.
Como comencé esta respuesta con el bajo factor de complicación de los requisitos, veamos un código:
$ 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
}
Lo que esto hará es producir la expresión regular para su segundo archivo. (He añadido agrupación extra para fines de demostración). Ahora, echemos un vistazo al segundo script:
$ cat tst2.awk
match($0, regex, a){ printf("Found matches %s and %s/n", a[3], a[5]) }
Lo que esto hará es obtener una regex
y hacerlo coincidir con cada línea leída del segundo archivo de entrada. Necesitamos proporcionar a este script un valor para regex
, como esto:
$ awk -f tst.awk input1.txt | xargs -I {} -n 1 awk -v regex={} -f tst2.awk input2.txt
La opción -v de awk nos permite definir una expresión regular, que se alimenta en esta llamada por el primer script.
$ cat input1.txt
TAACAAGGACCCTGTGTA
$ cat input2.txt
CTAAAACACCTTTGGGTTCCTCTTAAAAAAAAAGGGGGAGAGAGAAGAAAAAAAGAGAGGG
y el resultado es:
$ awk -f tst.awk input1.txt | xargs -I {} -n 1 awk -v regex={} -f tst2.awk input2.txt
Found matches TTCCT and GG
En conclusión: ¿debería usar expresiones regulares para resolver su problema? Sí, pero no debes ser demasiado ambicioso para que coincida con toda la cadena de una sola vez. Los cuantificadores como {1,12000} lo harán más lento, sea cual sea el idioma que elija.