【问题标题】:Return list of weighted objects with semi-randomized ranking返回具有半随机排名的加权对象列表
【发布时间】:2021-11-30 07:14:59
【问题描述】:

假设我有一个对象列表(在 Python 中),看起来像这样(包含标识符和排名/权重):

objects = [
    ("object_1", 0.50),
    ("object_2", 0.75),
    ("object_3", 0.25),
    ("object_4", 0.01),
    ("object_5", 0.99),
]

我想返回同样的 objects 数组,但它们的权重是半随机的。也就是说,我总是不想返回:

[
    ("object_5", 0.99),
    ("object_2", 0.75),
    ("object_1", 0.50),
    ("object_3", 0.25),
    ("object_4", 0.01),
]

但宁愿允许 一些 非确定性,因此,一般来说,返回的数组 看起来像上面但也可能看起来像:

[
    ("object_5", 0.99),
    ("object_1", 0.50),
    ("object_2", 0.75),
    ("object_4", 0.01),
    ("object_3", 0.25),
]

编辑:我认为我问的问题与this one 不同,因为这里的排序很重要;在另一个问题中,顺序无关紧要(我认为!)。

【问题讨论】:

  • 所以它可能是完全随机的?
  • @DaniMesejo 我想原始的objects 数组可能会以相反的顺序(从低权重到高权重)返回,但我希望这种可能性不太可能发生。这就是我所说的“半随机”,虽然我不确定我是否清楚。
  • 谢谢大家!我之前看到过这个问题,但不确定它是否会起作用,因为 - 在我的情况下 - 顺序很重要,而链接的解决方案是针对无序样本的,我认为无论如何。像..我希望排名较高的东西比后面的位置更频繁地出现在第一位置。但是让我玩弄下面发布的答案以及链接的这个答案,并将构建类似的可视化来验证自己:)

标签: python list random ranking weighted


【解决方案1】:

如果我没记错的话,一种方法可能是加权样本而不进行替换:

from random import choices


def weighted_sample_without_replacement(population, weights, k=1):
    #    https://stackoverflow.com/a/43649323/4001592
    weights = list(weights)
    positions = range(len(population))
    indices = []
    while True:
        needed = k - len(indices)
        if not needed:
            break
        for i in choices(positions, weights, k=needed):
            if weights[i]:
                weights[i] = 0.0
                indices.append(i)
    return [population[i] for i in indices]


data = [
    ("object_5", 0.99),
    ("object_2", 0.75),
    ("object_1", 0.50),
    ("object_3", 0.25),
    ("object_4", 0.01),
]

_, weights = zip(*data)
sample = weighted_sample_without_replacement(data, weights, k=len(data))
print(sample)

输出 (单次运行)

[('object_2', 0.75), ('object_5', 0.99), ('object_3', 0.25), ('object_1', 0.5), ('object_4', 0.01)]

一个基本的实验分析似乎验证我的假设:

from collections import defaultdict
from operator import itemgetter

_, weights = zip(*data)
counts = defaultdict(lambda : defaultdict(int))
for _ in range(1000):
    sample = weighted_sample_without_replacement(data, weights, k=len(data))
    for i, (key, _) in enumerate(sample):
        counts[i][key] += 1

for key, values in counts.items():
    print(key, sorted(values.items(), key=itemgetter(1), reverse=True))

输出 (实验)

0 [('object_5', 415), ('object_2', 290), ('object_1', 186), ('object_3', 106), ('object_4', 3)]
1 [('object_2', 322), ('object_5', 309), ('object_1', 241), ('object_3', 119), ('object_4', 9)]
2 [('object_1', 319), ('object_2', 259), ('object_3', 209), ('object_5', 199), ('object_4', 14)]
3 [('object_3', 533), ('object_1', 239), ('object_2', 126), ('object_5', 75), ('object_4', 27)]
4 [('object_4', 947), ('object_3', 33), ('object_1', 15), ('object_2', 3), ('object_5', 2)]

'object_5' 在 1000 次中有 724 次位于前两个位置,而在 1000 次中,'object_4' 位于最后一个位置 947 次。为了更好地可视化结果,请参见下图(可视化由额外运行的实验设置生成):

复制实验的代码可以在here找到。

【讨论】:

  • 感谢@Dani Masejo!您介意偶然粘贴可视化代码吗?我刚刚回到这个问题上,想玩一些不同的策略(包括你的!)并且已经用 matplotlib 敲了一个小时:P
  • @stevenchusm 更新了答案,其中包含重现实验和绘图的文件的链接
  • 再次感谢您抽出宝贵时间。但我想提出什么可能是一个问题?在这里查看我的 cmets:gist.github.com/chusteven/3bf766d56b0811b592dc3fb4988b9fa2 -- 我绝对不是统计专家,但感觉关键是我们是否在每次通过时选择了单个元素?想知道你的想法:) 无论如何,我实际上找到了一个解决方案,我会尽快发布,以防人们可以在 [0, 1) 之间设置权重gist.github.com/chusteven/0e9af20af4b7357ccf9ea22f633d3f2f
  • @stevenchusm 我不明白,解决方案有什么问题?
  • 我认为在您的解决方案中,您可能会在 while 循环的每次迭代中选择多个元素。但我认为你需要在每次迭代中只选择一个元素,对吧?我的原因是,在您选择一个元素后,weights 需要完全重新平衡?啊,对不起,我不确定我是否让自己清楚,并且通过 DM 更容易解释。我想另一件事是choices 返回一个无序的选择,对吧?
【解决方案2】:

如果您能够确保weight 值始终介于 [0, 1) 之间,那么以下代码将起作用!

from random import random


def weighted_sample_without_replacement(
    population: List[Tuple[Any, float]], weights: tuple
) -> List[Tuple[Any, float]]:
    return sorted(population, key=lambda x: x[1] * random())

population 的样子:

[
    ("object_5", 0.99),
    ("object_2", 0.75),
    ("object_1", 0.50),
    ("object_3", 0.25),
    ("object_4", 0.01),
]

weights点赞:

(
    0.99,
    0.75,
    0.50,
    0.25,
    0.01,
)

【讨论】:

    猜你喜欢
    • 2019-09-28
    • 2015-07-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    • 2019-08-24
    • 2014-07-21
    • 2017-08-10
    • 2023-03-18
    相关资源
    最近更新 更多