Jeg har en kolonne som heter «MATCH» i en dataramme og en liste over mønstre som heter «MØNSTER».
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Jeg vil bruke grepl til å sammenligne MATCH-kolonne med MØNSTER, hvis det er sant, vil jeg bruke funksjonene mine. Det ønskede resultatet vil være «ABC» samsvarer med «ABC» og «ABC abc». Dette er koden jeg brukte:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Jeg får feil:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I forstår at jeg ikke kan bruke grepl til en liste over vektorer. Er det noen måte å løse det på?
Kommentarer
Svar
TL; DR:
grepl
forventer at det første argumentet er en streng (lengde 1), ikke en vektor. Du kan løse dette med kombinasjoner avsapply
oglapply
(se nedenfor), men du får bedre betjening ved å bruke et enkelt regulært uttrykk som captu res det du vil matche idf1.MATCH
og ikke brukdf2.PATTERN
i det hele tatt. Dette andre alternativet er mye raskere (hvis mindre intelligent) for et stort datasett. For denne typen arbeid er det verdt å lære å bruke vanlige uttrykk til sitt fulle potensiale.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Forklaring
Dokumentasjonen for grepl
viser følgende bruk:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Argumentet pattern
er først, og dette argumentet skal være en streng (ett element). Du gir df1.MATCH
til dette argumentet, som er en vektor.
Vi kan bruke sapply
til å bruke grepl
til hvert 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
Se imidlertid på utdataene! Du vil sannsynligvis ikke ha en matrise. Hva skjer når vi kjører grepl
ett bare det første elementet i df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Vi får en vektor fordi grepl
sjekker ABC
mot hvert element i df2.PATTERN
. For å få en nyttig logisk vektor for filtrering, må du returnere en logisk vektor med samme lengde som df1.MATCH
. Jeg ser to måter å gjøre det på.
Metode 1: Bruk
any
Siden du vil vite hvilke elementer i df1.MATCH
samsvarer med alle elementene i df2.PATTERN
kan bruke any
, som returnerer TRUE
hvis noe element i argumentene er TRUE
. Vi trenger litt annen syntaks for å få dette til å fungere. Vi må pakke grepl
i lapply
for å lage en liste over tre vektorer (en for hvert element i df1.MATCH1
) som mates inn i sapply
pakket any
. Hvis vi bare bruker sapply
, vil any
bare returnere en verdi siden vi har en matriseinngang.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Metode 2: Skriv et bedre regulært uttrykk.
Du vil matche innholdet i df1.MATCH
mot mulige verdier som ser ut som abc
, ABC
, ABC ABC
, eller ABC abc
osv. Du kan inkludere alt dette i en enkelt regex-streng. Strengen du vil ha er
"^((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
Bruk deretter grepl
med ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
I et stort datasett vil en av disse fungere raskere. La oss finne ut. Resultatene dine for referanser vil variere etter maskinens ressurser.
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 til at den forbedrede regex-metoden er betydelig raskere. Dette skyldes at den bare utfører en operasjon per rad (grepl
) før filtrering. Den andre metoden utfører fire operasjoner per rad: lapply
utfører grepl
tre ganger (en for hvert element i df2.PATTERN
, og sapply
deretter utfører any
for hvert listeelement (hver rad).
Kommentarer
- Bruk
str_detect
frapackage::stringr
utfører på samme måte somgrepl
tilnærming:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))
df1.MATCH <- c("ABC", "abc" ,"ABC")
i stedet for siste streng er"BCD"
?