Designing an A/B Experiment

First lesson of the A/B Testing and Experimentation course, free to read: about 15 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Formulate a testable hypothesis and choose a primary metric and guardrail metrics
  • Set up a random, stable, and balanced assignment of users

Experiment hypothesisPrimary metric and guardrailsRandomization

An A/B test is the most reliable way to know whether a change (new button, new price, new email) causes a change in behavior. You show the current version (A, the control) to one group, the new version (B, the variant) to another, and you compare.

Why randomize?

Comparing “before” and “after” a release mixes the effect of the change with everything else: seasonality, marketing campaigns, the news. If users are split at random between A and B at the same time, the two groups are comparable on everything, including what you don’t measure. The only systematic difference is the change: the observed difference is therefore attributable to it, up to chance, which statistics measure.

The experiment plan, written before launch

  1. Hypothesis: “Showing shipping costs on the product page will increase the order rate, because customers will be less surprised at checkout.”
  2. Primary metric (just one): order rate per visitor.
  3. Guardrail metrics: average basket, refund rate, load time. They must not degrade.
  4. Randomization unit: the user.
  5. Sample size, duration, and alpha threshold (next lesson).

Fixing these choices in advance prevents you from “hunting” for a favorable metric after the fact.

Choosing the randomization unit

  • By user: the most common; everyone always sees the same version, which avoids inconsistencies.
  • By session or page view: more data, but the same customer may see both versions.
  • By region or store when individuals interact (marketplace, in-store pricing).

The metric must be computed at the same level as the unit: if you randomize users, you compute a rate per user.

Assigning in a stable way: hashing

Drawing at random on every visit would make a user switch groups. Instead, you use a hash function of the identifier: the result looks random, but it is always the same for the same user.

import hashlib

def assigner(user_id, experience, part_b=0.5):
    # we combine the experiment and the user: two experiments don't share the same split
    cle = f"{experience}:{user_id}".encode()
    h = int(hashlib.sha256(cle).hexdigest()[:8], 16)   # integer between 0 and 16**8 - 1
    position = h / 16**8                               # number between 0 and 1
    return "B" if position < part_b else "A"

print(assigner("u_123", "frais_livraison"))   # always the same answer

Then you check that the split is balanced:

import pandas as pd
groupes = pd.Series([assigner(f"u_{i}", "frais_livraison") for i in range(10_000)])
print(groupes.value_counts(normalize=True))   # close to 50% / 50%

Duration

Even if the sample size is reached in three days, you run the experiment for at least one full week: Monday behavior is not Saturday behavior.

Key takeaways

  • Randomization makes the comparison causal.
  • One hypothesis, one primary metric, guardrails, all defined before launch.
  • Stable assignment by hashing, at the level of the randomization unit.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 2Python in the browser2 hints

    Deterministic assignment

    Write assigner(user_id, experience, part_b=0.5) that returns ‘A’ or ‘B’ based on the SHA-256 hash of the string f"{experience}:{user_id}": take the first 8 hexadecimal characters, convert them to an integer, divide by 16**8, and return ‘B’ if the result is strictly less than part_b.

    import hashlib
    
    def assigner(user_id, experience, part_b=0.5):
        pass
  2. CodeDifficulty 1Python in the browser2 hints

    Checking group balance

    The DataFrame exp contains the users’ assignment and country. Compute repartition, the share of each group (a series indexed by ‘A’ and ‘B’), and part_fr_par_groupe, the share of French users (pays == ‘FR’) in each group.

    import pandas as pd
    
    exp = pd.DataFrame({
        'groupe': ['A', 'B', 'A', 'B', 'A', 'B', 'A', 'B'],
        'pays':   ['FR', 'FR', 'BE', 'FR', 'FR', 'CH', 'FR', 'FR'],
    })
    
    # repartition = ...
    # part_fr_par_groupe = ...
  3. Multiple choiceDifficulty 21 hint

    Choosing the randomization unit

    You are testing a new discount program displayed across the whole site, and your metric is revenue per customer over 30 days. Which randomization unit should you choose?

    • The page view
    • The session
    • The user (customer)
    • The product

End-of-lesson quiz

  1. Why does randomization allow a causal conclusion?

    • Because it increases the sample size
    • Because it makes the groups comparable on average on all factors, measured or not
    • Because it eliminates chance
  2. How many primary metrics should an experiment have?

    • Just one
    • At least five
    • As many as possible
  3. Why run a test for at least one full week?

    • To cover variations between days of the week
    • Because servers require it
    • To lower the alpha threshold

The rest of the course

  1. Sample Size, Minimum Detectable Effect, and Power18 min
  2. Analyzing a Conversion Test: The Two-Proportion Test18 min
  3. Continuous Metrics: Welch's Test and the Bootstrap18 min
  4. A/B Testing Pitfalls: SRM, Peeking, and Multiple Comparisons20 min

A/B Testing and Experimentation · Intermediate · 2 h 44 min