Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

ことの発端

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

thimi0412
見直し用
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした