Hexo

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

因果推断——PSM挑选对照组


问题描述

  • 共 100 个实验商家,需要从 2000+ 个商家中使用PSM匹配找到相似商家作为 AB 实验

PSM 基本流程

  • 共三个集合:实验组、对照组(初始化为空)、候选商家集合
  • 特征构造:选择可能影响实验结论的关键特征
  • label 构建:标记 100 个实验商家 label 为 1,其他商家 label 为 0
  • 模型训练:用构建的样本训练一个分类模型,一般为 LR 模型即可
  • PSM 匹配:迭代访问实验商家 A,分别进行如下操作
    • 在候选商家集合中选择模型预估值和 A 差异小于一定阈值的商家作为匹配候选集,从匹配候选集中随机选择一个商家 B
    • 将商家 B 加入对照组,并在候选商家集合中删除对照组

流程思考

  • 如果找不到指定阈值的商家作为匹配候选集

Python 实现 Demo

  • PSM 匹配 Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    import pandas as pd
    import numpy as np
    from sklearn.linear_model import LogisticRegression
    from sklearn.preprocessing import StandardScaler

    # 生成模拟数据(假设有2000个商家)
    np.random.seed(42)
    n = 2000

    # 生成商家特征
    data = pd.DataFrame({
    "shop_id": range(n),
    "monthly_sales": np.random.normal(50000, 15000, n), # 月销售额
    "store_size": np.random.choice([50, 100, 150, 200], n), # 店铺面积
    "is_chain": np.random.binomial(1, 0.3, n), # 是否连锁店
    "city_tier": np.random.choice([1, 2, 3], p=[0.2,0.5,0.3], n), # 城市等级
    "category": np.random.choice(["餐饮", "零售", "服务", "其他"], p=[0.4,0.3,0.2,0.1], n) # 类别
    })

    # 添加实验组标记(随机选择100家作为实验组)
    experiment_group = np.random.choice(n, 100, replace=False)
    data['is_treated'] = data.shop_id.isin(experiment_group).astype(int)

    # 特征预处理
    features = data[['monthly_sales', 'store_size', 'is_chain', 'city_tier']]
    scaler = StandardScaler()
    features_scaled = scaler.fit_transform(features)

    # 训练倾向得分模型
    model = LogisticRegression(max_iter=1000)
    model.fit(features_scaled, data['is_treated'])

    # 计算倾向得分
    data['propensity_score'] = model.predict_proba(features_scaled)[:, 1]

    # PSM匹配函数
    def psm_match(data, treated_col='is_treated', score_col='propensity_score', caliper=0.02):
    treated = data[data[treated_col] == 1]
    control = data[data[treated_col] == 0]

    matches = []
    for _, treat in treated.iterrows():
    # 寻找最近邻匹配
    control['distance'] = abs(control[score_col] - treat[score_col])
    candidates = control[control.distance <= caliper]

    if not candidates.empty:
    # 随机选择最近邻
    match = candidates.sample(1, random_state=42)
    matches.append(match.index[0])
    control = control.drop(match.index)

    return treated, data.loc[matches]

    # 执行匹配
    treated_group, control_group = psm_match(data)

    # 评估匹配质量
    print(f"匹配成功实验商家数:{len(treated_group)}")
    print(f"找到对照组商家数:{len(control_group)}")

    # 检查协变量平衡性
    matched_data = pd.concat([treated_group, control_group])
    for col in ['monthly_sales', 'store_size', 'is_chain']:
    treat_mean = treated_group[col].mean()
    control_mean = control_group[col].mean()
    std_diff = (treat_mean - control_mean) / treated_group[col].std()
    print(f"{col}标准化差异:{std_diff:.3f}")

    # 输出匹配结果
    matched_pairs = pd.DataFrame({
    'experiment_id': treated_group.shop_id.values,
    'control_id': control_group.shop_id.values
    })
  • 代码说明:

    • 数据模拟:生成包含销售额、店铺面积、是否连锁等特征的2000个商家数据
    • 实验组选择:随机选择100个商家作为实验组(实际业务中应根据业务逻辑选择)
    • 特征标准化:对连续变量进行标准化处理
    • 倾向得分模型:使用逻辑回归预测进入实验组的概率
    • 卡钳匹配:设置最大允许得分差异(caliper=0.02),确保匹配质量
    • 平衡性检查:通过标准化差异评估匹配质量(<0.1为良好)

ML——LdaModel在gensim的使用

LDA在Python库gensim中的模型和参数介绍


API

1
2
3
4
5
6
7
class LdaModel(interfaces.TransformationABC, basemodel.BaseTopicModel):
def __init__(self, corpus=None, num_topics=100, id2word=None,
distributed=False, chunksize=2000, passes=1, update_every=1,
alpha='symmetric', eta=None, decay=0.5, offset=1.0, eval_every=10,
iterations=50, gamma_threshold=0.001, minimum_probability=0.01,
random_state=None, ns_conf=None, minimum_phi_value=0.01,
per_word_topics=False, callbacks=None, dtype=np.float32)
1
2
3
# a simple example
import gensim
gensim.models.ldamodel.LdaModel(corpus, num_topics=2, id2word=dictionary, passes=20)

Parameters

  • 主要参数:

    • corpus: 语料库,类似于

      [ [(1, 1),(4, 1)], [(2, 1),(3, 2)] ]

      • gensim库中一般默认corpus参数是经过字典编码统计的,类似于上面的形式,而texts是文本的列表的形式
    • num_topics: 主题数量,超参数

    • id2word: dict of (int, str), :class:gensim.corpora.dictionary.Dictionary

      • 用于将corpus中的数字与词进行对应,这里应该为把texts转成corpus的那个字典
    • passes: 训练时的迭代次数

    • iterations: 推断时的迭代次数

    • alpha: 主题的先验概率

      • 一个num_topics大小的数组表明每个主题的概率
      • 也可以是str类型的值
        • “asymmetric”: 固定初始化为1.0/num_topics
    • decay: (0.5, 1]之间的浮点数,前一个lambda值被遗忘的百分比?【待确认参数】

  • 其他参数:

    • distributed: 是否使用分布式计算

相关类介绍

  • gensim.corpora.dictionary.Dictionary
    1
    2
    class Dictionary(utils.SaveLoad, Mapping):
    def__init__(self, documents=None, prune_at=2000000)
1
2
3
4
5
6
7
# a simple example
from gensim.corpora import Dictionary
texts = [['human', 'interface', 'computer']]
dct = Dictionary(texts) # initialize a Dictionary
dct.add_documents([["cat", "say", "meow"], ["dog"]]) # add more document (extend the vocabulary)
dct.doc2bow(["dog", "computer", "non_existent_word"])
# output: [(0, 1), (6, 1)]

完整代码示例

1…277278279…352
San Ye

San Ye

Stay Hungry. Stay Foolish.

704 posts
53 tags
© 2026 San Ye
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4