Acquisizione di gruppi di espressioni regolari in R con più gruppi di acquisizione


94

In R, è possibile estrarre l'acquisizione di gruppo da una corrispondenza di espressioni regolari? Per quanto ne so, nessuno di grep, grepl, regexpr, gregexpr, sub, o gsubrestituire le catture di gruppo.

Ho bisogno di estrarre coppie chiave-valore da stringhe codificate in questo modo:

\((.*?) :: (0\.[0-9]+)\)

Posso sempre eseguire più grep full-match o eseguire elaborazioni esterne (non R), ma speravo di poter fare tutto all'interno di R. C'è una funzione o un pacchetto che fornisce tale funzione per farlo?

Risposte:


118

str_match(), dal stringrpacchetto, lo farà. Restituisce una matrice di caratteri con una colonna per ogni gruppo nella partita (e una per l'intera partita):

> s = c("(sometext :: 0.1231313213)", "(moretext :: 0.111222)")
> str_match(s, "\\((.*?) :: (0\\.[0-9]+)\\)")
     [,1]                         [,2]       [,3]          
[1,] "(sometext :: 0.1231313213)" "sometext" "0.1231313213"
[2,] "(moretext :: 0.111222)"     "moretext" "0.111222"    

1
e str_match_all()per abbinare tutti i gruppi in una regex
smci

Come posso stampare solo i gruppi acquisiti per [, 1]?
nenur

Non sei sicuro di ciò che stai cercando. I gruppi catturati sono le colonne 2 e 3. [,1]è la corrispondenza completa. [,2:3]sono i gruppi catturati.
Kent Johnson,

50

gsub fa questo, dal tuo esempio:

gsub("\\((.*?) :: (0\\.[0-9]+)\\)","\\1 \\2", "(sometext :: 0.1231313213)")
[1] "sometext 0.1231313213"

è necessario eseguire il doppio escape delle \ s tra virgolette, quindi funzionano per la regex.

Spero che questo ti aiuti.


In realtà ho bisogno di estrarre le sottostringhe catturate per inserire un data.frame. Ma, guardando la tua risposta, immagino di poter concatenare gsub e un paio di strsplit per ottenere quello che voglio, forse: strsplit (strsplit (gsub (regex, "\\ 1 :: \\ 2 ::::", str ), "::::") [[1]], "::")
Daniel Dickison,

8
Grande. La gsubmanpage R ha davvero bisogno di un esempio che mostri che hai bisogno di '\\ 1' per sfuggire a un riferimento a un gruppo di cattura.
smci

33

Prova regmatches()e regexec():

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"

3
Grazie per la soluzione R vaniglia e per aver sottolineato regmatchesche non ho mai visto prima
Andy

Perché dovresti scrivere la stringa due volte?
Stefano Borini

@StefanoBorini regexecrestituisce una lista contenente informazioni riguardanti solo la posizione delle corrispondenze, quindi regmatchesrichiede all'utente di fornire la stringa a cui apparteneva la lista delle corrispondenze.
RTbecard il

19

gsub () può farlo e restituire solo il gruppo di cattura:

Tuttavia, affinché funzioni, è necessario selezionare esplicitamente gli elementi al di fuori del proprio gruppo di cattura, come menzionato nella guida gsub ().

(...) gli elementi dei vettori di caratteri 'x' che non vengono sostituiti verranno restituiti invariati.

Quindi, se il testo da selezionare si trova nel mezzo di una stringa, l'aggiunta di. * Prima e dopo il gruppo di cattura dovrebbe consentirti di restituirlo solo.

gsub(".*\\((.*?) :: (0\\.[0-9]+)\\).*","\\1 \\2", "(sometext :: 0.1231313213)") [1] "sometext 0.1231313213"


4

Mi piacciono le espressioni regolari compatibili con perl. Probabilmente lo fa anche qualcun altro ...

Ecco una funzione che esegue espressioni regolari compatibili con perl e corrisponde alla funzionalità delle funzioni in altri linguaggi a cui sono abituato:

regexpr_perl <- function(expr, str) {
  match <- regexpr(expr, str, perl=T)
  matches <- character(0)
  if (attr(match, 'match.length') >= 0) {
    capture_start <- attr(match, 'capture.start')
    capture_length <- attr(match, 'capture.length')
    total_matches <- 1 + length(capture_start)
    matches <- character(total_matches)
    matches[1] <- substr(str, match, match + attr(match, 'match.length') - 1)
    if (length(capture_start) > 1) {
      for (i in 1:length(capture_start)) {
        matches[i + 1] <- substr(str, capture_start[[i]], capture_start[[i]] + capture_length[[i]] - 1)
      }
    }
  }
  matches
}

3

È così che ho finito per aggirare questo problema. Ho usato due regex separate per abbinare il primo e il secondo gruppo di acquisizione ed eseguire due gregexprchiamate, quindi estrarre le sottostringhe corrispondenti:

regex.string <- "(?<=\\().*?(?= :: )"
regex.number <- "(?<= :: )\\d\\.\\d+"

match.string <- gregexpr(regex.string, str, perl=T)[[1]]
match.number <- gregexpr(regex.number, str, perl=T)[[1]]

strings <- mapply(function (start, len) substr(str, start, start+len-1),
                  match.string,
                  attr(match.string, "match.length"))
numbers <- mapply(function (start, len) as.numeric(substr(str, start, start+len-1)),
                  match.number,
                  attr(match.number, "match.length"))

+1 per un codice funzionante. Tuttavia, preferirei eseguire un comando di shell veloce da R e utilizzare una riga di Bash come questaexpr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)'
Aleksandr Levchuk

3

Soluzione con strcaptureda utils:

x <- c("key1 :: 0.01",
       "key2 :: 0.02")
strcapture(pattern = "(.*) :: (0\\.[0-9]+)",
           x = x,
           proto = list(key = character(), value = double()))
#>    key value
#> 1 key1  0.01
#> 2 key2  0.02

2

Come suggerito nel stringrpacchetto, questo può essere ottenuto utilizzando str_match()o str_extract().

Adattato dal manuale:

library(stringr)

strings <- c(" 219 733 8965", "329-293-8753 ", "banana", 
             "239 923 8115 and 842 566 4692",
             "Work: 579-499-7527", "$1000",
             "Home: 543.355.3679")
phone <- "([2-9][0-9]{2})[- .]([0-9]{3})[- .]([0-9]{4})"

Estrazione e combinazione dei nostri gruppi:

str_extract_all(strings, phone, simplify=T)
#      [,1]           [,2]          
# [1,] "219 733 8965" ""            
# [2,] "329-293-8753" ""            
# [3,] ""             ""            
# [4,] "239 923 8115" "842 566 4692"
# [5,] "579-499-7527" ""            
# [6,] ""             ""            
# [7,] "543.355.3679" ""   

Indicare i gruppi con una matrice di output (siamo interessati alle colonne 2+):

str_match_all(strings, phone)
# [[1]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "219 733 8965" "219" "733" "8965"
# 
# [[2]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "329-293-8753" "329" "293" "8753"
# 
# [[3]]
#      [,1] [,2] [,3] [,4]
# 
# [[4]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "239 923 8115" "239" "923" "8115"
# [2,] "842 566 4692" "842" "566" "4692"
# 
# [[5]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "579-499-7527" "579" "499" "7527"
# 
# [[6]]
#      [,1] [,2] [,3] [,4]
# 
# [[7]]
#      [,1]           [,2]  [,3]  [,4]  
# [1,] "543.355.3679" "543" "355" "3679"

che dire di 842 566 4692
Ferroao

Grazie per aver colto l'omissione. Corretto utilizzando il _allsuffisso per le stringrfunzioni pertinenti .
Megatron

0

Questo può essere fatto utilizzando il pacchetto unglue , prendendo l'esempio dalla risposta selezionata:

# install.packages("unglue")
library(unglue)

s <- c("(sometext :: 0.1231313213)", "(moretext :: 0.111222)")
unglue_data(s, "({x} :: {y})")
#>          x            y
#> 1 sometext 0.1231313213
#> 2 moretext     0.111222

Oppure partendo da un data frame

df <- data.frame(col = s)
unglue_unnest(df, col, "({x} :: {y})",remove = FALSE)
#>                          col        x            y
#> 1 (sometext :: 0.1231313213) sometext 0.1231313213
#> 2     (moretext :: 0.111222) moretext     0.111222

puoi ottenere la regex grezza dal modello unglue, opzionalmente con la cattura denominata:

unglue_regex("({x} :: {y})")
#>             ({x} :: {y}) 
#> "^\\((.*?) :: (.*?)\\)$"

unglue_regex("({x} :: {y})",named_capture = TRUE)
#>                     ({x} :: {y}) 
#> "^\\((?<x>.*?) :: (?<y>.*?)\\)$"

Maggiori informazioni: https://github.com/moodymudskipper/unglue/blob/master/README.md

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.