Capture de groupe Regex dans R avec plusieurs groupes de capture

94

Dans R, est-il possible d'extraire une capture de groupe à partir d'une correspondance d'expression régulière? Pour autant que je sache, aucun grep, grepl, regexpr, gregexpr, subou gsubrenvoyer les captures de groupe.

J'ai besoin d'extraire des paires clé-valeur à partir de chaînes qui sont encodées ainsi:

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

Je peux toujours simplement faire plusieurs greps à correspondance complète, ou faire un traitement extérieur (non-R), mais j'espérais pouvoir tout faire dans R. Y a-t-il une fonction ou un package qui fournit une telle fonction pour faire cela?

Daniel Dickison
la source

Réponses:

118

str_match(), à partir du stringrpackage, le fera. Il renvoie une matrice de caractères avec une colonne pour chaque groupe de la correspondance (et une pour l'ensemble de la correspondance):

> 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
la source
1
et str_match_all()pour faire correspondre tous les groupes dans une regex
smci
Comment puis-je imprimer uniquement les groupes capturés pour [, 1]?
nenur
Pas sûr de ce que vous cherchez. Les groupes capturés sont les colonnes 2 et 3. [,1]est la correspondance complète. [,2:3]sont les groupes capturés.
Kent Johnson
51

gsub fait cela, à partir de votre exemple:

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

vous devez double échapper les \ s entre les guillemets, puis ils fonctionnent pour l'expression régulière.

J'espère que cela t'aides.

David Lawrence Miller
la source
En fait, j'ai besoin de retirer les sous-chaînes capturées pour les mettre dans un data.frame. Mais, en regardant votre réponse, je suppose que je pourrais enchaîner gsub et quelques strsplit pour obtenir ce que je veux, peut-être: strsplit (strsplit (gsub (regex, "\\ 1 :: \\ 2 ::::", str ), "::::") [[1]], "::")
Daniel Dickison
8
Génial. La gsubpage de manuel R a vraiment besoin d'un exemple montrant que vous avez besoin de '\\ 1' pour échapper à une référence de groupe de capture.
smci
33

Essayez regmatches()et regexec():

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"
jaloux
la source
3
Merci pour la solution vanilla R et pour avoir signalé ce regmatchesque je n'ai jamais vu auparavant
Andy
Pourquoi devriez-vous écrire la chaîne deux fois?
Stefano Borini
@StefanoBorini regexecrenvoie une liste contenant des informations concernant uniquement l'emplacement des correspondances, regmatchesobligeant donc l'utilisateur à fournir la chaîne à laquelle appartenait la liste de correspondances.
RTbecard
19

gsub () peut le faire et ne renvoyer que le groupe de capture:

Cependant, pour que cela fonctionne, vous devez sélectionner explicitement des éléments en dehors de votre groupe de capture comme mentionné dans l'aide de gsub ().

(...) les éléments des vecteurs de caractères 'x' qui ne sont pas substitués seront retournés inchangés.

Donc, si votre texte à sélectionner se trouve au milieu d'une chaîne, l'ajout de. * Avant et après le groupe de capture devrait vous permettre de ne le retourner.

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

cashoes
la source
4

J'aime les expressions régulières compatibles avec Perl. Probablement quelqu'un d'autre le fait aussi ...

Voici une fonction qui fait des expressions régulières compatibles avec Perl et correspond aux fonctionnalités des fonctions dans d'autres langages auxquels je suis habitué:

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
la source
3

C'est ainsi que j'ai fini par contourner ce problème. J'ai utilisé deux expressions régulières distinctes pour faire correspondre les premier et deuxième groupes de capture et exécuter deux gregexprappels, puis extraire les sous-chaînes correspondantes:

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
la source
+1 pour un code fonctionnel. Cependant, je préfère exécuter une commande shell rapide à partir de R et utiliser un one-liner Bash comme celui-ciexpr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)'
Aleksandr Levchuk
3

Solution avec strcapturedu 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
la source
2

Comme suggéré dans le stringrpackage, cela peut être réalisé en utilisant soit str_match()ou str_extract().

Adapté du manuel:

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

Extraire et combiner nos groupes:

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

Indiquer les groupes avec une matrice de sortie (nous nous intéressons aux colonnes 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
la source
que diriez
842566
Merci d'avoir attrapé l'omission. Corrigé à l'aide du _allsuffixe des stringrfonctions concernées .
Megatron le
0

Cela peut être fait en utilisant le package unglue , en prenant l'exemple de la réponse sélectionnée:

# 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

Ou à partir d'une trame de données

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

vous pouvez obtenir le regex brut à partir du modèle unglue, éventuellement avec la capture nommée:

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

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

Plus d'infos: https://github.com/moodymudskipper/unglue/blob/master/README.md

Moody_Mudskipper
la source