Blog

Postado em em 27 de maio de 2024

Combinando Boxplots com Histogramas Usando Seaborn

Aprenda como combinar Boxplots com Histogramas usando o Seaborn! Veja como construir cada um individualmente e combiná-los em um único gráfico.

Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

Para receber por e-mail o(s) arquivo(s) utilizados na aula, preencha:

Combinando Boxplots com Histogramas Usando Seaborn

Na aula de hoje, eu quero te mostrar como combinar Boxplots com Histogramas usando o Seaborn!

Primeiro, iremos analisar e compreender os dados com os quais estamos trabalhando, para depois partir para a construção do Histograma e do Boxplot.

Vou te mostrar o que são histogramas e boxplots, como construí-los, qual a estrutura desses gráficos e a finalidade deles.

Por fim, vamos combiná-los em um único gráfico e ver como manipulá-lo para que você possa construir algo personalizado para os seus projetos e análises de dados.

Então, faça o download do material disponível e vem comigo aprender como combinar Boxplot com Histograma no Seaborn!

Boxplot com Histograma

Combinar o boxplot com histograma nos permite construir um gráfico muito útil para realizar análise exploratória com valores numéricos contínuos.

Boxplots com Histogramas

Esse gráfico nos permite visualizar o comportamento da distribuição dos valores dentro da base de dados, assim como identificar valores discrepantes, os outliers.

Para a construção dele, utilizaremos a biblioteca Seaborn, baseada no Matplotlib para criação de gráficos.

Análise da Base de Dados – Criando o DataFrame

Nessa aula, utilizaremos uma base de dados bastante simples que apresenta apenas os preços de fones de ouvido obtidos através de uma pesquisa online.

Vamos começar criando nosso DataFrame utilizando a biblioteca do Pandas.

import pandas as pd

DADOS = "dados/precos_fone_ouvido.csv"

df = pd.read_csv(DADOS)

df

Criando o DataFrame

Perceba que temos apenas uma coluna chamada Price, com 100 linhas contendo os diferentes preços dos fones de ouvidos encontrados nessa busca online.

Um passo importante sempre que estamos trabalhando com análises de dados é verificar os tipos de dados com os quais estamos lidando para garantir que eles realmente são do tipo esperado. Para isso, vamos usar o método info().

df.info()

Método info

A partir desse método, podemos constatar que existem de fato 100 valores não nulos do tipo float, que é um tipo de dado esperado quando estamos lidando com preço.

Além disso, podemos utilizar o método describe() para obter estatísticas descritivas básicas como média, desvio padrão, mínimo e máximo, e os quartis.

df.describe()

Método describe

Podemos melhorar a visualização desses dados da seguinte forma:

with pd.option_context("float_format", "{:.2f}".format):
    display(df.describe())

Método describe formatado

Dessa forma, estamos definindo um contexto para os nossos dados, fazendo com que os preços sejam exibidos como um float com duas casas decimais.

Variável Quantitativa Contínua

Quando estamos trabalhando com preços, a princípio, podemos ter qualquer valor possível entre o valor máximo e mínimo encontrado.

Esse tipo de variável é o que chamamos de variável quantitativa contínua. Esse tipo de variável pode assumir um número infinito de valores dentro de um intervalo específico.

As variáveis quantitativas contínuas podem assumir um número infinito de valores dentro de qualquer intervalo, incluindo frações e valores decimais.

Se observamos a coluna Price utilizando o método value_counts do Pandas, podemos obter um padrão e uma frequência em que os preços aparecem na tabela.

df["Price"].value_counts()

método value_counts

É possível perceber que o valor que mais aparece é o de 199 reais. E que, apesar de serem do tipo float, esses preços acabam se comportando como números inteiros, pois todos eles possuem zero centavos.

Dividindo Valores em Intervalos (Bins)

Quando estamos lidando com valores de variáveis contínuas, é mais interessante, para uma melhor compreensão dos dados, a distribuição deles em intervalos. Para isso, podemos utilizar o parâmetro bins, definindo quantos intervalos desejamos.

df["Price"].value_counts(bins=10)

Dividindo Valores em Intervalos (Bins)

Dessa forma, conseguimos encontrar as faixas de valores onde a maioria dos preços se encontram. O que torna nossa análise mais intuitiva e significativa do que quando observamos os preços individualmente.

Também é possível ordenarmos esses intervalos do menor para o maior, utilizando o método sort_index().

df["Price"].value_counts(bins=10).sort_index()

Dividindo Valores em Intervalos (Bins)

Com essa análise dos resultados por intervalos, fica mais fácil visualizarmos onde temos mais acúmulos de fones de ouvido por valor e a distribuição de preço deles no mercado. Esse é o mesmo princípio e lógica que seguimos quando vamos construir um histograma.

Histogramas

Um histograma é uma representação visual da distribuição de dados quantitativos. Para construir um histograma, o primeiro passo é agrupar o intervalo de valores – dividir todo o intervalo de valores em uma série de intervalos – e depois contar quantos valores caem em cada intervalo.

Os intervalos (bins, em inglês) são geralmente especificados como intervalos consecutivos e não sobrepostos de uma variável. Os bins devem ser adjacentes e geralmente (mas não são obrigados a ser) de tamanho igual.

Os histogramas dão uma ideia aproximada da densidade da distribuição subjacente dos dados e muitas vezes para a estimativa de densidade, ou seja, estimar a probabilidade da variável subjacente.

Para construir nosso Histograma, vamos importar a biblioteca Seaborn e vamos utilizar a função histplot() que é utilizada justamente para criar um histograma.

Para essa variável, vamos definir o nosso eixo x como sendo Price, e a data como nosso dataframe (df).

import seaborn as sns

sns.histplot(x="Price", data=df)

Histogramas

Podemos personalizar o número de intervalos do histograma através do parâmetro bins, que define a quantidade de faixas.

import seaborn as sns

sns.histplot(x="Price", data=df, bins=10)

Histogramas

Parâmetro KDE (Kernal Density Estimation) no Histograma

Para termos uma visualização de forma mais suave da nossa variável contínua, podemos definir o parâmetro KDE como sendo True.

import seaborn as sns

sns.histplot(x="Price", data=df, bins=10, kde=True)

Parâmetro KDE (Kernal Density Estimation) no Histograma

O KDE, Kernel Density Estimation, de forma simplificada, nos permite visualizar de forma mais suave uma variável contínua quando comparado a um histograma.

Através dele, podemos compreender melhor a distribuição dos valores. Por exemplo, é possível perceber que o ápice da linha criada, onde está a maior concentração de produtos, ocorre por volta do valor 190.

Boxplot – Diagrama de Caixa

Um boxplot, ou diagrama de caixa, é um gráfico que fornece informações sobre a distribuição de uma variável numérica.

É um método gráfico para demonstrar a localidade (tendência central), dispersão e assimetria de grupos de dados numéricos através de seus quartis.

Ele é composto por uma caixa que representa o intervalo entre o primeiro quartil (Q1) e o terceiro quartil (Q3) da distribuição. A linha dentro dela representa a mediana, que é o segundo quartil (Q2).

As linhas que se estendem para fora da caixa são chamadas de bigodes (whiskers em inglês) e representam a variabilidade fora do intervalo entre Q1 e Q3.

Os bigodes geralmente se estendem até o valor máximo e mínimo dentro de um intervalo determinado, que geralmente é Q1 – 1,5 * IQR para o limite inferior e Q3 + 1,5 * IQR para o limite superior. Pontos além dos whiskers são considerados outliers.

Boxplot – Diagrama de Caixa

Os quartis são valores que dividem a distribuição em quatro partes iguais:

  • O primeiro quartil (Q1) é o valor que divide os 25% menores valores da distribuição. Ou seja, até Q1, temos os 25% menores valores do intervalo.
  • O segundo quartil (Q2) é o valor que divide os 50% menores valores da distribuição, ou seja, a mediana. Até Q2 estão os 50% menores valores do intervalo.
  • O terceiro quartil (Q3) é o valor que divide os 75% menores valores da distribuição. Até Q3 estão os 75% menores valores dos dados.

O intervalo interquartil (IQR) é a diferença entre o terceiro e o primeiro quartil (IQR = Q3 – Q1). Um valor é considerado outlier se for menor que Q1 – 1,5 * IQR ou maior que Q3 + 1,5 * IQR.

Podemos visualizar esses quartis através do método describe().

with pd.option_context("float_format", "{:.2f}".format):
    display(df.describe())

método describe()

A partir do describe, conseguimos visualizar os valores referentes ao Q1, Q2 e Q3. Ou seja, 25% dos valores estão abaixo de 179.00 (Q1), 50% dos valores estão abaixo de 189.00 (Q2) e 75% dos valores estão abaixo de 199.00 (Q3).

Como Criar um Boxplot com Seaborn

Para criar um boxplot com o Seaborn, basta utilizarmos a função boxplot(), definindo o parâmetro x como Price e data como o dataframe (df).

sns.boxplot(x="Price", data=df)

Boxplot com Seaborn

A partir desse boxplot, já conseguimos identificar algumas informações importantes, como os nossos quartis, a mediana, os valores máximo e mínimo e a presença de outliers, que nesse caso são identificados por losangos pelo Seaborn.

Outro ponto importante é que, dentro da caixa do nosso diagrama, temos 50% dos dados, ou seja, 50% dos valores estão concentrados aproximadamente entre 180 e 200.

Combinando Boxplot com Histograma

Agora que já abordamos os conceitos, criação e análise dos boxplots e histogramas, vamos combinar esses dois gráficos em uma única figura. Para isso, utilizaremos os sistemas de eixos em figuras do Matplotlib.

O objetivo é criarmos dois sistemas de eixos (ax1 e ax2) na nossa figura (fig). Um para representar o boxplot e o outro para o histograma.

Como queremos plotar um gráfico em cima do outro, vamos utilizar os parâmetros nrows (número de linhas) e ncols (número de colunas). Isso nos permite estipular que nossa figura terá duas linhas, uma para cada gráfico, mas apenas uma coluna.

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1)

Feito isso, vamos plotar nossos gráficos como fizemos anteriormente, mas adicionando um terceiro parâmetro, o ax, que definirá em qual sistema de eixo cada gráfico será criado.

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(nrows=2, ncols=1)

sns.boxplot(x="Price", data=df, ax=ax1)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

plt.show()

Combinando Boxplot com Histograma

Com esse código, já estabelecemos a estrutura básica desejada, com o boxplot sendo exibido em cima do histograma. No entanto, a apresentação desses gráficos ainda requer melhorias, e é isso que abordaremos a seguir.

Manipulação de Gráficos com Matplotlib

Para chegarmos a um resultado como o exemplo do início da aula, o primeiro ponto que precisamos ajustar é referente ao eixo x que se repete para os dois gráficos.

Como os gráficos compartilham do mesmo eixo x, podemos definir o parâmetro sharex=True para a função subplots.

Além disso, para melhorar a visualização dos dados, precisamos ajustar a proporção de cada gráfico, deixando o Histograma maior e o Boxplot menor.

Para isso, utilizaremos o parâmetro gridspec_kw. Esse parâmetro recebe um dicionário em que definiremos as chaves height_ratios e hspace.

A chave height_ratios representa a razão de altura e recebe como valor uma tupla, contendo a porcentagem em valores decimais referente à altura do gráfico em ax1 e ax2. Dessa forma, podemos definir que ax1 terá 15% da altura (0.15) e ax2 85% (0.85).

Já a chave hspace serve para definir a distância entre o boxplot e o histograma. Como queremos que os gráficos fiquem mais próximos e o rótulo Price do boxplot seja coberto pelo histograma, podemos definir um valor baixo para essa chave, como 0.02.

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(
    nrows=2,
    ncols=1,
    sharex=True,
    gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02},
)

sns.boxplot(x="Price", data=df, ax=ax1)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

plt.show()

Ajustando Boxplot com Histograma

Repare que já temos uma apresentação bem melhor do que a figura anterior.

Outro elemento que podemos adicionar à nossa figura e que pode facilitar a leitura do gráfico são as linhas de grade.

Esse é um elemento que deve sempre ser ponderado de se adicionar ou não de acordo com o seu objetivo. Em alguns casos, é preferível termos um gráfico mais limpo e em outros um gráfico com mais informações é melhor para visualização dos dados.

Como queremos exibir a linha de grid nos dois gráficos, vamos percorrê-los através de um loop for aplicando o método grid com o parâmetro True.

Além disso, vamos passar os parâmetros linestyle para personalizar o estilo da linha, color para definir a cor dela e alpha para definir a transparência e opacidade, em que 0 é totalmente transparente e 1 é totalmente opaco.

Para que as linhas sejam exibidas por trás do gráfico e não por cima deles, vamos utilizar a função set_axisbelow como True.

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(
    nrows=2,
    ncols=1,
    sharex=True,
    gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02},
)

sns.boxplot(x="Price", data=df, ax=ax1)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

for ax in (ax1, ax2):
    ax.grid(True, linestyle="--", color="gray", alpha=0.5)
    ax.set_axisbelow(True)

plt.show()

Ajustando Boxplot com Histograma

Podemos ajustar os intervalos no eixo x, adicionando mais tracinhos (ticks) a ele, para uma melhor leitura e visualização das informações no gráfico. Dessa forma, podemos ter mais valores sendo exibidos, permitindo uma rápida identificação deles.

Para isso, vamos importar o módulo ticker do Matplotlib e manipular os marcadores do eixo x (xaxis) do ax2.

Como queremos mais valores sendo exibidos no eixo x, vamos utilizar a função set_major_locator para especificar que os principais marcadores devem ser posicionados a cada múltiplo de 5, ou seja, de cinco em cinco unidades.

Além disso, para melhorar a visualização dos valores, vamos configurar os parâmetros dos marcadores no eixo x utilizando o tick_params e passando o argumento rotation=90, para que os rótulos dos ticks sejam rotacionados em 90 graus.

import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

fig, (ax1, ax2) = plt.subplots(
    nrows=2,
    ncols=1,
    sharex=True,
    gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02},
)

sns.boxplot(x="Price", data=df, ax=ax1)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

ax2.xaxis.set_major_locator(mtick.MultipleLocator(base=5.0))
ax2.tick_params(axis="x", rotation=90)

for ax in (ax1, ax2):
    ax.grid(True, linestyle="--", color="gray", alpha=0.5)
    ax.set_axisbelow(True)

plt.show()

Ajustando Boxplot com Histograma

Dessa forma, temos uma melhor legibilidade, permitindo uma interpretação mais fácil e precisa das informações contidas no gráfico.

Agora, vamos adicionar três linhas verticais ao nosso histograma: uma para a média, outra para a mediana e uma para a moda (valor que mais aparece).

Para adicionar linhas verticais ao nosso histograma, utilizaremos a função axvline do Matplotlib. Como queremos posicionar as linhas nos valores da média, mediana e moda, utilizaremos os métodos .mean(), .median() e .mode()[0] para determinar a posição de cada uma.

  • O método .mean() calculará a média da coluna “Price” do nosso dataframe.
  • O método .median() calculará a mediana da coluna “Price”.
  • O método .mode()[0] calculará a moda dos valores na coluna “Price” e selecionará o primeiro valor modal, já que esse método retorna uma série de valores.

Para estilizar as linhas, vamos definir o parâmetro color utilizando os códigos de cores do Matplotlib e definir o estilo de traço com linestyle.

Para identificar cada uma dessas linhas, podemos adicionar o parâmetro label, passando o rótulo correspondente. E, para visualizarmos esses rótulos como legenda, utilizaremos a função legend().

import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

fig, (ax1, ax2) = plt.subplots(
    nrows=2,
    ncols=1,
    sharex=True,
    gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02},
)

sns.boxplot(x="Price", data=df, ax=ax1)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

ax2.xaxis.set_major_locator(mtick.MultipleLocator(base=5.0))
ax2.tick_params(axis="x", rotation=90)

for ax in (ax1, ax2):
    ax.grid(True, linestyle="--", color="gray", alpha=0.5)
    ax.set_axisbelow(True)

ax2.axvline(df["Price"].mean(), color="C1", linestyle="--", label="Média")
ax2.axvline(df["Price"].median(), color="C2", linestyle="--", label="Mediana")
ax2.axvline(df["Price"].mode()[0], color="C3", linestyle="--", label="Moda")

ax2.legend()

plt.show()

Adicionando linhas verticais ao histograma

Para finalizar, podemos adicionar uma linha correspondente à média ao nosso boxplot. Para fazer isso, precisamos definir o parâmetro showmeans=True. Isso informará ao Seaborn para mostrar a média dos dados no boxplot.

Para desenhar a linha no gráfico, vamos utilizar o parâmetro meanline=True e podemos personalizá-la adicionando o meanprops, que define as propriedades dessa linha, como cor, estilo e largura.

import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

fig, (ax1, ax2) = plt.subplots(
    nrows=2,
    ncols=1,
    sharex=True,
    gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02},
)

sns.boxplot(
    x="Price",
    data=df,
    ax=ax1,
    showmeans=True,
    meanline=True,
    meanprops={"color": "C1", "linestyle": "--", "linewidth": 1},
)
sns.histplot(x="Price", data=df, bins=10, kde=True, ax=ax2)

ax2.xaxis.set_major_locator(mtick.MultipleLocator(base=5.0))
ax2.tick_params(axis="x", rotation=90)

for ax in (ax1, ax2):
    ax.grid(True, linestyle="--", color="gray", alpha=0.5)
    ax.set_axisbelow(True)

ax2.axvline(df["Price"].mean(), color="C1", linestyle="--", label="Média")
ax2.axvline(df["Price"].median(), color="C2", linestyle="--", label="Mediana")
ax2.axvline(df["Price"].mode()[0], color="C3", linestyle="--", label="Moda")

ax2.legend()

plt.show()

Adicionando linhas verticais ao boxplot

Com isso, teremos nosso gráfico completo apresentando o boxplot com histograma, assim como o exemplo apresentado no início da aula.

Função para Combinar Boxplot com Histograma

Agora que já criamos e definimos nosso código que combina o boxplot com o histograma, seria interessante transformá-lo em uma função. Dessa forma, podemos salvá-la em um arquivo separado e reutilizá-la em novos projetos sempre que necessário.

Para isso, precisamos colocar nosso código dentro da definição de uma função que chamaremos de box_hist_plot(), que receberá como parâmetros os dados e a coluna.

O parâmetro dados corresponderá ao DataFrame. Portanto, onde no nosso código aparecia escrito df, substituiremos por dados. O mesmo deve ser feito para “Price”, substituindo-o por coluna.

Além disso, você pode parametrizar outros valores contidos na função, como o bins, que usamos para definir os intervalos do histograma. Este pode ser um parâmetro opcional que você pode alterar conforme necessário. Caso contrário, ele utilizará o padrão de 10.

def box_hist_plot(dados, coluna, bins=10):
    """
    Gera um boxplot e um histograma para uma coluna específica em um DataFrame.
    Autor: Francisco Bustamante - https://www.linkedin.com/in/flsbustamante
    Parâmetros
    ----------
    dados : DataFrame
        Os dados de entrada.
    coluna : str
        O nome da coluna no DataFrame a ser plotada.
    bins : int, opcional
        O número de bins a ser usado no histograma. O padrão é 10.
    -------
    """

    fig, (ax1, ax2) = plt.subplots(
        2, 1, sharex=True, gridspec_kw={"height_ratios": (0.15, 0.85), "hspace": 0.02}
    )

    sns.boxplot(
        x=coluna,
        data=dados,
        ax=ax1,
        showmeans=True,
        meanline=True,
        meanprops={"color": "C1", "linestyle": "--", "linewidth": 1},
    )
    sns.histplot(x=coluna, data=dados, bins=bins, kde=True, ax=ax2)

    ax2.xaxis.set_major_locator(mtick.MultipleLocator(base=5.0))
    ax2.tick_params(axis="x", rotation=90)

    for ax in (ax1, ax2):
        ax.grid(True, linestyle="--", color="gray", alpha=0.5)
        ax.set_axisbelow(True)

    ax2.axvline(dados[coluna].mean(), color="C1", linestyle="--", label="Média")
    ax2.axvline(dados[coluna].median(), color="C2", linestyle="--", label="Mediana")
    ax2.axvline(dados[coluna].mode()[0], color="C3", linestyle="--", label="Moda")

    ax2.legend()

    plt.show()

Esse código pode ser salvo em um arquivo à parte. Sempre que você precisar combinar um histograma com um boxplot, você pode importar essa função e chamá-la, passando o DataFrame e a coluna que deseja plotar.

Por exemplo, vou salvar toda essa função dentro de um arquivo chamado auxiliar.py. Além da função, é necessário importar no início do arquivo as bibliotecas e os módulos que utilizamos dentro dela.

Função para Combinar Boxplot com Histograma

Para utilizá-la em outro arquivo, basta importar a função a partir do arquivo auxiliar.py e passar o DataFrame e a coluna que deseja plotar o gráfico.

import pandas as pd

DADOS = "dados/precos_fone_ouvido.csv"
df = pd.read_csv(DADOS)
df

from auxiliar import box_hist_plot

box_hist_plot(df, "Price")

Utilizando a função que cria Boxplots com Histogramas

E pronto! A partir da função que definimos, conseguimos plotar nossos gráficos com poucas linhas. E você pode reutilizá-la para quantos projetos quiser.

Conclusão – Combinando Boxplots com Histogramas Usando Seaborn

Na aula de hoje, mostrei como combinar Boxplots com Histogramas usando o Seaborn e o Matplotlib.

Passamos por todo o processo de análise dos dados, construção e estrutura dos histogramas e boxplots para, ao final, combiná-los em um único gráfico.

Além disso, demonstrei como transformar o código em uma função para que você possa reutilizá-la sempre que necessário.

Hashtag Treinamentos

Para acessar outras publicações de Ciência de Dados, clique aqui!


Quer aprender mais sobre Python com um minicurso básico gratuito?