Pythonで表をHTMLに変換する
Pythonで表をHTML形式に簡単に変換するための手法です。特定条件を強調表示するという、スタイリング周りも込みでPandasで可能です。インデックスの非表示や抽出周りがややこしかったのでまとめました。
目次
はじめに
Pythonで表をHTMLに変換することが必要になったのでメモ。
どんな状況で必要になったかというと、AWS Lambdaからメールを送信するケースで、DynamoDBから引っこ抜いてきたデータを加工して、メールを送信したいのだが、表をHTMLメールで配信したいとき。これ以外にもいろいろユースケースはありそう。
方針としては、Pandasを使えばサクッとできますが、CSV出力と比べて情報が少ないのでまとめておきます。
to_htmlで出力する
最も簡単な例は、CSV出力でto_csv関数を使うのと同様に、to_htmlでHTMLを出力する方法です。DataFrameをHTMLにコンバートする関数はPandasに組み込まれています。具体例:
import pandas as pd
sell_df = pd.DataFrame({
"商品": ["RTX 4090", "RTX 4080", "RTX 4070", "RTX 4060"],
"数量": [5, 4, 20, 30],
"最終出荷日": ["2023/7/19", "2023/6/30", "2023/7/14", "2023/7/21"]
})
sell_df["最終出荷日"] = pd.to_datetime(sell_df["最終出荷日"])
sell_df.to_html("pandas_table01.html", index=False)
HTMLを読みかませると以下の通り。インデックスも込みで欲しい場合は、「index=False」を消す。
※アップロードしたHTMLには文字化け対策にmetaタグを別途付与しています。
列を中央揃えする
デフォルトだと左寄せされているので、中央寄せするにはjustify=”center”をつけます。
sell_df.to_html("pandas_table02.html", index=False, justify="center")
Style直下のto_htmlを使う
PandasのDataFrameには、スタイリング用にStyleという属性が用意されています。これが使われるのはJupyter向けが大半ですが、Jupyterもなかで動いているのはWebアプリなので、これもHTMLの吐き出しに使えます。
sell_df.style.to_html("pandas_table03.html")
DataFrame直下のto_htmlと、Style直下のto_htmlは別物で、異なる挙動をします。
Styleでインデックスを非表示にする
何も考えずにstyle.to_htmlとすると、インデックスが表示されてしまいます。DataFrame直下のto_htmlと異なり、index=Falseを指定しても非表示にはなりません。Style直下のto_htmlの場合、インデックスを非表示にする方法が結構トリッキーで、公式ドキュメントにある通りhideを使います。
https://pandas.pydata.org/docs/reference/api/pandas.io.formats.style.Styler.hide.html
「None, None, False」はデフォルトのパラメーターで、それを実行すると「行方向のインデックスが消える」とあるので、それを実行します。
sell_df.style.hide().to_html("pandas_table04.html")
このとおりインデックスが消えました。この方法探すの結構苦労しました。
強調表示する
特定条件による強調表示も簡単にできます。数量が10以上のセルを黄色で塗りつぶしてみます。
sell_df.style.hide().highlight_between(subset="数量", left=10, color="yellow").to_html("pandas_table05.html")
凝った強調表示をする
ただ、このケースは条件を満たしたときのみ強調で、行全体を強調してくれません。こういった強調表示をしたいときはapplyを使います。
以下は数量が10以上の行を強調表示するケースです。applyにデフォルトでくっついているaxisで動作の行列方向を制御できます。propsに割り当てるCSSのvalueを入れればいいです。元ネタはStackoverflowだったんですが、どの記事かは忘れました。
def highlight_table(s, low, column, props):
is_select = pd.Series(data=False, index=s.index)
is_select[column] = s.loc[column] >= low
return [props if is_select.any() else "" for v in is_select]
style_df = sell_df.style.hide().apply(highlight_table, axis=1, low=10, column="数量", props="background: #cdf4f0;")
style_df.to_html("pandas_table06.html")
複合条件で強調表示をする
このhighlight_table関数は複数の列を条件とすることができます。別途、最終出荷日からの現在までの経過日数を計算し、「数量が10以上または経過日数が10以上」の行も強調表示できます。
import datetime
df_x = sell_df.copy()
df_x["経過日数"] = (datetime.datetime.now() - df_x["最終出荷日"]).dt.days
style_df = df_x.style.hide().apply(highlight_table, axis=1, low=10, column=["数量", "経過日数"], props="background: #cdf4f0;")
style_df.to_html("pandas_table07.html")
gmailでは注意
この話にはオチがありまして、「メール通知するときに強調表示してくれたらわかりやすいだろうな」と思ってCSSで強調表示したのですが、gmailではstyleタグを読み込んでくれませんでした。インラインCSSなら受け付けてくれるのですが、その変換が正直面倒くさかったので、強調表示対象に「✓」を入れるカラムを別途作りました。
StyleによるHTML吐き出しいらねえ~っていうオチになりました。
参考:GmailでHTMLメールのCSSが効かない問題の対処法
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー