HOWTO · Matplotlib

Gráfico de densidade Matplotlib

Para gerar um gráfico de densidade usando Python, primeiro estimamos a função de densidade a partir dos dados fornecidos usando o método gaussian_kde() do módulo scipy.stats e depois traçamos a função de densidade para gerar o gráfico de densidade.

Nesta página

Um gráfico de densidade mostra uma estimativa suavizada de como os dados de uma amostra unidimensional estão distribuídos. Em Python, a via habitual é a estimativa de densidade por núcleo (KDE): estime uma função de densidade a partir das observações com gaussian_kde() do módulo scipy.stats, avalie-a numa grelha de valores de x e desenhe a curva resultante com o Matplotlib. Pode obter o mesmo resultado com menos código através de kdeplot() do pacote seaborn ou definindo kind="density" em pandas.DataFrame.plot(). Os exemplos abaixo utilizam NumPy, SciPy e Matplotlib; as variantes com Seaborn e pandas precisam adicionalmente desses pacotes instalados, e o gráfico do pandas requer o SciPy porque chama gaussian_kde internamente.

Crie um gráfico de densidade com scipy.stats.gaussian_kde

gaussian_kde constrói a estimativa colocando um núcleo — uma pequena protuberância com a forma de uma gaussiana — sobre cada observação e calculando a média dessas protuberâncias. O resultado é uma função de densidade invocável: passe-lhe uma grelha de posições de x e ela devolve a densidade estimada em cada posição, que é exatamente a linha que o Matplotlib desenha.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

Primeiro, ajuste a estimativa à amostra e avalie-a numa grelha:

import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data)
x = np.linspace(-2, 10, 300)
y = density(x)

print(f"Peak estimated density: {y.max():.4f} at x = {x[np.argmax(y)]:.2f}")
Peak estimated density: 0.2635 at x = 3.14

O programa ajusta gaussian_kde aos 18 valores da amostra e avalia a estimativa em 300 posições igualmente espaçadas entre -2 e 10; a estimativa atinge o pico perto de x = 3, onde a maioria das observações se concentra. Escolha a grelha a partir do intervalo dos seus próprios dados, por exemplo de um pouco abaixo do mínimo até um pouco acima do máximo; a janela de -2 a 10 aqui serve apenas esta amostra.

O script de desenho abaixo fixa o backend não interativo Agg do Matplotlib para que seja executado da mesma forma com ou sem um ecrã ligado, e silencia o registo da biblioteca para que uma mensagem de tipos de letra na primeira execução não apareça na saída de erro padrão. Execute o seguinte para ver qual é o backend selecionado:

import logging
import matplotlib
matplotlib.use("Agg")
logging.getLogger("matplotlib").setLevel(logging.ERROR)
import matplotlib.pyplot as plt

print("Backend:", matplotlib.get_backend())
Backend: Agg

Agora desenhe a curva avaliada com o script completo:

import logging
import matplotlib
matplotlib.use("Agg")
logging.getLogger("matplotlib").setLevel(logging.ERROR)
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data)
x = np.linspace(-2, 10, 300)
y = density(x)

plt.plot(x, y)
plt.title("Density Plot of the data")
plt.xlabel("Value")
plt.ylabel("Estimated density")

print("Plotted", len(plt.gca().get_lines()), "density line")
Plotted 1 density line

plt.plot() desenha a densidade estimada como uma única linha nos eixos atuais, e plt.gca().get_lines() reporta a linha que foi adicionada. Numa sessão interativa, pode omitir a chamada matplotlib.use("Agg") e adicionar plt.show() após as chamadas de desenho para abrir a janela da figura.

Curva de densidade estimada produzida por scipy.stats gaussian_kde para os dados da amostra

A curva desenhada é a densidade estimada: mais alta onde as observações estão concentradas, mais baixa onde são escassas. Por se tratar de uma estimativa, encare a sua forma como uma visão desta amostra e não como uma medição da distribuição verdadeira.

Ajuste a largura de banda do KDE

A largura de banda controla quanto a estimativa é suavizada. Uma largura de banda grande alarga cada núcleo e produz uma curva mais plana e suave que pode ocultar estrutura real; uma largura de banda pequena estreita os núcleos, pelo que a curva segue a amostra de perto e pode apresentar ondulações que são apenas ruído neste conjunto de dados em particular. Por predefinição, gaussian_kde escolhe a largura de banda com a regra de Scott; também pode passar "scott" ou "silverman" explicitamente, ou fornecer um escalar através do argumento bw_method, que define a largura do núcleo em relação à dispersão dos dados. O mesmo ajuste fica disponível mais tarde através do método público set_bandwidth(), pelo que não é necessário chamar auxiliares privados.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import gaussian_kde

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
density = gaussian_kde(data, bw_method=0.25)
x = np.linspace(-2, 10, 300)
y = density(x)

plt.plot(x, y)
plt.title("Density Plot with a smaller bandwidth")
plt.xlabel("Value")
plt.ylabel("Estimated density")

print(f"Peak estimated density: {y.max():.4f}")
Peak estimated density: 0.3913

Com bw_method=0.25, cada núcleo é mais estreito do que na predefinição, pelo que a curva sobe mais no seu pico e acompanha mais de perto a concentração da amostra. Aumente o valor para suavizar mais, reduza-o para suavizar menos e compare as formas, em vez de tratar qualquer largura de banda específica como universalmente correta.

Curva de densidade estimada calculada com uma largura de banda menor, mostrando um pico mais alto

Crie um gráfico de densidade com o Seaborn

seaborn encapsula a mesma ideia numa única chamada: kdeplot() estima a densidade internamente e desenha-a num eixo do Matplotlib. Passe a amostra através de x e ajuste a escala da largura de banda escolhida automaticamente com bw_adjust — valores acima de 1 suavizam mais, valores abaixo de 1 suavizam menos. A função mais antiga distplot() está descontinuada; utilize kdeplot() para a curva isolada ou displot(kind="kde") para a variante ao nível da figura.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import seaborn as sns

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
ax = sns.kdeplot(x=data, bw_adjust=0.5)
ax.set_title("Density Plot of the data")
ax.set_xlabel("Value")

print("kdeplot drew", len(ax.get_lines()), "density line")
kdeplot drew 1 density line

kdeplot devolve o Axes do Matplotlib em que desenhou, pelo que os títulos e rótulos são aplicados com os métodos habituais dos eixos; adicione plt.show() para apresentar a janela da figura de forma interativa.

Curva de densidade desenhada por seaborn kdeplot para os dados da amostra

Crie um gráfico de densidade com o pandas

Quando a amostra já está num DataFrame ou numa Series, pandas.DataFrame.plot pode produzir a curva diretamente: passe kind="density" (o alias kind="kde" também funciona). O pandas calcula a estimativa com gaussian_kde, pelo que o SciPy tem de estar instalado para este tipo de gráfico.

O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd

data = [2, 3, 3, 4, 2, 1, 5, 6, 4, 3, 3, 3, 6, 4, 5, 4, 3, 2]
df = pd.DataFrame(data, columns=["value"])
ax = df.plot(kind="density")
ax.set_title("Density Plot of the data")
ax.set_xlabel("Value")

print("pandas labeled the axis:", ax.get_ylabel())
pandas labeled the axis: Density

Chamar o método num DataFrame desenha uma curva por coluna e rotula automaticamente o eixo y como Density, um rótulo que permanece em inglês; chame-o sobre uma única Series para uma variável nomeada. Tal como na abordagem com SciPy, a suavidade segue a largura de banda do estimador, que o pandas deriva dos dados.