Introdução às Expressões Regulares em R

Tue, Mar 17, 2020 9-minute read
  • São utilizadas: Strings e Funções do R-Base e do {Stringr}; Conhecimento prévio necessário.

Parte 1.

One Reason Why

Trabalhar com texto em uma linguagem tão centrada em números quanto o R é um desafio para muitos. A quantidade de desafios a serem enfrentados já é grande desde o inicio, a começar pela falta de materiais disponíveis, e que quando estão disponíveis na maioria das vezes estes possuem somente versões em inglês. Por conta dessa deficiência iniciei este blog. Observando a falta de material relacionado ao Processamento de Linguagem Natural (NLP) em R disponível - em português - na internet, inicio este projeto como uma forma de popular o ecossistema de NLP em português.

Antes de tudo aproveito para lembrar ao leitor que blocos de texto no R são chamados de strings. Uma String não é nada mais que uma cadeia de caracteres, que tem como característica principal estar entre aspas simples (’’) ou aspas duplas ("").

Expressões Regulares

O que são, e motivos para aprender.

A maioria dos leitores deve apreciar um biscoitão caseiro. Sim, um biscoito, aquele que sua mãe fazia nas manhãs de domingo e que você provavelmente tomava com um leite ou algo do tipo. Sua mãe não fazia? Tudo bem, mas ainda assim, esses biscoitos são bons e são intimamente ligados com o assunto desse post.

Quando um biscoito é feito é comum que usemos de um cortador para que ele fique em um formato agradável, alguns gostam de estrelas, outros da lua, os americanos adoram aquele em formato de boneco. Sempre que pressionar o cortador contra o biscoito, parte do biscoito de solta com os contornos iguais aos do cortador. A melhor coisa disso é que isso pode ser repetido em qualquer superficie de biscoito maior que a forma, gerando biscoitinhos semelhantes ao primeiro.

Se você substituir o biscoitão por um texto e o cortador por um tipo de pseudo-código feito para fazer abstrações do texto (qual possui o nome técnico de denotations), você já entendeu o que são as expressões regulares.

Deixando a metáfora de lado, expressões regulares são um conjunto de comandos que possuem como objetivo representar uma construção textual. Estes podem ser usados para diversas operações encontrar um conjunto de palavras em textos de qualquer extensão.

frase = "Eu sou um grande apreciador de bolos e sobremesas doces"

stringr::str_extract(string = frase, pattern = "grande\\sapreciador\\sde\\s\\w{5}")
## [1] "grande apreciador de bolos"

Ou substituir um bloco de texto por outro. Não raro encontramos pessoas que não gostam de bolos, tudo bem, existem outras alternativas!

frase = "Eu sou um grande apreciador de bolos e sobremesas doces"

stringr::str_replace(string = frase, pattern = "b\\w{3}s", replacement = "tortas")
## [1] "Eu sou um grande apreciador de tortas e sobremesas doces"

Mas elas não aparecem somente quando você está lidando com strings, diversas funções aceitam expressões regulares em seus argumentos, como é o caso da função base::list.files(), essa função lista todos os arquivos que estão dentro de um diretório escolhido, mas aceita um argumento chamado “pattern”:

list.files(path = R.home("doc")) ## Essa função irá listar todos os arquivos do diretório.
##  [1] "AUTHORS"          "BioC_mirrors.csv" "COPYING"          "COPYRIGHTS"      
##  [5] "CRAN_mirrors.csv" "FAQ"              "html"             "KEYWORDS"        
##  [9] "KEYWORDS.db"      "manual"           "NEWS"             "NEWS.0"          
## [13] "NEWS.1"           "NEWS.2"           "NEWS.2.rds"       "NEWS.3"          
## [17] "NEWS.3.rds"       "NEWS.pdf"         "NEWS.rds"         "RESOURCES"       
## [21] "THANKS"
list.files(path = R.home("doc"), pattern = ".\\d$") ## Essa função irá listar todos os arquivos do diretório que terminem com ".[NUMERO]"
## [1] "NEWS.0" "NEWS.1" "NEWS.2" "NEWS.3"

Hora do Break para explicações:

Ok, talvez aqui tenhamos um trecho de código complicado para os iniciantes, então vou explicar rapidamente o que ocorre:

  • list.files - A função list.files() lista os arquivos que estão dentro de uma determinada pasta do seu computador. No exemplo, estão preenchidos dois argumentos path e pattern. O primeiro é o endereço da pasta qual você deseja saber quais arquivos tem dentro. O pattern é a expressão regular relacionada aos arquivos que você deseja obter.

  • R.home - A função R.home() retorna ao usuário a pasta onde o R está instalado naquele computador. Aqui usamos ela como exemplo pois é reprodutivel em todo computador.

Continuando…

Como é possível observar nos três exemplos acima, a operação é feita somente na parte do texto que a expressão é compatível e essa é a beleza das Expressões Regulares. Porém, como saber o que significa uma expressão regular, e mais do que isso, como escrever uma? No bloco seguinte tento explicar um pouco da lógica por trás das expressões regulares.

O Básico

Antes de tudo acho necessário recomendar a cheatsheet Work with Strings, que foi feita pelos anjos da RStudio. A Work with Strings é um material valioso para se ter à mão, mesmo que você não tenha conhecimentos avançados da lingua inglesa, visto que ela tem ilustrações demonstrando o funcionamento de cada comando de expressão regular. Qualquer dúvida pode ser solucionada por ela, que também serve como material para que se possa aprender mais.

Iremos estudar expressões regulares por meio de um parágrafo do livro que estou lendo durante esta infame quarentena forçada pelo Covid-19. O nome da obra é “A Ditadura Envergonhada”, escrito por Elio Gaspari (p. 137, 2003), usando ela, iremos explorar as opções disponíveis para extração e substituição de texto.

frase_eg = "Castello queria um ato institucional que durasse só três meses. Assinou três. Queria que as cassações se limitasse a uma ou duas dezenas de dirigentes do regime deposto. Cassou cerca de quinhentas pessoas e demitiu 2 mil. Seu governo durou 32 meses, 23 dos quais sobre a vigência de outros 37 atos complementares, seis deles associados aos poderes de baraço e cutelo do Exxxecutivo."

1. Sem operadores!

Vale lembrar que não é necessário que usemos operadores para que uma expressão regular seja válida - Os critérios técnicos que precisam ser atendidos para uma RegEx ser valida podem ser encontrados aqui. Qualquer palavra pode ser uma expressão regular. Na frase acima, se eu desejar substituir o nome “Castello” por “Barraco”, eu só preciso usar a função stringr::str_replace() para efetuar a substituição.

(frase_eg = stringr::str_replace(string = frase_eg, pattern = "Castello" , replacement = "Barraco"))
## [1] "Barraco queria um ato institucional que durasse só três meses. Assinou três. Queria que as cassações se limitasse a uma ou duas dezenas de dirigentes do regime deposto. Cassou cerca de quinhentas pessoas e demitiu 2 mil. Seu governo durou 32 meses, 23 dos quais sobre a vigência de outros 37 atos complementares, seis deles associados aos poderes de baraço e cutelo do Exxxecutivo."

E voilá, temos um ditador com nome popular, e uma Expressão Regular que funciona.

2. O operador “OU”

Contudo, pode ser de interesse do leitor remover mais de um termo ao mesmo tempo. E para isso é necessário o uso de parentesis () e do operador “ou” |. O parêntesis é necessário para que se evite confusões e que o R saiba qual a ordem das operações e qual a abrangência delas, o “ou” diz quais são as opções. Agora, removeremos trechos do texto usando a função stringr::str_remove()*.

Caso meu objetivo seja remover as palavras “institucional” e “complementares” pois já sei sobre quasi atos estamos falando, pode-se usar o operador, dentro de parentesis com as expressões desejadas.

(frase_eg = stringr::str_remove(string = frase_eg, pattern = "(institucional|complementares)"))
## [1] "Barraco queria um ato  que durasse só três meses. Assinou três. Queria que as cassações se limitasse a uma ou duas dezenas de dirigentes do regime deposto. Cassou cerca de quinhentas pessoas e demitiu 2 mil. Seu governo durou 32 meses, 23 dos quais sobre a vigência de outros 37 atos complementares, seis deles associados aos poderes de baraço e cutelo do Exxxecutivo."
  • Vale notar que str_remove(string = x, pattern = y) e str_replace(string = x, pattern = y, replacement= "") possuem efeitos idênticos, porém é recomendável o uso da primeira por questões de legibilidade.

3. Operador de Repetições

Algumas vezes nós queremos representar por meio de expressões regulares coisas que se repetem. Voltando a nossa frase favorita, percebi que na hora de transcrever ela do livro para o computador cometi um pequeno erro: na última palavra, “executivo” tem 3 vezes a letra “x”; Está escrito “exxxecutivo”. OK, agora eu preciso resolver isso, mas tem solução usando a função stringr::str_replace(). Existem duas possibilidades, a subótima e a ótima. Comecemos com a primeira:

(frase_eg = stringr::str_replace(string = frase_eg, pattern = "xxx", replacement = "x"))
## [1] "Barraco queria um ato  que durasse só três meses. Assinou três. Queria que as cassações se limitasse a uma ou duas dezenas de dirigentes do regime deposto. Cassou cerca de quinhentas pessoas e demitiu 2 mil. Seu governo durou 32 meses, 23 dos quais sobre a vigência de outros 37 atos complementares, seis deles associados aos poderes de baraço e cutelo do Executivo."

No exemplo acima, é possível ver que o pattern = "xxx" funciona e faz o trabalho desejado, porém, esse tipo de construção é confusa e existe uma alternativa melhor. A melhor forma de as chaves, que são um operador de repetições. Ou seja, usando {} é possivel determinar quantas vezes um padrão aparece, ou pode aparecer.

(frase_eg = stringr::str_replace(string = frase_eg, pattern = "x{3}", replacement = "x"))
## [1] "Barraco queria um ato  que durasse só três meses. Assinou três. Queria que as cassações se limitasse a uma ou duas dezenas de dirigentes do regime deposto. Cassou cerca de quinhentas pessoas e demitiu 2 mil. Seu governo durou 32 meses, 23 dos quais sobre a vigência de outros 37 atos complementares, seis deles associados aos poderes de baraço e cutelo do Executivo."

Aqui é possível ver que eu usei {3} para indicar que o x apareceria três vezes. Caso eu queira indicar que é para substituir por x caso o padrão apareça de 1 até 4 vezes, pode-se passar dois argumentos no operador x{j,k}: onde j indica o minimo de vezes que a menor quantidade de vezes que x irá aparecer, e k a maior quantidade de vezes.

poder = c('exxxecutivo', 'exxecutivo', 'exxxxxecutivo')

(frase_eg = stringr::str_replace(string = poder, pattern = "x{2,5}", replacement = "x"))
## [1] "executivo" "executivo" "executivo"

E olha só, nesse último exemplo aprendemos uma coisa nova, as funções do pacote stringr são todas vetorizadas, ou seja, ele realiza operações em vetores sem a necessidade de loops!

Com isso, termino esse primeiro post, já confirmando que haverá uma continuação. Nela abordarei operações em data.frames e o uso de Expressões Regulares dentro de uma Pipeline do Tidyverse. A continuação será publicada no dia 20/03/2020, sexta-feira. Irei colocar o link abaixo assim que publicá-la.

Abraços, Fiquem em casa, mantenham a quarentena!

ATENÇÃO: Esse post possuirá 2 partes que serão publicadas em datas diferentes.