如果键与另一个文件匹配并基于条件,则对文件中的值求和

如果键与另一个文件匹配并基于条件,则对文件中的值求和

我有两个文件。

文件1

NC_000001.11_NM_001005484.2 69270   234 69037
NC_000001.11_NM_001005484.2 69511   475 69037
NC_000001.11_NM_001005484.2 69761   725 69037
NC_000001.11_NM_001385640.1 942155  20  942136

文件2

NC_000001.11_NM_001005484.2 65565   9
NC_000001.11_NM_001005484.2 69037   969
NC_000001.11_NM_001385640.1 924432  517
NC_000001.11_NM_001385640.1 925922  92
NC_000001.11_NM_001385640.1 930155  182
NC_000001.11_NM_001385640.1 931039  51
NC_000001.11_NM_001385640.1 935772  125
NC_000001.11_NM_001385640.1 939040  90
NC_000001.11_NM_001385640.1 939272  141
NC_000001.11_NM_001385640.1 941144  163
NC_000001.11_NM_001385640.1 942136  116
NC_000001.11_NM_001385640.1 942410  79
NC_000001.11_NM_001385640.1 942559  500
NC_000001.11_NM_001385640.1 943253  125
NC_000001.11_NM_001385640.1 943698  111
NC_000001.11_NM_001385640.1 943908  243

如果文件 1 中的列 1 与文件 2 中的列 1 匹配,并且文件 2 的第 2 列中的值小于文件 1 中的第 4 列的值,我想将文件 2 的第 3 列与匹配的键相加。然后我想打印 file1 中的每一行以及 file2 中相应的总和。

预期产出

NC_000001.11_NM_001005484.2 69270   234 69037  9
NC_000001.11_NM_001005484.2 69511   475 69037  9
NC_000001.11_NM_001005484.2 69761   725 69037  9
NC_000001.11_NM_001385640.1 942155  20  942136 1361

我对 awk 或 python 的经验还不够,无法做到这一点,但现在已经修修补补了几天了。我将不胜感激任何帮助。

答案1

$4对于此任务,您需要存储第 1 行的每个键 ( )的值(在下面的脚本中,我将使用为此$1调用的数组,作为键和值)。keys$1$4

您还需要将每个实际行存储在另一个数组中(我将lines为此使用行号作为键,整行作为值)。请注意,如果很大的话,这可能会消耗大量内存file1......但是,除非它很大,否则在任何具有许多 GB RAM 的现代系统上可能不是问题。如果碰巧太大而无法放入 RAM,则必须修改脚本以再次迭代第一个文件,而不是将其存储在数组中lines

最后,您还需要存储与每个行号对应的键 ($1)(我将使用linekeys为此调用的数组,以行号作为索引,键$1作为值)。顺便说一句,如果第一个文件太大,您必须第二次处理它,那么就不需要这个数组,因为您可以$1在再次处理每一行时获取它。从技术上讲,这个数组并不是真正需要的,因为您可以在需要时split()从块lines[l]END{}获取它,但这样做更容易 - 用更多的内存使用来换取更简单的代码和可能更快的运行时间。

awk '# process the first file
     NR==FNR {
       keys[$1] = $4;      # remember the value of $4 for the key ($1)
       lines[FNR] = $0;    # store the entire line
       linekeys[FNR] = $1; # remember the key for that line
       next
     };

     # process any remaining file(s)
     $1 in keys {
       if ($2 < keys[$1]) {
         sum[$1]+=$3
       };
     };

     # All files have been processed, so print the output
     END {
       for (l in lines) {
         print lines[l], sum[linekeys[l]]
       }
     }' file1 file2
NC_000001.11_NM_001005484.2 69270   234 69037 9
NC_000001.11_NM_001005484.2 69511   475 69037 9
NC_000001.11_NM_001005484.2 69761   725 69037 9
NC_000001.11_NM_001385640.1 942155  20  942136 1361

顺便说一句,我建议将其保存在任一sh脚本中(除了用作"$@"参数而不是awk这样file1 file2 ,您可以在运行它时在命令行上指定输入行(例如bash scriptname.sh file1 file2或者将其保存为 awk 脚本(删除命令awk、单引号和文件名),以便您可以将其作为awk -f scriptname.awk file1 file2.用适当的#!线作为第一的行,您还可以使其可执行,这样您就可以直接运行它,而无需在运行时在命令行上键入解释器名称。

或者,如果您真的坚持,您可以将整个脚本压缩到一行中 - 在语句之间需要的地方保留分号以实现这一点。不过,我不推荐它,因为 shell 命令行是编辑脚本的糟糕地方,即使是这么短的脚本,甚至具有方便的功能(如Ctrl-XCtrl-Ebash)来编辑当前行vi或您喜欢的编辑器。

答案2

使用 GNU awk 处理数组的数组:

$ cat tst.awk
NR==FNR {
    addends[$1][$2][$3]
    next
}
$1 in addends {
    sum = 0
    for ( val in addends[$1] ) {
        if ( val < $4 ) {
            for ( addend in addends[$1][val] ) {
                sum += addend
            }
        }
    }
    print $0, sum
}

$ awk -f tst.awk file2 file1
NC_000001.11_NM_001005484.2 69270   234 69037 9
NC_000001.11_NM_001005484.2 69511   475 69037 9
NC_000001.11_NM_001005484.2 69761   725 69037 9
NC_000001.11_NM_001385640.1 942155  20  942136 1361

请注意,上面的内容将简单地以与 file1 中出现的顺序相同的顺序输出 file1 中的行,其他读取file1而不是file2写入内存的解决方案可能不会这样做,例如,如果它们使用for (i in array)之后打印它们,则会将它们洗牌成“随机” " 顺序由您使用的 awk 版本的内部结构决定,请参阅https://www.gnu.org/software/gawk/manual/gawk.html#Scanning-an-Array,因此即使您碰巧获得了某些特定示例输入的预期输出,也不要依赖于所有输入总是发生的情况。

相关内容