F# Data: CSV 型プロバイダー
この記事ではCSV 型プロバイダーを使って 静的に型付けされた方法でCSVファイルを扱う方法を紹介します。 この型プロバイダーは Try F# のWebサイトにある "Financial Computing" のチュートリアルとよく似ています。 したがってそちらを参考にするともう少し多くの例が見つけられるでしょう。
CSV 型プロバイダーは入力としてサンプルとなるCSVを受け取り、 このサンプル内の列データを元にした型を生成します。 列の名前は1行目(ヘッダ行)が元になり、各列の型は2行目以降にあるデータから 推測されます。
プロバイダーの基本
この型プロバイダーは FSharp.Data.Dll
アセンブリに含まれています。
このアセンブリが ../../../../bin
にあるとすると、
F# Interactiveでアセンブリを読み込むには以下のようにします:
1: 2: |
#r "../../../../bin/FSharp.Data.dll" open FSharp.Data |
株価をパースする
Yahoo FinanceのWebサイトでは以下のような構造のCSV形式で
日単位の株価が公開されています
(より大きな例としては data/MSFT.csv
ファイルを
参照してください):
Date,Open,High,Low,Close,Volume,Adj Close
2012-01-27,29.45,29.53,29.17,29.23,44187700,29.23
2012-01-26,29.61,29.70,29.40,29.50,49102800,29.50
2012-01-25,29.07,29.65,29.07,29.56,59231700,29.56
2012-01-24,29.47,29.57,29.18,29.34,51703300,29.34
一般的なCSVファイルと同じく、1行目にはヘッダ(各列の名前)があり、
2行目以降にデータがあります。
このファイルを CsvProvider
に指定すると、
静的に型付けされた方法でファイルの内容を参照できるようになります:
1:
|
type Stocks = CsvProvider<"../../data/MSFT.csv"> |
生成された型にはデータをロードするための2つのstaticメソッドがあります。
データが文字列の場合には Parse
メソッドを使用します。
データがファイルやWeb上のリソースの場合には Load
メソッドを使用します
(非同期バージョンの AsyncLoad
メソッドもあります)。
型プロバイダーに指定するサンプル用の引数には
ローカルのパスだけではなく、Web上のURLを指定することもできます。
以下の例ではYahoo FinanceのWebサイトで実際に公開されているCSVファイルの
URLを使って Load
メソッドを呼び出しています:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
// 株価データをダウンロード let msft = Stocks.Load("http://ichart.finance.yahoo.com/table.csv?s=MSFT") // 最新の行をチェックする。なお 'Date' プロパティは // 'DateTime' 型で、 'Open' プロパティは 'decimal' 型であることに注意 let firstRow = msft.Rows |> Seq.head let lastDate = firstRow.Date let lastOpen = firstRow.Open // 株価を四本値形式で表示 for row in msft.Rows do printfn "HLOC: (%A, %A, %A, %A)" row.High row.Low row.Open row.Close |
訳注:四本値とは高値、安値、始値、終値の4種の値段のこと
生成された型にはCSVファイルのデータを行コレクションとして返す
Rows
プロパティがあります。
ここでは for
ループを使って各行を走査しています。
見て分かるように、行のための(生成された)型には High
や Low
、
Close
など、CSVファイルの各列に対応するプロパティがあります。
また、型プロバイダーが各列の型を推測していることも確認できます。
Date
プロパティは(サンプルファイル中のデータが日付としてパースできるため)
DateTime
型として推測されていますが、一方で四本値はそれぞれ
decimal
として推測されています。
株価のチャート表示
FSharp.Charting ライブラリを使うと 上場からのMSFTの株価変化を単純な折れ線チャートとして描画できます:
1: 2: 3: 4: |
// FSharp.Chartingの読み込み #load "../../../../packages/FSharp.Charting.0.90.6/FSharp.Charting.fsx" open System open FSharp.Charting |
1: 2: 3: |
// 株価をビジュアル化 [ for row in msft.Rows -> row.Date, row.Open ] |> Chart.FastLine |
もう1つ例として、先月のデータの詳細を確認できるように
ローソク
(Candlestick)チャートにしてみます:
1: 2: 3: 4: 5: |
// 先月の株価を四本値形式で取得 let recent = [ for row in msft.Rows do if row.Date > DateTime.Now.AddDays(-30.0) then yield row.Date, row.High, row.Low, row.Open, row.Close ] |
1: 2: |
// ローソクチャートを使って株価をビジュアル化 Chart.Candlestick(recent).WithYAxis(Min = 35.0, Max = 45.0) |
測定単位を使用する
もう1つ興味深い機能として、CSV 型プロバイダーは測定単位をサポートしています。 標準SI単位の名前あるいは記号がヘッダに含まれている場合、 生成された型では特定の単位が付加された値が返されるようになります。
このセクションでは以下のような単純なデータが含まれた
data/SmallTest.csv
を使います:
Name, Distance (metre), Time (s)
First, 50.0, 3.7
見ての通り、2列目と3列目にはそれぞれ metre
と s
という単位があります。
コード側で測定単位を使う場合、
標準単位名を含んだ名前空間をオープンする必要があります。
その後、型プロバイダーのstatic引数に SmallTest.csv
を指定します。
なお今回は同じデータを実行時にも使用するため、
同じ引数を再度指定して Load
を呼び出すのではなく、
GetSample
メソッドを使っていることに注意してください。
1:
|
let small = CsvProvider<"../../data/SmallTest.csv">.GetSample() |
先ほどの例と同じく、行データは値 small
の Rows
プロパティで取得できます。
今回は生成されたプロパティ Distance
と Time
に単位が付加されています。
以下の単純な計算をみてください:
1: 2: 3: 4: 5: 6: |
open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames for row in small.Rows do let speed = row.Distance / row.Time if speed > 15.0M<metre/second> then printfn "%s (%A m/s)" row.Name speed |
Distance
と Time
の数値的な値はいずれも(かなり小さな値なので)
decimal
として推測されています。
したがって speed
の型は decimal<meter/second>
になります。
そうするとコンパイラは互換性のない値を比較していないかどうか、
つまりたとえばメートル毎秒とキロメートル毎時を比較していたりはしないか
チェックするようになります。
独自の区切り文字とタブ区切りファイル
CSV 型プロバイダーはデフォルトではカンマ( ,
)を区切り文字とします。
しかし場合によっては ,
ではない区切り文字が
CSVファイルで使われていることがあります。
ヨーロッパの一部の国では ,
が10進数の区切り文字として使われているため、
CSVの列区切り文字には代わりにセミコロン( ;
)が使われます。
CsvProvider
は省略可能なstatic引数 Separator
に
代わりとなる区切り文字を指定できます。
つまりこれを使えばタブ区切り形式のテキストも処理できるわけです。
以下では区切り文字として ;
を使っています:
1: 2: 3: 4: 5: |
let airQuality = CsvProvider<"../../data/AirQuality.csv", ";">.GetSample() for row in airQuality.Rows do if row.Month > 6 then printfn "Temp: %i Ozone: %f " row.Temp row.Ozone |
空気質のデータセット(data/AirQuality.csv
)は
統計解析向け言語 R の多くのサンプルで使われているものです。
このデータセットの簡単な説明については
R の言語マニュアル
を参照してください。
\t
を区切り文字とするようなタブ区切りファイルを処理する場合には
やはり区切り文字を明示的に指定してもよいでしょう。
ですが、URLまたはファイルの末尾にある拡張子が .tsv
になっていると
型プロバイダーはデフォルトで \t
を区切り文字とするようになります。
以下の例ではstatic引数 IgnoreErrors
を true
にして、
要素数が異なる行を自動的に無視するようにもしています
(サンプルファイル data/MortalityNY.csv
には
末尾に構造化されていないデータが追加されています):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
let mortalityNy = CsvProvider<"../../data/MortalityNY.tsv", IgnoreErrors=true>.GetSample() // 原因名をコードで検索 // (事故で負傷した自転車走者) let cause = mortalityNy.Rows |> Seq.find (fun r -> r.``Cause of death Code`` = "V13.4") // 負傷した走者数を出力 printfn "原因: %s" cause.``Cause of death`` for r in mortalityNy.Rows do if r.``Cause of death Code`` = "V13.4" then printfn "%s (%d 件)" r.County r.Count |
最後に、 CsvProvider
には複数種類の区切り文字を指定することもできます。
これはたとえばファイルが不正で、セミコロンかコロンのどちらかで
行が区切られているような場合に有効です。
具体的には
CsvProvider<"../../data/AirQuality.csv", Separator=";,">
というようにします。
値無し
統計的データセットでは一部の値が無しになっているということがよくあります。
data/AirQuality.csv
ファイルを開くと、
一部のオゾンの観測値が #N/A
と記録されていることが確認できます。
このような値はfloatとしてパースされ、F#であれば Double.NaN
という値とみなされます。
デフォルトでは #N/A
NA
:
が値無しを表す値と認識されますが、
CsvProvider
のstatic引数 MissingValues
を指定して
カスタマイズすることもできます。
以下のコードでは Double.NaN
になっている値を除いて、
オゾンの観測値の平均を計算しています。
まず各行の Ozone
プロパティを取得して値無しを除去した後、
標準の Seq.average
関数を使って平均を計算しています:
1: 2: 3: 4: 5: |
let mean = airQuality.Rows |> Seq.map (fun row -> row.Ozone) |> Seq.filter (fun elem -> not (Double.IsNaN elem)) |> Seq.average |
サンプルとして指定するデータにはどの列にも値無しのデータが含まれていないものの、
実行時にはどこかで値無しが現れる可能性があるという場合には
AssumeMissingValues
を true
に設定すれば、
CsvProvider
がどこかの列には値無しが現れるだろうと想定するようになります。
列の型を制御する
デフォルトではCSV 型プロバイダーは最初の1000行を使って型を推測します。
しかし CsvProvider
のstatic引数 InferRows
を使うと
この動作をカスタマイズできます。
0を指定するとファイル全体が使われるようになります。
0
1
Yes
No
True
False
しか含まない列は bool
になります。
数値を含む列はそれぞれ精度に応じて int
int64
decimal
float
の
いずれかになります。
いずれかの行で値無しになっている場合、CSV 型プロバイダーは
その行を(int
および int64
に対しては)null許容型、
あるいは(bool
DateTime
Guid
に対しては)オプション型とみなします。
decimal
と推測できる列に値無しが含まれる場合、代わりに float
とみなされ、
値無しが Double.NaN
として表現されます。
string
型はそれ自体が既にnullを許容するため、
デフォルトでは string option
と推測されることはありません。
すべての場合においてオプション型になるようにしたい場合には、
static引数 PreferOptionals
を true
にします。
この設定を行うと、空の文字列や Double.NaN
ではなく、
代わりに None
が返されるようになります。
他にもたとえば decimal
ではなく float
として行を扱いたいというような、
別の設定を使いたい場合には、ヘッダ行で列の型を丸括弧で囲んで記述することで
デフォルトの動作を上書きできます。
これは測定単位を指定する方法と同じです。
ヘッダ行による指定方法は AssumeMissingValues
や PreferOptionals
の動作を上書きします。
指定可能な型は以下の通りです:
int
int?
int option
int64
int64?
int64 option
bool
bool?
bool option
float
float?
float option
decimal
decimal?
decimal option
date
date?
date option
guid
guid?
guid option
string
string option
.
型と測定単位の両方を( float<metre>
のようにして)指定することもできます。
たとえば以下の通りです:
Name, Distance (decimal?<metre>), Time (float)
First, 50, 3
さらに、 CsvProvider
のstatic引数 Schema
で一部またはすべての型を
指定することもできます。
指定可能な形式は以下の通りです:
型
型<測定単位>
名前 (型)
名前 (型<測定単位>)
Schema
引数で指定された値はヘッダ行で指定されたものよりも常に優先されます。
ファイルの1行目がヘッダ行ではない場合、static引数 HasHeaders
を false
に
すると1行目もデータ行とみなされるようになります。
この場合、それぞれの行は Schema
引数で指定されていなければ
Column1
Column2
という名前になります。
なお Schema
引数で名前だけを上書きしつつ、
型プロバイダーに列の型を推測させることもできます。
たとえば以下のようにします:
1: 2: 3: |
let csv = CsvProvider<"1,2,3", HasHeaders = false, Schema = "Duration (float<second>),foo,float option">.GetSample() for row in csv.Rows do printfn "%f %d %f" (row.Duration/1.0<second>) row.foo (defaultArg row.Column3 1.0) |
必ずしもすべての列を上書きしなければいけないわけではなく、
一部をデフォルトのままにしておくこともできます。
たとえばKaggleから取得したタイタニックの乗船者データ
(data/Titanic.csv
)
を対象にしている時に、3列目( PClass
列)を Passenger Class
、
6列目( Fare
列)を decimal
ではなく float
にしたい場合、
これらだけを定義しておいて、その他の行が空になっているようなスキーマを
指定します(末尾で連続するカンマは省略できます)。
1: 2: 3: |
let titanic1 = CsvProvider<"../../data/Titanic.csv", Schema=",,Passenger Class,,,float">.GetSample() for row in titanic1.Rows do printfn "%s Class = %d Fare = %g" row.Name row.``Passenger Class`` row.Fare |
あるいは位置で指定する代わりに列の名前を使って上書きすることもできます:
1: 2: 3: |
let titanic2 = CsvProvider<"../../data/Titanic.csv", Schema="Fare=float,PClass->Passenger Class">.GetSample() for row in titanic2.Rows do printfn "%s Class = %d Fare = %g" row.Name row.``Passenger Class`` row.Fare |
これら2つのシンタックスを組み合わせて
Schema="int64,DidSurvive,PClass->Passenger Class=string"
とすることもできます。
CSVファイルの変形
CsvProvider
はファイルの読み取りだけでなく、データの変形もサポートしています。
Filter
Take
TakeWhile
Skip
SkipWhile
Truncate
という操作が可能です。
これらはいずれもスキーマを維持するようになっているため、
変形後は Save
メソッドのいずれかのオーバーロードを使って結果を保存できます。
結果をCSV形式で保存したくない場合、あるいはデータの形式を変更したい場合には、
Rows
プロパティで取得できる行のシーケンスに対して直接
Seq
モジュールの機能を使うこともできます。
1: 2: 3: 4: 5: |
// 値無しのデータを含まない先頭10行を新しいCSVファイルに保存する airQuality.Filter(fun row -> not (Double.IsNaN row.Ozone) && not (Double.IsNaN row.``Solar.R``)) .Truncate(10) .SaveToString() |
ビッグデータの処理
デフォルトでは行がキャッシュされるため、 Rows
プロパティを複数回走査しても
特に問題はありません。
しかし1回しか走査しないのであれば、 CsvProvider
のstatic引数
CacheRows
を false
にすればキャッシュを無効化できます。
行数が非常に多い場合、キャッシュを無効化しなければ
メモリを消費し尽くしてしまうことになるでしょう。
Cache
メソッドを使えば任意のタイミングでデータをキャッシュできますが、
データセットを小さなサイズに変形した後に限定すべきです:
1: 2: |
let stocks = CsvProvider<"http://ichart.finance.yahoo.com/table.csv?s=MSFT", CacheRows=false>.GetSample() stocks.Take(10).Cache() |
関連する記事
- F# Data: CSV パーサーおよびリーダー - CSVドキュメントを動的に処理するための詳しい説明があります。
- API リファレンス: CsvProvider 型プロバイダー
Full name: CsvProvider.Stocks
Full name: FSharp.Data.CsvProvider
<summary>Typed representation of a CSV file.</summary>
<param name='Sample'>Location of a CSV sample file or a string containing a sample CSV document.</param>
<param name='Separators'>Column delimiter(s). Defaults to `,`.</param>
<param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param>
<param name='InferRows'>Number of rows to use for inference. Defaults to `1000`. If this is zero, all rows are used.</param>
<param name='Schema'>Optional column types, in a comma separated list. Valid types are `int`, `int64`, `bool`, `float`, `decimal`, `date`, `guid`, `string`, `int?`, `int64?`, `bool?`, `float?`, `decimal?`, `date?`, `guid?`, `int option`, `int64 option`, `bool option`, `float option`, `decimal option`, `date option`, `guid option` and `string option`.
You can also specify a unit and the name of the column like this: `Name (type<unit>)`, or you can override only the name. If you don't want to specify all the columns, you can reference the columns by name like this: `ColumnName=type`.</param>
<param name='HasHeaders'>Whether the sample contains the names of the columns as its first line.</param>
<param name='IgnoreErrors'>Whether to ignore rows that have the wrong number of columns or which can't be parsed using the inferred or specified schema. Otherwise an exception is thrown when these rows are encountered.</param>
<param name='AssumeMissingValues'>When set to true, the type provider will assume all columns can have missing values, even if in the provided sample all values are present. Defaults to false.</param>
<param name='PreferOptionals'>When set to true, inference will prefer to use the option type instead of nullable types, `double.NaN` or `""` for missing values. Defaults to false.</param>
<param name='Quote'>The quotation mark (for surrounding values containing the delimiter). Defaults to `"`.</param>
<param name='MissingValues'>The set of strings recogized as missing values. Defaults to `NaN,NA,#N/A,:,-`.</param>
<param name='CacheRows'>Whether the rows should be caches so they can be iterated multiple times. Defaults to true. Disable for large datasets.</param>
<param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param>
Full name: CsvProvider.msft
Loads CSV from the specified uri
CsvProvider<...>.Load(reader: System.IO.TextReader) : CsvProvider<...>
Loads CSV from the specified reader
CsvProvider<...>.Load(stream: System.IO.Stream) : CsvProvider<...>
Loads CSV from the specified stream
Full name: CsvProvider.firstRow
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.head
Full name: CsvProvider.lastDate
Full name: CsvProvider.lastOpen
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
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.FastLine : data:seq<#key * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.GenericChart
Full name: CsvProvider.recent
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)
static member Chart.Candlestick : data:seq<#key * #value * #value * #value * #value> * ?Name:string * ?Title:string * ?Labels:#seq<string> * ?Color:Drawing.Color * ?XTitle:string * ?YTitle:string -> ChartTypes.CandlestickChart
Full name: CsvProvider.small
type metre
Full name: Microsoft.FSharp.Data.UnitSystems.SI.UnitNames.metre
type second
Full name: Microsoft.FSharp.Data.UnitSystems.SI.UnitNames.second
Full name: CsvProvider.airQuality
Full name: CsvProvider.mortalityNy
Full name: CsvProvider.cause
Full name: Microsoft.FSharp.Collections.Seq.find
Full name: CsvProvider.mean
Full name: Microsoft.FSharp.Collections.Seq.map
Full name: Microsoft.FSharp.Collections.Seq.filter
Full name: Microsoft.FSharp.Core.Operators.not
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 3 overloads
static val MinValue : float
static val MaxValue : float
static val Epsilon : float
static val NegativeInfinity : float
static val PositiveInfinity : float
...
end
Full name: System.Double
Full name: Microsoft.FSharp.Collections.Seq.average
Full name: CsvProvider.csv
Full name: Microsoft.FSharp.Core.Operators.defaultArg
Full name: CsvProvider.titanic1
Full name: CsvProvider.titanic2
Full name: CsvProvider.stocks