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?

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"    

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.


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"

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"


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
}

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

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

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"

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.