优化大型数据集的AWK脚本

时间:2017-01-24 16:57:56

标签: bash awk

对于以下输入数据,

Chr C   rsid    D   A1  A2  ID1_AA  ID1_AB  ID1_BB  ID2_AA  ID2_AB  ID2_BB  ID3_AA  ID3_AB  ID3_BB  ID4_AA  ID4_AB  ID4_BB  ID5_AA  ID5_AB  ID5_BB
10  p   rsid1   q   A   G   0.00    0.85    0.15    0.70    0.10    0.20    0.40    0.50    0.10    0.30    0.30    0.40    0.10    0.20    0.80
10  p   rsid2   q   C   T   0.90    0.10    0.00    0.80    0.10    0.10    0.70    0.10    0.20    0.30    0.40    0.30    0.30    0.20    0.40
10  p   rsid3   q   A   G   0.40    0.50    0.10    0.80    0.20    0.00    0.20    0.30    0.50    0.50    0.30    0.20    0.20    0.30    0.40

我需要生成以下输出数据。

rsid        ID1         ID2         ID3         ID4         ID5
rsid1      2.15        1.50        1.70        2.10        2.90
rsid2      1.10        1.30        1.50        2.00        1.90
rsid3      1.70        1.20        2.30        1.70        2.00

该表格通过乘以每个ID (_AA, _AB & _BB)的常数因子(1, 2, 3)来显示3列(ID1, ID2, ID3, etc)的总和。

Example: for rsID1 --> ID1 -> (ID1_AA*1 + ID1_AB*2 + ID1_BB*3) = (0.00*1 + 0.85*2 + 0.15*3) = 2.15

我编写了以下AWK脚本来建立任务,它完全正常。

请注意:我是AWK的初学者。

awk '{
    if(NR <= 1) { # header line
        str = $3; 
        for(i=7; i<=NF; i+=3) {
            split($i,s,"_”);
            str = str"\t"s[1]
        }
        print str
    }  else { # data line
        k = 0; 
        for(i=7; i<=NF; i+=3) 
            arr[k++] = $i*1 + $(i+1)*2 + $(i+2)*3; 
        str=$3; 
        for(i=0; i<=(NF-6)/3; i++) 
            str = str"\t"arr[i]; 
        print str
    }
}'  input.txt > out.txt

后来我被告知输入数据可能高达6000万行&amp; 300列,这意味着输出数据将是60Mx100K。如果我没错,AWK一次读一行&amp;因此,在一瞬间,将有300K列数据保存在内存中。这是个问题吗?鉴于这种情况,我该如何改进我的代码呢?

3 个答案:

答案 0 :(得分:4)

虽然两种方法都有优点/缺点,并且它们都可以处理任意数量的行/列,因为它们一次只能在内存中存储1行,所以我使用这种方法而不是the answer posted by Akshay每行有300,000列,所以他的方法要求你每行测试NR==1近100,000次,而下面的方法只需要每行执行1次测试,所以效率应该明显提高:

$ cat tst.awk
BEGIN { OFS="\t" }
{
    printf "%s", $3
    if (NR==1) {
        gsub(/_[^[:space:]]+/,"")
        for (i=7; i<=NF; i+=3) {
            printf "%s%s", OFS, $i
        }
    }
    else {
        for (i=7; i<=NF; i+=3) {
            printf "%s%.2f", OFS, $i + $(i+1)*2 + $(i+2)*3
        }
    }
    print ""
}

$ awk -f tst.awk file
rsid    ID1     ID2     ID3     ID4     ID5
rsid1   2.15    1.50    1.70    2.10    2.90
rsid2   1.10    1.30    1.50    2.00    1.90
rsid3   1.70    1.20    2.30    1.70    2.00

我强烈建议您阅读Arnold Robbins撰写的Effective Awk Programming,第4版,了解awk是什么以及如何使用它。

答案 1 :(得分:0)

awk -v OFS="\t" '
            {
              printf("%s",$3);
              for(i=7;i<=NF; i+=3)
              {
                if(FNR==1)
                {
                   sub(/_.*/,"",$i)
                   f = $i
                }else
                {
                    f = sprintf("%5.2f",$i*1 + $(i+1)*2 + $(i+2)*3)
                }
                   printf("%s%s",OFS,f)
              }
                print ""
            }
    ' file

<强>输出

rsid     ID1     ID2     ID3     ID4     ID5
rsid1    2.15    1.50    1.70    2.10    2.90
rsid2    1.10    1.30    1.50    2.00    1.90
rsid3    1.70    1.20    2.30    1.70    2.00

答案 2 :(得分:0)

  

您认为使用像C这样的低级语言吗?

C ++或C并不比awk自动更快,而且代码可读性更差,更脆弱。

我使用c++显示另一个解决方案,以进行比较

//p.cpp
#include <stdio.h>

//to modify this value
#define COLUMNS 5

int main() {
    char column3[256];
    bool header=true;
    while (scanf("%*s\t%*s\t%255s\t%*s\t%*s\t%*s\t", column3) == 1) {
        printf("%s", column3);
        if(header){
            header=false;
            char name[256];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%[^_]_%*s\t%*s\t%*s\t", name);
                printf("\t%s", name);
            }
        }else{
            float nums[3];
            for(int i=0; i<COLUMNS; ++i){
                scanf("%f %f %f", nums, nums + 1, nums + 2);
                float sum = nums[0]+nums[1]*2+nums[2]*3;
                printf("\t%2.2f", sum);
            }
        }
        printf("\n");
    }
}

运行它,就像

一样
g++ p.cpp -o p
cat file | ./p

<强>基准

输入中有1毫米的行和300列

  • Ed Morton解决方案:2分34秒

  • c ++:1m 19s

相关问题