領域が重なる間隔に値を割り当てる方法は?

領域が重なる間隔に値を割り当てる方法は?

2つの大きなファイルがあり、最初のファイルには約85K行のスペースが含まれています。

head data.intervals
id  id_uniq numberA numberB
1   g1  5   20
1   g2  6   29
1   g3  17  35
1   g4  37  46
1   g5  50  63
1   g6  70  95
1   g7  87  93
2   g8  3   15
2   g9  10  33
2   g10 60  77
2   g11 90  132

2番目のファイルには、200万行を超えるいくつかの場所が含まれています。

head data.posiitons
id  number
1   4
1   19
1   36
1   49
1   90
2   1
2   20
2   89
2   93
2   120

私が望むのは、位置ファイルの「number」列の各値について、data.intervalsファイル内の「numberA」と「numberB」の値のペアの1つと同じかその間にあるかを検索することです。 。

さらに、「numberA」と「numberB」の値のペアの場合、対応する「id」はdata.positionの「id」と一致する必要があります。これがすべて真であれば、data.intervalsの対応する "id.uniq"をdata.posiitonsファイルの対応する行列に挿入したいと思います。

これには別の問題があります。これらの間隔の一部が互いに重なり合い、位置が2つ以上の間隔範囲に属する可能性があります。各間隔に個別に割り当てたいです。

これは私が得たい最終的な出力です(NAは場所がある間隔の範囲内にないことを意味します)。

   id   number  assigned1
1   4   NA
1   19  g1,g2,g3
1   36  NA
1   49  NA
1   90  g6,g7
2   1   NA
2   20  g9
2   89  NA
2   93  g11
2   120 g11

BashまたはPerlスクリプトを使用してこれを行うことができるソリューションはありますか?

答え1

Perl次の方法を使用してこれを実行できます。

$ perl -lane '
   my($id, $uniq_id, $lower, $upper) = @F;
   $h{$id}{$uniq_id}{MIN} = $lower;
   $h{$id}{$uniq_id}{MAX} = $upper;
   push @{$order{$id}}, $uniq_id;
   }{
   while(<STDIN>) {
      chomp;
      my($id, $number) = split;
      print join "\t", $id, $number,
       join(",", grep { $h{$id}{$_}{MIN} < $number and $h{$id}{$_}{MAX} > $number } @{$order{$id}})
         || qw/NA/;;
   }
' data.intervals < data.posiitons

出力:

1  4     NA
1  19    g1,g2,g3
1  36    NA
1  49    NA
1  90    g6,g7
2  1     NA
2  20    g9
2  89    NA
2  93    g11
2  120   g11

働く:

  • まず、インターバルファイルを読み取り、ID、一意のIDでキーを指定し、範囲エンドポイントを含むハッシュデータ構造を構築します。
  • ハッシュは、%order一意のIDが見つかった順序を保存することによって同じ順序で再生されます。 OTW、ハッシュ順はランダムです。
  • 次に、場所ファイルを読み取り、各レコード(または行)を最初に解凍し、$ idおよび$numberスカラーに配置します。
  • grep範囲内で数値制約を満たす一意のIDを選択する必要があります。それ以外の場合は a が"NA"渡されます。

答え2

この場合、小さなデータベースの使用を検討することができますcsvsqlcsvkit(これはまた便利なcsvformatユーティリティを提供します)。

たとえば、データがタブintervals区切りのファイルにあり、positionsデフォルトのsqlite方言を使用しているとします。

csvsql --tabs --query '
SELECT id,number,group_concat(id_uniq) AS "assigned1" 
FROM positions JOIN intervals USING(id)
WHERE number >= numberA AND number <= numberB
GROUP BY id,number ORDER BY id,number
' positions intervals | csvformat --out-tabs
id  number  assigned1
1   19  g1,g2,g3
1   90  g6,g7
2   20  g9
2   93  g11
2   120 g11

アイテムをインポートすることも少し複雑ですN/A。これを行うには、positions元のテーブルを結果と組み合わせてフィールドNULL値を見つけることができますassigned1

csvsql --tabs --query '
SELECT id,number,IFNULL(assigned1,"NA") assigned1 FROM positions 
LEFT JOIN (
  SELECT id,number,group_concat(id_uniq) AS "assigned1" 
  FROM positions JOIN intervals USING(id) 
  WHERE number >= numberA AND number <= numberB
  GROUP BY id,number
) USING(id,number) ORDER BY id,number 
' positions intervals | csvformat --out-tabs
id  number  assigned1
1   4   NA
1   19  g1,g2,g3
1   36  NA
1   49  NA
1   90  g6,g7
2   1   NA
2   20  g9
2   89  NA
2   93  g11
2   120 g11

答え3

両方のファイルがソートに使用されたと仮定すると、以下をsort -b使用して、両方のファイルで同じIDを持つすべての行のすべての可能な組み合わせを組み合わせることができます。

join ranges.txt positions.txt

単純化のために、ファイルにヘッダーがあるという事実も無視しました(該当するヘッダーを削除することをお勧めします)。

与えられたデータに対して以下が生成される。

1 g1 5 20 4
1 g1 5 20 19
1 g1 5 20 36
1 g1 5 20 49
1 g1 5 20 90
1 g2 6 29 4
1 g2 6 29 19
1 g2 6 29 36
[...] (in total 55 lines)

IDには、テストする「固有ID」、範囲、位置の2つの値があります。

これはプログラムで解析できますawk

join ranges.txt positions.txt |
awk '!($1 SUBSEP $5 in count) { count[$1,$5]=0 }
     $5 >= $3 && $5 <= $4 && ++count[$1,$5]
     END {
         for (i in count)
             if (count[i] == 0) {
                 split(i,s,SUBSEP)
                 print s[1], s[2], "NA"
             }
     }'

これは、配列のキーで表されるIDと位置を追跡しますcount。この値は、特定の範囲内に位置が配置された回数を保持します。私たちは「この場所はいいえあらゆる範囲で見つかります。」

出力の現在の行にjoinフィールド 3 と 4 に対応するフィールド 5 の位置が含まれている場合は、数が増えて行が出力されます。

これにより、join範囲の位置に対応する出力のすべての行が印刷されます。このブロックはEND配列を繰り返し、値が0のときに要求された形式で質問から要求された情報を印刷し、count一致しない場所を処理します。count

与えられたデータに対して、これは次のようになります。

1 g1 5 20 19
1 g2 6 29 19
1 g3 17 35 19
1 g6 70 95 90
1 g7 87 93 90
2 g9 10 33 20
2 g11 90 132 93
2 g11 90 132 120
2 89 NA
1 36 NA
1 4 NA
2 1 NA
1 49 NA

到着崩れるawkこのデータは、他のプログラムに渡すことができる「固有ID」に基づいています。 (私はそれをすべて配列に保存するのを避けます。同じ awkプログラムには大量のデータが含まれる可能性があるため)。

awk '$NF == "NA" { print; next }
                 { key = $1 SUBSEP $NF }
     key == prev { group = group "," $2; next }
                 { if (group != "") print id, pos, group; id = $1; pos = $NF; group = $2; }
     END         { if (group != "") print id, pos, group }'

NAこれはすでに正しい形式になっているため、最後の列のすべての行を通過します。次に、IDと場所の「キー」を設定します。このキーが前の行と同じ場合、カンマを区切りgroup文字として使用して呼び出された文字列に「固有ID」が追加されます。

キーラーメンいいえ以前と同様に、新しいID位置一致セットを見つけて、先ほど解析したセットのデータを出力する必要があります。END最後のセットのデータを出力するには、ブロックでこの操作をやり直してください。

これらすべてをまとめて、両方の入力ファイルを並べ替える必要があることを覚えておくと、次のようになります。

join ranges.txt positions.txt |
awk '!($1 SUBSEP $5 in count) { count[$1,$5]=0 }
     $5 >= $3 && $5 <= $4 && ++count[$1,$5]
     END {
         for (i in count)
             if (count[i] == 0) {
                 split(i,s,SUBSEP)
                 print s[1], s[2], "NA"
             }
     }' |
awk '$NF == "NA" { print; next }
                 { key = $1 SUBSEP $NF }
     key == prev { group = group "," $2; next }
                 { if (group != "") print id, pos, group
                   prev = key; id = $1; pos = $NF; group = $2; }
     END         { if (group != "") print id, pos, group }'

これの出力は次のとおりです。

1 19 g1,g2,g3
1 90 g6,g7
2 20 g9
2 93 g11
2 89 NA
1 36 NA
1 4 NA
2 1 NA
1 49 NA
2 120 g11

順番を除くと予想されたのと同じです。注文を編集するには、に渡してくださいsort -k1,1n -k2,2n

関連情報