El caso más sencillo es cuando la variable es discreta. En este caso, el ancho de los bins del histograma no juega ningún rol*, ya que la variable solo puede tomar valores en lugares discretos. Por ejemplo, una variable poissoniana solo toma valores enteros. Al graficar el histograma, solo importa la altura de los bins mientras que el ancho graficado se puede modificar a gusto del consumidor.
*A menos que el ancho del bin abarque más de 1 de los valores posibles. Por ejemplo, bins de ancho 2 para una poissoniana. En ese caso, el ancho graficado tiene que ser tal que represente los números que abarca.
import numpy as np
from scipy.stats import poisson
from matplotlib import pyplot as plt
% matplotlib inline
np.random.seed(42) # Para obtener la misma secuencia de números aleatorios
X = poisson(mu=4.5) # Defino una variable aleatoria con distribución de Poisson de parámetro 4.5
Xi = X.rvs(size=1000) # Genero 1000 números de la variable aleatoria
Y, bins = np.histogram(Xi, bins=np.arange(0, Xi.max())) # Genero el histograma
f, ax = plt.subplots(ncols=3, figsize=(16,4), sharey=True)
f.subplots_adjust(wspace=0.05)
ax[0].bar(bins[:-1], Y, width=1, ec='k') # Grafico el histograma (gráfico de barras)
ax[1].bar(bins[:-1], Y, width=0.5, ec='k')
ax[2].stem(bins[:-1], Y, basefmt=' ') # Stem-plot
for k, a in enumerate(ax, 1):
a.set_title(k, loc='left', fontsize=20)
En este caso, normalizar es simplemente dividir por la cantidad total de datos, ya que queremos que la suma de (las alturas de) todos los bins (que representan a la probabilidad de que la variable $X$ tome ese valor) sea 1.
Y_normed = Y / np.sum(Y)
f, ax = plt.subplots(ncols=3, figsize=(16,4), sharey=True)
f.subplots_adjust(wspace=0.05)
ax[0].bar(bins[:-1], Y_normed, width=1, ec='k')
ax[1].bar(bins[:-1], Y_normed, width=0.5, ec='k')
ax[2].stem(bins[:-1], Y_normed, basefmt=' ')
for k, a in enumerate(ax, 1):
a.scatter(bins[:-1], X.pmf(bins[:-1]), color='red', zorder=3)
a.set_title(k, loc='left', fontsize=20)
Los puntos rojos corresponden a la distribución de probabilidad teórica de Poisson con el parámetro dado.
En el caso de una variable continua, queremos que el histograma represente la densidad de probabilidad de dicha variable. En este caso sí es importante considerar el ancho del histograma, ya que para normalizar vamos a querer calcular la integral, es decir, $\text{area} \times \text{altura}$ de cada bin.
Sea $Y_i$ la variable aleatoria correspondiente a la cantidad de datos en el bin $i$ de ancho $w_i$ de un histograma. El histograma representa a una densidad de probabilidad (para alguna variable aleatoria $X$), y para que esté normalizada, esta tiene que integrar a 1. Entonces, queremos normalizar a los $Y_i \mapsto y_i$ tal que $$ 1 = \int f_X(t) \, dt \approx \sum_i y_i \, w_i $$ donde aproximamos la integral por la suma de las areas de los bins.
Ahora, una forma de normalizar sería dividir a todos los $Y_i$ por $\sum_j w_j Y_j$:
$$ \sum_i w_i y_i = \sum_i w_i \frac{Y_i}{\sum_j w_j Y_j} = \frac{1}{\sum_j w_j Y_j} \sum_i w_i Y_i = 1 $$La integral da 1 como queríamos, pero esta normalización no representa la densidad de probabilidad cuando los bins son de distinto ancho.
En cambio, si normalizamos a cada bin por su ancho y por $N$, la cantidad total de datos en el histograma, tenemos:
$$ Y_i \mapsto \frac{Y_i}{w_i \sum_j Y_j} = \frac{Y_i}{w_i N} = y_i $$Esta normalización también integra a 1:
$$ \sum_i w_i y_i = \sum_i w_i \frac{Y_i}{w_i \sum_j Y_j} = \frac{\sum_i Y_i}{\sum_j Y_j} = 1 $$y representa correctamente a la densidad de probabilidad cuando los anchos son distintos (ver figuras abajo).
Nota: Uno podría usar directamente las funciones de numpy que normalizan el histograma, pero de todas maneras necesitan normalizar los errores, que por ahora no lo hacen. Para que la función numpy.histogram normalize correctamente cuando el ancho de los bins es variable hay que usar density=True, y no normed=True (ver el help de la función, donde aclara que normed se encuentra deprecada).
La normalización de los errores es la misma que para la variable, ya sea que consideren que los erroes del histograma son poissonianos o binomiales:
$$ Y_i \pm \sigma_{Y_i} \mapsto \frac{Y_i}{w_i \sum_j Y_j} \pm \frac{\sigma_{Y_i}}{w_i \sum_j Y_j} $$# Vuelvo a importar los paquetes por si no corrieron las celdas anteriores
import numpy as np
from matplotlib import pyplot as plt
% matplotlib inline
X = 2 * np.random.rand(100000) - 1 # Uniforme en [-1, 1]
# Genero bins con anchos distintos
bins = np.linspace(-1, 1, 30)
bins = np.sign(bins) * bins**2
w = np.diff(bins) # Ancho de los bins
Y = np.histogram(X, bins=bins)[0] # Histograma sin normalizar
y_mal = Y / (np.sum(w*Y)) # Mal normalizado a mano
y_bien = Y / (np.sum(Y) * w) # Bien normalizado a mano
# Usando directamente una opción de la función histograma
y_normed = np.histogram(X, bins=bins, normed=True)[0] # Histograma mal normalizado (cuando los bins no son constantes)
y_density = np.histogram(X, bins=bins, density=True)[0] # Histograma bien normalizado
f, axes = plt.subplots(nrows=2, ncols=3, figsize=(16,6))
f.subplots_adjust(hspace=0.5)
axes[1,0].axis('off')
axes = axes.flatten()
axes = np.delete(axes, 3)
for ax, x, title in zip(axes,
[Y, y_mal, y_bien, y_normed, y_density],
['Sin normalizar', 'Mal normalizado a mano','Bien normalizado a mano', 'histogram(..., normed=True)', 'histogram(..., density=True)']):
ax.bar(bins[:-1], x, width=w, align='edge', ec='k')
ax.set_title(title)