Captura de grupo de expresiones regulares en R con múltiples grupos de captura

94

En R, ¿es posible extraer la captura de grupo de una coincidencia de expresión regular? Por lo que yo puedo decir, ninguno de grep, grepl, regexpr, gregexpr, sub, o gsubdevolver las capturas de grupo.

Necesito extraer pares clave-valor de cadenas que están codificadas así:

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

Siempre puedo hacer varios greps de coincidencia completa, o hacer algún procesamiento externo (no R), pero esperaba poder hacerlo todo dentro de R. ¿Hay una función o un paquete que proporcione tal función para hacer esto?

Daniel Dickison
fuente

Respuestas:

118

str_match(), del stringrpaquete, hará esto. Devuelve una matriz de caracteres con una columna para cada grupo en la coincidencia (y una para toda la coincidencia):

> 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
fuente
1
y str_match_all()para hacer coincidir todos los grupos en una expresión regular
smci
¿Cómo puedo imprimir solo los grupos capturados para [, 1]?
nenur
No estás seguro de lo que buscas. Los grupos capturados son las columnas 2 y 3. [,1]es la coincidencia completa. [,2:3]son los grupos capturados.
Kent Johnson
51

gsub hace esto, de su ejemplo:

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

necesita doble escape de las \ s en las comillas, entonces funcionan para la expresión regular.

Espero que esto ayude.

David Lawrence Miller
fuente
En realidad, necesito sacar las subcadenas capturadas para poner un data.frame. Pero, mirando su respuesta, creo que podría encadenar gsub y un par de strsplit para obtener lo que quiero, tal vez: strsplit (strsplit (gsub (regex, "\\ 1 :: \\ 2 ::::", str ), "::::") [[1]], "::")
Daniel Dickison
8
Excelente. La página de gsubmanual de R necesita urgentemente un ejemplo que muestre que necesita '\\ 1' para escapar de una referencia de grupo de captura.
smci
33

Prueba regmatches()y regexec():

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"
jeales
fuente
3
Gracias por la solución vainilla R y por señalar algo regmatchesque nunca había visto antes
Andy
¿Por qué tendrías que escribir la cadena dos veces?
Stefano Borini
@StefanoBorini regexecdevuelve una lista que contiene información sobre solo la ubicación de las coincidencias, por lo que regmatchesrequiere que el usuario proporcione la cadena a la que pertenecía la lista de coincidencias.
RTbecard
19

gsub () puede hacer esto y devolver solo el grupo de captura:

Sin embargo, para que esto funcione, debe seleccionar explícitamente elementos fuera de su grupo de captura como se menciona en la ayuda de gsub ().

(...) los elementos de los vectores de caracteres 'x' que no estén sustituidos se devolverán sin cambios.

Entonces, si el texto que desea seleccionar se encuentra en el medio de alguna cadena, agregar. * Antes y después del grupo de captura debería permitirle devolverlo.

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

cashoes
fuente
4

Me gustan las expresiones regulares compatibles con Perl. Probablemente alguien más lo haga también ...

Aquí hay una función que hace expresiones regulares compatibles con Perl y coincide con la funcionalidad de funciones en otros lenguajes a los que estoy acostumbrado:

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
fuente
3

Así es como terminé resolviendo este problema. Usé dos expresiones regulares separadas para hacer coincidir el primer y segundo grupo de captura y ejecutar dos gregexprllamadas, luego extraje las subcadenas coincidentes:

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
fuente
+1 para un código de trabajo. Sin embargo, prefiero ejecutar un comando de shell rápido desde R y usar una expr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)'
frase de
3

Solución con strcapturede 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
fuente
2

Como se sugiere en el stringrpaquete, esto se puede lograr usando str_match()o str_extract().

Adaptado del manual:

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

Extrayendo y combinando nuestros grupos:

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

Indicando grupos con una matriz de salida (estamos interesados ​​en las columnas 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
fuente
¿Qué hay de
842566
Gracias por captar la omisión. Corregido usando el _allsufijo para las stringrfunciones relevantes .
Megatron
0

Esto se puede hacer usando el paquete unglue , tomando el ejemplo de la respuesta seleccionada:

# 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

O partiendo de un marco de datos

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

puede obtener la expresión regular sin procesar del patrón unglue, opcionalmente con captura con nombre:

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

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

Más información: https://github.com/moodymudskipper/unglue/blob/master/README.md

Moody_Mudskipper
fuente