{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8cb48980-a226-4c6e-b984-bc604e42b9d6",
   "metadata": {},
   "source": [
    "# Scissors Paper Rock data analysis (modules 3 and 4)\n",
    "\n",
    "_Copyright © 2020-, Julio Correa, Joseph T. Lizier; based on the original Matlab tutorials. Distributed under GNU General Public License v3._<br/>\n",
    "With thanks to Simon DeDeo for the idea of using scissors-paper-rock gameplay to introduce information-theoretic concepts!\n",
    "\n",
    "We will analyse the uncertainties and information contents of some sample Scissors-Paper-Rock gameplay.\n",
    "\n",
    "## Initial Questions:\n",
    "\n",
    "* Why are we interested in using measures of information theory to analyse this data set?\n",
    "* What in particular might we wish to measure?\n",
    "* _Information theory is all about questions and answers_. What questions might we ask of the data? What hypotheses might we have about the answers?\n",
    "\n",
    "# Stage 1 - Familiarisation\n",
    "\n",
    "We've done a lot of the data plumbing for you, so that we can concentrate on computing the information-theoretic quantities. Data plumbing is an important part of any analysis though, so do take a look in more detail at how the code was set up at some point.\n",
    "\n",
    "Note that these utilies require the additional libraries: `pandas` and `re`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2610da08-db9c-457b-9a9f-f8074b2ad73b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import the Scissors-Paper-Rock utilies\n",
    "import sprutils\n",
    "# Other libraries we require here:\n",
    "import numpy as np\n",
    "from scipy import stats\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7058b533-d14e-47aa-ad3a-b67cd3cc708d",
   "metadata": {},
   "source": [
    "For now though, let's get things working and start to explore the data set.\n",
    "\n",
    "1. Please download a copy of the *data set* (following instructions on the Module 3 page). Unzip them to any convenient location on your computer.\n",
    "2. Open the folder where the gameplay data set is stored. Open any file in a text editor, which includes the data for a game between two named players. The file contains each iteration of the game on one line, with $\\{0,1,2\\}$ encoding the player's selections amongst _{scissors,paper,rock}_.\n",
    "3. Set the appropriate paths in the code below for the following:<br/>\n",
    "    For the `simpleinfotheory` scripts, make sure you haved gathered the new functions you wrote into your `simpleinfotheory.py` script, and make sure it is referencable from here (you may need to change the folder referenced below) before you run the import line in the next cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72892341-0ba7-40d6-ab01-5c78ff28b616",
   "metadata": {},
   "outputs": [],
   "source": [
    "# CHANGE THIS to where your Scissors-Paper-Rock data files are stored:\n",
    "sprutils.setDataPath(\"../ScissorsPaperRock-SampleData\")\n",
    "\n",
    "# CHANGE THIS path to where your Matlab entropy scripts are from the previous modules\n",
    "# (if you are confident that they are working, or else the completed code solutions).\n",
    "import sys\n",
    "sys.path.append('../../Module01-Entropy/PythonSimpleFunctions/completed/')\n",
    "import simpleinfotheory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81f02d86-de95-4ff2-b62e-d03b8dfcd0de",
   "metadata": {},
   "source": [
    "4. Run `sprutils.listPlayers(True)` below to print and also return a list of which player names you can analyse."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28374c27-ac9b-461b-a594-b34fdca10a89",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "36a3c6fd-c6ab-4e80-ab7b-eee03996bf62",
   "metadata": {},
   "source": [
    "&nbsp;&nbsp;&nbsp;&nbsp; You can run `players = sprutils.listPlayers()` and then access each player name after that function call via `players[0]`, `players[1]`  etc up to `players[len(players)-1]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8594bdcd-9b13-4a29-aca7-c8fb45fd6753",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "09d63832-bd43-44b9-a97a-4b637006f2b6",
   "metadata": {},
   "source": [
    "5. Run `sprutils.loadGamesForPlayer(name, True)`, where `name` is the name string for any player (e.g. `'Joe'`), to display the games (including moves and results) for that player. Note: You can call `sprutils.loadGamesForPlayer('*', True)`, i.e. with name `'*'`, to get the data for all players.<br/>\n",
    "    The function can be called as `games = sprutils.loadGamesForPlayer(name)` to return a list of the data for each game for that player, which will be used in our information-theoretic analysis later. Each item in the list, e.g. `games[i]`, is a 2D numpy array for the given game index, where:\n",
    "    * the first column `games[i][:,0]` is the moves of the named player ({0,1,2} for {scissors,paper,rock}),\n",
    "    * the second column `games[i][:,1]` is the moves of their opponent, and\n",
    "    * the third column `games[i][:,2]` is the result for this player ({1,0,-1} for {win,tie,loss})."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e05c581-9377-441e-bdb4-b5b47053825c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "6d82034c-3b86-4d54-835b-63d20bfb0f76",
   "metadata": {},
   "source": [
    "# Stage 2 - Entropy calculations\n",
    "\n",
    "We will analyse the uncertainty in various player's moves using Shannon entropy, and consider whether this relates to their performance in the game. (Do you have a hypothesis on this?)\n",
    "\n",
    "1. See the function `computeEntropyForPlayer(name)` below. This aims to compute the entropy of moves for a given named player, over all the iterations in all of their games. The code retrieves the data for each game of this player using `loadGamesForPlayer(name)`, then loops over each game. Fill out the missing parts of code:\n",
    "    * In the loop, pull out the moves for that player (and their results), and append them into the arrays used to store these values over all iterations. A helpful hint is that if you had a 2D matrix data, and you wanted to pull out the first column of its contents, you would do this as: `data[:,0]` (but an extra reshape is need to keep it as a column).\n",
    "    * Compute the entropy over the players' moves, using our `simpleinfotheory.entropyempirical()` function.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ce7fd29-0a29-4e85-8598-c8b5d6ddec5a",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeEntropyForPlayer()\n",
    "\n",
    "Compute the entropy of moves for a given player, across all games/iterations\n",
    "\n",
    "Inputs:\n",
    "- name - name of the player\n",
    "- verbose - whether to print entropy out\n",
    "\n",
    "Outputs:\n",
    "- calculatedEntropy\n",
    "- winRate\n",
    "- lossRate\n",
    "- numGames\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeEntropyForPlayer(name: str, verbose: bool=False):\n",
    "    \n",
    "    # Step 1: load all of the player's games' data:\n",
    "    games = sprutils.loadGamesForPlayer(name)\n",
    "    \n",
    "    # Step 2: the player's moves are in the first column, pull these from\n",
    "    #  each game into an array of samples that we can compute entropy on:\n",
    "    moves = np.empty((0,1)) # empty column\n",
    "    results = np.empty((0,1)) # empty column\n",
    "    for gm in games:\n",
    "        # First column of numpy array gm is the player's move, second is opponent's\n",
    "        #  and third is the result.\n",
    "        # Pull out the player's moves in this game (first column of gm):\n",
    "        #  (reshape is required to keep it as a column rather than row vector)\n",
    "        movesInThisGame = ???.reshape(gm.shape[0],1)\n",
    "        # Pull out the results in this game (third column of gm) as a column:\n",
    "        resultsInThisGame = ???.reshape(gm.shape[0],1)\n",
    "        # Append this player's moves to the array we're storing over all iterations:\n",
    "        moves = np.row_stack((moves, movesInThisGame)) if moves.size else movesInThisGame\n",
    "        # Append this player's results to the array over all iterations:\n",
    "        results = np.row_stack((results, resultsInThisGame)) if results.size else resultsInThisGame\n",
    "    \n",
    "    # Step 3: compute the entropy for this player's moves using our existing scripts:\n",
    "    # (Don't forget that if your entropy script is returning a tuple, you just want the [0] entry)\n",
    "    calculatedEntropy = ???\n",
    "\n",
    "    # Step 4: compute the win and loss rates:\n",
    "    winRate = np.sum(results == 1)/len(results)\n",
    "    lossRate = np.sum(results == -1)/len(results)\n",
    "    numGames = len(results)\n",
    "\n",
    "    if (verbose):\n",
    "        print('Entropy for %s over %d iterations: %.04f' % (name, numGames, calculatedEntropy))\n",
    "\n",
    "    return calculatedEntropy, winRate, lossRate, numGames"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "327ad1b5-d51c-4373-a775-967ac36cde17",
   "metadata": {},
   "source": [
    "2. Call the script for a few different players, e.g. `computeEntropyForPlayer('Joe')`, and compare."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67d3d38c-cd53-490c-a7df-7c138de04a1f",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "9081c988-b1b3-4164-8f4b-80fb2bc5a409",
   "metadata": {},
   "source": [
    "3. Now call it using all players' data at once, in a single calculation: `computeEntropyForPlayer('*')`. What implicit assumption(s) are we making when we analyse the data in this way? What question is it asking of the data?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2e1e64d-64e8-484c-b704-e9f750f362a9",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "d4ad5c13-ba52-4fe9-83ff-1eded1c2f081",
   "metadata": {},
   "source": [
    "4. See the function `computeEntropyForAllPlayers()` below. This aims to compute entropy of moves for each player in turn (considering each player separately), then plots these, and looks for relationships between entropy and win/loss rates. Fill out the missing parts of code:\n",
    "    * In the loop over player names, use our previous function `computeEntropyForPlayer` to compute the entropy for that player.\n",
    "    * Once we have the entropy for each player and their win / loss ratios, compute the correlation between entropy and win ratio, and entropy and loss ratio. _HINT_: Use the `stats.pearsonr()` or `stats.spearmanr()` correlation functions from the `scipy.stats` library -- make sure that you check what is returned from this function call (earlier version return a tuple, later version return an object)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e84ac281-ec3a-4de7-8f40-8151e65d35d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeEntropyForAllPlayers()\n",
    "\n",
    "Compute the entropy of moves for each player, across all games/iterations\n",
    "\n",
    "Outputs:\n",
    "- names\n",
    "- entropies\n",
    "- winRates\n",
    "- lossRates\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeEntropyForAllPlayers():\n",
    "    \n",
    "    # Step 1: load all of the player's names:\n",
    "    names = sprutils.listPlayers()\n",
    "    # Step 2: compute entropy for each player\n",
    "    index = 0\n",
    "    entropies = np.zeros(len(names))\n",
    "    winRates = np.zeros(len(names))\n",
    "    lossRates = np.zeros(len(names))\n",
    "    \n",
    "    for name in names:\n",
    "        # Compute the entropy for the moves of this player.\n",
    "        # HINT: use the script that you just completed passing in name\n",
    "        calculatedEntropy, winRate, lossRate, numGames = ???\n",
    "        print('{} = {:.03f} bits,\\twin rate = {:.03f},\\tloss rate = {:.03f}, num games = {}'.\\\n",
    "              format(name, calculatedEntropy, winRate, lossRate, numGames))\n",
    "        \n",
    "        entropies[index] = calculatedEntropy\n",
    "        winRates[index] = winRate\n",
    "        lossRates[index] = lossRate\n",
    "\n",
    "        index += 1\n",
    "    \n",
    "    # Plot the winRates and lossRates versus entropies:\n",
    "    plt.figure();\n",
    "    plt.scatter(entropies, winRates, c='red', marker='x');\n",
    "    plt.title('Win rates versus entropies of single players')\n",
    "    plt.xlabel('Entropy of moves (bits)')\n",
    "    plt.ylabel('Win rate')\n",
    "    \n",
    "    plt.figure();\n",
    "    plt.scatter(entropies, lossRates, c='red', marker='x');\n",
    "    plt.title('Loss rates versus entropies of single players')\n",
    "    plt.xlabel('Entropy of moves (bits)')\n",
    "    plt.ylabel('Loss rate')\n",
    "\n",
    "    # Compute correlations of entropy to win rate and to loss rate:\n",
    "    winToEntropyCorr = ???\n",
    "    lossToEntropyCorr = ???\n",
    "    print('Correlation of win  rate to entropy is: {:.04f}'.\\\n",
    "        format(winToEntropyCorr))\n",
    "    print('Correlation of loss rate to entropy is: {:.04f}'.\\\n",
    "        format(lossToEntropyCorr))\n",
    "    # Are these statistically significant?\n",
    "    # Can you adjust your code to check for that?\n",
    "\t# HINT: look at other return values from the correlation functions\n",
    "\n",
    "    return names, entropies, winRates, lossRates"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b5ba6f7-48aa-4479-91f5-71c9d75402ce",
   "metadata": {},
   "source": [
    "5. Call the function to see the entropies of each player, the plots and correlation analyses on how this related to performance. Whose moves was there most uncertainty about? Did this correlate to wins? What about losses? Does this match your hypothesis?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8387951-f475-48ca-ba13-d873bc009368",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "14fc5575-32be-494c-88a3-2ea2a241fdf8",
   "metadata": {},
   "source": [
    "6. _Challenge_: are these correlation values statistically significant? Look up theory on how to compute whether a correlation value is statistically significant. To add this to the code above, you can check out the other return values from the `stats.pearson()` or `stats.spearmanr()` correlation functions.\n",
    "\n",
    "We will continue to investigate relationships between variables in this data set once we have learned about the mutual information."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f847f202-c739-4437-bb24-033ff58d1e32",
   "metadata": {},
   "source": [
    "# Stage 3 - Conditional entropy calculations\n",
    "\n",
    "Take a moment to reflect on our initial questions, right up the top of this notebook.\n",
    "\n",
    "We will now analyse the conditional uncertainty in the player's moves, given their previous move, and consider whether this relates to their performance in the game. (Do you have a hypothesis on this?).\n",
    "_The coding is very similar to what you already did in stage 2 previously._\n",
    "\n",
    "1. See the function `computeConditionalEntropyForPlayer(name)` below.\n",
    "This aims to compute the entropy of moves for a given named player, conditioned on their previous move, over all the iterations in all of their games. The code retrieves the data for each game of this player using `loadGamesForPlayer(name)`, then loops over each game. Fill out the missing parts of code:\n",
    "    * In the loop, pull out the moves for that player, their previous moves (and the results on the current, not previous, move), and append them into the arrays used to store these values over all iterations. Take care:\n",
    "        * You can only pull out moves which have a paired sample of a previous move in the given game. This means the moves from the 2nd iteration onwards. A helpful hint is that if you had a 2D matrix data, and you wanted to pull out the first column of its contents, but only from the 2nd row onwards, you could first pull out the first column as `myColumn = data[:,0]` and then pull the 2nd row onwards as `myColumn[1:]`. (You could do this in one go as: `data[1:,0]`).\n",
    "        * Similarly, you can only pull out previous moves which have a paired sample of a next move in the given game. This means the moves up to the 2nd last iteration. A helpful hint there is that if you had a 2D matrix data, and you wanted to pull out the first column 1 of its contents, but only up to the 2nd last row, you would first pull out the first column as `myColumn = data[:,0]` and then pull out all rows but the last as `myColumn[:-1]`. (You could do this in one go as: `data[:-1,0]`).\n",
    "        * You should also only pull out samples of results that relate to the current (but not previous) moves.\n",
    "    * Compute the conditional entropy over the players' moves given their previous moves, using your (or my) `simpleinfotheory.conditionalentropyempirical()` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12ecef23-76f6-4416-9dfa-9674e45ec5b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeConditionalEntropyForPlayer()\n",
    "\n",
    "Compute the conditional entropy of moves for a given player, conditioned on\n",
    " their previous move across all games/iterations\n",
    "\n",
    "Inputs:\n",
    "- name - name of the player\n",
    "- verbose - whether to print entropy out\n",
    "\n",
    "Outputs:\n",
    "- calculatedEntropy\n",
    "- winRate\n",
    "- lossRate\n",
    "- numGames\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeConditionalEntropyForPlayer(name: str, verbose: bool=False):\n",
    "\n",
    "    # Step 1: load all of the player's games' data:\n",
    "    games = sprutils.loadGamesForPlayer(name)\n",
    "    \n",
    "    # Step 2: the player's moves are in the first column, pull these from\n",
    "    #  each game into arrays of samples that we can compute conditional entropy on:\n",
    "    nextMoves = np.empty((0,1)) # empty column\n",
    "    previousMoves = np.empty((0,1)) # empty column\n",
    "    results = np.empty((0,1)) # empty column\n",
    "    \n",
    "    for gm in games:\n",
    "        # First column of numpy array gm is the player's move, second is opponent's\n",
    "        #  and third is the result.\n",
    "        # Pull out the player's moves in this game (first column of gm):\n",
    "        #  (reshape is required to keep it as a column rather than row vector)\n",
    "        movesInThisGame = gm[:,0].reshape(gm.shape[0],1)\n",
    "        # Pull out the results in this game (third column of gm) as a column:\n",
    "        resultsInThisGame = gm[:,2].reshape(gm.shape[0],1)\n",
    "        \n",
    "        # Append this player's moves to the array we're storing over all iterations.\n",
    "        #  TAKE CARE: Can we take all samples here, or only a limited number that\n",
    "        #  we're able to match up properly to compute conditional entropy?\n",
    "        nextMoves = np.row_stack((nextMoves, ???)) if nextMoves.size else ???\n",
    "        previousMoves = np.row_stack((previousMoves, ???)) if previousMoves.size else ???\n",
    "        # Append this player's results to the array over all iterations:\n",
    "        #  Which results do we want here -- those of the previous iteration or this one?\n",
    "        results = np.row_stack((results, ???)) if results.size else ???\n",
    "    \n",
    "    # Step 3: compute the condtional entropy for this player's moves using our existing scripts:\n",
    "    calculatedEntropy = ???\n",
    "    \n",
    "    # Step 4: compute the win and loss rates:\n",
    "    winRate = np.sum(results == 1)/len(results)\n",
    "    lossRate = np.sum(results == -1)/len(results)\n",
    "    numGames = len(results)\n",
    "\n",
    "    if verbose:\n",
    "        print('Conditional entropy for {} over {} iterations: {:.04f}'.\\\n",
    "                format(name, numGames, calculatedEntropy))\n",
    "                          \n",
    "    return calculatedEntropy, winRate, lossRate, numGames"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebd57e5f-11f8-47e7-97df-b8b698570f36",
   "metadata": {},
   "source": [
    "2. Call the script for a few different players, e.g. `computeConditionalEntropyForPlayer('Joe')`, and compare."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0175f515-e9e4-4a23-9720-819164641a91",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "f147a17f-f803-43f8-9823-d8fb68511586",
   "metadata": {},
   "source": [
    "3. Now call it to compute the conditional entropy using samples for all players' data in the one calculation: `computeConditionalEntropyForPlayer('*')`. What implicit assumption are we making when we analyse the data in this way?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b131665-f837-4d3f-97be-9a513ebbf09b",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "0d1a270a-1258-498b-ab13-e93e8ecba317",
   "metadata": {},
   "source": [
    "4. See the function `computeConditionalEntropyForAllPlayers()` below.\n",
    "This aims to compute conditional entropy of moves for each player in turn (considering each player separately), then plots these, and looks for relationships between conditional entropy and win/loss rates. Fill out the missing parts of code:\n",
    "    * In the loop over player names, use our previous function `computeConditionalEntropyForPlayer()` to compute the conditional entropy for that player.\n",
    "    * Once we have the conditional entropy for each player and their win / loss ratios, compute the correlation between conditional entropy and win ratio, and entropy and loss ratio. _HINT_: Use the `stats.pearsonr()` or `stats.spearmanr()` correlation functions from the `scipy.stats` library -- make sure that you check what is returned from this function call (earlier version return a tuple, later version return an object)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30c27c2a-a8b2-4808-ae9e-42a2c6e5a621",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeConditionalEntropyForAllPlayers()\n",
    "\n",
    "Compute the conditional entropy of moves for each player, conditioned on their previous move,\n",
    " across all games/iterations\n",
    "\n",
    "Outputs:\n",
    "- names\n",
    "- entropies\n",
    "- winRates\n",
    "- lossRates\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeConditionalEntropyForAllPlayers():\n",
    "\n",
    "    # Step 1: load all of the player's names:\n",
    "    names = sprutils.listPlayers()\n",
    "    # Step 2: compute entropy for each player\n",
    "    index = 0\n",
    "    entropies = np.zeros(len(names))\n",
    "    winRates = np.zeros(len(names))\n",
    "    lossRates = np.zeros(len(names))\n",
    "    \n",
    "    for name in names:\n",
    "        # Compute the entropy for the moves of this player.\n",
    "        # HINT: use the script that you just completed passing in name\n",
    "        calculatedEntropy, winRate, lossRate, numGames = ???\n",
    "        print('{} = {:.03f} bits,\\twin rate = {:.03f},\\tloss rate = {:.03f}, num games = {}'.\\\n",
    "              format(name, calculatedEntropy, winRate, lossRate, numGames))\n",
    "\n",
    "        entropies[index] = calculatedEntropy\n",
    "        winRates[index] = winRate\n",
    "        lossRates[index] = lossRate\n",
    "\n",
    "        index += 1\n",
    "   \n",
    "    # Plot the winRates and lossRates versus entropies:\n",
    "    plt.figure();\n",
    "    plt.scatter(entropies, winRates, c='red', marker='x');\n",
    "    plt.title('Win rates versus cond entropies of single players')\n",
    "    plt.xlabel('Entropy of moves (bits)')\n",
    "    plt.ylabel('Win rate')\n",
    "    \n",
    "    plt.figure();\n",
    "    plt.scatter(entropies, lossRates, c='red', marker='x');\n",
    "    plt.title('Loss rates versus cond entropies of single players')\n",
    "    plt.xlabel('Entropy of moves (bits)')\n",
    "    plt.ylabel('Loss rate')\n",
    "\n",
    "    # Compute correlations of entropy to win rate and to loss rate:\n",
    "    winToEntropyCorr = ???\n",
    "    lossToEntropyCorr = ???\n",
    "    print('Correlation of win  rate to entropy is: {:.04f}'.\\\n",
    "        format(winToEntropyCorr))\n",
    "    print('Correlation of loss rate to entropy is: {:.04f}'.\\\n",
    "        format(lossToEntropyCorr))\n",
    "    # Are these statistically significant?\n",
    "    # Can you adjust your code to check for that?\n",
    "    # HINT: look at other return values from the correlation functions\n",
    "\n",
    "    return names, entropies, winRates, lossRates"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db7d11fd-a292-4b38-8509-d3b274f0b52e",
   "metadata": {},
   "source": [
    "5. Call the function to see the conditional entropies of each player, the plots and correlation analyses on how this related to performance. Whose was most (conditionally) uncertainty? Did this correlate to wins? What about losses? Does this match your hypothesis?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2ee18289-7e48-4a4d-a1b1-d80838b7d0a3",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "ed568f2c-fcaa-40c7-946d-03e392da9786",
   "metadata": {},
   "source": [
    "6. _Challenge_: are these correlation values statistically significant? As per stage 2, look up theory on how to compute whether a correlation value is statistically significant. To add this to the code above, you can check out the other return values from the `stats.pearson()` or `stats.spearmanr()` correlation functions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e908780-5e9a-42ea-b5ad-f37e4effe453",
   "metadata": {},
   "source": [
    "# Stage 4 - Mutual information calculations\n",
    "\n",
    "We will now analyse the mutual information in the player's previous moves to their next move, and consider whether this relates to their performance in the game. Different to the above, here we're going to look at relationships not only to the player's own previous move but also to their opponent's previous move. (Do you have a hypothesis on this?).\n",
    "_The coding is very similar to what you already did in stage 3 above._\n",
    "\n",
    "1. See the function `computeConditionalEntropyForPlayer(name)` below.\n",
    "This aims to compute the mutual information of moves for a given named player to their previous move (or those of their opponent), over all the iterations in all of their games. The code retrieves the data for each game of this player using `loadGamesForPlayer(name)`, then loops over each game. Fill out the missing parts of code:\n",
    "    * In the loop, pull out the moves for that player, their previous moves or that of their opponent (and the results on the current, not previous, move), and append them into the arrays used to store these values over all iterations. Take note of how you performed the similar operations for the conditional entropy.\n",
    "    * Compute the mutual information between the moves and previous moves, using our `simpleinfotheory.mutualinformationempirical()` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32b3f184-a71e-42ea-b248-c13102c553a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeMutualInformationForPlayer()\n",
    "\n",
    "Compute the mutual information of moves for a given player with their own\n",
    " previous move, or the previous move of their opponent\n",
    "\n",
    "Inputs:\n",
    "- name - name of the player\n",
    "- fromSelf (boolean, default True) - if true, take MI from the player's own previous move; if false\n",
    "    take MI from opponent's previous move.\n",
    "- verbose - whether to print entropy out\n",
    "\n",
    "Outputs:\n",
    "- calculatedMI\n",
    "- winRate\n",
    "- lossRate\n",
    "- numGames\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeMutualInformationForPlayer(name: str, fromSelf: bool=True, verbose: bool=False):\n",
    "\n",
    "    # Step 1: load all of the player's games' data:\n",
    "    games = sprutils.loadGamesForPlayer(name)\n",
    "    \n",
    "    # Step 2: the player's moves are in the first column, pull these from\n",
    "    #  each game into arrays of samples that we can compute mutual info on:\n",
    "    nextMoves = np.empty((0,1)) # empty column\n",
    "    previousMoves = np.empty((0,1)) # empty column\n",
    "    results = np.empty((0,1)) # empty column\n",
    "    \n",
    "    for gm in games:\n",
    "        # First column of numpy array gm is the player's move, second is opponent's\n",
    "        #  and third is the result.\n",
    "        # Pull out the player's moves in this game (first column of gm):\n",
    "        #  (reshape is required to keep it as a column rather than row vector)\n",
    "        movesInThisGame = gm[:,0].reshape(gm.shape[0],1)\n",
    "        # Pull out the opponent's moves in this game (second column of gm):\n",
    "        opponentsMovesInThisGame = gm[:,1].reshape(gm.shape[0],1)\n",
    "        # Pull out the results in this game (third column of gm) as a column:\n",
    "        resultsInThisGame = gm[:,2].reshape(gm.shape[0],1)\n",
    "        \n",
    "        # Append this player's moves to the array we're storing over all iterations.\n",
    "        #  TAKE CARE: Can we take all samples here, or only a limited number that\n",
    "        #  we're able to match up properly to compute mutual information?\n",
    "        nextMoves = np.row_stack((nextMoves, ???)) if nextMoves.size else ???\n",
    "        if fromSelf:\n",
    "            # Grab the previous moves from this player:\n",
    "\t\t\t# HINT: This will be the same thing you did in computeConditionalEntropyForPlayer:\n",
    "            previousMoves = np.row_stack((previousMoves, ???)) if previousMoves.size else ???\n",
    "        else:\n",
    "            # Grab the previous moves from their opponent:\n",
    "            previousMoves = np.row_stack((previousMoves, ???)) if previousMoves.size else ???\n",
    "        # Append this player's results to the array over all iterations:\n",
    "        #  Which results do we want here -- those of the previous iteration or this one?\n",
    "        results = np.row_stack((results, ???)) if results.size else ???\n",
    "    \n",
    "    # Step 3: compute the mutual information for this player's moves using our existing scripts:\n",
    "    calculatedMI = ???\n",
    "    \n",
    "    # Step 4: compute the win and loss rates:\n",
    "    winRate = np.sum(results == 1)/len(results)\n",
    "    lossRate = np.sum(results == -1)/len(results)\n",
    "    numGames = len(results)\n",
    "\n",
    "    if verbose:\n",
    "        print('MI for {} over {} iterations: {:.04f} bits'.\\\n",
    "                format(name, numGames, calculatedMI))\n",
    "                          \n",
    "    return calculatedMI, winRate, lossRate, numGames"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46adc1ee-3d73-4622-b131-4b547b918892",
   "metadata": {},
   "source": [
    "2. Call the script for a few different players, e.g. `computeMutualInformationForPlayer('Joe', True)`, and compare."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da0e86fa-73b0-41d1-b4d1-eda9bb3019de",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "c47c9ddc-7f9c-4cd9-9d13-1712817a728a",
   "metadata": {},
   "source": [
    "3. Now call it to compute the mutual information using samples for all players' data in the one calculation: `computeMutualInformationForPlayer('*', True)`. What implicit assumption are we making when we analyse the data in this way?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebcdfcf7-13c5-4702-8a5d-927c7ee8755a",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "5f9a5afc-5da3-4cad-9449-934cac0e6792",
   "metadata": {},
   "source": [
    "4. See the function `computeMutualInformationForAllPlayers()` below.\n",
    "This aims to compute the mutual information of moves to previous moves for each player in turn (considering each player separately), then plots these, and looks for relationships between the mutual information and win/loss rates. Fill out the missing parts of code:\n",
    "    * In the loop over player names, use our previous function `computeMutualInformationForPlayer()` to compute the mutual information for that player. Take care: are we computing MI from our own previous moves or that of our opponent?\n",
    "    * Once we have the mutual information for each player and their win / loss ratios, compute the correlation between mutual information and win ratio, and entropy and loss ratio. _HINT_: Use the `stats.pearsonr()` or `stats.spearmanr()` correlation functions from the `scipy.stats` library -- make sure that you check what is returned from this function call (earlier version return a tuple, later version return an object)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc376fbd-6c8e-431e-ba33-a7cb03003072",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"function computeMutualInformationForAllPlayers()\n",
    "\n",
    "Compute the mutual information of moves for each player with their own\n",
    " previous move, or the previous move of their opponent, across all games/iterations.\n",
    "\n",
    "Inputs:\n",
    "- fromSelf (boolean, default True) - if true, take MI from the player's own previous move; if false\n",
    "    take MI from opponent's previous move.\n",
    "\n",
    "Outputs:\n",
    "- names\n",
    "- mutualInfos\n",
    "- winRates\n",
    "- lossRates\n",
    "\n",
    "Copyright (C) 2020-, Julio Correa, Joseph T. Lizier\n",
    "Distributed under GNU General Public License v3\n",
    "\"\"\"\n",
    "def computeMutualInformationForAllPlayers(fromSelf: bool = True):\n",
    "\n",
    "    # Step 1: load all of the player's names:\n",
    "    names = sprutils.listPlayers()\n",
    "    # Step 2: compute mutual info for each player\n",
    "    index = 0\n",
    "    mutualInfos = np.zeros(len(names))\n",
    "    winRates = np.zeros(len(names))\n",
    "    lossRates = np.zeros(len(names))\n",
    "    \n",
    "    for name in names:\n",
    "        # Compute the mutual info for the moves of this player.\n",
    "        # HINT: use the script that you just completed passing in name and fromSelf\n",
    "        calculatedMI, winRate, lossRate, numGames = ???\n",
    "        print('{} = {:.03f} bits,\\twin rate = {:.03f},\\tloss rate = {:.03f}, num games = {}'.\\\n",
    "              format(name, calculatedMI, winRate, lossRate, numGames))\n",
    "\n",
    "        mutualInfos[index] = calculatedMI\n",
    "        winRates[index] = winRate\n",
    "        lossRates[index] = lossRate\n",
    "\n",
    "        index += 1\n",
    "   \n",
    "    # Plot the winRates and lossRates versus mutualInfos:\n",
    "    plt.figure();\n",
    "    plt.scatter(mutualInfos, winRates, c='red', marker='x');\n",
    "    plt.title('Win rates versus mutual information for single players')\n",
    "    plt.xlabel('Mutual information of moves (bits)')\n",
    "    plt.ylabel('Win rate')\n",
    "    \n",
    "    plt.figure();\n",
    "    plt.scatter(mutualInfos, lossRates, c='red', marker='x');\n",
    "    plt.title('Loss rates versus mutual information of single players')\n",
    "    plt.xlabel('Mutual information of moves (bits)')\n",
    "    plt.ylabel('Loss rate')\n",
    "\n",
    "    # Compute correlations of entropy to win rate and to loss rate:\n",
    "    winToMICorr = ???\n",
    "    lossToMICorr = ???\n",
    "    print('Correlation of win  rate to MI is: {:.04f}'.\\\n",
    "        format(winToMICorr))\n",
    "    print('Correlation of loss rate to MI is: {:.04f}'.\\\n",
    "        format(lossToMICorr))\n",
    "    # Are these statistically significant?\n",
    "    # Can you adjust your code to check for that?\n",
    "    # HINT: look at other return values from the correlation function\n",
    "\n",
    "    return names, mutualInfos, winRates, lossRates"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ca15ace-a27e-4c09-b0f7-6467659cb9bd",
   "metadata": {},
   "source": [
    "5. Call the script to see the mutual information of each player, the plots and correlation analyses on how this related to performance. Do this for MI from the players' own previous move (pass argument `fromSelf=True`) and from their opponent (pass argument `fromSelf=False`). Who reflected the most information in their moves? Did this correlate to wins? What about losses? Does this match your hypothesis?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7a2b2b7f-4b2c-4ca0-8c47-9466e3456bc6",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "9b8d196c-de2e-447c-8d5c-d201e6ad39a3",
   "metadata": {},
   "source": [
    "6. _Challenge_: are these correlation values statistically significant? As above, look up theory on how to compute whether a correlation value is statistically significant. To add this to the code above, you can check out the other return values from the `stats.pearson()` or `stats.spearmanr()` correlation functions."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "237c7e42-9f56-4002-94cd-ba533d45ca7b",
   "metadata": {},
   "source": [
    "# Stage 5 - Further analysis\n",
    "\n",
    "Are there additional analyses that you would like to perform here?\n",
    "\n",
    "E.g. measuring mutual information from (jointly) the previous move of the player and their opponent, to the player's next move. What would you hypothesise about that? Or, is there any mutual information between concurrent moves? What would that mean?"
   ]
  }
 ],
 "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": 5
}
