ことの発端
- バッチ処理とかで
DataFrame
でcsvファイルを処理したい -
DataFrame
と言ったらpandas
- でもPythonは遅いので他の言語で扱えるものはないものか?
そこでこいつ!
- 使って人いたらどのように使っているか教えて欲しい
- とりあえず今回は使えそうなものを紹介します
まずは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
だけ紹介
もちろんOuterJoin
とRightJoin
もあります
// -------------
// 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を扱えるライブラリ
- pandas(Python): https://pandas.pydata.org/
- daru(Ruby): https://github.com/SciRuby/daru
- krangl(Ktolin): https://github.com/holgerbrandl/krangl
- R: https://www.rdocumentation.org/packages/base/versions/3.5.1/topics/data.frame
- PHP: https://packagist.org/packages/archon/dataframe
- Spark(Python, R, Java, Scala): https://spark.apache.org/docs/latest/sql-programming-guide.html
他にもあるなら教えて欲しい!
ハンズラボ Advent Calendar 2018 明日2日目は @watarukura です!