Successfully reported this slideshow.
Your SlideShare is downloading. ×

クロス集計をBash(とawk)だけで実装した話

Ad

クロス集計をbash(とawk)だけで実装した話
       ~ bash最強伝説 : 序 ~




                          2012-12-02
                          @iktak...

Ad

自己紹介
@iktakahiro
株式会社ALBERT システム開発部
レコメンドエンジンとか作ってるよ!


   最近気になっていること:
    破とQの間に伊吹マヤに何が起こったのか。

Ad

このスライドに書いてあること


それなりのデータ量(※1)のログファイルをbashス
クリプトでクロス集計表にするまで行ったというお
話。



 ※1 数GBとか、数千万行とか。それ以上は試してない。

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Ad

Loading in …3
×

Check these out next

1 of 27 Ad
1 of 27 Ad

クロス集計をBash(とawk)だけで実装した話

Download to read offline

みんな大好きシェル芸シリーズ。

元々社内用に書いていたシェルスクリプトでしたが、面白かったのでまとめました。
cut, paste, sed, awk などを駆使してTSV形式のクロス集計表加工まで行った話。

Github
https://github.com/iktakahiro/gis/tree/master/src/crosstabj_3columns

みんな大好きシェル芸シリーズ。

元々社内用に書いていたシェルスクリプトでしたが、面白かったのでまとめました。
cut, paste, sed, awk などを駆使してTSV形式のクロス集計表加工まで行った話。

Github
https://github.com/iktakahiro/gis/tree/master/src/crosstabj_3columns

Advertisement
Advertisement

More Related Content

More from Takahiro Ikeuchi (20)

Advertisement

クロス集計をBash(とawk)だけで実装した話

  1. 1. クロス集計をbash(とawk)だけで実装した話 ~ bash最強伝説 : 序 ~ 2012-12-02 @iktakahiro
  2. 2. 自己紹介 @iktakahiro 株式会社ALBERT システム開発部 レコメンドエンジンとか作ってるよ! 最近気になっていること: 破とQの間に伊吹マヤに何が起こったのか。
  3. 3. このスライドに書いてあること それなりのデータ量(※1)のログファイルをbashス クリプトでクロス集計表にするまで行ったというお 話。 ※1 数GBとか、数千万行とか。それ以上は試してない。
  4. 4. クロス集計とは? ピボットテーブルと言うやつです。 性別 血液型 A型 B型 O型 AB型 男子 A型 女子 A型 男子 O型 男子 1 0 1 1 女子 B型 男子 AB型 女子 2 1 0 0 女子 A型
  5. 5. カウントのパターンもあれば、合算のパターンも ある(今回はこっち)。 ユーザー 商品 金額 Aさん アイス 130 Aさん アイス 180 Bさん ジュース 120 アイス ジュース OREO Bさん アイス 130 Iさん OREO 210 Aさん 310 0 0 Iさん OREO 210 Iさん OREO 210 Bさん 130 120 0 Iさん 0 0 630
  6. 6. カジュアルなクロス集計の選択肢 Excel => 乙。 Rでやれば良いじゃない => ちょっと前まではやってたけど、メモリ 馬鹿食い&積んでも別の制約にひっかかかる CとかJAVAでry =>
  7. 7. bashでやろう。
  8. 8. なぜbashか? Linux/Unix あればおk cutでカラム抽出簡単 catで縦マージpasteで横マージ sort超速い <= Point1 実はJOINできる <= Point2 S・E・D ! S・E・D ! ※ さすがに一部の処理はしんどいのでawkでフォロー
  9. 9. テキストファイル処理との親和性が半端じゃないです。 TSVファイルのカラム入れ換えとか抽出とか、正規表現置換と かで苦労している人はいますぐ覚えましょう。 どんなに巨大なファイルでもheadとtailで中身覗ける。(今回は 登場しないけど) シェルスクリプトのTips的な要素もあり。
  10. 10. 処理の流れ 1) 必要なカラムを抽出 2) クロス集計の行と列の要素を取り出す 3) awkでファイル分割する 4) awkで集計する(sum + group by) 5) sort でソート 6) 2)で取り出した行の要素と 5)の結果をJOIN 7) paste と cat でマージしてリストを表に加工
  11. 11. 1)必要なカラムを抽出 ユーザー 商品 金額 月 Aさん アイス 130 12月 [月] の列は要らない。 Aさん アイス 180 11月 Bさん ジュース 120 11月 Bさん アイス 130 12月 必要なものだけ抽出されていればこの処理は不 Iさん OREO 210 11月 要だけど、データ量が多いとそれもしんどいの Iさん OREO 210 10月 で一番差最初にやる。 Iさん OREO 210 2月 抽出するのは以降の処理対象のデータを減らし たいというのが理由。
  12. 12. # 1, 2, 3 カラム目を抽出したいので cat hoge.txt ¦awk {print $1, $2, $3}' > tmp_data.txt ユーザー 商品 金額 月 Aさん アイス 130 12月 Aさん アイス 180 11月 ユーザー 商品 金額 Bさん ジュース 120 11月 Aさん アイス 130 Bさん アイス 130 12月 Aさん アイス 180 Iさん OREO 210 11月 Bさん ジュース 120 Iさん OREO 210 10月 Bさん アイス 130 Iさん OREO 210 2月 Iさん OREO 210 Iさん OREO 210 Iさん OREO 210
  13. 13. 2)クロス集計の行と列の要素を取り出す # クロス集計表の行になる部分を抽出してユニーク化&ソート cut -f 1 -d , tmp_data.txt ¦sort -u > rowname.txt ユーザー 商品 金額 Aさん アイス 130 ユーザー Aさん アイス 180 Aさん Bさん ジュース 120 Bさん Bさん アイス 130 Iさん Iさん OREO 210 Iさん OREO 210 MySQLとかの感覚だとこの処理は遅そうですが Iさん OREO 210 速いです。
  14. 14. # クロス集計表の列になる部分を抽出してユニーク化&ソート cut -f 2 -d , tmp_data.txt ¦sort -u > tmp_header.txt # 改行をタブに変えて縦横変換 tr 'n' 't' < tmp_header.txt > header.txt ユーザー 商品 金額 商品 Aさん アイス 130 アイス Aさん アイス 180 ジュース Bさん ジュース 120 OREO Bさん アイス 130 Iさん OREO 210 Iさん OREO 210 商品 アイス ジュース OREO Iさん OREO 210 MySQLとかの感覚だとこの処理は遅そうですが 速いです。(二回目
  15. 15. 3)awkでファイル分割する # tmp_data.txt を 2列目の中身でファイル分割 awk -F, '{print > "split_"$2}' tmp_data.txt ユーザー 商品 金額 Aさん アイス 130 Aさん アイス 180 ユーザー 商品 金額 Bさん アイス 130 Aさん アイス 130 Aさん アイス 180 ユーザー 商品 金額 Bさん ジュース 120 Bさん ジュース 120 Bさん アイス 130 ユーザー 商品 金額 Iさん OREO 210 Iさん OREO 210 Iさん OREO 210 Iさん OREO 210 Iさん OREO 210 Iさん OREO 210
  16. 16. 4)awkで集計する, 5) sortでソートする # tmp_data.txt を 2列目の中身でファイル分割 awk -F, '{a[$1]+=$3;}END{for(i in a)print i","a[i];}' split_${file} ¦ sort -t 1 > sort_${file} ユーザー 商品 金額 アイス買った人 合計金額 Aさん アイス 130 Aさん 310 Aさん アイス 180 Bさん 130 Bさん アイス 130 ジュース買った人 合計金額 Bさん 120 ユーザー 商品 金額 Bさん ジュース 120 豚 合計金額 Iさん 630 ユーザー 商品 金額 Iさん OREO 210 このawkの処理は色々使えるので覚えてね! Iさん OREO 210 集計結果をパイプで渡してsortをかけてるよ。 Iさん OREO 210
  17. 17. 6) 2)で取り出した行の要素と 5)の結果をJOIN ... 前に一応今回のポイントなので解説。 クロス集計表の作成がなぜめんどくさいか? => レコードがないものを 0 で埋めなければいけないから。 アイス ジュース OREO [ Aさん x OREO ] とか Aさん 310 0 0 [ Iさん x アイス] とかがそう。 Bさん 130 120 0 Iさん 0 0 630
  18. 18. 0 で埋めないと(nullでもいいけど)マージがうまくいかなくて 残念なことになっちゃう。 アイス買った人 合計金額 Aさん 310 Bさん 130 ジュース買った人 合計金額 それぞれ行の数がばらばら。分割したファイルに Bさん 120 漏れなく要素が存在するわけではない。 豚 合計金額 Iさん 630 ??? ユーザー アイス ジュース OREO Aさん 310 120 630 Bさん 130
  19. 19. で、join コマンドです。
  20. 20. # 2)で取り出した行の要素と 5)の結果をJOIN からの∼ S・E・D ! join -t ',' -1 1 -2 1 -a 1 rowname.txt sort_${file} ¦ awk -F, '{print $2}' ¦ sed 's/^$/0/g' > split_sum_${file} ユーザー アイス買った人 合計金額 ! Aさん Aさん 310 IN Bさん 130 JO Bさん Iさん 0 Iさん ジュース買った人 合計金額 アイス買った人 合計金額 Aさん 0 Aさん 310 Bさん 120 Bさん 130 Excellent! Iさん 0 ジュース買った人 合計金額 OREO買った人 合計金額 Bさん 120 Aさん 0 Bさん 0 豚 合計金額 Iさん 630 Iさん 630
  21. 21. # JOIN の解説 join -t ',' -1 1 -2 1 -a 1 rowname.txt sort_${file} join コマンドは、デフォルトでは join 可能だった行のみを出力する。ただし、-a オプションを つけると、「不一致でjoinできなかったキー」も含めて出力してくれる。 Aさん JOIN ! Bさん Bさん 120 Iさん Aさん 0 Bさん 120 120 Iさん 0 # この結果の2列目を awk で抽出し、空行に sed で 0 を挿入している。 awk -F, '{print $2}' ¦ sed 's/^$/0/g' > split_sum_${file}
  22. 22. 7)paste で マージしてリストを表に加工 # 見出しとなる行、列と、分割処理した集計結果をマージ cp header.txt sum_result.txt paste rowname.txt split_sum_* >> sum_result.txt 商品 アイス ジュース OREO ユーザー Aさん 310 0 0 Bさん 130 120 0 Iさん 0 0 630
  23. 23. できあがり! アイス ジュース OREO Aさん 310 0 0 Bさん 130 120 0 Iさん 0 0 630 ※ できあがるのはTSVファイル。
  24. 24. ベンチマーク 仮想環境上のCentOSでやったので レコード 2,500万 I/Oはかなり遅いです。 ファイルサイズ 800MB DBで2,500万インサートとかそれだけで 死ねる。 クロス結果行 300万 クロス結果列 30 メモリ 16GB Githubでスクリプトを公開するので 処理時間 5分 みんなでベンチマークしてね!
  25. 25. 補足 ファイル分割しているので、集計処理を並列化することもでき る。並列実行は & とか xargs とかを使うと良いよ! あまり大きい固まりをawkに投げると帰ってこなくなるしね。 joinするには 対象のファイル(のキー)がソート済みである必 要があります。 joinには一致しなかった行だけを出力する -v オプションも。 bashでsortする場合は問答無用で export LC_ALL=C しる! 手元では事故ったことないけどソート周りに罠がありそう。
  26. 26. まとめ bash最強!! AAAヴンダーってなんかもう
  27. 27. おわり。

×