代码之家  ›  专栏  ›  技术社区  ›  Max Ghenis shoyer

如何产生一个预算约束的加权随机样本,其中的项目有不同的概率和权重?

  •  1
  • Max Ghenis shoyer  · 技术社区  · 7 年前

    假设我想从一组三个记录中选择两个记录,其中三个记录的概率分别为0.1、0.5和0.4。每 this SO answer , numpy.random.choice 将工作:

    import pandas as pd
    from numpy import random
    
    df = pd.DataFrame({'prob': [0.1, 0.5, 0.4]})
    
    random.seed(0)
    random.choice(df.index, p=df.prob, size=2, replace=False)
    # array([1, 2])
    

    现在假设每个项目也有一个权重,我想选择一个最大权重,而不是选择两个项目。因此,如果这些项目的权重为4、5和6,而我的预算为10,我可以选择0、1或0、2。包含的每个项目的相对概率仍然由概率控制(尽管在实践中,我认为算法会更频繁地返回项目1,因为它的低权重可以作为填充物)。

    有办法适应吗 random.choice 为了这个,还是另一种方法来产生这个结果?

    2 回复  |  直到 7 年前
        1
  •  1
  •   Ben.T    7 年前

    你能做的就是 np.random.choice 像你这样的概率,除了你的全部数据。然后 reindex 这个 df 有了你的新订单 NP.随机选择 . 使用 cumsum 在列权重上,最后只返回索引,直到它达到所需的值。

    def weighted_budgeted_random_sample_all(df, budget):
       random_index_order = np.random.choice( df.index, size = len(df), 
                                              p = df.prob, replace = False)
       s = df.reindex(random_index_order).weight.cumsum()
       return s[s <= budget].index.values
    

    现在这个方法的问题是 东风 就像问题和 budget 在10个,那么一些解决方案只是索引1或2,因为如果 random_index_order 等于 [2,1,0] [1,2,0] 然后 累加 第二排高于10。

    看一看 Counter 的使用 tuple np.sort 只是为了 计数器 工作更容易看到结果:

    from collections import Counter
    print (Counter([ tuple(np.sort(weighted_budgeted_random_sample_all(df,10))) 
                     for i in range(1000)]))
    # Counter({(0, 1): 167, (0, 2): 111, (1,): 390, (2,): 332})
    

    如您所见,有些绘图的顺序是2和3作为前2个值,结果只有2或3,因为它们的权重之和是11。

    但是事实上,如果你尝试同样的方法,预算为11,那么你就得到了预期的产出:

    print (Counter([ tuple(np.sort(weighted_budgeted_random_sample_all(df,11))) 
                     for i in range(1000)]))
    # Counter({(0, 1): 169, (0, 2): 111, (1, 2): 720})
    

    在这里你可以找到三个可能的集合,事实是集合 {1,2} 得到的往往是有意义的。

    我看到你在评论说你之后修改了你的问题 我会一个项目一个项目的方法 . 我相信这样做会对总的概率产生影响,但我不知道为什么。如果你真的想要,那么我想你可以把你的方法和我的方法结合起来,以获得一些时间:

    def weighted_budgeted_random_sample_mixed(df, budget):
        ids = []
        total = 0
        dftemp = df.copy()
        while total < budget:
            remaining = budget - total
            dftemp = dftemp[dftemp.weight <= remaining]
            # Stop if there are no records with small enough weight.
            if dftemp.shape[0] == 0:
                break
            # New order
            new_index = np.random.choice( dftemp.index, size = len(dftemp), 
                                          p = (dftemp.prob/dftemp.prob.sum()), 
                                          replace = False)
            s = dftemp.reindex(new_index).weight.cumsum()
            #select only the necessary rows
            s = s[s <= remaining] 
            total += s.max() #last value in s which is less than remaining
            dftemp.drop(s.index, inplace=True)
            ids += s.index.tolist()
        return ids
    

    现在就结果与您的方法进行比较:

    #your approach
    print (Counter([ tuple(np.sort(weighted_budgeted_random_sample(df,10))) 
                     for i in range(1000)]))
    #Counter({(0, 1): 546, (0, 2): 454})
    
    #mixed approach
    print (Counter([ tuple(np.sort(weighted_budgeted_random_sample_mixed(df,10))) 
                     for i in range(1000)])
    #Counter({(0, 1): 554, (0, 2): 446})
    

    正如您所看到的,结果非常相似,在较大的数据帧上混合方法应该更快,因为它将 while

        2
  •  1
  •   Max Ghenis shoyer    7 年前

    以下是一次一次的方法:

    1. 获取一组权重低于预算的项目。
    2. 根据每个项目的概率从该集合中选择一个随机项目。
    3. 将此项添加到运行列表中,并将其从可用项集中删除。
    4. 重复1-3,直到没有剩余项目可以填补应计权重和预算之间的空白。

    这里有一个函数来执行它,正如预期的那样,它只生成示例中的集合0、1和0、2:

    def weighted_budgeted_random_sample(df, budget):
        """ Produce a weighted budgeted random sample.
    
        Args:
            df: DataFrame with columns for `prob` and `weight`.
            budget: Total weight budget.
    
        Returns:
            List of index values of df that constitute the sample.
    
        """
        ids = []
        total = 0
        while total < budget:
            remaining = budget - total
            df = df[df.weight <= remaining]
            # Stop if there are no records with small enough weight.
            if df.shape[0] == 0:
                break
            # Select one record.
            selection = random.choice(df.index, p=(df.prob / df.prob.sum()))
            total += df.loc[selection].weight
            df.drop(selection, inplace=True)
            ids.append(selection)
        return ids
    

    例子:

    df = pd.DataFrame({
        'weight': [4, 5, 6],
        'prob': [0.1, 0.5, 0.8]
    })
    
    weighted_budgeted_random_sample(df, 10)
    # [2, 0]
    

    这可能可以通过从 random.choice 对于一些不受预算约束的项目。