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ずつ位の成長なんですね。以上です。

B! LINE