Como posso plotar um histograma de forma que as alturas das barras somam 1 em matplotlib?

86

Eu gostaria de traçar um histograma normalizado de um vetor usando matplotlib. Tentei o seguinte:

plt.hist(myarray, normed=True)

assim como:

plt.hist(myarray, normed=1)

mas nenhuma das opções produz um eixo y de [0, 1] de forma que as alturas das barras do histograma somam 1. Eu gostaria de produzir esse histograma - como posso fazer isso?

Nbro
fonte
6
Eu sei que isso é antigo, mas para referência futura e qualquer pessoa que visitar esta página, este tipo de propagação de eixo é chamado de eixo de "densidade de probabilidade"!
ChristineB

Respostas:

50

Seria mais útil se você apresentasse um exemplo funcional (ou, neste caso, não funcional) mais completo.

Tentei o seguinte:

import numpy as np
import matplotlib.pyplot as plt

x = np.random.randn(1000)

fig = plt.figure()
ax = fig.add_subplot(111)
n, bins, rectangles = ax.hist(x, 50, density=True)
fig.canvas.draw()
plt.show()

Isso realmente produzirá um histograma de gráfico de barras com um eixo y que vai de [0,1].

Além disso, de acordo com a histdocumentação (ou seja, ax.hist?de ipython), acho que a soma também é adequada:

*normed*:
If *True*, the first element of the return tuple will
be the counts normalized to form a probability density, i.e.,
``n/(len(x)*dbin)``.  In a probability density, the integral of
the histogram should be 1; you can verify that with a
trapezoidal integration of the probability density function::

    pdf, bins, patches = ax.hist(...)
    print np.sum(pdf * np.diff(bins))

Experimente depois dos comandos acima:

np.sum(n * np.diff(bins))

Recebo um valor de retorno 1.0conforme o esperado. Lembre-se de que normed=Trueisso não significa que a soma do valor em cada barra será a unidade, mas, em vez da integral sobre as barras, a unidade. No meu caso np.sum(n)retornou aprox 7.2767.

dtlussier
fonte
3
Sim, é um gráfico de densidade de probabilidade, acho que ele quer um gráfico de massa de probabilidade.
NoName
201

Se você quiser que a soma de todas as barras seja igual à unidade, pondere cada caixa pelo número total de valores:

weights = np.ones_like(myarray) / len(myarray)
plt.hist(myarray, weights=weights)

Espero que ajude, embora o tópico seja bastante antigo ...

Nota para Python 2.x: adicione fundição a float()para um dos operadores da divisão, caso contrário, você terminaria com zeros devido à divisão inteira

Carsten König
fonte
8
Ótima resposta. Note que se myarray é um python array_likeem vez de uma matriz numpy você precisará elenco len(myarray)para float.
cmh
3
Além disso, se myarray for multidimensional e você estiver usando apenas uma dimensão, como myarray [0 ,:], então você pode trocar len (myarray) por np.size (myarray [0 ,:]) e isso funcionará da mesma maneira. (Caso contrário, diz que o objeto não pode ser chamado.)
ChristineB
22

Sei que esta resposta é tarde demais, considerando que a pergunta é datada de 2010, mas me deparei com essa pergunta porque eu mesmo estava enfrentando um problema semelhante. Como já foi afirmado na resposta, normed = True significa que a área total sob o histograma é igual a 1, mas a soma das alturas não é igual a 1. No entanto, eu queria, para conveniência da interpretação física de um histograma, fazer um com soma de alturas igual a 1.

Encontrei uma dica na seguinte pergunta - Python: Histograma com área normalizada para algo diferente de 1

Mas não consegui encontrar uma maneira de fazer as barras imitarem o recurso histtype = "step" hist (). Isso me desviou para: Matplotlib - histograma em etapas com dados já armazenados

Se a comunidade considerar aceitável, gostaria de apresentar uma solução que sintetize as ideias de ambas as postagens acima.

import matplotlib.pyplot as plt

# Let X be the array whose histogram needs to be plotted.
nx, xbins, ptchs = plt.hist(X, bins=20)
plt.clf() # Get rid of this histogram since not the one we want.

nx_frac = nx/float(len(nx)) # Each bin divided by total number of objects.
width = xbins[1] - xbins[0] # Width of each bin.
x = np.ravel(zip(xbins[:-1], xbins[:-1]+width))
y = np.ravel(zip(nx_frac,nx_frac))

plt.plot(x,y,linestyle="dashed",label="MyLabel")
#... Further formatting.

Isso funcionou maravilhosamente para mim, embora em alguns casos eu tenha notado que a "barra" mais à esquerda ou a "barra" mais à direita do histograma não fecha ao tocar o ponto mais baixo do eixo Y. Nesse caso, adicionar um elemento 0 no início ou no final de y alcançou o resultado necessário.

Só pensei em compartilhar minha experiência. Obrigado.

Assassino
fonte
eu acho que você precisa normed = True também em plt.hist. Também no Python 3 você deve usar list (zip (...)).
Sebastian Schmitz
11

Aqui está outra solução simples usando o np.histogram()método.

myarray = np.random.random(100)
results, edges = np.histogram(myarray, normed=True)
binWidth = edges[1] - edges[0]
plt.bar(edges[:-1], results*binWidth, binWidth)

Você pode, de fato, verificar se o total é de até 1 com:

> print sum(results*binWidth)
1.0
Yuri Brovman
fonte