【Awkでデータ解析のすゝめ】重複のあるCSVデータから重複を取り除くためのテクニック


※ 当ページには【広告/PR】を含む場合があります。
2022/08/08
2025/07/15
【Awk&Sed活用講座】CSVファイルから重複データを見つける&重複をカウントする
Jq&AwkコマンドでJSONファイル⇔CSVファイルに相互変換する方法を考察
蛸壺の技術ブログ|CSVで重複なしのユニークなリストを作る/検索結果から重複を取り除く

Awkコマンドを使ったシェルスクリプトのお話回です。

CSVの行を検索した結果に、要素の重複を含むケースが結構あります。

この重複を含んだままのデータをそのまま気づかすに処理してしまうと、最終的に思わぬ計算結果を得てしまうなど、好ましくないこともあります。

ちょっと前に、CSVデータの中身から重複を
「探す」「カウントする」の2つのテクニックを取り上げたことがありました。

合同会社タコスキングダム|蛸壺の技術ブログ
【Awk&Sed活用講座】CSVファイルから重複データを見つける&重複をカウントする

AwkとSedでCSVファイルからCSVデータの重複を取り扱うテクニックを簡単に載せます。

今回は重複のあるCSVデータの操作テクニックとして、

            * データで重複しているセル要素を見つける/カウントする
* データから重複を削除してユニークな要素だけの行要素にする
* 重複がある場合のデータ検索結果から重複を取り除く
        
のテクニックをまとめて紹介します。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

CSVデータで重複しているセル要素を見つける/カウントする

CSVファイル内に存在しているセルのデータが重複しているときに、Awkで何かしらの処理させたいときがあります。

Awkでデータの重複している・していないの判別は簡単なスクリプトを使って取り出せるので、一度覚えておけば、もしかしたときのCSVデータ取扱も怖くなくなります。

まずは、Awkによる重複の見つけ方/カウントのやり方を考察していきます。

重複のあるCsvデータ

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

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

            2,2,2,3
1,3,3,6
4,2,8,4
3,1,2,2
        
二次元数値データの代表的な例は、画像データの数値配列などが挙げられます。

このような二次元データを構成するセル要素は、基本に重複しても良いですし、しない要素もあります。

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

どの要素がCSVデータ全体として、何回出ているかを解析したい...そういう集計がやりたい場合にもAwkが便利です。

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
        
ここで出力された要素がセル内で重複していることを示しています。

要素の順番は出現した順番ですが、キー値の並び替え(ソート)を行いたい場合には、以前の記事の方で特集しましたテクニックをご参考ください。

合同会社タコスキングダム|蛸壺の技術ブログ
【Awkでデータ解析のすゝめ】gawk(GNU AWK)でカスタムソートを使ってみる

ビッグデータ解析の分野で利用できるAwkの高速カスタムソート術をご紹介します。

重複要素をカウントする

こちらも先程の説明の延長上で考えることができますが、念の為に解説しておきます。

            $ 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セクションで定義しておくようなひと手間を掛けておく必要があると思います。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

CSVファイルから重複をなくす/ユニークな行データにする

再集計などを必要としないCSVファイルがあれば、元データから重複項目をそのまま無くすことで、他のユーザーからも扱いやすくなります。

例えば以下のデータのように、株式銘柄をリストアップしたデータが並んでいて、そのいくつかの行が重複しています。

            1319,日経300F,東証
1390,ETFアジア太平洋株,東証
1391,ETFスイス株,東証
1419,タマホーム,東証P
1419,タマホーム,東証P
1580,日経平均ベア,東証
1674,ETFS白金,東証
1770,藤田エンジ,東証S
1826,佐田建,東証S
1835,東鉄工,東証P
1770,藤田エンジ,東証S
2032,ハンセンベア,東証
2109,三井糖,東証P
2838,米国国債投信(ヘッジなし),東証
2136,ヒップ,東証S
2433,博報堂DY,東証P
2032,ハンセンベア,東証
2109,三井糖,東証P
2648,ブルームバーグ米国債(為替ヘッジあり),東証
2838,米国国債投信(ヘッジなし),東証
2904,一正蒲鉾,東証P
3063,ジェイグループ,東証G
3261,グランディ,東証G
3261,グランディ,東証G
3329,東和フード,東証S
3492,タカラレーペン,東証
3865,北越コーポ,東証P
3492,タカラレーペン,東証
3063,ジェイグループ,東証G
4428,リンク,東証G
4031,チッカリン,東証S
4428,リンク,東証G
4544,HUグループ,東証P
4832,JFEシス,東証S
4928,ノエビアH,東証P
5020,JXホール,東証P
4544,HUグループ,東証P
5194,相模ゴ,東証S
5757,サンエツ,東証P
4544,HUグループ,東証P
6200,インソース,東証P
6474,不二越,東証P
6958,日本CMK,東証P
7226,極東開発,東証P
7559,ジーエフシー,東証S
8093,極東貿易,東証P
8801,三井不,東証P
8093,極東貿易,東証P
6200,インソース,東証P
9240,デリバリーコンサル,東証G
9533,邦ガス,東証P
9735,セコム,東証P
9533,邦ガス,東証P
9984,ソフトバンク,東証P
        
このようなデータは、未整理のままではデータとしての価値が低いので、まずは重複を取り除き、銘柄コード順にソートして、より解析しやすい状態にしたほうが後々使いやすくなるでしょう。

先程のデータをローカルにCSVファイルとして、作業フォルダに保存してから、以下のコマンドを試してみましょう。

            $ awk -F"," '!col[$1]++{ print $0 }' stocks.csv | \
sort -k 1 -t ','
#👇出力結果
1319,日経300F,東証
1390,ETFアジア太平洋株,東証
1391,ETFスイス株,東証
1419,タマホーム,東証P
1580,日経平均ベア,東証
1674,ETFS白金,東証
1770,藤田エンジ,東証S
1826,佐田建,東証S
1835,東鉄工,東証P
2032,ハンセンベア,東証
2109,三井糖,東証P
2136,ヒップ,東証S
2433,博報堂DY,東証P
2648,ブルームバーグ米国債(為替ヘッジあり),東証
2838,米国国債投信(ヘッジなし),東証
2904,一正蒲鉾,東証P
3063,ジェイグループ,東証G
3261,グランディ,東証G
3329,東和フード,東証S
3492,タカラレーペン,東証
3865,北越コーポ,東証P
4031,チッカリン,東証S
4428,リンク,東証G
4544,HUグループ,東証P
4832,JFEシス,東証S
4928,ノエビアH,東証P
5020,JXホール,東証P
5194,相模ゴ,東証S
5757,サンエツ,東証P
6200,インソース,東証P
6474,不二越,東証P
6958,日本CMK,東証P
7226,極東開発,東証P
7559,ジーエフシー,東証S
8093,極東貿易,東証P
8801,三井不,東証P
9240,デリバリーコンサル,東証G
9533,邦ガス,東証P
9735,セコム,東証P
9984,ソフトバンク,東証P
        
確かに簡単なAwkスクリプトで、重複した行もなく、ユニークなリストとして整理できました。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

CSVデータの行検索の結果から重複を取り除く

先ほどまでの話のように、CSVファイルのデータそのものの重複を取り除くのではなく、行検索から得られた結果のみに、重複を取り除く操作を行いたい場合もあります。

Awkのテクニックを説明する前に、コマンドオプションから検索する要素を指定したほうがスクリプトとしては便利なので、先に「スクリプト仕込み」に仕立てておきます。

この記事ではスクリプト仕立てをするテクニックの話省略しますが、以前の記事で解説していますので、そちらを参照してください。

合同会社タコスキングダム|蛸壺の技術ブログ
【シェルスクリプト&Excel】条件を与えて検索結果をCSVデータで出力するスクリプト

読み込んだCSVデータから特定の1列を検索キーでヒットした行要素を、CSV形式でピックアップして表示するシェルスクリプトを作成してみます。

そちらの記事から流用したシェルスクリプトを一部改良して、行要素を検索するツールに仕立て直し、検索キーとして銘柄コードの一部でコマンドオプションから与える使い方ができるようにしたのが以下のコードです。

            #!/bin/bash

usage_exit() {
    echo "USAGE: $(basename $0) [-k key] [-h help] [input_file]" 1>&2
    exit 1
}

noarg_err() {
    echo "ERROR: must provide key!" 1>&2
    exit 1
}

noinputfile_err() {
    echo "ERROR: not allowed input file to be empty!" 1>&2
    exit 1
}

while getopts k: OPT; do
    case $OPT in
        k ) KEY="$OPTARG"
            ;;
        \? ) usage_exit
            ;;
    esac
done

shift $((OPTIND - 1))

if [ -z "$KEY" ]; then
    noarg_err
fi

if [ -z "$1" ]; then
    noinputfile_err
fi

echo "FILE: $1, KEY: ${KEY}"

awk -F"," '
    $1~/'"${KEY}"'/ && !col[$1]++ { print $0 }
' $1
        
これで例えば、CSVデータから銘柄コードのどこかに44を含むデータ行を重複なしに抽出できるか試してみましょう。

            $ chmod +x stock_finder.sh
$ ./stock_finder.sh -k 44 stocks.csv
#👇以下、重複なしの検索結果を表示
FILE: stocks.csv, KEY: 44
4428,リンク,東証G
4544,HUグループ,東証P
        
どうやらちゃんと検索結果から重複が消えて、見やすいリスト表示が可能となっているようです。

ポイントは、Awkスクリプト部分の、
$1~/'"${KEY}"'/ && !col[$1]++になります。

最初の条件で、銘柄コードのある一列目に
KEY値が含まれているかを正規表現からフィルタリングし、二番目の条件で、重複が発生した行を弾くようにしています。

Awkなら、このような簡単なスクリプトの記述だけで、いとも簡単に重複なしのリストを得ることができます。

テクニックの肝をよく理解しておくことで、色々と応用が効くと思います。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

まとめ

以上、Awkでの重複要素を操作するテクニックをまとめてみました。

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