julia で、json 形式のファイルを関数で DataFrame に変換する覚え書き
julia で、json 形式のファイルを DataFrame (Array) に変換する覚え書きです。データには、Koto Furumiya さんの pokemon_data を使わせて頂きます。julia は 1.31です。
もちろん、for ループを使って、せっせと読むでいけば、json 形式のファイルを DataFrame (Array) に変換できます。しかし、 なぜ、今さら json 形式のファイルを DataFrame (Array) に変換について記載するかというと、関数化した際の初回実行速度の問題を感じているからです。 getindex() を使いますが、利点は、
- 関数化 (function ... end) した際の初回実行時間の短縮
- for ループの回避 (global 変数を触るのを抑える目的も兼ねる)
- Array の型宣言の簡略化
以上でしょう。for ループで1個の json を処理するだけなら、以下は不要かもしれません。関数化を検討していて1回目の回りが遅すぎると感じている方には役に立つと考えます。 自分の事例では、初回のみ10秒以上を費やすことが多かったためです。勝手な想像ですが、最適解を探すのに時間を使っているような印象です。 関数化2週目以降は、for ループでの制御でも、getindex() を使う下記の方法でも、自分の事例では大きくは違わない印象でした。
さて、以上が前書きです。まず、データを採ってきます。
using HTTP, JSON, DataFrames
pokeData_j = HTTP.request("GET", "https://raw.githubusercontent.com/kotofurumiya/pokemon_data/master/data/pokemon_data.json")
次に json を読み込みます。
pokeData = JSON.parse(String(pokeData_j.body))
これで、 pokeData に、
julia> summary(pokeData)
"918-element Array{Any,1}"
julia> summary(pokeData[1])
"Dict{String,Any} with 9 entries"
と、 9つのkey の Dict 形式で、918匹のポケモンのデータが手に入りました。
key が無いと、Dict 形式から、データが手に入らないので、key を調べてみます。
julia> cols = collect(reduce(union, keys.(pokeData))) 9-element Array{String,1}: "types" "stats" "form" "name" "evolutions" "abilities" "isMegaEvolution" "hiddenAbilities" "no"
上記では、`union` で全部の key を取得しましたが、`intersect` を使うと sparse なデータの場合は、全部のポケモンでデータ記載のあるものだけを選べます。今回のデータは、すべて入力されていますので、どちらも結果は同じになります。
この cols を key にして、一気に DataFrame まで行けるのですが、 cols の並べ代えをした方が見やすいでしょう。
julia> cols2 = map(x -> cols[x], [9,4,1,2,3,5,6,7,8]) 9-element Array{String,1}: "no" "name" "types" "stats" "form" "evolutions" "abilities" "isMegaEvolution" "hiddenAbilities"
では、この cols2 の順で、一気に DataFrame を組み上げます。
julia> df = DataFrame(collect(Symbol(c)=>getindex.(pokeData, c) for c in cols2)); julia> first(df,5) 5×9 DataFrame. Omitted printing of 6 columns │ Row │ no │ name │ types │ │ │ Int64 │ String │ Array{Any,1} │ ├─────┼───────┼────────────────┼──────────────────┤ │ 1 │ 1 │ フシギダネ │ ["くさ", "どく"] │ │ 2 │ 2 │ フシギソウ │ ["くさ", "どく"] │ │ 3 │ 3 │ フシギバナ │ ["くさ", "どく"] │ │ 4 │ 3 │ メガフシギバナ │ ["くさ", "どく"] │ │ 5 │ 4 │ ヒトカゲ │ ["ほのお"] │
と、一気に組み上がります。
少し順を追って見てみましょう。getindex.()を使うことで、列方向のデータを Array で手に入れる事ができます。"."が getindex と () の間にあるのを見落さないようにしましょう。ブロードキャストというやつです。
julia> pokeData[1]["name"] "フシギダネ" julia> getindex(pokeData[1], "name") "フシギダネ" julia> getindex.(pokeData, "name") 918-element Array{String,1}: "フシギダネ" "フシギソウ" "フシギバナ" "メガフシギバナ" "ヒトカゲ" "リザード" "リザードン" ⋮ "ツンデツンデ" "ズガドーン" "ゼラオラ"
さすがに、` DataFrame(collect(Symbol(c)=>getin ... ` の文法は覚え難いです。 もう少し、人間に優しく書く場合は、Array を一旦、通過させると良いでしょう。
julia> A1 = collect(getindex.(pokeData, c) for c in cols2);
julia> df2 = DataFrame(A1, Symbol.(cols2));
これなら、可読性と記憶の敷居は少し易しくなります。
この組み方では、Dict や Array がそのまま格納されています。
julia> df2.stats[1] Dict{String,Any} with 6 entries: "defence" => 49 "hp" => 45 "spAttack" => 65 "attack" => 49 "speed" => 45 "spDefence" => 65
export して、表計算ソフトで使いたい人には向きません。この場合は、getindex.() で一旦、 Dict を経由してから、もう1回、getindex.() で Array に組み直して vcat() などで積んでからDataFrameに持って行けば良いと考えます。
julia> statsData = getindex.(pokeData, "stats"); # "stats" の部分のデータだけ取り出します
julia> statsArray = collect(getindex.(statsData, keys_i) for keys_i in collect(reduce(union, keys.(statsData))));
# または
julia> statsArray2 = map(keys_i -> getindex.(statsData, keys_i) , collect(reduce(union, keys.(statsData))));
julia> statsArray2 == statsArray
true
julia> cols3 = map(x -> cols[x], [9,4,1,3,5,6,7,8]) 8-element Array{String,1}: "no" "name" "types" "form" "evolutions" "abilities" "isMegaEvolution" "hiddenAbilities" julia> cols4 = vcat(cols3, collect(reduce(union, keys.(statsData))) ) 14-element Array{String,1}: "no" "name" "types" "form" "evolutions" "abilities" "isMegaEvolution" "hiddenAbilities" "defence" "hp" "spAttack" "attack" "speed" "spDefence" julia> df3 = DataFrame(vcat(A3, statsArray), Symbol.(cols4)); julia> df3[1:3, [:name, :attack, :spAttack]] 3×3 DataFrame │ Row │ name │ attack │ spAttack │ │ │ String │ Int64 │ Int64 │ ├─────┼────────────┼────────┼──────────┤ │ 1 │ フシギダネ │ 49 │ 65 │ │ 2 │ フシギソウ │ 62 │ 80 │ │ 3 │ フシギバナ │ 82 │ 100 │
こんな感じです。20ずつ位の成長なんですね。以上です。