Akshay Agrawal commited on
Commit
b0625d6
·
unverified ·
2 Parent(s): c5222b9 f433105

Merge pull request #75 from marimo-team/haleshot/14_binomial

Browse files
probability/14_binomial_distribution.py ADDED
@@ -0,0 +1,545 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "marimo",
5
+ # "matplotlib==3.10.0",
6
+ # "numpy==2.2.4",
7
+ # "scipy==1.15.2",
8
+ # "altair==5.2.0",
9
+ # "wigglystuff==0.1.10",
10
+ # "pandas==2.2.3",
11
+ # ]
12
+ # ///
13
+
14
+ import marimo
15
+
16
+ __generated_with = "0.11.24"
17
+ app = marimo.App(width="medium", app_title="Binomial Distribution")
18
+
19
+
20
+ @app.cell(hide_code=True)
21
+ def _(mo):
22
+ mo.md(
23
+ r"""
24
+ # Binomial Distribution
25
+
26
+ _This notebook is a computational companion to ["Probability for Computer Scientists"](https://chrispiech.github.io/probabilityForComputerScientists/en/part2/binomial/), by Stanford professor Chris Piech._
27
+
28
+ In this section, we will discuss the binomial distribution. To start, imagine the following example:
29
+
30
+ Consider $n$ independent trials of an experiment where each trial is a "success" with probability $p$. Let $X$ be the number of successes in $n$ trials.
31
+
32
+ This situation is truly common in the natural world, and as such, there has been a lot of research into such phenomena. Random variables like $X$ are called **binomial random variables**. If you can identify that a process fits this description, you can inherit many already proved properties such as the PMF formula, expectation, and variance!
33
+ """
34
+ )
35
+ return
36
+
37
+
38
+ @app.cell(hide_code=True)
39
+ def _(mo):
40
+ mo.md(
41
+ r"""
42
+ ## Binomial Random Variable Definition
43
+
44
+ $X \sim \text{Bin}(n, p)$ represents a binomial random variable where:
45
+
46
+ - $X$ is our random variable (number of successes)
47
+ - $\text{Bin}$ indicates it follows a binomial distribution
48
+ - $n$ is the number of trials
49
+ - $p$ is the probability of success in each trial
50
+
51
+ ```
52
+ X ~ Bin(n, p)
53
+ ↑ ↑ ↑
54
+ | | +-- Probability of
55
+ | | success on each
56
+ | | trial
57
+ | +-- Number of trials
58
+ |
59
+ Our random variable
60
+ is distributed
61
+ as a Binomial
62
+ ```
63
+
64
+ Here are a few examples of binomial random variables:
65
+
66
+ - Number of heads in $n$ coin flips
67
+ - Number of 1's in randomly generated length $n$ bit string
68
+ - Number of disk drives crashed in 1000 computer cluster, assuming disks crash independently
69
+ """
70
+ )
71
+ return
72
+
73
+
74
+ @app.cell(hide_code=True)
75
+ def _(mo):
76
+ mo.md(
77
+ r"""
78
+ ## Properties of Binomial Distribution
79
+
80
+ | Property | Formula |
81
+ |----------|---------|
82
+ | Notation | $X \sim \text{Bin}(n, p)$ |
83
+ | Description | Number of "successes" in $n$ identical, independent experiments each with probability of success $p$ |
84
+ | Parameters | $n \in \{0, 1, \dots\}$, the number of experiments<br>$p \in [0, 1]$, the probability that a single experiment gives a "success" |
85
+ | Support | $x \in \{0, 1, \dots, n\}$ |
86
+ | PMF equation | $P(X=x) = {n \choose x}p^x(1-p)^{n-x}$ |
87
+ | Expectation | $E[X] = n \cdot p$ |
88
+ | Variance | $\text{Var}(X) = n \cdot p \cdot (1-p)$ |
89
+
90
+ Let's explore how the binomial distribution changes with different parameters.
91
+ """
92
+ )
93
+ return
94
+
95
+
96
+ @app.cell(hide_code=True)
97
+ def _(TangleSlider, mo):
98
+ # Interactive elements using TangleSlider
99
+ n_slider = mo.ui.anywidget(TangleSlider(
100
+ amount=10,
101
+ min_value=1,
102
+ max_value=30,
103
+ step=1,
104
+ digits=0,
105
+ suffix=" trials"
106
+ ))
107
+
108
+ p_slider = mo.ui.anywidget(TangleSlider(
109
+ amount=0.5,
110
+ min_value=0.01,
111
+ max_value=0.99,
112
+ step=0.01,
113
+ digits=2,
114
+ suffix=" probability"
115
+ ))
116
+
117
+ # Grid layout for the interactive controls
118
+ controls = mo.vstack([
119
+ mo.md("### Adjust Parameters to See How Binomial Distribution Changes"),
120
+ mo.hstack([
121
+ mo.md("**Number of trials (n):** "),
122
+ n_slider
123
+ ], justify="start"),
124
+ mo.hstack([
125
+ mo.md("**Probability of success (p):** "),
126
+ p_slider
127
+ ], justify="start"),
128
+ ])
129
+ return controls, n_slider, p_slider
130
+
131
+
132
+ @app.cell(hide_code=True)
133
+ def _(controls):
134
+ controls
135
+ return
136
+
137
+
138
+ @app.cell(hide_code=True)
139
+ def _(n_slider, np, p_slider, plt, stats):
140
+ # Parameters from sliders
141
+ _n = int(n_slider.amount)
142
+ _p = p_slider.amount
143
+
144
+ # Calculate PMF
145
+ _x = np.arange(0, _n + 1)
146
+ _pmf = stats.binom.pmf(_x, _n, _p)
147
+
148
+ # Relevant stats
149
+ _mean = _n * _p
150
+ _variance = _n * _p * (1 - _p)
151
+ _std_dev = np.sqrt(_variance)
152
+
153
+ _fig, _ax = plt.subplots(figsize=(10, 6))
154
+
155
+ # Plot PMF as bars
156
+ _ax.bar(_x, _pmf, color='royalblue', alpha=0.7, label=f'PMF: P(X=k)')
157
+
158
+ # Add a line
159
+ _ax.plot(_x, _pmf, 'ro-', alpha=0.6, label='PMF line')
160
+
161
+ # Add vertical lines
162
+ _ax.axvline(x=_mean, color='green', linestyle='--', linewidth=2,
163
+ label=f'Mean: {_mean:.2f}')
164
+
165
+ # Shade the stdev region
166
+ _ax.axvspan(_mean - _std_dev, _mean + _std_dev, alpha=0.2, color='green',
167
+ label=f'±1 Std Dev: {_std_dev:.2f}')
168
+
169
+ # Add labels and title
170
+ _ax.set_xlabel('Number of Successes (k)')
171
+ _ax.set_ylabel('Probability: P(X=k)')
172
+ _ax.set_title(f'Binomial Distribution with n={_n}, p={_p:.2f}')
173
+
174
+ # Annotations
175
+ _ax.annotate(f'E[X] = {_mean:.2f}',
176
+ xy=(_mean, stats.binom.pmf(int(_mean), _n, _p)),
177
+ xytext=(_mean + 1, max(_pmf) * 0.8),
178
+ arrowprops=dict(facecolor='black', shrink=0.05, width=1))
179
+
180
+ _ax.annotate(f'Var(X) = {_variance:.2f}',
181
+ xy=(_mean, stats.binom.pmf(int(_mean), _n, _p) / 2),
182
+ xytext=(_mean + 1, max(_pmf) * 0.6),
183
+ arrowprops=dict(facecolor='black', shrink=0.05, width=1))
184
+
185
+ # Grid and legend
186
+ _ax.grid(alpha=0.3)
187
+ _ax.legend()
188
+
189
+ plt.tight_layout()
190
+ plt.gca()
191
+ return
192
+
193
+
194
+ @app.cell(hide_code=True)
195
+ def _(mo):
196
+ mo.md(
197
+ r"""
198
+ ## Relationship to Bernoulli Random Variables
199
+
200
+ One way to think of the binomial is as the sum of $n$ Bernoulli variables. Say that $Y_i$ is an indicator Bernoulli random variable which is 1 if experiment $i$ is a success. Then if $X$ is the total number of successes in $n$ experiments, $X \sim \text{Bin}(n, p)$:
201
+
202
+ $$X = \sum_{i=1}^n Y_i$$
203
+
204
+ Recall that the outcome of $Y_i$ will be 1 or 0, so one way to think of $X$ is as the sum of those 1s and 0s.
205
+ """
206
+ )
207
+ return
208
+
209
+
210
+ @app.cell(hide_code=True)
211
+ def _(mo):
212
+ mo.md(
213
+ r"""
214
+ ## Binomial Probability Mass Function (PMF)
215
+
216
+ The most important property to know about a binomial is its [Probability Mass Function](https://marimo.app/https://github.com/marimo-team/learn/blob/main/probability/10_probability_mass_function.py):
217
+
218
+ $$P(X=k) = {n \choose k}p^k(1-p)^{n-k}$$
219
+
220
+ ```
221
+ P(X = k) = (n) p^k(1-p)^(n-k)
222
+ ↑ (k)
223
+ | ↑
224
+ | +-- Binomial coefficient:
225
+ | number of ways to choose
226
+ | k successes from n trials
227
+ |
228
+ Probability that our
229
+ variable takes on the
230
+ value k
231
+ ```
232
+
233
+ Recall, we derived this formula in Part 1. There is a complete example on the probability of $k$ heads in $n$ coin flips, where each flip is heads with probability $p$.
234
+
235
+ To briefly review, if you think of each experiment as being distinct, then there are ${n \choose k}$ ways of permuting $k$ successes from $n$ experiments. For any of the mutually exclusive permutations, the probability of that permutation is $p^k \cdot (1-p)^{n-k}$.
236
+
237
+ The name binomial comes from the term ${n \choose k}$ which is formally called the binomial coefficient.
238
+ """
239
+ )
240
+ return
241
+
242
+
243
+ @app.cell(hide_code=True)
244
+ def _(mo):
245
+ mo.md(
246
+ r"""
247
+ ## Expectation of Binomial
248
+
249
+ There is an easy way to calculate the expectation of a binomial and a hard way. The easy way is to leverage the fact that a binomial is the sum of Bernoulli indicator random variables $X = \sum_{i=1}^{n} Y_i$ where $Y_i$ is an indicator of whether the $i$-th experiment was a success: $Y_i \sim \text{Bernoulli}(p)$.
250
+
251
+ Since the [expectation of the sum](http://marimo.app/https://github.com/marimo-team/learn/blob/main/probability/11_expectation.py) of random variables is the sum of expectations, we can add the expectation, $E[Y_i] = p$, of each of the Bernoulli's:
252
+
253
+ \begin{align}
254
+ E[X] &= E\Big[\sum_{i=1}^{n} Y_i\Big] && \text{Since }X = \sum_{i=1}^{n} Y_i \\
255
+ &= \sum_{i=1}^{n}E[ Y_i] && \text{Expectation of sum} \\
256
+ &= \sum_{i=1}^{n}p && \text{Expectation of Bernoulli} \\
257
+ &= n \cdot p && \text{Sum $n$ times}
258
+ \end{align}
259
+
260
+ The hard way is to use the definition of expectation:
261
+
262
+ \begin{align}
263
+ E[X] &= \sum_{i=0}^n i \cdot P(X = i) && \text{Def of expectation} \\
264
+ &= \sum_{i=0}^n i \cdot {n \choose i} p^i(1-p)^{n-i} && \text{Sub in PMF} \\
265
+ & \cdots && \text{Many steps later} \\
266
+ &= n \cdot p
267
+ \end{align}
268
+ """
269
+ )
270
+ return
271
+
272
+
273
+ @app.cell(hide_code=True)
274
+ def _(mo):
275
+ mo.md(
276
+ r"""
277
+ ## Binomial Distribution in Python
278
+
279
+ As you might expect, you can use binomial distributions in code. The standardized library for binomials is `scipy.stats.binom`.
280
+
281
+ One of the most helpful methods that this package provides is a way to calculate the PMF. For example, say $n=5$, $p=0.6$ and you want to find $P(X=2)$, you could use the following code:
282
+ """
283
+ )
284
+ return
285
+
286
+
287
+ @app.cell
288
+ def _(stats):
289
+ # define variables for x, n, and p
290
+ _n = 5 # Integer value for n
291
+ _p = 0.6
292
+ _x = 2
293
+
294
+ # use scipy to compute the pmf
295
+ p_x = stats.binom.pmf(_x, _n, _p)
296
+
297
+ # use the probability for future work
298
+ print(f'P(X = {_x}) = {p_x:.4f}')
299
+ return (p_x,)
300
+
301
+
302
+ @app.cell(hide_code=True)
303
+ def _(mo):
304
+ mo.md(r"""Another particularly helpful function is the ability to generate a random sample from a binomial. For example, say $X$ represents the number of requests to a website. We can draw 100 samples from this distribution using the following code:""")
305
+ return
306
+
307
+
308
+ @app.cell
309
+ def _(n, p, stats):
310
+ n_int = int(n)
311
+
312
+ # samples from the binomial distribution
313
+ samples = stats.binom.rvs(n_int, p, size=100)
314
+
315
+ # Print the samples
316
+ print(samples)
317
+ return n_int, samples
318
+
319
+
320
+ @app.cell(hide_code=True)
321
+ def _(n_int, np, p, plt, samples, stats):
322
+ # Plot histogram of samples
323
+ plt.figure(figsize=(10, 5))
324
+ plt.hist(samples, bins=np.arange(-0.5, n_int+1.5, 1), alpha=0.7, color='royalblue',
325
+ edgecolor='black', density=True)
326
+
327
+ # Overlay the PMF
328
+ x_values = np.arange(0, n_int+1)
329
+ pmf_values = stats.binom.pmf(x_values, n_int, p)
330
+ plt.plot(x_values, pmf_values, 'ro-', ms=8, label='Theoretical PMF')
331
+
332
+ # Add labels and title
333
+ plt.xlabel('Number of Successes')
334
+ plt.ylabel('Relative Frequency / Probability')
335
+ plt.title(f'Histogram of 100 Samples from Bin({n_int}, {p})')
336
+ plt.legend()
337
+ plt.grid(alpha=0.3)
338
+
339
+ # Annotate
340
+ plt.annotate('Sample mean: %.2f' % np.mean(samples),
341
+ xy=(0.7, 0.9), xycoords='axes fraction',
342
+ bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.3))
343
+ plt.annotate('Theoretical mean: %.2f' % (n_int*p),
344
+ xy=(0.7, 0.8), xycoords='axes fraction',
345
+ bbox=dict(boxstyle='round,pad=0.5', fc='lightgreen', alpha=0.3))
346
+
347
+ plt.tight_layout()
348
+ plt.gca()
349
+ return pmf_values, x_values
350
+
351
+
352
+ @app.cell(hide_code=True)
353
+ def _(mo):
354
+ mo.md(
355
+ r"""
356
+ You might be wondering what a random sample is! A random sample is a randomly chosen assignment for our random variable. Above we have 100 such assignments. The probability that value $k$ is chosen is given by the PMF: $P(X=k)$.
357
+
358
+ There are also functions for getting the mean, the variance, and more. You can read the [scipy.stats.binom documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.binom.html), especially the list of methods.
359
+ """
360
+ )
361
+ return
362
+
363
+
364
+ @app.cell(hide_code=True)
365
+ def _(mo):
366
+ mo.md(
367
+ r"""
368
+ ## Interactive Exploration of Binomial vs. Negative Binomial
369
+
370
+ The standard binomial distribution is a special case of a broader family of distributions. One related distribution is the negative binomial, which can model count data with overdispersion (where the variance is larger than the mean).
371
+
372
+ Below, you can explore how the negative binomial distribution compares to a Poisson distribution (which can be seen as a limiting case of the binomial as $n$ gets large and $p$ gets small, with $np$ held constant).
373
+
374
+ Adjust the sliders to see how the parameters affect the distribution:
375
+
376
+ *Note: The interactive visualization in this section was inspired by work from [liquidcarbon on GitHub](https://github.com/liquidcarbon).*
377
+ """
378
+ )
379
+ return
380
+
381
+
382
+ @app.cell(hide_code=True)
383
+ def _(alpha_slider, chart, equation, mo, mu_slider):
384
+ mo.vstack(
385
+ [
386
+ mo.md(f"## Negative Binomial Distribution (Poisson + Overdispersion)\n{equation}"),
387
+ mo.hstack([mu_slider, alpha_slider], justify="start"),
388
+ chart,
389
+ ], justify='space-around'
390
+ ).center()
391
+ return
392
+
393
+
394
+ @app.cell(hide_code=True)
395
+ def _(mo):
396
+ mo.md(
397
+ r"""
398
+ ## 🤔 Test Your Understanding
399
+ Pick which of these statements about binomial distributions you think are correct:
400
+
401
+ /// details | The variance of a binomial distribution is always equal to its mean
402
+ ❌ Incorrect! The variance is $np(1-p)$ while the mean is $np$. They're only equal when $p=1$ (which is a degenerate case).
403
+ ///
404
+
405
+ /// details | If $X \sim \text{Bin}(n, p)$ and $Y \sim \text{Bin}(n, 1-p)$, then $X$ and $Y$ have the same variance
406
+ ✅ Correct! $\text{Var}(X) = np(1-p)$ and $\text{Var}(Y) = n(1-p)p$, which are the same.
407
+ ///
408
+
409
+ /// details | As the number of trials increases, the binomial distribution approaches a normal distribution
410
+ ✅ Correct! For large $n$, the binomial distribution can be approximated by a normal distribution with the same mean and variance.
411
+ ///
412
+
413
+ /// details | The PMF of a binomial distribution is symmetric when $p = 0.5$
414
+ ✅ Correct! When $p = 0.5$, the PMF is symmetric around $n/2$.
415
+ ///
416
+
417
+ /// details | The sum of two independent binomial random variables with the same $p$ is also a binomial random variable
418
+ ✅ Correct! If $X \sim \text{Bin}(n_1, p)$ and $Y \sim \text{Bin}(n_2, p)$ are independent, then $X + Y \sim \text{Bin}(n_1 + n_2, p)$.
419
+ ///
420
+
421
+ /// details | The maximum value of the PMF for $\text{Bin}(n,p)$ always occurs at $k = np$
422
+ ❌ Incorrect! The mode (maximum value of PMF) is either $\lfloor (n+1)p \rfloor$ or $\lceil (n+1)p-1 \rceil$ depending on whether $(n+1)p$ is an integer.
423
+ ///
424
+ """
425
+ )
426
+ return
427
+
428
+
429
+ @app.cell(hide_code=True)
430
+ def _(mo):
431
+ mo.md(
432
+ r"""
433
+ ## Summary
434
+
435
+ So we've explored the binomial distribution, and honestly, it's one of the most practical probability distributions you'll encounter. Think about it — anytime you're counting successes in a fixed number of trials (like those coin flips we discussed), this is your go-to distribution.
436
+
437
+ I find it fascinating how the expectation is simply $np$. Such a clean, intuitive formula! And remember that neat visualization we saw earlier? When we adjusted the parameters, you could actually see how the distribution shape changes—becoming more symmetric as $n$ increases.
438
+
439
+ The key things to take away:
440
+
441
+ - The binomial distribution models the number of successes in $n$ independent trials, each with probability $p$ of success
442
+
443
+ - Its PMF is given by the formula $P(X=k) = {n \choose k}p^k(1-p)^{n-k}$, which lets us calculate exactly how likely any specific number of successes is
444
+
445
+ - The expected value is $E[X] = np$ and the variance is $Var(X) = np(1-p)$
446
+
447
+ - It's related to other distributions: it's essentially a sum of Bernoulli random variables, and connects to both the negative binomial and Poisson distributions
448
+
449
+ - In Python, the `scipy.stats.binom` module makes working with binomial distributions straightforward—you can generate random samples and calculate probabilities with just a few lines of code
450
+
451
+ You'll see the binomial distribution pop up everywhere—from computer science to quality control, epidemiology, and data science. Any time you have scenarios with binary outcomes over multiple trials, this distribution has you covered.
452
+ """
453
+ )
454
+ return
455
+
456
+
457
+ @app.cell(hide_code=True)
458
+ def _(mo):
459
+ mo.md(r"""Appendix code (helper functions, variables, etc.):""")
460
+ return
461
+
462
+
463
+ @app.cell
464
+ def _():
465
+ import marimo as mo
466
+ return (mo,)
467
+
468
+
469
+ @app.cell(hide_code=True)
470
+ def _():
471
+ import numpy as np
472
+ import matplotlib.pyplot as plt
473
+ import scipy.stats as stats
474
+ import pandas as pd
475
+ import altair as alt
476
+ from wigglystuff import TangleSlider
477
+ return TangleSlider, alt, np, pd, plt, stats
478
+
479
+
480
+ @app.cell(hide_code=True)
481
+ def _(mo):
482
+ alpha_slider = mo.ui.slider(
483
+ value=0.1,
484
+ steps=[0, 0.01, 0.02, 0.03, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1],
485
+ label="α (overdispersion)",
486
+ show_value=True,
487
+ )
488
+ mu_slider = mo.ui.slider(
489
+ value=100, start=1, stop=100, step=1, label="μ (mean)", show_value=True
490
+ )
491
+ return alpha_slider, mu_slider
492
+
493
+
494
+ @app.cell(hide_code=True)
495
+ def _():
496
+ equation = """
497
+ $$
498
+ P(X = k) = \\frac{\\Gamma(k + \\frac{1}{\\alpha})}{\\Gamma(k + 1) \\Gamma(\\frac{1}{\\alpha})} \\left( \\frac{1}{\\mu \\alpha + 1} \\right)^{\\frac{1}{\\alpha}} \\left( \\frac{\\mu \\alpha}{\\mu \\alpha + 1} \\right)^k
499
+ $$
500
+
501
+ $$
502
+ \\sigma^2 = \\mu + \\alpha \\mu^2
503
+ $$
504
+ """
505
+ return (equation,)
506
+
507
+
508
+ @app.cell(hide_code=True)
509
+ def _(alpha_slider, alt, mu_slider, np, pd, stats):
510
+ mu = mu_slider.value
511
+ alpha = alpha_slider.value
512
+ n = 1000 - mu if alpha == 0 else 1 / alpha
513
+ p = n / (mu + n)
514
+ x = np.arange(0, mu * 3 + 1, 1)
515
+ df = pd.DataFrame(
516
+ {
517
+ "x": x,
518
+ "y": stats.nbinom.pmf(x, n, p),
519
+ "y_poi": stats.nbinom.pmf(x, 1000 - mu, 1 - mu / 1000),
520
+ }
521
+ )
522
+ r1k = stats.nbinom.rvs(n, p, size=1000)
523
+ df["in 95% CI"] = df["x"].between(*np.percentile(r1k, q=[2.5, 97.5]))
524
+ base = alt.Chart(df)
525
+
526
+ chart_poi = base.mark_bar(
527
+ fillOpacity=0.25, width=100 / mu, fill="magenta"
528
+ ).encode(
529
+ x=alt.X("x").scale(domain=(-0.4, x.max() + 0.4), nice=False),
530
+ y=alt.Y("y_poi").scale(domain=(0, df.y_poi.max() * 1.1)).title(None),
531
+ )
532
+ chart_nb = base.mark_bar(fillOpacity=0.75, width=100 / mu).encode(
533
+ x="x",
534
+ y="y",
535
+ fill=alt.Fill("in 95% CI")
536
+ .scale(domain=[False, True], range=["#aaa", "#7c7"])
537
+ .legend(orient="bottom-right"),
538
+ )
539
+
540
+ chart = (chart_poi + chart_nb).configure_view(continuousWidth=450)
541
+ return alpha, base, chart, chart_nb, chart_poi, df, mu, n, p, r1k, x
542
+
543
+
544
+ if __name__ == "__main__":
545
+ app.run()