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で可視化しやすくなってきたので、ぜひ利用してきましょう!
では!