Regex-Gruppenerfassung in R mit mehreren Erfassungsgruppen

94

Ist es in R möglich, die Gruppenerfassung aus einer Übereinstimmung mit regulären Ausdrücken zu extrahieren? Soweit ich sagen kann, nichts von grep, grepl, regexpr, gregexpr, sub, oder gsubdie Gruppe Captures zurück.

Ich muss Schlüssel-Wert-Paare aus Zeichenfolgen extrahieren, die folgendermaßen codiert sind:

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

Ich kann immer nur mehrere Greps mit vollständiger Übereinstimmung oder eine externe (Nicht-R-) Verarbeitung durchführen, aber ich hatte gehofft, dass ich alles innerhalb von R ausführen kann. Gibt es eine Funktion oder ein Paket, das eine solche Funktion bietet, um dies zu tun?

Daniel Dickison
quelle

Antworten:

118

str_match(), aus dem stringrPaket, wird dies tun. Es wird eine Zeichenmatrix mit einer Spalte für jede Gruppe in der Übereinstimmung (und einer für die gesamte Übereinstimmung) zurückgegeben:

> 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"    
Kent Johnson
quelle
1
und str_match_all()alle Gruppen in einem
regulären Ausdruck zu finden
Wie kann ich nur die erfassten Gruppen für [, 1] drucken?
nenur
Nicht sicher, wonach Sie suchen. Die erfassten Gruppen sind die Spalten 2 und 3. Dies [,1]ist die vollständige Übereinstimmung. [,2:3]ist die erfassten Gruppen.
Kent Johnson
50

gsub macht dies anhand Ihres Beispiels:

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

Sie müssen die \ s in den Anführungszeichen doppelt maskieren, damit sie für den regulären Ausdruck funktionieren.

Hoffe das hilft.

David Lawrence Miller
quelle
Eigentlich muss ich die erfassten Teilzeichenfolgen herausziehen, um sie in einen data.frame einzufügen. Aber wenn ich Ihre Antwort betrachte, könnte ich gsub und ein paar strsplits verketten, um das zu bekommen, was ich will, vielleicht: strsplit (strsplit (gsub (regex, "\\ 1 :: \\ 2 ::::", str ), "::::") [[1]], "::")
Daniel Dickison
8
Toll. Die R- gsubManpage benötigt dringend ein Beispiel, das zeigt, dass Sie '\\ 1' benötigen, um einer Capture-Gruppenreferenz zu entkommen.
smci
33

Versuchen Sie regmatches()und regexec():

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"
Eifersüchtige
quelle
3
Vielen Dank für die Vanille R-Lösung und für den Hinweis, regmatchesden ich noch nie gesehen habe
Andy
Warum müssten Sie die Zeichenfolge zweimal schreiben?
Stefano Borini
@StefanoBorini regexecgibt eine Liste zurück, die nur Informationen zum Speicherort der Übereinstimmungen enthält. Daher regmatchesmuss der Benutzer die Zeichenfolge angeben , zu der die Übereinstimmungsliste gehört.
RTbecard
19

gsub () kann dies tun und nur die Erfassungsgruppe zurückgeben:

Damit dies funktioniert, müssen Sie jedoch explizit Elemente außerhalb Ihrer Erfassungsgruppe auswählen, wie in der Hilfe zu gsub () angegeben.

(...) Elemente von Zeichenvektoren 'x', die nicht ersetzt werden, werden unverändert zurückgegeben.

Wenn Ihr zu wählender Text also in der Mitte einer Zeichenfolge liegt, sollten Sie durch Hinzufügen von. * Vor und nach der Erfassungsgruppe nur den Text zurückgeben können.

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

Cashoes
quelle
4

Ich mag Perl-kompatible reguläre Ausdrücke. Wahrscheinlich macht es auch jemand anderes ...

Hier ist eine Funktion, die Perl-kompatible reguläre Ausdrücke ausführt und mit der Funktionalität von Funktionen in anderen Sprachen übereinstimmt, an die ich gewöhnt bin:

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
}
Ruffbytes
quelle
3

So habe ich dieses Problem umgangen. Ich habe zwei separate reguläre Ausdrücke verwendet, um die erste und die zweite Erfassungsgruppe abzugleichen, zwei gregexprAufrufe auszuführen und dann die übereinstimmenden Teilzeichenfolgen herauszuziehen:

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"))
Daniel Dickison
quelle
+1 für einen Arbeitscode. Ich würde jedoch lieber einen schnellen Shell-Befehl von R ausführen und einen Bash- expr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)'
Einzeiler
3

Lösung mit strcaptureaus dem 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
Artem Klevtsov
quelle
2

Wie im stringrPaket vorgeschlagen, kann dies entweder mit str_match()oder erreicht werden str_extract().

Angepasst aus dem Handbuch:

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})"

Extrahieren und Kombinieren unserer Gruppen:

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" ""   

Anzeigen von Gruppen mit einer Ausgabematrix (wir interessieren uns für Spalten 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"
Megatron
quelle
Was ist mit 842 566 4692
Ferroao
Vielen Dank, dass Sie die Lücke geschlossen haben. Korrigiert mit dem _allSuffix für die relevanten stringrFunktionen.
Megatron
0

Dies kann mit dem Paket unglue erfolgen , wobei das Beispiel aus der ausgewählten Antwort entnommen wird:

# 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

Oder ausgehend von einem Datenrahmen

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

Sie können den rohen regulären Ausdruck aus dem nicht klebenden Muster erhalten, optional mit der benannten Erfassung:

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

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

Weitere Informationen: https://github.com/moodymudskipper/unglue/blob/master/README.md

Moody_Mudskipper
quelle