Skip to content
wannacode

A/B test

In short: An A/B test compares two variants on randomly split visitors and checks whether the gap in conversion is real or just noise.

Problem

“Is the new cart better?”

The team made a new cart page: shorter, with fewer fields. Nobody knows whether it is better. So for two weeks every visit to the site sees, at random, either the old (old) or the new (new) cart. The ordered column is 1 if the visit ends with an order:

Pythondata: the shop tables

This is an A/B test: two versions, visitors split at random, one measure. The measure here is the conversion: the share of visits that end with an order. The old cart has 3.10%, the new one 3.72%.

The gap is 0.62 percentage points: a difference between two percentages. That is about one fifth more orders. But is it noise?

The idea

Is the gap noise?

This is the question from the lesson on hypotheses. The null hypothesis: the cart makes no difference. If it is true, the labels old and new do not matter and you can shuffle them. How often do the shuffled data give a gap of at least 0.62 points?

Pythondata: the shop tables

In 2000 shuffles the largest gap is 0.76 points, but only 6 times is it at least as big as the real one. p is 0.003. A gap like this rarely comes from noise.

More precisely, if you are curious

ordered is 0 or 1, so the mean of the column is exactly the share of orders. The shuffling here is a loop: the table has 28,176 rows, and 2000 shuffles at once in one matrix would take a lot of memory in the browser.

Try it

On every device?

Does the lift come from phones or from desktops? Store in rates the conversion in percent for every device (rows) and variant (columns), and in lift the difference new − old for every device, in points.

Pythondata: the shop tables

The idea

The quicker way: chi2_contingency

For two shares there is a ready-made check. First pd.crosstab counts the pairs: how many visits of each variant have an order and how many do not. Then scipy.stats.chi2_contingency says how rare such a table is if the variant makes no difference:

Pythondata: the shop tables

p is 0.0046, about 0.005. The shuffling gave 0.003: both ways say the same. The small difference is because 2000 shuffles measure p roughly (6 cases), and chi2_contingency makes a small correction for a 2 × 2 table, which lifts p slightly.

The idea

How big is the lift?

p only says “hardly noise”. The manager asks something else: how much better is the new cart? For that you need an interval. As in the lesson on intervals, you draw new samples with replacement from each group and watch how the gap changes:

Pythondata: the shop tables

The lift is 0.62 points, and the 95% interval is from 0.21 to 1.04 points. Zero is outside the interval: that is the same “hardly noise”. But the interval also says how much: it may be a little over 0.2, it may be over 1 point.

More precisely, if you are curious

Here there are 2000 repeats, not 10,000, so that it runs fast: each one draws 14,000 rows. By the formula, the interval of a difference between two shares is the difference ± 1.96 × √(p₁(1 − p₁)/n₁ + p₂(1 − p₂)/n₂). It gives almost the same:

Pythondata: the shop tables

The idea

How many visits you need

How long should the test run? You decide that before you start, from two numbers: the conversion so far (3.1%) and the smallest lift that matters to you (0.6 points). There is a rule of thumb for an 80% chance to see the lift at the 0.05 threshold:

visits per group ≈ 16 × p × (1 − p) / lift²

Pythondata: the shop tables

You need 13,351 visits per group, 26,701 in total. The chance that the test catches a real lift is called power. You can check it with a simulation: make up 2000 tests with conversions of 3.1% and 3.7% and count how often p comes out under 0.05. rng.binomial(n, 0.031) gives how many orders come from n visits, each with a 3.1% chance:

Pythondata: the shop tables

With 2000 visits per group the test catches the lift in only 18% of cases, with 5000 in 37%. With 13,351 it is 78%, close to the 80% of the rule. A small test does not say “no difference”: it just cannot see.

More precisely, if you are curious

p_value is the check of two shares by formula (a z-test). It works on whole arrays at once, so 2000 tests take a moment. For a 2 × 2 table it gives almost the same as chi2_contingency. The 16 in the rule comes from the 0.05 threshold and 80% power: (1.96 + 0.84)² × 2 ≈ 16.

Try it

A bigger lift

The product team says: “We only care about a lift of at least 1 point.” How many visits per group does the rule need? Store them in per_group as a whole number.

Pythondata: the shop tables

The idea

Do not peek before the end

The temptation is to look at the result every day. Here is p computed from all visits up to each day:

Pythondata: the shop tables

On day three p is 0.053, on day four 0.077, on day five 0.041. Had you stopped on day five, you would have declared a win. Here the new cart really is better, but that shows from the whole test. The rule “I stop as soon as p falls under 0.05” is the mistake itself.

Why? Check it with an A/A test: both groups see the same thing, and there is no difference. We simulate 1000 such tests of 14 days and look at p every day:

Pythondata: the shop tables

If you only look at the end, the false “wins” are 4.9%, as much as the 0.05 threshold promises. If you look every day and stop at the first p under 0.05, they are 21.7%: four times more. The noise has 14 chances to dip under the line.

1. The test is planned for 14 days. On day five p is 0.041. A colleague says: “It is under 0.05, let’s stop and switch on the new cart.” What is wrong?

The idea

Many metrics, many segments

The new cart brings more orders. Are they bigger, too? Compare the value per order:

Pythondata: the shop tables

€26.30 with the new cart and €26.71 with the old one, p = 0.73. There is no evidence that the cart changes the order value. That is not “we proved they are equal”: there may be a small difference, but this test cannot see it.

Now picture a report with 20 metrics: orders, value, returns, time on the page… Each metric without a real difference has a 5% chance to come out with p under 0.05. How often does at least one “win”?

Pythondata: the shop tables

One metric “wins” in 5.1% of cases, as expected. But at least one of 20 “wins” in 64.1% of tests. The same holds for segments: mobile, desktop, new customers, each day of the week. That is why the main metric is chosen before the test. A chance “win” in the 20th metric is an idea for the next test, not a conclusion.

The idea

Why the test says “causes”

Why do you believe the cart causes more orders, and does not just go along with them? Because the variant was assigned at random. Randomness evens out everything you do not measure. Here are the devices in both groups:

Pythondata: the shop tables

In both groups desktops are 35% and phones 65%. The same goes for the hour, the day, people’s habits: the groups differ only in the cart. So the gap in orders comes from it.

When the data are not from a test, this is not so. In the next lesson a banner seems to lift orders, yet on each device on its own it does not.

Exercise

A report for the manager

The manager asks: “Does the new cart bring more money per visit?” Store in per_visit the average revenue per visit for each variant (all visits, including those without an order), and in p the p-value of the comparison with scipy.stats.ttest_ind.

Pythondata: the shop tables

Had you planned it from the start: how many days should the test have run? The visits per day are len(visits) / 14 on average. Store in per_group the visits per group by the rule (a lift of 0.6 points at 3.1%), in per_day the visits per day, and in days the days, rounded up.

Pythondata: the shop tables

Test

Check yourself

1. The check of the new cart gives p = 0.005. What does that mean?
2. You want to see a lift of 0.3 points instead of 0.6. How many visits do you need?
3. The report tracks 20 metrics. One of them has p = 0.04, the others do not. What do you say?
4. The average order is €26.30 with the new cart and €26.71 with the old one, p = 0.73. Which conclusion is right?
5. Why can you say “the new cart leads to more orders” after the A/B test, and not just “the two go together”?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·