Am o coloană numită „MATCH” într-un cadru de date și o listă de șabloane numită „PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Vreau să folosesc grepl pentru a compara coloana MATCH cu PATTERN, dacă este adevărat, îmi voi aplica funcțiile. Rezultatul dorit ar fi „ABC” se potrivește cu „ABC” și „ABC abc”. Acesta este codul pe care l-am folosit:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Am primit o eroare:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I înțeleg că nu pot folosi grepl într-o listă de vectori. Există vreo modalitate de a o rezolva?
Comentarii
Răspuns
TL; DR:
grepl
se așteaptă ca primul său argument să fie un șir (lungime 1), nu un vector. Puteți rezolva acest lucru cu combinații desapply
șilapply
(vezi mai jos), dar ești mai bine servit folosind o singură expresie regulată care captu rezoluți ceea ce doriți să se potrivească îndf1.MATCH
și nu utilizați delocdf2.PATTERN
. Această a doua opțiune este mult mai rapidă (dacă este mai puțin inteligentă) pentru un set mare de date. Pentru acest tip de lucrări, merită să învățați cum să folosiți expresiile regulate la potențialul lor maxim.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Explicație
Documentația pentru grepl
arată următoarea utilizare:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Argumentul pattern
este primul, iar acest argument ar trebui să fie un șir (un element). Furnizați df1.MATCH
acestui argument, care este un vector.
Am putea folosi sapply
pentru a aplica grepl
la fiecare element din df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
Cu toate acestea, uitați-vă la ieșire! Probabil că nu ați dorit o matrice. Ce se întâmplă când executăm grepl
unul doar primul element din df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Obținem un vector deoarece grepl
verifică ABC
împotriva fiecărui element din df2.PATTERN
. Pentru a obține un vector logic util pentru filtrare, trebuie să returnați un vector logic de aceeași lungime ca df1.MATCH
. Văd două moduri de a o face.
Metoda 1: Utilizați
any
Deoarece doriți să știți ce elemente din df1.MATCH
se potrivesc cu orice element din df2.PATTERN
, poate folosi any
, care returnează TRUE
dacă vreun element din argumentele sale este TRUE
. Avem nevoie de o sintaxă puțin diferită pentru a face acest lucru să funcționeze. Trebuie să înfășurăm grepl
în lapply
pentru a face o listă de trei vectori (unul pentru fiecare element din df1.MATCH1
) care se alimentează în sapply
înfășurat any
. Dacă folosim doar sapply
, any
va returna o singură valoare deoarece avem o intrare matricială.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Metoda 2: scrieți o expresie regulată mai bună.
Doriți să potriviți conținutul df1.MATCH
cu valorile posibile care arată ca abc
, ABC
, ABC ABC
sau ABC abc
etc. Puteți cuprinde toate acestea într-un singur șir regex. Șirul pe care îl doriți este
"^((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
Apoi utilizați grepl
cu ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
Într-un set de date mare, unul dintre acestea va funcționa mai repede. Să aflăm. Rezultatele dvs. de referință vor varia în funcție de resursele mașinii dvs.
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
Se pare că metoda regex îmbunătățită este semnificativ mai rapidă. Asta pentru că efectuează o singură operație pe rând (grepl
) înainte de filtrare. Cealaltă metodă efectuează patru operații pe rând: lapply
efectuează grepl
de trei ori (câte una pentru fiecare element din df2.PATTERN
și sapply
atunci efectuează any
pentru fiecare element de listă (fiecare rând).
Comentarii
- Folosirea
str_detect
dinpackage::stringr
în mod similar abordăriigrepl
:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))
df1.MATCH <- c("ABC", "abc" ,"ABC")
în loc de ultimul șir fiind"BCD"
?