こんにちは。 CTO 室の yuina です。
引き続き某CTOからの無茶振りを捌いております。
直近Pythonでの開発が多く、久々にGolangを触ったところ、便利なライブラリを見つけたので、ご紹介します。
なぜGolangを触ることになったかというと、今回の開発の要件上、スクリプトを提供する必要がありました。
Pythonですと、各環境にて環境構築が必要となるため、Golangでバイナリを配布する形が望ましいと判断しました。
スクリプトの内容としては、csvファイルを読み込み、データの集計・変換を行い、別のcsvファイルとして出力するというスクリプトです。
集計・変換に関して、Golangの標準ライブラリでも十分実装可能ですが、コード量が多くなり可読性が下がることが懸念されました。
Pythonであれば、pandasライブラリを使うことで、可読性が高い実装が可能です。
そのため、Golangでも同様のライブラリがないか調査したところ、QFrameというライブラリを見つけました。
QFrameとは
QFrameは、Golangでデータフレーム操作を可能にするライブラリです。 pandasのように、csvファイルの読み込み、データの集計・変換、csvファイルへの出力が可能です。
サンプルコード
今回は試しに、csvファイルを読み込み、特定の列でグルーピングし、集計した結果を別のcsvファイルとして出力するサンプルコードを作成しました。
標準ライブラリである"encoding/csv"を利用した場合とのコード量の差を比較してみます。
サンプルデータ
処理内容: Category列でグルーピングし、Value列を合計する
input.csv
Category,Value
A,10
B,20
A,15
B,5
C,12
標準ライブラリを利用した場合
package main import ( "encoding/csv" "fmt" "log" "os" "strconv" ) func main() { // 入力ファイルを開く inFile, err := os.Open("input.csv") if err != nil { log.Fatalf("failed to open input.csv: %v", err) } defer inFile.Close() // CSVリーダー作成 reader := csv.NewReader(inFile) // 全データ読み込み records, err := reader.ReadAll() if err != nil { log.Fatalf("failed to read CSV: %v", err) } if len(records) < 2 { log.Fatal("CSV has no data rows") } // ヘッダー行を確認 header := records[0] var categoryIdx, valueIdx int = -1, -1 for i, col := range header { if col == "Category" { categoryIdx = i } if col == "Value" { valueIdx = i } } if categoryIdx == -1 || valueIdx == -1 { log.Fatal("CSV must contain 'Category' and 'Value' columns") } // 集計用map groupSums := make(map[string]float64) // データ行を処理 for _, row := range records[1:] { category := row[categoryIdx] val, err := strconv.ParseFloat(row[valueIdx], 64) if err != nil { log.Printf("invalid value %q, skip row\n", row[valueIdx]) continue } groupSums[category] += val } // 出力ファイル作成 outFile, err := os.Create("output.csv") if err != nil { log.Fatalf("failed to create output.csv: %v", err) } defer outFile.Close() writer := csv.NewWriter(outFile) // 出力ヘッダー if err := writer.Write([]string{"Category", "TotalValue"}); err != nil { log.Fatalf("failed to write header: %v", err) } // 集計結果を書き出し for category, sum := range groupSums { row := []string{category, fmt.Sprintf("%.0f", sum)} if err := writer.Write(row); err != nil { log.Fatalf("failed to write row: %v", err) } } writer.Flush() if err := writer.Error(); err != nil { log.Fatalf("failed to flush CSV: %v", err) } log.Println("Aggregation successful: output.csv generated") }
QFrameを利用した場合
package main import ( "log" "os" "github.com/tobgu/qframe" "github.com/tobgu/qframe/config/groupby" ) func main() { // 入力ファイルを開く f, err := os.Open("input.csv") if err != nil { log.Fatalf("failed to open input.csv: %v", err) } defer f.Close() // CSV を QFrame に読み込む qf := qframe.ReadCSV(f) if qf.Err != nil { log.Fatalf("error reading CSV: %v", qf.Err) } // グループ化:Category 列でグループ化し、Value を合計 grouped := qf.GroupBy( groupby.Columns("Category"), ).Aggregate( qframe.Aggregation{ Column: "Value", Fn: func(vals []float64) float64 { sum := 0.0 for _, v := range vals { sum += v } return sum }, }, ) if grouped.Err != nil { log.Fatalf("error during group+aggregate: %v", grouped.Err) } // 結果を CSV に出力 out, err := os.Create("output.csv") if err != nil { log.Fatalf("failed to create output.csv: %v", err) } defer out.Close() if err := grouped.ToCSV(out); err != nil { log.Fatalf("error writing CSV: %v", err) } log.Println("Aggregation successful: output.csv generated") }
コード量の比較
- 標準ライブラリを利用した場合: 約80行
- ファイルのオープン、エラーハンドリング、CSVの読み書き、データのパース、集計ロジックなど、多くのコードが必要
- QFrameを利用した場合: 約40行
- CSVの読み込み、グループ化と集計、CSVへの書き出しが簡潔に記述可能
コード量が半分程度になり、可読性も向上しています。
もっと複雑な集計や変換を行う場合、QFrameの利便性がさらに際立つと思います。
まとめ
Golangでデータフレーム操作を行う場合、QFrameライブラリを利用することで、コード量を削減し、可読性を向上させることができます。
また、Pythonとは異なり、goroutineを活用した並列処理も容易に組み込むことができるため、大規模データの処理に対しても有効ではないかと考えています。
データの集計や変換を多く行うスクリプトをGolangで実装する際には、QFrameの利用を検討してみてください。