概要
Pythonのデータ分析界隈にはPandasという有名なデータフレームライブラリが存在します。Rubyにもいくつかのデータフレームライブラリが存在し、その一つに2022年度Rubyアソシエーション開発助成で開発されたRedAmberがあります。以下は作者による紹介記事です。
私は普段RedAmberを愛用していますが、最近「カラムで整数の配列を扱う場合に文字列化されるなどの挙動をする」ことに気づきました。また調査の結果、この問題は手動でスキーマとデータ型を明示的に設定することで回避できることが分かりました。
今回はRedAmberのスキーマを明示してデータフレームを作成し、それによってRedAmberのカラムで配列を安全に扱う方法を紹介したいと思います。スキーマを設定することで、意図しない型推論の回避策としてだけでなく、堅牢なデータフレームを作成するための知見として共有できれば幸いです。
※ 筆者は開発に関わっているわけではなく、リファレンスなどを調査したまとめですので、「もっと良い方法がある」「ここが間違っている」などありましたら、ぜひコメントで教えていただけると助かります。
環境
- OS: macOS 26.1
- Ruby: 3.4.7
- RedAmber: 0.5.2
- Apache Arrow / Red Arrow: 22.0.0
カラムに配列を入れた際の通常の挙動
まず、RedAmberで通常通りにデータフレームを作成してみます。
以下のように、カラムの中に配列の配列(ネストした配列)を入れてみます。
文字列の配列の場合
require 'red_amber'
# 配列の中に配列を入れる
df = RedAmber::DataFrame.new(
id: [1, 2, 3],
values: [
["ruby", "data"],
["red_amber"],
["arrow", "apache"]
]
)
p df
実行結果:
#<RedAmber::DataFrame : 3 x 2 Vectors, ...>
id values
<uint8> <list>
0 1 ["ruby", "data"]
1 2 ["red_amber"]
2 3 ["arrow", "apache"]
型(Type)を見ると <list> になっているのがわかります。これは期待通りの挙動です。
他にも、浮動小数の配列もきちんと配列として作成されます。
整数の配列の場合
一方、整数の配列を入力した場合は、動作が不安定になります。
require 'red_amber'
df = RedAmber::DataFrame.new(
id: [1, 2, 3],
values: [
[0, 1, 2],
[3, 4],
[5, 6, 7, 8]
]
)
p df
実行結果:
#<RedAmber::DataFrame : 3 x 2 Vectors, ...>
id values
<uint8> <string>
0 1 [0, 1, 2]
1 2 [3, 4]
2 3 [5, 6, 7, 8]
これでは、ただの文字列として保存されているため、配列としての操作ができません。またデータを取り出した際に、再び整数として扱うにも不便です。
データフレームの作成時にスキーマを設定する
RedAmberは、内部構造として RedArrow (Apache Arrow の Ruby バインディング) を使用しています。RedAmberのレイヤーで配列を渡すと自動的に型推論が行われる結果、文字列化されてしまうようです。
そこで推論に任せるのではなく、しっかりとスキーマを与えることによって、期待通りの動作をさせることができます。
実践
実際にやってみます。
require 'red_amber'
schema_hash = {
id: :uint32,
values: [:list, :uint32]
}
# schema を設定し、配列で入力する場合は行単位
rows = [
[1, [0, 1, 2]],
[2, [3, 4]],
[3, [5, 6, 7, 8]]
]
df = RedAmber::DataFrame.new(schema_hash, rows)
p df
今回は :uint32 を使用していますが、ここは利用しているデータによって適宜調整が必要です。
実行結果:
#<RedAmber::DataFrame : 3 x 2 Vectors, ...>
id values
<uint32> <list>
0 1 [0, 1, 2]
1 2 [3, 4]
2 3 [5, 6, 7, 8]
型が <list> になりました!
これで文字列ではなく、リスト(配列)として認識されています。
列指向で入力する
上の例は行指向での入力でしたが、列指向でも入力できます。ただ少し面倒です。
require 'red_amber'
schema_hash = {
id: :uint32,
values: [:list, :uint32]
}
schema = Arrow::Schema.new(schema_hash)
# データ型を指定して列を手作りする
## id
ids = Arrow::UInt32Array.new([1, 2, 3])
## values
list_data = [
[0, 1, 2],
[3, 4],
[5, 6, 7, 8]
]
values = Arrow::ListArray.new(schema[:values].data_type, list_data)
## 列
cols = [
ids,
values
]
df = RedAmber::DataFrame.new(schema, cols)
p df
実行結果:
#<RedAmber::DataFrame : 3 x 2 Vectors, ...>
id values
<uint32> <list>
0 1 [0, 1, 2]
1 2 [3, 4]
2 3 [5, 6, 7, 8]
フィルタリング等の動作確認
配列として認識されているので、RedAmberの機能を使ったフィルタリングなども行えるか確認します。
例えば、「:values の要素数が 2 の行」を抽出してみます。
# valuesカラムの各要素(配列)の長さを取得してフィルタリング
filtered_df = df.filter(df[:values].map { |v| v.length == 2 })
p filtered_df
実行結果:
#<RedAmber::DataFrame : 1 x 2 Vectors, ...>
id values
<uint32> <list>
0 2 [3, 4]
期待通り、配列として処理ができていることがわかります。
まとめ
- RedAmber で
DataFrame.newに直接ネストした配列を渡すと、意図しない型として解釈される場合がある - 回避策として、スキーマを定義してから
RedAmber::DataFrame.newに渡す - これにより
<list>型として正しく認識され、配列としての操作が可能になる
一大LLM時代ですが、何かしら記事を書かないとAIも学習しないので、RedAmberをはじめRubyのデータフレームに関する知見を積み重ねていきたいですね。