はじめに
StreamlitはPythonで簡単にWebアプリを作成できるパッケージです。
st.data_editorとは、Streamlitで編集可能なデータフレームを提供する機能です。
本記事は、st.data_editorを使った際に起きた不思議な現象と対処法を紹介します。
本記事でわかること
- Streamlitで
st.data_editorを使う方法
やったことと起きた現象
st.data_editorを用いてWebアプリ上へst.session_stateで保持しているデータフレームを表示しました。
具体的な方法は下記の通りです。
- データフレームを
st.session_state["df_test"]として保持できるようにした。 -
st.session_state["df_test"]をst.data_editorを用いて編集可能にした。 - 編集後のデータフレームは戻り値で取得できるため、
st.session_state["df_test"]を戻り値にした。
st.session_state["df_test"] = st.data_editor(st.session_state["df_test"])
Webアプリ上でデータフレームの値を変更したら、2回に1回値が元に戻りました。
やった作業は、check列の変更です。
1回目、0行目のチェックボックスにチェックを入れると、チェックありになります。
2回目、1行目のチェックボックスにチェックを入れると、一瞬チェックありになりますが、すぐにチェックなしに戻ります。
3回目、1行目のチェックボックスにチェックを入れると、チェックありになります。
(fruit列の文字列変更でも、同様の現象が起こります。)
※同様の問題は、StreamlitのGitHub Issueにも報告されています。[#7534],[#7749]
原因
上記のような挙動になる理由は、GitHub Issue#7749で下記のように言及されています。
(原文)
The reason why this doesn't work is related to a general design aspect of how widgets work at the moment. Changing the values of the input parameter of any Streamlit widget will create a new instance of the frontend component. This causes the double input issue since the component will lose its frontend state with every rerun(日本語訳)
これが機能しない理由は、現在のウィジェットの動作に関する一般的な設計上の側面に関連しています。Streamlitウィジェットの入力パラメータの値を変更すると、フロントエンドコンポーネントの新しいインスタンスが生成されます。これにより、コンポーネントが再実行のたびにフロントエンド状態を失うため、二重入力の問題が発生します。
対策
st.session_stateで保持するデータフレームの更新・再描画をst.data_editorの編集タイミングと明確に分けることで解決できます。
今回は、反映ボタンを押したタイミングでデータフレームを更新、st.rerunによって再描画します。
具体的な方法は下記の通りです。
- 編集後のデータフレームは、
st.session_state["df_test"]とは別のデータフレームに入れる。 - ボタンを押されたら、
st.session_state["df_test"]を変更された情報に更新、st.rerunによって再描画する。
# 編集後のデータフレームを別のデータフレームにする
df_temp = st.data_editor(st.session_state["df_test"])
if st.button("session_state反映"):
# データフレームを更新して再描画
st.session_state["df_test"] = df_temp
st.rerun()
サンプル
編集可能なデータフレームがあるサンプルアプリを作成しました。
実行環境
- ubuntu 22.04
- python 3.12
- pandas 2.3.3
- streamlit 1.51.0
コード
サンプルアプリのコードです。
反映ボタンを押したタイミングでst.session_stateのデータフレームを更新します。
import streamlit as st
import pandas as pd
# 変数の保存・呼び出し用の関数
def store_value(key):
st.session_state[key] = st.session_state["_"+key]
def load_value(key):
st.session_state["_"+key] = st.session_state[key]
def init_value(key, init_value):
if key not in st.session_state:
st.session_state[key] = init_value
# DataFrame
init_value("df_test", pd.DataFrame(columns=['check', 'fruit'], dtype="object"))
def main():
st.title("OK例")
# DF準備
if st.button("DFセット"):
data = [[False, 'Apple'], [False, 'Banana'], [False, 'Cherry']]
st.session_state["df_test"] = pd.DataFrame(data, columns=['check', 'fruit'])
# DF表示(編集可)
st.write("編集可能DF")
df_temp = st.data_editor(st.session_state["df_test"])
print(st.session_state["df_test"])
col1,col2 = st.columns(2)
with col1:
# DF表示(編集不可)
st.write("編集後DF")
st.dataframe(df_temp)
with col2:
# DF表示(編集不可)
st.write("session_stateのDF")
st.dataframe(st.session_state["df_test"])
if st.button("session_state反映"):
# DF更新して再描画
st.session_state["df_test"] = df_temp
st.rerun()
if __name__ == "__main__":
main()
サンプル画面イメージ
サンプルアプリの画面イメージです。
DFセットボタンを押下すると、st.session_state["df_test"]にデータをセットします。
①データフレームst.session_state["df_test"]を、st.data_editorを使って表示
②st.data_editorの戻り値を表示 ※st.session_state["df_test"]とは別のデータフレーム
③st.session_state["df_test"]を表示
(②③は、編集の必要がないため、st.dataframeで表示)
1. check列を押下したときの挙動
check列を押下したとき、Streamlitが再実行されて、①~③のデータフレームを再描画します。
①は、UIのみ編集を保持した状態のため、見た目はチェックありですが、実際はチェックなしのままです。
②は、編集後のデータ(チェックあり)になります。
③は、チェックなしのままです。(①の確認)
2. 反映ボタンを押下したときの挙動
反映ボタンを押下したとき、st.data_editorの戻り値をst.session_state["df_test"]に設定して、画面全体を再描画します。
①は、更新されたst.session_state["df_test"](チェックあり)になります。
②は、編集していないので前回と同じチェックありです。
③は、更新されたst.session_state["df_test"](チェックあり)になります。
st.session_state["df_test"]の更新・再描画をst.data_editorの編集タイミングと明確に分けたことで、期待通りに値を毎回変更することができるようになりました!
最後に
本記事では、st.data_editorを使った際に起きた不思議な現象と対処法を紹介しました。
読んでくれた方のお役に立てれば幸いです!
最後まで読んでいただき、ありがとうございました。
最後になりますが、本議事の内容に誤りなどあれば、コメントにてご教授お願いいたします。



