Jag har en kolumn med namnet ”MATCH” i en dataram och en lista med mönster som heter ”PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Jag vill använda grepl för att jämföra MATCH-kolumnen med PATTERN, om det är sant kommer jag att tillämpa mina funktioner. Det önskade resultatet skulle vara ”ABC” matchar ”ABC” och ”ABC abc”. Det här är koden jag använde:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Jag får fel:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I förstår att jag inte kan använda grepl till en lista med vektorer. Finns det något sätt att lösa det?
Kommentarer
Svar
TL; DR:
grepl
förväntar sig att dess första argument är en sträng (längd 1), inte en vektor. Du kan lösa detta med kombinationer avsapply
ochlapply
(se nedan), men du tjänar bättre med ett enda reguljärt uttryck som captu res vad du vill matcha idf1.MATCH
och inte alls användadf2.PATTERN
. Det andra alternativet är mycket snabbare (om det är mindre intelligent) för en stor datamängd. För denna typ av arbete är det värt att lära sig att använda reguljära uttryck till sin fulla potential.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Förklaring
Dokumentationen för grepl
visar följande användning:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Argumentet pattern
är först och detta argument bör vara en sträng (ett element). Du tillhandahåller df1.MATCH
till detta argument, som är en vektor.
Vi kan använda sapply
för att tillämpa grepl
till varje element i df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
Titta dock på utdata! Du ville antagligen inte ha en matris. Vad händer när vi kör din grepl
en bara det första elementet i df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Vi får en vektor eftersom grepl
kontrollerar ABC
mot varje element i df2.PATTERN
. För att få en användbar logisk vektor för filtrering måste du returnera en logisk vektor med samma längd som df1.MATCH
. Jag ser två sätt att göra det.
Metod 1: Använd
any
Eftersom du vill veta vilka element i df1.MATCH
matchar alla element i df2.PATTERN
, du kan använda any
, som returnerar TRUE
om något element i dess argument är TRUE
. Vi behöver lite olika syntax för att detta ska fungera. Vi måste slå in grepl
i lapply
för att skapa en lista med tre vektorer (en för varje element i df1.MATCH1
) som matas in i sapply
insvept any
. Om vi bara använder sapply
kommer any
bara att returnera ett värde eftersom vi har en matrisingång.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Metod 2: Skriv ett bättre reguljärt uttryck.
Du vill matcha innehållet i df1.MATCH
mot möjliga värden som ser ut som abc
, ABC
, ABC ABC
, eller ABC abc
, etc. Du kan omfatta allt detta i en enda regexsträng. Strängen du vill ha är
"^((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
Använd sedan grepl
med ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
I en stor dataset kommer en av dessa att fungera snabbare. Låt oss ta reda på det. Ditt riktmärkesresultat varierar beroende på maskinens resurser.
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
Det ser ut som att den förbättrade regexmetoden är betydligt snabbare. Det beror på att den bara utför en operation per rad (grepl
) innan filtrering. Den andra metoden utför fyra operationer per rad: lapply
utför grepl
tre gånger (en för varje element i df2.PATTERN
och sapply
sedan utför any
för varje listelement (varje rad).
Kommentarer
- Använd
str_detect
frånpackage::stringr
utför på samma sätt somgrepl
tillvägagångssätt:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))
df1.MATCH <- c("ABC", "abc" ,"ABC")
snarare än sista strängen är"BCD"
?