CLOVER🍀

That was when it all began.

統計解析ツールIncanterことはじめ

以前、こんなエントリを書きました。

Clojure/lucene-kuromojiでテキストマイニング入門 〜形態素解析からワードカウントまで〜
http://d.hatena.ne.jp/Kazuhira/20130911/1378914422

なんですけど、ぶっちゃけこの時は元の参照エントリで使っていたIncanterの意味がわからず、単純に持ってきただけでした。出力されていたグラフの内容はさすがにわかりますが…。

で、このIncanter、覚えておくと後々便利かな〜と思い、最近少し興味があったので試してみることにしました。

Incanter
http://incanter.org/

Incanterは、Clojureで書かれた統計解析ツールらしいです。入力したデータを元に、表やグラフを表示したり図やPDFとして保存が可能な模様。

で、始めるにあたってドキュメントページを見て…

http://data-sorcery.org/contents/

Getting Startedはあるものの、以下のPDFに沿ってやった方がよい感じでした。

Introduction to datasets and charts
http://incanter.org/docs/data-sorcery-new.pdf

環境設定等は、こちらで。

Building Incanter applications with Leiningen
http://data-sorcery.org/2009/11/20/leiningen-clojars/

あとは、適宜APIを見ながら。

名前空間一覧
http://liebke.github.io/incanter/

API for core
http://liebke.github.io/incanter/core-api.html

API for charts
http://liebke.github.io/incanter/charts-api.html

API for stats
http://liebke.github.io/incanter/stats-api.html

で、今回はlein-exec形式で実行する前提とするので、用意するClojureスクリプトの冒頭にこんな感じで定義。

(require '[leiningen.exec :as exec])

(exec/deps '[[incanter "1.5.4"]])

(require '[incanter.core :as c]
         '[incanter.stats :as s]
         '[incanter.charts :as ch])

datasets名前空間は、サンプルデータが入っているそうなのですが、最終的に要らないので外しました。
ちょっと関数の挙動とかを見る時に、手軽にデータを用意できる点としてはグッドです。

あと、Clojureでのコードの書き方として、useよりもrequireの方がいいみたいな話があったかと思うんですけど、どうなんでしょうね?ドキュメントに合わせた方がいいのかな?と思いつつも、今回はrequireにしました。

dataset

こちらのドキュメントによると、まずはdatasetというものを作成するのが基本になるようです。
Introduction to datasets and charts
http://incanter.org/docs/data-sorcery-new.pdf

core/dataset関数にキーとなるベクタ、値のベクタのベクタを渡し、それをそのままviewで表示します。

(c/view (c/dataset ["x1" "x2" "x3"]
                   [[1 2 3] [4 5 6]]))

すると、こんな結果になります。

なるほど。

もうひとつ。Mapからも作成できるみたいです。

(c/view (c/to-dataset [{"x1" 1 "x2" 2 "x3" 3}
                       {"x1" 4 "x2" 5 "x3" 6}
                       {"x1" 7 "x2" 8 "x3" 9}]))

列や行の指定からも、datasetは作成できるみたいです。

core.conj-cols

(c/view (c/conj-cols [1 2 3] [4 5 6] [7 8 9]))

core.conj-rows

(c/view (c/conj-rows [1 2 3] [4 5 6] [7 8 9]))

core.conj-rowsその2

(c/view (c/conj-rows [{:a 1 :b 2 :c 3}]
                     [[4 5 6]
                      [7 8 9]
                      [10 11 12]]))


演算やフィルタリング

中央値と標準偏差

(let [[m s] (c/with-data (c/dataset ["x1" "x2" "x3"]
                                    [[1 2 3] [4 5 6] [7 8 9]])
              [(s/mean (c/$ :x1))
               (s/sd (c/$ :x1))])]
  (assert (= m 4.0))   ;; 中央値
  (assert (= s 3.0)))  ;; 標準偏差

core.$で、列を指定可能みたいです。

列や行の絞り込み。

(c/with-data (c/dataset ["x1" "x2" "x3"]
                        [[1 2 3] [4 5 6] [7 8 9]])
  (c/view (c/$ [:x1 :x2]))              ;; x1とx2を表示
  (c/view (c/$ [:not :x1 :x2]))         ;; x1とx2以外を表示
  (c/view (c/$ 0 [:not :x1 :x2]))       ;; x1とx2以外の1行目を表示
  (c/view (c/$ [0 2] [:not :x1 :x2])))  ;; x1とx2以外の1行目と3行目を表示

それぞれ、結果。




絞り込み

core.$whereを使って、データの絞り込みが可能。

(c/with-data (c/dataset ["x1" "x2" "x3"]
                        [[1 2 3] [4 5 6] [7 8 9]])
  (c/view (c/$where {"x1" {:gt 1}})))
;; $whereで使える演算子は、query-datasetを見るべし

コメントにも書いていますが、$whereで使用できる演算子は、core.query-datasetを参照してください。
比較演算子や、関数指定が可能なようです。

:eq

(c/view (c/$where {"x1" {:eq 4}}
                  (c/dataset ["x1" "x2" "x3"]
                             [[1 2 3] [4 5 6] [7 8 9]])))

:in。引数は、セットで指定します。

(c/view (c/$where {"x1" {:in #{1 7}}}
                  (c/dataset ["x1" "x2" "x3"]
                             [[1 2 3] [4 5 6] [7 8 9]])))

組み合わせ。

(c/view (c/$where {"x1" {:gt 2, :lt 10}
                   "x2" {:eq 5}}
                  (c/dataset ["x1" "x2" "x3"]
                             [[1 2 3] [4 5 6] [7 8 9]])))

関数で条件を指定。

(c/view (c/$where (fn [row]
                    (or (= (row "x1") 1)
                        (= (row "x3") 9)))
                  (c/dataset ["x1" "x2" "x3"]
                             [[1 2 3] [4 5 6] [7 8 9]])))


ソート

ソートは、core.$orderで。

(c/view (c/$order :x3 :desc
                  (c/dataset ["x1" "x2" "x3"]
                             [[1 2 3] [4 5 6] [7 8 9]])))

結果。

scatter-plot

ここからは、グラフ?の描画へ。

chart/scatter-plotで、datasetの値のプロットが可能です。以下は、指定されたdetasetから、x1を横、x2を縦にプロットします。

(c/view (ch/scatter-plot :x1 :x2
                         :data (c/dataset ["x1" "x2" "x3"]
                                          [[1 2 3] [4 5 6] [7 8 9]])))

つまり、こんな感じ。

データが少ないので、寂しい感じになっているのはご愛嬌。

Group Byも可能みたいです。

(c/view (ch/scatter-plot :x1 :x2
                         :data (c/dataset ["x1" "x2" "x3"]
                                          [[1 4 "c1"]
                                           [2 8 "c2"]
                                           [3 6 "c1"]
                                           [4 7 "c2"]])
                         :group-by :x3))

この場合、グループごとにプロットされるデータの色が変わります。

bar-chart

続いて、棒グラフ。

以前使ったのは、この形式でdatasetを作成して渡すのではなく、ベクタを直接指定していたようです。

(c/view (ch/bar-chart [:x1 :x2] [10 5]))

結果。

datasetを使用する例。

(c/view (ch/bar-chart "x2" "x1"
                      :data (c/dataset ["x1" "x2" "x3"]
                                       [[1 2 3] [4 5 6] [7 8 9]])))

最初に指定したラベルが、横軸にくるようです。

先ほどまでのdatasetの例では使いませんでしたが、datasetに対してcore.$rollupを使うことで集合演算ができるようです。

(c/view (ch/bar-chart :x2 :x1
                      :data (c/$rollup :mean
                                       :x1
                                       :x2
                                       (c/dataset ["x1" "x2"]
                                                  [[1 "c1"] [4 "c2"] [7 "c1"] [10 "c2"]]))))

今回は、x2でグループ化して、x1の中央値を集計。

結果。

line-chart

最後、簡単に折れ線グラフ。

(c/view (ch/line-chart ["x1" "x2" "x3" "x4" "x5"]
                       [1 3 5 2 10]))

datasetは使っていません。

結果。

使っているデータは超適当ですが、なんとなく使い方わかってきました。