【问题标题】:Using awk to print pairs of records having overlapping range of values between two columns使用 awk 打印在两列之间具有重叠值范围的记录对
【发布时间】:2016-07-20 13:00:35
【问题描述】:

我有不同的记录对应于 start($6) 和 stop($7) 的范围。 我想要做的是打印出所有具有重叠范围的记录对。

例如,我的数据如下:

id1 0   376 . scaffold1 5165761 5166916 
id2 0   366 . scaffold1 2297244 2298403 
id3 155 456 . scaffold1 692777  693770 
id4 185 403 . scaffold1 102245  729675

我想要的是这样的结果

id3 id4

因为 id4 的范围与 id3 重叠。 我一直在互联网上搜索解决方案,但似乎没有什么可以解决我的问题。

如果有人能给我一些建议,我将不胜感激。


在听取了以下回复中一些人的建议后,我确​​实尝试了这个确实有效的代码!

awk '{start[$1]=$6;stop[$1]=$7;} END {for(i in start) {for(j in stop) {if(start[i] >= start[j] && start[i] <= stop[j]) print i,j}}}' file | awk '{if($1!=$2) print}' -

处理时间很短...对于一个有 1400 条记录的文件,甚至不到 1 分钟就完成了。

【问题讨论】:

  • 祝你好运。
  • What have you tried so far?edit 您的问题显示您遇到问题的代码的minimal reproducible example,然后我们可以尝试帮助解决具体问题。您还应该阅读How to Ask
  • 暴力破解其实很简单。将开始和停止数据读入由id 列索引的关联数组,然后检查所有可能的ID 对。只需谷歌重叠间隔或类似的东西。
  • @MichaelVehrs:是的,但如果数量或记录很大,处理时间(和内存)可能会爆炸。该文件包含多少条记录?
  • @CasimiretHippolyte 大约 1500 条记录...

标签: linux bash unix awk


【解决方案1】:

此解决方案需要 GNU awk

{
    start = $6 * 10 + 5;
    stop = $7 * 10;
    data[start] = data[start] " " $1;
    data[stop] = data[stop] " " $1;
}
END {
    PROCINFO["sorted_in"] = "@ind_num_asc";
    for (d in data) {
        count = split(data[d], fields);
        for (i in fields) {
            id = fields[i];
            if (d % 10 == 5) { # start
                for (s in started) {
                    print s, id;
                }
                started[id] = 1;
            } else { # stop
                delete started[id];
            }
        }
    }
}

基本思想是这样的:将开始和停止标记(我称它们为索引,可能是一个糟糕的选择)放在一个数组中,并按其索引对该数组进行排序。然后,遍历数组。如果遇到“开始”标记,请将其放入另一个数组(称为“开始”)。如果您遇到“停止”标记,请将其从该数组中删除。现在,如果您遇到“开始”标记,则该间隔与当前数组“开始”中的所有间隔重叠,因此打印出匹配项。通过确保“停止”标记位于具有相同原始索引的“开始”标记之前,您可以消除极端情况。

【讨论】:

  • 报告在没有重叠的地方有重叠,而错过了一些确实存在的地方。试试我添加到my answer的示例输入文件。
  • 它们在我提到的示例中是独一无二的。哦,等一下。索引是指开始和停止值,而不是每行开头的实际 id 值。那好吧....
  • @EdMorton 是的,我就是这个意思。事实上,加倍努力并不难。
  • 听起来不错。我个人无法理解逻辑,但可能只有我一个人,我还没有投入大量时间......
【解决方案2】:
$ cat tst.awk
{
    beg[$1] = $6
    end[$1] = $7
    ids[++numIds] = $1
}
END {
    for (i=1; i<=numIds; i++) {
        idI = ids[i]
        for (j=1; j<=numIds; j++) {
            idJ = ids[j]
            if (idI != idJ) {
                if ( ( (beg[idI] >= beg[idJ]) && (beg[idI] <= end[idJ]) ) ||
                     ( (end[idI] >= beg[idJ]) && (end[idI] <= end[idJ]) ) ) {
                    if ( !seen[(idI<idJ ? idI FS idJ : idJ FS idI)]++ ) {
                        print idI, idJ
                    }
                }
            }
        }
    }
}

$ awk -f tst.awk file
id3 id4

您在问题中提供的输入文件并未涵盖很多情况,因此鉴于此输入文件中包含更多重叠变体:

$ cat file
id1 185 403 . scaffold1 10  20
id2 185 403 . scaffold1 11  19
id3 185 403 . scaffold1  9  10
id4 185 403 . scaffold1 20  21
id5 185 403 . scaffold1  9  11
id6 185 403 . scaffold1 19  21
id7 185 403 . scaffold1 10  20
id8 185 403 . scaffold1  1   8

试试上面的:

$ awk -f tst.awk file
id1 id3
id1 id4
id1 id5
id1 id6
id1 id7
id2 id1
id2 id5
id2 id6
id2 id7
id3 id5
id3 id7
id4 id6
id4 id7
id5 id7
id6 id7

与您在答案末尾提供的脚本 + 管道相比:

$ awk '{start[$1]=$6;stop[$1]=$7;} END {for(i in start) {for(j in stop) {if(start[i] >= start[j] && start[i] <= stop[j]) print i,j}}}' file | awk '{if($1!=$2) print}' -
id3 id5
id4 id6
id4 id7
id4 id1
id5 id3
id6 id7
id6 id1
id6 id2
id7 id3
id7 id5
id7 id1
id1 id3
id1 id5
id1 id7
id2 id5
id2 id7
id2 id1

请注意,您的脚本会两次报告​​某些(但不是全部)ID 之间的重叠:

id1 id7
id7 id1
id3 id5
id5 id3

而我的脚本只报告了一次,由 !seen[(idI&lt;idJ ? idI FS idJ : idJ FS idI)]++ 提供。

【讨论】:

  • 非常感谢您的建议!
猜你喜欢
  • 2017-12-23
  • 2021-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多