Eu tenho uma coluna chamada “MATCH” em um dataframe e uma lista de padrões chamados “PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Quero usar o grepl para comparar a coluna MATCH com PATTERN, se verdadeiro, aplicarei minhas funções. O resultado desejado seria “ABC” corresponde a “ABC” e “ABC abc”. Este é o código que usei:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Recebo o erro:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I entenda que não posso usar grepl para uma lista de vetores. Existe alguma maneira de resolver isso?
Comentários
Resposta
TL; DR:
grepl
espera que seu primeiro argumento seja uma string (comprimento 1), não um vetor. Você pode resolver isso com combinações desapply
elapply
(veja abaixo), mas é melhor usar uma única expressão regular que captu res o que você deseja corresponder emdf1.MATCH
e não usardf2.PATTERN
de forma alguma. Esta segunda opção é muito mais rápida (se menos inteligente) para um grande conjunto de dados. Para este tipo de trabalho, vale a pena aprender a usar as expressões regulares em todo o seu potencial.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Explicação
A documentação de grepl
mostra o seguinte uso:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
O argumento pattern
é o primeiro, e este argumento deve ser uma string (um elemento). Você está fornecendo df1.MATCH
a este argumento, que é um vetor.
Poderíamos usar sapply
para aplicar grepl
para cada elemento de df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
No entanto, observe a saída! Você provavelmente não queria uma matriz. O que acontece quando executamos seu grepl
um apenas o primeiro elemento de df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Obtemos um vetor porque grepl
está verificando ABC
em relação a cada elemento de df2.PATTERN
. Para obter um vetor lógico útil para filtragem, você precisa retornar um vetor lógico do mesmo comprimento que df1.MATCH
. Vejo duas maneiras de fazer isso.
Método 1: Use
any
Como você deseja saber quais elementos em df1.MATCH
correspondem a quaisquer elementos em df2.PATTERN
, você pode usar any
, que retorna TRUE
se qualquer elemento em seus argumentos for TRUE
. Precisamos de uma sintaxe um pouco diferente para fazer isso funcionar. Precisamos envolver grepl
em lapply
para fazer uma lista de três vetores (um para cada elemento em df1.MATCH1
) que alimenta sapply
empacotado any
. Se usarmos apenas sapply
, any
retornará apenas um valor, pois temos uma entrada de matriz.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Método 2: Escreva uma expressão regular melhor.
Você deseja comparar o conteúdo de df1.MATCH
com valores possíveis que se parecem com abc
, ABC
, ABC ABC
ou ABC abc
, etc. Você pode englobar tudo isso em uma única string regex. A string que você deseja é
"^((ABC)( )*)+$" ^ # Nothing else before this (ABC) # Must contain ABC together as a group ( )* # followed by any number of spaces (including 0) ((ABC)( )*)+ # Look for the ABC (space) pattern repeated one or more times $ # And nothing else after it
Em seguida, use grepl
com ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Comparativo
Em um grande conjunto de dados, um desses terá um desempenho mais rápido. Vamos descobrir. Seus resultados de benchmark irão variar de acordo com os recursos de sua máquina.
df1.MATCH <- sample(c("ABC", "abc" ,"BCD"), size = 100000, replace = TRUE) df1 <- data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc") library(rbenchmark) benchmark("any lapply" = { df1 %>% filter(sapply(lapply(df1.MATCH, grepl, x=df2.PATTERN), any) ) }, "better regex" = { df1 %>% filter(grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE)) } ) test replications elapsed relative user.self sys.self user.child sys.child 1 any lapply 100 149.13 70.678 147.67 0.39 NA NA 2 better regex 100 2.11 1.000 2.10 0.02 NA NA
Parece que o método regex aprimorado é significativamente mais rápido. Isso porque ele está realizando apenas uma operação por linha (grepl
) antes da filtragem. O outro método está realizando quatro operações por linha: lapply
está executando grepl
três vezes (uma para cada elemento de df2.PATTERN
e sapply
então executa any
para cada elemento da lista (cada linha).
Comentários
- Usar
str_detect
depackage::stringr
executa de forma semelhante à abordagemgrepl
:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))
df1.MATCH <- c("ABC", "abc" ,"ABC")
em vez de última string sendo"BCD"
?