### IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
### CONSTANTS
# Between what options we choose.
TITLE_OPTION_ONE = 'Title Option One'
TITLE_OPTION_TWO = 'Title Option Two'
# "Real" CTRs that are not known for the bandit.
# We need them to simulate viewers' behavior.
OPTION_ONE_POPULATION_CLICK_THROUGH_RATE = 0.05
OPTION_TWO_POPULATION_CLICK_THROUGH_RATE = 0.07
# Number of visitors to whom we will show titles.
VISITORS_CNT = 1000
# Our prior belief about CTRs.
# Using a=1 and b=1, we state that there are no beliefs; any CTR is equally probable.
PRIOR_A = 1
PRIOR_B = 1
### SIMULATION
# To replicate results
np.random.seed(20231119)
# Variables to track how many viewers have seen and clicked on a title.
title_one_seen = 0
title_one_clicked = 0
title_two_seen = 0
title_two_clicked = 0
# These arrays are needed to evaluate the performance of algorithms later on
bandit_visitors_seen_highest_ctr_title = []
random_visitors_seen_highest_ctr_title = []
# For each visitor...
for _ in range(VISITORS_CNT):
# ... generate a posterior using Beta distribution adjusted based on observed data ...
option_one_beta_distribution = stats.beta(
a=PRIOR_A + title_one_clicked, # Add a number of "clicks"
b=PRIOR_B + (title_one_seen - title_one_clicked) # Add a number of "skips"
)
option_two_beta_distribution = stats.beta(
a=PRIOR_A + title_two_clicked, # Add a number of "clicks"
b=PRIOR_B + (title_two_seen - title_two_clicked) # Add a number of "skips"
)
# ... sample CTRs from these distributions ...
title_one_sample_ctr = option_one_beta_distribution.rvs()
title_two_sample_ctr = option_two_beta_distribution.rvs()
# ... choose what title to show using Thompson sampling.
if title_one_sample_ctr >= title_two_sample_ctr:
# Show title 1
title_one_seen += 1
# We know that Title One has a lower CTR. Hence, the algorithm made a mistake.
bandit_visitors_seen_highest_ctr_title.append(0)
# Determine if the visitor has clicked on the title.
if np.random.uniform(0.0, 1.0) <= OPTION_ONE_POPULATION_CLICK_THROUGH_RATE:
title_one_clicked += 1
else:
# Show title 2
title_two_seen += 1
# We know that Title Two has a higher CTR. Hence, the algorithm made a correct decision.
bandit_visitors_seen_highest_ctr_title.append(1)
# Determine if the visitor has clicked
if np.random.uniform(0.0, 1.0) <= OPTION_TWO_POPULATION_CLICK_THROUGH_RATE:
title_two_clicked += 1
# Our alternative to a Bayesian Multi-armed Bandit is a random split between both options
# Split is 50/50, which is why, with a probability of 0.5, random split will assign the higher CTR title.
if np.random.uniform(0.0, 1.0) <= 0.5:
random_visitors_seen_highest_ctr_title.append(1)
else:
random_visitors_seen_highest_ctr_title.append(0)
### SHOW RESULTS
print('RESULTS OF THE SIMULATION')
print('Viewers seen Title One', title_one_seen)
print('Viewers clicked on Title One', title_one_clicked)
print('Observed CTR of Title One', title_one_clicked / title_one_seen)
print()
print('RESULTS OF THE SIMULATION')
print('Viewers seen Title Two', title_two_seen)
print('Viewers clicked on Title Two', title_two_clicked)
print('Observed CTR of Title Two', title_two_clicked / title_two_seen)
print()
print('% of viewers whom Multi-armed Bandit showed the highest CTR title', np.mean(bandit_visitors_seen_highest_ctr_title))
print('% of viewers whom Random Split showed the highest CTR title', np.mean(random_visitors_seen_highest_ctr_title))
### RESULTS
# >>> RESULTS OF THE SIMULATION
# >>> Viewers seen Title One 227
# >>> Viewers clicked on Title One 10
# >>> Observed CTR of Title One 0.04405286343612335
# >>> RESULTS OF THE SIMULATION
# >>> Viewers seen Title Two 773
# >>> Viewers clicked on Title Two 54
# >>> Observed CTR of Title Two 0.06985769728331177
# >>> % of viewers whom Multi-armed Bandit showed the highest CTR title 0.773
# >>> % of viewers whom Random Split showed the highest CTR title 0.502