Como fazer uma limpeza de dados completa em Python

“Garbage in, garbage out”

Vitoria
Turing Talks
11 min readJul 20, 2020

--

Texto escrito por Enzo Bustos e Vitoria Rodrigues.

E aí, pessoal! Bem-vindos a mais um Turing Talks! No artigo de hoje “we are going back to the basics” para abordar um assunto que não trata propriamente de IA, mas se relaciona MUITO: Limpeza de dados.

Bem, se você ainda é novo no mundo da programação talvez não entenda onde exatamente a limpeza está situada, muito menos qual sua relação com IA, mas digo que eles se relacionam, e muito! Em qualquer área de conhecimento da programação que você esteja, provavelmente vai se deparar com algum problema que requer essa tarefa, já que um profissional dessa área lida com uma quantidade enorme de dados o tempo todo. Por exemplo, quando pensamos em Modelos de Predição, na área de Machine Learning, uma frase muita usada é “Garbage in, garbage out”, isso significa que, afim de se garantir ou melhorar o desempenho do seu modelo, o pré-processamento, ou seja, a limpeza, é essencial. Você pode ver mais sobre o assunto de predição em específico neste post aqui.

Certo, mas o que seria limpar seus dados? Quando falamos sobre isso, estamos falando de remover qualquer dado modificado que contenha inconstâncias, esteja incorreto, incompleto, duplicado, formatado incorretamente ou até mesmo seja irrelevante.

Mas por que fazer isso? Apenas com dados corretos podemos realizar uma análise completa e confiável, e por consequência, todas as outras etapas que dependem da limpeza e dela, serão feitas corretamente. Afinal, esses erros podem gerar um problema em cascata! Você não quer estar no meio da sua análise e descobrir que, por exemplo, na coluna da categoria gênero, existem três tipos de dados: “Mulher”, “Homem” e “Criança”, já que, afinal, “Criança” não é um gênero! Isso seria um exemplo perfeito de dados incorretos. E acredite, isso acontece frequentemente, já que a coleta dessa informações pode ser feita incorretamente.

Tem que limpar direitinho, hein

Lendo seu DataFrame e analisando suas features

Para este artigo, decidimos limpar e analisar um arquivo com dados relacionados a músicas do spotify. Porém, não pegamos o arquivo original e sim uma versão modificada pelo Grupo Turing, assim podemos limpá-lo e analisá-lo de forma mais abrangente.

Você pode encontrar e baixar o arquivo neste link. E clicando aqui você pode ver a descrição das colunas assim como suas respectivas definições. Essas informações foram retiradas desta imagem com tradução livre.

Então vamos começar!

Bibliotecas necessárias

Primeiro, importaremos as bibliotecas necessárias. Para isso, vamos utilizar os quatro cavaleiros do apocalipse de uma limpeza bem feita: Pandas, Matplotlib, Seaborn e Numpy.

The four horsemen of Data Analysis (thanks to Felipe Azank)

Lendo e visualizando o DataFrame

Com nossas bibliotecas já importadas é hora de ler nosso arquivo. Como você deve ter notado o arquivo está no formato .csv, uma maneira econômica de guardar dados em tabelas. A biblioteca pandas possui uma função específica para ler esse tipo de arquivo e transformá-lo em um DataFrame, chamada .read_csv(). Então vamos ler e depois visualizar nossos nos com a função df.head().

Aqui, também vamos mudar a ordem do nosso df de acordo com a popularidade, assim podemos ver mais claramente alguns erros. Fazemos isso com a função .sort_values que ordena os valores de uma coluna e que requer três argumentos: 1) a coluna pela qual você quer ordenar; 2) o ascending que indica se será ordenado do menor para o maior ou vice-versa — aqui definido como False, já que não queremos que seja ordenado dessa forma e sim do maior para o menor; 3) o inplace definido como True, para que as mudanças feitas nessa função sejam aplicadas ao dataframe original e não apenas a uma cópia. Vamos ver como fica:

Observem quantas vezes a música ‘I love it’ aparece, isso é estranho, certo? Também há uma música classificada como ‘não_sei’, e várias colunas com unidades de medida como ‘mol’ ou ‘kg’ — vale lembrar que elas foram alteradas, como falamos anteriormente. Todos esses erros que saltam aos olhos são a razão pela qual a limpeza do nosso DataFrame é tão essencial.

Analisando nossas features

Antes de partirmos para a limpeza, vamos analisar cada uma das nossas features (a.k.a colunas).

Dando uma olhada nos nomes conseguimos ter uma ideia sobre o que se trata cada uma delas, mas não podemos obter todas as informações, afinal algumas possuem uns nomes bem estranhos. Por isso, podemos pesquisar mais detalhadamente a respeito de cada feature do nosso dataset (essa parte já foi feita para você e o link está no começo do texto na parte de leitura do df).

Nosso dataset contém as mais diversas informações sobre cerca de 19000 músicas distribuídas entre 15 features. A única coluna em forma de string é a song_name, enquanto as outras são numéricas.

Limpeza

Checando as informações do DataFrame

Bom, para começar vamos ver algumas funções que vamos usar em toda nossa análise, a nossa tríade sagrada:

Essas funções são muito importantes justamente porque servem como uma bússola para nos orientar, ou seja, para sabermos se estamos ou não indo para o caminho certo na limpeza. Sem uma orientação isso acaba ficando um pouco complicado, pois na limpeza de dados não existe um gabarito, você que deve perceber o que está ou não errado e o que talvez não seja conveniente para o tipo de tarefa que você quer realizar.

A df.info()é a nossa queridinha, ela retorna algumas observações que podem ser muito úteis, já que analisa coluna por coluna, mostrando a quantidade de dados nulos do DataFrame, número total de entradas e datatypes de cada feature.

Só com essas informações você já sabe para onde caminhar com sua análise. Por exemplo, observe que a maioria das colunas estão armazenadas como ‘object’ — que é como o pandas guarda informações de texto — porém, boa parte dessas colunas são dados numéricos, portanto já podemos perceber que algo está errado com nossos datatypes, vamos explicar mais para frente como corrigir esses erros.

E agora, finalmente, essa função maravilhosa que nos dá mais detalhes sobre informações numéricas, tal como contagem, valores dos quartis, média, desvio padrão, máximo e mínimo. Todas essas informações podem ser usadas para descobrir coisas estranhas que podem estar aparecendo no seu dataframe. No nosso caso, colunas com os valores tabelados entre 0 e 1 com algo fora desse intervalo é automaticamente algo a ser corrigido e o describe nos ajuda nessa tarefa. Uma observação importante é que esse método pode receber como parâmetro o datatype com a especificação ‘include’, podendo ter informações mais específicas se estivermos trabalhando com categorias por exemplo.

Duplicatas

Algo que devemos nos atentar durante nossa limpeza são as duplicatas do df. Resumidamente, duplicatas são as linhas dentro do nosso dataframe que foram clonadas, ou seja, existem duas linhas com exatamente o mesmo valor para cada uma das features. Isso significa que essa informação não está agregando em nada para o nosso modelo, pois é uma mesma observação feita de maneira repetida.

Dados duplicados devem ser removidos do nosso dataframe, pois podem atrapalhar nossa análise e todos os passos que dependem dela.

Com essa função podemos ver todos os dados duplicados

No nosso df em questão os dados estão BEM poluídos por duplicatas, podemos jogar fora essas ocorrências de repetições pois elas realmente não importam já que são apenas observações repetidas, portanto, podemos excluir utilizando apenas uma linha de código. Porém, é importante deixar a primeira ocorrência da repetição para não desperdiçarmos dados. Podemos fazer isso com o argumento keep = ’first’ da função drop_duplicates. Vamos ver um exemplo:

Muito fácil, né?

Inconsistências

Certo, conseguimos sobreviver às duplicatas, e agora?

Para o próximo tópico vamos tratar das inconsistências, primeiramente vamos remover as coisas mais grotescas, como as unidades de medidas inadequadas ‘mol/L’ E ‘kg’. Vamos fazer isso com uma função:

Certo, em seguida vamos usar np.nan para declarar valores faltantes nos nossos dados, o NaN, que significa Not a Number, é um valor especial definido no numpy que pode decodificar um valor faltante, mas ainda assim ser lido como um numeral, pois é definido como float.

Dessa forma, é interessante trocar tanto dados faltantes sem uma formatação (‘nao_sei’) quanto alguns valores que imediatamente não fazem sentido para nossa análise pelo NaN, isso pode ser feito pela função replace do pandas:

Observe que vários destes valores foram achados um tanto empiricamente, por exemplo ao tentar plotar um gráfico podemos perceber esses valores destoante dos outros, ou ainda simplesmente pelo tipo de dado que estamos trabalhando, como a coluna ‘time_signature’, por exemplo, que é uma medida quantitativa de quantos pulsos temos por unidade de tempo musical, sendo inviável ser um valor “quebrado” ou tão grande quanto 2800000000.

Datatypes

Bom, o próximo passo é ajustar os tipos das nossas variáveis, geralmente pode acontecer de colunas que deveriam ter apenas algum tipo específico de dados estarem armazenadas com o tipo de variável incorreta. Como por exemplo, uma coluna com dados numéricos deve ser armazenada como int ou float, e não como object, que é como o pandas guarda strings (ou seja valores de texto).

Em resumo, nossas colunas devem estar com os dtypes condizentes, o que claramente não é o caso do nosso Dataframe. A maioria das nossas informações numéricas estão sendo tratadas como strings e não queremos isso, vamos então construir uma função para arrumar:

Podemos checar se deu tudo certo usando a função info() novamente e verificando os data types.

Outliers

Agora vamos tratar de outliers, que são dados que fogem muito do nosso padrão e que por conta disso acabam dificultando o processo de generalização do seu modelo de predição. Podemos pensar, por exemplo, na seguinte situação: queremos generalizar a renda média de cada pessoa em uma determinada região e temos acesso a uma amostra para fazer um levantamento de dados, se o Bill Gates estiver no meio, provavelmente, ele será um outlier, pois ele vai “puxar” a média do nosso grupo muito para cima, o que complicaria nossa análise, podendo comprometer observações e até agravar insights.

Em suma, outliers são valores que desequilibram o dataframe e comprometem a capacidade de extrair insights com base nos dados:

Outra descrição válida é que outlier é aquele ponto que faz seu TOC atiçar.

O primeiro passo para saber se um dado é ou não um outlier é o bom senso.
Isso pode parecer um pouco vago em determinados casos, porém o bom senso é o primeiro caminho que você deve tomar ao abrir um novo dataset, sendo assim é crucial conhecer a história por trás dos seus dados, seja de onde ele foi retirado, as fontes por trás, até talvez qual o parâmetro de medição de alguma feature. Pesquisar sobre o assunto que você está trabalhando também é um trabalho importante para um data scientist.

Claro que algumas informações podemos extrair diretamente do nosso conhecimento de mundo, como idades que sempre são números maiores que 0 e dificilmente maiores que 110, no caso do nosso dataset, por exemplo, estamos trabalhando com músicas, então já podemos presumir que o tempo de uma música deve estar em torno de 2,5 a 4 minutos, ou seja, dados fora desse intervalo provavelmente são outlier. No nosso caso até temos um facilitador, pois várias das nossas features são medidas entre 0 e 1, podendo excluir automaticamente qualquer coisa fora desse intervalo.

A segunda forma de detectarmos outliers é através de métodos estatísticos (tava demorando para aparecer uma matemática né heheh). Bom, para isso, vamos dar uma olhada nos intervalos de confiança de uma normal, que é geralmente como nossos dados aparecem distribuídos:

Dessa forma, para termos um intervalo de confiança que exclua apenas os outliers, vamos trabalhar com dados que estão no intervalo:

Portanto, abrangemos a grande maioria dos nossos dados e removemos os que destoam do normal. Então vamos fazer uma função que faça esse processo de remoção automaticamente:

Aqui criamos a função

Agora vamos separar nossas colunas numéricas e remover os outliers delas aplicando a função e trocando esses valores absurdos por NaN (not a number) que representa valores faltantes:

E aqui aplicamos a função nas respectivas colunas

Colunas desnecessárias

Algumas colunas podem ser consideradas desnecessárias para nossa análise, isso porque elas não nos passam informações relevantes a respeito do que queremos descobrir, ou até mesmo porque possuem tantos dados faltantes que mais atrapalham do que ajudam. Nesses casos uma forma rápida e fácil de solucionar esse problema seria excluí-las. Mas será que essa é a melhor solução?

A resposta mais segura para isso é: depende. Isso porque essa decisão pode variar conforme alguns aspectos e você precisa ter certeza absoluta de que aqueles dados que vai excluir não farão falta — e nem sempre podemos ter essa certeza. Vamos abordar uma forma de lidar com dados faltantes mais abaixo.

Nesse DataFrame em questão, não vamos excluir nenhuma coluna porque para nosso próximo post de análise todas serão utilizadas. Mas decidimos abordar esse assunto apenas por ser uma parte importante de um pré-processamento bem feito.

Dados faltantes

Por fim, vamos falar sobre dados faltantes ou nulos. Em algumas situações, podemos ter muitas informações incompletas no nosso df, ou, como vimos anteriormente na parte de Inconstâncias, pode acontecer de precisarmos substituir valores errados por NaN. Essas informações faltantes podem prejudicar nossa análise e outras etapas que dependem dela e do pré-processamento, portanto, precisamos removê-los ou substituir esses valores por outros. Vamos ver detalhadamente cada uma das opções:

Nossa primeira opção é substituir esses dados pela média da coluna, entretanto, às vezes, a média pode ter sido afetada pelos valores destoantes da coluna, então podemos substituir também pela moda ou mediana. Podemos fazer isso com a função .fillna que preenche todos os campos com dados ausentes. Vamos criar alguns loops como exemplo.

O primeiro passa por algumas colunas e substitui os valores faltantes pela moda:

Depois, vamos substituir os valores nas outras coluna pela mediana:

Em segundo lugar temos a opção mais rápida e prática: excluir as linhas com dados faltantes. Essa opção é boa quando não temos muitas colunas com observações vazias, garantindo assim que não apagaremos muitos dados. Vamos ver um exemplo prático:

Muito fácil, né?

Muitas vezes parece ser tentador utilizar o comando .dropna, já que ela resolve nossos problemas com dados vazios muito facilmente. Entretanto, o grande problema é acabar perdendo valiosas linhas de informação com isso, portanto, é recomendável tentarmos achar algum método de substituição de valores que consiga preencher esses dados sem comprometer o dataset.

Existem várias formas de fazer isso, como as que vimos acima substituindo os valores alterados pela moda, média, mediana. Temos também outros métodos mais sofisticados como estatística de janela, clustering imputer ou random imputer, mas não vamos entrar muito nesse mérito, já que acabaríamos nos estendendo demais e esse tema é tão amplo que merece um Turing Talks só para ele.

Conclusão

Nesse Turing Talks abordamos os pontos fundamentais para uma limpeza bem feita, entretanto, para cada conjunto de dados existe uma abordagem que funciona melhor, esse é o tipo de coisa que aprendemos melhor na prática. Portanto, não tenha medo de botar a mão na massa e explorar outros datasets para treinar a limpeza!

Semana que vem voltamos com a continuação desse Turing Talks que abordará a próxima parte depois da limpeza: A análise.

Até lá!

Agradecimentos especiais aos nossos mentores Felipe Azank, Julia Pociotti, Camila Lobianco, Leonardo Muramaki e William Fukushima, que nos ajudaram no que começou como um Mini Projeto e virou um Turing Talks! Obrigado também a Camilla Fonseca e ao Guilherme Fernandes.

--

--

Vitoria
Turing Talks

Estudante de Linguística na FFLCH e membro do Turing USP. Você pode me encontrar no GitHub: https://github.com/vitoriars