F#を使用して10分で学ぶDeedle
このドキュメントでは、F#のデータフレームライブラリにおける主要な機能の概要を説明します。 なおこのページをGitHubから F# スクリプトファイル としてダウンロードすれば、サンプルをインタラクティブに実行することもできます。
最初の手順として、 NuGet経由で Deedle.dll
をインストールします。
次にライブラリをロードします。
F# Interactive上でライブラリの .dll
をロードしている .fsx
ファイルをロードすることにより、
データフレームやシリーズデータを表す型に対する簡易プリンターを登録できます。
今回のサンプルでは F# Charting も必要になるため、
以下のようにします:
1: 2: 3: 4: 5: 6: 7: 8: |
#I "../../../packages/FSharp.Charting.0.90.6" #I "../../../packages/Deedle.0.9.5" #load "FSharp.Charting.fsx" #load "Deedle.fsx" open System open Deedle open FSharp.Charting |
シリーズおよびフレームの作成
データフレームは一意な列名を持ったシリーズのコレクションです (列名は実際には文字列である必要はありません)。 したがってデータフレームを作成するには、まず1つのシリーズを作成することになります:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
// キーとなるシーケンスと値のシーケンスを作成します let dates = [ DateTime(2013,1,1); DateTime(2013,1,4); DateTime(2013,1,8) ] let values = [ 10.0; 20.0; 30.0 ] let first = Series(dates, values) // 観測対象となる1つのリストからシリーズを作成します Series.ofObservations [ DateTime(2013,1,1) => 10.0 DateTime(2013,1,4) => 20.0 DateTime(2013,1,8) => 30.0 ] |
Keys |
2013/01/01 0:00:00 |
2013/01/04 0:00:00 |
2013/01/08 0:00:00 |
---|---|---|---|
Values |
10 |
20 |
30 |
1: 2: 3: 4: 5: |
// 'Series.ofObservations' の省略バージョンです series [ 1 => 1.0; 2 => 2.0 ] // キーを明示的(かつ順番通り)に指定してシリーズを作成します Series.ofValues [ 10.0; 20.0; 30.0 ] |
Keys |
0 |
1 |
2 |
---|---|---|---|
Values |
10 |
20 |
30 |
シリーズの型はジェネリックである点に注意してください。
Series<K, T>
はキーの型が K
で、値の型が T
です。
ではランダムな値を持った10日分のシリーズデータを生成してみましょう:
1: 2: 3: 4: 5: 6: 7: 8: |
/// 'first' から 'count' 日分の日付を生成します let dateRange (first:System.DateTime) count = (...) /// ランダムなdouble値を 'count' 個生成します let rand count = (...) // 10日分の値を持ったシリーズ let second = Series(dateRange (DateTime(2013,1,1)) 10, rand 10) |
Keys |
2013/01/01 0:00:00 |
2013/01/02 0:00:00 |
2013/01/03 0:00:00 |
2013/01/04 0:00:00 |
2013/01/05 0:00:00 |
... |
2013/01/08 0:00:00 |
2013/01/09 0:00:00 |
2013/01/10 0:00:00 |
---|---|---|---|---|---|---|---|---|---|
Values |
0.35 |
0.4 |
0.59 |
0.69 |
0.68 |
... |
0.85 |
0.39 |
0.89 |
そうすると first
と second
という2つの列を持ち、
それぞれが列と同名の値を持つような
データフレームを簡単に作成できるようになります:
1:
|
let df1 = Frame(["first"; "second"], [first; second]) |
first |
second |
|
---|---|---|
2013/01/01 0:00:00 |
10 |
0.35332411683785 |
2013/01/02 0:00:00 |
N/A |
0.402045726497679 |
2013/01/03 0:00:00 |
N/A |
0.588567314477902 |
2013/01/04 0:00:00 |
20 |
0.693529703045976 |
2013/01/05 0:00:00 |
N/A |
0.68279335493352 |
2013/01/06 0:00:00 |
N/A |
0.23253303497682 |
2013/01/07 0:00:00 |
N/A |
0.892836808642762 |
2013/01/08 0:00:00 |
30 |
0.850963400607446 |
2013/01/09 0:00:00 |
N/A |
0.388250130409491 |
2013/01/10 0:00:00 |
N/A |
0.887282186135315 |
データフレームを表す型 Frame<TRowKey, TColumnKey>
には2つのジェネリック引数があります。
1つめの引数は行キーを表す型で、先ほどの例では DateTime
を明示的に指定しましたが、
指定しない場合にはint
型になります。
2つめの引数は列キーの型です。
一般的には string
ですが、日付を列キーにした転置バージョンのフレームを作成すると
便利な場合もあるでしょう。
データフレームには異種データを同梱させることができるため、値の型がない場合もあります。
この場合にはデータフレームからデータを取得する際に型を指定する必要があります。
出力結果からわかるように、フレームを作成すると2つのシリーズのインデックスが
自動的に結合されます(すべてのシリーズに含まれるすべての日付が結果に含まれるよう、
「外部結合(outer join)」が行われます)。
データフレームの first
列にはいくつか値無しのデータが含まれています。
また、さらに手軽なシンタックスを使って行データから、あるいは個別のデータから フレームを作成することもできます:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
// 先と同じ let df2 = Frame.ofColumns ["first" => first; "second" => second] // 転置バージョン。ここでは行に"first"と"second"、列に日付が設定されます let df3 = Frame.ofRows ["first" => first; "second" => second] // 個別の値 (行 * 列 * 値) を指定してフレームを作成します let df4 = [ ("Monday", "Tomas", 1.0); ("Tuesday", "Adam", 2.1) ("Tuesday", "Tomas", 4.0); ("Wednesday", "Tomas", -5.4) ] |> Frame.ofValues |
データフレームはF#のレコード型(あるいはpublicで読み取り可能なプロパティを持った任意のクラス)の
コレクションからも簡単に作成できます。
Frame.ofRecords
関数を使用すると、レコードの名前とプロパティの型をリフレクションで探し出して、
同じ構造を持ったデータフレームを作成することができます。
1: 2: 3: 4: 5: 6: 7: 8: 9: |
// 'Price' というレコードと 'prices' コレクションがあるとします type Price = { Day : DateTime; Open : float } let prices = [ { Day = DateTime.Now; Open = 10.1 } { Day = DateTime.Now.AddDays(1.0); Open = 15.1 } { Day = DateTime.Now.AddDays(2.0); Open = 9.1 } ] // 'Day' と 'Open' という列を持ったデータフレームを作成します let df5 = Frame.ofRecords prices |
最後に、データフレームはCSVから読み取ることもできます:
1: 2: |
let msftCsv = Frame.ReadCsv(__SOURCE_DIRECTORY__ + "/../data/stocks/MSFT.csv") let fbCsv = Frame.ReadCsv(__SOURCE_DIRECTORY__ + "/../data/stocks/FB.csv") |
Date |
Open |
High |
... |
Close |
Volume |
Adj Close |
|
---|---|---|---|---|---|---|---|
0 |
2013-11-07 |
49.24 |
49.87 |
... |
47.56 |
96925500 |
47.56 |
1 |
2013-11-06 |
50.26 |
50.45 |
... |
49.12 |
67648600 |
49.12 |
2 |
2013-11-05 |
47.79 |
50.18 |
... |
50.11 |
76668300 |
50.11 |
3 |
2013-11-04 |
49.37 |
49.75 |
... |
48.22 |
80206200 |
48.22 |
4 |
2013-11-01 |
50.85 |
52.09 |
... |
49.75 |
94822200 |
49.75 |
5 |
2013-10-31 |
47.16 |
52.00 |
... |
50.21 |
248388200 |
50.21 |
6 |
2013-10-30 |
50.00 |
50.21 |
... |
49.01 |
116674400 |
49.01 |
7 |
2013-10-29 |
50.73 |
50.79 |
... |
49.40 |
101859700 |
49.40 |
... |
... |
... |
... |
... |
... |
... |
... |
367 |
2012-05-23 |
31.37 |
32.50 |
... |
32.00 |
73600000 |
32.00 |
368 |
2012-05-22 |
32.61 |
33.59 |
... |
31.00 |
101786600 |
31.00 |
369 |
2012-05-21 |
36.53 |
36.66 |
... |
34.03 |
168192700 |
34.03 |
370 |
2012-05-18 |
42.05 |
45.00 |
... |
38.23 |
573576400 |
38.23 |
データフレームはデータのロード時に値を解析して、
その値に最適な型を自動判別します。
しかし日付と時刻については自動変換は行われません。
どの日付型(DateTime
や DateTimeOffset
あるいは他のカスタム型)による表現が適切なのかを
ユーザーが決める必要があります。
インデックスと結合を指定する
ここまでで株価を含んだ fbCsv
と msftCsv
フレームを用意出来ましたが、
これらは数値順にインデックスされています。
つまりたとえば4番目の価格を取得するといったことができます。
しかしここでは日付順で並び替えようと思っています
(いくつか値無しのものが出てくるでしょう)。
そのためには行のインデックスを "Date" 列に設定します。
日付をインデックスに設定した後はインデックス順に並び替える必要があります。
Yahoo Financeの価格情報は新しいものから古いものの順で表示されていますが、
今回のデータフレームでは古いものから新しいものの昇順で表示させることにします。
フレームには順序付きインデックスがあるため、後で必要になる機能を追加しておきます (たとえばインデックスとして明示的に含まれていないような日付を指定して 部分範囲を選択できるようにします)。
1: 2: 3: 4: 5: |
// インデックスならびに行の順番としてDate列を使用します let msftOrd = msftCsv |> Frame.indexRowsDate "Date" |> Frame.sortRowsByKey |
indexRowsDate
関数は DateTime
型の行を新しいインデックスとして使用します。
ライブラリには他にも一般的な型のインデックスに対する関数(たとえば indexRowsInt
)や、
ジェネリック関数もあります。
ジェネリック関数を使用する場合、型アノテーションが必要になるため、
特定の型に対する関数を使用したほうがよいでしょう。
次は Frame
モジュールにある別の関数を使用して行をソートします。
このモジュールにはどのような状況でも使用できるような、便利な関数が多数定義されています。
サポートされている機能を確認するために、関数のリストに目を通しておくことをおすすめします。
さてこれで正しくインデックスが付けられた株価が用意できたので、 関心のあるデータ(始値と終値)だけを含む新しいデータフレームを作成して、 それらの差分を表す新しい列を追加します:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
// 始値(Open)と終値(Close)だけを含んだデータフレームを作成します let msft = msftOrd.Columns.[ ["Open"; "Close"] ] // 始値と終値の差分を含む新しい列を作成します msft?Difference <- msft?Open - msft?Close // Facebookデータに対しても同じ処理を行います let fb = fbCsv |> Frame.indexRowsDate "Date" |> Frame.sortRowsByKey |> Frame.sliceCols ["Open"; "Close"] fb?Difference <- fb?Open - fb?Close // これで差分を簡単にプロットできるようになります Chart.Combine [ Chart.Line(msft?Difference |> Series.observations) Chart.Line(fb?Difference |> Series.observations) ] |
f.Columns.[ .. ]
として列を選択すると、(既に行ったように)列のリスト、
あるいは単一の列キー、あるいは(関連づけられたインデックスが順序を持つのであれば)
単一の範囲として扱うことができます。
そして df?Column <- (...)
というシンタックスを使用すると、
データフレームに新しい列を追加できます。
これはデータフレームでサポートされている唯一の可変操作です。
その他の操作では新規作成されたデータフレームが結果として返されます。
次にMicrosoftとFacebook両方の(適切にアラインされた)データを含む
単一のデータフレームを作成します。
そのためには Join
メソッドを使用します。
ただしその前に、キーの重複は許容されていないため、それぞれの列名を変更します:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
// 列名が一意になるように変更します let msftNames = ["MsftOpen"; "MsftClose"; "MsftDiff"] let msftRen = msft |> Frame.indexColsWith msftNames let fbNames = ["FbOpen"; "FbClose"; "FbDiff"] let fbRen = fb |> Frame.indexColsWith fbNames // 外部結合 (値無しを含みつつ、アラインおよびフィルを行います) let joinedOut = msftRen.Join(fbRen, kind=JoinKind.Outer) // 内部結合 (値無しの行を削除します) let joinedIn = msftRen.Join(fbRen, kind=JoinKind.Inner) // 有効な値だけを対象にして、日ごとの差分を可視化します Chart.Rows [ Chart.Line(joinedIn?MsftDiff |> Series.observations) Chart.Line(joinedIn?FbDiff |> Series.observations) ] |
値の選択とスライシング
データフレームにはデータアクセス時に使用できる主要なプロパティが2つあります。
Rows
プロパティはそれぞれの行を(シリーズとして)含んだシリーズを返し、
Columns
プロパティはそれぞれの列を(シリーズとして)含んだシリーズを返します。
これらのシリーズに対して様々な方法でインデックス化
あるいはスライシングを行うことができます:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
// 特定の日付の行を確認します joinedIn.Rows.[DateTime(2013, 1, 2)] val it : ObjectSeries<string> = FbOpen -> 28.00 FbClose -> 27.44 FbDiff -> -0.5599 MsftOpen -> 27.62 MsftClose -> 27.25 MsftDiff -> -0.3700 // 2013年1月2日のFacebookの始値を取得します joinedIn.Rows.[DateTime(2013, 1, 2)]?FbOpen val it : float = 28.0 |
最初の式における返り値の型は ObjectSeries<string>
で、
この型は Series<string, obj>
から派生したもので、
型無しのシリーズを表します。
特定のキーに対する値を取得して、必要になる型に変換するためには
GetAs<int>("FbOpen")
メソッド(または TryGetAs
)を使用します。
型無しシリーズではデフォルトの ?
演算子
(静的に既知の値型を使用して値を返す演算子)が隠ぺいされて、
代わりに任意の値を float
へと自動変換する ?
演算子が用意されます。
先の例では単一キーのインデクサを使用しました。 しかし複数キーを(リストとして)指定したり、 (スライシングの文法を使用して)範囲を指定したりすることもできます:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
// 2013年1月の月初め3日の値を取得します let janDates = [ for d in 2 .. 4 -> DateTime(2013, 1, d) ] let jan234 = joinedIn.Rows.[janDates] // 3日間の始値の平均を計算します jan234?MsftOpen |> Stats.mean // 2013年1月全体の値を取得します let jan = joinedIn.Rows.[DateTime(2013, 1, 1) .. DateTime(2013, 1, 31)] |
Value 'Frame.map(round (jan*100.0))/100.0 |> Frame.mapRowKeys (fun dt -> dt.ToShortDateString())' could not be evaluated
1: 2: 3: |
// 月全体の平均を計算します jan?FbOpen |> Stats.mean jan?MsftOpen |> Stats.mean |
(先の例のように)単一の日付を指定した場合のインデックス演算子の結果は単一のデータシリーズ、 複数のインデックスや(今回の例のように)範囲を指定した場合の結果は新しいデータフレームになります。
今回使用した Series
モジュールには、 mean
sdv
sum
など、
データシリーズに対する便利な統計用関数が定義されています。
なお範囲を指定したスライシング(2番目の例)では 1月1日から31日までの日付シーケンスが実際に生成されているわけではない点に注意してください。 これら2つの日付がインデックスとして渡されているだけです。 データフレームには順序付きインデックスがあるため、 1月1日よりも大きく、1月31日よりも小さいすべてのキーがインデックスによって検索されるというわけです (ただしここには問題があります。 返されたデータフレームには1月1日のデータが含まれておらず、1月2日から始まっています。)
時系列データを使用する
既に説明したように、順序付きのシリーズまたはデータフレームがあれば、 様々な方法でデータを並び替えることができます。 先の例では厳密一致ではなく、下限上限を指定してスライシングしました。 同様に、ダイレクトルックアップを行う場合には 指定した値に最も近い小さな(あるいは大きな)要素を取得することもできます。
たとえば10日分10個のデータを持った2つのシリーズを用意します。
daysSeries
は DateTime.Today
(午前 12:00) を始点とするキーを持ち、
obsSeries
は 現在の時刻が設定された日付をキーに持つようにします
(これは間違った表現ですが、アイディアとしては伝わるはずです):
1: 2: |
let daysSeries = Series(dateRange DateTime.Today 10, rand 10) let obsSeries = Series(dateRange DateTime.Now 10, rand 10) |
Keys |
2014/06/17 0:00:00 |
2014/06/18 0:00:00 |
2014/06/19 0:00:00 |
2014/06/20 0:00:00 |
2014/06/21 0:00:00 |
... |
2014/06/24 0:00:00 |
2014/06/25 0:00:00 |
2014/06/26 0:00:00 |
---|---|---|---|---|---|---|---|---|---|
Values |
0.13 |
0.34 |
0.43 |
0.13 |
0.84 |
... |
0.12 |
0.64 |
0.57 |
Keys |
2014/06/17 11:47:16 |
2014/06/18 11:47:16 |
2014/06/19 11:47:16 |
2014/06/20 11:47:16 |
2014/06/21 11:47:16 |
... |
2014/06/24 11:47:16 |
2014/06/25 11:47:16 |
2014/06/26 11:47:16 |
---|---|---|---|---|---|---|---|---|---|
Values |
0.13 |
0.34 |
0.43 |
0.13 |
0.84 |
... |
0.12 |
0.64 |
0.57 |
daysSeries.[date]
というインデックス演算子は 厳密な 意味を持つため、
正確な日付が参照できない場合にはエラーになります。
一方、 Get
メソッドには要求された動作を指定するための引数があります:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
// 現在の時刻に対応するデータは無いのでエラーになります。 try daysSeries.[DateTime.Now] with _ -> nan try obsSeries.[DateTime.Now] with _ -> nan // 動作します。DateTime.Today (12:00 AM)に対応する値が取得できます。 daysSeries.Get(DateTime.Now, Lookup.ExactOrSmaller) // 動作しません。Today (12:00 AM) 以前で最も近いキーは存在しません。 try obsSeries.Get(DateTime.Today, Lookup.ExactOrSmaller) with _ -> nan |
(option値を取得する) TryGet
あるいは
(1度に複数のキーを指定してルックアップを行う) GetItems
を呼ぶ場合にも、
同じように動作を指定できます。
なおこの動作は順序付きインデックスを持ったシリーズまたはフレームに対してのみ
有効である点に注意してください。
順序が無い場合はすべての操作が厳密一致になります。
データフレームをleft joinまたはright joinする場合にも指定できます。 デモとして、1と2というインデックスをそれぞれ持った 2つのデータフレームを用意してみます:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: |
let daysFrame = [ 1 => daysSeries ] |> Frame.ofColumns let obsFrame = [ 2 => obsSeries ] |> Frame.ofColumns // 列2にあるすべての値は(一致する時間が無いため)値無しです let obsDaysExact = daysFrame.Join(obsFrame, kind=JoinKind.Left) // すべての値が有効です。 // 各日付において、やや経過した時間に対して最も近い小さな値をキーとする値を取得しています。 let obsDaysPrev = (daysFrame, obsFrame) ||> Frame.joinAlign JoinKind.Left Lookup.ExactOrSmaller // 1番目の値は値無しですが、2番目以降は有効な値です // (1番目のデータは最も小さなキーであるため、 // それよりも最も近くて大きい値は存在しません)。 let obsDaysNext = (daysFrame, obsFrame) ||> Frame.joinAlign JoinKind.Left Lookup.ExactOrGreater |
一般的に、 Series
や Frame
モジュール内の関数を使用して行えることは
いずれもオブジェクトのメンバー(あるいは拡張メンバー)を使用しても
行うことができるようになっています。
先の例では両方を使用しました。
まずオプション引数を指定した Join
をメンバーメソッドとして呼び出した後、
joinAlign
関数を呼び出しました。
好みに応じてどちらを使用しても構いません。
今回は(ページに収まらないような長い式を記述するのではなく)
コードをパイプライン化したかったので joinAlign
を使用しました
Join
メソッドには2つのオプション引数を指定できます。
引数 ?lookup
は ?kind
が Left
と Right
の
いずれでもない場合には無視されます。
また、データフレームに順序が無い場合には厳密一致のデフォルト動作になります。
joinAlign
関数も同様です。
射影とフィルタリング
シリーズに対するフィルタリング(filtering)と射影(projection)は
それぞれ Where
と Select
メソッドで行うことができます。
またこれらのメソッドには Series.map
と Series.filter
関数が対応します。
(値またはキーのいずれか一方だけを対象に変換したい場合には
Series.mapValues
や Series.mapKeys
関数も使用できます)。
これらのメソッドは直接データフレームに対して呼び出せないため、
(対象に応じて) df.Rows
または df.Columns
と記述する必要があります。
なお Frame
モジュールにも Frame.mapRows
という同じような関数があります。
以下のコードでは株価の高い方の名前
("FB"または"MSFT")を含んだ新しい列を追加しています:
1: 2: |
joinedOut?Comparison <- joinedOut |> Frame.mapRowValues (fun row -> if row?MsftOpen > row?FbOpen then "MSFT" else "FB") |
行を射影またはフィルタリングする場合、値無しのデータに注意する必要があります。
行に対するアクセサー row?MsftOpen
は特定の列の値を読み取ります
(そしてその値をfloat
に変換します)が、列の値が無効な場合には
MissingValueException
例外がすろーされます。
mapRowValues
のような射影関数ではこの例外が自動的にキャッチされて、
対応するシリーズの値が値無しだとマークされます
(ただしこれ以外の型の例外はキャッチされません)。
値無しに対する処理をより明示的に行う場合には Series.hasAll ["MsfOpen"; "FbOpen"]
として、必要な値がすべてシリーズにあるかどうかをチェックするようにします。
もし値が無いのであればラムダ関数で null
を返すようにします。
そうするとそれが自動的に値無しだとみなされるようになります
(そして以降の操作で処理対象から外されることになるでしょう)。
さてこれでMicrosoftの株価がFacebookを上回った日数、 あるいはその逆の日数を取得できるようになりました:
1: 2: 3: 4: 5: 6: 7: |
joinedOut.GetColumn<string>("Comparison") |> Series.filterValues ((=) "MSFT") |> Series.countValues val it : int = 220 joinedOut.GetColumn<string>("Comparison") |> Series.filterValues ((=) "FB") |> Series.countValues val it : int = 103 |
この場合には、有効な行しか含まれなくなる
joinedIn
を使用したほうがよかったかもしれません。
しかし値無しを含むデータフレームを処理することも多いため、
この方法を確認しておくことには意味があります。
別の方法も紹介しましょう:
1: 2: 3: 4: 5: 6: 7: 8: |
// 'Open'列だけを含むデータフレームを取得します let joinedOpens = joinedOut.Columns.[ ["MsftOpen"; "FbOpen"] ] // 値無しを含まない行だけを取得すれば // 安全にフィルタおよびカウントできます joinedOpens.RowsDense |> Series.filterValues (fun row -> row?MsftOpen > row?FbOpen) |> Series.countValues |
ポイントは6行目にある RowsDense
の部分です。
これは Rows
と同じような動作をしますが、
値無しを含まない行だけを返すという違いがあります。
したがって、チェックせずとも安全にフィルタリングを実行できるというわけです。
しかし FbClose
列は必要としていないため、
この列に値無しが含まれていても問題にはなりません。
そのため、元のデータフレームから必要な2つの列だけを射影して、
最初に joinedOpens
を作成しているというわけです。
グループ化と集計
最後にグループ化(grouping)と集計(aggregation)について簡単に紹介します。 時系列データのグループ化に関する詳細については 時系列機能のチュートリアル を参照してください。 また、 データフレームの機能 には 順序無しのフレームに対するグループ化に関する説明があります。
ここでは Frame.groupRowsUsing
という一番単純な機能を紹介します
(GroupRowsUsing
メンバーメソッドもあります)。
この関数には各行における新しいキーを選択するキーセレクタを指定できます。
列内の値を使用してデータをグループ化したい場合には
Frame.groupRowsBy column
というようにします。
以下のスニペットでは行を月および年でグループ化しています:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
let monthly = joinedIn |> Frame.groupRowsUsing (fun k _ -> DateTime(k.Year, k.Month, 1)) val monthly : Frame<(DateTime * DateTime),string> = FbOpen MsftOpen 5/1/2012 5/18/2012 -> 38.23 29.27 5/21/2012 -> 34.03 29.75 5/22/2012 -> 31.00 29.76 : ... 8/1/2013 8/12/2013 -> 38.22 32.87 8/13/2013 -> 37.02 32.23 8/14/2013 -> 36.65 32.35 |
出力結果はページに収まるように省略してあります。
見ての通り、 DateTime * DateTime
のタプルを行のキーとするような
データフレームが返されます。
このデータフレームは 階層的 (あるいはマルチレベル) インデックスとして
扱うことができます。
たとえば出力結果では(正しく順序づけられているとすれば)
自動的に複数行がグループとして表示されます
階層的インデックスに対しては様々な操作が可能です。 たとえば特定のグループ(2013年5月)に含まれる行を取得して、 グループ内の列の平均を計算することができます:
1: 2: 3: 4: 5: 6: 7: 8: |
monthly.Rows.[DateTime(2013,5,1), *] |> Stats.mean val it : Series<string,float> = FbOpen -> 26.14 FbClose -> 26.35 FbDiff -> 0.20 MsftOpen -> 33.95 MsftClose -> 33.76 MsftDiff -> -0.19 |
このスニペットではF# 3.1 (Visual Studio 2013)以降で
利用可能なスライシング記法を使用しています。
以前のバージョンであれば
monthly.Rows.[Lookup1Of2 (DateTime(2013,5,1))]
とすれば同じ動作になります。
これはキーの1番目だけを指定して、
2番目のコンポーネントは任意のものでよいということを示しています。
Frame.getNumericColumns
と Stats.levelMean
を組み合わせて
第1レベルの全グループの平均を取得することもできます:
1: 2: 3: 4: |
monthly |> Frame.getNumericColumns |> Series.mapValues (Stats.levelMean fst) |> Frame.ofColumns |
ここでは単にキーがタプルであることを利用しました。
fst
関数はキー(月および年)の1番目の日付を射影するため、
第1レベルのキーを含み、有効な数値列すべての平均値を持ったフレームが結果として返されます。
Full name: Tutorial.dates
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
Full name: Tutorial.values
Full name: Tutorial.first
module Series
from Deedle
--------------------
type Series =
static member ofNullables : values:seq<Nullable<'a0>> -> Series<int,'a0> (requires default constructor and value type and 'a0 :> ValueType)
static member ofObservations : observations:seq<'a0 * 'a1> -> Series<'a0,'a1> (requires equality)
static member ofOptionalObservations : observations:seq<'K * 'a1 option> -> Series<'K,'a1> (requires equality)
static member ofValues : values:seq<'a0> -> Series<int,'a0>
Full name: Deedle.FSharpSeriesExtensions.Series
--------------------
type Series<'K,'V (requires equality)> =
interface IFsiFormattable
interface ISeries<'K>
new : pairs:seq<KeyValuePair<'K,'V>> -> Series<'K,'V>
new : keys:seq<'K> * values:seq<'V> -> Series<'K,'V>
new : index:IIndex<'K> * vector:IVector<'V> * vectorBuilder:IVectorBuilder * indexBuilder:IIndexBuilder -> Series<'K,'V>
member After : lowerExclusive:'K -> Series<'K,'V>
member Aggregate : aggregation:Aggregation<'K> * observationSelector:Func<DataSegment<Series<'K,'V>>,KeyValuePair<'TNewKey,OptionalValue<'R>>> -> Series<'TNewKey,'R> (requires equality)
member Aggregate : aggregation:Aggregation<'K> * keySelector:Func<DataSegment<Series<'K,'V>>,'TNewKey> * valueSelector:Func<DataSegment<Series<'K,'V>>,OptionalValue<'R>> -> Series<'TNewKey,'R> (requires equality)
member AsyncMaterialize : unit -> Async<Series<'K,'V>>
member Before : upperExclusive:'K -> Series<'K,'V>
...
Full name: Deedle.Series<_,_>
--------------------
new : pairs:seq<Collections.Generic.KeyValuePair<'K,'V>> -> Series<'K,'V>
new : keys:seq<'K> * values:seq<'V> -> Series<'K,'V>
new : index:Indices.IIndex<'K> * vector:IVector<'V> * vectorBuilder:Vectors.IVectorBuilder * indexBuilder:Indices.IIndexBuilder -> Series<'K,'V>
Full name: Deedle.FSharpSeriesExtensions.series
Full name: Tutorial.dateRange
'first' から 'count' 日分の日付を生成します
Full name: Tutorial.rand
ランダムなdouble値を 'count' 個生成します
seq { for i in 0 .. (count - 1) -> rnd.NextDouble() }
Full name: Tutorial.second
Full name: Tutorial.df1
module Frame
from Deedle
--------------------
type Frame =
static member CreateEmpty : unit -> Frame<'R,'C> (requires equality and equality)
static member FromArray2D : array:'T [,] -> Frame<int,int>
static member FromColumns : cols:Series<'TColKey,Series<'TRowKey,'V>> -> Frame<'TRowKey,'TColKey> (requires equality and equality)
static member FromColumns : cols:Series<'TColKey,ObjectSeries<'TRowKey>> -> Frame<'TRowKey,'TColKey> (requires equality and equality)
static member FromColumns : columns:seq<KeyValuePair<'ColKey,ObjectSeries<'RowKey>>> -> Frame<'RowKey,'ColKey> (requires equality and equality)
static member FromColumns : columns:seq<KeyValuePair<'ColKey,Series<'RowKey,'V>>> -> Frame<'RowKey,'ColKey> (requires equality and equality)
static member FromColumns : rows:seq<Series<'ColKey,'V>> -> Frame<'ColKey,int> (requires equality)
static member FromRecords : values:seq<'T> -> Frame<int,string>
static member FromRecords : series:Series<'K,'R> -> Frame<'K,string> (requires equality)
static member FromRowKeys : keys:seq<'K> -> Frame<'K,string> (requires equality)
...
Full name: Deedle.Frame
--------------------
type Frame<'TRowKey,'TColumnKey (requires equality and equality)> =
interface IDynamicMetaObjectProvider
interface INotifyCollectionChanged
interface IFsiFormattable
interface IFrame
new : names:seq<'TColumnKey> * columns:seq<ISeries<'TRowKey>> -> Frame<'TRowKey,'TColumnKey>
private new : rowIndex:IIndex<'TRowKey> * columnIndex:IIndex<'TColumnKey> * data:IVector<IVector> -> Frame<'TRowKey,'TColumnKey>
member AddColumn : column:'TColumnKey * series:ISeries<'TRowKey> -> unit
member AddColumn : column:'TColumnKey * series:seq<'V> -> unit
member AddColumn : column:'TColumnKey * series:ISeries<'TRowKey> * lookup:Lookup -> unit
member AddColumn : column:'TColumnKey * series:seq<'V> * lookup:Lookup -> unit
...
Full name: Deedle.Frame<_,_>
--------------------
new : names:seq<'TColumnKey> * columns:seq<ISeries<'TRowKey>> -> Frame<'TRowKey,'TColumnKey>
Full name: Tutorial.df2
static member Frame.ofColumns : cols:seq<'a0 * #ISeries<'K>> -> Frame<'K,'a0> (requires equality and equality)
Full name: Tutorial.df3
static member Frame.ofRows : rows:Series<'a0,#ISeries<'a2>> -> Frame<'a0,'a2> (requires equality and equality)
Full name: Tutorial.df4
{Day: DateTime;
Open: float;}
Full name: Tutorial.Price
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
Full name: Tutorial.prices
Full name: Tutorial.df5
static member Frame.ofRecords : values:seq<'T> -> Frame<int,string>
Full name: Tutorial.msftCsv
static member Frame.ReadCsv : stream:IO.Stream * ?hasHeaders:bool * ?inferTypes:bool * ?inferRows:int * ?schema:string * ?separators:string * ?culture:string * ?maxRows:int -> Frame<int,string>
Full name: Tutorial.fbCsv
Full name: Tutorial.msftOrd
Full name: Deedle.Frame.indexRowsDate
Full name: Deedle.Frame.sortRowsByKey
Full name: Tutorial.msft
Full name: Tutorial.fb
Full name: Deedle.Frame.sliceCols
static member Area : data:seq<#value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Area : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Bar : data:seq<#value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member Bar : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> GenericChart
static member BoxPlotFromData : data:seq<#key * #seq<'a2>> * ?Name:string * ?Title:string * ?Color:Color * ?XTitle:string * ?YTitle:string * ?Percentile:int * ?ShowAverage:bool * ?ShowMedian:bool * ?ShowUnusualValues:bool * ?WhiskerPercentile:int -> GenericChart (requires 'a2 :> value)
static member BoxPlotFromStatistics : data:seq<#key * #value * #value * #value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?Percentile:int * ?ShowAverage:bool * ?ShowMedian:bool * ?ShowUnusualValues:bool * ?WhiskerPercentile:int -> GenericChart
static member Bubble : data:seq<#value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?BubbleMaxSize:int * ?BubbleMinSize:int * ?BubbleScaleMax:float * ?BubbleScaleMin:float * ?UseSizeForLabel:bool -> GenericChart
static member Bubble : data:seq<#key * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string * ?BubbleMaxSize:int * ?BubbleMinSize:int * ?BubbleScaleMax:float * ?BubbleScaleMin:float * ?UseSizeForLabel:bool -> GenericChart
static member Candlestick : data:seq<#value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> CandlestickChart
static member Candlestick : data:seq<#key * #value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Color * ?XTitle:string * ?YTitle:string -> CandlestickChart
...
Full name: FSharp.Charting.Chart
static member Chart.Line : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.GenericChart
Full name: Deedle.Series.observations
Full name: Tutorial.msftNames
Full name: Tutorial.msftRen
Full name: Deedle.Frame.indexColsWith
Full name: Tutorial.fbNames
Full name: Tutorial.fbRen
Full name: Tutorial.joinedOut
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : otherFrame:Frame<'TRowKey,'TColumnKey> * kind:JoinKind -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> * kind:JoinKind -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : otherFrame:Frame<'TRowKey,'TColumnKey> * kind:JoinKind * lookup:Lookup -> Frame<'TRowKey,'TColumnKey>
member Frame.Join : colKey:'TColumnKey * series:Series<'TRowKey,'V> * kind:JoinKind * lookup:Lookup -> Frame<'TRowKey,'TColumnKey>
| Outer = 0
| Inner = 1
| Left = 2
| Right = 3
Full name: Deedle.JoinKind
Full name: Tutorial.joinedIn
Full name: Tutorial.janDates
Full name: Tutorial.jan234
static member count : frame:Frame<'R,'C> -> Series<'C,int> (requires equality and equality)
static member count : series:Series<'K,'V> -> int (requires equality)
static member expandingCount : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingKurt : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingMax : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingMean : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingMin : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingSkew : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingStdDev : series:Series<'K,float> -> Series<'K,float> (requires equality)
static member expandingSum : series:Series<'K,float> -> Series<'K,float> (requires equality)
...
Full name: Deedle.Stats
static member Stats.mean : series:Series<'K,float> -> float (requires equality)
Full name: Tutorial.jan
Full name: Tutorial.daysSeries
Full name: Tutorial.obsSeries
Full name: Microsoft.FSharp.Core.Operators.nan
member Series.Get : key:'K * lookup:Lookup -> 'V
| Exact = 1
| ExactOrGreater = 3
| ExactOrSmaller = 5
| Greater = 2
| Smaller = 4
Full name: Deedle.Lookup
Full name: Tutorial.daysFrame
Full name: Tutorial.obsFrame
Full name: Tutorial.obsDaysExact
Full name: Tutorial.obsDaysPrev
Full name: Deedle.Frame.joinAlign
Full name: Tutorial.obsDaysNext
delegate of 'T * 'T -> int
Full name: System.Comparison<_>
Full name: Deedle.Frame.mapRowValues
member Frame.GetColumn : column:'TColumnKey * lookup:Lookup -> Series<'TRowKey,'R>
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
Full name: Deedle.Series.filterValues
Full name: Deedle.Series.countValues
Full name: Tutorial.joinedOpens
Full name: Tutorial.monthly
Full name: Deedle.Frame.groupRowsUsing
Full name: Deedle.Series.mapValues
Full name: Microsoft.FSharp.Core.Operators.fst