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?

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"    

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.


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"

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"


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
}

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

+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

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"

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.