从数据集中随机抽取一定数量的数据

Posted 边城狂人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从数据集中随机抽取一定数量的数据相关的知识,希望对你有一定的参考价值。

今天翻到一个以前回答的问题:从列表中随机抽取一定数量的数据。之前回答是使用 Fisher-Yates 洗牌算法来解决的,但是阅读了评论之后,又有了一些新想法。

先不说是什么算法,只说说随机抽取的思路。

随机抽取的算法演进

假设有 n 个数据保存在一个列表 ​​source​​​ 中(在 javascript 中是数组),需要随机抽取 m (m <= n) 个数据出来,结果放在另一个列表 ​​result​​​ 中。由于随机抽取是一个重复过程,可以使用一个 ​​m​​​ 次的循环来完成,循环体中每次从 ​​source​​​ 中选一个数出来(找到它,并把它从 ​​source​​​ 中删除),依次放在 ​​result​​ 中。用 JavaScript 来描述就是

function randomSelect(source, m) 
const result = [];
for (let i = 0; i < m; i++)
const rIndex = ~~(Math.random() * source.length);
result.push(source[rIndex]);
source.splice(rIndex, 1);

return result;

在多数语言中,从列表中间删除一个数据,都会造成之后的数据重排,是个较低效率的操作。考虑到从一组数据中随机抽取一个是等概率事件,与数据所在的位置无关,我们可以把选出来的数据去掉之后,不减少列表长度,而是直接把列表最后一个数据挪过来。下一次随机取找位置的时候,不把最后一个元素考虑在内。这样改进之后的算法:

function randomSelect(source, m) 
const result = [];
for (let i = 0, n = source.length; i < m; i++, n--)
// ^^^^^^^^^^^^^^^^^ ^^^
const rIndex = ~~(Math.random() * n);
result.push(source[rIndex]);
source[rIndex] = source[n - 1];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

return result;

注意到这里 ​​n--​​​ 和 ​​n - 1​​ 是可以合并的,合并后:

for (let i = 0, n = source.length; i < m; i++) 
...
source[rIndex] = source[--n];

这时候,再次注意到,​​source​​​ 后面没用的空间,其实是和 ​​result​​​ 空间一样大的,如果把这部分空间利用起来,就不再需要 ​​result​​。

function randomSelect(source, m) 
for (let i = 0, n = source.length; i < m; i++)
const rIndex = ~~(Math.random() * n);
--n;
// 交换选中位置的数据和当前最后一个位置的数据
[source[rIndex], source[n]] = [source[n], source[rIndex]];

// 把后面 m 个数据返回出来就是随机选中的
return source.slice(-m); // -m 和 source.length - m 等效

如果保留原来的 ​​result​​​ 及相关算法,会发现 ​​result​​ 和现在返回的数组元素正好是相反序排列的。但是不重要,因为我们的目的是随机选择,不管是否 revert,结果集都是随机的。

但是这么一来,假设 ​​m = source.length​​​,整个 ​​source​​ 中的数据都被随机排列了 —— 这就是 ​Fisher-Yates 算法​​。当然,实际上只需要进行 ​​source.length - 1​​ 次处理就可以达到完全洗牌的效果。

以上是关于从数据集中随机抽取一定数量的数据的主要内容,如果未能解决你的问题,请参考以下文章

Python实现从一个文件夹下随机抽取一定数量(比例)的图片移动到另一个文件夹

R 语言 随机抽样

如何在不替换的情况下随机抽取样本后引导函数

mysql随机抽取一定数量的记录

数据的随机抽取 及 jQuery补充效果(菜单移动)

MySQL随机抽取数据的性能问题