João Laitano
24/7/2020

[Rr]eg(ular)?\s?[Ee]x(pressions)?

Uma das tarefas mais frequentes que um cientista de dados tem que enfrentar é a de limpeza e pré-processamento de bases de dados. Muitas vezes (senão todas), as variáveis presentes nestas bases são palavras ou longas sequências de texto representadas por strings. Processar e limpar estas variáveis até se obter o resultado desejado pode se tornar uma tarefa enfadonha utilizando apenas python ou bibliotecas mais genéricas como pandas. Felizmente existem algumas ferramentas que tornam todo o processamento de strings muito mais fácil. Uma das ferramentas mais utilizadas e testadas ao longo do tempo são as Regular Expressions ou regex.

Regular expressions são basicamente padrões de texto representados por uma sintaxe especial utilizados para automatizar a procura e substituição de texto.

Regex já é utilizado há muito tempo e existe todo um embasamento matemático que vem do campo de teoria dos autômatos pertencente a área de Ciência da Computação, dito isto neste artigo não iremos abordar esta parte teórica.

Em um primeiro encontro regular expressions podem parecer visualmente intimidadoras e por esta razão muitos cientistas de dados fogem delas e acabam utilizando soluções mais desajeitadas. Porém aprender, e adorar, regular expressions pode se mostrar um investimento valioso de tempo visto que:

  • A partir do momento que se consolida alguns dos principais conceitos e padrões é possível realizar operações complexas envolvendo dados em formato string de maneira bem mais concisa e elegante.
  • Possuem tempo de execução bem mais veloz do que as soluções mais manuais.
  • Regular Expressions estão disseminadas em quase todas as linguagens de programação, ferramentas de tratamento de dados e utilidades de linhas de comando.

Raw strings

Antes de começar a falar sobre regex é bom introduzir o conceito de raw strings em python, dado que é uma boa prática sempre colocar os padrões regex em raw strings. De maneira simples raw strings são strings que vão acabar tratando backslash(\) de maneira literal e isso impedira que o caractere especial seja considerado como um caractere de escape. Como veremos adiante muitos metacaracteres de regex utilizam backslash(\) . A sintaxe das raw strings é simplesmente colocar um r (maiúsculo também é válido) antes de uma string:

#raw string
r”string”

Ex:

# string normal
>>> print("Amora\b faz bem")
Amor faz bem
# raw string
>>> print(r"Amora\b faz bem")
Amora\b faz bem

Ao utilizar raw strings evita-se possíveis erros nos padrões com caracteres especiais que usam backslash e o código se torna mais legível.

Regex

Quando se fala em regex o termo padrão é dito para descrever uma regular expression que foi escrita. Se o padrão é achado no meio do string que se está procurando diz-se que o padrão “deu match”. Se fosse desejado achar o string “me” em outros strings o padrão em regex seria simplesmente me:


É importante mencionar que as strings procuradas só vão dar match no que foi literalmente fornecido no padrão, nesse caso letras com acento e letras maiúsculas são consideradas diferentes de letras sem acento e letras minúsculas.

Módulo re do python

Python possui um módulo proṕrio para para regex chamado de módulo re. Este módulo possui um conjunto de funções e classes próprias para se trabalhar com regex. Uma das funções mais usadas é a re.search() onde o primeiro parâmetro é o padrão e o segundo é a string.

>>> match = re.search("me", "medalha")
>>> print(match)
<re.Match object; span=(0, 2), match='me'
>>>> match = re.search("me", "garrafa")
>>> print(match)
None

É possível observar acima que um objeto match é retornado quando ocorre o match, já quando o match não ocorre None é retornado. Objetos match podem ser considerados como um True booleano.

>>> lista = ['Câmera', 'alface', 'nova era', 'tomate',
>>>         'batedeira', 'era uma vez...', 'Era a última gota...']
>>> padrão = r'era'
>>> for i in lista:
>>>     if re.search(padrão ,i):
>>>         print(i, 'Deu match!')
>>>     else:
>>>         print(i, 'Não deu match :(')
Câmera Deu match!
alface Não deu match :(
nova era Deu match!
tomate Não deu match :(
batedeira Deu match!
era uma vez... Deu match!
Era a última gota... Não deu match :(

Cabe um comentário que existe uma outra função que tem uma funcionalidade parecida porém com uma diferença, a função re.match(). A função re.match() não faz a procura ao longo do string inteiro ela só vai dar match se o padrão encotrar no começo da string. Executando o código acima apenas trocando search por match temos os seguintes resultados:

>>> lista = ['Câmera', 'alface', 'nova era', 'tomate',
>>>         'batedera', 'era uma vez...', 'Era a última gota...']
>>> padrao = r'era'
>>> for i in lista:
>>>     if re.match(padrao ,i):
>>>         print(i, 'Deu match!')
>>>     else:
>>>         print(i, 'Não deu match :(')
Câmera Não deu match :(
alface Não deu match :(
nova era Não deu match :(
tomate Não deu match :(
batedeira Não deu match :(
era uma vez... Deu match!
Era a última gota... Não deu match :(

Apesar de a função match ser mais restrita ela tem tempo de execução mais rápido.

Caractere especial set([ ])

Até agora nada de especial foi mostrado que não pudéssemos fazer usando apenas python, a mágica do regex realmente acontece utilizando seus caracteres especiais, algumas vezes chamados de metacaracteres.

O primeiro desses caracteres especiais a ser apresentado é chamado de set que tem forma de caracteres dentro de [ ] (colchetes).


Um set permite poder escolher entre qualquer caractere dentro dos colchetes, um padrão que tivesse formato “cas[aoe]”, pode dar match tanto nas palavras casa, caso e case.


Digamos que queremos pegar instâncias em que a palavra data science apareça porém algumas vezes ela começa com letra maiúscula, pode ser separada por hífen e pode começar nas duas palavras com letra maiúscula. Nesse caso utilizar o set ajuda no problema.


>>> lista = ['Data Science é a melhor área',
>>>          'Eu trabalho com data science',
>>>          'Data science é divertido de se trabalhar',
>>>          'eu fiz um curso em data science',
>>>          'data-science está em alta']
>>> padrao = r'[Dd]ata[- ][Ss]cience'
>>> for i in lista:
>>>     if re.search(padrao ,i):
>>>         print('Deu match!')
>>>     else:
>>>         print('Não deu match :(')
Deu match!
Deu match!
Deu match!
Deu match!
Deu match!


Vale ressaltar que o espaço entre caracteres conta como um caractere também.


Existe ainda um outro caractere especial de regex que pode ser utilizado para abreviar os caracteres dentro de um set, o caractere “-” chamado de intervalo. Este caractere é usado para pegar tanto intervalos alfabético, digamos um padrão para pegar as letras de a à f ficaria [a-f] ou de números, um padrão que pegaria dos dígitos 1 ao 7 ficaria [1–7] .


Ex
: queremos pegar todas as datas de 1970 até 2005.


lista = ['2500', '1977', '1989', '1999','2122', '2005', '1820']
padrao = r'[12][90][0-9][0-9]'
for i in lista:
if re.match(padrao ,i):
print(i, 'Deu match!')
else:
print(i, 'Não deu match :(')
2500 Não deu match :(
1977 Deu match!
1989 Deu match!
1999 Deu match!
2122 Não deu match :(
2005 Deu match!
1820 Não deu match :(


Usando pandas para dar matches


O módulo pandas possui uma série de métodos especiais para strings que utilizam regex por trás.Um dos mais utilizados é o método de séries .str.contains() que desempenha um search verificando se cada string da série resulta em um match com o padrão e retorna uma máscara booleana.


Ex
:


>>> serie = pd.Series(['Data Science é a melhor área',
>>>          'Eu trabalho com data science',
>>>          'Data science é divertido de se trabalhar',
>>>          'eu fiz um curso em data science',
>>>          'data-science está em alta'])
>>> padrao = r'[Dd]ata[\- ][Ss]cience'
>>> mask = serie.str.contains(padrao)
>>> print(mask)
0    True
1    True
2    True
3    True
4    True
dtype: bool


Outro detalhe que vale mencionar é que como alguns caracteres especiais de regex usam caracteres como, hífens(-), colchetes([]) e etc, esses caracteres tem que ter uma barra(\) antes deles para identificá-los, como visto no exemplo acima com o hífen.


Quantificadores

Para certos padrões que se repetem ou ocorrem n vezes existe um conjunto de sintaxes especiais(Quantificadores) em regex que especificam isso.

Os quantificadores especificam quantas vezes o padrão ou caractere anterior tem que ocorrer.

Existem 2 maneiras de especificar quantificadores:

Numericamente:

Caracteres especiais:


Por exemplo queremos validar códigos em que os três primeiros dígitos vão de 2 à 5 e os últimos 3 são quaisquer dígitos.

>>> p = r'[2-5]{3}[0-9]{3}'
>>> serie = pd.Series(['234000','502331', '543121', '371087'])
>>> serie.str.contains(p)
0     True
1    False
2     True
3    False
dtype: bool

Caracteres especiais

Ainda existem alguns outros caracteres especiais que representam padrões constantemente utilizados.


Dessa forma o código acima pode ser simplificado da seguinte maneira:


>>> p = r'[2-5]{3}\d{3}'
>>> serie = pd.Series(['234000','502331', '543121', '371087'])
>>> serie.str.contains(p)
0     True
1    False
2     True
3    False
dtype: bool


Âncoras


Âncoras em regex são utilizadas para verificar se um padrão ocorre no final ou no começo de uma string.


>>> p = r'^[Dd]ata'
>>> serie = pd.Series(['Data science', 'Eu trabalho com data science', 'data risk', 'Big Data'])
>>> serie.str.contains(p)
0     True
1    False
2     True
3    False
dtype: bool

Grupos

Grupos, algumas vezes chamados de capturing groups, são utilizados em regex para agrupar uma sequência de caracteres. Utilizam ( )(parênteses) como sintaxe para pegar determinados grupos.

Ex:

Queremos validar códigos de 9 caracteres onde a cada grupo de 3 caracteres o primeiro é um dígito e os dois caracteres são letras sendo que esses grupos de 3 caracteres podem ser divididos por um hífen.

>>> p = r'\d[a-z]{2}(\-?\d[a-z]{2}){2}'
>>> serie = pd.Series(['1ab-2cv-8de', '54c-1yu-8bl', '0fr2et6qb', '1op-7xn--1kd'])
>>> serie.str.contains(p)
0     True
1    False
2     True
3    False
dtype: bool

Padrões negativos

É comum algumas vezes queremos dar match em padrões que não queremos que uma string possua, regex utiliza uma sintaxe apropriada para ter resultados dessa forma.


Ex: Queremos as linhas de uma série que contenham qualquer caractere que não seja um dígito.


>>> p = r'\D'
>>> serie = pd.Series(['1265', '541y827', '-----', 'bola'])
>>> serie.str.contains(p)
0    False
1     True
2     True
3     True
dtype: bool

Conclusão

Para cobrir regex na totalidade seria necessário pelo menos um livro inteiro. Este artigo almeja introduzir alguns conceitos básicos e algumas possíveis ideias de como regular expressions podem ser utilizadas para facilitar o processo de limpeza e tratamento de dados. Ainda existem alguns conceitos que o leitor pode pesquisar por conta própria nos sites mais especializados mencionados nas referências.


Referências

1. Curso Interativo de REGEX

2. rexegg — Site de referência ótimo para regex

3. Tutorial em português

4. Regexr Site que permite a construção de padrões com explicação e highlights

[Rr]eg(ular)?\s?[Ee]x(pressions)?
PARTE 2: https://medium.com/datarisk-io/rr-eg-ular-s-ee-x-pressions-segunda-parte-2-dois-cec5a510ea8d

Mais artigos da Datarisk

Ver todos os artigos
© Copyright 2017 - 2022 | Datarisk.io | Todos os direitos reservados