Aula 9 - Análise e visualização de dados com Python - biblioteca pandas (parte 5)

Aula 9 - Análise e visualização de dados com Python - biblioteca pandas (parte 5)

Como deixar o seu dataframe mais limpo ao lidar com dados nulos

Quando acessamos algumas bases de dados e colocamos as mãos em datasets para treinar nossas habilidades de análise e visualização de dados, seja em Python, ou em qualquer outra linguagem ou ferramenta, geralmente nos deparamos em dados que já passaram por um processo que chamamos de data cleaning, ou, em português, limpeza de dados. Esse processo é essencial quando vamos trabalhar com dados, sendo o que mais nos demanda atenção: boa parte do tempo de um analista é ocupado com limpeza, transformação e reorganização daquele monte de dados entulhados em algum lugar (há pessoas que dizem que essa parte ocupa até 80% do tempo! Outros dizem que não é bem assim...). Então, os dados que usualmente utilizamos para treinar, e que estão disponíveis em plataformas como o Kaggle estão limpos e organizados.

No entanto, quando nos deparamos com datasets do mundo real, inclusive aqueles que nós mesmos coletamos, percebemos que eles correm o grande risco de não serem completos e padronizados, fazendo com que todo aquele tempo com limpeza dos dados acabe por ocorrer conosco também. Mesmo com o processo de coleta e ingestão de dados mais rigoroso, mais cuidadoso possível, todo conjunto de dados está suscetível a alguma "sujeira". Por isso mesmo, é essencial aprender a detectar as inconsistências nos dados que podemos encontrar, e, sobretudo, saber o que fazer com elas.

Essa aula é dedicada a uma parte do processo de limpeza de dados, com exemplo e recomendação do que fazer quando você se deparar com aquilo que se costuma chamar dados nulos.

Então, eis o que veremos aqui:

  • O que são dados nulos (e por que eles aparecem);
  • Como detectar dados nulos;
  • Como lidar com dados nulos.

Afinal, o que são dados nulos (e por que eles aparecem?)

Dados nulos ou não-respostas (missing values, em inglês) são os dados que, por alguma circunstância, não estão disponíveis para análise. A origem deles pode ser variada, e aqui cita-se alguns exemplos:

  • Erros de digitação/transcrição para o banco de dados (ou seja, na ingestão desses dados);
  • Esquecimento da coleta de uma determinada variável em um momento específico;
  • Adição ou subtração de variáveis no decorrer do processo de coleta de dados (especialmente aquelas coletas que levam anos a serem feitas);
  • Falta de uma padronização na organização dos dados.

NOTA: Para entender mais sobre os tipos de dados nulos e como eles aparecem, recomendo algumas referências: os artigos de Kang (2013) e Dong e Peng (2013), além do verbete sobre missing data na Wikipédia em inglês e em português.

A presença de dados nulos, apesar de ser comum, pode afetar a análise dos dados, reduzindo seu poder estatístico e explicativo. Por isso mesmo, é bastante importante não ignorá-los; A seguir veremos como podemos detectar esses dados nulos no dataset com o qual estamos trabalhando.

Como detectar dados nulos

A detecção de dados nulos no pandas é bem simples, e normalmente é um das primeiras coisas ao se trabalhar com essa biblioteca. Na documentação do pandas, há um guia chamado "Working with missing data", onde funções relacionadas com dados nulos são apresentadas. Com o mesmo dataframe que estamos utilizando desde o início da parte prática da série, exploraremos aqui algumas funções (algumas citadas nesse guia) para detectar dados nulos.

Funções head()e tail()

Com as funções que usualmente iniciamos os nossos passos para análise de dados dentro da linguagem Python com o pandas, já é possível observar uma possível existência de dados nulos. Usando head() e tail(), podemos verificar se as primeiras e as últimas linhas da tabela possuem células preenchidas com NaN, NaT, ou None.

import pandas as pd #Sempre importe o pandas antes de começar
import numpy as np #Sempre bom importar o Numpy também

df = pd.read_csv("https://github.com/mhalmenschlager/python-biologia/raw/main/archives/surveys.csv") #Nosso dataset de exemplo. Importe-o no seu notebook ou IDE de preferência
df.head() #Cinco primeiras entradas
df.tail() #Cinco últimas entradas

Função info()

A função info(), que já foi vista por aqui, é uma das funções que podem ser utilizadas para observar a existência de dados nulos, através da análise de cada coluna/variável no dataframe. Essa função retorna uma lista com a quantidade de células em cada variável que possua dados válidos (non-null); é possível, a partir daí, comparar os números de células com dados válidos com o número total de linhas e colunas de df.

df.info() #Observação de dados não-nulos

Função isnull()

A resposta da função é um indicador booleano: para dados nulos, aparece o True, enquanto que para os dados não-nulos, é o Falsequem surge. Você pode utilizá-la junto da função sum()para quantificar os dados nulos de uma determinada coluna, por exemplo.

df.isnull() #Dados nulos (com retorno booleano) de todo o dataframe
df.column.isnull() #Dados nulos de uma variável
df.column.isnull().sum() #Soma de células com dados nulos em uma variável
nulo = df.isnull().sum().sort_values(ascending=False) #Verificação de dados nulos, em ordem decrescente
(nulo / df.shape[0])*100 #Verificação de porcentagem de dados nulos por variável

Função notnull()

A função notnull(), assim como a função isnull(), retorna um valor booleano, apenas aqui sendo o contrário: o Trueaparece para dados não-nulos. Usando a função sum(), o retorno também será quantitativo, com resultado semelhante à função info(), com a diferença da possibilidade de quantificação de uma só coluna.

df.notnull() #Dados válidos (com retorno booleano) de todo o dataframe
df.column.notnull() #Dados válidos (com retorno booleano) de uma variável
df.column.notnull().sum() #Soma de células de uma variável com dados válidos
### Usando as funções para criar novos objetos ###
valido = df.notnull().sum().sort_values(ascending=False) #Verificação de dados válidos, em ordem decrescente
(valido / df.shape[0])*100 #Verificação de porcentagem de dados nulos por variável

Funções isna() e notna()

Outro conjunto de funções que pode ser utilizado para verficiar a presença de dados nulos e que aparece na documentação do pandas é o composto por isna() e notna(). Essas funções são semelhantes a isnull() e notnull(), respectivamente, dando um retorno booleano ao processo de verificação.

df.isna() #Dados nulos (com retorno booleano) de todo o dataframe
df.notna() #Dados válidos (com retorno booleano) de todo o dataframe

Como lidar com dados nulos?

Existem diversas formas de fazer um processo de manipulação de dados para contornar o problema dos dados nulos; isso depende, portanto, da capacidade de discernimento do pesquisador ao definir qual é a melhor maneira a ser adotada. Tal capacidade vem com a compreensão do conjunto de dados (saber o que são as variáveis a serem analisadas, qual o peso delas para as análises a serem feitas...) e, principalmente, qual o problema a ser resolvido ou a pergunta que se quer responder com aquilo que foi coletado. Nessa parte da aula, veremos algumas funções do pandas que nos ajudam a fazer a "contenção de danos" ao lidarmos com a "sujeira" dos nossos dados.

Função dropna()

A função dropna()tem como objetivo retirar linhas ou colunas que contenham, pelo menos, um valor nulo. Por padrão, a função sozinha elimina todas as linhas, mas isso pode ser alterado de acordo com os argumentos que são passados na linha de código.

df.dropna() #Elimina todas as linhas que possuam qualquer dado nulo
df.dropna(axis=1) #Elimina todas as colunas que possuam qualquer dado nulo
df.dropna(axis=0, have='any') #Elimina todas as linhas que possuam qualquer dado nulo
df.dropna(axis=0, have='all') #Elimina todas as linhas que possuam todos os valores como dados nulos
df.dropna(axis=0, thresh=2) #Mantém linhas que possuam, no máximo, 2 valores como dados nulos, eliminando as demais

Função fillna()

Se a função dropna()serve para, simplesmente, eliminar dados nulos, a função fillna() serve para mantê-los, fazendo um processo de preenchimento das células que apresentam valores como NA, NaN, ou NaT. Para preenchimento efetivo, é necessário especificar o método a ser adotado, passando argumentos ao código que será executado.

df.fillna(0) #Preenche todos os elementos NaN com o valor 0
df.fillna(method='pad') #Preenche os elementos NaN com a última observação válida
df.fillna(method='ffill') #Preenche os elementos NaN com a última observação válida
df.fillna(method='bfill') #Preenche os elementos NaN com a observação válida posterior a eles
df.fillna(method='backfill') #Preenche os elementos NaN com a observação válida posterior a eles
df.fillna(value=0, limit=1) #Preenche apenas o primeiro elemento NaN do dataframe com o valor 0

#### Usando índices de tendência central ####

df['column'].fillna(int(mean), inplace=True) #Preenche elementos NaN com a média
df['column'].fillna(int(median), inplace =True) #Preenche elementos NaN com a mediana
df['column'].fillna(int(mode), inplace=True) #Preenche elementos NaN com a moda

Função interpolate()

Quando as medidas de tendência central não parecem ser suficientes para preencher o espaço dos valores nulos num dataframe, é possível lançar mão do método chamado de interpolação. Basicamente, interpolar é gerar um dado novo a partir de um conjunto de dados que já são conhecidos. Nesse caso, o pandas pode usar os valores vizinhos de uma célula cujo valor ainda não é conhecido aqui (NaN) para estimar um valor novo que pareça fazer mais sentido àquele conjunto de dados que estamos analisando. A função de interpolação é semelhante ao fillna(), mas com a diferença é que é muito mais flexível; ao usar interpolate(), o analista possui uma variedade interessante de interpolações à mão, sendo que a interpolação padrão por parte do pandas é a do tipo linear. Algumas dessas interpolações podem ser vistas no exemplo abaixo:

df.interpolate() #Interpolação do tipo linear para os valores nulos do dataframe
df.interpolate(method='polynomial', order=2) #Interpolação do tipo polinomial para os valores nulos do dataframe
df.interpolate(method='pad', limit=2) #Interpolação com preenchimento com o valor acima do valor nulo, com limite de 2 valores NaN
df.interpolate(method='linear', limit_direction='forward') #Interpolação do tipo linear para os valores nulos do dataframe utilizando o valor anterior como limite
df.interpolate(method ='linear', limit_direction ='backward', limit = 1) #Interpolação do tipo linear para os valores nulos do dataframe utilizando o valor posterior ao NaN como limite, sendo 1 o número máximo de valores NaN que podem ser preenchidos com esse método

E agora, Que tal fazer você mesmo?

Com todos os códigos em mãos, aproveite apara abrir um novo arquivo .ipynb ou .pyno Google Colab, no Anaconda ou em outra IDE que você conheça, e vá explorando os blocos de código aqui citados, tanto com o dataset de exemplo, quanto o seu próprio conjunto de dados, ou algum outro que você viu na Internet ou que passaram para você treinar.


Ao terminar essa aula, recomendo a você que dê uma olhada na documentação do pandas e nas referências que estão nos hyperlinks e na seção 'Para ler mais' abaixo para entender bem essa parte de verificar dados nulos e lidar com eles. Por ser um processo que se inclui na limpeza de dados, é uma das mais importantes ações que é preciso fazer ao lidar com análise e visualização de dados, e as aplicações posteriores a isso. Não se esqueça também de praticar exaustivamente com os códigos aqui passados, para absorver todo o potencial de cada função do pandas explanada aqui.

Então, cuide bem dos seus dados! Economiza tempo, energia e, dependendo do caso, até dinheiro...

Um grande abraço, divirta-se com os códigos e até a próxima aula!


Para ler mais: