(几乎)从列表中均匀选择项目

Dil*_*rix 5 python random math list permutation

我有一个元素列表N,我想对M (<= N)尽可能均匀分布的值进行采样。更具体地说,选择应该最小化采样点之间的间距差异。例如,假设我正在构造一个布尔索引数组(即 in python)来选择元素,

我尝试了该算法(来自这个类似但不同的问题:How do you split a list into equal-sized chunks?):

q, r = divmod(N, M)
indices = [q*jj + min(jj, r) for jj in range(M)]
Run Code Online (Sandbox Code Playgroud)

有时这很有效:

N=11 M=6
good_index = [0 1 0 1 0 1 0 1 0 1 0]

N=14 M=6
good_index = [0 1 1 0 1 1 0 1 0 1 0 1 0 1]
Run Code Online (Sandbox Code Playgroud)

这里,第一个例子很简单,因为数组可以被均匀划分。第二个例子不能平均划分,但点之间的间距尽可能相似(2,2,1,1,1,1)。

但往往效果不佳:

N=16 M=10
bad_index = [0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0]

N=14 M=10
bad_index = [0 1 0 1 0 1 0 1 0 0 0 0 0 0]
Run Code Online (Sandbox Code Playgroud)

因为你的价值观最终会堆积起来。


编辑 1:哎哟,刚刚意识到上面的每个列表在技术上都是颠倒的(0 应该是 1,反之亦然)....但仍然应该传达正确的想法。


编辑2:上述算法往往效果更好(即通过选择随机数进行目视检查,而不是概念上更简单的算法,例如,

step = int(floor(N/M))
last = M * step  # this prevents us from getting M+1 elements
indices = [ii for ii in range(0, last, step)]
Run Code Online (Sandbox Code Playgroud)

Dil*_*rix 5

查看一些测试的结果(甚至上面包含的测试),问题在于何时M > N/2。即当超过一半的值被采样时。但它非常适合M < N/2. 因此,我目前使用的解决方案只是在以下情况下反转问题M > N/2

注意:这实际上是创建一个大小为 False 的掩码列表,N元素间距M尽可能均匀。

import numpy as np

def even_select(N, M):
    if M > N/2:
        cut = np.zeros(N, dtype=int)
        q, r = divmod(N, N-M)
        indices = [q*i + min(i, r) for i in range(N-M)]
        cut[indices] = True
    else:
        cut = np.ones(N, dtype=int)
        q, r = divmod(N, M)
        indices = [q*i + min(i, r) for i in range(M)]
        cut[indices] = False

    return cut
Run Code Online (Sandbox Code Playgroud)

如果存在更优雅的解决方案,我仍然会感兴趣。