<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 The pair of dice problem
Copyright 2018 Allen Downey
MIT License
Step1:  The BayesTable class
Here's the class that represents a Bayesian table.
Step2:  The pair of dice problem
Suppose I have a box that contains one each of 4-sided, 6-sided, 8-sided, and 12-sided dice.  I choose two dice at random and roll them without letting you see the die or the outcome.  I report that the sum of the dice is 3.
1) What is the posterior probability that I rolled each possible pair of the dice?
2) If I roll the same dice again, what is the probability that the sum of the dice is 11?
Solution
I'll start by making a list of possible pairs of dice.
Step3:  Here's a BayesTable that represents the hypotheses.
Step4:  Since we didn't specify prior probabilities, the default value is equal priors for all hypotheses.  They don't have to be normalized, because we have to normalize the posteriors anyway.
Now we can specify the likelihoods
Step5:  Now we can use update to compute the posterior probabilities
Step6:  Part two
The second part of the problem asks for the (posterior predictive) probability of getting a total of 11 if we roll the same dice again.
For this, it will be useful to write a more general function that computes the probability of getting a total, k, given n1 and n2.
Here's an example with the 4 and 6 sided dice
Step7:  And here's the general function
Step8:  To check the results, I'll compare them to the likelihoods in the previous table
Step9:  Now we can answer the second part of the question using the law of total probability.  The chance of getting 11 on the second roll is the
$\sum_{n1, n2} P(n1, n2 ~|~ D) \cdot P(11 ~|~ n1, n2)$
The first term is the posterior probability, which we can read from the table; the second term is prob_total(11, n1, n2).
Here's how we compute the total probability
Step10:  This calculation is similar to the first step of the update, so we can also compute it by
1) Creating a new table with the posteriors from table.
2) Adding the likelihood of getting a total of 11 on the next roll.
3) Computing the normalizing constant.
Step11:  Using a Suite
We can solve this problem more concisely, and more efficiently, using a Suite.
First, I'll create Pmf object for each die.
Step12:  And a Pmf object for the sum of each pair of dice.
Step14:  Here's a Dice class that implements Likelihood by looking up the data, k, in the Pmf that corresponds to hypo
Step15:  Here's the prior
Step16:  And the posterior
Step17:  And the posterior probability of getting 11 on the next roll.
<ASSISTANT_TASK:>
Python Code:
# Configure Jupyter so figures appear in the notebook
%matplotlib inline
# Configure Jupyter to display the assigned value after an assignment
%config InteractiveShell.ast_node_interactivity='last_expr_or_assign'
import numpy as np
import pandas as pd
import thinkplot
from thinkbayes2 import Pmf, Suite
from fractions import Fraction
Explanation: The pair of dice problem
Copyright 2018 Allen Downey
MIT License: https://opensource.org/licenses/MIT
End of explanation
class BayesTable(pd.DataFrame):
    def __init__(self, hypo, prior=1, **options):
        columns = ['hypo', 'prior', 'likelihood', 'unnorm', 'posterior']
        super().__init__(columns=columns, **options)
        self.hypo = hypo
        self.prior = prior
    
    def mult(self):
        self.unnorm = self.prior * self.likelihood
        
    def norm(self):
        nc = np.sum(self.unnorm)
        self.posterior = self.unnorm / nc
        return nc
    
    def update(self):
        self.mult()
        return self.norm()
    
    def reset(self):
        return BayesTable(self.hypo, self.posterior)
Explanation: The BayesTable class
Here's the class that represents a Bayesian table.
End of explanation
sides = [4, 6, 8, 12]
hypo = []
for die1 in sides:
    for die2 in sides:
        if die2 > die1:
            hypo.append((die1, die2))
            
hypo
Explanation: The pair of dice problem
Suppose I have a box that contains one each of 4-sided, 6-sided, 8-sided, and 12-sided dice.  I choose two dice at random and roll them without letting you see the die or the outcome.  I report that the sum of the dice is 3.
1) What is the posterior probability that I rolled each possible pair of the dice?
2) If I roll the same dice again, what is the probability that the sum of the dice is 11?
Solution
I'll start by making a list of possible pairs of dice.
End of explanation
table = BayesTable(hypo)
Explanation: Here's a BayesTable that represents the hypotheses.
End of explanation
for i, row in table.iterrows():
    n1, n2 = row.hypo
    table.loc[i, 'likelihood'] = 2 / n1 / n2
    
table
Explanation: Since we didn't specify prior probabilities, the default value is equal priors for all hypotheses.  They don't have to be normalized, because we have to normalize the posteriors anyway.
Now we can specify the likelihoods: if the first die has n1 sides and the second die has n2 sides, the probability of getting a sum of 3 is
2 / n1 / n2
The factor of 2 is there because there are two ways the sum can be 3, either the first die is 1 and the second is 2, or the other way around.
So the likelihoods are:
End of explanation
table.update()
table
Explanation: Now we can use update to compute the posterior probabilities:
End of explanation
n1, n2 = 4, 6
d1 = Pmf(range(1, n1+1))
d2 = Pmf(range(1, n2+1))
total = d1 + d2
thinkplot.Hist(total)
Explanation: Part two
The second part of the problem asks for the (posterior predictive) probability of getting a total of 11 if we roll the same dice again.
For this, it will be useful to write a more general function that computes the probability of getting a total, k, given n1 and n2.
Here's an example with the 4 and 6 sided dice:
End of explanation
def prob_total(k, n1, n2):
    d1 = Pmf(range(1, n1+1))
    d2 = Pmf(range(1, n2+1))
    total = d1 + d2
    return total[k]
Explanation: And here's the general function:
End of explanation
for i, row in table.iterrows():
    n1, n2 = row.hypo
    p = prob_total(3, n1, n2)
    print(n1, n2, p, p == row.likelihood)
Explanation: To check the results, I'll compare them to the likelihoods in the previous table:
End of explanation
total = 0
for i, row in table.iterrows():
    n1, n2 = row.hypo
    p = prob_total(11, n1, n2)
    total += row.posterior * p 
    
total
Explanation: Now we can answer the second part of the question using the law of total probability.  The chance of getting 11 on the second roll is the
$\sum_{n1, n2} P(n1, n2 ~|~ D) \cdot P(11 ~|~ n1, n2)$
The first term is the posterior probability, which we can read from the table; the second term is prob_total(11, n1, n2).
Here's how we compute the total probability:
End of explanation
table2 = table.reset()
for i, row in table2.iterrows():
    n1, n2 = row.hypo
    table2.loc[i, 'likelihood'] = prob_total(11, n1, n2)
    
table2
table2.update()
table2
Explanation: This calculation is similar to the first step of the update, so we can also compute it by
1) Creating a new table with the posteriors from table.
2) Adding the likelihood of getting a total of 11 on the next roll.
3) Computing the normalizing constant.
End of explanation
dice = {}
for n in sides:
    dice[n] = Pmf(range(1, n+1))
Explanation: Using a Suite
We can solve this problem more concisely, and more efficiently, using a Suite.
First, I'll create Pmf object for each die.
End of explanation
pairs = {}
for n1 in sides:
    for n2 in sides:
        if n2 > n1:
            pairs[n1, n2] = dice[n1] + dice[n2]
Explanation: And a Pmf object for the sum of each pair of dice.
End of explanation
class Dice(Suite):
    
    def Likelihood(self, data, hypo):
        Likelihood of the data given the hypothesis.
        
        data: total of two dice
        hypo: pair of sides
        
        return: probability
        
        return pairs[hypo][data]
Explanation: Here's a Dice class that implements Likelihood by looking up the data, k, in the Pmf that corresponds to hypo:
End of explanation
suite = Dice(pairs.keys())
suite.Print()
Explanation: Here's the prior:
End of explanation
suite.Update(3)
suite.Print()
Explanation: And the posterior:
End of explanation
suite.Update(11)
Explanation: And the posterior probability of getting 11 on the next roll.
End of explanation
<END_TASK>