2012年8月12日日曜日

Clojureで帳票処理(csv読み書き、RDBMS利用、グラフ描画、PDF出力)

はじめに

おはようございます。当ブログにアクセス頂き、ありがとうございます。
授業が始まってからグログ更新が滞っていた、たなけんです。
本エントリでは、Clojureを利用した帳票処理について記載します。

事の発端

現在、大学院で会計を専攻しているのですが、任意科目でいくつかのMBA専攻のクラスを受けています。(前期までに、ほぼ会計は履修したため、今期は75%がMBAのクラス)
その履修しているMBAクラスのひとつに、企業戦略を立案するクラスを履修しています。
面白いことに、このクラスの成績は、シュミレーションゲーム上のスコアにより決定されます。(クラスのメンバーを8チームに分け、チーム間で競争することとなります。)
決定すべき項目として、製品開発(高機能戦略や低価格戦略)、マーケティング(世界を4地域に分け競合の出方を伺いながらどのエリアを攻めるか)、生産(工場の人員配置や設備投資など)、財務(株式発行、社債発行など)などがあり、毎週50項目ほどのパラメータを検討して、事業戦略を実装し、他チームと結果を競い合っています。
各チームが決定する50項目のうち、27項目は年度末(実際は毎週日曜日)に結果とともに全チームに公開され、翌年のパラメータ検討の際の参考資料とすることができます。
他チームが見る事のできるパラメータは27項目なのですが、それがチーム毎(8)、地区毎(4)、四半期毎(4)で公開されるため、結果として毎年3,456(=27*8*4*4)項目に目を通すこととなります。また、年間の比較を考えると、1年(1週間)毎にデータが3,456項目ずつ増えることになりますので、数値だけを見て比較するのは困難であると考えられました。
そこで、数値の変化を、各社、各地域で比較出来るようグラフを作成することとなりました。

仕様

入力: 会社、地域ごとに1csvファイル。行は項目、列は四半期ごとの数値が記載されている。
出力:1項目1ページ、1ページに各地域のグラフを縦に配置、1グラフに8社と平均値を表示、横軸は時間、縦軸は値。

出力例
製品販売価格



数値をグラフにすることにより、各チームの意思決定が比較しやすくなっているのが分かると思います。例えば、EA市場ではほとんどのチームが価格を他の地域より高めに設定していること、また、year7に突然ピンクチームがAP、LA、NA市場で価格を高く設定したことなどがこのグラフから読み取れます。

実装

全ソースはtana-kenのGithubにて公開しています。
興味を持たれた方は是非ご覧になって下さい。
以下、ポイントを絞って、実装内容を紹介します。

nsマクロ

https://gist.github.com/3331553
ライブラリとしてclojure.java.io、clojure.data.csv、clojure.java.jdbc、incanter.core、incanter.chartsを利用します。

csvファイル読み書き

(defn read-csv
"read csv data"
[in-file]
(with-open [in (io/reader in-file)]
(doall (csv/read-csv in))))
(defn write-csv
"write csv data"
[out-file lists]
(with-open [out (io/writer out-file :append true)]
(csv/write-csv out lists)))
view raw core.clj hosted with ❤ by GitHub

ファイルサイズが十分に小さいため、遅延シーケンスとして処理をせず、doallにより全データをメモリ上に読み込んでいます。

行列入れ替え

(defn into-2d-str-array
"make a 2 dimention Stirng array"
[lists]
(into-array (for [list lists] (into-array String list))))
(defn transpose-2d-str-array
"return transposed 2 dimention String array"
[in-array]
(let [x (alength in-array)
y (alength (aget #^String in-array 0))
out-array (make-array String y x)]
(doseq [i (range 0 x) j (range 0 y)]
(aset #^String out-array j i (aget #^String in-array i j)))
out-array))
view raw core.clj hosted with ❤ by GitHub

元ファイルは、列方向にデータが追加される仕様であったため、行方向にデータが追加されるよう、行と列を入れ替える処理を実装しました。

RDBMS利用

(def h2 {:subprotocol "h2"
:subname "/Users/kenta/h2/globus"
:user "sa"
:password ""
:classname "org.h2.Driver"
})
(defn select-from-h2
""
[sql]
(jdbc/with-connection h2
(jdbc/with-query-results
rows
[sql]
(doall rows))))
view raw core.clj hosted with ❤ by GitHub

組み込みRDBMSとしてH2 Database Engineを採用しました。
csvファイル読み込み同様、想定される取得データが十分小さいため、doallにより全データをメモリ上に読み込んでいます。
また、今回はテーブルを結合する必要がないため、H2 Database Engineのcsvファイル読み込み機能を利用しました。(csvファイル読み込み -> 整形 -> 1ファイルに結合 -> H2 Database Engineにてテーブルとして利用)

グラフ描画

(defn load-dataset-with-col-names
[item rg]
(icore/col-names
(iio/read-dataset (str prcs-dir "cir/" item rg ".csv"):header false)
'("C" "X" "Y")))
(defn generate-cir-charts
""
[]
(doseq [{item :item rg :rg} cir-keys]
(icore/save
(icharts/scatter-plot
:X
:Y
:data(load-dataset-with-col-names item rg)
:group-by :C
:legend false
:title (str (cir-hash item) "_" rg)
:x-label nil
:y-label nil)
(str out-dir "cir/" item "_" rg ".png")
:width 600
:height 200)))
view raw core.clj hosted with ❤ by GitHub

incanterを通じてjfreechartオブジェクトを作成しています。
RDBMSからグラフ描画に必要なデータセットを取り出し、グラフを描画します。

nsマクロ

(ns pdf.core
(:require
[clj-pdf.core :as pdf]))
view raw core.clj hosted with ❤ by GitHub

incanterで利用しているjarと依存関係で不整合があったため、プロジェクトを分けました。ライブラリにはclj-pdfを利用しています。

内容作成

(def contents
(reduce
into
(sort-by
#(parse-int ((re-matches #"i(\d\d)" ((first %) 1)) 1))
(for [[k v] cir-hash]
[[:phrase k]
[:image {:xscale 0.6 :yscale 0.6} (str jpg-dir k "_NA.jpg")]
[:image {:xscale 0.6 :yscale 0.6} (str jpg-dir k "_EA.jpg")]
[:image {:xscale 0.6 :yscale 0.6} (str jpg-dir k "_AP.jpg")]
[:image {:xscale 0.6 :yscale 0.6} (str jpg-dir k "_LA.jpg")]
[:phrase v]
[:pagebreak]]
))))
view raw core.clj hosted with ❤ by GitHub

タグ付けされたベクタにて要素を構成しています。
sort-by関数により、ページを制御しています。

PDF描画

(defn main
""
[]
(pdf/pdf
(apply conj
[{:title "CIR Report"
:author "Kenta TANAKA"
:creator "Kenta TANAKA"}]
contents)
(str pdf-dir "cir.pdf")))
view raw core.clj hosted with ❤ by GitHub

pdfマクロにて、与えられた内容をpdfファイルとして出力します。

今回の作業は以上、最後までお読み頂きありがとうございました。
たなけん(作業時間4時間)

0 件のコメント:

コメントを投稿