随机抽样

本节仿真与实验设计。我们学习随机抽样的 Urn 模型,并用例子说明随机仿真中偏差和精度带来的问题,如何比较两个实验结果,如何基于仿真进行假设检验。

抽样

我们首先学习抽样方法。A/B 测试就需要抽样。大家有没有注意到,在咱们看过的招聘需求里,都要求统计学知识。抽样就属于统计学知识。

为什么要做抽样呢?因为我们的目标人群很大。目标人群是一个人口的概念,比如我们的目标人群是年龄在 22 岁到 25 岁之间,北京交通大学的硕士研究生。但我又不能把所有这 5000 名研究生全部调研一遍,所以我就抽样 50 个人或者 100 个人,做一个抽样调查。被抽样的同学,就是我的样本。

为了保证调查质量,我们在抽样前,要做好抽样人群的选择,在抽样过程中做好抽样人群的把控,保证我们的测量人群和目标人群匹配。

随机抽样的 Urn 模型

抽样这个统计问题,伯努利在 300 年以前就研究过,并提出了随机抽样遵循 Urn 模型。伯努利在 18 世纪早期提出了这个模型,就是这里有个罐子,罐子里有一些大理石球。这些球有黑的,也有白的。比如:现在有三个黑的,一个白的。我们就从它们中随机抽样。

随机抽样有两种方式:一种是有放回的,就是拿出一个,记下它的颜色后,又把它放回去;一种是不放回的,即拿出后,就不放回了,就少一个这样的了。假设我在咱们 80 个同学里面一次请 10 个同学回答问题,请问这是有放回的,还是不放回的?是不放回的,对吧?如果我是每次找一个同学出来回答问题。他回答完后,坐下,我又在所有的同学里随机找一个人出来回答(也可能再找到前面回答过问题的同学),一共找了10次,这个是有放回的。这两种抽样的模型是不一样的。有放回的是二项分布。无放回的是超几何分布。

AI 辅助的仿真代码编写

很多同学特别向往编程。我们下面就在 AI 的帮助下,尝试编程。

请大家打开任何一个类似 ChatGPT 的 AI 工具,输入:请给我一个特别简单的 Urn 抽样的仿真代码。这些工具一般来说就会给大家一套代码,里面还有注释。大家都学过英语,可以大致看看,试着理解理解这些代码的意思。

你可以让 AI 解释其中的一些语句。如果你觉得它的解释太啰嗦了的话,那么就可以跟它说:你的解释太复杂。我是一位高中生,从来没有编过程,因此,请不要用技术术语说,而是简洁地重新解释一下。它的解释就不会这么啰嗦了。

抽样仿真代码

我们下面用一个例子,来学习基于 Python 的随机抽样仿真代码。这个例子是,我们想仿真:在罐子里放 3 个蓝球,2 个白球,每次从中随机地抽两个,这两个球的颜色一样的概率。为此,我们要仿真 1 万次,然后统计其中出现两个球颜色相同的个数,由此估计它的概率。代码如下图所示。


import numpy as np

n = 10_000

urn = ["b", "b", "b", "w", "w"]

samples = [np.random.choice(urn, size=2, 
      replace=False) for _ in range(n)]

is_matching = [marble1 == marble2 for marble1, 
      marble2 in samples]

print(f"Proportion of samples with matching 
      marbles: {np.mean(is_matching)}")

Proportion of samples with matching marbles: 0.4032

我们来看上面的代码。

首先,我们用 numpy 库里的 random 类的 choice 函数进行随机抽样。numpy 是 Python 里面进行数学运算的库。我们会用到该库里的 random 类的choice 函数进行随机抽样。如上面的代码所示,choice 函数有三个参数:第一个参数是桶里的各种球的列表,包括 3 个蓝球,2 个白球;第二个参数是我们要从中随机抽出几个。我们现在是抽取 2 个;第三个参数是要不要放回。我们设置的是不放回。每次执行这一个语句,它就会返回抽样结果,即两个球的列表。因为是随机抽样,所以每次执行得到的结果都不一样。

其次,我们用 Python 的一种很重要的语法:List Comprehension 来提高仿真的速度。在做数据分析时,我们经常会做大量的数据分析和仿真,因此,我们的数据量会非常大。因此,我们经常用如上图所示的 List Comprehension 语法。它让我们根据一个列表的值创建一个新列表。如上图所示,samples 这行代码就实现了:随机抽样一万次,并且把这 1 万个结果放到 samples 这个列表里。这样的话,我们就避免了写个 for 循环,每得到一个抽样结果后,往 samples 列表里面加。这样一个个加的话,运行起来会特别慢。而像现在这样用 List Comprehension 就快多了。这个是个技巧。请大家掌握。

最后,我们还是用 List Comprehension 进行抽样结果的两个球的颜色的比较,计算出两球一样的出现概率。具体来说,我们在 is_matching 这一行,首先计算抽样结果中两个球颜色是否匹配的布尔值,然后用 numpy 的 mean 函数计算其均值。这样就算出了这 1 万次仿真中,抽中两个相同颜色的球的仿真概率。

让我们来仿真一下。我们运算上面的程序多次。每次都仿真 1 万次。大家会发现,每次程序输出的结果都不一样:有时候 0.52,有时候又 0.45。所以,仿真中随机性的影响还是非常明显的。

排列和组合

我们有时候要做排列和组合的仿真。下面是它们的实现方法。

为了生成组合,我们可以用 Python 的迭代库 itertools 的 combinations 函数。它能够生成各种组合。比如说你送进去 “abcdefg” 和一个 3,它就会给出其中任意三个的字符的组合。注意它是组合,不是排列。它是没有顺序的。


from itertools import combinations
  all_samples = ["".join(sample) for sample in 
      combinations("ABCDEFG", 3)]

 print(all_samples)

['ABC', 'ABD', 'ABE', 'ABF', 'ABG', 'ACD', 
'ACE', 'ACF', 'ACG', 'ADE', 'ADF', 'ADG', 'AEF', 
'AEG', 'AFG', 'BCD', 'BCE', 'BCF', 'BCG', 'BDE', 
'BDF', 'BDG', 'BEF', 'BEG', 'BFG', 'CDE', 'CDF', 
'CDG', 'CEF', 'CEG', 'CFG', 'DEF', 'DEG', 'DFG', 
'EFG']

为了生成排列,我们可以用 permutation 函数,它的用法如下:

from itertools import permutations

print(["".join(sample) for sample in 
    permutations("ABC")])

['ABC', 'ACB', 'BAC', 'BCA', 'CAB', 'CBA']

你看,它给的结果,就考虑顺序了。这些都是我们做仿真的时候用得着的。

分层与聚类

抽样的话,有两种:

常用的是分层抽样。分层抽样方法非常重要。它把样本分为不重叠的层,分别在各层中进行抽样。比如说我把大家分成博士、硕士、本科三层,或者分成 80 岁以上、60~80岁、60岁以下三层。然后在每一层中进行抽样。为什么要做这个分层呢?因为这样可以在层内去掉分层因素的影响,然后又可以通过层间的比较,来分析分层因素的影响。比如说:如果我们测量的东西和大家的学历有关,那么我们按大家的学历分层后,分别抽样、统计,这样得到的结果,在每组同学内部,就没有学历的影响了;而通过比较各组同学的差别,就能够观察学历的影响。

分层抽样能够帮助我们减少覆盖偏差。分层方案的设计,和我们对目标人群的理解有关。通过对目标人群的理解,我们大概知道他们可能分几类,就可以按照这个类别进行分层。这样的话,就能避免覆盖偏差,确保每一类人群都覆盖到了。

还有一种抽样方法是聚类抽样。它不需要我们指定怎么分层,而是先把人群按照它们的各种特征进行聚类,然后在各个类中进行采样。

无放回抽样

我们下面看看无放回抽样。无放回的抽样符合超几何分布(hypergeometric distribution)。我们用 numpy 的 random.hypergeometric 函数来实现这种抽样。如下图所示,这个函数有四个参数。第一个 ngood,指的是咱们桶子里面好球有 4 个,nbad 就是坏球有 3 个,所以一共是 7 个。然后,nsample 是 3,意味着我们会在这 7 个里面抽样 3 个;最后 size = 1 万,意味着我们一共抽样 1 万次。

下图也现实了最后的抽样结果。如下图所示,我们最后会得到一个包括 1 万个元素的数组,里面存着每次抽样得到的好球的个数。比如:第一次抽样 3 个,里面有 1 个好球。这个抽样方法是我们后面要用到的。

simulations_fast = np.random.hypergeometric(
      ngood=4, nbad=3, nsample=3, size=10_000
  )
print(simulations_fast)
  [1 1 2 ... 1 2 2]

我们也可以用 scipy 的 stats 库里的 hypergeom 函数,来计算抽样结果的概率。代码如下:

from scipy.stats import hypergeom
  num_goods = [0, 1, 2, 3]
  pd.DataFrame({
      "Number of Goods": num_goods,
      "Fraction of samples": hypergeom.pmf
      (num_goods, 7, 4, 3),})

    Number of goods    Fraction of Samples
  0        0                    0.03
  1        1                    0.34
  2        2                    0.51
  3        3                    0.11

如上图所示,hypergeom.pmf(num_goods, 7, 4, 3) 就会返回抽样的 3 个样本中有 num_goods 个好球的概率。

民调的随机性

民调是我们工作中经常要做的一个工作。比如调查一下我这个产品受不受欢迎。假设我们从 600 万人中抽样 1500 人。这是一个抽样过程,所以我们来仿真一下可能的民调结果。

假设我们的调查问卷上有三种选择:第一种是喜欢我这个产品的,第二种是不喜欢的,第三种是做不出决定的。假设第一种人在 600 万人中的概率是 0.4818。第二种是 0.4746。因此第一种人比第二种人也就高一点点。因此,我能算出 600 万人中,有多少是喜欢我们产品,又有多少是不喜欢我们产品的,剩下的人就是做不出决定的人。代码和计算结果如下图所示:

proportions = np.array([0.4818, 0.4746, 
      1 - (0.4818 + 0.4746)])

N = 6_165_478

votes = np.trunc(N * proportions).astype(int)

array([2970527, 2926135,  268814])

我们然后在这 600 万人中,随机抽样 1500 人。因为是不放回的抽样,我们用多变量超几何分布的抽样函数 rvs 进行抽样。代码如下图所示。

from scipy.stats import multivariate_hypergeom
n = 1_500
votes = array([2970527, 2926135, 268814])
multivariate_hypergeom.rvs(votes, n)
array([727, 703, 70])

如上图所示,因为我们现在要仿真三种人,所以我们采用 scipy stat 库的函数 multivariate_hypergeom。它能够进行多变量的超几何分布的抽样。因此,我们就把这 600 万人,都扔到桶里面去了,然后进行抽样。抽出来 n = 1500 个人。

如上图所示,这次抽出来的 1500 人中,喜欢的人是 727,不喜欢的是 703,决定不了的是 70 人。这个结果和我们前面说的:第一种人比第二种人多一点点,是一致的。

但是,如果我们多运行几次,就会发现也会出现:在抽样出来的人中,第一种人比第二种人少的情况。这种结果就会让我们得出和我们前面说的:第一种人比第二种人多一点点,相反的观察。

这就是随机抽样带来的结果。我们重复上面的过程,仿真一万次,就会发现其实有大约 40% 的仿真结果中,第一种人比第二种人少的。这是因为在我们的目标人群中,第一种人比第二种人只多一点点。

这时候大家就理解了:抽样不能只看一次的胜负。因为抽样的随机性,所以一次的胜负很可能只是随机抽样的结果。因此,我们最好多抽几遍,看胜负的比例,才能真正推测出目标人群的喜好分布。这也就是抽样结果的 Variation 带来的误差。

民调偏差

我们下面介绍民调时可能出现的偏差,如何减少偏差。

第一种是选择偏差。我们做调查问卷的时候,总是喜欢选择那些比较配合的人,回答我们调查问卷。这就漏掉了目标人群不太配合的人。

第二种是覆盖偏差。我们本来设计得很好的,给他打电话,但他不接电话,或者他没有电话,这就把他给漏了。

所以,我们在调查的时候,一定要首先客观设计,减少选择偏差,然后在调查中间,关注调查进展,及时发现覆盖偏差,然后费尽心思,一定要调查到我们想调查的人。这样才能确保测量人群和目标人群的匹配,使我们的调查有意义。这才是一个专业的调查。否则,我们调查出来的结果可能就是个错的。一旦错了,就可能造成很大的损失。

如果是上门调查的话,我们还要对调查员进行严格的培训。比如:训练调查员不要去引导人家。

为了及早发现问题,进行调整,我们通常还在全面调查之前,先做一个预调研(pilot survey),然后分析其中的偏差,改进问题的提法。对于仪器测量,我们就不断校准仪器,防止偏差。

除了偏差,还有精度的问题。具体来说,用户的选择是会变化的。这也对民调结果产生影响。

小结

本节我们学习了仿真的 Urn 模型,然后通过示例,理解了民调时调查方法偏差的影响,在比较两个方案时要注意它们数据 Scope 的区别,学会了利用仿真进行假设检验。


Index Next