繁体   English   中英

随机抽样python集而不转换为列表

[英]Randomly Sample a python Set without converting to list

我花了很多时间阅读关于在python中获取随机样本的各种答案, random.sample似乎是自然而且最常见的选择,但是我试图从python set对象中进行采样并且希望能够做到这一点有效率的。

由于python中非常好用且高效的设置功能(交叉点,差异等),我使用的是一组。 对于我的目的,集合是一种非常有效的数据结构,而列表则没有。 我有一个算法情况,我在一个集合中有N元素,并且可能需要为集合的每个采样采用任意大小的N个子样本。 集合的每个子采样都不是完全相同的集合,并且由我必须生成子样本的每个元素的属性定义。 这是一些模糊的代码,它演示了算法的复杂性:

main_set = set(...) # Values sourced from elsewhere.
capacity = 20

for element in list:
    potential_values = main_set - element.set # Exclude values already in element
    sample_size = capacity - len(element.set) # Num needed to fill the set to capacity
    new_vals = sample(potential_values, sample_size) # <- insert sampling idea here

    element.set = element.set | new_vals # Union of sample and element set

根据我在网上和在某些测试中收集的内容, random.sample似乎将一个set转换为一个list对象。 main_set - element.set的大小main_set - element.setpotential_values几乎总是远大于element.set的大小,因此如果每次采样必须将potential_values转换为列表,那么算法将极大地受到性能的影响。

那么,有没有人对如何有效地使用集合有任何建议或想法? 我很欣赏有关此问题的任何意见,在任何人跳到“过早优化”例程之前,我非常清楚它将要执行的规模以及O(n)和O之间的差异(n ^ 2)非常可观。


澄清编辑:

我特别关心提供的任何sample()方法的输出。 我从拉动实际样品potential_values相比,规模较小 potential_values 相反,所有建议的sample()方法都需要类似列表的输入才能工作,这意味着必须首先将potential_values转换为可索引类型,这是我想要避免的。

我现在也意识到我以非常模糊的方式提出了大O符号,可能不应该有。 当我的意思是我想避免O(n ^ 2)时,我的意思是我想避免在循环中添加另一个O(n)操作。 正如我所指出的那样main_set - element.setlist(main_set)具有相同的时间复杂度,因此它已经是O(n ^ 2)。 添加list转换使整个算法更像O(2n ^ 2),但这些都不是很重要。

你可以使用heapq.nlargest ,它可以接受任何迭代,并提供一个随机密钥来选择,例如:

import random, heapq

sample = heapq.nlargest(sample_size, your_set, key=lambda L: random.random())

注意 - 这将为您提供一个list对象,因此您需要在必要时进行转换...

在IPython中快速尝试计时表明使用heapq.nlargest不一定比现有方法更好,适当调整实际数据的特征:

import random
import heapq

set_size = 100000
sample_size = 1000

def sample_heapq(your_set, sample_size):
    sample = heapq.nlargest(sample_size, your_set, key = lambda e: random.random())
    return sample

def sample_original(your_set, sample_size):
    sample = random.sample(your_set, sample_size)
    return sample

eg_set = set(range(sample_size))

通过timeit运行这些:

%timeit sample_heapq(eg_set, sample_size)
1000 loops, best of 3: 523 µs per loop

%timeit sample_original(eg_set, sample_size)
1000 loops, best of 3: 479 µs per loop

正如@ user2357112建议的那样,这里是我原始问题中的代码的拒绝采样版本,它有效地从源集合中采样n个元素,因为我只是从main_set中采样尚未在elements.set

main_set = set(...) # Values sourced from elsewhere.
capacity = 20
listed_set = list(main_set) # initially convert set to list so we can sample
for element in list:
    while len(element.set) < capacity
        item = random.choice(listed_set)
        element.set.add(item) # Sets cannot contain duplicates, no conditional required

虽然这不能解决如何直接从python中的set中进行采样的问题,但它确实有效地解决了我的算法尝试做的事情。 如果过了一段时间,没有人想出直接从集合中采样的想法或比这更有效的东西,我可能会将此标记为答案。 感谢@ user2357112的想法!


正如@LieRyan指出的那样,如果element.setmain_set重叠很大的百分比,则此算法将无法从random.choice()获得非重叠项。 因此,如果我们期望高重叠(例如可能是50%),那么只需使用main_set - element.set获取两个集之间的唯一项,并将其转换为列表将比此方法快得多。 本质上,此算法适用于main_setelement.set重叠非常少的情况,作为main_set的百分比。

取决于您对随机的定义。

只是一些元素,我不在乎哪个:

[s.copy().pop() for i in range(count)]  # with replacement

copy = s.copy()
[copy.pop() for i in range(count)]  # without replacement

具有体面[伪]随机分布的元素:

copy = list(s)
random.sample(copy, count)

可重复的伪随机分布:

copy = sorted(s)
# random.seed(...)
random.sample(copy, count)

可重复的伪随机, 假设具有较少的运行时开销:

heapq.nlargest(...)  # per Jon or Marius

讨论:

  • set.pop()已经删除并返回任意元素,但是如果对象散列值在set中是相同的,那么它是非常可预测的,例如,如果每次都是相同的数字集,那么每次set都不同时可以接受
  • set.copy()O(N)
  • sorted();list.sort()O(NlogN)分摊的,可能因为set是由hash随机化的
  • heapq.nlargest可以是每个中位数的 O(N) ,Python实现是一个恒定大小的二进制堆,使其成为O(N*log(n)) ,因为N个元素通过大小为n的堆筛来过滤。 请注意,sing key=添加一个值得注意的线性开销,因此O(C*N*log(n)) ,您的域将确定C*log(n) <?> logN

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM