Ik heb een kolom met de naam “MATCH” in een dataframe en een lijst met patronen met de naam “PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Ik wil grepl gebruiken om de MATCH-kolom met PATTERN te vergelijken, indien waar, zal ik mijn functies toepassen. Het gewenste resultaat is “ABC” komt overeen met “ABC” en “ABC abc”. Dit is de code die ik heb gebruikt:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Ik krijg een foutmelding:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I begrijp dat ik “grepl niet kan gebruiken voor een lijst met vectoren. Is er een manier om het op te lossen?
Reacties
- Ik kan filter (grepl (plak (df1.MATCH, collapse = " | "), df2.PATTERN)) en dat werkt voor dit voorbeeld. mijn echte dataframe heeft ~ 1 miljoen rijen en ik kreeg een foutmelding bij het gebruik van deze code.
- Op de eerste rij van uw codevoorbeeld, moet er
df1.MATCH <- c("ABC", "abc" ,"ABC")
staan in plaats van de laatste string is"BCD"
?
Antwoord
TL; DR:
grepl
verwacht dat het eerste argument een string (lengte 1) is, niet een vector. Je kunt dit oplossen met combinaties vansapply
enlapply
(zie hieronder), maar u kunt beter één reguliere expressie gebruiken die captu res wat je wilt matchen indf1.MATCH
en gebruik helemaal geendf2.PATTERN
. Deze tweede optie is veel sneller (zij het minder intelligent) voor een grote dataset. Voor dit soort werk is het de moeite waard om te leren hoe u reguliere expressies optimaal kunt gebruiken.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Uitleg
De documentatie voor grepl
toont het volgende gebruik:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Het pattern
-argument is het eerste, en dit argument zou een string (één element) moeten zijn. U geeft df1.MATCH
aan dit argument, dat een vector is.
We kunnen sapply
gebruiken om grepl
naar elk element van df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
Kijk echter naar de uitvoer! Waarschijnlijk wilde u geen matrix. Wat gebeurt er als we uw grepl
uitvoeren op slechts het eerste element van df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
We krijgen een vector omdat grepl
ABC
controleert op elk element van df2.PATTERN
. Om een bruikbare logische vector voor filteren te krijgen, moet u een logische vector met dezelfde lengte als df1.MATCH
retourneren. Ik zie twee manieren om het te doen.
Methode 1: gebruik
any
Aangezien u wilt weten welke elementen in df1.MATCH
overeenkomen met elementen in df2.PATTERN
, kan any
gebruiken, wat TRUE
retourneert als een element in zijn argumenten TRUE
is. We hebben een iets andere syntaxis nodig om dit te laten werken. We moeten grepl
in lapply
plaatsen om een lijst van drie vectoren te maken (één voor elk element in df1.MATCH1
) die wordt ingevoerd in sapply
verpakt any
. Als we sapply
gebruiken, zal any
slechts één waarde retourneren aangezien we een matrixinvoer hebben.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Methode 2: schrijf een betere reguliere expressie.
U wilt de inhoud van df1.MATCH
vergelijken met mogelijke waarden die eruit zien als abc
, ABC
, ABC ABC
, of ABC abc
, enz. Je kunt dit allemaal samenvatten in een enkele regex-string. De gewenste string is
"^((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
Gebruik vervolgens grepl
met ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
In een grote dataset zal een van deze sneller werken. Laten we eens kijken. Uw benchmarkresultaten zullen variëren afhankelijk van de bronnen van uw computer.
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
Het lijkt erop dat de verbeterde regex-methode aanzienlijk sneller is. Dat komt omdat het slechts één bewerking per rij uitvoert (grepl
) voordat wordt gefilterd. De andere methode voert vier bewerkingen per rij uit: lapply
voert grepl
drie keer uit (één voor elk element van df2.PATTERN
, en sapply
voert any
uit voor elk lijstelement (elke rij).
str_detect
vanpackage::stringr
presteert vergelijkbaar met degrepl
benadering:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))