我需要对bed
文件随机排序 10000 次,每次取前 1000 行。目前,我正在使用以下代码:
for i in {1..100}; do
for j in {1..100}; do
sort -R myfile.bed_sorted | tail -n 1000 > myfile.bed.$i.$j.bed
done
done
对每个文件执行此操作大约需要 6 个小时。我大约有 150 个需要解决。有没有更快的解决方案?
我有数据样本(myfile.bed_sorted):
chr1 111763899 111766405 peak1424 1000 . 3224.030 -1 -1
chr1 144533459 144534584 peak1537 998 . 3219.260 -1 -1
chr8 42149384 42151246 peak30658 998 . 3217.620 -1 -1
chr2 70369299 70370655 peak16886 996 . 3211.600 -1 -1
chr8 11348914 11352994 peak30334 990 . 3194.180 -1 -1
chr21 26828820 26830352 peak19503 988 . 3187.820 -1 -1
chr16 68789901 68791150 peak11894 988 . 3187.360 -1 -1
chr6 11458964 11462245 peak26362 983 . 3169.750 -1 -1
chr1 235113793 235117308 peak2894 982 . 3166.000 -1 -1
chr6 16419968 16422194 peak26522 979 . 3158.520 -1 -1
chr6 315344 321339 peak26159 978 . 3156.320 -1 -1
chr1 111756584 111759633 peak1421 964 . 3110.520 -1 -1
chrX 12995098 12997685 peak33121 961 . 3100.000 -1 -1
chr9 37408601 37410262 peak32066 961 . 3100.000 -1 -1
chr9 132648603 132651523 peak32810 961 . 3100.000 -1 -1
chr8 146103178 146104943 peak31706 961 . 3100.000 -1 -1
chr8 135611963 135614649 peak31592 961 . 3100.000 -1 -1
chr8 128312253 128315935 peak31469 961 . 3100.000 -1 -1
chr8 128221486 128223644 peak31465 961 . 3100.000 -1 -1
chr8 101510621 101514237 peak31185 961 . 3100.000 -1 -1
chr8 101504210 101508005 peak31184 961 . 3100.000 -1 -1
chr7 8173062 8174642 peak28743 961 . 3100.000 -1 -1
chr7 5563424 5570618 peak28669 961 . 3100.000 -1 -1
chr7 55600455 55603724 peak29192 961 . 3100.000 -1 -1
chr7 35767878 35770820 peak28976 961 . 3100.000 -1 -1
chr7 28518260 28519837 peak28923 961 . 3100.000 -1 -1
chr7 104652502 104654747 peak29684 961 . 3100.000 -1 -1
chr6 6586316 6590136 peak26279 961 . 3100.000 -1 -1
chr6 52362185 52364270 peak27366 961 . 3100.000 -1 -1
chr6 407805 413348 peak26180 961 . 3100.000 -1 -1
chr6 32936987 32941352 peak26978 961 . 3100.000 -1 -1
chr6 226477 229964 peak26144 961 . 3100.000 -1 -1
chr6 157017923 157020836 peak28371 961 . 3100.000 -1 -1
chr6 137422769 137425128 peak28064 961 . 3100.000 -1 -1
chr5 149789084 149793727 peak25705 961 . 3100.000 -1 -1
chr5 149778033 149783125 peak25702 961 . 3100.000 -1 -1
chr5 149183766 149185906 peak25695 961 . 3100.000 -1 -1
答案1
假设您有足够的内存来读取文件,您可以尝试
perl -e 'use List::Util 'shuffle'; @k=shuffle(<>); print @k[0..999]' file.bed
由于您想执行 10000 次,因此我建议将重复集成到脚本中并洗牌指数而不是数组本身来加快速度:
$ time perl -e 'use List::Util 'shuffle';
@l=<>; for $i (1..10000){
open(my $fh, ">","file.$i.bed");
@r=shuffle(0..$#l);
print $fh @l[@r[0..999]]
}' file.bed
real 1m12.444s
user 1m8.536s
sys 0m3.244s
上面从包含 37000 行的文件创建了 10000 个文件,每个文件 1000 行(您的示例文件重复了 1000 次)。正如您所看到的,在我的系统上花费了略多于三分钟的时间。
解释
use List::Util 'shuffle';
:这会导入一个 Perl 模块,该模块提供shuffle()
随机化数组的函数。@l=<>;
:将输入文件(<>
)加载到数组中@l
。for $i (1..10000){}
:运行 10000 次。@r=shuffle(0..$#l);
:是so$#l
中的元素数量,现在是数组索引号(输入文件的行)的随机列表。@l
@r
@l
open(my $fh, ">","file.$i.bed");
:打开一个需要写入的文件file.$i.bed
。$i
取值范围为 1 到 10000。print $fh @l[@r[0..999]]
:获取打乱数组中的前 1000 个索引并打印相应的行( 的元素@l
)。
另一种方法是使用shuf
(谢谢@frostschutz):
$ time for i in {1..10000}; do shuf -n 1000 file.bed > file.$i.abed; done
real 1m9.743s
user 0m23.732s
sys 0m31.764s
答案2
如果您想要一个基准测试来看看它能完成多快,请将其复制粘贴到10kshuffle.cpp
并编译g++ 10kshuffle.cpp -o 10kshuffle
。然后你可以运行它:
10kshuffle filename < inputfile
其中filename
是用于输出文件的基本路径;它们将被命名为filename.0
、filename.1
等,并且每个都包含随机播放的前 1000 行。它会随时写入每个文件的名称。
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <unistd.h>
#include <vector>
using namespace std;
unsigned int randomSeed () {
int in = open("/dev/urandom", O_RDONLY);
if (!in) {
cerr << strerror(errno);
exit(1);
}
unsigned int x;
read(in, &x, sizeof(x));
close(in);
return x;
}
int main (int argc, const char *argv[]) {
char basepath[1024];
strcpy(basepath,argv[1]);
char *pathend = &basepath[strlen(basepath)];
// Read in.
vector<char*> data;
data.reserve(1<<16);
while (!cin.eof()) {
char *buf = new char[1024];
cin.getline(buf,1023);
data.push_back(buf);
}
srand(randomSeed());
for (int n = 0; n < 10000; n++) {
vector<char*> copy(data);
// Fisher-Yates shuffle.
int last = copy.size() - 1;
for (int i = last; i > 0; i--) {
int r = rand() % i;
if (r == i) continue;
char *t = copy[i];
copy[i] = copy[r];
copy[r] = t;
}
// Write out.
sprintf(pathend, ".%d", n);
ofstream file(basepath);
for (int j = 0; j < 1000; j++) file << copy[j] << endl;
cout << basepath << endl;
file.close();
}
return 0;
}
在单个 3.5 Ghz 核心上,运行时间约为 20 秒:
time ./10kshuffle tmp/test < data.txt
tmp/test.0
[...]
tmp/test.9999
real 19.95, user 9.46, sys 9.86, RSS 39408
data.txt
问题重复了 37000 行。如果您希望输出文件中包含整个随机播放而不是前 1000 行,请将第 54 行更改为:
for (int j = 0; j < copy.size(); j++) file << copy[j] << endl;
答案3
因此,您的问题涉及 Unix 方面,但值得首先解决您的基本问题,然后尝试找到一种 Unix-y 方法来实现该解决方案。
您需要从行数未知的文件中创建 10,000 个样本,每个样本大小为 1,000。可以在以下位置执行此操作一次通过如果内存中可以容纳 10,000 x 1,000 行,则可以查看文件的内容。如果您无法在内存中保存那么多行,并且您知道文件包含多少行,则仍然可以一次性完成。如果您不知道文件包含多少行,则需要额外一次来计算行数。
在更困难的情况下,当您不知道行数时,该算法将对每个样本执行以下操作(并行地将样本保留在内存中):
- 包括样本中的前 1,000 行
- 对于第 n 行(其中
n > 1000
),将其包含在概率中1000 / n
,并从已选择的行中丢弃随机行。 (由于可能会丢弃某些行,我们需要将样本保留在内存中直到输入结束)
实现第二步的一个优雅方法是k
在 中生成一个随机整数[1, n]
。如果k <= 1000
则包含该行并k
用它替换现有的第 行。这是该算法的更标准的描述:http://en.wikipedia.org/wiki/Reservoir_sampling
如果您知道行数 ,R
则:
- 从样本大小
s
0开始 - 包含概率的第 n 行
(1000 - s) / (R - n + 1)
并立即输出(并增加样本大小s
)
如何在 Unix 上执行此操作?awk
似乎是互联网上这篇文章的答案(我不能保证其正确性,但代码就在那里)https://news.ycombinator.com/item?id=4840043