如何使用Python hashlib对客户ID做哈希实现AB测试均等分箱
基于hashlib实现稳定AB组分箱的方案
这种哈希分箱的核心优势是确定性运算,相同输入永远得到相同输出,分箱结果可复现,不需要维护随机种子,同一个用户在同个实验中每次运行都会分到同一个分箱,比随机打乱的方案更适合需要回溯的实验场景。
完整实现代码
import pandas as pd import hashlib # 你原来的测试数据 df = pd.DataFrame({'exp_variant_id' : [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], 'test_control' : ['control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'control', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test'], 'metric_name' : ['order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt', 'order_qnt'], 'metric_value' : [234, 1, 3234, 1, 44, 1, 1, 22, 234, 22, 33, 234, 2, 22, 3, 2, 22, 432, 55, 50, 45, 40, 35, 30, 24, 20, 16, 12, 8, 4, 5, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 75, 79], 'user_client_id' : [21118, 20905444, 56313312, 10738744, 103594830, 83751548, 93299328, 16483186, 130606918, 48995246, 137653680, 63458228, 6968570, 36369312, 13743160, 10331626, 5928990, 50640476, 58021806, 34024250, 4354098, 32314326, 51335518, 31380850, 3710088, 147258556, 6087272, 11858930, 7993504, 100529398, 70127992, 34863920, 18179366, 10651330, 7390348, 18208032, 63247112, 136361832, 36873330, 64035326, 17854770, 106190670, 85201466, 64841914, 116933396, 11482546, 43565902, 112231512, 150755048, 14561480]}) # 自定义参数:每箱用户数,可按需求修改 CHUNK_SIZE = 5 # 自定义盐值:不同实验用不同盐,避免不同实验分箱逻辑重合导致偏差 SALT = 'ab_test_bin_202406' # 哈希转换辅助函数:把输入值转成可排序的大整数 def get_hash_int(input_val): hash_obj = hashlib.md5(f"{SALT}_{str(input_val)}".encode('utf-8')) # 把16进制哈希值转成整数 return int(hash_obj.hexdigest(), 16) # 存储每个用户对应的分箱ID user_bin_map = {} # 按AB组分别分箱 for variant in df['exp_variant_id'].unique(): # 取当前组的唯一用户列表 variant_users = df[df['exp_variant_id'] == variant]['user_client_id'].unique() # 给每个用户计算哈希值,按哈希值排序实现稳定打乱 users_with_hash = [(user, get_hash_int(user)) for user in variant_users] users_with_hash.sort(key=lambda x: x[1]) sorted_users = [item[0] for item in users_with_hash] # 按指定大小切分分箱 for bin_id, start_idx in enumerate(range(0, len(sorted_users), CHUNK_SIZE), 1): current_bin_users = sorted_users[start_idx: start_idx + CHUNK_SIZE] for user in current_bin_users: user_bin_map[user] = bin_id # 把分箱ID映射回原DataFrame df['bin_id'] = df['user_client_id'].map(user_bin_map) # 验证分箱结果 print(df.groupby(['exp_variant_id', 'bin_id'])['user_client_id'].nunique())
注意事项
- 如果担心MD5碰撞,把
hashlib.md5替换成hashlib.sha256即可,逻辑完全一致 - 只要盐值和每箱大小不变,同一个用户的分箱结果永远固定,不会因为重跑代码、新增用户导致历史分箱变动
- 哈希排序的均匀性有保证,分箱用户数差异和随机打乱的效果一致
内容的提问来源于stack exchange,提问作者bigsbi
相关产品推荐
相关产品推荐

