Edited at

Goでもpandasっぽいことできる!


ことの発端


  • バッチ処理とかでDataFrameでcsvファイルを処理したい


  • DataFrameと言ったらpandas

  • でもPythonは遅いので他の言語で扱えるものはないものか?



そこでこいつ!

GoのDataFrame


  • 使って人いたらどのように使っているか教えて欲しい

  • とりあえず今回は使えそうなものを紹介します



まずはgo get

go get github.com/kniren/gota/dataframe



DataFrameを作る

DataFrameの作り方は6つあります

使用できる型はInt, Float, String, Boolです



DataFrameを作成する関数

関数名
説明
型を指定する

New
列から作成する
できる

LoadRecords
行から作成する
できる

LoadStruct
Strauctから作成
できる

LoadMaps
Mapから作成
できない

ReadCSV
CSVから作成
できない

ReadJson
Jsonから作成
できない

型指定しなくても、ある程度自動で判定してくれます

ReadJsonはあんまり使えないので紹介しません(詳しくはGitHubのexampleを)



New(列から作成)

df := dataframe.New(

series.New([]string{"shimizu", "saito", "kondo"}, series.String, "Name"),
series.New([]int{23, 23, 22}, series.Int, "Age"),
series.New([]float64{23.4, 30.4, 22.7}, series.Float, "Power"),
)

若干面倒そうだがまぁまぁ



LoadRecords(行から作成)

users := [][]string{

[]string{"Name", "Age", "Power"},
[]string{"shimizu", "23", "23.4"},
[]string{"saito", "23", "30.4"},
[]string{"kondo", "22", "22.7"},
}

df := dataframe.LoadRecords(users,
dataframe.WithTypes(map[string]series.Type{
"Name": series.String,
"Age": series.Int,
"Power": series.Float,
}))
df := dataframe.LoadRecords(users)

dataframe.WithTypesがなくてもDataFrameを作成してくれます(型は指定できませんが)



LoadStruct(Structから作成)

type User struct {

Name string
Age int
Power float64
}

users := []User{
User{"shimizu", 23, 23.4},
User{"saito", 23, 30.4},
User{"kondo", 22, 22.7},
}
df := dataframe.LoadStructs(users)

個人的におすすめ、おそらくファイルから読み取る方法以外ではこの方法が一番スマートな方法だと思います



LoadMaps(Mapから作成)

users := []map[string]interface{}{

map[string]interface{}{
"Name": "shimizu",
"Age": 23,
"Power": 23.4,
},
map[string]interface{}{
"Name": "saito",
"Age": 23,
"Power": 30.4,
},
map[string]interface{}{
"Name": "kondo",
"Age": 22,
"Power": 22.7,
},
}
df := dataframe.LoadMaps(users)

これはちょっとないかな



ReadCSV(CSVファイルから作成)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

TSVやスペース区切りもオプションを使用すれば読み込めます



出力はこんな感じ

fmt.Println(df)

[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

Goには標準でPrint関数がありますが、fmtパッケージのPrint関数でしか出力できないので注意



データ操作



Select(列を選択)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

select := df.Select([]string{"Name", "Power"})
// select := df.Select([]int{0, 2}) indexでも指定できる
fmt.Println(select)

[3x2] DataFrame

Name Power
0: shimizu 23.400000
1: saito 30.400000
2: kondo 22.700000
<string> <float>



SubSet(指定した行を取得)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

row := df.Subset([]int{0}) // もちろん複数指定可能
fmt.Println(row)

[1x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
<string> <int> <float>



Mutate(列を追加)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

mut := df.Mutate(
series.New([]string{"blue", "red", "green"}, series.String, "Color"),
)

fmt.Println(mut)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x4] DataFrame

Name Age Power Color
0: shimizu 23 23.400000 blue
1: saito 23 30.400000 red
2: kondo 22 22.700000 green
<string> <int> <float> <string>

列を追加する際、すでに存在するカラム名を指定すればカラムの更新になります



RBind(行を結合)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)
fmt.Println(df)

df2 := dataframe.LoadRecords(
[][]string{
[]string{"Name", "Age", "Power"},
[]string{"kojima", "22", "20.1"},
},
)
df3 := df.RBind(df2)
fmt.Println(df3)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[4x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
3: kojima 22 20.100000
<string> <int> <float>

DataFrameの行を結合します。ただしカラム名とカラム数が一致していることが前提です



CBind(列を結合)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)
fmt.Println(df)

df2 := dataframe.LoadRecords(
[][]string{
[]string{"From", "Language"},
[]string{"nagano", "Python"},
[]string{"kanagawa", "Java"},
[]string{"kanagawa", "Kotlin"},
},
)

df3 := df.CBind(df2)
fmt.Println(df3)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x5] DataFrame

Name Age Power From Language
0: shimizu 23 23.400000 nagano Python
1: saito 23 30.400000 kanagawa Java
2: kondo 22 22.700000 kanagawa Kotlin
<string> <int> <float> <string> <string>

DataFrameの列を結合します。ただしレコード数が一致していることが前提です



Set(データの更新)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

seted := df.Set(
series.Ints([]int{0, 2}), // indexが0と2を更新
dataframe.LoadRecords( // DetaFrame型でカラムが一致しているならなんでもOK
[][]string{
[]string{"Name", "Age", "Power"},
[]string{"hara", "25", "25.6"},
[]string{"daisaku", "23", "24.0"},
},
),
)

fmt.Println(seted)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x3] DataFrame

Name Age Power
0: hara 25 25.600000
1: saito 23 30.400000
2: daisaku 23 24.000000
<string> <int> <float>



Arrange(ソート)

複数キー指定できる

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

sorted := df.Arrange(
dataframe.RevSort("Power"), // Powerの降順
dataframe.Sort("Age"), // Ageの昇順
)
fmt.Println(sorted)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x3] DataFrame

Name Age Power
0: saito 23 30.400000
1: shimizu 23 23.400000
2: kondo 22 22.700000
<string> <int> <float>



Filter(条件に一致した行を取得)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)

filter := df.Filter(
dataframe.F{
Colname: "Age",
Comparator: series.LessEq, // "<="でもok
Comparando: 22,
},
)
// filter := df.Filter(dataframe.F{"Age", "<=", 22}) // こんな感じもok
fmt.Println(filter)

[1x3] DataFrame

Name Age Power
0: kondo 22 22.700000
<string> <int> <float>



dataframe.F


  • Colname: カラム名

  • Comparator: 比較

  • Comparando: 比較値

    省略も可能(私はvscodeで書いて、省略をすると警告が出ます)

比較
比較演算子

series.Eq
==

series.Neq
!=

series.Greater
>

series.GreaterEq
>=

series.Less
<

series.LessEq
<=



注意点

Filter内にdataframe.Fは何個でも条件として指定できるがandではなくorなので注意

filter := df.Filter(

dataframe.F{"Age", ">", 22},
dataframe.F{"Power", ">", 22.0},
)
fmt.Println(filter)

[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>



andしたい

andは現状このようにやるしかない

filter := df.Filter(dataframe.F{"Age", ">", 22}).

Filter(dataframe.F{"Power", ">=", 22.0})

fmt.Println(filter)

[2x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
<string> <int> <float>

面倒臭い!(時間があればプルリクを投げたい)。



InnerJoin(内部結合)

content, _ := ioutil.ReadFile("user.csv")

ioContent := strings.NewReader(string(content))

df := dataframe.ReadCSV(ioContent)
fmt.Println(df)

df2 := dataframe.LoadRecords(
[][]string{
[]string{"Name", "From"},
[]string{"shimizu", "Nagano"},
[]string{"saito", "Yokohama"},
[]string{"kojima", "Aichi"},
},
)
fmt.Println(df2)

innerJoin := df.InnerJoin(df2, "Name") // NameでJoin
fmt.Println(innerJoin)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x2] DataFrame

Name From
0: shimizu Nagano
1: saito Yokohama
2: kojima Aichi
<string> <string>

[2x4] DataFrame

Name Age Power From
0: shimizu 23 23.400000 Nagano
1: saito 23 30.400000 Yokohama
<string> <int> <float> <string>

割と簡単



LeftJoin(外部結合)

とりあえずLeftJoinだけ紹介

もちろんOuterJoinRightJoinもあります

// -------------

// InnerJoinと同じ
// -------------

leftJoin := df.LeftJoin(df2, "Name")
fmt.Println(leftJoin)


[3x3] DataFrame

Name Age Power
0: shimizu 23 23.400000
1: saito 23 30.400000
2: kondo 22 22.700000
<string> <int> <float>

[3x2] DataFrame

Name From
0: shimizu Nagano
1: saito Yokohama
2: kojima Aichi
<string> <string>

[3x4] DataFrame

Name Age Power From
0: shimizu 23 23.400000 Nagano
1: saito 23 30.400000 Yokohama
2: kondo 22 22.700000 NaN // nilではないので注意
<string> <int> <float> <string>



番外 DataFrameを扱えるライブラリ

他にもあるなら教えて欲しい!

ハンズラボ Advent Calendar 2018 明日2日目は @watarukura です!