sgykfjsm.github.com

Saddleを試す

Saddleとは

http://saddle.github.io/

SaddleはハイパフォーマンスなScala向けデータ操作ライブラリで、Scala Data Libraryの各頭文字から名付けられている。

Saddleでは以下の様なデータ形式や性質をもたらす。

  • array-backed
  • Indexed
  • 1次元または2次元データ構造
  • ベクトル計算
  • 自動的データアライメント(?)
  • データ欠損への堅牢性
  • I/O周り
  • rangeとかshuffleとか、joda DataTime objectsのヘルパーといった便利機能いっぱい

SaddleはR言語やその統計環境、numpypandasPython、そしてScalaのコレクションライブラリなどから影響を受けている。
SaddleはJVM上での構造化されたデータへのプログラミングを簡便にし、より表現豊かにすることができる。

導入の仕方

公式では状況に応じていくつかの方法が提示されている。

Sadleを使ったプロジェクトを作成したい

おなじみg8を使う。

1
2
3
$ g8 saddle/saddle.g8
# follow the promts, go to your new project directory, and run
$ sbt console

既存のプロジェクト内で使いたい

1
2
3
4
5
6
7
8
resolvers ++= Seq(
  "Sonatype Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots",
  "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases"
)

libraryDependencies ++= Seq(
  "org.scala-saddle" %% "saddle" % "1.0.1"
)

なお、自分で試した時には以下のようなログが出ており、resolversにはrepo.typesafe.comも加えておいたほうが良さそう(自分の場合はすでにrepo.typesafe.comを加えていた)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[info] downloading http://repo.typesafe.com/typesafe/repo/org/scala-saddle/saddle_2.9.2/1.0.1/saddle_2.9.2-1.0.1.jar ...
[info]  [SUCCESSFUL ] org.scala-saddle#saddle_2.9.2;1.0.1!saddle_2.9.2.jar (32904ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/com/googlecode/efficient-java-matrix-library/ejml/0.19/ejml-0.19.jar ...
[info]  [SUCCESSFUL ] com.googlecode.efficient-java-matrix-library#ejml;0.19!ejml.jar (8050ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/org/apache/commons/commons-math/2.2/commons-math-2.2.jar ...
[info]  [SUCCESSFUL ] org.apache.commons#commons-math;2.2!commons-math.jar (11273ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/it/unimi/dsi/fastutil/6.5.2/fastutil-6.5.2.jar ...
[info]  [SUCCESSFUL ] it.unimi.dsi#fastutil;6.5.2!fastutil.jar (185619ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/it/unimi/dsi/dsiutils/2.0.15/dsiutils-2.0.15.jar ...
[info]  [SUCCESSFUL ] it.unimi.dsi#dsiutils;2.0.15!dsiutils.jar (11210ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/org/scala-saddle/jhdf5/2.9/jhdf5-2.9.jar ...
[info]  [SUCCESSFUL ] org.scala-saddle#jhdf5;2.9!jhdf5.jar (13801ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/com/martiansoftware/jsap/2.1/jsap-2.1.jar ...
[info]  [SUCCESSFUL ] com.martiansoftware#jsap;2.1!jsap.jar (4825ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar ...
[info]  [SUCCESSFUL ] com.google.guava#guava;14.0-rc3!guava.jar(bundle) (26383ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/log4j/log4j/1.2.17/log4j-1.2.17.jar ...
[info]  [SUCCESSFUL ] log4j#log4j;1.2.17!log4j.jar(bundle) (7654ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/commons-configuration/commons-configuration/1.8/commons-configuration-1.8.jar ...
[info]  [SUCCESSFUL ] commons-configuration#commons-configuration;1.8!commons-configuration.jar (8191ms)
[info] downloading http://repo.typesafe.com/typesafe/repo/org/apache/commons/commons-math3/3.1.1/commons-math3-3.1.1.jar ...
[info]  [SUCCESSFUL ] org.apache.commons#commons-math3;3.1.1!commons-math3.jar (21990ms)
[info] Done updating.
1
2
3
4
5
6
resolvers ++= Seq(
  "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases",
  "Typesafe repo" at "http://repo.typesafe.com/typesafe/repo",
  "Sonatype Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots",
  "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases"
)

色々試す

こんなcsvファイルを読み込む

1
2
3
4
5
6
7
8
9
10
11
12
$ curl -s http://hojin.ctot.jp/markets/CSV/01_USDJPY_D.csv | sed -e '1d' > data.csv
$ head data.csv
"01","2013/01/30",90.71,91.41,90.66,91.08
"01","2013/01/29",90.85,91.01,90.33,90.71
"01","2013/01/28",91.03,91.25,90.56,90.84
"01","2013/01/25",90.32,91.19,90.29,90.9
"01","2013/01/24",88.61,90.55,88.42,90.32
"01","2013/01/23",88.7,88.79,88.06,88.61
"01","2013/01/22",89.61,90.09,88.36,88.7
"01","2013/01/21",90.103,90.25,89.34,89.64
"01","2013/01/18",89.87,90.2,89.64,90.09
"01","2013/01/17",88.37,90.12,88.13,89.86

CsvParser.parseParはCSVデータをパラレルで読み込んでFrameデータを作成する。 引数の1つ目はパースの種類で以下の4つがある

  1. parseDouble
  2. parseFloat
  3. parseInt
  4. parseLong

引数の2つ目は抽出する列で、ゼロから初めてリストで指定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
scala> import org.saddle.io._
import org.saddle.io._

scala> val csvfile = CsvFile("/tmp/data.csv")
csvfile: org.saddle.io.CsvFile = CsvFile(/tmp/data.csv,  encoding: UTF-8)

scala> val data = CsvParser.parsePar(CsvParser.parseDouble, List(2,3,4))(csvfile)
data: org.saddle.io.ParsedData[Double] =
ParsedData([3 x 1]
90.71
91.41
90.66
,Vector([199 x 1]
90.8000
91.0000
90.3000
88.6000
88.0000
 ...
79.0000
80.0000
80.9000
81.3000
81.0000
, [199 x 1]
91.0100
91.2500
91.1900
90.5500
88.7900
 ...
80.2900
80.3800
81.4400
81.4100
81.6800
, [199 x 1]

90.3300
90.5600
90.2900
88.4200
88.0600
 ...
79.6300
79.7300
80.2100
80.6600
81.0700
))

ヘッダーなしのフレームデータにする。toFrameだと1行めがヘッダー扱いされる(ここ参照)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala> val framedata = data.toFrameNoHeader
framedata: org.saddle.Frame[Int,Int,Double] =
[199 x 3]
             0       1       2
       ------- ------- -------
  0 -> 90.8000 91.0100 90.3300
  1 -> 91.0000 91.2500 90.5600
  2 -> 90.3000 91.1900 90.2900
  3 -> 88.6000 90.5500 88.4200
  4 -> 88.0000 88.7900 88.0600
...
194 -> 79.0000 80.2900 79.6300
195 -> 80.0000 80.3800 79.7300
196 -> 80.9000 81.4400 80.2100
197 -> 81.3000 81.4100 80.6600
198 -> 81.0000 81.6800 81.0700

指定行を取り出す。

1
2
3
4
5
6
scala> framedata.row(2)
res38: org.saddle.Frame[Int,Int,Double] =
[1 x 3]
           0       1       2
     ------- ------- -------
2 -> 90.3000 91.1900 90.2900

指定範囲の行を取り出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scala> framedata.rowAt(1, 2)
res40: org.saddle.Frame[Int,Int,Double] =
[2 x 3]
           0       1       2
     ------- ------- -------
1 -> 91.0000 91.2500 90.5600
2 -> 90.3000 91.1900 90.2900


scala> framedata.rowAt(1 -> 5)
res41: org.saddle.Frame[Int,Int,Double] =
[5 x 3]
           0       1       2
     ------- ------- -------
1 -> 91.0000 91.2500 90.5600
2 -> 90.3000 91.1900 90.2900
3 -> 88.6000 90.5500 88.4200
4 -> 88.0000 88.7900 88.0600
5 -> 89.6000 90.0900 88.3600

指定列を取り出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scala> framedata.col(1)
res39: org.saddle.Frame[Int,Int,Double] =
[199 x 1]
             1
       -------
  0 -> 91.0100
  1 -> 91.2500
  2 -> 91.1900
  3 -> 90.5500
  4 -> 88.7900
...
194 -> 80.2900
195 -> 80.3800
196 -> 81.4400
197 -> 81.4100
198 -> 81.6800

指定範囲の列を取り出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
scala> framedata.colAt(1, 2)
res42: org.saddle.Frame[Int,Int,Double] =
[199 x 2]
             1       2
       ------- -------
  0 -> 91.0100 90.3300
  1 -> 91.2500 90.5600
  2 -> 91.1900 90.2900
  3 -> 90.5500 88.4200
  4 -> 88.7900 88.0600
...
194 -> 80.2900 79.6300
195 -> 80.3800 79.7300
196 -> 81.4400 80.2100
197 -> 81.4100 80.6600
198 -> 81.6800 81.0700


scala> framedata.colAt(1 -> 2)
res43: org.saddle.Frame[Int,Int,Double] =
[199 x 2]
             1       2
       ------- -------
  0 -> 91.0100 90.3300
  1 -> 91.2500 90.5600
  2 -> 91.1900 90.2900
  3 -> 90.5500 88.4200
  4 -> 88.7900 88.0600
...
194 -> 80.2900 79.6300
195 -> 80.3800 79.7300
196 -> 81.4400 80.2100
197 -> 81.4100 80.6600
198 -> 81.6800 81.0700

セルを指定して取り出す。

1
2
scala> framedata.at(1, 2)
res44: org.saddle.scalar.Scalar[Double] = 90.56

連続した指定範囲のセルを取り出す。

1
2
3
4
5
6
7
8
scala> framedata.at(2 -> 4, 1 -> 2)
res52: org.saddle.Frame[Int,Int,Double] =
[3 x 2]
           1       2
     ------- -------
2 -> 91.1900 90.2900
3 -> 90.5500 88.4200
4 -> 88.7900 88.0600

飛び石で指定した範囲のセルを取り出す。

1
2
3
4
5
6
7
8
scala> framedata.at(Array(1, 3, 7), Array(0, 2))
res55: org.saddle.Frame[Int,Int,Double] =
[3 x 2]
           0       2
     ------- -------
1 -> 91.0000 90.5600
3 -> 88.6000 88.4200
7 -> 89.8000 89.6400

指定した値以下のデータの個数を数える。maskは指定した式がTrueとなる値をNAにする。countNAではない有効なデータの個数を数える。

1
2
3
4
5
6
scala> framedata.mask(_ > 80).count
res71: org.saddle.Series[Int,Int] =
[3 x 1]
0 -> 132
1 -> 116
2 -> 138

各列の総和と平均

1
2
3
4
5
6
7
8
9
10
[sgyk@fujishima] 13-04-08 0:36:36 /tmp
$ awk -F"," 'BEGIN{sum3=0;sum4=0;sum5=0}{sum3 = sum3 + $3;sum4 = sum4 + $4;sum5 = sum5 + $5;}END{print sum3"\n"sum4"\n"sum5}' data.csv
16154.1
16214.9
16099.1
[sgyk@fujishima] 13-04-08 0:37:55 /tmp
$ awk -F"," 'BEGIN{sum3=0;sum4=0;sum5=0}{sum3 = sum3 + $3;sum4 = sum4 + $4;sum5 = sum5 + $5;}END{print sum3/NR"\n"sum4/NR"\n"sum5/NR}' data.csv
80.7703
81.0747
80.4957
1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> framedata.reduce(_.sum)
res120: org.saddle.Series[Int,Double] =
[3 x 1]
0 -> 16045.4400
1 -> 16123.5300
2 -> 16008.4800


scala> framedata.reduce(_.mean)
res121: org.saddle.Series[Int,Double] =
[3 x 1]
0 -> 80.6304
1 -> 81.0228
2 -> 80.4446

なんか微妙に結果が合わない…。色々調べた結果、

  • データ中にダブルクォーテーションが入っているとダブルクォーテーションの右隣りの列の値が正しく取れない?
  • toFrameNoHeaderは本来列見出しになるはずだった1列目を破棄する。

っぽい感じだったので、ちょっとデータからダブルクォーテーションを削除して、アテの見出しをつけるようにデータを修正した。

1
2
3
4
5
6
7
8
9
10
[sgyk@fujishima] 13-04-08 1:18:37 /tmp
$ awk -F"," 'BEGIN{NR>1;sum3=0;sum4=0;sum5=0}{sum3 = sum3 + $3;sum4 = sum4 + $4;sum5 = sum5 + $5;}END{print sum3"\n"sum4"\n"sum5}' data.csv
16154.1
16214.9
16099.1
[sgyk@fujishima] 13-04-08 1:18:50 /tmp
$ awk -F"," 'BEGIN{NR>1;sum3=0;sum4=0;sum5=0}{sum3 = sum3 + $3;sum4 = sum4 + $4;sum5 = sum5 + $5;}END{print sum3/(NR-1)"\n"sum4/(NR-1)"\n"sum5/(NR-1)}' data.csv
80.7703
81.0747
80.4957
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 読み込み部分は省略

scala> val framedata = data.toFrameNoHeader
framedata: org.saddle.Frame[Int,Int,Double] =
[200 x 3]
             0       1       2
       ------- ------- -------
  0 -> 90.7100 91.4100 90.6600
  1 -> 90.8500 91.0100 90.3300
  2 -> 91.0300 91.2500 90.5600
  3 -> 90.3200 91.1900 90.2900
  4 -> 88.6100 90.5500 88.4200
...
195 -> 79.8000 80.2900 79.6300
196 -> 80.3000 80.3800 79.7300
197 -> 80.9700 81.4400 80.2100
198 -> 81.3200 81.4100 80.6600
199 -> 81.3000 81.6800 81.0700


scala> framedata.sum
res152: org.saddle.Series[Int,Double] =
[3 x 1]
0 -> 16154.0530
1 -> 16214.9400
2 -> 16099.1400


scala> framedata.mean
res153: org.saddle.Series[Int,Double] =
[3 x 1]
0 -> 80.7703
1 -> 81.0747
2 -> 80.4957

あとはもう少しSQLっぽい操作ができるようになれば、結構便利そう(Saddleじゃなくて自分がって意味で)。