{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Implementing PMFs\n",
"\n",
"This notebook outlines the API for `Pmf` objects in the `empiricaldist` library, showing the implementations of many methods.\n",
"\n",
"[Click here to run this notebook on Colab](https://colab.research.google.com/github/AllenDowney/empiricaldist/blob/master/empiricaldist/pmf_demo.ipynb)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" import empiricaldist\n",
"except ImportError:\n",
" !pip install empiricaldist"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import inspect\n",
"\n",
"def psource(obj):\n",
" \"\"\"Prints the source code for a given object.\n",
"\n",
" obj: function or method object\n",
" \"\"\"\n",
" print(inspect.getsource(obj))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Constructor\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/1).\n",
"\n",
"The `Pmf` class inherits its constructor from `pd.Series`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can create an empty `Pmf` and then add elements.\n",
"\n",
"Here's a `Pmf` that represents a six-sided die."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"from empiricaldist import Pmf\n",
"\n",
"d6 = Pmf()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"for x in [1,2,3,4,5,6]:\n",
" d6[x] = 1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Initially the probabilities don't add up to 1."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 1 | \n",
"
\n",
" \n",
" 2 | \n",
" 1 | \n",
"
\n",
" \n",
" 3 | \n",
" 1 | \n",
"
\n",
" \n",
" 4 | \n",
" 1 | \n",
"
\n",
" \n",
" 5 | \n",
" 1 | \n",
"
\n",
" \n",
" 6 | \n",
" 1 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 1\n",
"2 1\n",
"3 1\n",
"4 1\n",
"5 1\n",
"6 1\n",
"dtype: int64"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`normalize` adds up the probabilities and divides through. The return value is the total probability before normalizing."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def normalize(self):\n",
" \"\"\"Make the probabilities add up to 1 (modifies self).\n",
"\n",
" Returns: normalizing constant\n",
" \"\"\"\n",
" total = self.sum()\n",
" self /= total\n",
" return total\n",
"\n"
]
}
],
"source": [
"psource(Pmf.normalize)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.normalize()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the Pmf is normalized."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"dtype: float64"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"###Properties\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/2).\n",
"\n",
"In a `Pmf` the index contains the quantities (`qs`) and the values contain the probabilities (`ps`).\n",
"\n",
"These attributes are available as properties that return arrays (same semantics as the Pandas `values` property)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([1, 2, 3, 4, 5, 6])"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.qs"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([0.16666667, 0.16666667, 0.16666667, 0.16666667, 0.16666667,\n",
" 0.16666667])"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.ps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sharing\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/3).\n",
"\n",
"Because `Pmf` is a `Series` you can initialize it with any type `Series.__init__` can handle.\n",
"\n",
"Here's an example with a dictionary."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" a | \n",
" 1 | \n",
"
\n",
" \n",
" b | \n",
" 2 | \n",
"
\n",
" \n",
" c | \n",
" 3 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"a 1\n",
"b 2\n",
"c 3\n",
"dtype: int64"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d = dict(a=1, b=2, c=3)\n",
"pmf = Pmf(d)\n",
"pmf"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here's an example with two lists."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.25\n",
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"dtype: float64"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"qs = [1,2,3,4]\n",
"ps = [0.25, 0.25, 0.25, 0.25]\n",
"d4 = Pmf(ps, index=qs)\n",
"d4"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can copy a `Pmf` like this."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"dtype: float64"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6_copy = Pmf(d6)\n",
"d6_copy"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, you have to be careful about sharing. In this example, the copies share the arrays:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.index is d6_copy.index"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.ps is d6_copy.ps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can avoid sharing with `copy=True`"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"dtype: float64"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6_copy = Pmf(d6, copy=True)\n",
"d6_copy"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.index is d6_copy.index"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.ps is d6_copy.ps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Or by calling `copy` explicitly."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.25\n",
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"dtype: float64"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4_copy = d4.copy()\n",
"d4_copy"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.index is d4_copy.index"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.ps is d4_copy.ps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Displaying PMFs\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/4).\n",
"\n",
"`Pmf` provides `_repr_html_`, so it looks good when displayed in a notebook."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def _repr_html_(self):\n",
" \"\"\"Returns an HTML representation of the series.\n",
"\n",
" Mostly used for Jupyter notebooks.\n",
" \"\"\"\n",
" df = pd.DataFrame(dict(probs=self))\n",
" return df._repr_html_()\n",
"\n"
]
}
],
"source": [
"psource(Pmf._repr_html_)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`Pmf` provides `bar`, which plots the Pmf as a bar chart."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def bar(self, **options):\n",
" \"\"\"Make a bar plot.\n",
"\n",
" Note: A previous version of this function used pd.Series.plot.bar,\n",
" but that was a mistake, because that function treats the quantities\n",
" as categorical, even if they are numerical, leading to hilariously\n",
" unexpected results!\n",
"\n",
" Args:\n",
" options: passed to plt.bar\n",
" \"\"\"\n",
" underride(options, label=self.name)\n",
" plt.bar(self.qs, self.ps, **options)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.bar)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def decorate_dice(title):\n",
" \"\"\"Labels the axes.\n",
" \n",
" title: string\n",
" \"\"\"\n",
" plt.xlabel('Outcome')\n",
" plt.ylabel('PMF')\n",
" plt.title(title)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA0LElEQVR4nO3df1yV9f3/8ecB5IcKqCCgiKL5E3+honyxlFosbLak9VHyY0nkdG2ydGxs4UzyY4U29YOlSfbJXLfl9GOfdLYMM6a2btJI0OWvmf0wmHpAp0FioXHO949unXby+AMCLuD9uN9u122e9/W63tfrfd1qPb3Odc6xOZ1OpwAAAAziZXUDAAAAzY0ABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYhwAEANfp0Ucflc1mcxuLjo7W/fffb01DABqMAATAUocOHdK9996ryMhI+fn5qXv37po2bZoOHTpkdWsA2jAfqxsAYK5XXnlFU6dOVZcuXTRjxgz17t1bx48f1/PPP6+XX35ZGzZs0F133WV1m1d19OhReXnxd0mgtSEAAbDEhx9+qPvuu099+vTRW2+9pa5du7r2zZkzR+PGjdN9992n9957T3369LGw06vz8/OzugUADcBfWwBY4ne/+50uXLigNWvWuIUfSQoNDdWzzz6rmpoaPfnkk67xr5/B+eCDD3T//ferU6dOCg4OVnp6ui5cuHDZOf7whz9o1KhRCggIUJcuXXTPPfeovLz8uvp7++23NXr0aPn7++uGG27Qs88+67HO0zNAn376qebOnauoqCj5+fmpb9++WrJkiRwOx3WdG0DT4w4QAEu8+uqrio6O1rhx4zzuHz9+vKKjo/Xaa69dtm/KlCnq3bu3cnNzVVpaqv/5n/9RWFiYlixZ4qp5/PHH9cgjj2jKlCn68Y9/rNOnT+vpp5/W+PHjtW/fPnXq1OmKvR04cEC33XabunbtqkcffVRffvmlcnJyFB4efs11XbhwQYmJiTpx4oR+8pOfqGfPntqzZ4+ys7N16tQp5eXlXXMOAM3ACQDN7NNPP3VKck6aNOmqdXfeeadTkrO6utrpdDqdOTk5TknOBx54wK3urrvucoaEhLheHz9+3Ont7e18/PHH3eoOHDjg9PHxuWz821JSUpz+/v7OTz75xDV2+PBhp7e3t/Pb/7fZq1cvZ1pamuv1okWLnB06dHC+//77bnUPP/yw09vb21lWVnbVcwNoHrwFBqDZffbZZ5KkwMDAq9Z9vb+6utpt/MEHH3R7PW7cOP3rX/9y1b3yyityOByaMmWKzpw549oiIiLUr18/7dy584rnrKur0/bt25WSkqKePXu6xgcNGqTk5ORrrm3Tpk0aN26cOnfu7HbupKQk1dXV6a233rrmHACaHm+BAWh2Xwebr4PQlVwpKP17MJGkzp07S5LOnTunoKAgHTt2TE6nU/369fM4b7t27a54ztOnT+vzzz/3eOyAAQO0bdu2q/Z87Ngxvffee5c91/S1ysrKqx4PoHkQgAA0u+DgYHXr1k3vvffeVevee+89RUZGKigoyG3c29vbY73T6ZQkORwO2Ww2vf766x5rO3bs2MDOr83hcOj73/++fv3rX3vc379//yY7N4DrRwACYIk77rhDzz33nN5++23ddNNNl+3/61//quPHj+snP/lJvee+4YYb5HQ61bt373oHjq5duyogIEDHjh27bN/Ro0ev69znz59XUlJSvc4LoHnxDBAAS2RlZSkgIEA/+clP9K9//ctt39mzZ/Xggw+qffv2ysrKqvfcP/rRj+Tt7a2FCxe67gp9zel0Xna+f+ft7a3k5GRt2bJFZWVlrvEjR45o+/bt1zz3lClTVFRU5LH2008/1ZdfflmPlQBoKtwBAmCJfv366fe//72mTZumoUOHXvZN0GfOnNEf//hH3XDDDfWe+4YbbtBjjz2m7OxsHT9+XCkpKQoMDNTHH3+szZs3a9asWfrVr351xeMXLlyogoICjRs3Tj/72c/05Zdf6umnn9bgwYOv+bZdVlaWtm7dqjvuuEP333+/Ro0apZqaGh04cEAvv/yyjh8/rtDQ0HqvCUDjIgABsMzkyZM1cOBA5ebmukJPSEiIbrnlFs2bN09Dhgxp8NwPP/yw+vfvr//+7//WwoULJUlRUVG67bbbdOedd1712GHDhmn79u3KzMzUggUL1KNHDy1cuFCnTp26ZgBq3769du/erSeeeEKbNm3Siy++qKCgIPXv318LFy5UcHBwg9cEoPHYnN++PwwAANDG8QwQAAAwDgEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBx+B4gDxwOh06ePKnAwEDZbDar2wEAANfB6XTqs88+U/fu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AAAAmk29AlBiYmJT9QEAANBseAgaAAAYp153gLy9va+rjmeAAABAS1bvh6B79eqltLQ0t4efAQAAWpN6BaDi4mI9//zzWrFihXr37q0HHnhA06ZNU+fOnZuqPwAAgEZXr2eA4uLitHr1ap06dUqZmZnavHmzevTooXvuuUc7duxoqh4BAAAaVYMegvb399e9996rwsJCHTx4UJWVlZowYYLOnj3b2P0BAAA0unq9Bfbv/vnPf2rdunVat26dLly4oKysLL4BGgAAtAr1CkAXL17U5s2b9fzzz+uvf/2rbr/9duXl5en222+/7k+IAQAAWK1eAahbt24KDAxUWlqannnmGYWFhUmSampq3Oq4EwQAAFqyegWgc+fO6dy5c1q0aJEee+yxy/bzW2AAAKA14LfAAACAceoVgG666SYtXbpUW7du1cWLF3XrrbcqJydHAQEBTdUfAABAo6vXx+CfeOIJzZs3Tx07dlRkZKRWrFih2bNnN1VvAAAATaJeAejFF1/UM888o+3bt2vLli169dVX9dJLL8nhcDRVfwAAAI2uXgGorKxMP/jBD1yvk5KSZLPZdPLkye/UxKpVqxQdHS1/f3/Fx8eruLj4qvWbNm3SwIED5e/vr6FDh2rbtm1u+8+fP6+MjAz16NFDAQEBiomJUX5+/nfqEQAAtB31CkBffvml/P393cbatWunS5cuNbiBjRs3KjMzUzk5OSotLdXw4cOVnJysyspKj/V79uzR1KlTNWPGDO3bt08pKSlKSUnRwYMHXTWZmZkqKCjQH/7wBx05ckRz585VRkaGtm7d2uA+AQBA21HvX4O///775efn5xr74osv9OCDD6pDhw6usVdeeeW651y+fLlmzpyp9PR0SVJ+fr5ee+01rV27Vg8//PBl9StWrNCECROUlZUlSVq0aJF27NihlStXuu7y7NmzR2lpabr55pslSbNmzdKzzz6r4uJi3XnnnfVZMgAAaIPqdQcoLS1NYWFhCg4Odm333nuvunfv7jZ2vS5evKiSkhIlJSV905CXl5KSklRUVOTxmKKiIrd6SUpOTnarHzt2rLZu3aoTJ07I6XRq586dev/993XbbbfVZ7kAAKCNqtcdoBdeeKFRT37mzBnV1dUpPDzcbTw8PFz/+Mc/PB5jt9s91tvtdtfrp59+WrNmzVKPHj3k4+MjLy8vPffccxo/frzHOWtra1VbW+t6XV1d3dAlAQCAVqBBvwbf0j399NN65513tHXrVpWUlGjZsmWaPXu23nzzTY/1ubm5bnewoqKimrljAADQnBr8a/CNITQ0VN7e3qqoqHAbr6ioUEREhMdjIiIirlr/+eefa968edq8ebMmTpwoSRo2bJj279+vpUuXXvb2mSRlZ2crMzPT9bq6upoQBABAG2bpHSBfX1+NGjVKhYWFrjGHw6HCwkIlJCR4PCYhIcGtXpJ27Njhqr906ZIuXbokLy/3pXl7e1/x+4r8/PwUFBTktgEAgLbL0jtA0lcfWU9LS1NcXJzGjBmjvLw81dTUuD4VNn36dEVGRio3N1eSNGfOHCUmJmrZsmWaOHGiNmzYoL1792rNmjWSvvol+sTERGVlZSkgIEC9evXS7t279eKLL2r58uWWrRMAALQclgeg1NRUnT59WgsWLJDdbldsbKwKCgpcDzqXlZW53c0ZO3as1q9fr/nz52vevHnq16+ftmzZoiFDhrhqNmzYoOzsbE2bNk1nz55Vr1699Pjjj+vBBx9s9vUBAICWx/IAJEkZGRnKyMjwuG/Xrl2XjU2ePFmTJ0++4nwRERGN/ok1AADQdrTJT4EBAABcDQEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGAcAhAAADAOAQgAABiHAAQAAIxDAAIAAMYhAAEAAOMQgAAAgHEIQAAAwDgEIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGCcFhGAVq1apejoaPn7+ys+Pl7FxcVXrd+0aZMGDhwof39/DR06VNu2bbus5siRI7rzzjsVHBysDh06aPTo0SorK2uqJQAAgFbE8gC0ceNGZWZmKicnR6WlpRo+fLiSk5NVWVnpsX7Pnj2aOnWqZsyYoX379iklJUUpKSk6ePCgq+bDDz/UTTfdpIEDB2rXrl1677339Mgjj8jf37+5lgUAAFowywPQ8uXLNXPmTKWnpysmJkb5+flq37691q5d67F+xYoVmjBhgrKysjRo0CAtWrRII0eO1MqVK101v/3tb/WDH/xATz75pEaMGKEbbrhBd955p8LCwpprWQAAoAWzNABdvHhRJSUlSkpKco15eXkpKSlJRUVFHo8pKipyq5ek5ORkV73D4dBrr72m/v37Kzk5WWFhYYqPj9eWLVuu2Edtba2qq6vdNgAA0HZZGoDOnDmjuro6hYeHu42Hh4fLbrd7PMZut1+1vrKyUufPn9fixYs1YcIEvfHGG7rrrrv0ox/9SLt37/Y4Z25uroKDg11bVFRUI6wOAAC0VJa/BdbYHA6HJGnSpEn6xS9+odjYWD388MO64447lJ+f7/GY7OxsVVVVubby8vLmbBkAADQzHytPHhoaKm9vb1VUVLiNV1RUKCIiwuMxERERV60PDQ2Vj4+PYmJi3GoGDRqkt99+2+Ocfn5+8vPza+gyAABAK2PpHSBfX1+NGjVKhYWFrjGHw6HCwkIlJCR4PCYhIcGtXpJ27Njhqvf19dXo0aN19OhRt5r3339fvXr1auQVAACA1sjSO0CSlJmZqbS0NMXFxWnMmDHKy8tTTU2N0tPTJUnTp09XZGSkcnNzJUlz5sxRYmKili1bpokTJ2rDhg3au3ev1qxZ45ozKytLqampGj9+vG655RYVFBTo1Vdf1a5du6xYIgAAaGEsD0Cpqak6ffq0FixYILvdrtjYWBUUFLgedC4rK5OX1zc3qsaOHav169dr/vz5mjdvnvr166ctW7ZoyJAhrpq77rpL+fn5ys3N1UMPPaQBAwbo//7v/3TTTTc1+/oAAEDLY3kAkqSMjAxlZGR43Ofprs3kyZM1efLkq875wAMP6IEHHmiM9gAAQBvT5j4FBgAAcC0EIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGAcAhAAADAOAQgAABiHAAQAAIxDAAIAAMYhAAEAAOMQgAAAgHEIQAAAwDgEIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA47SIALRq1SpFR0fL399f8fHxKi4uvmr9pk2bNHDgQPn7+2vo0KHatm3bFWsffPBB2Ww25eXlNXLXAACgtbI8AG3cuFGZmZnKyclRaWmphg8fruTkZFVWVnqs37Nnj6ZOnaoZM2Zo3759SklJUUpKig4ePHhZ7ebNm/XOO++oe/fuTb0MAADQilgegJYvX66ZM2cqPT1dMTExys/PV/v27bV27VqP9StWrNCECROUlZWlQYMGadGiRRo5cqRWrlzpVnfixAn9/Oc/10svvaR27do1x1IAAEArYWkAunjxokpKSpSUlOQa8/LyUlJSkoqKijweU1RU5FYvScnJyW71DodD9913n7KysjR48OBr9lFbW6vq6mq3DQAAtF2WBqAzZ86orq5O4eHhbuPh4eGy2+0ej7Hb7desX7JkiXx8fPTQQw9dVx+5ubkKDg52bVFRUfVcCQAAaE0sfwussZWUlGjFihVat26dbDbbdR2TnZ2tqqoq11ZeXt7EXQIAACtZGoBCQ0Pl7e2tiooKt/GKigpFRER4PCYiIuKq9X/9619VWVmpnj17ysfHRz4+Pvrkk0/0y1/+UtHR0R7n9PPzU1BQkNsGAADaLksDkK+vr0aNGqXCwkLXmMPhUGFhoRISEjwek5CQ4FYvSTt27HDV33fffXrvvfe0f/9+19a9e3dlZWVp+/btTbcYAADQavhY3UBmZqbS0tIUFxenMWPGKC8vTzU1NUpPT5ckTZ8+XZGRkcrNzZUkzZkzR4mJiVq2bJkmTpyoDRs2aO/evVqzZo0kKSQkRCEhIW7naNeunSIiIjRgwIDmXRwAAGiRLA9AqampOn36tBYsWCC73a7Y2FgVFBS4HnQuKyuTl9c3N6rGjh2r9evXa/78+Zo3b5769eunLVu2aMiQIVYtAQAAtDKWByBJysjIUEZGhsd9u3btumxs8uTJmjx58nXPf/z48QZ2BgAA2qI29ykwAACAayEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGAcAhAAADAOAQgAABiHAAQAAIxDAAIAAMYhAAEAAOMQgAAAgHEIQAAAwDgEIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYp0UEoFWrVik6Olr+/v6Kj49XcXHxVes3bdqkgQMHyt/fX0OHDtW2bdtc+y5duqTf/OY3Gjp0qDp06KDu3btr+vTpOnnyZFMvAwAAtBKWB6CNGzcqMzNTOTk5Ki0t1fDhw5WcnKzKykqP9Xv27NHUqVM1Y8YM7du3TykpKUpJSdHBgwclSRcuXFBpaakeeeQRlZaW6pVXXtHRo0d15513NueyAABAC2Z5AFq+fLlmzpyp9PR0xcTEKD8/X+3bt9fatWs91q9YsUITJkxQVlaWBg0apEWLFmnkyJFauXKlJCk4OFg7duzQlClTNGDAAP2///f/tHLlSpWUlKisrKw5lwYAAFooSwPQxYsXVVJSoqSkJNeYl5eXkpKSVFRU5PGYoqIit3pJSk5OvmK9JFVVVclms6lTp04e99fW1qq6utptAwAAbZelAejMmTOqq6tTeHi423h4eLjsdrvHY+x2e73qv/jiC/3mN7/R1KlTFRQU5LEmNzdXwcHBri0qKqoBqwEAAK2F5W+BNaVLly5pypQpcjqdWr169RXrsrOzVVVV5drKy8ubsUsAANDcfKw8eWhoqLy9vVVRUeE2XlFRoYiICI/HREREXFf91+Hnk08+0V/+8pcr3v2RJD8/P/n5+TVwFQAAoLWx9A6Qr6+vRo0apcLCQteYw+FQYWGhEhISPB6TkJDgVi9JO3bscKv/OvwcO3ZMb775pkJCQppmAQAAoFWy9A6QJGVmZiotLU1xcXEaM2aM8vLyVFNTo/T0dEnS9OnTFRkZqdzcXEnSnDlzlJiYqGXLlmnixInasGGD9u7dqzVr1kj6Kvz8x3/8h0pLS/XnP/9ZdXV1rueDunTpIl9fX2sWCgAAWgzLA1BqaqpOnz6tBQsWyG63KzY2VgUFBa4HncvKyuTl9c2NqrFjx2r9+vWaP3++5s2bp379+mnLli0aMmSIJOnEiRPaunWrJCk2NtbtXDt37tTNN9/cLOsCAAAtl+UBSJIyMjKUkZHhcd+uXbsuG5s8ebImT57ssT46OlpOp7Mx2wMAAG1Mm/4UGAAAgCcEIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGAcAhAAADAOAQgAABiHAAQAAIxDAAIAAMYhAAEAAOMQgAAAgHEIQAAAwDgEIAAAYBwCEAAAMA4BCAAAGIcABAAAjEMAAgAAxiEAAQAA47SIALRq1SpFR0fL399f8fHxKi4uvmr9pk2bNHDgQPn7+2vo0KHatm2b236n06kFCxaoW7duCggIUFJSko4dO9aUSwAAAK2I5QFo48aNyszMVE5OjkpLSzV8+HAlJyersrLSY/2ePXs0depUzZgxQ/v27VNKSopSUlJ08OBBV82TTz6pp556Svn5+frb3/6mDh06KDk5WV988UVzLQsAALRglgeg5cuXa+bMmUpPT1dMTIzy8/PVvn17rV271mP9ihUrNGHCBGVlZWnQoEFatGiRRo4cqZUrV0r66u5PXl6e5s+fr0mTJmnYsGF68cUXdfLkSW3ZsqUZVwYAAFoqSwPQxYsXVVJSoqSkJNeYl5eXkpKSVFRU5PGYoqIit3pJSk5OdtV//PHHstvtbjXBwcGKj4+/4pwAAMAsPlae/MyZM6qrq1N4eLjbeHh4uP7xj394PMZut3ust9vtrv1fj12p5ttqa2tVW1vrel1VVSVJqq6ursdqrp+j9kKTzNvSfZfraeo1k7huDfFd/93lujUM163+TL1mUtP8N/brOZ1O5zVrLQ1ALUVubq4WLlx42XhUVJQF3bRdwXlWd9A6cd3qj2vWMFy3huG6NUxTXrfPPvtMwcHBV62xNACFhobK29tbFRUVbuMVFRWKiIjweExERMRV67/+34qKCnXr1s2tJjY21uOc2dnZyszMdL12OBw6e/asQkJCZLPZ6r2ulqq6ulpRUVEqLy9XUFCQ1e20Gly3+uOaNQzXrWG4bg3TFq+b0+nUZ599pu7du1+z1tIA5Ovrq1GjRqmwsFApKSmSvgofhYWFysjI8HhMQkKCCgsLNXfuXNfYjh07lJCQIEnq3bu3IiIiVFhY6Ao81dXV+tvf/qaf/vSnHuf08/OTn5+f21inTp2+09pasqCgoDbzD3tz4rrVH9esYbhuDcN1a5i2dt2udefna5a/BZaZmam0tDTFxcVpzJgxysvLU01NjdLT0yVJ06dPV2RkpHJzcyVJc+bMUWJiopYtW6aJEydqw4YN2rt3r9asWSNJstlsmjt3rh577DH169dPvXv31iOPPKLu3bu7QhYAADCb5QEoNTVVp0+f1oIFC2S32xUbG6uCggLXQ8xlZWXy8vrmw2pjx47V+vXrNX/+fM2bN0/9+vXTli1bNGTIEFfNr3/9a9XU1GjWrFn69NNPddNNN6mgoED+/v7Nvj4AANDy2JzX86g02oTa2lrl5uYqOzv7srf8cGVct/rjmjUM161huG4NY/p1IwABAADjWP5N0AAAAM2NAAQAAIxDAAIAAMYhAAEAAOMQgAzw1ltv6Yc//KG6d+8um82mLVu2WN1Si5ebm6vRo0crMDBQYWFhSklJ0dGjR61uq8VbvXq1hg0b5vpitYSEBL3++utWt9WqLF682PV9ZriyRx99VDabzW0bOHCg1W21CidOnNC9996rkJAQBQQEaOjQodq7d6/VbTU7ApABampqNHz4cK1atcrqVlqN3bt3a/bs2XrnnXe0Y8cOXbp0Sbfddptqamqsbq1F69GjhxYvXqySkhLt3btX3/ve9zRp0iQdOnTI6tZahXfffVfPPvushg0bZnUrrcLgwYN16tQp1/b2229b3VKLd+7cOd14441q166dXn/9dR0+fFjLli1T586drW6t2Vn+RYhoerfffrtuv/12q9toVQoKCtxer1u3TmFhYSopKdH48eMt6qrl++EPf+j2+vHHH9fq1av1zjvvaPDgwRZ11TqcP39e06ZN03PPPafHHnvM6nZaBR8fnyv+biQ8W7JkiaKiovTCCy+4xnr37m1hR9bhDhBwHaqqqiRJXbp0sbiT1qOurk4bNmxQTU2N67f6cGWzZ8/WxIkTlZSUZHUrrcaxY8fUvXt39enTR9OmTVNZWZnVLbV4W7duVVxcnCZPnqywsDCNGDFCzz33nNVtWYI7QMA1OBwOzZ07VzfeeKPbT67AswMHDighIUFffPGFOnbsqM2bNysmJsbqtlq0DRs2qLS0VO+++67VrbQa8fHxWrdunQYMGKBTp05p4cKFGjdunA4ePKjAwECr22uxPvroI61evVqZmZmaN2+e3n33XT300EPy9fVVWlqa1e01KwIQcA2zZ8/WwYMHeb7gOg0YMED79+9XVVWVXn75ZaWlpWn37t2EoCsoLy/XnDlztGPHDn6vsB7+/W39YcOGKT4+Xr169dL//u//asaMGRZ21rI5HA7FxcXpiSeekCSNGDFCBw8eVH5+vnEBiLfAgKvIyMjQn//8Z+3cuVM9evSwup1WwdfXV3379tWoUaOUm5ur4cOHa8WKFVa31WKVlJSosrJSI0eOlI+Pj3x8fLR792499dRT8vHxUV1dndUttgqdOnVS//799cEHH1jdSovWrVu3y/4yMmjQICPfPuQOEOCB0+nUz3/+c23evFm7du0y9iHBxuBwOFRbW2t1Gy3WrbfeqgMHDriNpaena+DAgfrNb34jb29vizprXc6fP68PP/xQ9913n9WttGg33njjZV/p8f7776tXr14WdWQdApABzp8/7/a3oo8//lj79+9Xly5d1LNnTws7a7lmz56t9evX609/+pMCAwNlt9slScHBwQoICLC4u5YrOztbt99+u3r27KnPPvtM69ev165du7R9+3arW2uxAgMDL3u2rEOHDgoJCeGZs6v41a9+pR/+8Ifq1auXTp48qZycHHl7e2vq1KlWt9ai/eIXv9DYsWP1xBNPaMqUKSouLtaaNWu0Zs0aq1trfk60eTt37nRKumxLS0uzurUWy9P1kuR84YUXrG6tRXvggQecvXr1cvr6+jq7du3qvPXWW51vvPGG1W21OomJic45c+ZY3UaLlpqa6uzWrZvT19fXGRkZ6UxNTXV+8MEHVrfVKrz66qvOIUOGOP38/JwDBw50rlmzxuqWLGFzOp1Oi7IXAACAJXgIGgAAGIcABAAAjEMAAgAAxiEAAQAA4xCAAACAcQhAAADAOAQgAABgHAIQAAAwDgEIgKXKy8v1wAMPqHv37vL19VWvXr00Z84c/etf/7ruOY4fPy6bzab9+/c3XaMA2hQCEADLfPTRR4qLi9OxY8f0xz/+UR988IHy8/NVWFiohIQEnT171uoWAbRRBCAAlpk9e7Z8fX31xhtvKDExUT179tTtt9+uN998UydOnNBvf/tbSZLNZtOWLVvcju3UqZPWrVsnSerdu7ckacSIEbLZbLr55ptddWvXrtXgwYPl5+enbt26KSMjw7WvrKxMkyZNUseOHRUUFKQpU6aooqLCtf/RRx9VbGys1q5dq549e6pjx4762c9+prq6Oj355JOKiIhQWFiYHn/8cbfePv30U/34xz9W165dFRQUpO9973v6+9//3ohXDsB3RQACYImzZ89q+/bt+tnPfqaAgAC3fREREZo2bZo2btyo6/m5wuLiYknSm2++qVOnTumVV16RJK1evVqzZ8/WrFmzdODAAW3dulV9+/aVJDkcDk2aNElnz57V7t27tWPHDn300UdKTU11m/vDDz/U66+/roKCAv3xj3/U888/r4kTJ+qf//yndu/erSVLlmj+/Pn629/+5jpm8uTJqqys1Ouvv66SkhKNHDlSt956K3e0gBbEx+oGAJjp2LFjcjqdGjRokMf9gwYN0rlz53T69OlrztW1a1dJUkhIiCIiIlzjjz32mH75y19qzpw5rrHRo0dLkgoLC3XgwAF9/PHHioqKkiS9+OKLGjx4sN59911XncPh0Nq1axUYGKiYmBjdcsstOnr0qLZt2yYvLy8NGDBAS5Ys0c6dOxUfH6+3335bxcXFqqyslJ+fnyRp6dKl2rJli15++WXNmjWrAVcLQGMjAAGw1PXc4WmIyspKnTx5UrfeeqvH/UeOHFFUVJQr/EhSTEyMOnXqpCNHjrgCUHR0tAIDA1014eHh8vb2lpeXl9tYZWWlJOnvf/+7zp8/r5CQELfzff755/rwww8bbX0AvhsCEABL9O3bVzabTUeOHNFdd9112f4jR46oc+fO6tq1q2w222VB6dKlS1ed/9tvqzVUu3bt3F7bbDaPYw6HQ5J0/vx5devWTbt27bpsrk6dOjVKTwC+O54BAmCJkJAQff/739czzzyjzz//3G2f3W7XSy+9pNTUVNlsNnXt2lWnTp1y7T927JguXLjgeu3r6ytJqqurc40FBgYqOjpahYWFHs8/aNAglZeXq7y83DV2+PBhffrpp4qJiWnwukaOHCm73S4fHx/17dvXbQsNDW3wvAAaFwEIgGVWrlyp2tpaJScn66233lJ5ebkKCgr0/e9/X5GRka5PV33ve9/TypUrtW/fPu3du1cPPvig212YsLAwBQQEqKCgQBUVFaqqqpL01ae4li1bpqeeekrHjh1TaWmpnn76aUlSUlKShg4dqmnTpqm0tFTFxcWaPn26EhMTFRcX1+A1JSUlKSEhQSkpKXrjjTd0/Phx7dmzR7/97W+1d+/e73C1ADQmAhAAy/Tr10979+5Vnz59NGXKFN1www2aNWuWbrnlFhUVFalLly6SpGXLlikqKkrjxo3Tf/7nf+pXv/qV2rdv75rHx8dHTz31lJ599ll1795dkyZNkiSlpaUpLy9PzzzzjAYPHqw77rhDx44dk/TV21Z/+tOf1LlzZ40fP15JSUnq06ePNm7c+J3WZLPZtG3bNo0fP17p6enq37+/7rnnHn3yyScKDw//TnMDaDw2Z1M9gQgAANBCcQcIAAAYhwAEAACMQwACAADGIQABAADjEIAAAIBxCEAAAMA4BCAAAGAcAhAAADAOAQgAABiHAAQAAIxDAAIAAMYhAAEAAOP8f8URhfWs0sTfAAAAAElFTkSuQmCC",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"d6.bar()\n",
"decorate_dice('One die')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`Pmf` inherits `plot` from `Series`."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAlEAAAHHCAYAAACfqw0dAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA7dElEQVR4nO3de1SVVf7H8c/hjnJTEBBDEPFGXiIRhsyYkiTHLs74U2s08ZJ2IU35ddGZ0mwKtHRiKtNovLWaKX+1ylFLTRm1mlAUhkmhzGsYCF4BhUTlPL8/Wp6ZE6DyiB5h3q+1nrU4e++z9/d5Zk3n43P2OcdiGIYhAAAANIqTowsAAABojghRAAAAJhCiAAAATCBEAQAAmECIAgAAMIEQBQAAYAIhCgAAwARCFAAAgAmEKAAAABMIUQBwDT3//POyWCx2beHh4Ro7dqxjCgJgGiEKQLNXUFCg0aNHq0OHDnJ3d1dISIhGjRqlgoICR5cGoAVzcXQBAHAlPvroIz3wwANq27atJkyYoE6dOungwYNavHixPvzwQ73//vv69a9/7egyL2r37t1ycuLftEBzQ4gC0Gzt27dPDz74oCIiIvT555+rXbt2tr4nnnhCAwYM0IMPPqivv/5aERERDqz04tzd3R1dAgAT+KcPgGbrlVdeUXV1tTIzM+0ClCQFBATorbfeUlVVlV5++WVb+4U9SXv37tXYsWPl5+cnX19fjRs3TtXV1XXWePfdd9W3b195enqqbdu2uv/++3Xo0KHLqu/LL79Uv3795OHhoc6dO+utt96qd1x9e6LKy8s1depUhYaGyt3dXZGRkZo7d66sVutlrQ3g6uNOFIBma/Xq1QoPD9eAAQPq7b/tttsUHh6uTz75pE7fiBEj1KlTJ6WnpysvL09//vOfFRgYqLlz59rGvPTSS3ruuec0YsQIPfTQQzp69Khef/113XbbbfrnP/8pPz+/BmvbuXOnBg0apHbt2un555/X+fPnNWvWLAUFBV3yvKqrq5WQkKDi4mI9/PDD6tixo7766ivNmDFDhw8fVkZGxiXnAHANGADQDJWXlxuSjPvuu++i4+69915DklFZWWkYhmHMmjXLkGSMHz/ebtyvf/1rw9/f3/b44MGDhrOzs/HSSy/Zjdu5c6fh4uJSp/3nhg4danh4eBjff/+9ra2wsNBwdnY2fv6f3rCwMCM5Odn2+A9/+IPRunVr47vvvrMbN336dMPZ2dkoKiq66NoArg3ezgPQLJ06dUqS5O3tfdFxF/orKyvt2h955BG7xwMGDNDx48dt4z766CNZrVaNGDFCx44dsx3BwcHq0qWLNm3a1OCatbW1Wr9+vYYOHaqOHTva2nv06KGkpKRLntsHH3ygAQMGqE2bNnZrJyYmqra2Vp9//vkl5wBw9fF2HoBm6UI4uhCmGtJQ2PrPcCNJbdq0kSSdPHlSPj4+2rNnjwzDUJcuXeqd19XVtcE1jx49qh9//LHe53br1k2ffvrpRWves2ePvv766zr7vC44cuTIRZ8P4NogRAFolnx9fdW+fXt9/fXXFx339ddfq0OHDvLx8bFrd3Z2rne8YRiSJKvVKovForVr19Y71svLy2Tll2a1WnXnnXfq6aefrre/a9euV21tAJePEAWg2br77rv19ttv68svv9Stt95ap/+LL77QwYMH9fDDDzd67s6dO8swDHXq1KnRoaVdu3by9PTUnj176vTt3r37stY+ffq0EhMTG7UugGuLPVEAmq2nnnpKnp6eevjhh3X8+HG7vhMnTuiRRx5Rq1at9NRTTzV67t/85jdydnbW7NmzbXenLjAMo856/8nZ2VlJSUlauXKlioqKbO3ffPON1q9ff8m1R4wYoezs7HrHlpeX6/z58404EwBXC3eiADRbXbp00fLlyzVq1Cj16tWrzjeWHzt2TO+99546d+7c6Lk7d+6sF198UTNmzNDBgwc1dOhQeXt768CBA/r44481adIkPfnkkw0+f/bs2Vq3bp0GDBigxx57TOfPn9frr7+uG2+88ZJvQT711FNatWqV7r77bo0dO1Z9+/ZVVVWVdu7cqQ8//FAHDx5UQEBAo88JQNMiRAFo1oYPH67u3bsrPT3dFpz8/f11++2363e/+5169uxpeu7p06era9euevXVVzV79mxJUmhoqAYNGqR77733os/t3bu31q9fr9TUVM2cOVM33HCDZs+ercOHD18yRLVq1UpbtmxRWlqaPvjgA73zzjvy8fFR165dNXv2bPn6+po+JwBNx2L8/D41AAAALok9UQAAACYQogAAAEwgRAEAAJhAiAIAADCBEAUAAGACIQoAAMAEvifqKrJarSopKZG3t7csFoujywEAAJfBMAydOnVKISEhcnJq+H4TIeoqKikpUWho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bds223OGDx+uI0eOaO3atcrNzdXNN9+sgQMHcmcNuE64OLoAALgSe/bskWEY6tGjR739PXr00MmTJ3X06NFLztWuXTtJkr+/v4KDg23tL774ov73f/9XTzzxhK2tX79+kqSsrCzt3LlTBw4cUGhoqCTpnXfe0Y033qjt27fbxlmtVi1ZskTe3t6KiorS7bffrt27d+vTTz+Vk5OTunXrprlz52rTpk2Ki4vTl19+qZycHB05ckTu7u6SpHnz5mnlypX68MMPNWnSJBNXC0BTIkQBaBEu506TGUeOHFFJSYkGDhxYb/8333yj0NBQW4CSpKioKPn5+embb76xhajw8HB5e3vbxgQFBcnZ2VlOTk52bUeOHJEk/etf/9Lp06fl7+9vt96PP/6offv2Ndn5ATCPEAWgWYuMjJTFYtE333yjX//613X6v/nmG7Vp00bt2rWTxWKpE7bOnTt30fl//hahWa6urnaPLRZLvW1Wq1WSdPr0abVv316bN2+uM5efn1+T1ATgyrAnCkCz5u/vrzvvvFNvvvmmfvzxR7u+0tJS/eUvf9HIkSNlsVjUrl07HT582Na/Z88eVVdX2x67ublJkmpra21t3t7eCg8PV1ZWVr3r9+jRQ4cOHdKhQ4dsbYWFhSovL1dUVJTp87r55ptVWloqFxcXRUZG2h0BAQGm5wXQdAhRAJq9N954QzU1NUpKStLnn3+uQ4cOad26dbrzzjvVoUMH26fe7rjjDr3xxhv65z//qR07duiRRx6xuxsUGBgoT09PrVu3TmVlZaqoqJD006fr5s+fr9dee0179uxRXl6eXn/9dUlSYmKievXqpVGjRikvL085OTkaM2aMEhISFBMTY/qcEhMTFR8fr6FDh+qzzz7TwYMH9dVXX+n3v/+9duzYcQVXC0BTIUQBaPa6dOmiHTt2KCIiQiNGjFDnzp01adIk3X777crOzlbbtm0lSfPnz1doaKgGDBig3/72t3ryySfVqlUr2zwuLi567bXX9NZbbykkJET33XefJCk5OVkZGRl68803deONN+ruu+/Wnj17JP30Ftzf/vY3tWnTRrfddpsSExMVERGhFStWXNE5WSwWffrpp7rttts0btw4de3aVffff7++//57BQUFXdHcAJqGxbhauzEBAABaMO5EAQAAmECIAgAAMIEQBQAAYAIhCgAAwARCFAAAgAmEKAAAABMIUQAAACYQogAAAEwgRAEAAJhAiAIAADCBEAUAAGACIQoAAMCE/wfXyi3/wmTXWgAAAABJRU5ErkJggg==",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"d6.plot()\n",
"decorate_dice('One die')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Make Pmf from sequence\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/5).\n",
"\n",
"\n",
"The following function makes a `Pmf` object from a sequence of values."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" @staticmethod\n",
" def from_seq(\n",
" seq,\n",
" normalize=True,\n",
" sort=True,\n",
" ascending=True,\n",
" dropna=True,\n",
" na_position=\"last\",\n",
" **options,\n",
" ):\n",
" \"\"\"Make a PMF from a sequence of values.\n",
"\n",
" Args:\n",
" seq: iterable\n",
" normalize: whether to normalize the Pmf, default True\n",
" sort: whether to sort the Pmf by values, default True\n",
" ascending: whether to sort in ascending order, default True\n",
" dropna: whether to drop NaN values, default True\n",
" na_position: If ‘first’ puts NaNs at the beginning,\n",
" ‘last’ puts NaNs at the end.\n",
" options: passed to the pd.Series constructor\n",
"\n",
" Returns: Pmf object\n",
" \"\"\"\n",
" # compute the value counts\n",
" series = pd.Series(seq).value_counts(\n",
" normalize=normalize, sort=False, dropna=dropna\n",
" )\n",
" # make the result a Pmf\n",
" # (since we just made a fresh Series, there is no reason to copy it)\n",
" options[\"copy\"] = False\n",
" underride(options, name=\"\")\n",
" pmf = Pmf(series, **options)\n",
"\n",
" # sort in place, if desired\n",
" if sort:\n",
" pmf.sort_index(\n",
" inplace=True, ascending=ascending, na_position=na_position\n",
" )\n",
"\n",
" return pmf\n",
"\n"
]
}
],
"source": [
"psource(Pmf.from_seq)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" a | \n",
" 0.2 | \n",
"
\n",
" \n",
" e | \n",
" 0.2 | \n",
"
\n",
" \n",
" l | \n",
" 0.4 | \n",
"
\n",
" \n",
" n | \n",
" 0.2 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"a 0.2\n",
"e 0.2\n",
"l 0.4\n",
"n 0.2\n",
"Name: , dtype: float64"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pmf = Pmf.from_seq(list('allen'))\n",
"pmf"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.2 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.4 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.2 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.2 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.2\n",
"2 0.4\n",
"3 0.2\n",
"5 0.2\n",
"Name: , dtype: float64"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pmf = Pmf.from_seq(np.array([1, 2, 2, 3, 5]))\n",
"pmf"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Selection\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/6).\n",
"\n",
"`Pmf` overrides `__getitem__` to return 0 for values that are not in the distribution."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def __getitem__(self, key):\n",
" check_dict_or_set_indexers(key)\n",
" key = com.apply_if_callable(key, self)\n",
"\n",
" if key is Ellipsis:\n",
" return self\n",
"\n",
" key_is_scalar = is_scalar(key)\n",
" if isinstance(key, (list, tuple)):\n",
" key = unpack_1tuple(key)\n",
"\n",
" if is_integer(key) and self.index._should_fallback_to_positional:\n",
" warnings.warn(\n",
" # GH#50617\n",
" \"Series.__getitem__ treating keys as positions is deprecated. \"\n",
" \"In a future version, integer keys will always be treated \"\n",
" \"as labels (consistent with DataFrame behavior). To access \"\n",
" \"a value by position, use `ser.iloc[pos]`\",\n",
" FutureWarning,\n",
" stacklevel=find_stack_level(),\n",
" )\n",
" return self._values[key]\n",
"\n",
" elif key_is_scalar:\n",
" return self._get_value(key)\n",
"\n",
" # Convert generator to list before going through hashable part\n",
" # (We will iterate through the generator there to check for slices)\n",
" if is_iterator(key):\n",
" key = list(key)\n",
"\n",
" if is_hashable(key) and not isinstance(key, slice):\n",
" # Otherwise index.get_value will raise InvalidIndexError\n",
" try:\n",
" # For labels that don't resolve as scalars like tuples and frozensets\n",
" result = self._get_value(key)\n",
"\n",
" return result\n",
"\n",
" except (KeyError, TypeError, InvalidIndexError):\n",
" # InvalidIndexError for e.g. generator\n",
" # see test_series_getitem_corner_generator\n",
" if isinstance(key, tuple) and isinstance(self.index, MultiIndex):\n",
" # We still have the corner case where a tuple is a key\n",
" # in the first level of our MultiIndex\n",
" return self._get_values_tuple(key)\n",
"\n",
" if isinstance(key, slice):\n",
" # Do slice check before somewhat-costly is_bool_indexer\n",
" return self._getitem_slice(key)\n",
"\n",
" if com.is_bool_indexer(key):\n",
" key = check_bool_indexer(self.index, key)\n",
" key = np.asarray(key, dtype=bool)\n",
" return self._get_rows_with_mask(key)\n",
"\n",
" return self._get_with(key)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.__getitem__)"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.16666666666666666"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6[1]"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.16666666666666666"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6[6]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you use square brackets to look up a quantity that's not in the `Pmf`, you get a `KeyError`. "
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [],
"source": [
"# d6[7]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`Pmf` objects are mutable, but in general the result is not normalized."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"d7 = d6.copy()"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 7 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"7 0.166667\n",
"dtype: float64"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d7[7] = 1/6\n",
"d7"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.1666666666666665"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d7.sum()"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.1666666666666665"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d7.normalize()"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0000000000000002"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d7.sum()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Statistics\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/7).\n",
"\n",
"`Pmf` overrides the statistics methods to compute `mean`, `median`, etc.\n",
"\n",
"These functions only work correctly if the `Pmf` is normalized."
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def mean(self):\n",
" \"\"\"Computes expected value.\n",
"\n",
" Returns: float\n",
" \"\"\"\n",
" if not np.allclose(1, self.sum()):\n",
" raise ValueError(\"Pmf must be normalized before computing mean\")\n",
"\n",
" if not pd.api.types.is_numeric_dtype(self.dtype):\n",
" raise ValueError(\"mean is only defined for numeric data\")\n",
"\n",
" return np.sum(self.ps * self.qs)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.mean)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3.5"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.mean()"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def var(self):\n",
" \"\"\"Variance of a PMF.\n",
"\n",
" Returns: float\n",
" \"\"\"\n",
" m = self.mean()\n",
" d = self.qs - m\n",
" return np.sum(d**2 * self.ps)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.var)"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2.9166666666666665"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.var()"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def std(self):\n",
" \"\"\"Standard deviation of a PMF.\n",
"\n",
" Returns: float\n",
" \"\"\"\n",
" return np.sqrt(self.var())\n",
"\n"
]
}
],
"source": [
"psource(Pmf.std)"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.707825127659933"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.std()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sampling\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/8).\n",
"\n",
"`choice` chooses a random values from the Pmf, following the API of `np.random.choice`"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def choice(self, *args, **kwargs):\n",
" \"\"\"Makes a random sample.\n",
"\n",
" Uses the probabilities as weights unless `p` is provided.\n",
"\n",
" Args:\n",
" args: same as np.random.choice\n",
" kwargs: same as np.random.choice\n",
"\n",
" Returns: NumPy array\n",
" \"\"\"\n",
" underride(kwargs, p=self.ps)\n",
" return np.random.choice(self.qs, *args, **kwargs)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.choice)"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([3, 3, 5, 6, 1, 5, 3, 6, 6, 1])"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.choice(size=10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`sample` chooses a random values from the `Pmf`, following the API of `pd.Series.sample`"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def sample(self, *args, **kwargs):\n",
" \"\"\"Samples with replacement using probabilities as weights.\n",
"\n",
" Uses the inverse CDF.\n",
"\n",
" Args:\n",
" n: number of values\n",
"\n",
" Returns: NumPy array\n",
" \"\"\"\n",
" cdf = self.make_cdf()\n",
" return cdf.sample(*args, **kwargs)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.sample)"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([3., 3., 5., 1., 1., 1., 3., 5., 3., 6.])"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.sample(n=10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Arithmetic\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/9).\n",
"\n",
"`Pmf` provides `add_dist`, which computes the distribution of the sum.\n",
"\n",
"The implementation uses outer products to compute the convolution of the two distributions."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def add_dist(self, x):\n",
" \"\"\"Computes the Pmf of the sum of values drawn from self and x.\n",
"\n",
" Args:\n",
" x: Distribution, scalar, or sequence\n",
"\n",
" Returns: new Pmf\n",
" \"\"\"\n",
" if isinstance(x, Distribution):\n",
" return self.convolve_dist(x, np.add.outer)\n",
" else:\n",
" return Pmf(self.ps.copy(), index=self.qs + x)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.add_dist)"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def convolve_dist(self, dist, ufunc):\n",
" \"\"\"Convolve two distributions.\n",
"\n",
" Args:\n",
" dist: Distribution\n",
" ufunc: elementwise function for arrays\n",
"\n",
" Returns: new Pmf\n",
" \"\"\"\n",
" dist = dist.make_pmf()\n",
" qs = ufunc(self.qs, dist.qs).flatten()\n",
" ps = np.multiply.outer(self.ps, dist.ps).flatten()\n",
" series = pd.Series(ps).groupby(qs).sum()\n",
"\n",
" return Pmf(series)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.convolve_dist)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here's the distribution of the sum of two dice."
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 2 | \n",
" 0.027778 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.055556 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.083333 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.111111 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.138889 | \n",
"
\n",
" \n",
" 7 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 8 | \n",
" 0.138889 | \n",
"
\n",
" \n",
" 9 | \n",
" 0.111111 | \n",
"
\n",
" \n",
" 10 | \n",
" 0.083333 | \n",
"
\n",
" \n",
" 11 | \n",
" 0.055556 | \n",
"
\n",
" \n",
" 12 | \n",
" 0.027778 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"2 0.027778\n",
"3 0.055556\n",
"4 0.083333\n",
"5 0.111111\n",
"6 0.138889\n",
"7 0.166667\n",
"8 0.138889\n",
"9 0.111111\n",
"10 0.083333\n",
"11 0.055556\n",
"12 0.027778\n",
"dtype: float64"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6 = Pmf.from_seq([1,2,3,4,5,6])\n",
"\n",
"twice = d6.add_dist(d6)\n",
"twice"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.999999999999998"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAA14UlEQVR4nO3df1iUdb7/8dcA8kMF8keCKImmpagJCrKoSZuzoctm7LZoZsFSq6dNThp7PIn5Y8+xQks9WJpkJ3I7J9PT2XQtjZYmNVtREqTNNLNSYXUHdC1QSPDLzPcPL6cz6/gDQ2+G+/m4rvvK+dzv+3O/7/tSeHXPfc9YnE6nUwAAACbiY3QDAAAA1xsBCAAAmA4BCAAAmA4BCAAAmA4BCAAAmA4BCAAAmA4BCAAAmA4BCAAAmA4BCAAAmA4BCAA8sFgs+t3vfud6vXr1alksFh0+fNiwngC0HAIQgOvCYrFc0bJ161ajWwVgAn5GNwDAHP7rv/7L7fVrr72moqKiC8YHDBhwPdu6Yg8++KDuu+8+BQQEGN0KgBZAAAJwXTzwwANur3fu3KmioqILxlsrX19f+fr6Gt0GgBbCW2AAWoVf/OIXGjp0qNvY3XffLYvFoo0bN7rGdu3aJYvFonfffdc19vXXXystLU2dO3dW+/bt9aMf/UibNm26ov02NDTo8ccf14033qjg4GCNHz9ef/3rXy+ou9g9QO+++66SkpIUHByskJAQxcfHa82aNW41u3bt0tixYxUaGqr27dsrKSlJf/7zn6+oPwDXBgEIQKtw++2365NPPlFtba0kyel06s9//rN8fHy0fft2V9327dvl4+OjkSNHSpKqqqo0YsQIvffee3r00Uf19NNP68yZMxo/frzWr19/2f3++te/Vl5enu666y4tXLhQ7dq1U0pKyhX1vHr1aqWkpOjkyZPKycnRwoULFRMTo8LCQlfNBx98oNGjR6u2tlbz58/XM888o2+//VZ33nmnSkpKmnOKALQkJwAYYNq0ac7/+yPo448/dkpybt682el0Op1/+ctfnJKcaWlpzoSEBFfd+PHjnbGxsa7XM2bMcEpybt++3TV26tQpZ+/evZ1RUVHOpqami/ZQXl7ulOR89NFH3cbvv/9+pyTn/PnzXWOvvvqqU5Lz0KFDTqfT6fz222+dwcHBzoSEBOd3333ntr3D4XD9t1+/fs7k5GTXmNPpdNbX1zt79+7t/MlPfnK50wTgGuEKEIBWITY2Vh07dtSHH34o6dyVnp49eyo9PV1lZWWqr6+X0+nURx99pNtvv9213ebNmzV8+HCNGjXKNdaxY0dNnTpVhw8f1r59+y66z82bN0uSHnvsMbfxGTNmXLbfoqIinTp1SrNmzVJgYKDbOovFIkkqLy/XwYMHdf/99+vvf/+7Tpw4oRMnTqiurk5jxozRhx9+KIfDcdl9AWh53AQNoFXw9fVVYmKi6+2u7du36/bbb9eoUaPU1NSknTt3KiwsTCdPnnQLQEeOHFFCQsIF851/muzIkSMaNGiQx30eOXJEPj4+uvnmm93Gb7311sv2+9VXX0nSReeWpIMHD0qSMjIyLlpTU1OjTp06XXZ/AFoWAQhAqzFq1CjXPTzbt2/Xk08+qRtuuEGDBg3S9u3bFRYWJkluAag1O39157nnnlNMTIzHmo4dO17HjgCcRwAC0Grcfvvtamxs1BtvvKGjR4+6gs7o0aNdAeiWW25xBSFJ6tWrlw4cOHDBXJ9//rlr/cX06tVLDodDX331ldtVH0/z/aPzV4327t2rvn37XrImJCREVqv1snMCuH64BwhAq5GQkKB27dpp0aJF6ty5swYOHCjpXDDauXOntm3bdsHVn5/+9KcqKSlRcXGxa6yurk6rVq1SVFSUoqOjL7q/cePGSZKef/55t/G8vLzL9nrXXXcpODhYubm5OnPmjNs6p9MpSRo2bJhuvvlmLV68WKdPn75gjuPHj192PwCuDa4AAWg12rdvr2HDhmnnzp2uzwCSzl0BqqurU11d3QUBaNasWXrjjTc0btw4PfbYY+rcubN+//vf69ChQ/rDH/4gH5+L/39eTEyMJk2apBdffFE1NTUaMWKEbDabvvzyy8v2GhISov/4j//Qr3/9a8XHx+v+++9Xp06d9Mknn6i+vl6///3v5ePjo//8z//UuHHjNHDgQGVmZqpHjx46evSotmzZopCQEL399ts/7KQBuCoEIACtyvmrPf/3qa7w8HD17dtXX3755QUBKCwsTDt27NATTzyhF154QWfOnNFtt92mt99++4o+z6egoEA33nijXn/9dW3YsEF33nmnNm3apMjIyMtu+/DDD6tbt25auHChFixYoHbt2ql///56/PHHXTV33HGHiouLtWDBAi1fvlynT59WeHi4EhIS9E//9E/NODMAWpLFef5aLQAAgElwDxAAADAdAhAAADAdAhAAADAdAhAAADAdAhAAADAdAhAAADAdPgfIA4fDoWPHjik4ONj1QWwAAKB1czqdOnXqlCIiIi75IagSAcijY8eOXdGHoAEAgNansrJSPXv2vGQNAciD4OBgSedOYEhIiMHdAACAK1FbW6vIyEjX7/FLIQB5cP5tr5CQEAIQAABe5kpuX+EmaAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDoEIAAAYDp+RjcAAM0RNWuT0S1Ikg4vTDG6BQA/AFeAAACA6RCAAACA6RCAAACA6RCAAACA6RCAAACA6RCAAACA6RCAAACA6RCAAACA6RCAAACA6RgegFasWKGoqCgFBgYqISFBJSUlF6397LPPdO+99yoqKkoWi0V5eXke644ePaoHHnhAXbp0UVBQkAYPHqzdu3dfoyMAAADextAAtG7dOmVnZ2v+/PkqKyvTkCFDlJycrOrqao/19fX16tOnjxYuXKjw8HCPNd98841Gjhypdu3a6d1339W+ffu0ZMkSderU6VoeCgAA8CKGfhfY0qVLNWXKFGVmZkqS8vPztWnTJhUUFGjWrFkX1MfHxys+Pl6SPK6XpEWLFikyMlKvvvqqa6x3797XoHsAAOCtDLsC1NjYqNLSUlmt1u+b8fGR1WpVcXHxVc+7ceNGxcXFKS0tTd26dVNsbKxefvnlS27T0NCg2tpatwUAALRdhgWgEydOqKmpSWFhYW7jYWFhstvtVz3v119/rZUrV6pfv35677339Jvf/EaPPfaYfv/73190m9zcXIWGhrqWyMjIq94/AABo/Qy/CbqlORwODR06VM8884xiY2M1depUTZkyRfn5+RfdJicnRzU1Na6lsrLyOnYMAACuN8MCUNeuXeXr66uqqiq38aqqqove4HwlunfvrujoaLexAQMGqKKi4qLbBAQEKCQkxG0BAABtl2EByN/fX8OGDZPNZnONORwO2Ww2JSYmXvW8I0eO1IEDB9zGvvjiC/Xq1euq5wQAAG2LoU+BZWdnKyMjQ3FxcRo+fLjy8vJUV1fneiosPT1dPXr0UG5urqRzN07v27fP9eejR4+qvLxcHTt2VN++fSVJjz/+uEaMGKFnnnlGEyZMUElJiVatWqVVq1YZc5AAAKDVMTQATZw4UcePH9e8efNkt9sVExOjwsJC143RFRUV8vH5/iLVsWPHFBsb63q9ePFiLV68WElJSdq6daukc4/Kr1+/Xjk5Ofr3f/939e7dW3l5eZo8efJ1PTYAANB6WZxOp9PoJlqb2tpahYaGqqamhvuBgFYmatYmo1uQJB1emGJ0CwD+QXN+f7e5p8AAAAAuhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMhwAEAABMp1UEoBUrVigqKkqBgYFKSEhQSUnJRWs/++wz3XvvvYqKipLFYlFeXt4l5164cKEsFotmzJjRsk0DAACvZXgAWrdunbKzszV//nyVlZVpyJAhSk5OVnV1tcf6+vp69enTRwsXLlR4ePgl5/7444/10ksv6bbbbrsWrQMAAC9leABaunSppkyZoszMTEVHRys/P1/t27dXQUGBx/r4+Hg999xzuu+++xQQEHDReU+fPq3Jkyfr5ZdfVqdOna5V+wAAwAsZGoAaGxtVWloqq9XqGvPx8ZHValVxcfEPmnvatGlKSUlxm/tiGhoaVFtb67YAAIC2y9AAdOLECTU1NSksLMxtPCwsTHa7/arnXbt2rcrKypSbm3tF9bm5uQoNDXUtkZGRV71vAADQ+hn+FlhLq6ys1PTp0/X6668rMDDwirbJyclRTU2Na6msrLzGXQIAACP5Gbnzrl27ytfXV1VVVW7jVVVVl73B+WJKS0tVXV2toUOHusaampr04Ycfavny5WpoaJCvr6/bNgEBAZe8nwgwi6hZm4xuQYcXphjdQotoDedSajvnE2hphl4B8vf317Bhw2Sz2VxjDodDNptNiYmJVzXnmDFj9Omnn6q8vNy1xMXFafLkySovL78g/AAAAPMx9AqQJGVnZysjI0NxcXEaPny48vLyVFdXp8zMTElSenq6evTo4bqfp7GxUfv27XP9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XysuLk4HDx7UG2+8oS+//FL5+fmy2WxKTEzUyZMnjW4RQBtFAAJgmGnTpsnf319/+tOflJSUpJtuuknjxo3T+++/r6NHj+rJJ5+UJFksFm3YsMFt2xtuuEGrV6+WdO6bviUpNjZWFotFd9xxh6uuoKBAAwcOVEBAgLp3766srCzXuoqKCt1zzz3q2LGjQkJCNGHCBFVVVbnW/+53v1NMTIwKCgp00003qWPHjnr00UfV1NSkZ599VuHh4erWrZuefvppt96+/fZb/frXv9aNN96okJAQ3Xnnnfrkk09a8MwB+KEIQAAMcfLkSb333nt69NFHFRQU5LYuPDxckydP1rp163QlX1dYUlIiSXr//ff1t7/9TW+99ZYkaeXKlZo2bZqmTp2qTz/9VBs3blTfvn0lSQ6HQ/fcc49Onjypbdu2qaioSF9//bUmTpzoNvdXX32ld999V4WFhXrjjTf0yiuvKCUlRX/961+1bds2LVq0SHPmzNGuXbtc26Slpam6ulrvvvuuSktLNXToUI0ZM4YrWkAr4md0AwDM6eDBg3I6nRowYIDH9QMGDNA333yj48ePX3auG2+8UZLUpUsXhYeHu8afeuop/fa3v9X06dNdY/Hx8ZIkm82mTz/9VIcOHVJkZKQk6bXXXtPAgQP18ccfu+ocDocKCgoUHBys6Oho/fjHP9aBAwe0efNm+fj46NZbb9WiRYu0ZcsWJSQk6KOPPlJJSYmqq6sVEBAgSVq8eLE2bNig//3f/9XUqVOv4mwBaGkEIACGupIrPFejurpax44d05gxYzyu379/vyIjI13hR5Kio6N1ww03aP/+/a4AFBUVpeDgYFdNWFiYfH195ePj4zZWXV0tSfrkk090+vRpdenSxW1/3333nb766qsWOz4APwwBCIAh+vbtK4vFov379+vnP//5Bev379+vTp066cYbb5TFYrkgKJ09e/aS8//j22pXq127dm6vLRaLxzGHwyFJOn36tLp3766tW7deMNcNN9zQIj0B+OG4BwiAIbp06aKf/OQnevHFF/Xdd9+5rbPb7Xr99dc1ceJEWSwW3Xjjjfrb3/7mWn/w4EHV19e7Xvv7+0uSmpqaXGPBwcGKioqSzWbzuP8BAwaosrJSlZWVrrF9+/bp22+/VXR09FUf19ChQ2W32+Xn56e+ffu6LV27dr3qeQG0LAIQAMMsX75cDQ0NSk5O1ocffqjKykoVFhbqJz/5iXr06OF6uurOO+/U8uXLtWfPHu3evVuPPPKI21WYbt26KSgoSIWFhaqqqlJNTY2kc09xLVmyRM8//7wOHjyosrIyvfDCC5Ikq9WqwYMHa/LkySorK1NJSYnS09OVlJSkuLi4qz4mq9WqxMREpaam6k9/+pMOHz6sHTt26Mknn9Tu3bt/wNkC0JIIQAAM069fP+3evVt9+vTRhAkTdPPNN2vq1Kn68Y9/rOLiYnXu3FmStGTJEkVGRur222/X/fffr3/5l39R+/btXfP4+fnp+eef10svvaSIiAjdc889kqSMjAzl5eXpxRdf1MCBA/Wzn/1MBw8elHTubas//vGP6tSpk0aPHi2r1ao+ffpo3bp1P+iYLBaLNm/erNGjRyszM1O33HKL7rvvPh05ckRhYWE/aG4ALcfivFZ3IAIAALRSXAECAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACm8/8BID/F6ZS+DSIAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"twice.bar()\n",
"decorate_dice('Two dice')\n",
"twice.mean()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To add a constant to a distribution, you could construct a deterministic `Pmf`"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"5 0.25\n",
"dtype: float64"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"const = Pmf.from_seq([1])\n",
"d4.add_dist(const)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But `add_dist` also handles constants as a special case:"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"5 0.25\n",
"dtype: float64"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.add_dist(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Other arithmetic operations are also implemented"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" -3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" -2 | \n",
" 0.083333 | \n",
"
\n",
" \n",
" -1 | \n",
" 0.125000 | \n",
"
\n",
" \n",
" 0 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.125000 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.083333 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"-3 0.041667\n",
"-2 0.083333\n",
"-1 0.125000\n",
" 0 0.166667\n",
" 1 0.166667\n",
" 2 0.166667\n",
" 3 0.125000\n",
" 4 0.083333\n",
" 5 0.041667\n",
"dtype: float64"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.sub_dist(d4)"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.1875 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 8 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 9 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 12 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 16 | \n",
" 0.0625 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.0625\n",
"2 0.1250\n",
"3 0.1250\n",
"4 0.1875\n",
"6 0.1250\n",
"8 0.1250\n",
"9 0.0625\n",
"12 0.1250\n",
"16 0.0625\n",
"dtype: float64"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.mul_dist(d4)"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 0.250000 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 0.333333 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 0.500000 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 0.666667 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 0.750000 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 1.000000 | \n",
" 0.2500 | \n",
"
\n",
" \n",
" 1.333333 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 1.500000 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 2.000000 | \n",
" 0.1250 | \n",
"
\n",
" \n",
" 3.000000 | \n",
" 0.0625 | \n",
"
\n",
" \n",
" 4.000000 | \n",
" 0.0625 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"0.250000 0.0625\n",
"0.333333 0.0625\n",
"0.500000 0.1250\n",
"0.666667 0.0625\n",
"0.750000 0.0625\n",
"1.000000 0.2500\n",
"1.333333 0.0625\n",
"1.500000 0.0625\n",
"2.000000 0.1250\n",
"3.000000 0.0625\n",
"4.000000 0.0625\n",
"dtype: float64"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.div_dist(d4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Comparison operators\n",
"\n",
"`Pmf` implements comparison operators that return probabilities.\n",
"\n",
"You can compare a `Pmf` to a scalar:"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.3333333333333333"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.lt_dist(3)"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.75"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.ge_dist(2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Or compare `Pmf` objects:"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.25"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.gt_dist(d6)"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.41666666666666663"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6.le_dist(d4)"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.16666666666666666"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4.eq_dist(d6)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Interestingly, this way of comparing distributions is [nontransitive]()."
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {},
"outputs": [],
"source": [
"A = Pmf.from_seq([2, 2, 4, 4, 9, 9])\n",
"B = Pmf.from_seq([1, 1, 6, 6, 8, 8])\n",
"C = Pmf.from_seq([3, 3, 5, 5, 7, 7])"
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.5555555555555556"
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"A.gt_dist(B)"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.5555555555555556"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"B.gt_dist(C)"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.5555555555555556"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"C.gt_dist(A)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Joint distributions\n",
"\n",
"For comments or questions about this section, see [this issue](https://github.com/AllenDowney/EmpyricalDistributions/issues/10).\n",
"\n",
"`Pmf.make_joint` takes two `Pmf` objects and makes their joint distribution, assuming independence."
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def make_joint(self, other, **options):\n",
" \"\"\"Make joint distribution (assuming independence).\n",
"\n",
" Args:\n",
" other: Pmf\n",
" options: passed to Pmf constructor\n",
"\n",
" Returns: new Pmf\n",
" \"\"\"\n",
" qs = pd.MultiIndex.from_product([self.qs, other.qs])\n",
" ps = np.multiply.outer(self.ps, other.ps).flatten()\n",
" return Pmf(ps, index=qs, **options)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.make_joint)"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.25\n",
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"Name: , dtype: float64"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d4 = Pmf.from_seq(range(1,5))\n",
"d4"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"Name: , dtype: float64"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d6 = Pmf.from_seq(range(1,7))\n",
"d6"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.041667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 1 0.041667\n",
" 2 0.041667\n",
" 3 0.041667\n",
" 4 0.041667\n",
" 5 0.041667\n",
" 6 0.041667\n",
"2 1 0.041667\n",
" 2 0.041667\n",
" 3 0.041667\n",
" 4 0.041667\n",
" 5 0.041667\n",
" 6 0.041667\n",
"3 1 0.041667\n",
" 2 0.041667\n",
" 3 0.041667\n",
" 4 0.041667\n",
" 5 0.041667\n",
" 6 0.041667\n",
"4 1 0.041667\n",
" 2 0.041667\n",
" 3 0.041667\n",
" 4 0.041667\n",
" 5 0.041667\n",
" 6 0.041667\n",
"dtype: float64"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint = Pmf.make_joint(d4, d6)\n",
"joint"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The result is a `Pmf` object that uses a MultiIndex to represent the values."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"MultiIndex([(1, 1),\n",
" (1, 2),\n",
" (1, 3),\n",
" (1, 4),\n",
" (1, 5),\n",
" (1, 6),\n",
" (2, 1),\n",
" (2, 2),\n",
" (2, 3),\n",
" (2, 4),\n",
" (2, 5),\n",
" (2, 6),\n",
" (3, 1),\n",
" (3, 2),\n",
" (3, 3),\n",
" (3, 4),\n",
" (3, 5),\n",
" (3, 6),\n",
" (4, 1),\n",
" (4, 2),\n",
" (4, 3),\n",
" (4, 4),\n",
" (4, 5),\n",
" (4, 6)],\n",
" )"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.index"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you ask for the `qs`, you get an array of pairs:"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 1), (2, 2),\n",
" (2, 3), (2, 4), (2, 5), (2, 6), (3, 1), (3, 2), (3, 3), (3, 4),\n",
" (3, 5), (3, 6), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6)],\n",
" dtype=object)"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.qs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can select elements using tuples:"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.041666666666666664"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint[1,1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can get unnnormalized conditional distributions by selecting on different axes:"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.041667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.041667\n",
"2 0.041667\n",
"3 0.041667\n",
"4 0.041667\n",
"5 0.041667\n",
"6 0.041667\n",
"dtype: float64"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Pmf(joint[1])"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.041667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.041667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.041667\n",
"2 0.041667\n",
"3 0.041667\n",
"4 0.041667\n",
"dtype: float64"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Pmf(joint.loc[:, 1])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But `Pmf` also provides `conditional(i, val)` which returns the conditional distribution where the value on level `i` is `val`."
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def conditional(self, i, val, name=None):\n",
" \"\"\"Gets the conditional distribution of the indicated variable.\n",
"\n",
" Args:\n",
" i: index of the variable we're conditioning on\n",
" val: the value the ith variable has to have\n",
" name: string\n",
"\n",
" Returns: Pmf\n",
" \"\"\"\n",
" pmf = Pmf(self.xs(key=val, level=i), copy=True, name=name)\n",
" pmf.normalize()\n",
" return pmf\n",
"\n"
]
}
],
"source": [
"psource(joint.conditional)"
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"dtype: float64"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.conditional(0, 1)"
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.25\n",
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"dtype: float64"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.conditional(1, 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It also provides `marginal(i)`, which returns the marginal distribution along axis `i`"
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" def marginal(self, i, name=None):\n",
" \"\"\"Gets the marginal distribution of the indicated variable.\n",
"\n",
" Args:\n",
" i: index of the variable we want\n",
" name: string\n",
"\n",
" Returns: Pmf\n",
" \"\"\"\n",
" # The following is deprecated now\n",
" # return Pmf(self.sum(level=i))\n",
"\n",
" # here's the new version\n",
" return Pmf(self.groupby(level=i).sum(), name=name)\n",
"\n"
]
}
],
"source": [
"psource(Pmf.marginal)"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.25 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.25 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.25\n",
"2 0.25\n",
"3 0.25\n",
"4 0.25\n",
"dtype: float64"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.marginal(0)"
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" probs | \n",
"
\n",
" \n",
" \n",
" \n",
" 1 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 2 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 3 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 4 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 5 | \n",
" 0.166667 | \n",
"
\n",
" \n",
" 6 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
"1 0.166667\n",
"2 0.166667\n",
"3 0.166667\n",
"4 0.166667\n",
"5 0.166667\n",
"6 0.166667\n",
"dtype: float64"
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"joint.marginal(1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some ways of iterating through a joint distribution."
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(1, 1)\n",
"(1, 2)\n",
"(1, 3)\n",
"(1, 4)\n",
"(1, 5)\n",
"(1, 6)\n",
"(2, 1)\n",
"(2, 2)\n",
"(2, 3)\n",
"(2, 4)\n",
"(2, 5)\n",
"(2, 6)\n",
"(3, 1)\n",
"(3, 2)\n",
"(3, 3)\n",
"(3, 4)\n",
"(3, 5)\n",
"(3, 6)\n",
"(4, 1)\n",
"(4, 2)\n",
"(4, 3)\n",
"(4, 4)\n",
"(4, 5)\n",
"(4, 6)\n"
]
}
],
"source": [
"for q in joint.qs:\n",
" print(q)"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n",
"0.041666666666666664\n"
]
}
],
"source": [
"for p in joint.ps:\n",
" print(p)"
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(1, 1) 0.041666666666666664\n",
"(1, 2) 0.041666666666666664\n",
"(1, 3) 0.041666666666666664\n",
"(1, 4) 0.041666666666666664\n",
"(1, 5) 0.041666666666666664\n",
"(1, 6) 0.041666666666666664\n",
"(2, 1) 0.041666666666666664\n",
"(2, 2) 0.041666666666666664\n",
"(2, 3) 0.041666666666666664\n",
"(2, 4) 0.041666666666666664\n",
"(2, 5) 0.041666666666666664\n",
"(2, 6) 0.041666666666666664\n",
"(3, 1) 0.041666666666666664\n",
"(3, 2) 0.041666666666666664\n",
"(3, 3) 0.041666666666666664\n",
"(3, 4) 0.041666666666666664\n",
"(3, 5) 0.041666666666666664\n",
"(3, 6) 0.041666666666666664\n",
"(4, 1) 0.041666666666666664\n",
"(4, 2) 0.041666666666666664\n",
"(4, 3) 0.041666666666666664\n",
"(4, 4) 0.041666666666666664\n",
"(4, 5) 0.041666666666666664\n",
"(4, 6) 0.041666666666666664\n"
]
}
],
"source": [
"for q, p in joint.items():\n",
" print(q, p)"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 1 0.041666666666666664\n",
"1 2 0.041666666666666664\n",
"1 3 0.041666666666666664\n",
"1 4 0.041666666666666664\n",
"1 5 0.041666666666666664\n",
"1 6 0.041666666666666664\n",
"2 1 0.041666666666666664\n",
"2 2 0.041666666666666664\n",
"2 3 0.041666666666666664\n",
"2 4 0.041666666666666664\n",
"2 5 0.041666666666666664\n",
"2 6 0.041666666666666664\n",
"3 1 0.041666666666666664\n",
"3 2 0.041666666666666664\n",
"3 3 0.041666666666666664\n",
"3 4 0.041666666666666664\n",
"3 5 0.041666666666666664\n",
"3 6 0.041666666666666664\n",
"4 1 0.041666666666666664\n",
"4 2 0.041666666666666664\n",
"4 3 0.041666666666666664\n",
"4 4 0.041666666666666664\n",
"4 5 0.041666666666666664\n",
"4 6 0.041666666666666664\n"
]
}
],
"source": [
"for (q1, q2), p in joint.items():\n",
" print(q1, q2, p)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Copyright 2019 Allen Downey\n",
"\n",
"BSD 3-clause license: https://opensource.org/licenses/BSD-3-Clause"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}