Expressões Regulares e Auditoria: Será que dá match?

O objetivo desse post é apresentar possíveis situações em que o uso de expressões regulares pode ser útil em trabalhos de auditoria e com isso estimular os auditores a conhecer um pouquinho mais sobre esse recurso.

Marcos F. Silva true
02-07-2021

Duas situações para motivação…

Imagine que você está realizando uma auditoria e que após executar alguns testes baseados na Lei de Benford você tenha descoberto que os valores que iniciam pelos caracteres “501 tenham fugido muito ao que seria o esperado.

Para uma análise mais detalhada das não conformidades identificadas você resolve filtrar a base de dados para obter apenas os registros em que os valores da variável objeto de análise inicie com a string50”. Expressões regulares podem ser utilizadas para auxiliar na realização desse filtro.

Uma outra situação em que conhecer expressões regulares pode ser bastante útil é realizar filtragem de bases de dados com base em variáveis cujos valores sejam texto. Um exemplo disso seria uma base de dados composta por notas de empenhos e se deseja filtrar os dados com base na variável que contenha a descrição da despesa para identificar os empenhos que tenham relação, por exemplo, com “COVID-19”.

Essas são duas situações particulares de um caso mais geral que é aplicar filtros a uma base de dados utilizando expressões regulares.

Na prática o uso de expressões regulares é muito mais amplo e auxilia bastante no processo de pré-processamento das bases de dados.

Mas acredito que a essa altura você deva estar se perguntando: “Legal, mas pra mim isso tá parecendo com o recurso de localizar existente no Word. Qual a vantagem de aprender isso?

Diferentemente do recurso de localizar, onde o “match” só ocorre se as strings coincidirem de forma exata, as expressões regulares são muito mais flexíveis para a definição de padrões que descrevam as strings desejadas e isso pode economizar muito tempo, em razão do auditor não precisar testar várias strings semelhantes.

Mas o que é uma expressão regular?

De acordo com o tutorial contido no pacote stringr, expressões regulares são ferramentas para a descrição de padrões em strings de caracteres de forma concisa e flexível.

Não é minha intenção que este post seja um tutorial sobre expressões regulares, mas chamar a atenção dos auditores para esse recurso, mostrar possibilidades de aplicações e com isso estimular um aprofundamento dos conhecimentos. E para isso elenco mais adiante alguns recursos disponíveis na internet.

Vou usar inicialmente alguns exemplos muito simples para fins didáticos e depois faço aplicações em um conjunto de dados mais realista.

Uso funções do pacote stringr para demonstrar o uso das expressões regulares e aproveito para chamar a atenção dos auditores para o fato de que manipular valores textuais é uma habilidade que pode ser muito útil.

Considere o seguinte conjunto de strings:

library(stringr)
library(readr)

nomes<- c("Marcolino", "Marcos", "José", "joão", "Marcio")
nomes
[1] "Marcolino" "Marcos"    "José"      "joão"      "Marcio"   

Vamos agora supor que eu queira identificar nesse conjunto de strings (cada nome é uma string) aquelas que comecem com a letra jota maiúscula ou minúscula. Como deve ser a expressão que ‘casa’ com esse padrão?

A função str_view() do pacote stringr permite verificar para quais strings houve um “match” com o padrão definido pela expressão regular. No exemplo, o padrão é: “string que inicia com a letra jota”.

str_view(string=nomes, pattern="^[Jj]")

Como é possível ver pelo resultado, as strings José e joão ‘casaram’ com a expressão regular ^[Jj] que define o padrão desejado, ou seja, iniciam com a letra jota, seja ela maiúscula ou minúscula.

A definição de expressões regulares faz uso de caracteres que possuem significado especial os quais são denominados de metacaracteres. Os metacaracteres ^ e [] tem funções bem específicas.

A indicação de que a letra jota deve estar no ínício da string é dada pelo metacaractere ^, enquanto a indicação que o jota poderia ser maiúsculo ou minúsculo é dada pelo metacaractere [] que, no exemplo, informa que a string pode iniciar com qualquer dos caracteres em seu interior.

E se quisermos identificar as strings que terminem com “o” ou “os”?

str_view(string=nomes, pattern="os?$")

Esse exemplo, embora ainda simples, já começa a indicar que as expressões regulares podem ser bem complexas. E de fato são.

Nesse exemplo, aparecem mais dois novos metacaracteres: $ e ?. O metacaractere $ é responsável por indicar que os caracteres desejados devem estar no final da string enquanto o metacaractere ? informa que o caractere que o precede é opcional, podendo aparecer ou não na string. Assim, essa expressão regular define o seguinte padrão: strings que terminem com o caractere o sucedido ou não do caractere s.

str_view(string=nomes, pattern="[nc]os?$")

No exemplo acima, a expressão regular define o seguinte padrão: o caractere o precedido dos caracteres n ou c e sucedido ou não do caractere s no final da string.

Naturalmente que existem muitos outros metacaracteres que não foram abordados nesses exemplos simples cujo objetivo é apenas passar a ideia do que sejam expressões regulares.

Convido o leitor a consultar as referências elencadas mais adiante para um tratamento mais aprofundado do assunto.

Agora vou usar um conjunto de dados um pouco mais realista. Trata-se de uma relação de empenhos obtida no portal da transparência do Tribunal de Contas do Estado da Paraíba.

empenhos <- read_csv("_EmpenhosLista.csv")
head(empenhos)
# A tibble: 6 x 9
  DT_EMPENHO DS_TIPO   NumNE       CD_ORGAO  ElementoDespesa DS_CREDOR
  <chr>      <chr>     <chr>       <chr>     <chr>           <chr>    
1 23/01/2020 PRINCIPAL 2020NE00001 VALOR DA~ 11-VENCIMENTOS~ 08.778.2~
2 23/01/2020 PRINCIPAL 2020NE00002 VALOR DA~ 11-VENCIMENTOS~ 08.778.2~
3 23/01/2020 PRINCIPAL 2020NE00003 VALOR DA~ 11-VENCIMENTOS~ 08.778.2~
4 23/01/2020 PRINCIPAL 2020NE00004 VALOR DA~ 05-OUTROS BENE~ 08.778.2~
5 23/01/2020 PRINCIPAL 2020NE00005 VALOR DA~ 11-VENCIMENTOS~ 08.778.2~
6 23/01/2020 PRINCIPAL 2020NE00006 VALOR DA~ 11-VENCIMENTOS~ 08.778.2~
# ... with 3 more variables: Valor_Despesa1 <chr>, Textbox7 <chr>,
#   Valor_Despesa <chr>

Durante o processo de exportação dos dados do site, algumas colunas indesejadas foram exportadas também, são elas: Textbox7 e Valor_Despesa. Vou eliminar essas colunas da base de dados:

library(dplyr)
empenhos <- empenhos %>% 
              select(-Textbox7, -Valor_Despesa)

glimpse(empenhos)
Rows: 6,781
Columns: 7
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <chr> "4.999.161,91", "2.388,72", "103.288,98", "1~

A variável Valor_Despesa1 foi importada como caractere em vez de número. Com o uso de expressões regulares é possível fazer o pré-processamento com vistas à conversão para o formato numérico.

Várias funções no R recebem como argumentos expressões regulares. No código a seguir vou utilizar a função str_replace_all() do pacote stringr para realizar a conversão da variável Valor_Despesa1 para o formato numérico.

Essa função recebe como argumentos a variável contendo as strings, a expressão regular definindo o padrão a ser ‘casado’ e a string a ser utilizada para substituir a string que deu match com o padrão especificado.

Para que a conversão possa ocorrer será necessário: (1) “remover” os pontos e (2) “substituir” as vírgulas por pontos.

No código a seguir eu utilizo a expressão regular \\. para ‘casar’ os pontos e substituir por uma string nula.

empenhos <- empenhos %>% 
              mutate(Valor_Despesa1 = str_replace_all(Valor_Despesa1, "\\.", ""))
glimpse(empenhos)
Rows: 6,781
Columns: 7
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <chr> "4999161,91", "2388,72", "103288,98", "14245~

Como pode ser visto, os pontos foram removidos. A expressão regular \\. ‘casa’ os pontos. Como o ponto tem um significado especial em expressões regulares, para que seja possível casar o ‘ponto literal’ é necessário colocar as duas barras antes dele. Em expressões regulares, o ponto tem a função de ‘casar’ com qualquer caractere.

Agora é necessário substituir a vírgula por ponto.

empenhos <- empenhos %>% 
              mutate(Valor_Despesa1 = str_replace_all(Valor_Despesa1, ",", "."))
glimpse(empenhos)
Rows: 6,781
Columns: 7
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <chr> "4999161.91", "2388.72", "103288.98", "14245~

As vírgulas foram substituídas por pontos. Agora é só converter a variável para o formato numérico.

empenhos <- empenhos %>% 
              mutate(Valor_Despesa1 = as.numeric(Valor_Despesa1))
glimpse(empenhos)
Rows: 6,781
Columns: 7
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <dbl> 4999161.91, 2388.72, 103288.98, 14245.66, 32~

Agora a variável Valor_Despesa1 está no formato numérico.

Continuando com nosso exemplo, vamos supor que eu queira filtrar a base de dados de forma que apenas os valores dos empenhos iniciados por “50” sejam selecionados. Usando a expressão regular já vista anteriormente posso fazer isso da seguinte forma:

empenhos_50 <- empenhos %>% 
                  filter(str_detect(Valor_Despesa1, "^50"))
knitr::kable(head(empenhos_50))
DT_EMPENHO DS_TIPO NumNE CD_ORGAO ElementoDespesa DS_CREDOR Valor_Despesa1
12/02/2020 PRINCIPAL 2020NE00253 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.807.644- - JOSE PATRICIO DA SILVA 50
12/02/2020 PRINCIPAL 2020NE00254 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.807.644- - JOSE PATRICIO DA SILVA 50
12/02/2020 PRINCIPAL 2020NE00257 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.421.508- - TIAGO FRANCISO DE SOUSA DA SILVA 50
12/02/2020 PRINCIPAL 2020NE00258 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.421.508- - TIAGO FRANCISO DE SOUSA DA SILVA 50
13/02/2020 PRINCIPAL 2020NE00261 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.421.508- - TIAGO FRANCISO DE SOUSA DA SILVA 50
13/02/2020 PRINCIPAL 2020NE00262 IMPORTANCIA EMPENHADA PARA A REALIZACAO DA DESPESA COM PAGAMENTO DE DIARIA CONFORME MAPA DE CONCESSAO EM ANEXO 14-DIÁRIAS - CIVIL *.421.508- - TIAGO FRANCISO DE SOUSA DA SILVA 50

Mais um exemplo. A variável DS_CREDOR possui a identificação do credor do empenho. Essa identificação consiste do número do CNPJ seguido do nome do fornecedor caso esse seja pessoa jurídica. No caso de pessoa física, essa descrição consiste de parte do CPF seguido do nome da pessoa.

Vamos supor que eu queira criar um novo campo na base de dados contendo apenas o CNPJ. Como eu posso fazer isso usando expressões regulares? Eu preciso criar uma expressão regular que defina o padrão de um CNPJ, que é: dois dígitos, ponto, três digitos, ponto, três dígitos, barra, três zeros, um, hífen, dois dígitos. Como eu crio uma expressão regular que “case” com esse padrão?

Uma possibilidade:

padrao_cnpj <- "\\d{2}\\.\\d{3}\\.\\d{3}/0001-\\d{2}"

Explicando um pouco. O metacaractere \\d{n} indica que o padrão buscado é n dígitos. O \\. indica que queremos ‘casar’ o ponto. Como o ponto possui um significado especial nas expressões regulares (é um metacaractere), é necessário precedê-lo com as duas barras.

Vamos agora criar a nova coluna.

empenhos <- empenhos %>% 
              mutate(CNPJ_CREDOR = str_extract(DS_CREDOR, padrao_cnpj))
glimpse(empenhos)
Rows: 6,781
Columns: 8
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <dbl> 4999161.91, 2388.72, 103288.98, 14245.66, 32~
$ CNPJ_CREDOR     <chr> "08.778.250/0001-69", "08.778.250/0001-69", ~

A nova coluna foi criada apenas com o CNPJ como desejado. Mas vamos supor ainda que eu queira cruzar esta base de dados com uma outra tomando o CNPJ como chave para o cruzamento. Ocorre que nessa outra base o CNPJ está sem pontuação, ou seja, os CNPJ aparecem dessa forma: “08778250000169”. Assim eu preciso remover a pontuação na variável recém criada. Mais uma vez vou utilizar expressão regular.

empenhos <- empenhos %>% 
              mutate(CNPJ_CREDOR = str_remove_all(CNPJ_CREDOR, "[[:punct:]]"))
glimpse(empenhos)
Rows: 6,781
Columns: 8
$ DT_EMPENHO      <chr> "23/01/2020", "23/01/2020", "23/01/2020", "2~
$ DS_TIPO         <chr> "PRINCIPAL", "PRINCIPAL", "PRINCIPAL", "PRIN~
$ NumNE           <chr> "2020NE00001", "2020NE00002", "2020NE00003",~
$ CD_ORGAO        <chr> "VALOR DA FOLHA DE PESSOAL - DEMAIS DA EDUCA~
$ ElementoDespesa <chr> "11-VENCIMENTOS E VANTAGENS FIXAS - PESSOAL ~
$ DS_CREDOR       <chr> "08.778.250/0001-69 - SECRETARIA DE ESTADO D~
$ Valor_Despesa1  <dbl> 4999161.91, 2388.72, 103288.98, 14245.66, 32~
$ CNPJ_CREDOR     <chr> "08778250000169", "08778250000169", "0877825~

A expressão regular [[:punct:]] “casa” com os sinais de pontuação.

Mais um exemplo para finalizar. A variável CD_ORGAO contém a descrição da despesa objeto do empenho. Como poderíamos identificar os empenhos que se refiram a compra de merenda escolar, por exemplo?

No código a seguir vou usar expressão regular para “casar” a string MERENDA na descrição da despesa.

empenhos_merenda <- empenhos %>% 
                      filter(str_detect(CD_ORGAO, "MERENDA")) 

O novo conjunto de dados empenhos_merenda contém apenas os registros referentes aos empenhos em que a string MERENDA aparece na descrição da despesa.

Espero que estes exemplos tenham dado uma ideia do poder que as expressões regulares trazem para a análise de dados e, consequentemente, para a auditoria e que o post de modo geral tenha aguçado sua curiosidade para aprender mais sobre esta ferramenta fantástica.

Onde aprofundar os conhecimentos

Com uma rápida pesquisa na internet é possível encontrar uma grande quantidade de material sobre expressões regulares.

Listo a seguir alguns materiais que vão te ajudar a aprofundar o conhecimento sobre esse tópico.


  1. Nesse post vou adotar a seguinte terminologia: um caractere pode ser qualquer dígito, letra, pontuação, etc e uma string será um conjunto de caracteres. Assim, “@marcos2006”, “123456” e “Apt. 708” são strings↩︎

Citation

For attribution, please cite this work as

Silva (2021, Feb. 7). Audinalytics: Expressões Regulares e Auditoria: Será que dá match?. Retrieved from audinalytics.netlify.app/posts/2021-02-04-expresses-regulares-e-auditoria-ser-que-d-match/

BibTeX citation

@misc{silva2021expressões,
  author = {Silva, Marcos F.},
  title = {Audinalytics: Expressões Regulares e Auditoria: Será que dá match?},
  url = {audinalytics.netlify.app/posts/2021-02-04-expresses-regulares-e-auditoria-ser-que-d-match/},
  year = {2021}
}