Python DatasourceでJSONをいい感じにパースして可視化していこう

この記事は Redash Advent Calendar 2017 の10日目の記事です。

Redashでは「Python Datasource」というものが使え、Redash上でPythonスクリプトを書くことができます。
これを使えば、Redash上でほぼなんでもできるようになります。

今回やるのは、「JSONのパース」です。

BigQueryでスキーマレスなデータを扱う

Redashの用途として、BigQueryのデータを解析・グラフ化というものがあると思います。

BigQueryのデータ内にJSONを入れ、そのJSONを解析していい感じにグラフ化したいという要望がきたとして
スキーマレスなJSONが入っていた場合、BigQueryでの解析は難しいです。

例えば以下のようなJSONがあるとします。

{"id": 1, "name": "Taro", "record": "[{\"Mathematics\": 80},{\"English\": 90}]"}
{"id": 2, "name": "Hanako", "record": "[{\"Mathematics\": 65},{\"English\": 70}]"}
{"id": 3, "name": "Pochi", "record": "[{\"Mathematics\": 100},{\"English\": 60}]"}
{"id": 4, "name": "Ken", "record": "[{\"Mathematics\": 40},{\"English\": 80}]"}

上記JSONをBigQueryにインポートしてRedashで普通に実行すると以下のようになります。

select
  id
  ,name
  ,record
from
  samplejson
order by
  id

ここから「各生徒の数学と英語の合計点を算出せよ」みたいな要件がある場合、こんな感じのSQLを書かないといけません。

select
  id
  ,name
  ,record
  ,SAFE_CAST(JSON_EXTRACT_SCALAR(record, '$.0.Mathematics') AS INT64) + SAFE_CAST(JSON_EXTRACT_SCALAR(record, '$.1.English') AS INT64) as result
from
  samplejson
order by
  id

かなり危ういSQLになってます。

今回のサンプルのJSONはわざとややこしい感じにしましたが、
こういう構造のJSONや、また配列があったりネストされているJSONもあったりします。
こういう場合、BigQueryが用意している JSON_EXTRACT_SCALAR などでは正直つらいです。
なのでこういうのはPythonでやってしまいたいと思います。

Python Datasourceを使う

Python Datasourceは「Datasource」の管理画面から設定を行えます。

では先ほどのJSONをパースし、表に出す処理をPythonで書いてみます。

import json

# Redashで保存したクエリーの結果をこれで参照できる
# query id = 8は上記のテーブルをそのまま出したもの
q_res = get_query_result(8)

result = {}
for row in q_res["rows"]:
    j = json.loads(row["record"])
    math = 0
    eng  = 0
    
    for record in j:
        if "Mathematics" in record:
            math = record["Mathematics"]
        if "English" in record:
            eng  = record["English"]

    add_result_row(result, {
        "id": row["id"],
        "name": row["name"],
        "result": math + eng
    })
add_result_column(result, 'id', '', 'integer')
add_result_column(result, 'name', '', 'string')
add_result_column(result, 'result', '', 'integer')

これを実行すると以下のようになります。

いい感じになりました。
Pythonでできることはなんでも出来るので、ネストされてようが配列だろうが好きに扱えます。

生のJSONを表にする

Pythonが書けるんだから、もうなんでも表にできます。
今回はAWSのEC2の料金表をRedashで表示してみます。
(本来は料金表をDBに入れてそれを操作したほうがよいです)

AWSの料金表はJSONで取れるので、それをいい感じにパースします。

# EC2の料金表
import json, urllib2

url = "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/ap-northeast-1/index.json"

r = urllib2.urlopen(url)
root = json.loads(r.read())
r.close()

result = {}
p_key_list = []
for p_key in root["products"]:
    if root["products"][p_key]["productFamily"] != "Compute Instance":
        continue
    
    if root["products"][p_key]["attributes"]["tenancy"] != "Shared":
        continue
    
    if root["products"][p_key]["attributes"]["operatingSystem"] != "Linux":
        continue
    p_key_list.append(p_key)

for p_key in p_key_list:
    pricePerUnit = root["terms"]["OnDemand"][p_key][p_key + ".JRTCKXETXF"]["priceDimensions"][p_key + ".JRTCKXETXF.6YS6EN2CT7"]["pricePerUnit"]["USD"]
    pricePerMonth = float(pricePerUnit) * 24.0 * 31.0
    add_result_row(result, {
        "sku": p_key,
        "instanceType": root["products"][p_key]["attributes"]["instanceType"],
        "vcpu" : root["products"][p_key]["attributes"]["vcpu"],
        "memory" : root["products"][p_key]["attributes"]["memory"],
        "storage" : root["products"][p_key]["attributes"]["storage"],
        "price/hour":pricePerUnit,
        "price/month": pricePerMonth
    })

add_result_column(result, 'sku', '', 'string')
add_result_column(result, 'instanceType', '', 'string')
add_result_column(result, 'vcpu', '', 'string')
add_result_column(result, 'memory', '', 'string')
add_result_column(result, 'storage', '', 'string')
add_result_column(result, 'price/hour', '', 'float')
add_result_column(result, 'price/month', '', 'float')

はい、いい感じに表にできました。

最後に

このようにBigQueryでスキーマレスなJSONをパースしてみたり、ただのJSONをパースできたりします。
今回はJSONを焦点に当てましたが、どんなフォーマットでもPythonが解釈できればRedashで表にすることができます。

こんな感じで身の回りのものをRedashで可視化しやすくなってきたので、ぜひ利用してきましょう!

では!