[Awkでデータ解析のすゝめ] 重複している要素を見つける&重複している要素のカウントする


2021/02/19

csvファイル内のデータが重複しているときの操作をAwkで処理させたいときがあります。

Awkでデータの重複している・していないの判別は簡単なスクリプトを使って執り行えるので、一度覚えておけばもしかしたときのデータ取扱に非常に頼もしい限りです。

今回はAwkでの重複をどう見つける/処理するのかをじっくり考察していきます。

以下は関連テーマの記事リンクです。


やりたかったこと

例えば、何かの集合を表している配列[1,2,3,4,5,6,7,8]があるとします。

この要素を使って2次元の重みデータで構成される4x4マスのcsvファイルが例えば以下のように記録してあります。

            
            2,2,2,3
1,3,3,6
4,2,8,4
3,1,2,2
        
データは構成要素が重複しても良いですし、無い要素もあります。この程度なら人間の眼で見ても重複している要素は判別可能ですが、これが何MBのテキストデータともなってくるとコンピュータによる処理で重複を判断させるしかありません。

どの要素が何回出ているかを解析したい...そういう集計がやりたい場合にAwkが便利です。


重複有り・無しを判別させる

まずは重複回数をカウントしないで、重複しているかしていないかをチェックするだけのAwkスクリプトを試します。

先程のデータセットを使い回すため変数
RAW_DATASETに仕込んでおきます。

            
            $ RAW_DATASET=$(cat <<EOF
2,2,2,3
1,3,3,6
4,2,8,4
3,1,2,2
EOF
)
        
重複を判定する上で、もっとも基礎的なAwk内の配列の働きを簡単なスクリプトで確認します。以下のスクリプトを実行してみましょう。

            
            $ echo "$RAW_DATASET" | awk -F"," '{
    for (i=1; i<=NF; i++) {
        print !label[$i]++;
    }
}'
#👇出力
1
0
0
1
1
0
0
1
1
0
1
0
0
0
0
0
        
ちなみに組込変数NFは各行の列数を取り出すものです。forループの部分で$1, $2, ..., $NFで一行ごとに全ての列の要素を取り出しています。

そのi列目に着目すると、
$iの要素をキー値とした、label[$i]という連想配列を使っていることがポイントで、$iという要素を発見する度に、初期値(この場合ではゼロ)がインクリメント(++)されて、値がカウントされます。

この連想配列に否定演算子
!に作用させることで、始めて出現した要素は1が返り、既に出た要素がある場合には0が返ります。

ということで、この仕組みを踏まえると、1回以上データセット無いに出現していた要素を取り出す場合には以下のように書けます。

            
            $ echo "$RAW_DATASET" | awk -F"," '{
    for (i=1; i<=NF; i++) {
        if (!label[$i]++) {print $i;}
    }
}'
#👇出力
2
3
1
6
4
8
        
なお、要素の順番は出現した順番ですが、キー値の並び替えを行いたい場合には、以前の記事の方で特集しましたテクニックをご参考ください。


重複要素のカウント

もはや先程の説明でこのお題は解けてはいますが、念の為に解説しておきます。

            
            $ echo "$RAW_DATASET" | awk -F"," '
{
    for (i=1; i<=NF; i++) { label[$i]++; }
}
END {
    for(j in label) {
        print j "は" label[j] "回でました";
    }
}
'
#👇出力
1は2回でました
2は6回でました
3は4回でました
4は2回でました
6は1回でました
8は1回でました
        
ここでのポイントとしては、要素の値をキーに持つ連想配列labelが要素の出現回数でカウントされることが前節で説明しましたが、最終的なカウント結果はENDセクションで集計的に処理を行わせていることです。

なお、
for ... inループはデフォルトで回すキー値の昇順で自動ソートされます。

また配列
[1,2,3,4,5,6,7,8]の内で出なかった要素を抽出する場合は、

            
            $ echo "$RAW_DATASET" | awk -F"," '
{
    for (i=1; i<=NF; i++) { label[$i]++; }
}
END {
    for(j=1; j<=8 ;j++) {
        if(!label[j]) {
            print j "は出ませんでした...";
        }
    }
}
'
#👇出力
5は出ませんでした...
7は出ませんでした...
        
と出来ます。

探したい配列が整数であれば通常のforループのインデックスが、そのまま検索される連想配列のキーとして使えるので簡単に使えましたが、キーとしては何でも利用できます。

より柔軟なキー値使いたいのであれば、検索用の配列をBEGINかENDセクションで定義しておくようなひと手間を掛けておく必要があると思います。


まとめ

以上、awkでの重複要素を操作するテクニックを簡潔に解説しました。

なお重複を取り出したい、カウントしたいなどの操作は、sortコマンドでも可能ですが、高度な集計を行いたいのであればawkスクリプトで一本化するのも良いでしょう。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。