영역이 겹치는 간격에 값을 할당하는 방법은 무엇입니까?

영역이 겹치는 간격에 값을 할당하는 방법은 무엇입니까?

두 개의 큰 파일이 있는데 첫 번째 파일에는 약 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백만 줄이 넘는 일부 위치가 포함되어 있습니다.

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" 값 쌍 중 하나와 같거나 그 사이에 있는지 검색하는 것입니다. .

또한 "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

이 경우 작은 데이터베이스 사용을 고려할 수 있습니다 csvsql.csvkit(이것은 또한 편리한 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", 범위, 위치 두 가지 값이 있습니다.

이는 프로그래밍 방식으로 구문 분석할 수 있습니다 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.

관련 정보