具有人口平衡的分层随机抽样
Posted
技术标签:
【中文标题】具有人口平衡的分层随机抽样【英文标题】:Stratified random sampling with Population Balancing 【发布时间】:2018-05-14 11:44:19 【问题描述】:考虑如下所示的类分布偏斜的人口
ErrorType Samples
1 XXXXXXXXXXXXXXX
2 XXXXXXXX
3 XX
4 XXX
5 XXXXXXXXXXXX
我想从 40 个中随机抽取 20 个样本,而不会对参与人数较少的任何课程进行欠采样。例如在上面的情况下,我想采样如下
ErrorType Samples
1 XXXXX|XXXXXXXXXX
2 XXXXX|XXX
3 XX***|
4 XXX**|
5 XXXXX|XXXXXXX
即-1型和-2型的5个和-3型的,-3型的2个和-4型的3个
-
这保证我的样本大小接近我的目标,即 20 个样本
没有一个课程参与其中,尤其是课程 -3 和 -4。
我最终写了一个迂回的代码,但我相信可以有更简单的方法来利用 pandas 方法或一些 sklearn 函数。
sample_size = 20 # Just for the example
# Determine the average participaction per error types
avg_items = sample_size / len(df.ErrorType.unique())
value_counts = df.ErrorType.value_counts()
less_than_avg = value_counts[value_counts < avg_items]
offset = avg_items * len(value_counts[value_counts < avg_items]) - sum(less_than_avg)
offset_per_item = offset / (len(value_counts) - len(less_than_avg))
adj_avg = int(non_act_count / len(value_counts) + offset_per_item)
df = df.groupby(['ErrorType'],
group_keys=False).apply(lambda g: g.sample(min(adj_avg, len(g)))))
【问题讨论】:
所以提供的数据是您实际拥有的数据还是为了说明问题? @Bharath:用于说明目的。 出于好奇,您是否可以向我们展示实际数据的样本?我看到的所有数据都是要替换的正则表达式。但这与字符串无关吧? 数据采用pandas.dataframe
的形式,包含 100 列和数百万行各种数据类型(字符串、整数、浮点数、基数)。我用来分层的类是一个类别代码,目前有 15 个类别代码,但会增长。我的用例是 ML,而不是一些文本处理。请参考我在问题中包含的示例代码。
现在很有趣。您想要从每行中抽取最多 5 个样本的样本,对吗?即使一行少于 5 行,它们都应该存在。
【参考方案1】:
您可以使用辅助列来查找长度大于样本大小的样本并使用pd.Series.sample
即
例子:
df = pd.DataFrame('ErrorType':[1,2,3,4,5],
'Samples':[np.arange(100),np.arange(10),np.arange(3),np.arange(2),np.arange(100)])
df['new'] =df['Samples'].str.len().where(df['Samples'].str.len()<5,5)
# this is let us know how many samples can be extracted per row
#0 5
#1 5
#2 3
#3 2
#4 5
Name: new, dtype: int64
# Sampling based on newly obtained column i.e
df.apply(lambda x : pd.Series(x['Samples']).sample(x['new']).tolist(),1)
0 [52, 81, 43, 60, 46]
1 [8, 7, 0, 9, 1]
2 [2, 1, 0]
3 [1, 0]
4 [29, 24, 16, 15, 69]
Name: sample2, dtype: object
我写了一个函数来返回带有 thresh 的样本大小,即
def get_thres_arr(sample_size,sample_length):
thresh = sample_length.min()
size = np.array([thresh]*len(sample_length))
sum_of_size = sum(size)
while sum_of_size< sample_size:
# If the lenght is more than threshold then increase the thresh by 1 i.e
size = np.where(sample_length>thresh,thresh+1,sample_length)
sum_of_size = sum(size)
#increment threshold
thresh+=1
return size
df = pd.DataFrame('ErrorType':[1,2,3,4,5,1,7,9,4,5],
'Samples':[np.arange(100),np.arange(10),np.arange(3),np.arange(2),np.arange(100),np.arange(100),np.arange(10),np.arange(3),np.arange(2),np.arange(100)])
ndf = pd.DataFrame('ErrorType':[1,2,3,4,5,6],
'Samples':[np.arange(100),np.arange(10),np.arange(3),np.arange(1),np.arange(2),np.arange(100)])
get_thres_arr(20,ndf['Samples'].str.len())
#array([5, 5, 3, 1, 2, 5])
get_thres_arr(20,df['Samples'].str.len())
#array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
现在你得到了你可以使用的尺寸:
df['new'] = get_thres_arr(20,df['Samples'].str.len())
df.apply(lambda x : pd.Series(x['Samples']).sample(x['new']).tolist(),1)
0 [64, 89]
1 [4, 0]
2 [0, 1]
3 [1, 0]
4 [41, 80]
5 [25, 84]
6 [4, 0]
7 [2, 0]
8 [1, 0]
9 [34, 1]
希望对您有所帮助。
【讨论】:
但是您是如何计算出限制为 5 的?就我而言,它是 5,因为这保证即使某些课程的物品少于 5,我也会拿起 20 件物品。 你在代码中硬编码了 5,但没有显示你是如何得到这个幻数的。 你的代码是我的变种,只是你没有展示如何计算adj_avg
@Abhijit 所以你想找到那个神奇的数字,所以从每行取样后的整个总样本量是 20 我对吗?
是和不是。如果有一些函数可以在不确定每个类的样本大小的情况下进行抽样,它也应该可以工作。【参考方案2】:
哇。让书呆子对这个嗤之以鼻。我已经编写了一个函数,它可以在 numpy 中执行您想要的操作,没有任何神奇的数字......它并不漂亮,但我不能浪费所有时间写一些东西而不将它作为答案发布。现在有两个输出 n_for_each_label
和 random_idxs
分别是每个类的选择数和随机选择的数据。我想不出当你有random_idxs
时为什么你会想要n_for_each_label
。
编辑: 据我所知,在 scikit 中没有执行此操作的功能,这不是为 ML 划分数据的一种非常常见的方法,所以我怀疑是否存在任何问题。
# This is your input, sample size and your labels
sample_size = 20
# in your case you'd just want y = df.ErrorType
y = np.hstack((np.ones(15), np.ones(8)*2,
np.ones(2)*3, np.ones(3)*4,
np.ones(12)*5))
y = y.astype(int)
# y = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
# 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
# Below is the function
unique_labels = np.unique(y)
bin_c = np.bincount(y)[unique_labels]
label_mat = np.ones((bin_c.shape[0], bin_c.max()), dtype=int)*-1
for i in range(unique_labels.shape[0]):
label_loc = np.where(y == unique_labels[i])[0]
np.random.shuffle(label_loc)
label_mat[i, :label_loc.shape[0]] = label_loc
random_size = 0
i = 1
while random_size < sample_size:
i += 1
random_size = np.sum(label_mat[:, :i] != -1)
if random_size == sample_size:
random_idxs = label_mat[:, :i]
n_for_each_label = np.sum(random_idxs != -1, axis=1)
random_idxs = random_idxs[random_idxs != -1]
else:
random_idxs = label_mat[:, :i]
last_idx = np.where(random_idxs[:, -1] != -1)[0]
n_drop = random_size - sample_size
drop_idx = np.random.choice(last_idx, n_drop)
random_idxs[drop_idx, -1] = -1
n_for_each_label = np.sum(random_idxs != -1, axis=1)
random_idxs = random_idxs[random_idxs != -1]
输出:
n_for_each_label = 数组([5, 5, 2, 3, 5])
要抽样的每种错误类型的编号,或者如果您想跳到最后:
random_idxs = array([ 3, 11, 8, 13, 9, 22, 15, 17, 20, 18, 23, 24, 25, 26, 27, 36, 32, 38, 35, 33])
【讨论】:
你知道@ncfirth Op 正在寻找一种找到阈值的方法,即他正在寻找每行中有多少样本会导致所需的样本量,即如果他有 10,10,3,2 ,10 那么他想要 5,5,3,2,5 所以这将等于 20。如果他有 10,10,4,4,10 那么他想要 4,4,4,4,4 所以它将是等于 20。 在我的回答中的示例数据上运行你的代码,没有人能理解仅仅是代码。您应该提及如何将其插入 OPs 数据框。 我的答案中也有示例数据,不过我可以让它更明确。鉴于 OP 的问题,这是一个微不足道的联系y=df.ErrorType
【参考方案3】:
没有神奇的数字。简单地从整个人口中抽样,以明显的方式编码。
第一步是将每个“X”替换为其所在层的数字代码。这样编码后,整个人口都存储在一个字符串中,称为entire_population
。
>>> strata =
>>> with open('skewed.txt') as skewed:
... _ = next(skewed)
... for line in skewed:
... error_type, samples = line.rstrip().split()
... strata[error_type] = samples
...
>>> whole = []
>>> for _ in strata:
... strata[_] = strata[_].replace('X', _)
... _, strata[_]
... whole.append(strata[_])
...
('3', '33')
('2', '22222222')
('1', '111111111111111')
('5', '555555555555')
('4', '444')
>>> entire_population = ''.join(whole)
给定sample_size
必须为20的约束,从整个总体中随机抽样,形成一个完整的样本。
>>> sample = []
>>> sample_size = 20
>>> from random import choice
>>> for s in range(sample_size):
... sample.append(choice(entire_population))
...
>>> sample
['2', '5', '1', '5', '1', '1', '1', '3', '5', '5', '5', '1', '5', '2', '5', '1', '2', '2', '2', '5']
最后,通过计算其中每个层的代表,将样本表征为抽样设计。
>>> from collections import Counter
>>> Counter(sample)
Counter('5': 8, '1': 6, '2': 5, '3': 1)
【讨论】:
以上是关于具有人口平衡的分层随机抽样的主要内容,如果未能解决你的问题,请参考以下文章