より高速なデータの並べ替え


11

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
ファイルの大きさと、「ランダム」の概念の厳格さはどれくらいですか?split1つのファイルを1000行ずつに分割できるため、1回の呼び出しでより多くのファイルを取得できますsort。また、ファイル全体を読み取る必要がないためheadよりも少し速いかどうかを確認しましたかtail
Ulrich Schwarz 2014年

@UlrichSchwarz:上記で貼り付けたサンプルファイルには、約33000行が含まれています。一般に、すべてのベッドファイルの行数はほぼ同じです。また、例:33000行のファイルから、1回の実行で33個のサブセット(それぞれ1000行)を取得したくありません。各実行から上位1000行のみを取得したいと思います。同じファイルの末尾も作成します。headここではサンプルとして使用しました。
biobudhan 2014年

マニュアルページによると、sort -R「キーのランダムハッシュ」を使用しています。ハッシュの作成は時間の無駄であり、おそらく何よりも時間がかかります。行を配列に読み込み、インデックスを使用してそれをシャッフルすることをお勧めします。個人的には、それを使用perlします。あなたはそれを行うことbashができますが、乱数を生成する関数が必要になります。
goldilocks 2014年

@goldilocks:私はperl人ではありません!手伝ってくれませんか。
biobudhan 2014年

6
shuf代わりに試してくださいsort -R。かなり高速です。もちろん、それをメモリ内で実行すると(Perlの回答を参照)、シェルでファイル全体を再度読み取る必要があるものに勝ります。
frostschutz 2014年

回答:


14

ファイルを丸呑みするのに十分なメモリがあると仮定して、あなたは試すことができます

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行を含むファイルからそれぞれ1000行の10000ファイルを作成しました(サンプルファイルは1000回繰り返されています)。ご覧のとおり、私のシステムでは3分強かかりました。

説明

  • use List::Util 'shuffle';:これはshuffle()、配列をランダム化する関数を提供するPerlモジュールをインポートします。
  • @l=<>;:入力ファイル(<>)を配列にロードします@l
  • for $i (1..10000){} :これを10000回実行します。
  • @r=shuffle(0..$#l);$#lの要素数である@lように@r、今、配列のインデックス番号のランダム化されたリストである@l(入力ファイルの行)。
  • open(my $fh, ">","file.$i.bed");file.$i.bed書き込み用に呼び出されたファイルを開きます。$i1から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分で動作しました:-)もう1つ質問があります。ファイルの最後の1000行も取得するのはどうですか?これを達成するためにファイルの長さ(行数)を知る必要があるからですか?助けてください!
biobudhan 2014年

1
@biobudhanはfrostschutzのshuf提案に従って検討しますfor i in {1..10000}; do shuf -n 1000 file.bed > file.$i.bed; done。私のシステムでは、1分ほどかかりました。最後の1000行については、必要なのはだけですtail -n 1000
terdon

1
@biobudhanは、3倍高速なperlバージョンの更新された回答も参照してください。
terdon

はい、試してみましたが、より速く動作します!! どうもありがとうございました!!!:-)
biobudhan 2014年

perlバージョンの出力ファイルを再確認しましたか?私には、sysファイルI / Oのように時間がほとんどないのは奇妙に思えます。shufこれは、30秒程度の時間とまったく同じであってはなりませんsys。そこで、私はここでperl 1(カットアンドペースト)をテストし、O_Oを実行すると1000ファイルが作成されましたが、すべてのファイルが空
でした

9

ベンチマークで実行速度を確認したい場合は、これをコピーし10kshuffle.cppてにコンパイルしてくださいg++ 10kshuffle.cpp -o 10kshuffle。その後、それを実行できます。

10kshuffle filename < inputfile

filename出力ファイルに使用するベースパスはどこですか。それらは、などの名前が付けられfilename.0filename.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の方法を見つけることは価値があります。

不明な多数の行を含むファイルから、サイズ1,000のサンプル10,000を作成する必要があります。メモリに10,000 x 1,000行を保持できる場合は、ファイルの1回のパスでこれを実行できます。メモリにその数の行を保持できない場合でも、ファイルに含まれる行数がわかっていれば、1回のパスでそれを実行できます。ファイルに含まれる行数がわからない場合は、行数をカウントするために1つの追加パスが必要です。

行数がわからない場合のアルゴリズムは、サンプルごとに次の操作を実行することです(並行して、サンプルをメモリに保持します)。

  • サンプルの最初の1,000行を含める
  • n番目の行(ここでn > 1000)の場合、1000 / nその行を確率とともに含め、すでに選択した行からランダムな行を破棄します。(一部の行を破棄する可能性があるため、入力の最後までサンプルをメモリに保持する必要があります)

第二のステップを実装するためのエレガントな方法は、ランダムな整数を生成するkには[1, n]。その場合k <= 1000、行を含め、既存のk-番目の行をそれで置き換えます。これは、アルゴリズムのより標準的な説明です:http : //en.wikipedia.org/wiki/Reservoir_sampling

行数がわかっている場合は、次のようになりますR

  • s0のサンプルサイズで開始
  • n番目の行を確率で含め、(1000 - s) / (R - n + 1)すぐに出力します(そしてサンプルサイズを増やしますs

Unixでこれを行うには?awkインターネット上のこの投稿による答えのようです(正確さは保証できませんが、コードはあります)https://news.ycombinator.com/item?id=4840043

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.