カテゴリー
【Awkでデータ解析のすゝめ】awkのみで2つのファイルを効率的に結合させる方法
※ 当ページには【広告/PR】を含む場合があります。
2021/02/10
2022/09/30

Awkを使ったデータサイエンス向けに不定期で紹介しているワンポイント講座シリーズの二回目です。
時系列解析するデータによってはキーになる値は共通しているのに、ラベルがバラバラのファイルとして四散して存在している場合があります。
今回はこのような作業をAwkのみを使って効率的に結合させるためのテクニックを考察してみます。
Awkでのファイルの結合
論より証拠ということで、早速具体的なAwkスクリプトを作成しながら、Awkによるパワフルかつ柔軟なファイルの結合をやってみます。
2つのファイル間でのデータ結合
まずはテスト用に以下のような2つのデータファイルを作成します。
$ cat << EOF > open.csv
2020-05-14,1596
2020-05-15,1529
2020-05-18,1565
2020-05-19,1575
2020-05-20,1570
2020-05-21,1599
2020-05-22,1551
2020-05-25,1553
2020-05-26,1583
2020-05-27,1569
2020-05-28,1537
2020-05-29,1571
2020-06-01,1558
2020-06-02,1564
2020-06-03,1599
2020-06-04,1590
2020-06-05,1571
EOF
$ cat << EOF > volume.csv
2020-05-19,9500
2020-05-20,9600
2020-05-21,8300
2020-05-22,3100
2020-05-25,3800
2020-05-26,9700
2020-05-27,14300
2020-05-28,18400
2020-05-29,12200
2020-06-01,4600
2020-06-02,11200
2020-06-03,12300
2020-06-04,8000
2020-06-05,8300
2020-06-08,15200
2020-06-09,19900
2020-06-10,8100
2020-06-11,16600
EOF
例としてはなんでもよかったのですが、今回は、とある株式銘柄の日足データのうち、期間の始値と出来高を記録したデータを1つのファイルに結合することにしましょう。
データの見てのとおりで、キーにあたる日付の列がファイルごとにバラバラですので、単純なAwkの操作で行数同士の要素を繋ぎ合わせるだけでは正しい結合が出来ません。
キー値(ここでは1列目の日付)で共通項目を括りだしながら、ファイルを結合していくためには以下のようにAwkを使ってあげると上手くいきます。
$ awk -F "," '
BEGIN {
OFS=","
}
F == 0 {
open_arr[$1] = $2;
next;
}
{
if($1 in open_arr) {
print $1, open_arr[$1], $2;
}
}
' F=0 open.csv F=1 volume.csv
#👇実行結果
2020-05-19,1575,9500
2020-05-20,1570,9600
2020-05-21,1599,8300
2020-05-22,1551,3100
2020-05-25,1553,3800
2020-05-26,1583,9700
2020-05-27,1569,14300
2020-05-28,1537,18400
2020-05-29,1571,12200
2020-06-01,1558,4600
2020-06-02,1564,11200
2020-06-03,1599,12300
2020-06-04,1590,8000
2020-06-05,1571,8300
このスクリプトが行っているポイントは、まずコマンドのファイルを読み込んでいる引数の箇所
... F=0 open.csv F=1 volume.csv
F
F
FILE
FL
これによってAwk内のスクリプトブロックの内部で、
F == 0 {...}
F=0
open.csv
また
F == 0 {...}
open_arr
open.csv
next
F == 0 {...}
open.csv
以上のテクニックから2つのファイルから適切にデータを結合できるようなAwkスクリプトができました。
複数のファイルの結合を同時に行う
先程は2つのファイル間での結合操作を行いましたが、折角なのでもっと応用的なところも狙ってみます。
場合によっては複数の結合したいファイルがあるときもあります。
先程説明していた2つのファイルづつ結合させて、さらに結合後のファイルをまた別のファイルと結合....を繋いでいくとなんとか泥臭く複数ファイルの結合できるかとは思いますが、一気に複数のファイルをAwkで捌くことも可能です。
試しに3つのファイルで結合操作する例を挙げておきます。 このテクニックを応用すると、ファイルが4つでも5つでも一気に結合することが可能になります。
まずは先程の利用例からもう一つ、以下のようなとある株式銘柄の日足の終値を持つ
close.csv
$ cat << EOF > close.csv
2020-05-15,1565
2020-05-18,1540
2020-05-19,1567
2020-05-20,1598
2020-05-21,1551
2020-05-22,1541
2020-05-25,1578
2020-05-26,1569
2020-05-27,1569
2020-05-28,1570
2020-05-29,1558
2020-06-01,1554
2020-06-02,1588
2020-06-03,1569
2020-06-04,1565
2020-06-05,1566
2020-06-08,1563
2020-06-09,1536
2020-06-10,1549
2020-06-11,1516
2020-06-12,1482
2020-06-15,1479
2020-06-16,1539
2020-06-17,1539
2020-06-18,1550
EOF
以下が同時に3つのデータファイルを結合するAwkスクリプトになります。
$ awk -F "," '
BEGIN {
OFS=","
}
F == 0 {
open_arr[$1] = $2;
next;
}
F == 1 {
if($1 in open_arr) {
close_arr[$1] = $2;
}
next;
}
{
if($1 in open_arr) {
print $1, open_arr[$1], close_arr[$1], $2;
}
}
' F=0 open.csv F=1 close.csv F=2 volume.csv
#👇実行結果
2020-05-19,1575,1567,9500
2020-05-20,1570,1598,9600
2020-05-21,1599,1551,8300
2020-05-22,1551,1541,3100
2020-05-25,1553,1578,3800
2020-05-26,1583,1569,9700
2020-05-27,1569,1569,14300
2020-05-28,1537,1570,18400
2020-05-29,1571,1558,12200
2020-06-01,1558,1554,4600
2020-06-02,1564,1588,11200
2020-06-03,1599,1569,12300
2020-06-04,1590,1565,8000
2020-06-05,1571,1566,8300
シンプルな実装でなかなか痒いところまで手が届くような結合になっていると思います。
おまけ〜データ解析しないならjoinコマンドの方が手っ取り早い
余談ですが単なる2つのファイルの結合だけを行う場合にはjoinコマンドが利用できるので、awkを使う必要もありません。
例えば今回の例で言うと以下にようになります。
$ join -t"," -1 1 open.csv -2 1 volume.csv
2020-05-19,1575,9500
2020-05-20,1570,9600
2020-05-21,1599,8300
2020-05-22,1551,3100
2020-05-25,1553,3800
2020-05-26,1583,9700
2020-05-27,1569,14300
2020-05-28,1537,18400
2020-05-29,1571,12200
2020-06-01,1558,4600
2020-06-02,1564,11200
2020-06-03,1599,12300
2020-06-04,1590,8000
2020-06-05,1571,8300
この程度ならjoinでも良いのですが、データを数値的に変形したり、データが複数のファイルに複雑に広がっている場合などに、高度な解析を加える必要がある場合には、joinコマンドを使うよりAwkによる処理を検討したほうが良いでしょう。
Awkで複数の標準入力の結合
さきほどまでは複数のファイルを指定して、データをスマートに結合・統合する例を紹介しましたが、既に変数としてメモリに取り込んであるデータをファイルとして一旦ローカルに保存して、そのファイルをもう一度読み込むのはあまり好ましいやり方ではありません。
どうにかローカルファイルとして読み込まず、複数のデータをそのまま標準入力としてAwkで捌きたいというやり方もここで考えてみましょう。
複数の標準入力をファイルの代わりに扱う
まずは理解しやすいように単純なサンプルコードで、標準入力からcatコマンドにデータの中身をリダイレクトしてみましょう。
#👇データの生成
$ OPEN_DATA=$(
cat << EOF
2020-05-19,1575
2020-05-20,1570
2020-05-21,1599
2020-05-22,1551
EOF
)
#👇データを標準入力としてcatにリダイレクト
$ cat <(echo "$OPEN_DATA")
2020-05-19,1575
2020-05-20,1570
2020-05-21,1599
2020-05-22,1551
ここで使った標準入力のリダイレクトのテクニックは、
コマンド2 <(コマンド1)
<
<
(
この構文はコマンド1で実行された標準出力がそのままリダイレクトされて、コマンド2の標準入力としてパイプされるので、作用としては
コマンド1 | コマンド2
この
コマンド2 <(コマンド1)
|
どういうことかというと、先程のコマンドに加えてさらに以下のコマンドも実行してみましょう。
#👇別のデータを生成
$ VOL_DATA=$(
cat << EOF
2020-05-19,9500
2020-05-20,9600
2020-05-21,8300
2020-05-22,3100
EOF
)
#👇データを標準入力(マルチストリーム)としてcatにリダイレクト
$ cat <(echo "$OPEN_DATA") <(echo "$VOL_DATA")
2020-05-19,1575
2020-05-20,1570
2020-05-21,1599
2020-05-22,1551
2020-05-19,9500
2020-05-20,9600
2020-05-21,8300
2020-05-22,3100
これで分かるのは、
<(コマンド1) <(コマンド2) <(コマンド3) ...
標準入力のマルチストリームをAwkで処理する
先程の例で、複数の標準入力をまとめてリダイレクトできる方法は理解していただけたかと思いますが、これをAwkと組み合わせると面白い処理が可能です。
以下のコマンドで確認してみましょう。
#👇データを標準入力(マルチストリーム)をAwkで合成
$ awk -F"," '
BEGIN {
OFS=","
}
F == 0 {
open_arr[$1] = $2;
next;
}
{
if($1 in open_arr) {
print $1, open_arr[$1], $2;
}
}
' F=0 <(echo "$OPEN_DATA") F=1 <(echo "$VOL_DATA")
#👇合成結果
2020-05-19,1575,9500
2020-05-20,1570,9600
2020-05-21,1599,8300
2020-05-22,1551,3100
ということで、期待通りに高度で効率のよいデータ行の合成が成功していることが分かります。
まとめ
今回はAwkによる複数のデータファイルを同時に捌くような効率的な処理方法の実装を説明していきました。
内容的にはデータサイエンス方面の用途を想定していますが、このテクニックを覚えたらエクセルを使う日々のデスクワークの業務にも使えますので是非ともAwk脳を鍛えて、煩わしい業務の効率化を図ってもいかがかと思います。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー