From 5c6ad8f84061d6a7312bb4ed38fca7d0538f2cba Mon Sep 17 00:00:00 2001 From: Peter Norvig Date: Wed, 19 Feb 2025 23:11:17 -0800 Subject: [PATCH] Add files via upload --- ipynb/Overtime.ipynb | 1492 +++++++++++++++++++++--------------------- 1 file changed, 757 insertions(+), 735 deletions(-) diff --git a/ipynb/Overtime.ipynb b/ipynb/Overtime.ipynb index 4ae7b1f..37f5054 100644 --- a/ipynb/Overtime.ipynb +++ b/ipynb/Overtime.ipynb @@ -11,7 +11,7 @@ "\n", "In American football, if the game is tied at the end of 60 minutes of play, an overtime period commences, with these rules:\n", "\n", - "- There is a coin toss; the team that guesses right has the option of possessing the ball first or second.\n", + "- There is a coin toss; the team that wins the flip has the option of possessing the ball first or second.\n", "- Both teams get one possession to attempt to score points.\n", "- After those two possessions, if the score is still tied, the game continues and the next score wins the game.\n", "\n", @@ -19,28 +19,31 @@ "\n", "- A team scores **3** points for kicking a **field goal**. \n", "- A team scores **6** points for a **touchdown**. \n", - "- After scoring a touchdown, if the game is not over, a team has the option of trying for extra point(s): either **one** point (easy) or **two** points (harder).\n", + "- After scoring a touchdown, if the game is not over, a team gets a chance to score **extra point**(s); they have the choice of going for either **one** point (easier) or **two** points (harder).\n", "\n", "\n", "\n", - "The 2024 Super Bowl went into overtime, and there was some criticism of San Francisco 49ers coach Kyle Shanahan, who, after winning the coin toss, elected to possess the ball first rather than second. As it turned out the 49ers scored a field goal and then the Chiefs scored a touchdown to win. If the 49ers had taken the ball second, and had they known that the Chiefs scored a touchdown first, they could have gone for their own touchdown rather than the field goal, and perhaps tied or won the game.\n", + "The 2024 Super Bowl went into overtime, and there was some criticism of San Francisco 49ers coach Kyle Shanahan, who, after winning the coin toss, elected to possess the ball first rather than second. As it turned out the 49ers scored a field goal and then the Chiefs scored a touchdown to win. If the 49ers had taken the ball second, and they had known that the Chiefs scored a touchdown first, they could have gone for their own touchdown rather than the field goal, and perhaps tied or won the game. There are two key question:\n", "\n", - "The first question is: Is it better to possess the ball first (team **A**) or second (team **B**)? There are two main points to consider:\n", + "1) Is it better to possess the ball first or second?\n", + "2) After a team scores a touchdown, should they go for 1 extra point or 2?\n", + "# TL;DR\n", "\n", - "- **B** has the advantage of knowing **A**'s score on their first possession.\n", - "- **A** has the advantage that if the score is tied after each team has their first possession, **A** gets the ball next (third), and next score wins.\n", + "The short answers:\n", + "1) It is (slightly) better to **take the ball first.** (Why? Despite the avantage of possessing the ball second rather than first, it is more important to possess it *third*, because then if you score you win. So it is better to be first (and myabe third) than second (and maybe fourth).)\n", + "3) The first team should **go for 1** extra point. If they succeed, the second team should **go for 2** extra points. \n", "\n", - "The second question is: if a team scores a touchdown on their first possession, what should their strategy be for the extra point(s)? It sems like **A** should go for 1, because if they go for 2 and miss, then it is too easy for **B** to make a 1-point conversion and win. On the other hand, it seems that **B** should go for 2, because if they tie the score at 7-7, then it is too easy for **A** to score next and win.\n", - "\n", - "In this notebook I do a simulation to answer these two questions.\n", + "In this notebook I make some simplifying assumptions, and then do a simulation to verify these answers.\n", "\n", "\n", "## Code to Simulate One Random Overtime Game\n", "\n", - "Calling the function `overtime()` below runs a random simulation and returns a tuple of the two scores for team **A** and team **B**. Optionally, you can set certain probability parameters (they will be the same for both teams):\n", - "- **TD**: the probability of scoring a touchdown on a given possession, for both teams. (About 20%, by league average in recent years.)\n", - "- **FG**: the probability of scoring a field goal, for both teams. (About 25%.)\n", - "- **go**: the *additional* probability of scoring a touchdown for a team that must score a touchdown or lose–i.e., if team **A** scores a touchdown on their first possession, team **B** would never kick a field goal (or punt), in those situations they will instead go for a touchdown, which they get with probability **go**. (Set at 10%, but I'm not sure if that is a good estimate.)\n", + "\n", + "\n", + "Calling the function `overtime()` below runs a random simulation of an overtime game and returns a pair of integers, (*A*, *B*), indicating the scores for the first team to possess the ball (**A**) and the second team (**B**). I could have done a detailed play-by-play simulation, but I decided to use a possession-by-possession simulation that considers just a few probabilities (and makes the inaccurate assumption that each possession is independent of the previous ones). You can set the following probability parameters (they will be the same for both teams):\n", + "- **TD**: the probability of scoring a touchdown on a given possession. (By default 20%, the league average in recent years.)\n", + "- **FG**: the probability of scoring a field goal on a given possession. (By default 25%.)\n", + "- **go**: the *additional* probability of scoring a touchdown for a team that must score a touchdown or lose–i.e., if team **A** scores a touchdown on their first possession, team **B** would never attempt a field goal (or punt). They will instead *go* for a touchdown, which they get with probability **go**. (By default 5%, but I'm not sure if that is a good estimate.)\n", "- **one**: the probability of succesfully kicking a 1-point conversion attempt. (About 98%.)\n", "- **two**: the probability of succesfully scoring on a 2-point conversion attempt. (About 48%.)\n", "- **A_extra**: 1 or 2, denoting what **A** should try for if they score a touchdown on their first possession.\n", @@ -50,36 +53,38 @@ { "cell_type": "code", "execution_count": 1, - "id": "890a789e-719f-4ca2-aa9e-f5384d7dfd80", + "id": "53085ed6-cb47-4a65-96bb-a21e2baa40e3", "metadata": {}, "outputs": [], "source": [ "import random\n", "from collections import Counter\n", - "Prob = float # The type for a probability, a number between 0 and 1.\n", + "from typing import Iterable, Tuple\n", + "from statistics import mean\n", "\n", - "def overtime(TD=0.20, FG=0.25, go=0.10, one=0.98, two=0.48, A_extra=1, B_extra=2) -> tuple:\n", + "Prob = float # The type for a probability, a number between 0 and 1.\n", + "Scores = Tuple[int, int] # Type for an (A, B) score.\n", + "\n", + "def overtime(TD=0.20, FG=0.25, go=0.05, one=0.98, two=0.48, A_extra=1, B_extra=2) -> Scores:\n", " \"\"\"Given probabilities, play a random overtime and return (team_A_score, team_B_score).\n", " Both teams have same probabilities:\n", " TD: probability of scoring a touchdown on a 'normal' possession. \n", " FG: probability of scoring a field goal on a 'normal' possession.\n", - " go: additional probability of scoring a touchdown, if you resolve not to kick.\n", + " go: additional probability of scoring a touchdown, if you must score or lose.\n", " one: probability of making a one-point conversion.\n", " two: probability of making a two-point conversion.\n", - " A_extra: what team A goes for on the extra point.\n", - " B_extra: what team B goes for on the extra point (when behind by 1).\"\"\"\n", + " A_extra: what team A goes for on the extra point (1 or 2).\n", + " B_extra: what team B goes for on the extra point (1 or 2), when behind by 1.\"\"\"\n", " A = B = 0 # The scores of the two teams\n", - " possession = 1 # The number of possessions for each team\n", + " first_possession = True\n", " while A == B:\n", - " extra = (0 if possession > 1 else P(one, 1) if A_extra == 1 else P(two, 2))\n", + " extra = (0 if not first_possession else P(one, 1) if A_extra == 1 else P(two, 2))\n", " A += score(TD, FG, extra)\n", - " if possession == 1 or A == B: # B gets a chance on their first possession, or if it is still tied.\n", - " extra = (0 if B + 6 > A else P(one, 1) if B + 6 == A or (B_extra == 1 and B + 7 == A) else P(two, 2))\n", - " B += (score(TD + go, 0, extra) if A - B > 3 # Must go for TD if behind by more than 3\n", - " else \n", - " score(TD, FG, 0))\n", - " possession += 1\n", - " return A, B\n", + " if first_possession or A == B: # B gets a chance on their first possession, or if the game is still tied.\n", + " extra = (0 if B + 6 > A else P(one, 1) if B + 7 > A or (B_extra == 1 and B + 7 == A) else P(two, 2))\n", + " B += (score(TD, FG, 0) if B + 3 >= A else score(TD + go, 0, extra)) # must go for it if down by more than 3\n", + " first_possession = False\n", + " return A, B # The scores of the two teams\n", "\n", "def score(TD: Prob, FG: Prob, extra: int) -> int: \n", " \"\"\"Randomly simulate a score, given probabilities for TD and FG, and given the number of extra points.\"\"\"\n", @@ -126,32 +131,32 @@ "source": [ "# Code to Simulate a Million Games, and Draw Conclusions\n", "\n", - "OK, but that's just one game. What if we play a million games?" + "OK, but that's just one game. What if we play, say, a million games?" ] }, { "cell_type": "code", "execution_count": 3, - "id": "b8c62d41-0bd3-4483-ba84-7a084b05510a", + "id": "e881dac9-b490-46b6-95e2-137b50679f8d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[((3, 0), 245539),\n", - " ((0, 3), 196710),\n", - " ((0, 6), 157459),\n", - " ((7, 0), 137498),\n", - " ((6, 0), 89649),\n", - " ((3, 6), 63006),\n", - " ((7, 6), 30297),\n", - " ((7, 8), 28557),\n", - " ((6, 3), 22473),\n", - " ((9, 3), 17667),\n", - " ((3, 9), 9921),\n", - " ((6, 7), 1206),\n", - " ((12, 6), 6),\n", - " ((9, 6), 5),\n", + "[((3, 0), 246591),\n", + " ((0, 3), 198249),\n", + " ((0, 6), 157443),\n", + " ((7, 0), 146508),\n", + " ((6, 0), 89334),\n", + " ((3, 6), 62197),\n", + " ((7, 6), 25421),\n", + " ((7, 8), 23293),\n", + " ((6, 3), 22070),\n", + " ((9, 3), 18140),\n", + " ((3, 9), 9734),\n", + " ((6, 7), 996),\n", + " ((12, 6), 9),\n", + " ((9, 6), 8),\n", " ((6, 12), 4),\n", " ((6, 9), 3)]" ] @@ -162,7 +167,11 @@ } ], "source": [ - "Counter(overtime() for _ in range(1000_000)).most_common()" + "def overtimes(games=1_000_000, **kwds) -> Iterable[Scores]:\n", + " \"\"\"Play `games` games and return an iterable of the `(A, B)` score for each game.\"\"\"\n", + " return (overtime(**kwds) for _ in range(games))\n", + "\n", + "Counter(overtimes()).most_common()" ] }, { @@ -170,19 +179,19 @@ "id": "3429fec7-2efe-4ee2-8309-acb8bed6b8a3", "metadata": {}, "source": [ - "That gives us the range of possible scores and the frequency of each one. Note:\n", + "That gives us the possible scores and the frequency of each one. Note:\n", "- Field goals are more common than touchdowns, so the most common scores are 3-0 and 0-3.\n", - "- 3-0 is more common than 0-3, because 3-0 includes times when **A** scored first or third (or later, but that's rarer).\n", - "- Why is 0-6 more common than 7-0? Because 0-6 includes times when **B** scored second, or fourth (or later).\n", + "- 3-0 is more common than 0-3, because 3-0 includes times when **A** scored first and also third (or later, but that's rarer).\n", + "- Why is 0-6 more common than 7-0? Because 0-6 includes times when **B** scored second or fourth (or later), but 7-0 is only first.\n", "- Note that the total of 7-0 (**A** scores first) and 6-0 (**A** scores third, or first and misses the kick) is more than the count for 0-6.\n", "- Note that 3-6 is almost 3 times more common than 6-3. Either could result from 3 field goals, but 3-6 could also be **B** scoring a touchdown; 6-3 could never result from **A** scoring a touchdown.\n", - "- 6-9, 9-6, 12-6, and 6-12 are all rare scores, because they mean that *both* teams missed a kick.\n", + "- 6-9, 9-6, 12-6, and 6-12 are very rare scores, because they mean that *both* teams missed a conversion kick.\n", "\n", - "That's all interesting, but the question remains:\n", + "That's all interesting, but the two questions remain:\n", "\n", - "## Who Has the Advantage, Team A or Team B?\n", + "# Question 1: Who Has the Advantage, Team A or Team B?\n", "\n", - "We can add up the games in which **A** wins:" + "We can acompute the probability of team **A** winning:" ] }, { @@ -192,10 +201,9 @@ "metadata": {}, "outputs": [], "source": [ - "def win_probability(n=100_000, **kwds) -> float:\n", - " \"\"\"Probability that first team (team A) wins, in n simulations of overtime(**kwds).\"\"\"\n", - " scores = (overtime(**kwds) for _ in range(n))\n", - " return sum(A > B for A, B in scores) / n" + "def A_win_probability(scores) -> float:\n", + " \"\"\"Probability that first team (team A) wins.\"\"\"\n", + " return mean(A > B for A, B in scores)" ] }, { @@ -207,7 +215,7 @@ { "data": { "text/plain": [ - "0.5438" + "0.548365" ] }, "execution_count": 5, @@ -216,7 +224,7 @@ } ], "source": [ - "win_probability()" + "A_win_probability(overtimes())" ] }, { @@ -224,633 +232,58 @@ "id": "c7d4f929-04aa-484e-851d-2a83758d4948", "metadata": {}, "source": [ - "Using the given scoring percentages, my simulation says team **A** has about a 54% chance of winning. This supports Shanahan's decision. \n", + "My simulation says team **A** has nearly a 55% chance of winning. This supports Shanahan's decision. \n" + ] + }, + { + "cell_type": "markdown", + "id": "de405c60-d215-46e9-85f4-ccd6d67a3ec7", + "metadata": {}, + "source": [ + "# Question 2: What Are the Best Strategies for Extra Points?\n", "\n", - "However, I don't have confidence that the probability parameter values I chose are reflective of reality, so let's explore a wider range of parameters: " + "Earlier I claimed that **A** should go for 1 extra point and **B** for 2:" ] }, { "cell_type": "code", "execution_count": 6, - "id": "be360c09-bb94-4328-b439-4bcef5ff6ef3", + "id": "95f231d1-b7d4-4387-8cbf-4ae5cb3f7cdb", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.548867" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "import pandas as pd\n", - "from IPython.display import HTML\n", - " \n", - "def chart(TDs=(0.15, 0.20, 0.33), \n", - " FGs=(0.20, 0.25, 0.33), \n", - " gos=(0.05, 0.10, 0.20), \n", - " twos=(0, 0.48, 0.60),\n", - " n=100_000) -> HTML:\n", - " \"\"\"Create a chart of Win percentages for various parameter values.\"\"\"\n", - " data = [((win_probability(n=n, TD=TD, FG=FG, go=go)), TD, FG, go, two)\n", - " for TD in TDs for FG in FGs for go in gos for two in twos]\n", - " df = pd.DataFrame(data, columns=('Win', 'TD', 'FG', 'go', 'two')).sort_values('Win')\n", - " print(f'Team A win probability: min: {min(df.Win):.1%}, max: {max(df.Win):.1%}')\n", - " return HTML(df.to_html(index=False, formatters={'Win': '{:.1%}'.format}))" + "A_win_probability(overtimes(A_extra=1, B_extra=2))" + ] + }, + { + "cell_type": "markdown", + "id": "931acdc1-753a-4b7f-ae68-42c648eefef2", + "metadata": {}, + "source": [ + "We can see that if **A** goes for 2, they will do worse (by about half a percent):" ] }, { "cell_type": "code", "execution_count": 7, - "id": "e323e49e-84d7-43f6-99f2-8b9def6a4b0d", + "id": "3a374721-3e4e-4185-90f5-4e27cc0de458", "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Team A win probability: min: 51.3%, max: 55.7%\n" - ] - }, { "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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
WinTDFGgotwo
51.3%0.330.200.200.48
51.4%0.330.200.200.00
51.6%0.330.250.200.60
51.7%0.330.200.200.60
51.7%0.330.250.200.00
51.9%0.330.250.200.48
52.2%0.330.330.200.60
52.6%0.330.330.200.48
52.7%0.330.330.200.00
53.2%0.330.250.100.00
53.2%0.330.200.100.60
53.2%0.200.200.200.00
53.2%0.200.250.200.48
53.3%0.200.200.200.48
53.3%0.200.200.200.60
53.3%0.200.250.200.00
53.4%0.330.250.100.60
53.4%0.330.200.100.00
53.4%0.200.250.200.60
53.4%0.150.200.200.00
53.5%0.330.250.100.48
53.5%0.330.200.100.48
53.5%0.150.250.200.00
53.7%0.150.200.200.48
53.7%0.330.330.100.00
53.7%0.150.250.200.60
53.7%0.330.200.050.00
53.8%0.150.250.200.48
53.8%0.150.200.200.60
53.9%0.330.200.050.48
53.9%0.330.330.100.60
54.0%0.330.250.050.00
54.0%0.200.250.100.60
54.0%0.200.330.200.48
54.0%0.200.330.200.60
54.1%0.200.200.100.60
54.1%0.330.330.100.48
54.1%0.150.200.100.00
54.1%0.200.200.100.48
54.1%0.330.200.050.60
54.1%0.200.330.200.00
54.2%0.200.200.100.00
54.2%0.330.250.050.48
54.2%0.150.200.100.48
54.2%0.150.200.100.60
54.3%0.150.330.200.00
54.3%0.200.200.050.00
54.3%0.200.200.050.60
54.3%0.330.250.050.60
54.4%0.200.250.100.48
54.4%0.150.330.200.48
54.5%0.200.250.100.00
54.5%0.150.250.100.48
54.5%0.200.250.050.00
54.5%0.150.200.050.60
54.6%0.150.200.050.48
54.6%0.150.200.050.00
54.6%0.200.200.050.48
54.7%0.330.330.050.48
54.7%0.150.250.100.60
54.7%0.330.330.050.60
54.7%0.150.330.200.60
54.8%0.150.250.100.00
54.8%0.150.250.050.60
54.8%0.330.330.050.00
54.8%0.200.250.050.60
54.9%0.150.250.050.00
55.0%0.150.250.050.48
55.0%0.200.330.100.48
55.0%0.200.330.100.00
55.1%0.200.250.050.48
55.1%0.200.330.100.60
55.2%0.200.330.050.00
55.2%0.150.330.100.60
55.3%0.150.330.100.00
55.3%0.200.330.050.48
55.3%0.150.330.100.48
55.4%0.150.330.050.00
55.4%0.200.330.050.60
55.4%0.150.330.050.60
55.7%0.150.330.050.48
" - ], "text/plain": [ - "" + "0.54451" ] }, "execution_count": 7, @@ -859,71 +292,7 @@ } ], "source": [ - "chart()" - ] - }, - { - "cell_type": "markdown", - "id": "5773d2a1-642e-4a28-bfe9-b63e37aab2a2", - "metadata": {}, - "source": [ - "Now I feel more confident that across a wide range of parameter values, **A** always has the advantage.\n", - "\n", - "Another question still remains:\n", - "\n", - "## What Are the Best Strategies for Extra Points?\n", - "\n", - "Earlier I claimed that **A** should go for 1 and **B** for 2. That gives us about a 54% win probability for **A**:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "95f231d1-b7d4-4387-8cbf-4ae5cb3f7cdb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.54277" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "win_probability(A_extra=1, B_extra=2)" - ] - }, - { - "cell_type": "markdown", - "id": "931acdc1-753a-4b7f-ae68-42c648eefef2", - "metadata": {}, - "source": [ - "We can see that if **A** goes for 2, they will do worse:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "3a374721-3e4e-4185-90f5-4e27cc0de458", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.53674" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "win_probability(A_extra=2, B_extra=2)" + "A_win_probability(overtimes(A_extra=2, B_extra=2))" ] }, { @@ -936,14 +305,659 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "8a24b181-16d3-4c30-84af-25ceb5c55929", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.55175" + "0.555354" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "A_win_probability(overtimes(A_extra=1, B_extra=1))" + ] + }, + { + "cell_type": "markdown", + "id": "642faec7-035f-45b2-828f-afdc188510fa", + "metadata": {}, + "source": [ + "These results support the claim that **A** should go for 1 extra point and **B** for 2.\n", + "\n", + "# Question 1: Varying the Parameters\n", + "\n", + "However, I don't have confidence that the probability parameter values I chose are reflective of reality, so for each parameter I'll try a lower and a higher value, and look at all combinations, displayed as a dataframe:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "be360c09-bb94-4328-b439-4bcef5ff6ef3", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from IPython.display import HTML\n", + " \n", + "def chart(TDs=(0.15, 0.20, 0.33), \n", + " FGs=(0.20, 0.25, 0.33), \n", + " gos=(0.02, 0.05, 0.10), \n", + " twos=(0.4, 0.48, 0.60),\n", + " games=100_000) -> HTML:\n", + " \"\"\"Create a chart of Win percentages for various parameter values.\"\"\"\n", + " data = [(TD, FG, go, two, A_win_probability(overtimes(games=games, TD=TD, FG=FG, go=go)))\n", + " for TD in TDs for FG in FGs for go in gos for two in twos]\n", + " df = pd.DataFrame(data, columns=('TD', 'FG', 'go', 'two', 'Win')).sort_values('Win', ascending=False)\n", + " print(f'Team A win probability: min: {min(df.Win):.1%}, max: {max(df.Win):.1%}')\n", + " return HTML(df.to_html(index=False, formatters={'Win': '{:.1%}'.format}))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e323e49e-84d7-43f6-99f2-8b9def6a4b0d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Team A win probability: min: 53.0%, max: 56.1%\n" + ] + }, + { + "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", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TDFGgotwoWin
0.150.330.020.4856.1%
0.150.330.020.4056.0%
0.200.330.020.4855.8%
0.150.330.020.6055.7%
0.150.330.050.4055.7%
0.200.330.020.4055.7%
0.150.330.050.6055.6%
0.200.330.020.6055.6%
0.200.330.050.6055.5%
0.200.330.050.4055.5%
0.200.330.050.4855.4%
0.330.330.020.4055.4%
0.150.330.100.4055.4%
0.150.330.050.4855.4%
0.330.330.020.6055.4%
0.330.330.020.4855.3%
0.150.250.020.4055.2%
0.150.330.100.6055.2%
0.200.250.020.4855.2%
0.150.330.100.4855.2%
0.200.250.020.4055.1%
0.150.250.020.4855.1%
0.200.330.100.4055.1%
0.150.250.020.6055.0%
0.150.250.050.4055.0%
0.200.250.050.6055.0%
0.150.250.050.6054.9%
0.150.200.020.4854.9%
0.200.250.020.6054.9%
0.150.250.050.4854.9%
0.200.330.100.4854.9%
0.200.200.020.6054.8%
0.330.330.050.4854.8%
0.150.250.100.6054.8%
0.330.330.050.4054.8%
0.200.330.100.6054.8%
0.150.250.100.4054.7%
0.200.250.050.4054.7%
0.150.200.050.4854.7%
0.150.200.020.6054.7%
0.330.250.020.4854.7%
0.330.330.050.6054.7%
0.200.250.050.4854.7%
0.330.250.020.4054.7%
0.200.200.020.4854.7%
0.200.200.050.4854.7%
0.150.200.020.4054.7%
0.150.200.050.4054.7%
0.200.200.020.4054.6%
0.200.200.050.4054.6%
0.150.200.050.6054.6%
0.330.250.020.6054.6%
0.330.200.020.6054.6%
0.200.250.100.4054.5%
0.150.250.100.4854.5%
0.330.200.020.4054.4%
0.200.250.100.4854.4%
0.200.250.100.6054.3%
0.150.200.100.6054.3%
0.330.200.020.4854.3%
0.330.200.050.4854.2%
0.200.200.050.6054.2%
0.330.250.050.4054.2%
0.150.200.100.4854.2%
0.330.330.100.6054.2%
0.200.200.100.4054.2%
0.150.200.100.4054.2%
0.330.200.050.4054.1%
0.330.200.050.6054.1%
0.330.250.050.4854.0%
0.330.250.050.6054.0%
0.200.200.100.6054.0%
0.330.330.100.4853.9%
0.200.200.100.4853.8%
0.330.330.100.4053.8%
0.330.250.100.6053.8%
0.330.250.100.4053.4%
0.330.200.100.4053.3%
0.330.250.100.4853.3%
0.330.200.100.4853.2%
0.330.200.100.6053.0%
" + ], + "text/plain": [ + "" ] }, "execution_count": 10, @@ -952,7 +966,15 @@ } ], "source": [ - "win_probability(A_extra=1, B_extra=1)" + "chart()" + ] + }, + { + "cell_type": "markdown", + "id": "caf22648-ff10-45ac-9d5a-635672592778", + "metadata": {}, + "source": [ + "The results show that over a wide range of assumptions about the probabilities, team **A** always wins a majority of the time (between 53% and 56%). So no more Shanahan shaming." ] } ],