33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ハンズラボAdvent Calendar 2018

Day 1

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

Last updated at Posted at 2018-11-30

ことの発端

  • バッチ処理とかで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 です!

33
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?