Ho una colonna denominata “MATCH” in un dataframe e un elenco di pattern denominati “PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD") df1 <- as.data.frame(df1.MATCH) df2.PATTERN <- c("ABC", "abc", "ABC abc")
Voglio usare grepl per confrontare la colonna MATCH con PATTERN, se è vero, applicherò le mie funzioni. Il risultato desiderato sarebbe “ABC” corrisponde a “ABC” e “ABC abc”. Questo è il codice che ho usato:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Ottengo un errore:
"Warning message: In grepl(TXN_GROUP, parm[3]) :argument "pattern" has length > 1 and only the first element will be used"
I capisco che non posso “utilizzare grepl per un elenco di vettori. Cè un modo per risolverlo?
Commenti
- Posso usare il filtro (grepl (incolla (df1.MATCH, collapse = " | "), df2.PATTERN)) e funziona per questo esempio. Tuttavia, il mio vero dataframe ha ~ 1 milione di righe e ho ricevuto un errore durante lutilizzo di questo codice.
- Nella prima riga del tuo esempio di codice, dovrebbe essere indicato
df1.MATCH <- c("ABC", "abc" ,"ABC")
anziché lultima stringa è"BCD"
?
Risposta
TL; DR:
grepl
si aspetta che il suo primo argomento sia una stringa (lunghezza 1), non un vettore. Puoi risolvere questo problema con combinazioni disapply
elapply
(vedi sotto), ma è meglio utilizzare ununica espressione regolare che captu Cerca ciò che desideri trovare indf1.MATCH
e non utilizzare affattodf2.PATTERN
. Questa seconda opzione è molto più veloce (anche se meno intelligente) per un set di dati di grandi dimensioni. Per questo tipo di lavoro, vale la pena imparare a utilizzare le espressioni regolari al massimo delle loro potenzialità.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Spiegazione
La documentazione per grepl
mostra il seguente utilizzo:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE)
Largomento pattern
è il primo e questo argomento dovrebbe essere una stringa (un elemento). Stai fornendo df1.MATCH
a questo argomento, che è un vettore.
Potremmo usare sapply
per applicare grepl
a ogni elemento di df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN) ABC abc BCD [1,] TRUE FALSE FALSE [2,] FALSE TRUE FALSE [3,] TRUE TRUE FALSE
Tuttavia, guarda loutput! Probabilmente non volevi una matrice. Cosa succede quando eseguiamo il tuo grepl
solo il primo elemento di df1.MATCH
?
grepl("ABC",df2.PATTERN) [1] TRUE FALSE TRUE
Otteniamo un vettore perché grepl
sta verificando ABC
rispetto a ogni elemento di df2.PATTERN
. Per ottenere un vettore logico utile per il filtraggio, è necessario restituire un vettore logico della stessa lunghezza di df1.MATCH
. Vedo due modi per farlo.
Metodo 1: utilizza
any
Dato che desideri sapere quali elementi in df1.MATCH
corrispondono a qualsiasi elemento in df2.PATTERN
, può utilizzare any
, che restituisce TRUE
se qualsiasi elemento nei suoi argomenti è TRUE
. Abbiamo bisogno di una sintassi leggermente diversa per farlo funzionare. Dobbiamo inserire grepl
in lapply
per creare un elenco di tre vettori (uno per ogni elemento in df1.MATCH1
) che alimenta sapply
wrapping any
. Se usiamo solo sapply
, any
restituirà solo un valore poiché abbiamo un input di matrice.
any(grepl("ABC", df2.PATTERN)) [1] TRUE sapply( lapply(df1.MATCH, grepl, x = df2.MATCH), any) [1] TRUE TRUE FALSE
Metodo 2: scrivi unespressione regolare migliore.
Desideri abbinare i contenuti di df1.MATCH
a possibili valori simili a abc
, ABC
, ABC ABC
o ABC abc
e così via. Puoi racchiudere tutto questo in una singola stringa regex. La stringa che desideri è
"^((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
Quindi utilizza grepl
con ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE) [1] TRUE TRUE FALSE
Benchmarking
In un set di dati di grandi dimensioni, uno di questi funzionerà più velocemente. Scopriamolo. I risultati del tuo benchmark varieranno in base alle risorse della tua macchina.
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
Sembra che il metodo regex migliorato sia significativamente più veloce. Questo perché esegue una sola operazione per riga (grepl
) prima del filtro. Laltro metodo esegue quattro operazioni per riga: lapply
esegue grepl
tre volte (una per ogni elemento di df2.PATTERN
e sapply
quindi esegue any
per ogni elemento dellelenco (ogni riga).
str_detect
dapackage::stringr
esegue in modo simile allapprocciogrepl
:str_detect(df1.MATCH, regex("^((ABC)( )*)+$", ignore_case = TRUE))