カテゴリー
【Awkでデータ解析のすゝめ】重複のあるCSVデータから重複を取り除くためのテクニック
※ 当ページには【広告/PR】を含む場合があります。
2022/08/08
2025/07/15

Awkコマンドを使ったシェルスクリプトのお話回です。
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
ここで出力された要素がセル内で重複していることを示しています。
要素の順番は出現した順番ですが、キー値の並び替え(ソート)を行いたい場合には、以前の記事の方で特集しましたテクニックをご参考ください。
重複要素をカウントする
こちらも先程の説明の延長上で考えることができますが、念の為に解説しておきます。
$ 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のテクニックを説明する前に、コマンドオプションから検索する要素を指定したほうがスクリプトとしては便利なので、先に「スクリプト仕込み」に仕立てておきます。
この記事ではスクリプト仕立てをするテクニックの話省略しますが、以前の記事で解説していますので、そちらを参照してください。
そちらの記事から流用したシェルスクリプトを一部改良して、行要素を検索するツールに仕立て直し、検索キーとして銘柄コードの一部でコマンドオプションから与える使い方ができるようにしたのが以下のコードです。
#!/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スクリプトで一本化するのも良いのではと思います。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー