如果一行的列与另一行的列匹配,则添加

如果一行的列与另一行的列匹配,则添加

我想根据同一表第一列中给出的条目对下表各列中列出的数值进行求和。表内容如下:

10,Mumbai,0,4,5,0,6,3,55,M
2,Mumbai,1,3,2,0,4,4,4,M
4,Chennai,5,6,7,8,9,0,6,F

预期结果如下(数据按第二列和最后一列分组):

12,Mumbai,1,7,7,0,10,7,59,M
4,Chennai,5,6,7,8,9,0,6,F

如何在 Linux 上使用 awk 来获得此输出?

答案1

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

awk '
    BEGIN { FS=OFS="," }
    $2 != vals[2] {
        if ( NR>1 ) {
            prt()
        }
        split($0,vals)
        next
    }
    {
        for ( i=1; i<=NF; i++ ) {
            if ( $i+0 == $i ) {
                vals[i] += $i
            }
        }
    }
    END {
        prt()
    }
    function prt(    i) {
        for (i=1; i<=NF; i++) {
            printf "%s%s", vals[i], (i<NF ? OFS : ORS)
        }
    }
' "${@:--}"

$ ./tst.sh file
12,Mumbai,1,7,7,0,10,7,59,M
4,Chennai,5,6,7,8,9,0,6,F

如果您的输入文件尚未按第二个字段分组(如您发布的示例输入中所示),则更改此:

awk '...' "${@:--}"

对此:

sort -t',' -k2,2 "${@:--}" | awk '...'

答案2

使用第二作为键并在输出时保持记录的顺序,我们可以这样做。

awk -F, -v OFS=, '!seen[$2]++{ recNr++ }
{ for(i=1; i<=NF; i++)
      if(i!=2 && i!=NF)
          sumCol[recNr, i, $2]+= $i
      else 
          sumCol[recNr, i, $2]= $i (i==NF?ORS:"")
}

END{ for (key in sumCol){
         if(sumCol[key]!=""){
             recNumbr++; sep=""
             split(key, tmp, SUBSEP)
             for(j=1; j<=NF; j++){
                 printf ("%s", sep sumCol[recNumbr, j, tmp[3]])
                 sep=OFS
                 delete sumCol[recNumbr, j, tmp[3]]
             }
         }
     }
}' infile

答案3

使用 GNU datamash

$ datamash -s -t , groupby 2,10 sum 1,3-9 <file | datamash -t , cut 3,1,4-10,2
4,Chennai,5,6,7,8,9,0,6,F
12,Mumbai,1,7,7,0,10,7,59,M

这用于datamash对第 1 列以及第 3 列到第 9 列求和,同时按第 2 列和第 10 列的组合对输入进行分组。

由于datamash首先在输出中输出分组列,因此我们进行第二次遍历datamash以按原始顺序重新排列它们。

输出在分组列上排序,这就是为什么Chennaiinoutputted before Mumbai。如果原始数据已经排序,-s则从命令中删除。

另一个例子:

$ cat file
10,Mumbai,0,4,5,0,6,3,55,M
2,Mumbai,1,3,2,0,4,4,4,M
4,Chennai,5,6,7,8,9,0,6,F
4,Chennai,5,6,7,8,9,0,6,F
4,Chennai,5,6,7,8,9,0,6,M
$ datamash -s -t , groupby 2,10 sum 1,3-9 <file | datamash -t , cut 3,1,4-10,2
8,Chennai,10,12,14,16,18,0,12,F
4,Chennai,5,6,7,8,9,0,6,M
12,Mumbai,1,7,7,0,10,7,59,M

答案4

使用(以前称为 Perl_6)

~$ raku -e 'my %class = lines.classify(*.split(",").[1, *-1].join("\t"), as => {$_.split(",").[ 0,2..*-2 ][*;*]});  \
            for %class.kv -> $k,$v {say $k => $v.elems > 1 ?? [Z+] $v<> !! $v[*;*]};'   file

OP 可能想考虑使用 Perl 系列语言来解决这个问题。以上仅代表一种使用 Raku 的方法。简而言之,lines读入并classify编辑第二列和最后一列(逗号后面[1, *-1]的索引)。如果split分类器中的列信息key也保留在 中,则该列信息是多余的value,因此该as参数用于classify从组件中删除两个非数字列value。数据存储在哈希值中%class

从这里开始,这些%class对被分成kv键/值组件,打印key,并value使用 Raku 的三元运算符测试 s 以查看它们是否包含多个elems(元素)。如果找到多个元素,则对列进行求和并put使用[Z+] $v<>(取消容器化,然后按元素相加)。如果只有一个元素,则列将被淘汰put$v[*;*]仅展平,不求和)。

输入示例:

10,Mumbai,0,4,5,0,6,3,55,M
2,Mumbai,1,3,2,0,4,4,4,M
4,Chennai,5,6,7,8,9,0,6,F
4,Chennai,5,6,7,8,9,0,6,F
4,Chennai,5,6,7,8,9,0,6,M

示例输出(制表符分隔keys):

Chennai M => (4 5 6 7 8 9 0 6)
Chennai F => (8 10 12 14 16 18 0 12)
Mumbai  M => (12 1 7 7 0 10 7 59)

请注意,在 Raku 中当然可以使用逗号分隔的输出,但是为了简单起见,下面的答案仍然将两个“分组”列抽象为第一列和第二列:

~$ raku -e 'my %class = lines.classify(*.split(",").[1, *-1].join(","), as => {$_.split(",").[ 0,2..*-2 ][*;*]});  \
            for %class.kv -> $k,$v {put $k ~","~ ($v.elems > 1 ?? [Z+] $v<> !! $v[*;*]).join(",")};'  file
Chennai,F,8,10,12,14,16,18,0,12
Mumbai,M,12,1,7,7,0,10,7,59
Chennai,M,4,5,6,7,8,9,0,6

最后,感谢@Kusalananda 提供了更广泛的样本输入数据集。

https://docs.raku.org/routine/classify
https://docs.raku.org/language/operators#index-entry-operator_ternary
https://raku.org

相关内容