grep usando un vector de caracteres con múltiples patrones


132

Estoy tratando de usar greppara probar si un vector de cadenas está presente en otro vector o no, y generar los valores que están presentes (los patrones coincidentes).

Tengo un marco de datos como este:

FirstName Letter   
Alex      A1
Alex      A6
Alex      A7
Bob       A1
Chris     A9
Chris     A6

Tengo un vector de patrones de secuencias que se encuentran en las columnas "letra", por ejemplo: c("A1", "A9", "A6").

Me gustaría comprobar si alguna de las cadenas en el vector de patrón está presente en la columna "Letra". Si lo son, me gustaría la salida de valores únicos.

El problema es que no sé cómo usarlo grepcon múltiples patrones. Lo intenté:

matches <- unique (
    grep("A1| A9 | A6", myfile$Letter, value=TRUE, fixed=TRUE)
)

Pero me da 0 coincidencias, lo cual no es cierto, ¿alguna sugerencia?


3
No puede usar fixed=TRUEporque su patrón es una verdadera expresión regular.
Marek

66
Usar matcho %in%incluso ==es la única forma correcta de comparar coincidencias exactas. regex es muy peligroso para tal tarea y puede conducir a resultados inesperados.
David Arenburg

Respuestas:


269

Además del comentario de @ Marek sobre no incluir fixed==TRUE, también debe no tener los espacios en su expresión regular. Debería ser "A1|A9|A6".

También mencionas que hay muchos patrones. Asumiendo que están en un vector

toMatch <- c("A1", "A9", "A6")

Luego puede crear su expresión regular directamente usando pastey collapse = "|".

matches <- unique (grep(paste(toMatch,collapse="|"), 
                        myfile$Letter, value=TRUE))

¿Alguna forma de hacer esto cuando su lista de cadenas incluye operadores de expresiones regulares como puntuación?
user124123

@ user1987097 Debería funcionar de la misma manera, con o sin otros operadores de expresiones regulares. ¿Tuviste un ejemplo específico para el que esto no funcionó?
Brian Diggs

@ user1987097 usa 2 barras invertidas antes de un punto o corchete. La primera barra invertida es un carácter de escape para interpretar el segundo necesario para desactivar el operador.
mbh86

3
Usar expresiones regulares para coincidencias exactas me parece peligroso y puede tener resultados inesperados. ¿Por qué no solo toMatch %in% myfile$Letter?
David Arenburg

@ user4050 No hay razón específica. La versión en la pregunta lo tenía y probablemente solo lo llevé a cabo sin pensar si era necesario.
Brian Diggs

34

Buenas respuestas, sin embargo, no te olvides filter()de dplyr:

patterns <- c("A1", "A9", "A6")
>your_df
  FirstName Letter
1      Alex     A1
2      Alex     A6
3      Alex     A7
4       Bob     A1
5     Chris     A9
6     Chris     A6

result <- filter(your_df, grepl(paste(patterns, collapse="|"), Letter))

>result
  FirstName Letter
1      Alex     A1
2      Alex     A6
3       Bob     A1
4     Chris     A9
5     Chris     A6

3
Creo que greplfunciona con un patrón en ese momento (necesitamos un vector con longitud 1), tenemos 3 patrones (vector de longitud 3), por lo que podemos combinarlos con uno usando algunos amigables para el separador grepl |, pruebe su suerte con otros :)
Adamm

3
oh Ahora lo entiendo. Entonces, es una forma comprimida de generar algo como A1 | A2 así que si uno quisiera todas las condiciones, entonces el colapso sería con un signo &, genial gracias.
Ahdee

1
Hola, utilizando )|(a los patrones separados podría hacer esto más robusta: paste0("(", paste(patterns, collapse=")|("),")"). Desafortunadamente, también se vuelve un poco menos elegante. Esto da como resultado un patrón (A1)|(A9)|(A6).
fabern

14

Esto debería funcionar:

grep(pattern = 'A1|A9|A6', x = myfile$Letter)

O incluso más simplemente:

library(data.table)
myfile$Letter %like% 'A1|A9|A6'

11
%like%no está en la base R, por lo que debe mencionar qué paquete (s) se necesitan para usarlo.
Gregor Thomas el

1
Para otros que miran esta respuesta, %like%es parte del data.tablepaquete. También en similares data.tableson like(...), %ilike%y %flike%.
SteveB

8

Según la publicación de Brian Digg, aquí hay dos funciones útiles para filtrar listas:

#Returns all items in a list that are not contained in toMatch
#toMatch can be a single item or a list of items
exclude <- function (theList, toMatch){
  return(setdiff(theList,include(theList,toMatch)))
}

#Returns all items in a list that ARE contained in toMatch
#toMatch can be a single item or a list of items
include <- function (theList, toMatch){
  matches <- unique (grep(paste(toMatch,collapse="|"), 
                          theList, value=TRUE))
  return(matches)
}

5

¿Has probado las funciones match()o charmatch()?

Ejemplo de uso:

match(c("A1", "A9", "A6"), myfile$Letter)

1
Una cosa a tener en cuenta matches que no está usando patrones, está esperando una coincidencia exacta.
SteveB

5

No estoy seguro de si esta respuesta ya apareció ...

Para el patrón particular en la pregunta, puede hacerlo con una sola grep()llamada,

grep("A[169]", myfile$Letter)

4

Para agregar a Brian Diggs, responda.

Otra forma de utilizar grepl devolverá un marco de datos que contiene todos sus valores.

toMatch <- myfile$Letter

matches <- myfile[grepl(paste(toMatch, collapse="|"), myfile$Letter), ]

matches

Letter Firstname
1     A1      Alex 
2     A6      Alex 
4     A1       Bob 
5     A9     Chris 
6     A6     Chris

Tal vez un poco más limpio ... tal vez?


2

Quita los espacios. Entonces hazlo:

matches <- unique(grep("A1|A9|A6", myfile$Letter, value=TRUE, fixed=TRUE))

1

Utilizando la sapply

 patterns <- c("A1", "A9", "A6")
         df <- data.frame(name=c("A","Ale","Al","lex","x"),Letters=c("A1","A2","A9","A1","A9"))



   name Letters
1    A      A1
2  Ale      A2
3   Al      A9
4  lex      A1
5    x      A9


 df[unlist(sapply(patterns, grep, df$Letters, USE.NAMES = F)), ]
  name Letters
1    A      A1
4  lex      A1
3   Al      A9
5    x      A9

-1

Sugiero escribir un pequeño guión y hacer múltiples búsquedas con Grep. Nunca he encontrado una manera de buscar múltiples patrones, y créeme, ¡he buscado!

Así, su archivo de shell, con una cadena incrustada:

 #!/bin/bash 
 grep *A6* "Alex A1 Alex A6 Alex A7 Bob A1 Chris A9 Chris A6";
 grep *A7* "Alex A1 Alex A6 Alex A7 Bob A1 Chris A9 Chris A6";
 grep *A8* "Alex A1 Alex A6 Alex A7 Bob A1 Chris A9 Chris A6";

Luego ejecute escribiendo myshell.sh.

Si desea poder pasar la cadena en la línea de comando, hágalo así, con un argumento de shell: esta es la notación bash por cierto:

 #!/bin/bash 
 $stingtomatch = "${1}";
 grep *A6* "${stingtomatch}";
 grep *A7* "${stingtomatch}";
 grep *A8* "${stingtomatch}";

Etcétera.

Si hay muchos patrones para hacer coincidir, puedes ponerlo en un bucle for.


Gracias ChrisBean Los patrones son muchos en realidad, y tal vez sería mejor usar un archivo entonces. Soy nuevo en BASH, pero tal vez algo como esto debería funcionar ... #! / Bin / bash para i en 'pattern.txt' do echo $ ij = 'grep -c "$ {i}" myfile.txt' echo $ j si [$ j -eq o] entonces echo $ i >> coincide con .txt fi hecho
usuario971102

no funciona ... el mensaje de error es '[grep: comando no encontrado' ... Tengo grep en la carpeta / bin, y / bin está en mi $ PATH ... No estoy seguro de lo que está sucediendo ... ¿Pueden ayudarme?
user971102
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.