Tratamento e Transformação de Dados NaN: Uma visão geral e prática

Tratando dados missing (NaN) com as bibliotecas Pandas e Sklearn, utilizando SimpleImputer.

Iury Rosal
Data Hackers
12 min readMar 9, 2021

--

0. Introdução

O tratamento de dados é essencial para qualquer projeto envolvendo dados, se resumindo em tratar seus dados, transformando-os, alterando sua estrutura e valores. Esse procedimento é importante para deixar a base de dados “limpa” e garantir que os dados que serão trabalhados estejam nas melhores condições para alguma análise. O procedimento de tratamento de dados se encontra dentro do processo ETL (Extract, Transform e Load), utilizado principalmente em Data Warehouses e em outros projetos de ciência de dados.

Os benefícios desse tratamento de dados se traduzem em organização e praticidade, pois é reduzido o risco de problemas durante a aplicação desses dados devido à dados nulos, duplicados, formatos incorretos, etc… Além disso, o processo de tratamento de dados auxilia na compatibilidade entre aplicações, afinal, um mesmo conjunto de dados pode ser utilizado para diferentes projetos.

Mas nem tudo são flores… O tratamento de dados tem suas desvantagens… Dependendo da situação, o custo para o tratamento pode exigir uma grande infraestrutura ou um grande esforço, o que pode tornar um processo bem caro.. Além disso, a falta de experiência do analista, tanto do ponto de vista prático como também envolvendo conhecimento das regras de negócios, pode ocasionar em falhas no tratamento de dados. Outro risco é o tratamento não funcionar de maneira unificada na empresa. Por exemplo, em uma aplicação foi feita o tratamento de formato do dado que é repassado para outra aplicação que desfaz esse tratamento (ou seja, desfaz o procedimento feito anteriormente).

As transformações dos dados podem ser separadas em 4 grupos:

  • Construtiva: adiciona ou copia dados;
  • Destrutiva: dados são substituídos ou deletados;
  • Estética: formatam a estrutura dos dados (datas, hora, endereço, etc…)
  • Estrutural: ocorre entre as colunas: renomeação, combinação e movimentação de colunas na tabela.

Neste artigo, iremos ver, especificamente, o processo de tratamento de dados envolvendo dados missing (NaN). Iremos aprofundar esse artigo seguindo a lógica do Círculo de Ouro: Por que lidar com esses dados, como identificá-los e o que fazer com eles.

Fonte: https://unsplash.com/photos/j_nQT4YIbac?utm_source=unsplash&utm_medium=referral&utm_content=creditShareLink

1. Dados Missing (NaN)

Dados NaN (Not a Number) representam dados missing, que são registros que não existem ou que se perderam dentro do conjunto da base de dados. Outra definição para esse termo é essa, extraída da Wikipédia:

Em computação, NaN (acrônimo em inglês para Not a Number) é um valor ou símbolo usado nas linguagens de programação para representar um valor numérico indefinido ou irrepresentável.

O motivo para o surgimento desses dados pode ser ocasionado pelo não preenchimento de informações, dentro do banco de dados, por algum fator externo. Por exemplo, em uma base de clientes pode existir uma coluna RG com valores vazios por conta de clientes que se negaram a fornecer o documento ou por displicência de quem é responsável pela base de não ter coletado e armazenado essa informação. Outro exemplo seria existir uma coluna Nome_do_Conjuge, em que determinados registros estão NaN pelo fato da pessoa ter o valor ‘solteiro’ na coluna Estado_Civil. É muito importante, a partir do entendimento dos processos e regras de negócio, compreender a causa do surgimento destes dados NaN, pois esse entendimento é muito necessário para decidir como tratar esses dados da forma mais adequada possível.

Cuidado!

Dados NaN são literalmente dados não preenchidos. Um registro preenchido por 0, None ou Null não, obrigatoriamente, significa ser um dado NaN. Em boa parte dos casos, esses dados não remetem à algum significado dentro das regras de negócio e, por esse motivo, podem ser considerados como um caso de dados NaN, mas nem sempre isso ocorre. Aqui entra, novamente, a importância do entendimento das regras de negócios para a identificação dos dados missing.

Vale ressaltar que dentro das funções de carregamento de arquivos do pandas, como read_csv() e read_sql(), existe o parâmetro na_values, em que você pode passar uma lista (array) com valores que são considerados NaN, como 0, None, Null, N/A e variações desse tipo. Sem o uso desse parâmetro, na leitura de um csv, por exemplo, onde existe uma célula em branco (não preenchida), o pandas já a identifica como NaN, mas células diferentes disso não são convertidas para NaN. Nesse caso, se a célula estiver preenchida por “ “, por exemplo, o pandas não irá substituí-la como NaN.

1–1 Tipos de dados NaN

Para incrementar nossa compreenssão sobre os dados NaN, formalmente podemos dividir os dados NaN em três grupos:

  • Missing Completely at Random (MCAR): A probabilidade de uma instância estar ausente não depende nem de valores conhecidos e nem do próprio valor ausente. Isso entra no exemplo de valores ausentes na coluna RG por descuido do responsável pela coluna que simplesmente não registrou o dado e não pelo fato da pessoa não ter RG.
  • Missing at Random (MAR): A probabilidade de uma instância estar ausente pode depender de valores conhecidos, mas não do próprio valor ausente. Por exemplo, esse caso entraria na situação do dado ser NaN na coluna Nome_do_Conjuge pelo fato da pessoa ter a informação ‘solteiro’ na coluna de Estado_Civil.
  • Not Missing at Random (NMAR): A probabilidade de uma instância estar ausente pode depender do valor da própria variável. Podemos citar como exemplo o caso de uma coluna de notas de alunos de uma prova possuir dados NaN devido a alunos que zeraram a prova ou faltaram a prova final (também levando a uma nota 0).

2. Por que se importar com eles?

Bem, agora que sabemos o que são esses dados NaN. Por que devemos nos dedicar em tratá-los? Baseado no que já foi comentado, a existência de dados como None, 0 e Null, devem ser avaliados se devem ser considerados como dados NaN ou se estes envolvem alguma lógica dentro das regras de negócio. Ignorar isso, pode ocasionar em análises erradas ou perdas de tempo no processo de análise, podendo provocar análises incorretas.

Na construção de um modelo preditivo, a existência de dados NaN pode ocasionar em erros na modelagem, na predição ou até impossibilitar a construção do modelo.

3. Como identificá-los?

Existem diversas formas de identificar dados NaN. Neste artigo, irei mostrar a forma que considero mais rápida e prática para identificar dados NaN.

3–1 Utilizando info()

A função info() da biblioteca Pandas é muito útil para identificar dados NaN. Essa função aplicada sobre um dataframe, retorna uma tabela com informações de cada coluna do dataframe: quantidade de valores não NaN e tipo de dados.

Essa ferramenta é uma ótima análise inicial para identificar colunas que possuam NaN. Via o parâmetro shape é possível checar a quantidade total de linhas. A diferença do número total de linhas pelo número de linhas com dados não NaN da coluna é a quantidade de dados NaN presentes naquelas coluna.

Exemplo de uso da função info(). Se observamos quando aplicado o parâmetro shape na variável dataframe, tenho 3 linhas e 3 colunas. Ao utilizar info(), observo que Nome tem 3 valores não nulos, enquanto Nota_AP_1 e Nota_AP_2 possuem apenas 2 valores não nulos. Então, sabemos que existem valores nulos nessas duas colunas e comparando com o número total de linhas tenho exatamente 1 valor NaN em cada coluna.

3–2 Utilizando isnull()

Bem, o método isnull() verifica se o valor é NaN ou não. Nesse caso, se o valor for NaN, este será substituído por True, se não, será retornado False. Combinar esse método junto com a função sum(), permite uma verificação de quantos dados NaN possuímos por coluna. Acaba sendo um pouco mais eficiente que o info() já que ele retorna exatamente a quantidade de valores NaN (você não precisa fazer a subtração do total com o valor retornado no info() ).

Utilizamos o isnull() e verificamos a presença de valores nulos em alguns locais (booleano True). No entanto, em datasets muito grandes é interessante utilizamos o sum() para ele efetuar a soma das linhas (como se True = 1 e False = 0). No fim, temos a quantidade de linhas NaN de cada coluna. Nesse caso, identificamos 1 valor NaN em Nota_AP_1 e em Nota_AP_2.

Um outro incremento interessante nesse lógica é verificar, em %, quantos dados NaN existem na coluna em relação ao total de linhas. Isso ajuda a ter uma noção de quais colunas estão mais afetadas por dados NaN e a proporção disso. Para isso, vale lembrarmos que o parâmetro shape retorna as dimensões do dataframe no formato: (número de linhas, número de colunas). Logo, ao usarmos shape[0] obtemos apenas o número de linhas do dataframe. Tendo isso, basta dividirmos o número de dados NaN pelo número total de linhas e, em seguida, multiplicar o resultado por 100 para obtenção de %. Logo, a fórmula que será efetuada para cada coluna será:

Fórmula descrita no procedimento acima e executado na imagem abaixo.
Realizando o procedimento comentado anteriormente, observamos que Nota_AP_1 e Nota_AP_2 possui 33.33%, aproximadamente, dos seus dados como NaN.

4. O que fazer com dados NaN?

Existem diferentes formas de lidar com dados NaN. Como já foi discutido, o conhecimento da origem dos dados NaN é um ótimo aliado nessa hora. Mas, além disso, a quantidade de dados NaN pode ajudar nessa decisão. A seguir iremos discutir alguns procedimentos.

4–1 Eliminação dos registros com dados NaN

A eliminação dos registros (linhas) que possuem, pelo menos, um dado NaN em uma de suas colunas é vista como o procedimento mais fácil e rápido para eliminar dados missing do conjunto de dados. No entanto, vale lembrar que essa operação leva na perda de informação das outras colunas que, muitas vezes, não possuiam dados NaN, o que pode prejudicar em análises pela falta de dados ou desequilíbrio de dados categóricos. Essa operação para tratamento com dados NaN é, provavelmente, uma das mais destrutivas.

Por isso, para fazer essa operação deve-se realizar uma análise cuidadosa de como isso afetará as outras colunas. Normalmente essa técnica é feita quando existem poucas colunas com dados NaN e o número de registros com alguma incidência de NaN não ultrapassa 10% da base de dados.

Aqui as linhas 0, 2 e 3 seriam apagadas do dataframe, pois existe pelo menos um valor NaN em uma de suas colunas. Nesse caso, como existem 6 linhas ao todo, cerca de 50% do dataset seria perdido, o que é uma % considerável.

No pior caso, o total de linhas perdidas nessa operação é igual a soma da quantidades de valores NaN de todas as colunas. Ou seja, se eu tenho 30 valores NaN, ao todo, no meu dataframe, posso perder até 30 registros (caso esses valores NaN estejam dispostos de uma forma que não existem 2 ou mais valores NaN em uma mesma linha).

Para realizar essa operação basta efetuar a função dropna(). O parâmetro inplace com valor True permite que essa operação seja aplicada em cima do dataframe. No entanto, como é uma operação muito destrutiva, o ideal é alocar em uma outra variável para evitar perdas de informações.

Aplicação do dropna() com parâmentro Inplace = False para não aplicar essa operação na variável dataframe e parâmetro axis = 0 para realizar essa operação nas linhas, ou seja, ele irá retirar registros em que pelo menos uma de suas colunas possui um valor NaN.

Caso a quantidade de linhas que seriam perdidas nessa operação seja considerável, deve-se analisar a realização de uma das seguintes operações: Eliminação da coluna com muitos dados NaN ou substituir esses dados NaN por algum outro valor.

4–2 Eliminação de colunas com dados NaN

Uma outra alternativa, em vez de retirar os registros, podemos retirar as colunas. Essa alternativa é uma opção quando mais de 60%-70% dos dados em uma determinada coluna são NaN, pois caso ocorra o procedimento 4–1 perderá uma quantidade considerável de dados. Por ser uma quantidade considerável de valores NaN, substituir por alguma informação muitas vezes não se torna uma tarefa fácil ou estaria numa situação de “mascarar” boa parte das informações em relação a esse feature no conjunto, o que poderia prejudicar a modelagem preditiva, por exemplo.

Utilizando o dropna() com o parâmetro axis = 1 ocorrerá o apagamento de colunas em que pelo menos um dos seus valores é nulo. No entanto, ele fará isso para todas as colunas do seu dataframe.

Observe que a coluna Nota_AP_1 e Nota_AP_2 foram apagadas.

Todavia, se você possuir colunas, com valores NaN, que deseja realizar alguma estratégia diferente e eliminar apenas alguma coluna específica, o procedimento acima se torna inviável. Por exemplo, uma coluna possui 50% dos dados NaN e outra possui um valor bem menor que isso. Como mostra abaixo:

50% dos dados da coluna Sexo são NaN, enquanto Nota_AP_1 possui apenas 16.67%, aproximadamente, de dados NaN. Queremos eliminar a coluna Sexo mas não eliminar Nota_AP_1.

Para eliminar uma coluna em específica podemos usar a função drop(), informando as colunas em específico que queremos eliminar.

Eliminamos especificamente a coluna Sexo, sem aplicar essa alteração na variável dataframe.

4–3 Substituição dos dados NaN

Caso uma determinada coluna (feature) possua dados NaN, mas, por ser uma informação relevante dentro da regra de negócio, se toma a decisão de não eliminar essa coluna (tópico 4–2) e nem eliminar esses registros NaN (tópico 4–1), pois perderia uma quantidade considerável de informações. Então, a técnica que sobra é o preenchimento desses dados NaN por algo para aproveitamento desses registros.

Esse procedimento também é chamado de Imputação. Essa imputação pode ocorrer de forma única ou múltipla. Na imputação única , um valor único para cada uma das observações ausentes é gerado. O valor imputado é tratado como o valor verdadeiro, ignorando o fato de que nenhum método de imputação pode fornecer o valor exato. Portanto, a imputação única não reflete a incerteza dos valores ausentes. Enquanto na imputação múltipla, muitos valores imputados para cada uma das observações ausentes são gerados. Dessa forma, essa técnica reflete a incerteza dos valores ausentes, sendo mais vantajoso nesse sentido ao comparar com a imputação única. No entanto, neste artigo, irei apenas envolver a imputação única.

Existe a opção de substituí-los por um valor fixo, pela média dos dados que não são NaN naquela coluna, o dado mais frequente, entre outras… Para saber qual a melhor forma de preenchimento deve-se avaliar a lógica daquela feature dentro das regras de negócio. Por exemplo, se uma coluna que representa notas de uma prova possui dados NaN, uma possível interpretação é usar esses dados NaN como notas nulas ou alunos que faltaram a prova. Uma substituição seria colocar no lugar dos dados NaN o valor 0.

| Preenchimento usando fillna()

Uma função simples de se utilizar é o fillna(). Ela realiza o preenchimento de todos os dados NaN por um determinado valor, podendo ser aplicado sob todo o dataframe ou em uma coluna específica.

Aplicando fillna para todo o dataframe. Dá para observar que Sexo foi preenchido por 0 sendo que é uma coluna categórica e não numérica. Então talvez, para esse dataframe, vale a pena usar fillna() para cada coluna, tendo uma estratégia mais específica.
Aplicou-se o fillna apenas na coluna Sexo, colocando NI (Não Identificado), fazendo mais sentido com a característica dessa feature e com o seu tipo de dado.

No entanto, quando queremos aplicar estratégias de preenchimento envolvendo pegar os dados mais frequentes, substituir pela mediana ou média, temos que combinar outras funções com a fillna(). Porém, existe uma alternativa que é a função SimpleImputer().

| Preenchimento usando SimpleImputer

Essa função se encontra dentro do Sklearn, permitindo que criemos variáveis que representem estratégias para preenchimento de dados NaN. Facilitando esse processo e tornando-o mais simples, principalmente quando você tem uma grande quantidade de colunas com dados NaN que será aplicada a mesma estratégia de substituição de dados.

Ao chamar essa função, existem os seguintes parâmetros:

  • missing_values: como os valores missing podem ser interpretados, o padrão é np.nan, mas você pode definir dependendo da situação da tabela.
  • strategy: estratégia de substituição dos dados NaN, tendo opção: ‘mean’ (média), ‘median’ (mediana), ‘most_frequency’ (mais frequente) e ‘constant’ (constante).
  • fill_value: se em strategy for colocado ‘constant’, aqui você coloca qual a constante que substituirá o NaN. Caso strategy seja outra coisa, esse parâmetro não é necessário.

Tanto a estratégia ‘most_frequency’ como ‘constant’ pode ser utilizado para valores numéricos e categóricos.

Definição das estratégias de substituição de dados missing pelo Simple Imputer

Para aplicar a estratégia do SimpleImputer basta combinar com o fit_transform() substituindo a coluna em que será aplicada a estratégia. Observe os exemplos a seguir:

Exemplo onde substituimos os dados NaN da coluna Nota_AP_1 pela estratégia constante, a coluna Turma pelo valor mais frequente, a coluna Idade pela mediana e a coluna Peso pela média, utilizando o SimpleImputer() combinado com o fit_transform().
Resultado da tabela após essa operação.

Bem, vimos uma forma interessante de susbtituir os valores NaN, mas você deve estar se perguntando, quando substituir por uma constante, pelo mais frequente, média ou mediana? Cada caso é um caso, mas a seguir pontuo alguns aspectos que podem ajudar nessa decisão.

Substituição por constante

Quando a existência dos missing values são conhecidos utilizamos essa estratégia. Por exemplo, no caso das notas, se sabe que os NaN na coluna notas remetem a alunos que zeraram a prova ou faltaram a prova. Se a situação permitir considerar essas duas situações como nota 0, pode-se substituir os valores nulos pela constante 0. Logo, é nesse tipo de situação que podemos aplicar a estratégia de constante.

Substituição pelo valor mais frequente

Essa prática não é muito comum para colunas numéricas, sendo mais comum em colunas categóricas, em que trabalhamos com classes e valores discretos.

Substituição pela média ou mediana

A substituição pela média ou mediana dos valores se aplica apenas em colunas numéricas, em que apenas é recomendado essa estratégia quando não existe um grande volume de dados NaN. Essa estratégia é usada quando não se sabe o exato motivo ou origem dos dados NaN. A escolha entre substituir a média ou mediana depende do contexto, em determinados casos uma pode ser melhor que a outra e vice-versa. Mas uma dica é observar a distribuição dos dados dessa coluna.

Se os dados possuírem uma distribuição simétrica, a média e mediana são relativamente próximas, então, no fim, não faz muita diferença substituir pela média ou mediana. Em caso de uma distribuição assimétrica, a mediana se torna uma melhor escolha, pois a média será influenciada pelos valores na extremidade da distribuição. Logo, a mediana é a melhor representação da maioria dos valores da variável.

Distribuição simétrica e assimétrica. Fonte: https://www.slideshare.net/JoaoAlessandro/aula-20-medidas-de-assimetria/4

5. Conclusão

Neste artigo introduzimos a ideia de transformação e tratamento de dados NaN, mostrando formas de identificar e trabalhar com esses dados. A intenção é ser um artigo introdutório e focalizado para essa área de tratamento de dados. Apresentamos algumas funções interessantes do Pandas e do Sklearn para auxiliar nesse processso. Espero que tenha sido útil! Se tiver interesse em conversar ou me enviar sugestões/dicas, fique à vontade de comentar o post ou me contatar via Linkedin.

--

--

Iury Rosal
Data Hackers

Analista de Dados @Accenture | Bacharel em Engenharia de Computação @UFC