こんにちは。
現役エンジニアの”はやぶさ”@Cpp_Learningです。仕事でもプライベートでも機械学習で色々やってます。また、最近Go言語入門しました。
今回は、データ分析などに使えそうな dataframe-go と Gonum を試してみます。
Contents
Go言語の環境構築
Go言語の環境構築については、以下の記事をご参照下さい。
Go言語で扱えるデータフレーム -Gotaとdataframe-go比較-
Go言語から扱える主なデータフレームは4つあります。どれを使うか悩むときは Gota を使うのが良いと感じました。
- Githubのスター数が多い
- ドキュメント充実
- 分かりやすい解説記事がある
- 実際に触ってみたけど、直感的に使える
ただ、今回は dataframe-go を使います。
- CSV, JSONL, MySQL, PostgreSQLのインポート/エクスポートをサポート
- カスタムシリーズが作れる柔軟性
- gonum との相互運用ができる
- 実際に触ってみたけど、直感的に使える
pythonでデータ分析などをするとき、pandas や numpy を行ったり来たりすることが多いと思います。
同じ感覚でGo言語で dataframe-go と Gonum を相互運用できることに魅力を感じました。
まだ安定版ではないようですが、応援してます。
dataframe-goの基本的な使い方
dataframe-goの基本的な使い方を紹介します。
dataframe-goとGonumをインストール
まずは以下のコマンドで dataframe-go をインストールします。
go get github.com/rocketlaunchr/dataframe-go
あとから gonum との連携もしたいので、以下のコマンドでインストールします。
go get -u gonum.org/v1/gonum/…
追記 -トラブルシューティング-
もしコンパイル時に以下のようなエラーが出たら…
cannot find package “github.com/rocketlaunchr/mysql-go”
以下のコマンドでインストールすれば解消できます。
go get github.com/rocketlaunchr/mysql-go
データフレームを作る
以下のコードで簡単にデータフレームの作成と表示ができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import ( "fmt" "github.com/rocketlaunchr/dataframe-go" ) func main() { // データフレーム作成 s1 := dataframe.NewSeriesInt64("data", nil, 1, 2, 3, 4, 5) s2 := dataframe.NewSeriesFloat64("weight", nil, 5.3, 2.4, nil, 7.2, 0.9) df := dataframe.NewDataFrame(s1, s2) // 表示 fmt.Print(df.Table()) } |
Gonumで統計量(平均と標準偏差)を算出
データフレームを変換すれば、Gonumで統計量の算出ができます。
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 |
package main import ( "context" "fmt" "log" "github.com/rocketlaunchr/dataframe-go" "gonum.org/v1/gonum/stat" ) func main() { // データフレーム作成 df := dataframe.NewSeriesFloat64("data", nil, 1, 2, 3, 4, 5, 6, 7, 8, 9) fmt.Println("df:") fmt.Print(df.Table()) // 空のContextを作成 ctx := context.Background() // SeriesFloat64型に変換 sf, err := df.ToSeriesFloat64(ctx, false) if err != nil { log.Fatal(err) } fmt.Println(sf) // gonumで平均値を算出 mean := stat.Mean(sf.Values, nil) fmt.Println("Mean:", mean) // gonumで標準偏差を算出 std := stat.StdDev(sf.Values, nil) fmt.Println("Std:", std) } |
今回は統計量(平均と標準偏差)の算出にGonumを使いましたが、もっと色んな統計量を算出したい場合は stats 使うのがオススメです。
Gonumでベクトル演算
Gonumならベクトル演算も簡単です。
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 51 52 53 54 55 56 57 58 59 60 |
package main import ( "context" "fmt" "log" "github.com/rocketlaunchr/dataframe-go" "gonum.org/v1/gonum/mat" ) // print matrix func matPrint(X mat.Matrix) { fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze()) fmt.Printf("%v\n", fa) } func main() { // データフレーム作成 df := dataframe.NewSeriesFloat64("data", nil, 1, 2, 3, 4, 5, 6, 7, 8, 9) fmt.Println("df:") fmt.Print(df.Table()) // 空のContextを作成 ctx := context.Background() // SeriesFloat64型に変換 sf, err := df.ToSeriesFloat64(ctx, false) if err != nil { log.Fatal(err) } fmt.Println(sf) // ベクトル v := mat.NewVecDense(9, sf.Values) fmt.Println("v:") matPrint(v) // ベクトルの各要素を参照 a0 := v.AtVec(0) a1 := v.AtVec(1) fmt.Println("a[0]:", a0) fmt.Println("a[1]:", a1) // 以下のコードでもベクトルの各要素を参照できる a2 := v.At(2, 0) fmt.Println("a[2]:", a2) // a[1] = 2 を a[1] = 3.2 に上書き v.SetVec(1, 3.2) fmt.Println("v:") matPrint(v) // ベクトルw=v+v w := mat.NewVecDense(len(sf.Values), nil) // [0,0,..,0] w.AddVec(v, v) fmt.Println("w:") matPrint(w) } |
要素の参照だけなら pandas-go のみでも良いけど、Gonumとの連携によりベクトル演算が手軽にできるのが良いですね。
Gonumで行列演算
Gonumが威力を発揮するのは直感的な行列演算ですよね。
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 51 52 53 54 55 56 |
package main import ( "context" "fmt" "log" "github.com/rocketlaunchr/dataframe-go" "gonum.org/v1/gonum/mat" ) // print matrix func matPrint(X mat.Matrix) { fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze()) fmt.Printf("%v\n", fa) } func main() { // データフレーム作成 df := dataframe.NewSeriesFloat64("data", nil, 1, 2, 3, 4, 5, 6, 7, 8, 9) fmt.Println("df:") fmt.Print(df.Table()) // 空のContextを作成 ctx := context.Background() // SeriesFloat64型に変換 sf, err := df.ToSeriesFloat64(ctx, false) if err != nil { log.Fatal(err) } // fmt.Println(sf) // 行列A A := mat.NewDense(3, 3, sf.Values) fmt.Println("A:") matPrint(A) // A(0,1)=2 を A(0,1)=3.2 に上書き A.Set(0, 1, 3.2) fmt.Println("A:") matPrint(A) // 行列B=A+A B := mat.NewDense(3, 3, nil) B.Add(A, A) fmt.Println("B:") matPrint(B) // 行列C=2*A C := mat.NewDense(3, 3, nil) C.Scale(2, A) fmt.Println("C:") matPrint(C) } |
データフレーム(pandas-go)から行列演算(Gonum)までの流れがスマートで素敵ですね。
もっとGonumの練習をしたい人は、以下の記事が参考になりますよ。
dataflame-goの応用的な使い方
以降からは dataframe-go の応用的な使い方と注意点を説明します。
データフレームのソート
以下のコードで簡単にデータフレームのソートができます。
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 |
package main import ( "context" "fmt" "github.com/rocketlaunchr/dataframe-go" ) func main() { // データフレーム作成 s1 := dataframe.NewSeriesInt64("data", nil, 1, 2, 3, 4, 5) s2 := dataframe.NewSeriesFloat64("weight", nil, 5.3, 2.4, nil, 7.2, 0.9) df := dataframe.NewDataFrame(s1, s2) fmt.Println("df:") fmt.Print(df.Table()) // 空のContextを作成 ctx := context.Background() // ソート sks := []dataframe.SortKey{ {Key: "data", Desc: true}, {Key: "weight", Desc: true}, } df.Sort(ctx, sks) fmt.Println("Sort df:") fmt.Print(df.Table()) } |
ただし、ソートが不安定なので、NaNがある場合などは失敗します。
csvとjsonのデータフレーム化
以下のコードでcsvやjsonフォーマットをデータフレーム化できます。
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 |
package main import ( "context" "fmt" "log" "strings" "github.com/rocketlaunchr/dataframe-go/imports" ) const csvStr = ` country,day,age,id "United States",2019-02-01,33,1 "Japan",2019-08-01,29,2 "United Kingdom",2020-05-07,30,3 "Spain",2018-02-01,42,4 ` const jsonStr = ` {"id": 1, "name": "Hayabusa", "comment": "I like coffee", "age": 23} {"id": 2, "name": "Kururu", "comment": "Raspberry Pi is good", "age": 3} {"id": 3, "name": "John", "comment": "My mobile phone is iPhone", "age": 19} {"id": 4, "name": "Bob", "comment": "I push the commit to GitHub", "age": 31} ` func main() { // 空のContextを作成 ctx := context.Background() // csvをインポート csv_df, err := imports.LoadFromCSV(ctx, strings.NewReader(csvStr)) if err != nil { log.Fatal(err) } fmt.Println("csv_df:") fmt.Print(csv_df.Table()) // jsonをインポート json_df, err := imports.LoadFromJSON(ctx, strings.NewReader(jsonStr)) if err != nil { log.Fatal(err) } fmt.Println("json_df:") fmt.Print(json_df.Table()) } |
ただし、型がStringになるので、この状態ではGonumとの連携はできません。
オプション機能
CSVLoadOptions を使えば、型の自動検出などのオプション機能が使えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { // 空のContextを作成 ctx := context.Background() opts := imports.CSVLoadOptions{ InferDataTypes: true, } // csvをインポート csv_df, err := imports.LoadFromCSV(ctx, strings.NewReader(string(csvStr)), opts) if err != nil { log.Fatal(err) } fmt.Println("csv_df:") fmt.Print(csv_df.Table()) } |
ただし、jsonだとオプション機能が使えません。
Selectがない
Selectがないので、任意の列を抽出できません。なので”age”のみを抽出したい場合は、それ以外の列を削除する必要があります。
1 2 3 |
csv_df.RemoveSeries("country") csv_df.RemoveSeries("day") csv_df.RemoveSeries("id") |
Selectほしいなー
dataflame-goとGonum連携でデータ分析したかった…
最後に iris.csv を読み込んでデータ分析を実践したかったけど、型変換が上手くできずGonumと連携できませんでした。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package main import ( "bytes" "context" "fmt" "io/ioutil" "log" "github.com/rocketlaunchr/dataframe-go" "github.com/rocketlaunchr/dataframe-go/imports" "gonum.org/v1/gonum/mat" ) // print matrix func matPrint(X mat.Matrix) { fa := mat.Formatted(X, mat.Prefix(""), mat.Squeeze()) fmt.Printf("%v\n", fa) } // 不要なデータを削除 func delete_data(header []string, df dataframe.DataFrame) dataframe.DataFrame { output_df := df for _, target := range header { err := output_df.RemoveSeries(target) if err != nil { log.Fatal(err) } } return output_df } func main() { // ファイル読込み f, err := ioutil.ReadFile("./iris.csv") if err != nil { log.Fatal(err) } // 空のContextを作成 ctx := context.Background() opts := imports.CSVLoadOptions{ InferDataTypes: true, } // csv読込み df, err := imports.LoadFromCSV(ctx, bytes.NewReader(f), opts) if err != nil { log.Fatal(err) } fmt.Println("df:") fmt.Print(df.Table()) // fmt.Println("type(df)", reflect.TypeOf(df)) // 型確認 // ヘッダー情報(削除対象) header := []string{"SepalLength", "PetalLength", "PetalWidth", "Name"} // 不要な列を削除 SepalWidth_df := delete_data(header, *df) fmt.Println("SepalWidth_df:") fmt.Print(SepalWidth_df.Table()) /* ====== gonum連携したかった ====== */ // SeriesFloat64型に変換 // sf, err := SepalWidth_df.ToSeriesFloat64(ctx, false) // if err != nil { // log.Fatal(err) // } // fmt.Println(sf) } |
まとめ
pythonの pandas と numpy と同じ感覚でGo言語の dataframe-go と Gonum を相互運用できたら素敵だと感じたので、色々と試してみました。
Go言語を使ったデータ分析もしたかったけど…
まだdataframe-goが不安定なのと私のGo言語スキルが低いのもあり、Gonumとの連携が手軽にできませんでした。
dataframe-goの安定版リリースに期待しつつ、Go言語スキルを磨きたいと思います。
以下 Go言語の本紹介