Snowflake x Streamlit でデータを可視化しよう
こんにちは、データチームにてデータ基盤開発を行なっています、井山です。
データチームでは、データ基盤に関係する各種運用メタデータ(wfの処理時間やデータ格納時間等々)をSnowflake上に格納、Tableau上で可視化しているのですが、半年程前にStreamlitがSnowflakeによって買収され、今後機能として追加されていきそうなので、今のうちに試しておこうと思います。
Streamlit とは
データアプリケーションを手軽に作成することができるオープンソースのWebアプリケーションフレームワークです。 フロントエンドの経験がなくともPythonのみで記述ができ、手軽に試すことができます。
前準備
今回は下記のdevcontainerを利用します。
https://github.com/jroes/streamlit-getting-started-m1
基本Readmeに従って、こちらのリポジトリをCloneして立ち上げます。 VsCodeのターミナルに以下を入力すると
のような表示がされると思うので、ブラウザで開くを押下し、以下のような表示がされればOKです。
Snowflakeへの接続
ここからはSnowflakeに実際に接続して、Snowflake側のデータを参照して可視化していきたいと思います。 今回利用している環境ではSnowflake接続のためのパッケージが足りないのでインストールしておきます。
conda install snowflake-connector-python
前もってDockerfileを編集しておくでもOK
RUN conda install snowflake-connector-python
上記ができたら以下のドキュメントを参考にSnowflakeへの接続を確認します。
※Snowflake環境はトライアル環境を利用します。
ドキュメントに従って、データベースとテーブルを作成します。
CREATE DATABASE PETS; CREATE TABLE MYTABLE ( NAME varchar(80), PET varchar(80) ); INSERT INTO MYTABLE VALUES ('Mary', 'dog'), ('John', 'cat'), ('Robert', 'bird'); SELECT * FROM MYTABLE;
今回はCloudの方のStreamlitではなくローカルで試す形なので、 .streamlit
配下に secret.toml
を作成し、そこに認証情報を記載します。
# .streamlit/secrets.toml [snowflake] user = "xxx" password = "xxx" account = "xxx" warehouse = "xxx" database = "xxx" schema = "xxx"
ここまでできたら下記のコマンドを実行し、実際に接続できているかを確認します。
streamlit run streamlit_app.py
テスト用データの準備
せっかくなので何かしら可視化用のデータが欲しいので、Snowflakeのハンズオンでも利用されているCITIBIKEのデータをImportします。
※CITIBIKEについて.
ニューヨークにあるレンタル自転車バイクシェアプログラム S3に関連するデータを公開してくれています。 https://ride.citibikenyc.com/system-data
データのインポートについては特に変わったことはしないので省略します。
今回は下記のS3に入っているデータをインポートしました。(一部CSVファイルでエラーが出るので ON_ERROR = SKIP_FILE でエラーが出たファイルは飛ばしてインポート)
s3://snowflake-workshop-lab/citibike-trips
SCHEMA 情報
データプレビュー
実際に可視化してみる
いったん簡単な可視化から試してみます。 可視化の際に選べるチャートの種類は以下にある通りです、幾つか試してみたいと思います。
Table
import streamlit as st import pandas as pd import snowflake.connector @st.experimental_singleton def init_connection(): return snowflake.connector.connect( **st.secrets["snowflake"], client_session_keep_alive=True ) conn = init_connection() # Table used_count = pd.read_sql("SELECT START_STATION_NAME, COUNT(*) AS USE_COUNT FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2018-01-01' GROUP BY START_STATION_NAME ORDER BY COUNT(*) DESC LIMIT 10;", conn) st.header('利用回数が多い駅TOP10') st.table(used_count)
line_chart
import streamlit as st import pandas as pd import snowflake.connector @st.experimental_singleton def init_connection(): return snowflake.connector.connect( **st.secrets["snowflake"], client_session_keep_alive=True ) conn = init_connection() # LineChart duration_per_days = pd.read_sql("SELECT to_date(STARTTIME) AS USE_DATE, SUM(TRIPDURATION) AS SUM_TRIPDURATION FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2018-01-01' GROUP BY to_date(STARTTIME) ORDER BY USE_DATE;", conn) duration_per_days = duration_per_days.set_index(['USE_DATE']) st.header('日別 TRIP DURATION') st.line_chart(duration_per_days)
bar_chart
import streamlit as st import pandas as pd import snowflake.connector @st.experimental_singleton def init_connection(): return snowflake.connector.connect( **st.secrets["snowflake"], client_session_keep_alive=True ) conn = init_connection() # bar_chart count_per_start_station = pd.read_sql("SELECT to_date(STARTTIME) AS USE_DATE, START_STATION_NAME, COUNT(*) AS USE_COUNT FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2017-02-01' AND START_STATION_NAME IN (SELECT START_STATION_NAME FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2018-01-01' GROUP BY START_STATION_NAME ORDER BY COUNT(*) DESC LIMIT 3) GROUP BY to_date(STARTTIME), START_STATION_NAME;", conn) count_per_start_station = count_per_start_station.set_index(['START_STATION_NAME']) st.header("日別・スタート駅別 利用回数") st.bar_chart(count_per_start_station, x="USE_DATE", y="USE_COUNT")
基本的なチャートの描画はすぐ出来る感じですが、もう少しリッチな感じにしたい場合は altair_chart等 を利用するとよさそうです。
altair_chart
import streamlit as st import pandas as pd import altair as alt import snowflake.connector @st.experimental_singleton def init_connection(): return snowflake.connector.connect( **st.secrets["snowflake"], client_session_keep_alive=True ) conn = init_connection() count_per_start_station = pd.read_sql("SELECT to_date(STARTTIME) AS USE_DATE, START_STATION_NAME, COUNT(*) AS USE_COUNT FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2017-02-01' AND START_STATION_NAME IN (SELECT START_STATION_NAME FROM TRIPS WHERE STARTTIME BETWEEN '2017-01-01' AND '2018-01-01' GROUP BY START_STATION_NAME ORDER BY COUNT(*) DESC LIMIT 3) GROUP BY to_date(STARTTIME), START_STATION_NAME;", conn) c = alt.Chart(count_per_start_station).mark_bar().encode( x='USE_DATE', y='USE_COUNT', color='START_STATION_NAME', tooltip=['USE_DATE', 'USE_COUNT', 'START_STATION_NAME']) st.header("日別・スタート駅別 利用回数") st.altair_chart(c, use_container_width=True)
他のグラフライブラリにも対応しているので自身が行いたい可視化に合わせて使えば良い感じかなーと思います。
- Vega-Lite
- Plotly
- Bokeh
- PyDeck
- dagre-d3
snowparkでやってみる
Snowflakeには snowpark というものがあります。
Snowparkライブラリは、データパイプライン内のデータをクエリおよび処理するための直感的な API を提供します。このライブラリを使用すると、アプリケーションコードが実行されるシステムにデータを移動することなく、Snowflakeでデータを処理するアプリケーションを構築できます。Snowparkには、他のクライアントライブラリとの差別化をもたらすいくつかの機能があります。
Snowflake上で動作するアプリケーションを開発できる機能で現在は Java、Scala、 Python に対応しています。 せっかくなので Snowpark for Python を利用して試してみたいと思います。
devcontainer を少しいじる
デフォルトの devcontainer の環境だと snowflake-snowpark-python が入っていないので少し弄ります。 devconatiner の conda の python バージョンが 3.10 だったので、snowflake-snowpark-python の対応バージョンに合わせるため 3.8.13 に変更し、その後 snowflake-snowpark-python をインストールします。
conda install python=3.8.13 conda install -c main snowflake-snowpark-python
Snowpark での Snowflake への接続確認
from snowflake.snowpark import Session connection_parameters = { "account": "XXXX", "user": "XXXX", "password": "XXXX", "role": "XXXX", "warehouse": "XXX", "database": "XXXX", "schema": "XXX" } session = Session.builder.configs(connection_parameters).create() df_table = session.table("TRIPS") df_table.show() session.close()
実行結果
2022-10-21 03:07:29.329 query: [SELECT * FROM ( SELECT * FROM (TRIPS)) LIMIT 10 OFFSET 0] 2022-10-21 03:07:29.686 query execution done 2022-10-21 03:07:29.686 Number of results in first chunk: 10 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |"TRIPDURATION" |"STARTTIME" |"STOPTIME" |"START_STATION_ID" |"START_STATION_NAME" |"START_STATION_LATITUDE" |"START_STATION_LONGITUDE" |"END_STATION_ID" |"END_STATION_NAME" |"END_STATION_LATITUDE" |"END_STATION_LONGITUDE" |"BIKEID" |"MEMBERSHIP_TYPE" |"USERTYPE" |"BIRTH_YEAR" |"GENDER" | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |832 |2018-02-11 21:11:17 |2018-02-11 21:25:09 |435 |W 21 St & 6 Ave |40.74173969 |-73.99415556 |448 |W 37 St & 10 Ave |40.75660359 |-73.9979009 |32963 |Annual Membership |Subscriber |1976 |1 | |395 |2018-02-11 21:11:18 |2018-02-11 21:17:53 |433 |E 13 St & Avenue A |40.72955361 |-73.98057249 |439 |E 4 St & 2 Ave |40.7262807 |-73.98978041 |15472 |Annual Membership - Save 15% |Subscriber |1993 |2 | |354 |2018-02-11 21:11:20 |2018-02-11 21:17:14 |3140 |1 Ave & E 78 St |40.77140426 |-73.9535166 |3288 |E 88 St & 1 Ave |40.778301 |-73.9488134 |18199 |Annual Membership |Subscriber |1967 |2 | |193 |2018-02-11 21:11:30 |2018-02-11 21:14:44 |3632 |E 12 St & Avenue B |40.728048571 |-73.978811502 |266 |Avenue D & E 8 St |40.72368361 |-73.97574813 |32922 |Annual Membership |Subscriber |1991 |1 | |1389 |2018-02-11 21:11:37 |2018-02-11 21:34:47 |447 |8 Ave & W 52 St |40.76370739 |-73.9851615 |2021 |W 45 St & 8 Ave |40.75929124 |-73.98859651 |32298 |Annual Membership |Subscriber |1976 |1 | |223 |2018-02-11 21:11:44 |2018-02-11 21:15:27 |3304 |6 Ave & 9 St |40.668127 |-73.98377641 |3365 |3 St & 7 Ave |40.6703837 |-73.97839676 |28643 |Annual Membership |Subscriber |NULL |0 | |965 |2018-02-11 21:11:46 |2018-02-11 21:27:52 |405 |Washington St & Gansevoort St |40.739323 |-74.008119 |325 |E 19 St & 3 Ave |40.73624527 |-73.98473765 |31174 |Annual Membership |Subscriber |1986 |2 | |968 |2018-02-11 21:11:52 |2018-02-11 21:28:01 |405 |Washington St & Gansevoort St |40.739323 |-74.008119 |325 |E 19 St & 3 Ave |40.73624527 |-73.98473765 |32687 |Annual Membership |Subscriber |1986 |1 | |1141 |2018-02-11 21:11:52 |2018-02-11 21:30:54 |3518 |Lenox Ave & W 126 St |40.808442 |-73.9452087 |2006 |Central Park S & 6 Ave |40.76590936 |-73.97634151 |30204 |Annual Membership |Subscriber |1987 |1 | |463 |2018-02-11 21:11:53 |2018-02-11 21:19:36 |280 |E 10 St & 5 Ave |40.73331967 |-73.99510132 |3263 |Cooper Square & Astor Pl |40.729514962 |-73.990752697 |15975 |Annual Membership |Subscriber |1969 |1 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ちゃんとデータが確認できました。
DF の操作については以下のドキュメントにまとまっているので、必要なものを利用します。
Streamlit に渡して描画してみる
from snowflake.snowpark import Session from snowflake.snowpark.functions import avg, sum, col,lit import streamlit as st import pandas as pd st.header("snowpark") connection_parameters = { "account": "xxxx", "user": "xxxx", "password": "xxxx", "role": "xxxx", "warehouse": "xxxx", "database": "xxxx", "schema": "xxxx" } session = Session.builder.configs(connection_parameters).create() df = session.table("TRIPS").select(col("TRIPDURATION"), col("START_STATION_ID") ,col("STARTTIME"), col("STOPTIME")).filter(col("START_STATION_ID") == 342) pd_df = df.to_pandas() st.table(pd_df) session.close()
まとめ
Snowflake上にあるデータをサクッと可視化したい時などある場合に使い慣れたpythonで簡単に可視化が可能でとても便利な印象です。 また詰まりやすい環境構築がSnowflake上で解決できる可能性もあるので、今後どのようにSnowflakeの機能として追加されるのかが楽しみです!(Snowflake上でホスト出来たりしないかなーと妄想しています)
利用したドキュメント等
- https://github.com/jroes/streamlit-getting-started-m1
- https://docs.streamlit.io/knowledge-base/tutorials/databases/snowflake
- https://ride.citibikenyc.com/system-data
- https://docs.streamlit.io/library/api-reference/charts
- https://docs.streamlit.io/library/api-reference/charts/st.altair_chart
- https://altair-viz.github.io/gallery/
- https://docs.snowflake.com/ja/developer-guide/snowpark/index.html
- https://docs.snowflake.com/ja/developer-guide/snowpark/python/index.html
- https://docs.snowflake.com/ja/developer-guide/snowpark/python/working-with-dataframes.html#constructing-a-dataframe