Skip to Tutorial Content

Créditos imagem: freepik

Identificação de padrões

Neste tutorial, iremos trabalhar com a identificação, extração e substituição de expressões textuais a partir de padrões por meio de um motor chamado Expressões Regulares ou regex. Regex usa uma série de operadores a fim de encontrar tais padrões textuais e manuseá-los. Esses operadores ajudam a singularizar certos padrões, extraí-los do texto, substituí-los, contá-los ou simplesmente confirmar sua presença.

Por exemplo, suponha que você queira extrair somente os números do CNPJ. Como sabemos, o número do CNPJ geralmente vem com os números separados por pontos, barra e hífen.

\[ 23.268.449/0001-30 \] Com regex você pode separar números dos demais caracteres ou vice-versa.

Nós utilizaremos as funções do pacote stringr para operar com regex. Todas as funções possuem basicamente dois ou três argumentos. O primeiro com a expressão, o segundo com o padrão a ser encontrado, o terceiro, no caso de substituição, com o novo texto.

Regex utiliza vários operadores e metacaracteres para facilitar o encontro de padrões. Esses operadores e metacaracteres operam como abstrações de caracteres. Abaixo listamos os principais. Sugiro que retorne sempre a eles, quando estiver em dúvida sobre qual operador ou metacaractere utilizar.

padrao descricao
\\d Número
\\w Número ou letra inclusive o sublinhado _
\\s Espaço em branco, inclusive quebra de linha, tab, retorno de carro
[a-zA-Z0-9] Número ou letra
. Qualquer caractere
\\X Qualquer caractere, inclusive quebra de linha, tab e retorno de carro
* Caractere precedente zero ou mais vezes
+ Caractere precedente uma ou mais vezes
? Caractere precedente zero ou uma vez (pode também tornar * e o + operadores preguiçosos)
[:punct:] Pontuação
\\W Negação de \\w
\\D Negação de \\d
\\S Negação de \\s
[:alpha:] Somente letras
[a-zA-Z] Somente letras
\\p{L} Somente letras
^ Início da linha
$ Final da linha
^$ Vazio, nada
{n} Caractere precedente n vezes
{n,} Caractere precedente no mínimo n vezes
{m,n} Caractere precedente no mínimo m vezes, no máximo n vezes
\n Quebra de linha
\t Tab
\r Retorno de carro
(…) Grupo de captura
[…] Classe caractere. Torna os caracteres alternativos, ex. [oa] pega o ou a
[^...] Negação de […], ou seja, não pega os caracteres
(?:) Ignorar grupo de captura
(?i) Ignorar maiúsculas e minúsculas
(?<=) Lookbehind: buscar apenas se, e depois de, ocorrer determinado padrão
(?=) Lookahead: buscar apenas se, e antes de, ocorrer determinado padrão
(?<!) Negative lookbehind: buscar apenas se antes não ocorrer determinado padrão
(?!) Negative lookahead: buscar apenas se depois não ocorrer determinado padrão
| Ou
\\b Limite de palavra
\\n Back reference: retorna o n grupo de captura

Stringr

O pacote stringr possui um conjunto de funções para o uso de regex. Iremos passar pelas principais. Vamos carregar este pacote:

library(stringr)

Extração: str_extract

A principal função para extração de padrões é str_extract e sua versão para mais de uma linha: str_extract_all. Vamos começar por exemplos simples. Suponha que você tenha um vetor de endereços como este:

endereco <- c("Avenida Paulista, 458, apto 1070, cep 01500-000, município de São Paulo, estado de são Paulo","rua Padrão, 658, cep 01200-017, Sao Paulo, estado de Sao paulo")

Vamos extrair apenas o cep. A primeira coisa a fazer é observar o que queremos, se existe um padrão para o cep que não se confunde com os demais. É fácil ver que o cep é composto por cinco números, seguidos de um hífen, seguido de três números. Consulte a tabela para saber como extrair números. Vamos tentar:

str_extract(endereco,"\\d{5}-\\d{3}")
## [1] "01500-000" "01200-017"

Observe que eu informe que quero cinco números, entre chaves, depois um hífen e, por fim, três números.

Sua vez

Extraia a primeira sequência de números de cada elemento do vetor endereco.

str_extract(endereco,"\\d+")

A função str_extract funciona bem para situações de apenas uma ocorrência e o padrão a ser encontrado não está depois de uma quebra e linha. Quando houver mais de uma ocorrência no mesmo texto, você tem de usar str_extract_all. Vejamos um exemplo com a primeira estrofe desse belo poema de Camões:

poema <- "Alma minha gentil, que te partiste
Tão cedo desta vida descontente,
Repousa lá no Céu eternamente,
E viva eu cá na terra sempre triste."

Iremos extrair todos os advérbios que terminam em “ente”. O primeiro está na segunda linha, o segundo, na terceira.

str_extract_all(poema, "\\w+ente")
## [[1]]
## [1] "descontente" "eternamente"

Note que eu usei \\w+, ou seja, extraia qualquer letra ou número uma ou mais vezes, seguidas de “ente”. Você poderia alcançar o mesmo resultado, usando [a-z]+ente, [:alpha:]+ente, [:alnum:]+ente ou \\p{L}+ente.

O retorno é uma lista. Esta é uma outra diferença entre as duas versões de extrair.

Sua vez

Extraia as variações de vida ou viver.

str_extract_all(poema,"vi\\w+")

Substituicão: str_replace

Por vezes, você não quer extrair nada, mas sim substituir algo dentro de um texto. As funções str_replace e str_replace_all são suas amigas. Diferentemente das anteriores, que esperam dois e apenas dois argumentos, estas esperam três e apenas três argumentos: o texto, o padrão e a substituição do padrão.

Vamos retornar ao vetor de endereços. Suponha que você queira substituir a vírgula que separa o logradouro do seu número pela palavra “número”. A primeira coisa a fazer é observar o que esta vírgula tem de diferente na sua própria sentença e o que ela tem de comum com as demais sentenças do vetor. De cara, da para ver que ela é a primeira ocorrência de uma vírgula no texto Assim fica fácil:

str_replace(endereco,","," número")
## [1] "Avenida Paulista número 458, apto 1070, cep 01500-000, município de São Paulo, estado de são Paulo"
## [2] "rua Padrão número 658, cep 01200-017, Sao Paulo, estado de Sao paulo"

Veja que eu adicionei um espaço antes da palavra número. Você também pode usar [:punct:] para identificar a vírgula:

str_replace(endereco,"[:punct:]"," número")
## [1] "Avenida Paulista número 458, apto 1070, cep 01500-000, município de São Paulo, estado de são Paulo"
## [2] "rua Padrão número 658, cep 01200-017, Sao Paulo, estado de Sao paulo"

Vejamos um exemplo com str_replace_all. Se você imprimir no console a estrofe do poema de Camões, verá que as frases estão separadas por “”, ou seja, quebra de linha. Vamos substituí-las por espaços:

str_replace_all(poema, "\n"," ")
## [1] "Alma minha gentil, que te partiste Tão cedo desta vida descontente, Repousa lá no Céu eternamente, E viva eu cá na terra sempre triste."

Uma pequena confusão feita por quem está aprendendo regex é pensar que o terceiro argumento, a substituição, tem de ser um padrão regex também. Na verdade, o terceiro argumento é literal. Por exemplo, vamos substituir todas as vírgulas por ponto:

endereco2 <- str_replace_all(endereco,",",".")

Se você quiser reverter, não pode usar a mesma sintaxe. Lembre que ponto em regex é qualquer caractere. Então, terá de dar um escape nele.

str_replace_all(endereco2, "\\.",",")
## [1] "Avenida Paulista, 458, apto 1070, cep 01500-000, município de São Paulo, estado de são Paulo"
## [2] "rua Padrão, 658, cep 01200-017, Sao Paulo, estado de Sao paulo"

Sua vez

Substitua o hífen do cep por espaço.

str_replace(endereco,"-"," ")

Remoção: str_remove

Dada a frequência com o que ocorre, o pacote sringr possui um par de funções para substituir um padrão por nada, ou seja, remover determinado padrão. Os comandos str_remove e str_remove_all não são nada mais do que atalhos para str_replace e str_replace_all respectivamente.

Irei deixar para você mesma trabalhar com ele.

Sua vez

Remova todos os caracteres não numéricos do CNJP mencionado no início do tutorial.

str_remove_all("23.268.449/0001-30","\\D")

Contagem: str_count

Por vezes, você quer contar o número de ocorrência de um caractere ou de uma sequência de caracteres. Para tanto, você usa str_count. Por exemplo, para contar o número de ocorrências de Paulo:

str_count(endereco, "(?i)paulo")
## [1] 2 2

Perceba que eu adicionei “(?i)” antes do padrão. Isso porque, eventualmente, e de fato ocorreu, a palavra Paulo pode vir em minúsculo ou maiúsculo. Esse padrão pede para ignorar.

Sua vez

Verifique quantas vezes a palavra São aparece em cada sentença do vetor endereco. Note que “São” pode aparecer com ou sem acento e iniciar por minúsculo ou maiúsculo.

str_count(endereco, "(?i)s[ãa]o")

Localização: str_locate

Caso você queira saber onde se encontra o padrão desejado, use as funções str_locate e str_locate_all. Por exemplo, vamos localizar onde se encontra a primeira sequência de números no endereço:

str_locate(endereco,"\\d+")
##      start end
## [1,]    19  21
## [2,]    13  15

Se quiser localizar onde se encontram todas as sequências de números no endereço:

str_locate_all(endereco,"\\d+")
## [[1]]
##      start end
## [1,]    19  21
## [2,]    29  32
## [3,]    39  43
## [4,]    45  47
## 
## [[2]]
##      start end
## [1,]    13  15
## [2,]    22  26
## [3,]    28  30

Sua vez

Localize a posição da palavra cep no endereço.

str_locate(endereco, "cep")

Detecção: str_detect

Outra função extremamente útil é str_detect. Ela retorna TRUE caso o padrão desejado se encontre no texto. Vamos verificar se a palavra padrão se encontra no texto.

str_detect(endereco,"(?i)padrão")
## [1] FALSE  TRUE

Veja que a palavra consta apenas do segundo texto.

Esta função é particularmente útil dentro de ifelse do R base ou case_when do dplyr. Imagine que você tenha um dataframe e queira preencher uma coluna com base nos em padrões encontrados em outra. Vamos criar uma dataframe com a quantidade de drogas apreendida e criar duas novas colunas com o tipo de droga e outra associando a quantidade a um grupo.

apreensao
20 gramas de maconha
crack pesando 2grs
pinos de cocaína com 0,5 gramas cada
30 gramas de crack
papelotes de cocaina com 1 grama
cannabis 6 gramas

Com str_detect fica fácil:

drogas <- drogas %>% 
  mutate(quantidade = case_when(
    str_detect(apreensao, "[2-9]\\d+") ~ "média",
    str_detect(apreensao,"0,\\d*") ~ "mínima",
    str_detect(apreensao,"\\b[1-9]\\D") ~ "pequena",
    TRUE ~ NA_character_
  ))
knitr::kable(drogas)
apreensao quantidade
20 gramas de maconha média
crack pesando 2grs pequena
pinos de cocaína com 0,5 gramas cada mínima
30 gramas de crack média
papelotes de cocaina com 1 grama pequena
cannabis 6 gramas pequena

Explicando:

  1. User mutate do dplyr para criar a coluna chamada quantidade.
  2. Usei case_when do dplyr, que funciona como um ifelse múltiplo para controlar o fluxo.
  3. Usei str_detect dentro do case_when para, para procurar um padrão no texto, por exemplo, se houver um número que começa entre 2 e 9, a quantidade é média. Se houver um número que começa com zero, seguido de vírgula, a quantidade é mínima e se houver um único número de 1 a 9 seguido por um não número, a quantidade é pequena.

Vamos criar uma nova coluna com o tipo de droga:

drogas <- drogas %>% 
  mutate(tipo_droga = case_when(
    str_detect(apreensao,"(?i)maconha|cannabis") ~ "maconha",
    str_detect(apreensao,"(?i)coca[íi]na") ~  "cocaína",
    str_detect(apreensao,"(?i)crack") ~ "crack"
    
  ))
knitr::kable(drogas)
apreensao quantidade tipo_droga
20 gramas de maconha média maconha
crack pesando 2grs pequena crack
pinos de cocaína com 0,5 gramas cada mínima cocaína
30 gramas de crack média crack
papelotes de cocaina com 1 grama pequena cocaína
cannabis 6 gramas pequena maconha

Repare que maconha pode aparecer escrita como maconha ou como cannabis, por isso eu usei a barra vertical. Algumas letras podem aparecer em maiúsculo ou minísculos. Cocaína pode aparecer com ou sem acento.

filtragem: str_subset

Similar a str_detect, str_subset mantêm somente os textos que contêm o padrão:

str_subset(endereco,"(?i)padrão")
## [1] "rua Padrão, 658, cep 01200-017, Sao Paulo, estado de Sao paulo"

Indexação: str_which

A função str_which devolve o índice dos textos no vetor onde se encontram o padrão:

str_which(endereco,"(?i)padrão")
## [1] 2

Além dessas funções, há uma outra muito útil chamada str_match. Falaremos dela mais adiante quando tratarmos de grupos de captura.

Literalidade

Se você observar na tabela inicial, perceberá que alguns caracteres são tratados como metacaracteres. Exemplos típicos são o ponto, o sinal de interrogação, o sinal de mais e o asterisco. Caso você queria encontrar esses caracteres em sua literalidade, ou seja, o ponto como ponto e a interrogação como interrogação, é necessário dar um escape neles. Veja o vetor abaixo:

telefones <- c("(11)12345-6789","(98)98765.9865")

Se você quiser substituir o ponto por hífen, sem dar escape, terá uma desagradável surpresa:

str_replace(telefones,".","-")
## [1] "-11)12345-6789" "-98)98765.9865"

Para tratar o ponto como ponto, faça isso:

str_replace(telefones,"\\.","-")
## [1] "(11)12345-6789" "(98)98765-9865"

Sua vez

Substitua os parênteses por espaço no vetor de endereços. Dica, use | indicar alternativa.

str_replace_all(telefones,"\\(|\\)"," ")

Uma outra maneira de indicar literalidade, é colocar o padrão entre colchetes. No entanto, observe que colchetes têm dupla função em regex. Uma é indicar qualquer um dos caracteres dentro dele, outra é tomar o caractere em sua literalidade. O exemplo abaixo funciona:

str_replace_all(telefones,"[()]"," ")
## [1] " 11 12345-6789" " 98 98765.9865"

Sua vez

Substitua o ponto por hífen, como fizemos no início desta seção, mas usando colchetes em vez de escape.

str_replace(telefones,"[.]"," ")
## [1] "(11)12345-6789" "(98)98765 9865"

Lookaround

Uma ferramenta muito poderosa em regex é lookaround, que basicamente é considerar ou desconsidear um padrão antes ou depois de outro padrão. Há quatro modalidades de lookaround, passaremos uma por uma.

Lookbehind

Com lookbehind, você considera um padrão somente se ele ocorrer depois de outro padrão. Por exemplo, vamos pegar a sequência numérica que ocorre antes do hífen no endereço.

str_extract(endereco,"(?<=-)\\d+")
## [1] "000" "017"

A sintaxe de lookbehind é sempre “(?<=padrão_referência)padrão_extraível”.

Lookahead

Como lookahead, você consera um padrão somente se ele ocorrer antes de outro padrão. Vamos pegar a sequência de números que vem antes do hífen no cep de endereço.

str_extract(endereco,"\\d+(?=-)")
## [1] "01500" "01200"

Negative lookbehind

Negative lookbehind faz o contrário de loodbehind, ou positive lookbehind, extraí, identifica ou substitui o padrão somente se ele não ocorrer depois de certo padrão.

str_extract(endereco,"(?<!-)\\d+")
## [1] "458" "658"

Note que a diferença é que substituímos o sinal de igual pelo sinal de exclamação. A primeira sequência de números foi extraída, pois ela não segue um hífen. Porém, se quisermos extrair todas as sequências numéricas que não vêm depois do hífen, teremos surpresa:

str_extract_all(endereco,"(?<!-)\\d+")
## [[1]]
## [1] "458"   "1070"  "01500" "00"   
## 
## [[2]]
## [1] "658"   "01200" "17"

Perceba que ele apenas ignorou o primeiro número. Esta é uma limitação das formas negativas de lookaround no R, pois elas não fazem backtrack, ou seja, limitam-se a uma única ocorrência.

Negative lookbehind

Com negative lookbehind, você igualmente pode ter surpresas. Por ele, pegamos somente um único padrão não seguindo pelo padrão de referência. Por exemplo, vamos tentar pegar todas as sequências numéricas não seguidas por hífen.

str_extract_all(endereco,"\\d(?!-)")
## [[1]]
##  [1] "4" "5" "8" "1" "0" "7" "0" "0" "1" "5" "0" "0" "0" "0"
## 
## [[2]]
##  [1] "6" "5" "8" "0" "1" "2" "0" "0" "1" "7"

Note que somente o último número da sequência antes do hífen foi ignorado.

Back Reference

Back Reference é um recurso bastante usado para encontrar a mesma sequência e caracteres novamente. É particularmente útil para inserir caracteres dentro de um texto. Vejamos o exemplo do CNPJ: “23.268.449/0001-30”. Suponha que esse CNPJ tenha vindo no seguinte formato: 23268449000130. Se quisermos inserir a pontuação, usamos grupos de captura para cada sequência de números e usamos str_replace para substituir o que vem entre eles, ou seja, nada, pelo caractere desejado.

str_replace("23268449000130","(\\d{2})(\\d{3})(\\d{3})(\\d{4})(\\d{2})","\\1.\\2.\\3/\\4-\\5")
## [1] "23.268.449/0001-30"

Note que eu defini cinco grupos de captura, o primeiro contendo os dois primeiros números, o segundo contendo os três números seguintes, o terceiro contendo os próximos três números, seguido por um quarto grupo contendo quatro números e, por fim, o quinto e último grupo contendo os dois últimos números. Depois disso, na substituição, eu chamei cada um desses grupos, seguidos pela pontuação desejada.

Operadores gulosos e preguiçosos

Os operadores * e + são considerados operadores gulosos, ou seja, buscam tudo mesmo que haja um lookahead na frente. Suponha que você queira extrair o logradouro sem o número. A princípio a solução seria pegar qualquer caractere até encontrar a vírgula.

str_extract(endereco, ".+(?=,)")
## [1] "Avenida Paulista, 458, apto 1070, cep 01500-000, município de São Paulo"
## [2] "rua Padrão, 658, cep 01200-017, Sao Paulo"

Note que ele irá continar mesmo encontrando a vírgula. Isso porque a vírgula está incluída no metacaractere ponto. Se você quiser pedir para ele parar na primeira vírgula, inclua o sina de interrogação depois do sinal de mais para torná-lo preguiçoso:

str_extract(endereco,".+?(?=,)")
## [1] "Avenida Paulista" "rua Padrão"

Agora, sim, tivemos o retorno esperado.

Dupla função

Você deve ter notado ao longo do tutorial que alguns metaracteres cumprem mais uma função. Por exemplo, o sinal de interrogação às vezes opera como operador zero ou um, às vezes opera como operador preguiçoso. O hífen opera como metacaracteres somente dentro de colchetes para indicar sequência de letras ou de números.

Operadores OU e AND

É comum queremos saber se um padrão ou outro consta no texto. Nesse caso, usamos a barra vertical para informar cada um dos padrões. Exemplo:

str_detect(endereco, "cep|paulo")
## [1] TRUE TRUE

No exemplo acima, como “cep” está em ambos endereços, o retorno é TRUE, pois não importa se “paulo” está ou não.

No entanto, por vezes, queremos garantir que dois padrões estejam presentes. Se você sabe a ordem em que eles devem estar presentes, uma solução é esta:

str_detect(endereco, "cep.*paulo")
## [1] FALSE  TRUE

No entanto, esta solução não ajuda se não sabemos qual dos padrões vem primeiro. Nesses casos, precisamos recorrer a lookahead para identificá-los:

str_detect(endereco,"(?=.*paulo)(?=.*cep)")
## [1] FALSE  TRUE

Note que, mesmo “paulo” vindo depois de “cep” no texto, a função retorna TRUE para o segundo endereço porque a ordem de aparição não importa. Isso ocorre porque o lookahead interrompe a busca de modo que o padrão dentro do segundo lookahead constitui uma nova busca.

Isso tem implicações. Se você tiver muitos ANDs, sua busca poderá ser bastante lenta.

Introdução ao regex com R