同じ表の最初の列に与えられた項目に基づいて、下の表の列にリストされている数値を合計したいと思います。表の内容は次のとおりです。
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
予想される結果は次のとおりです(2番目と最後の列にグループ化されたデータ)。
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
入力ファイルがまだ2番目のフィールドにグループ化されていない場合(公開された入力例のように)、以下を変更します。
awk '...' "${@:--}"
これに関して:
sort -t',' -k2,2 "${@:--}" | awk '...'
答え2
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
グループ化された列は出力から最初に出力されるため、2番目のステップを実行して元の順序datamash
に並べ替えます。
出力はグループ化列に基づいてソートされるため、以前に入力Chennai
されます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を使用する1つの方法のみを示しています。つまり、2番目と最後の列(コンマの後のインデックス)をlines
読み、編集します。この列情報は分類子にも保持されている場合は重複するため、このパラメータはコンポーネントから数字ではなく2つの列を削除するために使用されます。データはハッシュに保存されます。classify
[1, *-1]
split
key
value
as
classify
value
%class
ここでは、ペアをキー/値コンポーネント%class
に分割して印刷し、Rakuの三項演算子を使用してテストして、複数の要素が含まれていることを確認します。複数の要素が見つかると、列が合計されて使用されます(コンテナ化されていない次の要素ごとに合計されます)。要素が1つしかない場合、列は削除されます(合計されず、平面化のみ)。kv
key
value
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では、カンマで区切られた出力は確かに可能ですが、以下の答えでは、単純化のために2つの「グループ化」列を列1と列2に抽象化します。
~$ 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/言語/operators#index-entry-operator_ternary
https://raku.org