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

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*](https://www.tableau.com/learn/articles/what-is-data-cleaning), ou, em português, [**limpeza de dados**](https://www.ibpad.com.br/blog/comunicacao-digital/uma-visao-geral-sobre-a-limpeza-dos-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!](https://www.projectpro.io/article/why-data-preparation-is-an-important-part-of-data-science/242) Outros dizem que [não é bem assim](https://blog.ldodds.com/2020/01/31/do-data-scientists-spend-80-of-their-time-cleaning-data-turns-out-no/)...). Então, os dados que usualmente utilizamos para treinar, e que estão disponíveis em plataformas como o [Kaggle](https://kaggle.com) 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](https://jakevdp.github.io/PythonDataScienceHandbook/03.04-missing-values.html), 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"**](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3668100/). 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](https://pt.wikipedia.org/wiki/N%C3%A3o-resposta) ([*missing values*](https://en.wikipedia.org/wiki/Missing_data), 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)](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3668100/) e [Dong e Peng (2013)](https://pubmed.ncbi.nlm.nih.gov/23853744/), além do verbete sobre *missing data* na [Wikipédia em inglês](https://en.wikipedia.org/wiki/Missing_data) e [em português](https://pt.wikipedia.org/wiki/N%C3%A3o-resposta).

A presença de dados nulos, apesar de ser comum, [pode afetar a análise dos dados, reduzindo seu poder estatístico e explicativo](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5548942/). 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"](https://pandas.pydata.org/pandas-docs/stable/user_guide/missing_data.html), 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`.

````python
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`.

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

#### Função [`isnull()`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.isnull.html)

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

````python
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()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.notnull.html)

A função `notnull()`, assim como a função `isnull()`, retorna um valor booleano, apenas aqui sendo o contrário: o `True`aparece 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.

````python
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()`](https://pandas.pydata.org/docs/reference/api/pandas.isna.html) e [`notna()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.notna.html)

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.

````python
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()`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.dropna.html)

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.

````python
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()`](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.fillna.html)

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.

````python
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()`](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.interpolate.html)

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:

````python
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 `.py`no 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:**

- **[Manipulando Missing Values com Pandas - Minerando Dados](https://minerandodados.com.br/manipulando-missing-values-com-pandas/)**
- **[Handling Missing Data - Python Data Science Handbook](https://jakevdp.github.io/PythonDataScienceHandbook/03.04-missing-values.html)**
- **[Data Cleaning with Python and Pandas: Detecting Missing Values, por John Sullivan - Towards Data Science](https://towardsdatascience.com/data-cleaning-with-python-and-pandas-detecting-missing-values-3e9c6ebcf78b)**
- **[Handling Missing Data in Pandas: NaN Values Explained – BMC Software](https://www.bmc.com/blogs/pandas-nan-missing-data/)**
- **[Como Tratar Dados Ausentes com Pandas - Sigmoidal](https://sigmoidal.ai/como-tratar-dados-ausentes-com-pandas/)**
- **[How to Handle Missing Data, por Alvira Swalin - Towards Data Science](https://towardsdatascience.com/how-to-handle-missing-data-8646b18db0d4)**
- **[How to Deal with Missing Data - Masters in Data Science](https://www.mastersindatascience.org/learning/how-to-deal-with-missing-data/)**
- **[Methods for handling missing data. - PsycNET](https://psycnet.apa.org/record/2012-27075-004)**
- **[A Guide to Handling Missing values in Python - Kaggle](https://www.kaggle.com/parulpandey/a-guide-to-handling-missing-values-in-python)**
- **[Junção de Dados com Pandas em Python – FLAI](https://www.flai.com.br/vitordiego/juncao-de-dados-com-pandas-em-python/)**
- **[Pythonic Data Cleaning With Pandas and NumPy - Real Python](https://realpython.com/python-data-cleaning-numpy-pandas/)**


