【问题标题】:stratified sampling with priors in pythonpython中先验的分层抽样
【发布时间】:2022-09-23 16:09:23
【问题描述】:

语境

应用分层抽样的常见场景是选择一个随机样本,该样本大致保持所选变量的分布,使其具有代表性。

目标:

目标是创建一个函数来执行分层抽样,但提供了一些所考虑变量的比例,而不是原始数据集的比例。

功能:

def stratified_sampling_prior(df,column,prior_dict,sample_size):
   ...
   return df_sampled
  • column:这是一个用于执行分层抽样的分类变量。
  • prior_dict:它包含所选变量中按类别划分的百分比。
  • df:输入数据集。
  • sample_size:这是我们希望获得样本的实例数量。

例子

在这里,我提供了一个工作数据示例:

import pandas as pd

priors_dict = {
  \"A\":0.2
  \"B\":0.2
  \"C\":0.1
  \"D\":0.5
}


df = pd.DataFrame({\"Category\":[\"A\"]*10+[\"B\"]*50+[\"C\"]*15+[\"D\"]*100,
             \"foo\":[\"foo\" for i in range(175)],
             \"bar\":[\"bar\" for i in range(175)]})

使用定义了sample_size 的传统分层抽样,我们将得到以下输出:

df[\"Category\"].value_counts()/df.shape[0]*100
D    57.14
B    28.57
C     8.57
A     5.71

但是,使用prior_dict 时的预期结果是输出的比例为:

df_sample = stratified_sampling_prior(df,\"Category\",prior_dict,sample_size=100):
df_sample[\"Category\"].value_counts()/df_sample.shape[0]*100
D    50.00
B    20.00
C    10.00
A    20.00

    标签: python dataframe sampling


    【解决方案1】:

    从您的问题来看,您是否需要将其作为概率函数尚不清楚。也就是说,比例的期望收敛到先验,还是无论如何你都希望它符合先验?

    如果您希望它符合先前的规定,那么我会看到两个主要问题:

    1. 抽样的随机性可能会受到严重损害 - 想象一下应该包括所有 a 类别行的情况。

    2. 另一方面,有时几乎不可能满足。如果在您的示例中有 0 个 A 示例,则无法使其占采样点的 20%:

      df = pd.DataFrame({"Category":["A"]*0+["B"]*50+["C"]*15+["D"]*100,
                   "foo":["foo" for i in range(165)],
                   "bar":["bar" for i in range(165)]})
      

      概率函数

      在这种情况下,您可以使用先验来计算每个样本的权重。我们需要类别的当前比例,我们可以通过以下方式获得:

      df['Category'].value_counts(normalize=True)
      
      D    0.571429
      B    0.285714
      C    0.085714
      A    0.057143
      

      假设我们从每个条目的权重1 开始,我们现在知道如何缩放每个点以获得新的权重:

      new_weight = desired_proportion / present_proportion
      

      D 为例,这意味着每个示例权重为new_weight = 1 * (0.5 / 0.571) = 0.875。我们需要为每节课重复一遍。

      这是一个可以做到这一点的sn-p:

      prior = {
            "A":0.2,
            "B":0.2,
            "C":0.1,
            "D":0.5
      }
      df['weight'] = 1
      present_dist = df['Category'].value_counts(normalize=True)
      for cat, p in present_dist.items():
          df.loc[df['Category'] == cat, 'weight'] = prior[cat] / (p + 1e-6)
      
      sampledf = df.sample(weights = df['weight'])
      

      测试

      我进行了一些实验,结果表明我们确实收敛到了期望的先验。我进行了 100,000 次实验,这是我们得到的分布:

      {'A': 19917, 'B': 19982, 'C': 9975, 'D': 50126}
      

      这对应于:

      A: 19.92%
      B: 19.98%
      C: 9.975%
      D: 50.13%
      

      编辑:我夸大了 df 大小并使用样本大小 10,000 来查看每个样本是否收敛到所需的分布:

      # df composition (you can see it vastly differs from our desired prior)
      A_l = 90000
      B_l = 4500466
      C_l = 5243287
      D_l = 144144
      
      tot = A_l + B_l + C_l + D_l
      
      df = pd.DataFrame({"Category":["A"]*A_l+["B"]*B_l+["C"]*C_l+["D"]*D_l,
                       "foo":["foo" for _ in range(tot)],
                       "bar":["bar" for _ in range(tot)]})
      

      以下是对 10k 行进行抽样的 10 个测试:

      {'A': 2007, 'B': 2038, 'C': 1029, 'D': 4926}
      {'A': 1999, 'B': 1974, 'C': 1042, 'D': 4985}
      {'A': 2018, 'B': 2024, 'C': 1011, 'D': 4947}
      {'A': 1996, 'B': 2046, 'C': 979, 'D': 4979}
      {'A': 2027, 'B': 2012, 'C': 1043, 'D': 4918}
      {'A': 1991, 'B': 2031, 'C': 1027, 'D': 4951}
      {'A': 1984, 'B': 1984, 'C': 1075, 'D': 4957}
      {'A': 1972, 'B': 2014, 'C': 962, 'D': 5052}
      {'A': 1975, 'B': 1998, 'C': 962, 'D': 5065}
      {'A': 2016, 'B': 1966, 'C': 994, 'D': 5024}
      

      您可以看到,无论分布更改如何,我们都设法强制执行我们的先前操作。 如果您仍然想要确定性函数,请告诉我,但是我强烈建议不要使用它,因为它在数学上不正确,并且会在以后给您带来痛苦。

    【讨论】:

    • 概率方法很好。在您提出的解决方案中,样本量如何?因为目标是抽取一个满足先验集的大小为 n 的样本。
    • 我添加了更多测试以表明它也适用于大样本
    • 感谢您扩展主题,一个问题,样本量控制参数是什么?目标是抽取一个满足先验的给定大小的样本,在答案中我看到了分布,但没有看到选择的大小。
    • 执行代码时,sampledf 结果的形状为 (1,5) 。生成的 df 必须具有已在输入中指示的大小 n。
    • 知道了!谢谢帕特里克教授
    【解决方案2】:

    由于这个线程,获得了以下函数来完成这样的任务,希望这对社区有所帮助。欢迎进一步改进。

    import pandas as pd
    
    def stratified_samping_prior(df,stratify_variable,prior_dict,sample_size, epsilon=1e-6):
      """ By means of a probabilistic function it is fixed the original distribution into a optimal one.
      Input: 
        - df: as an input dataframe.
        - stratify_variable: a string which identifies the colname present in df to perform a stratified weighted sampling with priors by category.
        - prior_dict: a dict with all categories present in stratify_variable and its new proportions.
        - sample_size: the sample size of the output.
      Output:
        - df with the new stratify_variable proportions.
        """
      
      if not all(elem in prior_dict.keys() for elem in list(df[stratify_variable].unique())):
        raise Exception("Update prior dict error: The prior dict has missing categories that are present in the input df.")
        
      # Compute old proportions, the considered hook/variable is bias
      present_dist = df[stratify_variable].value_counts(normalize=True)
      
      # A prior dict is used to correct the old priors with the new ones.
      for cat, p in present_dist.items():
        df.loc[df[stratify_variable] == cat, 'sample_weight'] = prior_dict[cat] / (p + epsilon) 
      
      # Every time the sample is executed there is a probability to have a result, so this is distributed as the prior indicates in a sample_size.
      output_df = pd.concat([df.sample(weights = df['sample_weight']) for experiment in range(0,sample_size)])
    
      return output_df
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-22
      • 1970-01-01
      • 1970-01-01
      • 2016-07-16
      • 1970-01-01
      • 1970-01-01
      • 2022-10-13
      • 2017-10-31
      相关资源
      最近更新 更多