Trilhas
/
R
R
/
Programa
/
Expressões regulares
Ex

Expressões regulares em R

1 exercício

Sobre Expressões regulares

Expressões regulares são uma forma muito versátil de fazer correspondência de padrões em strings, usando uma linguagem de domínio específico (DSL) criada para esse fim.

Advanced

Assim como muitas outras linguagens de programação, o R não tenta implementar sua própria biblioteca de regex. Em vez disso, ele usa o motor de expressões regulares ICU. O ICU é parecido com a popular biblioteca PCRE2 usada pela maioria das linguagens mais recentes, embora alguns casos extremos sejam tratados de forma diferente.

Muitos recursos de regex já vêm embutidos no Base R, e você pode ver exemplos em códigos mais antigos.

Para código novo, é fortemente recomendado usar a biblioteca stringr.

Como observado no conceito Strings, o stringr tem boa documentação, geralmente escrita pelo(s) autor(es) do pacote.

Note

Este programa de estudos de R assume que você já conhece a sintaxe básica de regex. Vamos nos concentrar apenas nos recursos específicos do R.

Abaixo estão alguns recursos para relembrar seus conhecimentos de expressões regulares.

Muitas das funções já discutidas no conceito Strings são feitas para buscas com regex por padrão.

Mantivemos o conceito Strings relativamente simples, fazendo correspondência apenas com literais de string, mas qualquer padrão de regex pode ser usado no lugar.

Formalmente, uma regex é criada no R chamando regex(). Por conveniência, as funções do stringr tratam qualquer string de padrão como uma regex. No exemplo abaixo, o primeiro str_replace_all() é açúcar sintático para a segunda versão:

input <- "Some input string"

# "\\s" matches any whitespace character
input |> str_replace_all("\\s", "_")
[1] "Some_input_string"

input |> str_replace_all(regex("\\s"), "_")
[1] "Some_input_string"

A cheatsheet é um guia útil: todas as funções com um argumento pattern (impressas em vermelho) aceitam uma regex, e a página 2 resume a sintaxe de regex.

Repare que a função regex() ainda pode ser importante se você precisar substituir os padrões, como a diferenciação entre maiúsculas e minúsculas ou a localidade.

# default is case sensitive
"A Mixed Assortment" |> 
    str_replace_all(regex("[aeiou]", ignore_case = TRUE), "*")
#> [1] "* M*x*d *ss*rtm*nt"

Muitos códigos de regex incluem uma barra invertida, como o \s usado acima. Em strings normais do R, o \ precisa ser escapado com uma barra dupla, daí "\\s".

As barras invertidas podem se multiplicar e ficar confusas, então pode ajudar usar strings brutas em casos complexos.

Por padrão, a sintaxe para definir uma string bruta é r"( )", mas outros delimitadores são permitidos. Como parênteses ( ) e colchetes [ ] são comuns na sintaxe de regex (para grupos de captura e grupos de opções), as chaves { } podem ser uma boa escolha.

input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"

Detectar correspondências

Aqui estão algumas perguntas e funções que podem ajudar a respondê-las.

Alguns exemplos usam fruit, um vetor de 80 nomes de frutas incluído no stringr para praticar.

Uma string contém uma correspondência? str_detect(), ou str_starts(), str_ends() para evitar usar as âncoras ^ ou $.

# berries begining with b
fruit |> str_detect("^b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

# alternative syntax
fruit |> str_starts("b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

Retornar apenas as strings que contêm uma correspondência? Use str_subset() para obter as strings, str_which() para obter os índices.

fruit |> str_subset("a...e")
[1] "apple"       "blackberry"  "mandarine"   "nectarine"   "pineapple"   "pomegranate" "raspberry"   "salal berry"
> fruit |> str_which("a...e")
[1]  1  7 48 51 62 64 70 73

Quantas correspondências há em cada string? str_count()

# count vowels
fruit |> str_count("[aeiou]") |> head(10)
#> [1] 2 3 4 3 3 2 2 3 5 3

Onde está a correspondência? str_view() para uso interativo, str_locate() ou str_locate_all() de forma mais geral.

# str_view only shows matching elements, with match in < > delimiters
fruit |> str_view("a...e")
 [1] │ <apple>
 [7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry

# str_locate returns all elements
fruit |> str_locate("a...e") |> head(10)
      start end
 [1,]     1   5
 [2,]    NA  NA
 [3,]    NA  NA
 [4,]    NA  NA
 [5,]    NA  NA
 [6,]    NA  NA
 [7,]     3   7
 [8,]    NA  NA
 [9,]    NA  NA
[10,]    NA  NA

Capturas

Normalmente, queremos saber o que corresponde. Isso é conseguido incluindo grupos de captura entre parênteses dentro da regex.

É possível referenciar capturas dentro da mesma regex, usando \1, \2, etc. Por exemplo, encontrando pares repetidos de letras:

fruit |> str_subset("(..)\\1")
[1] "banana"      "coconut"     "cucumber"    "jujube"      "papaya"      "salal berry"

Com uma sintaxe semelhante, as capturas podem ser usadas em strings de substituição:

# double to triple
fruit |> 
  str_subset("(..)\\1") |> 
  str_replace("(..)\\1", "\\1\\1\\1")
[1] "bananana"      "cococonut"     "cucucumber"    "jujujube"      "papapaya"      "salalal berry"

Reúna as correspondências para processá-las depois usando a função str_match() para apenas a primeira correspondência, ou str_match_all() para todas as correspondências.

Grupos de captura tendem a produzir saídas complicadas em qualquer linguagem. A abordagem do R (que também é vetorizada como um desafio extra) é gerar uma matriz a partir de str_match(), com uma linha por string de entrada, a coluna 1 contendo a string completa correspondida e as colunas restantes contendo os vários grupos de captura.

Usar grupos nomeados pode ser uma vantagem, já que eles se tornam nomes de colunas na matriz de resultados.

recipe <- "dissolve 25g sugar in 200ml water"
recipe |> str_match("(\\d+g) .* (\\d+ml)")
     [,1]                 [,2]  [,3]   
[1,] "25g sugar in 200ml" "25g" "200ml"

# use named capture groups
recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
                          wt    vol    
[1,] "25g sugar in 200ml" "25g" "200ml"

Não falamos muito sobre matrizes neste programa de estudos, embora haja um documento de conceito que você pode acessar. As matrizes não se encaixam bem no ecossistema Tidyverse, apesar de fazerem parte do Base R há muito tempo.

Em resumo: trate matrizes como vetores com 2 índices, na ordem [rows, cols]. Deixe qualquer um dos índices em branco para obter a linha/coluna inteira.

Ao obter uma única linha r com m[r,] ou uma única coluna c com m[, c], o valor retornado é achatado em um vetor por padrão.

> amounts <- recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")

# get all of row 1, as a named vector
> amt1 <- amounts[1,]
> amt1
                                       wt                  vol 
"25g sugar in 200ml"                "25g"              "200ml" 

# get a vector element
amt1["wt"] |> unname()
[1] "25g"

Inevitavelmente, str_match_all() produz uma saída com ainda mais estrutura:

  • O resultado é uma lista, com um elemento por string de entrada.
  • Se houver apenas uma string de entrada, use a indexação [[1]] para converter a saída em um vetor simples, conforme descrito no conceito Listas.
  • Cada elemento é uma matriz, com colunas como em match().
  • Cada linha da matriz é uma captura bem-sucedida.
mix <- "Mix 200g of sugar, 180g of flour and 150g of butter"

# raw output
> str_match_all(mix, "(\\d+)g")
[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# we just want the first list element
> str_match_all(mix, "(\\d+)g")[[1]]
     [,1]   [,2] 
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"

# captures are in column 2, so get that as a vector
> str_match_all(mix, "(\\d+)g")[[1]][,2]
[1] "200" "180" "150"

É claro que as correspondências podem falhar. O resultado será então o valor especial NA. Esteja pronto para testar isso, como descrito no conceito Nothingness.

# failed match
recipe |> str_match("(not here)")
     [,1] [,2]
[1,] NA   NA  
Note

O R for Data Science observa:

str_match() retorna uma matriz, então não é particularmente fácil de trabalhar com ela.

Com a nota de rodapé:

Principalmente porque nunca discutimos matrizes neste livro!

Um dos autores do livro também escreveu a biblioteca stringr, e está, implicitamente, criticando o próprio trabalho.

Será interessante ver como a implementação dessa função evolui em versões futuras.

Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Expressões regulares