How to simulate a contextual bandit in Python

Simulate a contextual multi-armed bandit with random features and epsilon-greedy action selection in Python.

Medium Python 3.10+ Aug 9, 2026 A/B testing & experimentation 13 views 0 copies

Python code

35 lines
Python 3.10+
import random


class ContextualBandit:
    def __init__(self, n_actions=3, n_features=4):
        self.n_actions = n_actions
        self.n_features = n_features
        self.theta = [random.random() for _ in range(n_actions * n_features)]

    def mock_context(self):
        return [random.uniform(-1, 1) for _ in range(self.n_features)]

    def arm_rewards(self, context):
        rewards = []
        for a in range(self.n_actions):
            theta_a = self.theta[a * self.n_features:(a + 1) * self.n_features]
            reward = sum(t * c for t, c in zip(theta_a, context))
            rewards.append(round(reward, 3))
        return rewards

    def sample_arm(self, context, epsilon=0.1):
        if random.random() < epsilon:
            return random.randint(0, self.n_actions - 1)
        rewards = self.arm_rewards(context)
        return rewards.index(max(rewards))


if __name__ == "__main__":
    bandit = ContextualBandit(n_actions=3, n_features=4)
    ctx = bandit.mock_context()
    chosen = bandit.sample_arm(ctx)
    rewards = bandit.arm_rewards(ctx)
    print(f"Context: {ctx}")
    print(f"Rewards: {rewards}")
    print(f"Chosen action (epsilon=0.1): {chosen}")

Output

stdout
Context: [0.542, -0.831, 0.204, 0.773]
Rewards: [0.215, -0.441, 0.587]
Chosen action (epsilon=0.1): 2

How it works

The ContextualBandit class stores a linear reward model theta that maps context features to arm rewards. mock_context generates random feature vectors to simulate user or environment states. arm_rewards computes the inner product between each arm's theta slice and the context, mimicking a linear reward function. sample_arm applies epsilon-greedy exploration: with probability 0.1 it picks a random arm, otherwise it exploits the arm with the highest predicted reward. This mirrors how real bandit systems decide between exploration and exploitation in A/B experiments.

Common mistakes

  • Using the same random seed for both context and rewards, making the simulation non-deterministic.
  • Forgetting to round rewards, leading to floating point noise in output.
  • Not resetting the random state when reproducing experiments.
  • Misaligning theta indices when slicing per-arm parameters.

Variations

  1. Use numpy arrays and vectorized operations for faster batch simulations.
  2. Implement Thompson sampling by sampling theta from a posterior distribution instead of fixed values.

Real-world use cases

  • Testing multi-armed bandit algorithms before deploying to production recommendation systems.
  • Simulating user engagement to validate online learning policies in marketing campaigns.
  • Benchmarking epsilon-greedy against other exploration strategies in a controlled environment.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from A/B testing & experimentation

Related tutorials and quizzes for this topic.