¿Cómo puedo trazar un histograma de modo que las alturas de las barras sumen 1 en matplotlib?

85

Me gustaría trazar un histograma normalizado a partir de un vector usando matplotlib. Intenté lo siguiente:

plt.hist(myarray, normed=True)

tanto como:

plt.hist(myarray, normed=1)

pero ninguna de las opciones produce un eje y de [0, 1] tal que las alturas de las barras del histograma sumen 1. Me gustaría producir un histograma de este tipo, ¿cómo puedo hacerlo?

nbro
fuente
5
Sé que esto es antiguo, pero para referencia futura y para cualquiera que visite esta página, ¡este tipo de extensión del eje se llama eje de "densidad de probabilidad"!
ChristineB

Respuestas:

48

Sería más útil si presentara un ejemplo de trabajo más completo (o en este caso no funcional).

Intenté lo siguiente:

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()

De hecho, esto producirá un histograma de gráfico de barras con un eje y que va desde [0,1].

Además, según la histdocumentación (es decir, ax.hist?de ipython), creo que la suma también está bien:

*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))

Dando una oportunidad a esto después de los comandos anteriores:

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

Obtengo un valor de retorno de 1.0lo esperado. Recuerde que eso normed=Trueno significa que la suma del valor en cada barra será la unidad, sino que la integral sobre las barras es la unidad. En mi caso np.sum(n)devolvió aprox 7.2767.

dtlussier
fuente
3
Sí, ese es un gráfico de densidad de probabilidad, creo que quiere un gráfico de masa de probabilidad.
NoName
200

Si desea que la suma de todas las barras sea igual a la unidad, pese cada contenedor por el número total de valores:

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

Espero que ayude, aunque el hilo es bastante antiguo ...

Nota para Python 2.x: agregue conversión a float()para uno de los operadores de la división, ya que de lo contrario terminaría con ceros debido a la división de enteros

Carsten König
fuente
8
Gran respuesta. Tenga en cuenta que si myarray es una pitón array_likeen lugar de una matriz numpy tendrá que fundido len(myarray)a float.
cmh
3
Además, si myarray es multidimensional y solo está usando una dimensión, como myarray [0 ,:], puede intercambiar len (myarray) con np.size (myarray [0 ,:]) y eso funcionará mismo camino. (De lo contrario, dice que el objeto no se puede llamar.)
ChristineB
22

Sé que esta respuesta es demasiado tarde considerando que la pregunta tiene fecha de 2010, pero me encontré con esta pregunta porque yo mismo enfrentaba un problema similar. Como ya se indicó en la respuesta, normed = True significa que el área total debajo del histograma es igual a 1 pero la suma de las alturas no es igual a 1. Sin embargo, quería, por conveniencia de la interpretación física de un histograma, hacer uno con suma de alturas igual a 1.

Encontré una pista en la siguiente pregunta: Python: histograma con un área normalizada a algo diferente a 1

Pero no pude encontrar una manera de hacer que las barras imiten la característica histtype = "step" hist (). Esto me desvió a: Matplotlib - Histograma escalonado con datos ya agrupados

Si la comunidad lo considera aceptable, me gustaría proponer una solución que sintetice las ideas de los dos mensajes anteriores.

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.

Esto ha funcionado maravillosamente para mí, aunque en algunos casos he notado que la "barra" más a la izquierda o la "barra" más a la derecha del histograma no se cierra al tocar el punto más bajo del eje Y. En tal caso, la adición de un elemento 0 al principio o al final de y logró el resultado necesario.

Solo pensé en compartir mi experiencia. Gracias.

Asesino
fuente
Creo que también necesitas normed = True en plt.hist. También en Python 3 tienes que usar list (zip (...)).
Sebastian Schmitz
11

Aquí hay otra solución simple usando el 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)

De hecho, puede verificar que el total sume hasta 1 con:

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