Streamlitでドリルダウンしよう！

# はじめに
皆さんはStreamlitでドリルダウンの可視化ができるのはご存じでしょうか？
今回は以下のGitリポジトリを参考に中身をかみ砕いて説明していきたいと思います。

https://github.com/sqlinsights/streamlit-altair-drilldowns/blob/main/streamlit_app.py

# プラグラム説明
今回は`altair`を使用してドリルダウンを作成していきます。
まずは必要なライブラリをインポートします。ついでにレイアウトをwideモードにしておきましょうか。タイトルも書いておきます。

```python
import streamlit as st
import altair as alt
import pandas as pd
import numpy as np

st.set_page_config(layout="wide")
st.title("2022 Sales Dashboard")
```

表示する大本のデータを作成します。
今回は地域、カラー、月をあらかじめ作成しておきます。

```python
regions = ["LATAM", "EMEA", "NA", "APAC"]
colors = [
    "#aa423a",
    "#f6b404",
    "#327a88",
    "#303e55",
    "#c7ab84",
    "#b1dbaa",
    "#feeea5",
    "#3e9a14",
    "#6e4e92",
    "#c98149",
    "#d1b844",
    "#8db6d8",
]
months = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
]
```
ではドリルダウンに使用する中身のデータを作成していきます。

```python
@st.cache_data
def get_data():
    dates = pd.date_range(start="1/1/2022", end="12/31/2022")
    data = pd.DataFrame()
    sellers = {
        "LATAM": ["S01", "S02", "S03"],
        "EMEA": ["S10", "S11", "S12", "S13"],
        "NA": ["S21", "S22", "S23", "S24", "S25", "S26"],
        "APAC": ["S31", "S32", "S33", "S34", "S35", "S36"],
    }
    rows = 25000
    data["transaction_date"] = np.random.choice([str(i) for i in dates], size=rows)
    data["region"] = np.random.choice(regions, size=rows, p=[0.1, 0.3, 0.4, 0.2])
    data["transaction_amount"] = np.random.uniform(100, 250000, size=rows).round(2)
    data["seller"] = data.apply(
        lambda x: np.random.choice(sellers.get(x["region"])), axis=1
    )
    return data.sort_values(by="transaction_date").reset_index(drop=True)

sales_data = get_data()
```
`@st.cache_data`はStreamlitに搭載されている外部関数で、データをキャッシュすることができます。
今回のデータはランダムで作成するので、それを保持し続けるために入れてます。
関数の内容は、1年分のデータをランダムで作成し、それぞれに100~250000の数字をランダムで割り当てています。
また、`region`のデータはそれぞれ`p`で割り当てる割合を決定しています。
今回は`LATAM`が10%、`EMEA`が30%、`NA`が40%、`APAC`が20%となっています。
あとはランダムで`region`に対する売り手をランダムに入力するという関数になっています。

--- 

それではドリルダウンを作成していきましょう。
```python
region_select = alt.selection_single(fields=["region"], empty="all")
region_pie = (
    (
        alt.Chart(sales_data)
        .mark_arc(innerRadius=50)
        .encode(
            theta=alt.Theta(
                "transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Sum of Transactions",
            ),
            color=alt.Color(
                field="region",
                type="nominal",
                scale=alt.Scale(domain=regions, range=colors),
                title="Region",
            ),
            opacity=alt.condition(region_select, alt.value(1), alt.value(0.25)),
        )
    )
    .add_selection(region_select)
    .properties(title="Region Sales")
)

region_summary = (
    (
        alt.Chart(sales_data)
        .mark_bar()
        .encode(
            x=alt.X(
                "month(transaction_date)",
                type="temporal",
            ),
            y=alt.Y(
                field="transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Total Sales",
            ),
            color=alt.Color(
                "region",
                type="nominal",
                title="Regions",
                scale=alt.Scale(domain=regions, range=colors),
                legend=alt.Legend(
                    direction="vertical",
                    symbolType="triangle-left",
                    tickCount=4,
                ),
            ),
        )
    )
    .transform_filter(region_select)
    .properties(width=700, title="Monthly Sales")
)

sellers_monthly_pie = (
    (
        alt.Chart(sales_data)
        .mark_arc(innerRadius=10)
        .encode(
            theta=alt.Theta(
                field="transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Total Transactions",
            ),
            color=alt.Color(
                "month(transaction_date)",
                type="temporal",
                title="Month",
                scale=alt.Scale(domain=months, range=colors),
                legend=alt.Legend(
                    direction="vertical",
                    symbolType="triangle-left",
                    tickCount=12,
                ),
            ),
            facet=alt.Facet(
                field="seller",
                type="nominal",
                columns=8,
                title="Sellers",
            ),
            tooltip=alt.Tooltip(["sum(transaction_amount)", "month(transaction_date)"]),
        )
    )
    .transform_filter(region_select)
    .properties(width=150, height=150, title="Sellers transactions per month")
)
```
少し長いですが…
`region_select`で選択したデータをドリルダウンするように設定しています。
データを選択していない場合は、すべてのデータを表示するようになっています。

`region_pie`で円グラフ（パイチャート）を作成しています。
encodeメソッドでデータと色の指定を行っており、`opacity`で選択されたデータの透過率は1で、それ以外のデータは0.25に設定することで、どのデータに着目してるのかが一目でわかるようになっています。

`region_summary`では、棒グラフを作成しています。

`sellers_monthly_pie`では、売り手ごとのトランザクション額を示すサブチャートを円グラフで作成していますね。

中身に関して、詳しくはわたしも解読中ですm(__)m
理解はできますが、説明が難しいですね(;'∀')

---
最後に作成したグラフを表示します。
```python
top_row = region_pie | region_summary
full_chart = top_row & sellers_monthly_pie
st.altair_chart(full_chart)
```
`top_row = region_pie | region_summary`：この行では、`region_pie`（地域ごとの円グラフ）と `region_summary`（地域別のバーグラフ）を組み合わせて、1つの行に配置します。| 演算子を使用して、これらのチャートを水平に配置します。

`full_chart = top_row & sellers_monthly_pie`：この行では、`top_row`と `sellers_monthly_pie` を組み合わせて、最終的なダッシュボードのチャートを作成します。& 演算子を使用して、これらのチャートを垂直に配置します。

`st.altair_chart(full_chart)`:この行で、`full_chart`を表示しています。

ではどのようになっているか見てみましょう。
![visualization.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524375/decc785e-952e-2000-0a3e-a4be1bd74a2d.png)

パイチャート、棒グラフ、サブチャートそれぞれできていますね。
例えば、左上のパイチャートのデータをクリックすると…
![visualization (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524375/b620e157-a222-f474-cf0b-c99a6fbd1c73.png)

ドリルダウンできていますね！
選択した`region`以外は透過率0.25なので薄くなっていますね。
さらに、棒グラフ、サブチャートでも選択した`region`のデータが反映されていることがわかります。

# スクリプト全体
```python
import streamlit as st
import altair as alt
import pandas as pd
import numpy as np

st.set_page_config(layout="wide")
regions = ["LATAM", "EMEA", "NA", "APAC"]
colors = [
    "#aa423a",
    "#f6b404",
    "#327a88",
    "#303e55",
    "#c7ab84",
    "#b1dbaa",
    "#feeea5",
    "#3e9a14",
    "#6e4e92",
    "#c98149",
    "#d1b844",
    "#8db6d8",
]
months = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
]
st.title("2022 Sales Dashboard")

@st.cache_data
def get_data():
    dates = pd.date_range(start="1/1/2022", end="12/31/2022")
    data = pd.DataFrame()
    sellers = {
        "LATAM": ["S01", "S02", "S03"],
        "EMEA": ["S10", "S11", "S12", "S13"],
        "NA": ["S21", "S22", "S23", "S24", "S25", "S26"],
        "APAC": ["S31", "S32", "S33", "S34", "S35", "S36"],
    }
    rows = 25000
    data["transaction_date"] = np.random.choice([str(i) for i in dates], size=rows)
    data["region"] = np.random.choice(regions, size=rows, p=[0.1, 0.3, 0.4, 0.2])
    data["transaction_amount"] = np.random.uniform(100, 250000, size=rows).round(2)
    data["seller"] = data.apply(
        lambda x: np.random.choice(sellers.get(x["region"])), axis=1
    )
    return data.sort_values(by="transaction_date").reset_index(drop=True)


sales_data = get_data()

region_select = alt.selection_single(fields=["region"], empty="all")
region_pie = (
    (
        alt.Chart(sales_data)
        .mark_arc(innerRadius=50)
        .encode(
            theta=alt.Theta(
                "transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Sum of Transactions",
            ),
            color=alt.Color(
                field="region",
                type="nominal",
                scale=alt.Scale(domain=regions, range=colors),
                title="Region",
            ),
            opacity=alt.condition(region_select, alt.value(1), alt.value(0.25)),
        )
    )
    .add_selection(region_select)
    .properties(title="Region Sales")
)

region_summary = (
    (
        alt.Chart(sales_data)
        .mark_bar()
        .encode(
            x=alt.X(
                "month(transaction_date)",
                type="temporal",
            ),
            y=alt.Y(
                field="transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Total Sales",
            ),
            color=alt.Color(
                "region",
                type="nominal",
                title="Regions",
                scale=alt.Scale(domain=regions, range=colors),
                legend=alt.Legend(
                    direction="vertical",
                    symbolType="triangle-left",
                    tickCount=4,
                ),
            ),
        )
    )
    .transform_filter(region_select)
    .properties(width=700, title="Monthly Sales")
)

sellers_monthly_pie = (
    (
        alt.Chart(sales_data)
        .mark_arc(innerRadius=10)
        .encode(
            theta=alt.Theta(
                field="transaction_amount",
                type="quantitative",
                aggregate="sum",
                title="Total Transactions",
            ),
            color=alt.Color(
                "month(transaction_date)",
                type="temporal",
                title="Month",
                scale=alt.Scale(domain=months, range=colors),
                legend=alt.Legend(
                    direction="vertical",
                    symbolType="triangle-left",
                    tickCount=12,
                ),
            ),
            facet=alt.Facet(
                field="seller",
                type="nominal",
                columns=8,
                title="Sellers",
            ),
            tooltip=alt.Tooltip(["sum(transaction_amount)", "month(transaction_date)"]),
        )
    )
    .transform_filter(region_select)
    .properties(width=150, height=150, title="Sellers transactions per month")
)

top_row = region_pie | region_summary
full_chart = top_row & sellers_monthly_pie
st.altair_chart(full_chart)
```
# まとめ
StreamlitでBIのようなことができました！
ただ、プログラムの内容を理解することと、コードが長くなりすぎることは欠点ですね(;'∀')
ですが、こういうこともできるということが知れてよかったです！
それでは！！！