如何使用 awk 按 ID 列压缩列?

如何使用 awk 按 ID 列压缩列?

我有通常在 R 中运行的代码,但文件太大,所以我尝试在 awk 中运行相同的命令。

我正在尝试按 ID 列(或Gene我的情况下的列)对列中的值进行分组。

我的数据如下所示:

Gene       col1   col2   col3
ACE         1     0.4    BP
ACE         2     0.5    DP
RPP-I.1     1     0.01   BP
NOS2      -0.1   0.2    DP
NOS2       1.4   2.5    SP
NOS2        1      1    BP

我想将其分组Gene为:

Gene     col1          col2          col3
ACE      1, 2          0.4, 0.5      BP, DP
RPP-I.1  1             0.01          BP
NOS2     -0.1, 1.4, 1  0.2, 2.5, 1   BP, SP, DP

我的真实数据为 14.8GB,约有 200 列和 24972316 行,我最初尝试使用 R 的 data.table,但这在尝试读取文件时出现总线错误。

有什么办法可以用 awk 尝试这个吗?

答案1

使用 GNU 的通用解决方案awk是:

gawk 'NR>1{ for (i=2; i<=NF; i++) {
               c[i][$1]= c[i][$1]?c[i][$1] s $i:$i;
           } next;
}1;

ENDFILE{
    for (x in c[2]) {
        printf ("%s", x);
        for (i=2;i<=NF;i++) { printf ("\t%s", c[i][x]); delete c[i][x]; };
        print "";
     };
}' s=', ' infile  |column -s $'\t' -t

上面的命令不会将所有但几乎全部的输入文件加载到内存中,并且您说您有 30GB 的 RAM 并且您的文件大小约为 15GB,所以如果您有足够的可用内存至少 15GB,我认为不会有问题。

但下面是一种解决方法,但不是最佳解决方案,可以将 bigfile.txt 分成小文件,每个文件具有相同的 GeneName,然后awk对所有*.small文件应用上述命令,并以附加模式将输出保存到单个文件。

我说它不是最优的,因为基因名称的分布可能不均等,可能有些少,有些多;但是你可以这样做:

  1. 在第一列将输入文件拆分为小尺寸Gene

    awk 'NR>1{ print >$1".small"; }' bigfile.txt
    
  2. 然后对文件执行awk上面给定的命令;只需删除开头的*.small条件即可,因为当我们拆分 bigfile.txt 时,我们已经跳过了它。NR>1

    gawk '{ ... }; ENDFILE{ ... }' s=', ' *.small >>proccedfile
    
  3. rm *.small稍后删除文件。

答案2

以下内容旨在通过仅sort需要一次处理整个文件来处理大文件,并sort旨在通过使用需求分页等来处理该文件,因此它实际上不必将整个输入存储在内存中。在 awk 命令中,一次仅存储当前值,$1因此不会出现任何内存问题:

$ cat tst.sh
#!/usr/bin/env bash

awk -v OFS='\t' '{print (NR>1), NR, $0}' "${@:--}" |
sort -k1,1n -k3,3 -k2,2n |
cut -f 3- |
awk '
    BEGIN { OFS="\t" }
    NR == 1 { $1=$1; print; next }
    $1 != prev { prt() }
    {
        for (i=2; i<=NF; i++) {
            col[i] = (i in col ? col[i] ", " : "") $i
        }
    }
    END { prt() }

    function prt(       i) {
        if ( prev != "" ) {
            printf "%s%s", prev, OFS
            for (i=2; i<=NF; i++) {
                printf "%s%s", col[i], (i<NF ? OFS : ORS)
            }
        }
        delete col
        prev = $1
    }
'

$ ./tst.sh file
Gene     col1          col2         col3
ACE      1, 2          0.4, 0.5     BP, DP
NOS2     1, 1.4, -0.1  1, 2.5, 0.2  BP, SP, DP
RPP-I.1  1             0.01         BP

上述脚本的输出是制表符分隔的,我认为这对您有好处,因为您可以轻松地在其上运行其他工具,将其导入电子表格等。如果您希望它生成视觉上对齐的列,则添加| column -s $'\t' -t到脚本结束,但是您引入了另一个程序,该程序可能必须将整个输出文件读入内存以在打印之前计算最大字段宽度,因此 YMMV.如果您无法忍受制表符分隔的输出,并且无法用于column生成表格输出,那么请发布一个与此相关的新问题。

无论输入来自文件还是管道,上面的代码都可以工作。

相关内容