Poisson distributions turn up a lot in high-energy astronomy. If you take the photons from a source and sort them into phase bins, the number in each phase bin will be Poisson distributed, with an expected value corresponding to the source's emission. This is something we often do, for example to look for evidence that the source's emission varies: in addition to various statistical tests, we plot a histogram of photon arrival phases. Part of plotting such a histogram is plotting error bars on each bin, to give the reader an indication of the likelihood that such variations are real.
The error bars on each bin are supposed to indicate the uncertainty on the value in that bin. Now, a Poisson random variable with expected value λ has standard deviation √λ, but of course we don't actually know λ. We can estimate λ as the number n of observed photons (in fact this is an unbiased estimate and about as good as we can hope for from a single measurement). It turns out that if n is decently large, √n is a decent estimate for √λ. It's not unbiased, and it's definitely wrong if n is small, but in that case the distribution isn't much like a normal distribution anyway, and so you enter realms of messy statistics. For histograms we try to keep the number n in each bin pretty high anyway. So √n is a decent value for the error bar in each bin.
import scipy.stats
from IPython.zmq.pylab import backend_inline
cfg = backend_inline.InlineBackendConfig.instance()
cfg.figure_format = 'svg' # 'png' to switch back
phases = (np.random.vonmises(np.pi, 1, size=1000)/(2*np.pi)) % 1
ns, bin_edges = np.histogram(phases, range=(0,1), bins=10)
plt.plot(bin_edges, np.concatenate((ns,[ns[0]])), drawstyle='steps-post')
plt.errorbar((bin_edges[:-1]+bin_edges[1:])/2., ns, np.sqrt(ns), linestyle="none")
<Container object of 3 artists>
We can even check the reasonableness of the error estimate. If we pick a λ and generate Poisson samples ni, then for each ni we can compute zi=(λ−ni)/√ni, which should be distributed as a normal distribution with mean zero and unit standard deviation. So let's test that.
lam = 100.
ni = np.random.poisson(lam, size=1000000)
zi = (lam-ni)/np.sqrt(ni)
ui = scipy.stats.norm.cdf(zi)
ui.sort()
plt.plot(np.arange(len(ui))/float(len(ui)), ui)
plt.plot((0,1),(0,1))
[<matplotlib.lines.Line2D at 0x1ac6dd10>]
Not too bad. It's going to flunk a Kolmogorov-Smirnov test because it's discrete, and we can't really do much about that without going to higher n where we expect the approximation to be better anyway. Well, I guess we could choose the λ independently for each trial, but that's getting really very messy. And it's not what I'm really interested in anyway: the results so far are straight from the textbook.
What interests me right now is a more tricky variation on the problem. With the Fermi space telescope, the tool gtsrcprob gives us a probability for each photon that it's actually from the source (rather than from some nearby contaminating source). So what we want to plot in a histogram is not the number of photons, but the total probability, the expected number of photons. What's the uncertainty on that?
It's not entirely clear how best to pose the problem mathematically. But let's try this: there is some unknown distribution of photon weights. To find the expected number of photons in each bin, we draw a number of photons from a Poisson distribution, then we draw the weight from our (unknown) distribution.
Taking this as our mathematical model, what's the standard deviation of the result? Well, first let's get the mean: if the Poisson distribution has mean λ, and the unknown distribution has mean μ, the expected value will be E(T)=∞∑n=0P(n|λ)nμ=λμ.
This doesn't quite answer our question, since what we're looking for is a way to estimate the standard deviation given a list of n weights wi drawn from a single sample. But we can take a guess based on the Poisson approach. We can estimate λ as n and E(U2) as the root mean squared of the wi. How does this stand up?
lam = 1000.
ni = np.random.poisson(lam, size=10000)
mu = np.exp(0.5)
wi = [np.exp(np.random.normal(size=n)) for n in ni]
ti = np.array([np.sum(w) for w in wi])
np.mean([np.mean(w) for w in wi])/mu
0.99987120363348647
vi = np.array([np.sum(w**2)-np.mean(w)**2 for w in wi])
si = np.sqrt(vi)
zi = (lam*mu - ti)/si
ui = scipy.stats.norm.cdf(zi)
plt.hist(zi, bins=np.ceil(np.sqrt(len(zi))), normed=True)
xs = np.linspace(np.amin(zi),np.amax(zi),1000)
plt.plot(xs,scipy.stats.norm.pdf(xs))
[<matplotlib.lines.Line2D at 0x1aca3c10>]
ui.sort()
plt.plot(np.arange(len(ui))/float(len(ui)), ui)
plt.plot((0,1),(0,1))
[<matplotlib.lines.Line2D at 0x11aebd90>]
scipy.stats.kstest(ui, lambda x:x)
(0.0230439056929167, 4.8824192933359981e-05)
The second of these values is the false positive probability, that is, the chance that you would obtain such a deviation from uniformity drawing from a genuinely uniform distribution.
In short, if you observe n weights wi, the (1σ) uncertainty you should quote is √n∑i=1w2i−(ˉwi)2.
A little experimentation shows that for small numbers of photons, the approximation isn't very good. Worse, what matters is the number of photons with weight near the maximum weight. All those small-weight photons don't help improve the approximation substantially.