Curso
Nota do editor: Jean-Nicholas Hould é cientista de dados na Intel Security, em Montreal, e ensina como começar em ciência de dados no seu blog.
Análise exploratória de dados (EDA) é uma abordagem estatística voltada a descobrir e resumir um conjunto de dados. Nesta etapa do processo de ciência de dados, você explora a estrutura do seu dataset, as variáveis e seus relacionamentos.
Neste post, vamos focar em um aspecto da análise exploratória: o data profiling.
Data profiling é, essencialmente, resumir seu dataset por meio de estatísticas descritivas. A ideia é usar um conjunto amplo de medidas para entender melhor seus dados. Tipos de dados, valores ausentes, média, mediana e desvio padrão são apenas alguns dos muitos elementos que você precisa levantar ao fazer o perfil de um dataset. O objetivo é ter uma compreensão sólida dos dados para, depois, começar a consultar e visualizar de diferentes formas.
Saiba de onde vêm seus dados
Antes de mergulhar em qualquer EDA, procure saber o máximo possível sobre a procedência dos dados que você está analisando. Entenda como os dados foram coletados e processados. Há transformações anteriores que possam afetar sua análise?
Você deve conseguir responder perguntas como:
- Como foi coletado?
- É uma amostra?
- Foi amostrado corretamente?
- O dataset passou por alguma transformação?
- Existem problemas conhecidos no dataset?
Se você não entende de onde os dados vêm, será difícil tirar conclusões significativas. Além disso, aumenta o risco de cometer erros importantes na análise.
Também vale checar se o dataset está estruturado de forma padronizada. O formato recomendado é a terceira forma normal, também chamada de tidy data. Um dataset “organizado” tem as seguintes características:
- cada variável forma uma coluna e contém valores
- cada observação forma uma linha
- cada tipo de unidade observacional forma uma tabela
Seguir esse formato padronizado acelera sua análise, já que é compatível com muitas ferramentas e bibliotecas.
Data profiling
Neste post, usaremos um dataset de cervejas artesanais (Craft Beers) do site CraftCans. Esse dataset contém apenas dados de cervejas em lata de cervejarias dos Estados Unidos. Não está claro no site se ele cobre todas as cervejas enlatadas produzidas nos EUA. Para jogar seguro, vamos tratá-lo como uma amostra que pode conter vieses.
Esta é a estrutura dos datasets que você vai usar:
Beers:
ID: identificador único da cerveja.Name: nome da cerveja.ABV: teor alcoólico por volume da cerveja.IBU: International Bittering Units da cerveja.Style: estilo da cerveja.Ounces: onças de cerveja.
Breweries:
ID: identificador único da cervejaria.Name: nome da cervejaria.City: cidade onde a cervejaria está localizada.State: estado onde a cervejaria está localizada.
Tipos de dados
O primeiro passo é entender a composição do seu dataset.
Com quais variáveis você está lidando?
Em geral, os dados se encaixam em uma destas categorias:
- numérico
- categórico
- texto
- data
Primeiro, vamos importar os datasets disponíveis neste repositório com a função from_csv do pandas. Também vamos juntar os datasets beers e breweries para facilitar as análises depois.
import pandas as pd
beers = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_beers.csv")
breweries = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_breweries.csv")
beers_and_breweries = pd.merge(beers,
breweries,
how='inner',
left_on="brewery_id",
right_on="id",
sort=True,
suffixes=('_beer', '_brewery'))
Com a biblioteca pandas, você pode usar a função dtypes para listar cada coluna e seus tipos de dados.
beers.dtypes
O resultado é o seguinte:
abv float64
ibu float64
id int64
name object
style object
brewery_id int64
ounces float64
dtype: object
Como dá para ver, essa função não agrupa os diferentes tipos de forma muito clara. Os vários tipos numéricos (float64 e int64) não entram em uma única categoria, como gostaríamos. Além disso, algumas colunas aparecem como objetos, o que não ajuda muito.
Para contornar isso, você pode criar sua própria função para determinar a categoria de cada coluna em um DataFrame.
def get_var_category(series):
unique_count = series.nunique(dropna=False)
total_count = len(series)
if pd.api.types.is_numeric_dtype(series):
return 'Numerical'
elif pd.api.types.is_datetime64_dtype(series):
return 'Date'
elif unique_count==total_count:
return 'Text (Unique)'
else:
return 'Categorical'
def print_categories(df):
for column_name in df.columns:
print(column_name, ": ", get_var_category(df[column_name]))
Beers variáveis
print_categories(beers)
O comando acima retorna:
abv : Numerical
ibu : Numerical
id : Numerical
name : Categorical
style : Categorical
brewery_id : Numerical
ounces : Numerical
Breweries variáveis
print_categories(breweries)
Que gera o seguinte resultado:
name : Categorical
city : Categorical
state : Categorical
id : Numerical
Com essas informações, você já entende melhor o dataset. Sabemos que estamos lidando apenas com dados categóricos e numéricos. Variáveis numéricas permitem extrair diversas medidas, como média, desvio padrão etc. Variáveis categóricas são ótimas para segmentar e agrupar os dados. Por exemplo, você pode querer entender como o IBU varia entre os diferentes estilos de cerveja.
Estatísticas descritivas
Nesta seção, vamos percorrer várias estatísticas descritivas que ajudam a entender melhor os dados. Note que cada medida, isoladamente, não diz muita coisa. É na combinação delas que você extrai mais valor.
Vamos focar na variável IBU por ser numérica. Esse tipo de variável oferece uma gama mais ampla de métricas do que as categóricas. Você até pode fazer medições em variáveis categóricas, mas com muito mais limitações.
Quantidade de observações
A função len conta o número de observações em uma Series. Ela conta todas as observações, independentemente de haver valores ausentes ou nulos.
length = len(beers["ibu"])
print(length)
Nesta Series, temos um total de 2410 observações.
Contagem (não nulos)
A função count retorna o número de observações não nulas em uma Series.
count = beers["ibu"].count()
print(count)
Como você vê, existem 1405 observações não nulas na Series.
Valores ausentes
Com a Length e a Count, conseguimos calcular o número de valores ausentes. É a diferença entre Length e Count.
number_of_missing_values = length - count
pct_of_missing_values = float(number_of_missing_values / length)
pct_of_missing_values = "{0:.1f}%".format(pct_of_missing_values*100)
print(pct_of_missing_values)
Para exibir a porcentagem de ausentes, basta dividir o número de valores ausentes pelo total de observações (length). A função float garante que os decimais sejam considerados na divisão. A função format formata o número como porcentagem.
Neste caso, faltam quase 42% dos valores de IBU. Isso é importante porque vai afetar sua análise. A maioria das estatísticas descritivas ignora valores ausentes, o que certamente pode introduzir viés.
Valor mínimo/máximo
Os valores mínimo e máximo podem ser obtidos facilmente com as funções min e max em uma Series.
print("Minimum value: ", beers["ibu"].min())
print("Maximum value: ", beers["ibu"].max())
Os valores mínimo/máximo ajudam a entender a faixa de uma variável. Aqui, o IBU vai de 4 a 138.
Moda
A moda é o valor mais frequente em um dataset. Você a obtém com a função mode em uma Series.
print(beers["ibu"].mode())
Em uma distribuição normal, a moda é igual à mean e à median.
Neste caso, a moda da variável IBU é 20. É o IBU mais frequente no dataset.
Média
A média é uma medida de tendência central. Representa a soma dos valores dividida pela contagem de observações não ausentes.
Pode ser obtida com a função mean em uma Series.
mean = beers["ibu"].mean()
A média é sensível a outliers. Alguns valores extremos podem puxá-la para cima ou para baixo.
Mediana
A mediana também é uma medida de tendência central. É o valor exatamente no meio de uma lista ordenada de valores numéricos.
median = beers["ibu"].median()
Em distribuições assimétricas, a mediana é uma medida melhor de tendência central do que a média.
No caso da distribuição de IBU, média e mediana estão na mesma ordem de grandeza.
Desvio padrão
O desvio padrão é uma medida de dispersão. Um desvio padrão alto indica que os pontos estão espalhados por uma faixa ampla de valores. Ele é expresso na mesma unidade dos valores.
standarddev = beers["ibu"].std()
Neste caso, o desvio padrão é ~26 (25.954065911259324, para ser exato). Se a distribuição de IBU fosse normal, ~68% das observações estariam a uma distância de um desvio padrão da média.
Estatísticas de quantis
Quantis são pontos de corte que dividem uma distribuição em partes iguais. Muitos quantis têm nome próprio. Ao dividir em quatro grupos iguais, temos os quartiles. É fácil calcular quantis com a função quantile em uma Series. Você passa um array com os quantis desejados. Abaixo, vamos dividir em quatro grupos iguais.
quantile = beers["ibu"].quantile([.25, .5, .75])
| 0.25 | 21.0 |
| 0.50 | 35.0 |
| 0.75 | 64.0 |
| Name: ibu, | dtype: float64 |
Como você vê, o quantil de 50% é igual à mediana: o valor que divide o dataset ao meio. Note também que 75% das observações são menores ou iguais a 64 IBU. Além disso, 50% da distribuição está entre 21 e 64 IBU. Importante: valores ausentes não entram nesses cálculos.
Gráficos de distribuição
Visualizações são muito úteis na análise exploratória. Aqui não vamos aprofundar o tema visualização, mas não dá para falar de data profiling sem mencionar a importância de um gráfico de distribuição de frequências. É uma das visualizações mais simples e poderosas: mostra a frequência de cada valor no dataset.
Para criar essa visualização, usamos a biblioteca seaborn com a função displot. Essa função espera uma Series sem valores ausentes.
import seaborn as sns
sns.set(color_codes=True)
sns.set_palette(sns.color_palette("muted"))
sns.distplot(beers["ibu"].dropna());

Nesse gráfico, dá para ver claramente alguns dos valores que calculamos. O mínimo fica perto de 0 IBU e o máximo perto de 140 IBU. A moda fica perto de 20 IBU. Além disso, aparece um pico próximo de 60 IBU.
Por que há dois picos nessa distribuição?
O que explica isso? Esse é um ponto para explorar na segunda fase da análise exploratória.
Correlação
Correlação é ótima para descobrir relações entre variáveis numéricas. Há várias formas de calculá-la. O coeficiente de correlação de Pearson é amplamente usado e mede a dependência linear entre duas variáveis. O coeficiente varia de -1 a 1: 1 é correlação totalmente positiva, -1 é totalmente negativa e 0 indica ausência de correlação linear. Podemos calcular isso com a função corr em um Series. Por padrão, ela usa Pearson, mas é possível escolher outros métodos.
beers[["abv", "ibu", "ounces"]].corr()
| abv | ibu | ounces | |
| abv | 1 | 0.670621 | 0.172529 |
| ibu | 0.670621 | 1 | 0.054691 |
| ounces | 0.172529 | 0.054691 | 1 |
Como esperado, a correlação de IBU com ele mesmo é 1. Mais interessante: a correlação entre ABV e IBU é 0.670621. Não é uma correlação perfeita, mas é alta. Vale explorar esse ponto mais a fundo.
Algumas notas sobre variáveis não numéricas
As métricas acima valem, em sua maioria, para valores numéricos. Se você estiver lidando com dados de outros tipos, como categóricos, ainda dá para coletar medidas interessantes. Por exemplo, calcular a frequência de cada valor no dataset.
DataFrames têm a função describe, que resume o dataset. Se o seu DataFrame tiver apenas valores categóricos ou de texto, o resumo será adaptado para esse tipo de dado.
beers[["name", "style"]].describe()
| name | style | |
| count | 2410 | 2405 |
| unique | 2305 | 99 |
| top | Nonstop Hef Hop | American IPA |
| freq | 12 | 424 |
Bibliotecas para profiling
Como você viu, coletar estatísticas descritivas pode ser trabalhoso. Felizmente, existem bibliotecas que fazem esse processamento para você e geram um perfil claro dos seus dados. Uma delas é a pandas-profiling, que oferece profiling estatístico pronto para uso. Como nosso dataset está organizado e padronizado, podemos aplicar a biblioteca diretamente.
import pandas_profiling
pandas_profiling.ProfileReport(beers_and_breweries)
Mais perguntas
Em geral, depois de fazer o profiling do dataset, você termina com muito mais perguntas do que no início. Ótimo: essas novas perguntas vão impulsionar sua análise exploratória.
Aqui vão algumas questões que surgiram durante o profiling:
- 41,7% dos valores de
IBUestão ausentes. Por quê? Como isso pode afetar nossa análise? - Há dois picos na distribuição de
IBU. O que explica isso? - O que explica a correlação entre
IBUeABV? Qual é a influência do estilo da cerveja nessa correlação? - Existem diferenças de
IBU,ABVouStyleentre regiões geográficas? E entre East Coast e West Coast?
Data profiling não é um processo linear. À medida que você filtra e segmenta o dataset, vai voltar a ele e coletar estatísticas descritivas em subgrupos.
Próximos passos
Neste post, você viu como fazer o perfil de um dataset. Agora você sabe como atribuir variáveis a grupos de tipos de dados específicos. Também calculou diferentes estatísticas descritivas e entende como interpretá-las. Por fim, conheceu bibliotecas que ajudam a automatizar o profiling. E, mais importante, gerou novas perguntas para turbinar sua análise exploratória.
Confira o tutorial de análise exploratória de dados em Python da DataCamp.


