<jupyter_start><jupyter_text>!!! Warning !!!This notebook was a starting point for this whole repo.  While it doesn't use the current api, it still has value in deriving how we use the violin plot and its relation to the beta bandit.  It will eventually be updated after iterating through a few other demos and write-ups.!!! Warning !!!  Beta Bandit ExplanationProperties of beta bandits:* Responses are rates - [0,1]* Enables conjugacy - a beta's prior is a betaProperties of Thompson sampling* near-best performance in several comparative studies (todo: add citataion)* enables sensible batch-sampling<jupyter_code>from faker import Faker
from matplotlib import pyplot as plt
from scipy import stats
import numpy as np

from metrics.plots import plot_custom_violin, plot_per_batch_performance
factory = Faker()<jupyter_output><empty_output><jupyter_text>Beta Distribution ExplorationLet's look directly at the beta dist and see how it fits our use case<jupyter_code># Plot several overlaid examples of Beta as learning progresses...
def demo_idealized_beta_learning_stacked():
    fig = plt.figure(0, (12, 8))
    x = np.linspace(0., 1., 1000)
    plt.title(f"Idealized Beta, 25%CR, nsamples = 4*10**i, i=[0,1,2,3]")
    for i in range(4):
        a = 1*10**i+1
        b = 3*10**i+1
        # adding 1 here:
        # * at the smallest data sizes gives lets beta match our intuition
        #   of never guessing a rate that is at the extreme of [0, 1]
        # * as data grows in size will have negligible effect
        # * todo: need to check the math on what happens around 10 samples.
        n = a + b

        this_beta_pdf = stats.beta.pdf(x, a, b)
        plt.plot(x, this_beta_pdf, label=f"n={n-2}+2")
    plt.legend()

    # Make background transparent
    ax = fig.axes[0]
    ax.set_facecolor((0., 0., 0., 0.))

    plt.show()

demo_idealized_beta_learning_stacked()<jupyter_output><empty_output><jupyter_text>Demo The Full Bandit<jupyter_code># A pile of boilerplate to be converted into generating the later-on hardcoded
# sample data

# n_contacts = 300
# contacts = [Contact(factory.name(), factory.email()) for i in range(n_contacts)]
#
# n_email_templates = 5
# email_templates = [EmailTemplate(factory.sentence(), factory.paragraph())
#                    for i in range(n_email_templates)]
#
#
# print(f"Several example of the {len(contacts)} contacts:")
# for c in contacts[:3]:
#     print(c)
#
# print(f"Several examples of the {len(email_templates)} email templates:")
# for e in email_templates[:3]:
#     print(e)

# class SimulatedEmailResponseGaussian:
#     def __init__(self, p):
#         self.p = p
#     def get_response(self):
#         # import gaussian with given mean
#         raise NotImplementedError
#
#         # TODO:
#         #  * make into Faker provider?
#         #  * or at least transition to these to allow us to calibrate params
#         pass

# Trigger first batch of emails
# playbook = BagOfEmailsPlaybook(
#     contacts=contacts, email_templates=email_templates)
# # playbook.add_email_templates()
# # playbook.add_contacts()<jupyter_output><empty_output><jupyter_text>Fabricate Leversand give each lever noticably different expected returns.<jupyter_code># Let's say each template was used
# and that either they were hard-coded to send an equal number or the Bandit
# alg happened to pick and even number across all since there is yet no info.
simulated_responses = [  # [number responded, number didn't respond]
    [100, 300],  # .25
    [5, 35],  # .175
    [30, 50],  # .375
    [45, 155],  # .225
    [30, 370],  # .075
]

# actual_response_rates = [  # for generating this data randomly
#     0.2,
#     0.21,
#     0.35,
#     0.25,
#     0.13
# ]
n_levers = len(simulated_responses)

# IRL, we'll have to separate sending of email and waiting some time to have
# a sufficient amount of email responses or properly ignored.
# Worth doing its own analysis to see email response time.

# A pile of examples for violin plot:
# https://seaborn.pydata.org/generated/seaborn.violinplot.html

email_names = ['Email A', 'Email B', 'Email C', 'Email D', 'Email E']
x = np.linspace(0., 1., 1000)
perf = [stats.beta.pdf(x, a, b) for a, b in simulated_responses]
plot_custom_violin(x, perf, email_names)
plt.show()<jupyter_output><empty_output><jupyter_text>Demo What Learning Would Look LikeNow say we have multiple batches, and we want to look at our net performanceover time<jupyter_code>simulated_batches = [  # Manually fabricated numbers
    [
        [100, 300],  # .25
        [70, 430],  # .175
        [150, 250],  # .375
        [90, 310],  # .225
        [30, 370],  # .075
    ], [
        [105, 316],
        [51, 244],
        [237, 396],
        [85, 295],
        [9, 118],
    ], [
        [100, 300],
        [5, 35],
        [300, 500],
        [45, 155],
        [10, 30],
    ], [
        [25, 75],
        [0, 0],
        [525, 875],
        [9, 31],
        [0, 0],
    ]
]

plot_per_batch_performance(simulated_batches[:-1], email_names)
plt.title("What Three Batches May Look Like")
plt.show()

plot_per_batch_performance(simulated_batches, email_names)
plt.title("Hypothetical Fourth Treatment")
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11200459506031017, 'fraction_numerical': 0.04097262109898526, 'mean_word_length': 3.2854799015586544, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 1, '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': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '534670', 'license': 'Apache-2.0', 'n_tokens_mistral': 1878, 'n_tokens_neox': 1672, 'n_words': 561, 'path': 'notebooks/exploration/beta_bandit_explanation.ipynb', 'repo_name': 'BellwethrInc/decision_engine', 'size': '5223'}