Mam kolumnę o nazwie „MATCH” w ramce danych i listę wzorców o nazwie „WZÓR”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Chcę użyć grepl do porównania kolumny MATCH z WZORCIEM, jeśli prawda, zastosuję moje funkcje. Żądanym wynikiem byłoby dopasowanie „ABC” do „ABC” i „ABC abc”. Oto kod, którego użyłem:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Pojawia się błąd:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I rozumiem, że nie mogę użyć grepl do listy wektorów. Czy istnieje sposób, aby to rozwiązać?
Komentarze
Odpowiedź
TL; DR:
grepl
oczekuje, że pierwszym argumentem będzie łańcuch (długość 1), a nie wektor. Możesz rozwiązać ten problem za pomocą kombinacjisapply
ilapply
(patrz poniżej), ale lepiej jest używać pojedynczego wyrażenia regularnego, res to, co chcesz dopasować wdf1.MATCH
i w ogóle nie używajdf2.PATTERN
. Ta druga opcja jest znacznie szybsza (jeśli mniej inteligentna) w przypadku dużego zestawu danych. Przy tego typu pracach warto nauczyć się jak w pełni wykorzystywać wyrażenia regularne.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Wyjaśnienie
Dokumentacja grepl
przedstawia następujące użycie:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Argument pattern
jest pierwszym, a ten argument powinien być łańcuchem (jednym elementem). Dostarczasz df1.MATCH
do tego argumentu, który jest wektorem.
Możemy użyć sapply
, aby zastosować grepl
do każdego elementu df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
Jednak spójrz na wynik! Prawdopodobnie nie chciałeś matrycy. Co się stanie, gdy uruchomimy Twój grepl
jako pierwszy element df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Otrzymujemy wektor, ponieważ grepl
sprawdza ABC
z każdym elementem df2.PATTERN
. Aby uzyskać użyteczny wektor logiczny do filtrowania, musisz zwrócić wektor logiczny o tej samej długości co df1.MATCH
. Widzę dwa sposoby, aby to zrobić.
Metoda 1: Użyj
any
Ponieważ chcesz wiedzieć, które elementy w df1.MATCH
pasują do dowolnego elementu w df2.PATTERN
, musisz może użyć any
, co zwraca TRUE
, jeśli którykolwiek element w jego argumentach to TRUE
. Aby to zadziałało, potrzebujemy trochę innej składni. Musimy zawinąć grepl
w lapply
, aby utworzyć listę trzech wektorów (po jednym na każdy element w df1.MATCH1
), który trafia do sapply
opakowanego any
. Jeśli użyjemy tylko sapply
, any
zwróci tylko jedną wartość, ponieważ mamy dane wejściowe macierzowe.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Metoda 2: Napisz lepsze wyrażenie regularne.
Chcesz dopasować zawartość df1.MATCH
do możliwych wartości, które wyglądają jak abc
, ABC
, ABC ABC
lub ABC abc
itp. Możesz to wszystko ująć w jednym ciągu wyrażenia regularnego. Żądany ciąg to
"^((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
Następnie użyj grepl
z ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
W przypadku dużego zbioru danych jeden z nich będzie działać szybciej. Przekonajmy się. Wyniki testu porównawczego będą się różnić w zależności od zasobów komputera.
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
Wygląda na to, że ulepszona metoda wyrażeń regularnych jest znacznie szybsza. Dzieje się tak dlatego, że przed filtrowaniem wykonuje tylko jedną operację na wiersz (grepl
). Druga metoda wykonuje cztery operacje na wiersz: lapply
wykonuje grepl
trzy razy (po jednym na każdy element df2.PATTERN
, a następnie sapply
wykonuje any
dla każdego elementu listy (każdego wiersza).
Komentarze
- Używając
str_detect
zpackage::stringr
wykonano podobnie do podejściagrepl
:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))
df1.MATCH <- c("ABC", "abc" ,"ABC")
zamiast ostatni ciąg to"BCD"
?