{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Module 1 - Uncertainty and Entropy I\n",
    "\n",
    "Author: Julio Correa, 2020; based on the original Matlab tutorials.<br/>\n",
    "Adaptations by: J. Lizier, 2023-\n",
    "\n",
    "The following block aims to import all the relevant libraries to analyse data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. Coding Shannon information content\n",
    "\n",
    "During our Introduction to Information Theory block, we will alter several Python functions in order to compute information-theoretic quantities.\n",
    "\n",
    "Our first activity with these templates is to implement the Shannon information content:\n",
    "\n",
    "$h\\left(x\\right)=\\log_2{\\left( \\frac{1}{p(x)} \\right)}=-\\log_2 {p(x)}$\n",
    "\n",
    "1. Edit the Python function <code>infocontent(p)</code> below to return the Shannon information content for an outcome $x$ with probability $p(x)$. Make sure that you use the function <code>np.log2()</code> rather than <code>np.log()</code> to get your answers in bits rather than nats.\n",
    "    1. the value that we want is <code>-np.log2(p)</code>\n",
    "    1. and we can assign this to be returned from the function by calling this from the return line: <code>return -np.log2(p)</code>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function infocontent(p)\n",
    "Computes the Shannon information content for an outcome x of a random variable\n",
    "X with probability p.\n",
    "\n",
    "Inputs:\n",
    "- p - probability to compute the Shannon info content for\n",
    "\n",
    "Outputs:\n",
    "- result - Shannon info content of the probability p\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "\n",
    "def infocontent(p):\n",
    "    \n",
    "    # Alter the equation below to provide the correct Shannon information \n",
    "    # content:\n",
    "\n",
    "    return -np.log2(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. To evaluate a function in Python, we type it's name with an appropriate argument supplied in brackets. For example, to evaluate the Shannon information content with our function for an outcome with had probability 0.2, you would call:\n",
    "<code>infocontent(0.2)</code>\n",
    "If you want to see the output printed to the screen, then enclose this in a <code>print</code> function:\n",
    "<code>print(infocontent(0.2))</code>\n",
    "\n",
    "    Compute the following using your function:\n",
    "    - h(heads) for a fair coin?\n",
    "    - h(1) for a 6-sided die? h(not 1) for a 6-sided die?\n",
    "    - h(1) for a 20-sided die? h(not 1) for a 20-sided die?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0\n",
      "2.584962500721156\n",
      "0.2630344058337938\n",
      "4.321928094887363\n",
      "0.07400058144377693\n"
     ]
    }
   ],
   "source": [
    "# h(heads) for a fair coin?\n",
    "print(infocontent(0.5))\n",
    "# h(1) for a 6-sided die?\n",
    "print(infocontent(1/6))\n",
    "# h(not 1) for a 6-sided die?\n",
    "print(infocontent(5/6))\n",
    "# h(1) for a 20-sided die?\n",
    "print(infocontent(1/20))\n",
    "# h(not 1) for a 20-sided die?\n",
    "print(infocontent(19/20))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. Reproduce the plot below of $h(x)$ versus $p(x)$ using the matlibplot <code>plot()</code> function.\n",
    "\n",
    "    Hints:\n",
    "    1. Input <code>p</code> to <code>infocontent(p)</code> as a vector across the range <code>p = np.arange(0.01,1.001,0.01)</code>.\n",
    "    2. Make an inline plot with matplotlib by calling <code>plt.plot(x, y)</code>, with <code>p</code> and <code>infocontent(p)</code>"
   ]
  },
  {
   "attachments": {
    "image.png": {
     "image/png": "[encoded data, 166272 characters]"
    }
   },
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"attachment:image.png\" width=\"400\"/>\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 20556 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Define the array for p\n",
    "p = np.arange(0.01,1.001,0.01);\n",
    "# Compute the infocontent() of the array p\n",
    "# Make the plot and don't forget to label axes\n",
    "plt.plot(p, infocontent(p))\n",
    "plt.xlabel('p(x)');\n",
    "plt.ylabel('h(x)');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. Have a look at the characters that we could play Guess Who? with on the [Kooky characters sheet](https://web.archive.org/web/20170215034006/http://www.hasbro.com/upload/guesswho/GWc_Kooky-en_GB.pdf). Assuming that your partner selects one of these characters at random, compute the probability and then the Shannon information content of their character:\n",
    "    1. being Jason?\n",
    "    2. having one eye?\n",
    "    3. having more than one eye?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4.584962500721156\n",
      "2.263034405833794\n",
      "0.33703498727757075\n"
     ]
    }
   ],
   "source": [
    "# h(Jason)?\n",
    "print(infocontent(1/24))\n",
    "# h(one eye)?\n",
    "print(infocontent(5/24))\n",
    "# h(more than one eye)?\n",
    "print(infocontent(19/24))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5. Based on those answers, reflect on the following:\n",
    "    1. Would a good first question be \"does your character have one eye?\" ? Why / why not?\n",
    "    2. Would a good first question be \"are you Jason?\" ? Why / why not?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Coding Shannon entropy\n",
    "\n",
    "In this exercise we continue to generate Python code to measure the Shannon entropy for a distribution $p(x)$:\n",
    "\n",
    "$H(X)=-\\sum_xp\\left(x\\right)\\log_2p\\left(x\\right)$\n",
    "\n",
    "Your task is to edit the Python function <code>entropy(p)</code> in the next cell to return the Shannon entropy for the given distribution $p(x)$ over outcomes $x$ of $X$.\n",
    "\n",
    "Note the input argument to the function is a vector <code>p</code>, representing the probability mass for each outcome of $x$. That is, <code>p</code> is a vector with the $n$th entry in the vector giving the probability for the $n$th value that $x$ may take. The sum of the items in the vector <code>p</code> must be 1.\n",
    "\n",
    "For example, for a binary $x$ we could have <b>p = np.array([0.25, 0.75])</b> where $p(x=0) = 0.25$ and $p(x=1) = 0.75$.\n",
    "\n",
    " - If we knew x was a binary variable, and we only took one argument, $p = p(x=1)$, how could you write one line of code to compute H(X) from p? (_Hint_: what would $\\log_2(p(x=1))$ be as a function of $p$? What would $\\log_2(p(x=0))$ be as a function of $p$? Can you combine these to give $H(X)$ ?) \n",
    "\n",
    "Let's assume that we don't know how many values $x$ could take, and write the code for an arbitrary length vector $p$.\n",
    "\n",
    "1. Can you think of two ways to write the code to sum up the contribution for each item $p(x)$ in the vector $x$, being:\n",
    "    1. for loop over the items of p, or<br>\n",
    "    1. the sum of a vector multiplication or dot product in Python?\n",
    "\n",
    "    Implement one of these in <code>entropy(p)</code>. (Usually the latter is faster)\n",
    "\n",
    "2. Think of possible error conditions here, and how you can handle these in your code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function entropy(p)\n",
    "Computes the Shannon entropy for a probability distribution p.\n",
    "\n",
    "Inputs:\n",
    "- p - (numpy array or list which much sum to 1) - a probability distribution to compute the Shannon info content for\n",
    "\n",
    "Outputs:\n",
    "- result - Shannon entropy of the probability distribution p\n",
    " \n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def entropy(p):  \n",
    "    # First make sure the array is now a numpy array\n",
    "    if type(p) != np.array:\n",
    "        p = np.array(p)\n",
    "\n",
    "    # Should we check any potential error conditions on the input?\n",
    "    if (abs(np.sum(p) - 1) > 0.00001):\n",
    "        raise Exception(\"Probability distribution must sum to 1: sum is %.4f\" % np.sum(p))\n",
    "    if (np.any(p > 1)):\n",
    "        raise Exception(\"Probability distribution must have all entries <= 1\")\n",
    "    if (np.any(p < 0)):\n",
    "        raise Exception(\"Probability distribution must have all entries >= 0\")\n",
    "    \n",
    "    # We need to take the expectation value over the Shannon info content at\n",
    "    # p(x) for each outcome x:\n",
    "    weightedShannonInfos = p*(infocontent(p))\n",
    "    # nansum ignores the nans from calling infocontent(0), but we still get the warning if an entry in p is zero\n",
    "    return np.nansum(weightedShannonInfos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. Write down the answer you expect, and test that your code gives answers you expect for:\n",
    "    1. <code>entropy([0.5, 0.5])</code>\n",
    "    2. <code>entropy([0.25, 0.25, 0.25, 0.25])</code>\n",
    "    3. <code>entropy([1, 0])</code>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0\n",
      "2.0\n",
      "0.0\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_232971/3406804068.py:20: RuntimeWarning: divide by zero encountered in log2\n",
      "  return -np.log2(p)\n",
      "/tmp/ipykernel_232971/253220426.py:28: RuntimeWarning: invalid value encountered in multiply\n",
      "  weightedShannonInfos = p*(infocontent(p))\n"
     ]
    }
   ],
   "source": [
    "# entropy([0.5, 0.5]) ?\n",
    "print(entropy([0.5, 0.5]))\n",
    "# entropy([0.25, 0.25, 0.25, 0.25]) ?\n",
    "print(entropy([0.25, 0.25, 0.25, 0.25]))\n",
    "# entropy([1, 0]) ?\n",
    "print(entropy([1, 0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. _Challenge_: Plot $H(X)$ as a function of $p(x=1)$ for binary $X$. (See the plot we expect on the figure below). This will involve a loop over values of $p = p(x=1)$ to call the <code>entropy(p)</code> function with a vector corresponding to each $\\{p(x=1), p(x=0)\\}$ pair."
   ]
  },
  {
   "attachments": {
    "Captura%20de%20Pantalla%202021-01-25%20a%20la%28s%29%2017.16.58.png": {
     "image/png": "[encoded data, 242260 characters]"
    }
   },
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"attachment:Captura%20de%20Pantalla%202021-01-25%20a%20la%28s%29%2017.16.58.png\" width=\"400\"/>\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_232971/3406804068.py:20: RuntimeWarning: divide by zero encountered in log2\n",
      "  return -np.log2(p)\n",
      "/tmp/ipykernel_232971/253220426.py:28: RuntimeWarning: invalid value encountered in multiply\n",
      "  weightedShannonInfos = p*(infocontent(p))\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 28476 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Add your code to plot H(X) vs p(x=1) below\n",
    "pInterval = 0.001;\n",
    "numPs = (int) (1/0.001)+1;\n",
    "p = np.linspace(0,1,numPs)\n",
    "H = np.zeros(numPs);\n",
    "\n",
    "for ix in np.arange(0,numPs):\n",
    "    H[ix] = entropy(np.array([p[ix], 1-p[ix]]))\n",
    "\n",
    "plt.plot(p,H)\n",
    "plt.xlabel('p(X=1)');\n",
    "plt.ylabel('H(X)');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5. Coming back to the characters that we could play Guess Who? with on the [Kooky characters sheet](https://web.archive.org/web/20170215034006/http://www.hasbro.com/upload/guesswho/GWc_Kooky-en_GB.pdf), validate that (using your <code>entropy</code> function):\n",
    "    1. $H(who) = 4.585$ bits  (entropy of the character's identity)\n",
    "    1. $H(one\\ eye?) = 0.738$ bits  (entropy of whether the character has one eye or more than one eye)\n",
    "    1. $H(Jason) = 0.2499$ bits  (entropy of whether the character is Jason or not)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4.584962500721156\n",
      "0.7382848661434506\n",
      "0.24988229283318544\n"
     ]
    }
   ],
   "source": [
    "# Add your code here to validate the entropies as above:\n",
    "print(entropy(np.ones(24)*1/24))\n",
    "print(entropy([5/24, 19/24]))\n",
    "print(entropy([1/24, 23/24]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6. Based on those answers and in comparison to your responses on the earlier exercise, reflect on the following:\n",
    "    1. Would a good first question be _\"does your character have one eye?\"_ ? Why / why not?\n",
    "    1. Would a good first question be _\"are you Jason?\"_ ? Why / why not?\n",
    "    1. What is the best question to ask first that you can think of, and why? (as an optional tangent, you could watch [a video](https://youtu.be/FRlbNOno5VA) which goes into some detail about what the best questions might be -- again, think about the information-theoretic view on what is being said there)"
   ]
  }
 ],
 "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
