考虑以下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.5
和1
概率为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)
答案 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)