<jupyter_start><jupyter_text>Hypothesis spaceThe hypothesis space represents all possible fairnesses of the coin where 0 means 100% tail biased and 1 100% head-biased. Calculating likelihoods**Non flawed machine:**For a non-flawed machine we can trust the outcome of what it sees, so the likelihoods are as follows:* $H_l: hs$; fliping heads agrees with the hypothesis space* $T_l: 1-hs$; flipping tails is against the hypothesis space**Full flawed machine**For a full flawed machine is the other way round. This means that when the machine sees a head, it's indeed a tail:* $H_l: 1-hs$; fliping heads is against the hypothesis space (as we actually flipped a tail, not a head)* $T_l: hs$; flipping tails agrees with the hypothesis space**Half-flawed machine**For a half-flawed machine no matter the outcome of the flip, half of the hypothesis space aggrees with the result and the other half disagrees:* $H_l: .5\cdot hs + .5\cdot(1-hs)$* $T_l: .5\cdot hs + .5\cdot(1-hs)$    This, effectively, bring us back to the starting point where every hypothesis is equally probable. We are substituting the randomness of a coin by the randomness of the machine.**Intermediate flaw levels**For intermediate flaw values we have a mix in both quantities, this is for a 1/5 flaw:* $H_l: .8\cdot hs + .2\cdot(1-hs)$* $T_l: .2\cdot hs + .8\cdot(1-hs)$**General expression**  Regarding all above we can come up with a common expression for all the cases  $H_l = (1-f)\cdot hs + f(1-hs)$  $T_l = f\cdot hs + (1-f)\cdot(1-hs)$<jupyter_code># Solution goes here
from empiricaldist import Pmf
import numpy as np
import seaborn as sns

# The hypothesis space represents all possible fairnesses of the coin
# Where 0 means 100% tail biased and 1 100% head-biased
hs = np.linspace(0, 1, 101)

def calc_posterior(hs, dataset, flaw=0):
    """
    Calculates the posterior probability.
    
    Args:
        hs: numpy array with the hypothesis space
        dataset: a string with the outcome of the experiment.
        flaw: a float that indicates how flawed is the machine.
    returns: Pmf with the posterior probability for the given args.
    """
    prior = Pmf(1, hs)
    assert flaw >= 0 and flaw <= 1
    
    likelihood = {
        'H': (1-flaw) * hs + flaw * (1-hs),
        'T': flaw * hs + (1-flaw) * (1-hs)
    }

    posterior = prior.copy()

    for flip in dataset:
        posterior *= likelihood[flip]

    posterior.normalize()
    return posterior

dataset = 140 * 'H' + 110 * 'T'
y0 = calc_posterior(hs, dataset)
y2 = calc_posterior(hs, dataset, flaw=.2)
y4 = calc_posterior(hs, dataset, flaw=.4)
y5 = calc_posterior(hs, dataset, flaw=.5)

sns.lineplot(x=hs, y=y0, label='y0');
sns.lineplot(x=hs, y=y2, label='y=.2');
sns.lineplot(x=hs, y=y4, label='y=.4');
sns.lineplot(x=hs, y=y5, label='y=.5');<jupyter_output><empty_output><jupyter_text>24/07/2021 Exercise<jupyter_code>import numpy as np
import seaborn as sns
# The hypothesis space represents all possible fairnesses of coins where 0 means
# a 100% tail biased coin and 1 represents a 100% head biased one
hs = np.linspace(0, 1, 101)

# Examine what happens at the extremes for possibility
# y=0 means that the machine always reports the outcome of the coin so the
# likelihoods are those of a regular coin:
likes_y0 = {
    'H': hs,
    'T': 1-hs
}

# Conversely, y=1 means that the machine always fails the outcome of the flip,
# so we have to swap the likelihoods
likes_y1 = {
    'H': 1-hs,
    'T': hs
}

# Finding the likelihood as a function of y
# let's split the problem by outcome, first heads.
# We have to find a function of y such that when y=0 returns hs and when y=1
# returns 1-hs. That function is y + hs - 2yhs
# Now tails. This is quite straightforward as we only have to replace hs by
# 1-hs
def get_likes_y(y=0):
    """Return the likelihood as a function of y."""
    return {
        'H': y + hs - 2*hs*y,
        'T': y + 1 - hs -(2*y*(1-hs))
    }

# let's do some assertions that confirm above formula
likes_y = get_likes_y()
assert (likes_y['H'] == hs).all()
assert (likes_y['T'] == (1-hs)).all()

likes_y = get_likes_y(y=1)
assert np.allclose(likes_y['H'], (1-hs)) # because floating point arithmetic
assert np.allclose(likes_y['T'], hs)

# Even more, y=.5 should give a steady value of .5 as our uncertainty is max
likes_y = get_likes_y(y=.5)
assert np.allclose(likes_y['H'], .5)
assert np.allclose(likes_y['T'], .5)

# Machine report
report = 140 * 'H' + 110 * 'T'

def find_map(posterior):
    """Return the index where the max probability lives."""
    return np.where(posterior==posterior.max())[0][0]

def find_confidence_interval(posterior):
    """Return the lower and upper bounds indices for a 90% ci."""
    cdf = posterior.cumsum()
    lower = np.where(cdf >= .05)[0][0]
    upper = np.where(cdf <= .95)[0][-1]
    return lower, upper

for y in (.0, .2, .4, .5):
    likes_y = get_likes_y(y)
    posterior = np.ones(101)
    for flip in report:
        posterior *= likes_y[flip]
    posterior /= posterior.sum()
    sns.lineplot(x=hs, y=posterior, label=f"y={y}");
    MAP = find_map(posterior)
    lower, upper = find_confidence_interval(posterior)
    print(f"the MAP for y={y} is {hs[MAP]} and the confidence interval lays between {hs[lower]} and {hs[upper]}")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12003780718336483, 'fraction_numerical': 0.026465028355387523, 'mean_word_length': 3.7325581395348837, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '665037', 'license': 'MIT', 'n_tokens_mistral': 1897, 'n_tokens_neox': 1746, 'n_words': 679, 'path': 'my_exercices/flawed machine.ipynb', 'repo_name': 'nablabits/ThinkBayes2', 'size': '5290'}