Engenharia de Features: Transformando dados categóricos em dados numéricos

Utilizando técnicas de Label Encoder, One Hot Encoding e Dummies Enconding para trabalhar com dados categóricos em modelagens de machine learning

Iury Rosal
Data Hackers
10 min readMar 21, 2021

--

Fonte: https://unsplash.com/photos/lRoX0shwjUQ

0. Introdução

Dados categóricos são comuns de serem encontrados em bases de dados. Esse dados são basicamente classes que permitem agruparmos os registros das bases em relação à eles, sendo relacionados com regras e processos do negócio responsáveis pela base de dados. Por exemplo, é comum localizar a feature “UF” nas bases de dados, o que permite que agrupemos os registros por estado, o que leva à realização de uma análise descritiva das outras variáveis por estado.

Esses dados categóricos podem ser ordinais ou nominais. Os nominais são aqueles que não remetem a uma ordem, por exemplo, “UF”, “Gênero”, “Tipo de filme/jogo”, etc… Já dados categóricos ordinais remetem a uma ordenação, como tamanho de roupa, classificação de algo (1 estrela, 2 estrelas, ….), etc… No geral, o tratamento de dados categóricos nominais e ordinais não se diferem muito, apenas com o detalhe que nos ordinais temos esse fator ordenação. Ao longo do artigo veremos as diferenças de trabalho entre esses dois tipos de dados categóricos.

Quando falamos de uma modelagem de machine learning, esses dados categóricos, muitas vezes, devem ser incluídos no modelo por expor alguma característica relevante. Tendo um exemplo, uma base de residências a venda de uma determinada cidade. Na tentativa de construir um modelo preditivo para previsão dos valores das casas, a feature “Bairro” pode ser uma variável categórica interessante, pois determinados bairros podem valorizar os imóveis mais facilmente que outros, o que influencia consideravelmente no preço do imóvel.

Todavia, os modelos de machine learning trabalham basicamente com a Matemática, por isso, não existe a possibilidade do modelo trabalhar diretamente com dados categóricos em formato de string (texto), como normalmente encontramos. Nesse caso, entra a importância das técnicas de Label Enconding, One Hot Enconding e Dummy Enconding para converter esses dados categóricos em dados numéricos, sem prejudicar a lógica de separação proporcionada pelo próprio dado categórico e assim poder ser utilizado dentro de uma modelagem.

Neste artigo iremos entender esses conceitos, seguindo a lógica do Círculo de Ouro: Por quê, Como e O quê. Bem, o porquê da existência de dados categóricos e de utilizar essas técnicas já abordamos nessa introdução, iremos agora entender sobre como podemos trabalhar com os dados categóricos e, por fim, o que fazer com esses dados categóricos aplicando as técnicas.

1. Identificando dados categóricos

Inicialmente, uma ferramenta muito boa é o conhecimento da base de dados e das regras de negócio. A partir desse conhecimento fica fácil de localizar as colunas que representam features categóricas, assim como o significado de cada classe presente naquela coluna. Combinado a isso, deixo a sugestão do uso da função info() da biblioteca Pandas, em que você pode ver todas as colunas do seu dataframe, assim como a quantidade de dados não nulos e a tipagem dos dados. Deixo a sugestão de leitura do meu artigo sobre como lidar com dados NaN (dados missing).

Utilizando info() para investigar colunas do dataframe e localizar colunas categóricas. Neste exemplo, vemos que Turma é uma feature categórica e possui 8 registros não nulos do tipo string. Podemos dizer que turma é uma feature categórica nominal.

Normalmente, os dados categóricos vem com a tipagem object que remete ser do tipo string. Mas dependendo da situação, esses dados categóricos podem vim com formato inteiro, com valores discretos. Ou seja, o número 1 simboliza uma determinada classe, assim como o número 2, etc…

Identificado as colunas dos dados categóricos, o próximo passo é verificar a frequência de classes, para entendermos como está a distribuição de cada classe no dataframe. Para isso podemos utilizar o método value_counts() da biblioteca Pandas, que realiza justamente essa contagem de frequência por valor.

Uso do value_counts() na coluna Turma para verificar a contagem de frequência de cada classe. Observamos que “T 1” é mesma coisa que “T1” apenas tendo um erro de formatação. Em breve, lidaremos com isso. Mas vemos que T2 e T1 possuem a mesma frequência no geral (4 registros).

Com a função unique() podemos visualizar as classes presentes em uma determina feature categórica.

Utilizando unique() é exibido apenas as classes presentes na coluna

Uma curiosidade é que podemos melhorar a visualização da frequência das variáveis categóricas utilizando um gráfico de barras. Para isso utilizaremos o MatPlotLib para chamar a função bar(), que gerará esse gráfico de barras. Passamos como primeiro parâmetro (o que ficará no eixo X) as classes. Podemos fazer isso na função value_counts(), acessando o atributo index, que retorna um array justamente com essa informação. O segundo parâmetro (a altura das barras) será a frequência de cada classe. Utilizamos o value_counts(), no entanto, como queremos apenas os valores das frequências pegamos o atributo values combinada a essa função para apenas obtermos o array com os valores dessas frequências. A imagem abaixo resume esse processo e apresenta o resultado:

Visualização da frequência das classes

Cuidado!

Foi utilizado o value_counts().index em vez do unique() no eixo X, pois o unique(), apesar de retornar todas as classes, não as retorna na mesma ordem que o value_counts(), pois o value_counts() ordena da classe com maior frequência para a de menor frequência, sendo que não é algo levado em conta no unique().

Por meio dessa verificação você pode notar classes que não aparecem nos registros ou classes duplicadas. A compreensão da existência ou não desses aspectos é extremamente importante.

2. O que fazer com esses dados?

Agora que vimos formas de visualizar os dados categóricos, assim como, as classes presentes em cada feature categórica. Vamos começar a ver como podemos lidar com a transformação dessas features categóricas em variáveis numéricas. Mas antes de tudo, é importante lidarmos com a situação de classes duplicadas…

2–1 Classes duplicadas

No nosso exemplo apareceu duas classes que representam a mesma coisa, logo, podemos substituir uma dessas classes pela outra para facilitar o processamento desses dados. Podemos usar um dicionário, que conterá nas keys o nome atual de cada classe e em value o novo nome da classe. Após isso, usamos esse dicionario dentro da função map() aplicando na coluna do dataframe. Observe:

Como ‘T 1’ e ‘T1’ representam a mesma coisa, renomeamos as classes de forma que ‘T 1’ e ‘T1’ sejam unificados em uma única classe.

No entanto, para esse processo ser aplicado em cima do dataframe precisamos sobrescrever essa coluna, já que map() não possui um parâmetro Inplace.

Aplicando o processo no dataframe original

Dessa forma, conseguimos eliminar classes duplicadas, unificando-as.

2–2 Precisamos de números…

Com classes sem duplicação, podemos começar a lidar com o processo de transformação de dados categóricos em numéricos.

Como comentamos na introdução desse artigo, para podermos utilizar esses dados categóricos em uma modelagem precisamos que se tornem dados numéricos, pois, muitas vezes, eles vem no formato de string. Como fazer isso?

Aqui entra a técnica de Label Enconder, onde as classes são substituídas por números. Por exemplo, ‘Classe A’ virará 0, ‘Classe B’ virará 1, ... Para fazer isso podemos utilizar o método LabelEnconder() da biblioteca Sklearn para aplicar esse processo, como fazemos abaixo:

Se observar, ao anexar no dataframe uma coluna com o resultado do LabelEnconder(), os registros que pertendem a ‘T1’ na “Turma” possuem valor 0 na coluna “Labels”. Enquanto os registros que pertecem a ‘T2’ na “Turma” possuem valor 1 na coluna “Labels”

No fim, o LabelEnconder() funciona pela lógica de mapeamento aplicado pela função map() apresentado no tópico 2–1. A vantagem aqui é que não precisamos construir aquele dicionário, o próprio LabelEnconder() faz isso pra gente e aplica a substituição das classes por valores numéricos. Aqui fizemos um processo não destrutivo anexando essa informação em uma coluna separada, mas você pode sobrescrever a coluna “Turma”, sendo uma operação destrutiva.

Vamos colocar um subtópico, pois o processo acima pode ser feito em dados categóricos nominais, no entanto, ao replicar o mesmo em dados categóricos ordinais, podemos correr o risco de perder a lógica de ordenação. Por exemplo, ‘Classe B’ vem primeiro que a ‘Classe A’ na lógica de ordenação, mas ao aplicar o LabelEnconder() da forma anteriormente apresentada a ‘Classe A’ virará 0 e a ‘Classe B’ virará 1, o que quebra a lógica de ordenação. Precisamos evitar que isso aconteça.

Label Enconder em Dados Categóricos Ordinais

Como não tem como o método advinhar a ordenação das classes, pois é algo estritamente ligado a regra de negócio por trás da base de dados, para resolver essa questão deve-se fazer o procedimento manual, utilizando a função map(). Ou seja, cria-se o dicionário definindo essa ordem e aplica-se a função sobre a coluna, análogo ao que foi apresentado no tópico 2–1.

2–4 Mas calma-lá… esses números não podem ser a melhor saída!

Bem, após aplicar a técnica do Label Enconder, você pode supor que já pode utilizar essa coluna dentro da modelagem sem nenhum problema, correto? Na verdade, utilizar a coluna gerada na técnica de Label Enconder na modelagem não é o passo final, pois existe um problema. Como sabemos a modelagem é pura matemática. Ao aplicar essa lógica, o modelo pode interpretar como uma classe ser mais importante ou de maior valor que as outras e isso pode enviesar a predição do modelo. Por exemplo, uma ‘Classe A’ recebe valor 0 no Label Enconder, enquanto uma ‘Classe I’ recebe valor 7. A modelagem pode interpretar a ‘Classe I’ como superior à ‘Classe A’, pois 7 > 0. Para evitar esse problema, a partir do Label Enconder, podemos aplicar a técnica do One Hot Enconding.

Nesta técnica, suponha que temos 1 feature categórica com m classes. Nesse processo, essa coluna será substituída por m colunas de valor binário, ou seja, colunas preenchidas por apenas valor 0 e 1. Para afirmar que um determinado registro pertence a uma classe, o valor 1 deve estar na coluna que representa essa classe e 0 nas demais colunas (que representam as outras classes). Essa lógica se aplica para todos os registros. Ou seja, agora em vez de termos uma única coluna com a informação da classe, temos um conjunto de colunas que a interseção delas dirá qual a classe o determinado registro pertence.

Para aplicar esse processo, podemos utilizar o método OneHotEnconder() presente na biblioteca do Sklearn.

Aplicação do OneHotEncoder(). Observe que aplicamos sobre a coluna “Labels” que é a coluna resultante do LabelEnconder(). O método toarray() auxilia na formatação do resultado do processo. Nosso exemplo possuia 2 classes: ‘T1’ e ‘T2’, veja que o Output do OneHotEncoder() foram duas colunas binárias.

Cuidado!

O OneHotEnconder() deve ser aplicado após o uso do LabelEnconder(). O OneHotEnconder() deve ser utilizado na coluna que representa o output do LabelEnconder().

Agora vamos colocar esse resultado no nosso dataframe. Inicialmente, podemos gerar um dataframe com o resultado do OneHotEnconder(). Para isso utilizamos a feature_arr com os valores binários e pegamos as classes via o atributo classes_ da variável label_enconder que criamos durante o tópico 2–2, para darmos nomes a essas colunas binárias. Após isso, criamos o dataframe conforme mostra a seguir:

Construção do dataframe resultante do OneHotEnconder(). Observe que agora temos a coluna binária “T1” e “T2".

Por fim, anexamos essas novas colunas no dataframe original utilizando a função concat() do Pandas, como parâmetros axis = 1 para justamente anexarmos colunas ao nosso dataframe original.

Dataframe original com colunas originadas do processo de One Hot Enconding

Observe nosso resultado… O registro 0 (“Nome” = ‘Iury’) está na turma T1 que é a label 0. Nas colunas binárias, “T1” possui o valor 1 e “T2” possui o valor 0, confirmando que esse registro pertence a classe ‘T1’. Dessa forma, na modelagem preditiva poderia ser utilizada essas colunas “T1” e “T2” em vez da coluna “Turma”, que é inviável por não ser numérica, e da coluna “Labels”, que poderia causar problemas da interpretação do modelo quanto ao significado dos valores discretos que representam as classes.

Esse processo pode ser usado da mesma forma tanto para dados categóricos nominais como ordinais, tendo apenas cuidado no passo do LabelEnconder() como foi citado no tópico 2–2.

2–4 Dummies do Pandas, uma imitação do One Hot Enconder?

Essa estratégia do Pandas é bem similar ao One Hot Enconder. No entanto, existem uma diferença!

Você não precisa aplicar LabelEnconder() antes da estratégia do Dummies Enconding, você pode aplicá-la diretamente na coluna de classe com as strings se quiser.

Aplicando a estratégia de get_dummies()

Observe na imagem que utilizamos a mesma estratégia de concatenação ao dataframe original via as colunas (axis=1), conforme fizemos no One Hot Enconding no tópico 2–3. Aplicamos o get_dummies sobre a feature categórica denominada “Turma”, sem a necessidade de aplicação da estratégia Label Enconder antes do uso desta.

A estratégia de reduzir uma coluna binária

Uma possível estratégia do Dummy Enconding é, tendo m classes em 1 feature categórica, gerar m-1 colunas binárias. A classe que teve sua coluna binária descartada na geração das colunas binárias fica simbolizada por uma linha totalmente preenchida por 0. Ou seja, se tenho ‘Classe A’, ‘Classe B’, … ‘Classe m’, serão gerados colunas binárias ‘Classe B’, …, ‘Classe m’, considerando que a ‘Classe A’ (primeira) foi a escolhida para ser ignorada. Logo, o registro que pertence a ‘Classe A’ terá valor 0 em todas as colunas binárias de ‘Classe B’, …, ‘Classe m’.

Vamos ver um exemplo aplicando essa estratégia utilizando a função get_dummies() da biblioteca Pandas.

Observe que aplicamos o get_dummies() na coluna “Turma” e o parâmetro drop_first ativado (True) escolhe a primeira classe como a ser esquecida. O método get_dummies() retorna um dataframe com o resultado desse processo, por isso, usamos o pd.concat para anexar esse resultado ao dataframe original

Veja que os registros que pertencem a ‘ T2' possuem valor 1 na coluna, enquanto registros com valor ‘T1’ na coluna “Turma” possuem valor 0 na coluna ‘T2’, indicando que o registro pertence a ‘T1’. Se existisse mais turmas, as colunas binárias referentes à elas possuiriam valor 0 também para os registros que pertencem a ‘T1’.

O resultado dessa operação do get_dummies() pode ser facilmente reproduzida pelo One Hot Enconder, basta eliminar uma coluna das binárias, por exemplo, a última, você obteria um resultado análogo, em que os registros que pertencem a essa classe que a coluna binária foi deletada teriam valor 0 nas outras colunas binárias referentes as outras classes.

Mas você deve estar se perguntando, por que descartar uma das colunas? Bem, pode ser um problema se um conjunto de dados tiver recursos altamente correlacionados, pois eles codificam efetivamente as mesmas informações. Por exemplo, uma coluna de “Gênero” ao gerar duas colunas, uma para a classe “Masculino” e outro para a classe “Feminino”, as duas podem ser correlacionadas negativamente. Se o registro for do gênero Masculino, implica ele não pertencer ao gênero Feminino, e vice-versa. Nessa situação, precisamos apenas utilizar uma das colunas.

No entanto, um cuidado deve ser ter… Dependendo da modelagem e da situação, se a variável a ser prevista possua uma alta correlação com as colunas que foram omitidas pode ocasionar em problemas de predição, então nesta situação o ideal é manter as colunas.

3. Conclusão

Neste artigo, vimos as estratégias para lidar com dados categóricos quando desejamos convertê-los para dados numéricos, a fim de utilizá-los em modelos de machine learning. Espero que tenha sido útil! Caso deseje enviar sugestões ou feedbacks deixo meu Linkedin.

--

--

Iury Rosal
Data Hackers

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