从具有定义概率的Pandas组中进行采样

时间:2018-05-14 12:16:19

标签: python pandas

考虑以下Pandas数据框,

df = pd.DataFrame(
    [
         ['X', 0, 0.5],
         ['X', 1, 0.5],

         ['Y', 0, 0.25],
         ['Y', 1, 0.3],
         ['Y', 2, 0.45],

         ['Z', 0, 0.6],
         ['Z', 1, 0.1],
         ['Z', 2, 0.3]
    ], columns=['NAME', 'POSITION', 'PROB'])

请注意df定义了每个唯一NAME值的离散概率分布,即

assert ((df.groupby('NAME')['PROB'].sum() - 1)**2 < 1e-10).all()

我想做的是从这些概率分布中抽样。

我们可以将POSITION视为与概率对应的值。因此,在考虑X时,样本将0概率0.51概率为0.5

我想创建一个新的数据框,其中['NAME', 'POSITION', 'PROB', 'SAMPLE']列代表这些样本。每个唯一SAMPLE值代表一个新样本。 PROB列现在始终为0或1,表示在给定样本中是否选择了给定行。例如,如果我要选择3个样本,则示例结果如下:

df_samples = pd.DataFrame(
    [
         ['X', 0, 1, 0],
         ['X', 1, 0, 0],
         ['X', 0, 0, 1],
         ['X', 1, 1, 1],
         ['X', 0, 1, 2],
         ['X', 1, 0, 2],

         ['Y', 0, 1, 0],
         ['Y', 1, 0, 0],
         ['Y', 2, 0, 0],
         ['Y', 0, 0, 1],
         ['Y', 1, 0, 1],
         ['Y', 2, 1, 1],
         ['Y', 0, 1, 2],
         ['Y', 1, 0, 2],
         ['Y', 2, 0, 2],

         ['Z', 0, 0, 0],
         ['Z', 1, 0, 0],
         ['Z', 2, 1, 0],
         ['Z', 0, 0, 1],
         ['Z', 1, 0, 1],
         ['Z', 2, 1, 1],
         ['Z', 0, 1, 2],
         ['Z', 1, 0, 2],
         ['Z', 2, 0, 2],
    ], columns=['NAME', 'POSITION', 'PROB', 'SAMPLE'])

当然,由于涉及随机性,这只是众多可能结果之一。

该程序的单元测试将是随着样本的增加,根据大数定律,每个(NAME, POSITION)对的样本的平均数应该倾向于实际概率。可以根据使用的总样本计算置信区域,然后确保真实概率在其中。例如,使用normal approximation to binomial outcomes(要求总样本n_samples为“大”&#39;)( - 4 sd,4 sd)区域测试将是:

z = 4

p_est = df_samples.groupby(['NAME', 'POSITION'])['PROB'].mean()
p_true = df.set_index(['NAME', 'POSITION'])['PROB']

CI_lower = p_est - z*np.sqrt(p_est*(1-p_est)/n_samples)
CI_upper = p_est + z*np.sqrt(p_est*(1-p_est)/n_samples)

assert p_true < CI_upper
assert p_true > CI_lower

Pandas最有效的方法是什么?我觉得我想将一些sample函数应用于df.groupby('NAME')对象。

P.S。

更为明确的是,这是一个使用Numpy做这件事的漫长道路。

n_samples = 3
df_list = []
for name in ['X', 'Y', 'Z']:
    idx = df['NAME'] == name
    position_samples = np.random.choice(df.loc[idx, 'POSITION'], 
                                        n_samples, 
                                        p=df.loc[idx, 'PROB'])
    prob = np.zeros([idx.sum(), n_samples])
    prob[position_samples, np.arange(n_samples)] = 1
    position = np.tile(np.arange(idx.sum())[:, None], n_samples)
    sample = np.tile(np.arange(n_samples)[:,None], idx.sum()).T

    df_list.append(pd.DataFrame(
        [[name, prob.ravel()[i], position.ravel()[i], 
          sample.ravel()[i]] 
         for i in range(n_samples*idx.sum())], 
        columns=['NAME', 'PROB', 'POSITION', 'SAMPLE']))

df_samples = pd.concat(df_list)

1 个答案:

答案 0 :(得分:1)

如果我理解正确,您正在寻找groupby + sample,然后是一些索引材料

概率的第一个样本:

n_samples = 3
df_samples = df.groupby('NAME').apply(lambda x: x[['NAME', 'POSITION']] \
                               .sample(n_samples, replace=True,
                                       weights=x.PROB)) \
                               .reset_index(drop=True)

现在添加额外的列:

df_samples['SAMPLE'] = df_samples.groupby('NAME').cumcount()
df_samples['PROB'] = 1


print(df_samples)

  NAME  POSITION  SAMPLE  PROB
0    X         1       0     1
1    X         0       1     1
2    X         1       2     1
3    Y         1       0     1
4    Y         1       1     1
5    Y         1       2     1
6    Z         2       0     1
7    Z         0       1     1
8    Z         0       2     1

请注意,这并不包括初始问题中所请求的每个样本的0个概率位置,但它是一种更简洁的信息存储方式。

如果我们想要包含0个概率位置,我们可以在其他位置合并如下:

domain = df[['NAME', 'POSITION']].drop_duplicates()
df_samples.drop('PROB', axis=1, inplace=True)
df_samples = pd.merge(df_samples, domain, on='NAME', 
                      suffixes=['_sample', ''])
df_samples['PROB'] = (df_samples['POSITION'] ==
                     df_samples['POSITION_sample']).astype(int)
df_samples.drop('POSITION_sample', axis=1, inplace=True)