Athena for Apache Spark の Notebook で DataFrame.toPandas().plot() した際の日本語が文字化けしないようにする
awspythonsparkAthena for Apache Spark は re:Invent 2022 で発表された マネージドでサーバーレスな Jupyter Notebook からインタラクティブに Spark による分析が行える機能。 Athena は アドホックに SQL を実行して分析できる手軽さと、サーバーレスによって使ってない時間帯は料金がかからない利点があったが、 重いクエリを実行すると scale factor やタイムアウトによって失敗することがあったので今回 Spark が動かせるようになり用途の幅の広がりを感じる。
通常の Athena がロード量による課金なのに対して、Spark は 4 vCPU + 16 GB メモリのDPU時間あたり $0.35 の料金がかかるので、使い勝手としては Redshift Serverless に近いかもしれない。
Redshift Serverlessと他のサーバーレス集計サービス、Glue Data Catalogのテーブルへのクエリ実行 - sambaiz-net
価格としては 通常の Glue job より安く、予備のリソースで実行する flexible execution より少し高いくらいの設定になっている。 なぜか Glue の Interactive Session より安く、DynamicFrame といった Glue 独自の機能や、対応していない MLlib などを使いたいというわけでなければこちらを使うと良いのではと考えている。
セットアップ
service role がなければ作成し、KMS と出力先の bucket を設定して workgroup を作成する。
セッションに割り当てるリソース量を設定して notebook を作成する。 何も実行しないまま Session idle timeout が過ぎるとするとセッションが終了するが、再度 Notebook にアクセスすれば再開できる。
Glue Data Catalog にアクセスするには service role に policy を追加する必要がある。 デフォルトで付いていないのが不思議だ。
{
"Effect": "Allow",
"Action": [
"glue:GetDatabase",
"glue:GetTable",
"glue:GetPartitions"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::*"
]
}
グラフの描画と日本語の文字化けの解消
PySpark には DataFrame.toPandas() という Spark の DataFrame を Pandas の DataFrame に変換するメソッドがあり、 この plot() を呼んで、 Jupyter Notebook からインタラクティブにリモートの Spark クラスタでコードを実行できるようにする Sparkmagic の %matplot を書くとサーバー上で描画したグラフを Notebook 上に表示できる。
ただ、Athena for Apache Spark 環境で日本語がラベルなどに含まれているグラフを描画すると文字化けしてしまう。 フォント一覧を確認しても日本語非対応の DejaVu フォントしかないようだ。
import matplotlib.font_manager as fm
print(fm.findSystemFonts())
'''
['/usr/share/fonts/dejavu/DejaVuSerifCondensed-BoldItalic.ttf',
'/usr/share/fonts/dejavu/DejaVuSerifCondensed-Italic.ttf',
'/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSansMono-BoldOblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSerifCondensed.ttf',
'/usr/share/fonts/dejavu/DejaVuSansCondensed-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSerif.ttf',
'/usr/share/fonts/dejavu/DejaVuSans.ttf',
'/usr/share/fonts/dejavu/DejaVuSansMono.ttf',
'/usr/share/fonts/dejavu/DejaVuSansMono-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSansCondensed-Oblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSerif-BoldItalic.ttf',
'/usr/share/fonts/dejavu/DejaVuSans-Oblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSerifCondensed-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSans-BoldOblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSerif-Italic.ttf',
'/usr/share/fonts/dejavu/DejaVuSerif-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSans-ExtraLight.ttf',
'/usr/share/fonts/dejavu/DejaVuSansMono-Oblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSansCondensed-BoldOblique.ttf',
'/usr/share/fonts/dejavu/DejaVuSansCondensed.ttf']
'''
そこで日本語に対応している Noto Sans Japanese を japanize-matplotlib よろしく matplotlib.font_manager に追加して font.family に指定すると日本語が描画されるようになった。 もちろん IPA フォント でも問題ない。
import subprocess
subprocess.run(['curl', '-o', 'Noto_Sans_JP.zip', 'https://fonts.google.com/download?family=Noto%20Sans%20JP'])
subprocess.run(['unzip', '-o', 'Noto_Sans_JP.zip'])
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import font_manager
paths = font_manager.findSystemFonts(fontpaths='.')
for path in paths:
font_manager.fontManager.addfont(path)
print(f"added {path}")
plt.rcParams['font.family'] = ['Noto Sans JP']
df = spark.sql("select inline(array(struct('ひゃく', 100), struct('二百', 200), struct('400', 400)))")
df.toPandas().plot.barh(x='col1' ,y='col2', figsize=(8,4))
%matplot plt