我想根据同一表第一列中给出的条目对下表各列中列出的数值进行求和。表内容如下:
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
以按原始顺序重新排列它们。
输出在分组列上排序,这就是为什么Chennai
inoutputted 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