Deedle で遊ぼう


この記事は F# Advent Calendar 2013 の 17 日目担当です。

Deedle というパッケージを簡単に紹介します。なお,本文で使うソースコードは GitHub に公開しています。

Deedle ってなんだ

Deedle は,データフレームと呼ばれるデータ構造およびその操作を提供するライブラリーです。今年に入って登場した新しいライブラリーで, F# で記述されています[A]。他の言語に明るい方なら, Scala の SADDLE や Python の Pandas に相当するものだと言えば,わかる方がいるのかもしれません。 F# 製ですが, F# のみではなく, C# から使われることも想定された設計になっています。

データフレームとは,統計モデリングを行う上で基本的なデータ構造です。次のような形式をとります。

インデックス 目的変数 1 目的変数 目的変数 応答変数
インデックス 1
インデックス 2
インデックス
インデックス

これを行指向で表現するとレコードのリストのような形式になりますが,データフレームは列指向のデータ形式となっています。なぜ列指向になっているかは,『なぜデータフレームという形式が基本なのか』という記事を R Advent Calendar 2013 で書きましたので,ご覧ください。ちなみに,レコードのリストからデータフレームに変換するのは容易で, Frame.ofRecord を使うだけです。

Deedle は,データフレームに対し,以下のような操作を可能にします。

  • 行や列での任意のデータの切り出し。
  • 行と列の転置操作。
  • データクレンジング。例えば NA を含む行を除くなどの処理。
  • 列の連結。いわゆる join 操作。
  • 行の連結。すなわち同じ列を持つ 2 つのデータフレームを 1 つにまとめる操作。
  • データを特定の条件でグルーピングして集計してまとめる処理。いわゆる split-apply-combine 操作。
  • クロス集計。いわゆるピボットテーブル。
  • 要約統計量の算出。列方向での平均の算出など。
  • 列に対する演算。例えば列同士の和を求めたり,列を対数変換するなど。

最後の 2 つが列指向な感じがでていますね。

例題

さて,例を挙げて実際に使ってみましょう。

[note]
大学教養程度の数学が出てきます。苦手な方は「数学っぽい演算が簡単に出きて便利なのね」くらいを感じ取って先にお進み下さい。
[/note]

ここで利用するのは以下のデータです。ただし,インデックスは便宜上つけていますが,今回のケースでは使用しません。

Index Girth Height Volume
1 8.3 70 10.3
2 8.6 65 10.3
3 8.8 63 10.2
4 10.5 72 16.4
5 10.7 81 18.8
6 10.8 83 19.7
7 11.0 66 15.6
8 11.0 75 18.2
9 11.1 80 22.6
10 11.2 75 19.9
11 11.3 79 24.2
12 11.4 76 21.0
13 11.4 76 21.4
14 11.7 69 21.3
15 12.0 75 19.1
16 12.9 74 22.2
17 12.9 85 33.8
18 13.3 86 27.4
19 13.7 71 25.7
20 13.8 64 24.9
21 14.0 78 34.5
22 14.2 80 31.7
23 14.5 74 36.3
24 16.0 72 38.3
25 16.3 77 42.6
26 17.3 81 55.4
27 17.5 82 55.7
28 17.9 80 58.3
29 18.0 80 51.5
30 18.0 80 51.0
31 20.6 87 77.0

このデータが次のような CSV で提供されているとします。

Girth,Height,Volume
8.3,70,10.3
8.6,65,10.3
8.8,63,10.2
(以下省略)

これは R の組み込みデータの trees という伐採された black cherry の測定データで,各列は胸高[B]直径 (インチ),樹高 (フィート),体積 (立方フィート) を示します[C]

CSV からデータフレームに読み込むには, Frame.ReadCsv を使います[D]

open Deedle

let trees = Frame.ReadCsv ("trees.csv")

Girth とは周囲長のことですが,データは直径というややこしいデータになっています。混乱のもとなので列名を DBH (Diameter at Breast Height) に修正しましょう。

trees.RenameSeries ("Girth", "DBH")

体積を DBH と樹高から推定したいと考えます。次のような推定式を考えます。

インチとフィートで単位が混在していますが,その辺の細かい調整は に押し込んで考えればよいです[E]

このままでは考えづらいので,両辺の対数をとりましょう。

わかりやすくなりました。さらに式を単純にするために と書き換えましょう。すると次の式を得ます。

この推定式で求められる体積と,実際の体積との誤差 (二乗和) がもっとも小さくなるような を求めます。すなわち をデータ数として,

に対して

を解くことになります。整理すると以下のようになります。

に対応する を明記すればきれいな関係ですね。

これくらいなら手計算でもなんとかなりますが,ここでは Math.NET Numerics を使いましょう。 Math.NET Numerics には多重線形回帰のための Fit.LinearMultiDim というメソッドも用意されているのですが,使いづらいので今回は正面突破でいきます。とはいえ,単純な連立方程式を解くだけなので,大したことはやりませんが。

open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.LinearAlgebra.Double

let n = float trees.RowCount
let D = Series.Log trees?DBH
let H = Series.Log trees?Height
let V = Series.Log trees?Volume
let sum = Series.sum  // コードが横に長くなるので
let a =
   matrix [
      [    n; sum D         ; sum H         ]
      [sum D; sum (D ** 2.0); sum (H * D)   ]
      [sum H; sum (H * D)   ; sum (H ** 2.0)]
   ]
let b =
   vector [
      sum V
      sum (D * V)
      sum (H * V)
   ]
let coef = a.QR().Solve(b)
printfn "log(Volume) = %f + %f log(DBH) + %f log(Height)"
   coef.[0]  // -6.631617
   coef.[1]  //  1.982650
   coef.[2]  //  1.117123

シンプルですね。 Deedle が列指向の演算をサポートしているおかげで,元の数式と同じように簡潔に記述することができたのです。

結果に関しては,底面が直径 で高さが の円柱の体積が で与えられることを考えれば, であるため,妥当な結果であるといえます。

ところでフィールドワークでは樹高の測定は大変なので,通常は胸高周囲長のみを測定し,樹高は別に求めたアロメトリー式から推定するというのが一般的です。つまり胸高直径から樹高を推定する式をあらかじめ林分や樹種ごとに求めておいて,それを予測式に使うという手法です。やることはほとんど同じ (むしろ変数が減るので簡単) なので省略しますが, F# 使いのフィールドワーカーは練習問題としてやってみるとよいでしょう。

R を使う

Deedle には統計解析環境 R との相互運用のためのプラグイン Deedle.RPlugin があります。これを利用すると,上のような複雑な計算を実質 1 行でやってくれます[F]。なお, RPlugin の利用には R をあらかじめインストールしておく必要があります[G]

open RProvider
open RProvider.``base``
open RProvider.stats

let formula = "log(Volume) ~ log(DBH) + log(Height)"
using (R.lm (formula, trees)) (R.summary >> R.print)
|> ignore

これは以下の R スクリプトに相当します。

formula <- log(Volume) ~ log(DBH) + log(Height)
print(summary(lm(formula, trees)))

R の組み込みデータにも trees という組み込みデータがありますが,この trees は F# 側で作成されたデータフレームを R のオブジェクトに変換したものであることに注意してください。

ちなみに R のデータフレームを Deedle にもってくることも可能です。例えば今回使用した trees の場合は,以下のようにします。

open RProvider.datasets

let trees : Frame<int, string> = R.trees.GetValue ()

おわりに

Deedle を使うことで,データの処理や統計解析が容易になります。今回は示しませんでしたが,例えば FSharp.Charting などの描画パッケージとともに使うことで,よりリッチなデータ解析が行えるようになります。実は Deedle などデータサイエンス関連のパッケージをまとめた FsLab という NuGet パッケージがあります。今回使用したライブラリーはいずれも FsLab に含まれています。 F# でデータ解析関連の処理を試す場合は,このパッケージを使うとよいでしょう。

参考文献

おまけ

去年の乱数ネタは API 修正等を経て現在 FsRandom という名前になりました。 NuGet でインストール可能 なので遊んでやってください。

ちなみに FsRandom のドキュメントFAKEFSharp.Formatting で半自動生成しています[H]

脚注

  1. 以前 F# 談話室でちょろっとだけ紹介したことがあるので,覚えている方がいるかもしれませんが,当時は Deedle という名前も付いていない本当にスタートアップの状態でした。 []
  2. ここでは 4 フィート 6 インチ (約 140 センチメートル)。ちなみに日本では通常 130 センチメートルを胸高として測定します。 []
  3. 1 インチは 2.54 センチメートル, 1 フィートは 0.3048 メートル, 1 立方フィートは 0.0283 平方メートルです。 []
  4. 今のところウェブ上のデータを読み込むとキャッシュのタイミングがおかしくてシークしようとしてエラーがでます。なのでローカルに保存してから読み込む必要があります。 []
  5. Deedle というか FSharp.Data ではスキームに単位を与えれば F# の単位も扱えるみたいですが,うまくいったことがない…。 []
  6. しかも統計学的仮説検定までやってくれるおまけ付き。 []
  7. 最近の更新で Mac/Linux でも動作するようになったようですが,未確認です。 []
  8. Linux/Mono 環境でうまく動かせていないので半自動です。 []