Captura de grupo Regex em R com vários grupos de captura

94

Em R, é possível extrair a captura de grupo de uma correspondência de expressão regular? Tanto quanto eu posso dizer, nenhum de grep, grepl, regexpr, gregexpr, sub, ou gsubretornar a captura do grupo.

Preciso extrair pares de valor-chave de strings que são codificadas assim:

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

Sempre posso fazer vários greps de correspondência total ou fazer algum processamento externo (não R), mas esperava poder fazer tudo dentro de R. Há uma função ou pacote que fornece tal função para fazer isso?

Daniel Dickison
fonte

Respostas:

118

str_match(), do stringrpacote, fará isso. Ele retorna uma matriz de caracteres com uma coluna para cada grupo na correspondência (e uma para a correspondência inteira):

> 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
fonte
1
e str_match_all()para combinar todos os grupos em uma regex
smci
Como posso imprimir apenas os grupos capturados para [, 1]?
nenur
Não tenho certeza do que você está procurando. Os grupos capturados são as colunas 2 e 3. [,1]é a correspondência completa. [,2:3]são os grupos capturados.
Kent Johnson
50

gsub faz isso, a partir do seu exemplo:

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

você precisa fazer um escape duplo de \ s nas aspas para que funcionem para o regex.

Espero que isto ajude.

David Lawrence Miller
fonte
Na verdade, preciso retirar as substrings capturadas para colocar em um data.frame. Mas, olhando para sua resposta, acho que poderia encadear gsub e alguns strsplit para conseguir o que desejo, talvez: strsplit (strsplit (gsub (regex, "\\ 1 :: \\ 2 ::::", str ), "::::") [[1]], "::")
Daniel Dickison
8
Ótimo. A página de gsubmanual R precisa muito de um exemplo mostrando que você precisa de '\\ 1' para escapar de uma referência de grupo de captura.
smci
33

Experimente regmatches()e regexec():

regmatches("(sometext :: 0.1231313213)",regexec("\\((.*?) :: (0\\.[0-9]+)\\)","(sometext :: 0.1231313213)"))
[[1]]
[1] "(sometext :: 0.1231313213)" "sometext"                   "0.1231313213"
ciúmes
fonte
3
Obrigado pela solução vanilla R e por apontar o regmatchesque eu nunca tinha visto antes
Andy
Por que você teria que escrever a string duas vezes?
Stefano Borini,
@StefanoBorini regexecretorna uma lista contendo informações apenas sobre a localização das correspondências, portanto, regmatchesexige que o usuário forneça a string à qual a lista de correspondências pertence.
RTbecard
19

gsub () pode fazer isso e retornar apenas o grupo de captura:

No entanto, para que isso funcione, você deve selecionar explicitamente os elementos fora do seu grupo de captura, conforme mencionado na ajuda do gsub ().

(...) os elementos dos vetores de caracteres 'x' que não são substituídos serão retornados inalterados.

Portanto, se o texto a ser selecionado estiver no meio de alguma string, adicionar. * Antes e depois do grupo de captura permitirá que você apenas o retorne.

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

cashoes
fonte
4

Eu gosto de expressões regulares compatíveis com perl. Provavelmente outra pessoa também ...

Aqui está uma função que faz expressões regulares compatíveis com perl e corresponde à funcionalidade de funções em outras linguagens com as quais estou acostumado:

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

Foi assim que acabei contornando esse problema. Usei duas regexes separadas para combinar o primeiro e o segundo grupos de captura e executei duas gregexprchamadas, em seguida, retirei as substrings correspondentes:

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
fonte
1 para um código funcional. No entanto, prefiro executar um comando shell rápido de R e usar um one-liner Bash como esteexpr "xyx0.0023xyxy" : '[^0-9]*\([.0-9]\+\)'
Aleksandr Levchuk
3

Solução com strcapturea partir do 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
fonte
2

Conforme sugerido no stringrpacote, isso pode ser feito usando str_match()ou str_extract().

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

Extraindo e combinando nossos 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 com uma matriz de saída (estamos interessados ​​nas colunas 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
fonte
e 842 566 4692
Ferroao
Obrigado por perceber a omissão. Corrigido usando o _allsufixo para as stringrfunções relevantes .
Megatron
0

Isso pode ser feito usando o pacote unglue , tomando o exemplo da resposta selecionada:

# 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 a partir de um quadro de dados

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

você pode obter o regex bruto do padrão de descolagem, opcionalmente com a captura nomeada:

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

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

Mais informações: https://github.com/moodymudskipper/unglue/blob/master/README.md

Moody_Mudskipper
fonte