Compute ARPU for a test group and its implicit control
gmv_by_user maps each user id to their GMV; test_users lists the test-group ids. Everyone in gmv_by_user not in test_users is the control group. ARPU = total GMV of a group / number of users in it. Return (arpu_test, arpu_control).
A user with zero GMV still counts in the denominator.
def arpu(gmv_by_user: dict[int, float], test_users: list[int]) -> tuple[float, float]:
# your code here
Write the implementation.
Split users into test (those in test_users) and control (everyone else in gmv_by_user), then divide each group's summed GMV by its user count. The trap is the denominator: a zero-GMV user still counts as a user, so divide by the number of users, not the number of paying users.
- ✗Excluding zero-GMV users from the denominator
- ✗Reporting one pooled ARPU for both groups
- ✗Forgetting control is everyone not in the test set
- →Why must a zero-GMV user stay in the denominator?
- →How would you extend this to ARPPU (paying users only)?
Partition users by the test set, then divide each group's summed GMV by its user count — keeping zero-GMV users in the denominator.
def arpu(gmv_by_user, test_users):
test = set(test_users)
test_gmv = test_n = control_gmv = control_n = 0
for uid, gmv in gmv_by_user.items():
if uid in test:
test_gmv += gmv; test_n += 1
else:
control_gmv += gmv; control_n += 1
return (round(test_gmv / test_n, 2), round(control_gmv / control_n, 2))
For {1:100, 2:100, 3:300, 4:400, 5:700, 6:0} and test=[1,2,6]: test GMV is 200 over 3 users → 66.67; control GMV is 1400 over 3 users → 466.67. User 6 contributes 0 GMV but still counts in the test denominator.