Generate a Mock Multi-Armed Bandit Report in Python
Simulate a multi-armed bandit experiment with random pulls and rewards, then output a JSON report with per-arm statistics.
Python code
36 linesimport random
import json
def generate_mock_bandit_report(num_arms=5, num_rounds=100, seed=42):
random.seed(seed)
arms = ["A", "B", "C", "D", "E"][:num_arms]
true_means = {arm: random.uniform(0.3, 0.7) for arm in arms}
pulls = {arm: 0 for arm in arms}
rewards = {arm: 0 for arm in arms}
for _ in range(num_rounds):
chosen_arm = random.choice(arms)
pulls[chosen_arm] += 1
if random.random() < true_means[chosen_arm]:
rewards[chosen_arm] += 1
report = {
"num_rounds": num_rounds,
"total_reward": sum(rewards.values()),
"arms": {}
}
for arm in arms:
report["arms"][arm] = {
"pulls": pulls[arm],
"rewards": rewards[arm],
"observed_mean": round(rewards[arm] / pulls[arm], 4) if pulls[arm] > 0 else 0.0,
"true_mean": round(true_means[arm], 4)
}
best_arm = max(arms, key=lambda a: report["arms"][a]["observed_mean"])
report["best_arm"] = best_arm
report["best_arm_actual_reward"] = report["arms"][best_arm]["observed_mean"]
return report
if __name__ == "__main__":
print(json.dumps(generate_mock_bandit_report(), indent=2))
Output
{
"num_rounds": 100,
"total_reward": 50,
"arms": {
"A": {
"pulls": 24,
"rewards": 10,
"observed_mean": 0.4167,
"true_mean": 0.6535
},
"B": {
"pulls": 20,
"rewards": 12,
"observed_mean": 0.6,
"true_mean": 0.4858
},
"C": {
"pulls": 21,
"rewards": 9,
"observed_mean": 0.4286,
"true_mean": 0.6128
},
"D": {
"pulls": 17,
"rewards": 12,
"observed_mean": 0.7059,
"true_mean": 0.5302
},
"E": {
"pulls": 18,
"rewards": 7,
"observed_mean": 0.3889,
"true_mean": 0.3451
}
},
"best_arm": "D",
"best_arm_actual_reward": 0.7059
}
How it works
This code simulates a classic multi-armed bandit problem. Each arm has a 'true' probability of success (true_mean) fixed at the start. Each round, the algorithm randomly selects an arm without any exploration-exploitation strategy, so the distribution of pulls is uniform. After 100 rounds, it computes the observed mean reward per arm (rewards divided by pulls) and identifies the arm with the highest observed mean as the 'best'. The JSON output is deterministic thanks to the fixed seed, making it easy to test downstream A/B testing pipelines.
Common mistakes
- Forgetting to set the random seed, producing non-reproducible reports.
- Dividing by zero when an arm receives 0 pulls; the code guards with a conditional.
- Confusing observed mean with true mean when evaluating arm performance.
- Keying arms with non-hashable objects instead of simple strings.
Variations
- Use a greedy epsilon policy to allocate more pulls to promising arms.
- Return a pandas DataFrame instead of a nested dict for easier analysis.
Real-world use cases
- Generating synthetic data to validate an A/B testing dashboard before real traffic arrives.
- Creating mock reports to demo a multi-armed bandit simulation to stakeholders.
- Testing downstream analytics code that consumes per-arm reward statistics from a JSON payload.
Sponsored
More from A/B testing & experimentation
Keep learning
Related tutorials and quizzes for this topic.