通过匹配标题将列合并到一个文件中

通过匹配标题将列合并到一个文件中

我希望获取一个具有多行和多列的文件,水平添加(求和)具有匹配标题的列的值。在我的文件中(不是这个例子)我忽略了前 9 列和第一行,因为我希望它们不受影响地打印在结果中。

这些列不按重复顺序排列,并且列数比示例多得多。

这个想法是这样的:

输入:

var x y x y x y
a 1 0 1 1 0 1
b 1 1 0 0 1 1
c 1 1 0 0 0 0

输出:

var x y
a 2 2
b 2 2
c 1 1

这是我到目前为止所得到的......

awk -F '\t' '{FS==OFS} FNR==1; FNR>1 {for (i=10; i<=NF; i++) {} print}' FILE.tsv > FILE_norepcols.tsv

另外,如果可能的话,我试图了解如何更好地编写此代码,因此请建议我可以/应该更改的任何内容。

答案1

$ perl -lane '$,="\t";
   print(qw/var x y/),next if $. == 1;
   push @A, shift @F;
   $A[$|--+1] += $_ for @F;
   print splice @A;
' file

结果:

var x   y
a   2   2
b   2   2
c   1   1

假设:

  • 字段数量为奇数。
  • Field#2 及之后的字段均为数字。

解释(简短的):

  • 标题已明确打印,如 OP 所示。

  • @A通过在使用该函数打印数组时清空数组,为每一行重新填充数组splice

  • 数组@F存储输入记录的字段$_,零索引。

  • 数组的第一个元素(而不是第零个元素)@A从数组的前面移出@F。请注意,每次读入新记录@F时都会生成数组perl。这类似于$1, $2, $3, ..., $NF中的字段awk

  • 数组的其余部分@A是一个二进制(0|1)+1=>(1|2)元素索引,它累加数组中的相应总和@F

希望它是清楚的。

答案2

使用相当冗长的变量名称和中间变量来帮助您理解发生了什么(而不是注释每一行或稍后添加解释):

$ cat tst.awk
BEGIN { FS=OFS="\t" }
NR==1 {
    for (inFldNr=2; inFldNr<=NF; inFldNr++) {
        fldName = $inFldNr
        if ( !(fldName in fldName2outFldNr) ) {
            outFldNr2name[++numOutFlds] = fldName
            fldName2outFldNr[fldName] = numOutFlds
        }
        outFldNr = fldName2outFldNr[fldName]
        out2inFldNrs[outFldNr,++numInFlds[outFldNr]] = inFldNr
    }

    printf "%s%s", $1, OFS
    for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
        outFldName = outFldNr2name[outFldNr]
        printf "%s%s", outFldName, (outFldNr<numOutFlds ? OFS : ORS)
    }
    next
}
{
    printf "%s%s", $1, OFS
    for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
        sum = 0
        for (inFldIdx=1; inFldIdx<=numInFlds[outFldNr]; inFldIdx++) {
            inFldNr = out2inFldNrs[outFldNr,inFldIdx]
            sum += $inFldNr
        }
        printf "%s%s", sum, (outFldNr<numOutFlds ? OFS : ORS)
    }
}

$ awk -f tst.awk file
var     x       y
a       2       2
b       2       2
c       1       1

答案3

使用 GNU datamash

<file datamash -W transpose | 
  datamash -W --headers -s -g1 sum 2-4 |
  datamash --output-delimiter=' ' transpose
  • 使用空格作为分隔符转置文件 ( -W)
  • 对第一个字段 ( -g1) 进行分组,在分组 ( -s) 之前进行排序,并对字段 2-4 上的值求和。第一个输入行被视为列标题并打印在输出中 ( --headers)
  • 转置结果并使用空格字符而不是制表符作为分隔符。

输出:

GroupBy(var) x y
sum(a) 2 2
sum(b) 2 2
sum(c) 1 1

使用以下命令打印结果sed

<file datamash -W transpose | 
  datamash -W --headers -s -g1 sum 2-4 |
  datamash --output-delimiter=' ' transpose | 
  sed 's/^[^(]*(\([^)]*\))/\1/'

输出:

var x y
a 2 2
b 2 2
c 1 1

您可以使用以下cut命令从输入文件中删除前九列并将它们添加回结果中paste

paste -d ' ' <(cut -d' ' -f-9 file) <(cut -d' ' -f10- file | datamash ... )

head -n1可以使用 打印并跳过附加标题行tail -n+2

{
  head -n1 file 
  paste -d ' ' <(tail -n+2 file | cut -d' ' -f-9) <(tail -n+2 file | cut -d' ' -f10- | datamash ... )
} 

相关内容