{ "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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
11
21
31
41
51
61
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
a1
b2
c3
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.25
20.25
30.25
40.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.25
20.25
30.25
40.25
\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/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qgMHDriNpaena+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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
a0.2
e0.2
l0.4
n0.2
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.2
20.4
30.2
50.2
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
70.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
20.027778
30.055556
40.083333
50.111111
60.138889
70.166667
80.138889
90.111111
100.083333
110.055556
120.027778
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
20.25
30.25
40.25
50.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
20.25
30.25
40.25
50.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
-30.041667
-20.083333
-10.125000
00.166667
10.166667
20.166667
30.125000
40.083333
50.041667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.0625
20.1250
30.1250
40.1875
60.1250
80.1250
90.0625
120.1250
160.0625
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
0.2500000.0625
0.3333330.0625
0.5000000.1250
0.6666670.0625
0.7500000.0625
1.0000000.2500
1.3333330.0625
1.5000000.0625
2.0000000.1250
3.0000000.0625
4.0000000.0625
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.25
20.25
30.25
40.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
110.041667
20.041667
30.041667
40.041667
50.041667
60.041667
210.041667
20.041667
30.041667
40.041667
50.041667
60.041667
310.041667
20.041667
30.041667
40.041667
50.041667
60.041667
410.041667
20.041667
30.041667
40.041667
50.041667
60.041667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.041667
20.041667
30.041667
40.041667
50.041667
60.041667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.041667
20.041667
30.041667
40.041667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.25
20.25
30.25
40.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.25
20.25
30.25
40.25
\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
probs
10.166667
20.166667
30.166667
40.166667
50.166667
60.166667
\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 }