所有文章

資料科學第六週-期中了!用Streamlit展示文字資訊與圖表

12,73732 分鐘閱讀
資料科學python
資料科學第六週-期中了!用Streamlit展示文字資訊與圖表

前言-會使用到什麼讓網站上線

在這週,我們要將前面所學到的DataFrame與資料視覺化圖表的上線,可參考動態展示網站:

https://taoyuan-data.streamlit.app/

Image

若只是在本地(Local)端呈現,會使用到的技術包含:

  • Streamlit - 建置網站
  • Pandas and Plotly - 資訊分析與視覺化工具

如果要上雲端,讓人能夠連上,要學會的包含(我會寫在下一篇):

  • Venv - Python內建虛擬環境設定
  • Git and Github - 儲存和協作軟體開發專案的線上平台
  • Streamlit Cloud - 雲端部署網站的平台服務

Streamlit簡介

Streamlit 是一個開源的 Python 框架,專為快速建立美觀且互動性強的資料展示而設計,只要寫幾行程式碼,就可以將 Python 腳本轉變成互動式網頁應用。Let’s try it!

Streamlit安裝與匯入

bash
pip install streamlit

創建一檔案這取名為app.py ,將套件匯入

bash
import streamlit as st

Streamlit 文字類型

在使用文字時與平常記筆記的Notion類似,使用Markdown語法可以設定標題

Image

如圖,標題可以使用

bash
st.title('桃園市相關數據展示')

使用title 等同於使用write 加上markdown語法#如下:

python
st.write('# 桃園市相關數據展示')

所以不想記太多函式的話,可以全部用write 也OK

次標題可以用##

python
st.write('## 中壢高商乙丙級通過率')

Streamlit 上線

這樣,執行後就可以在本地端測試了:

Image

會看到streamlit run 你的檔案位置 複製並輸入在命令提示字元就可以看到結果。

Image

接者每當你改動程式碼後存檔,只要重新整理頁面,就會跟改動了

更方便的是,你可以點選右上角的… > Settings

Image

再點選Run on save ,網頁就會在你存檔時,不用重整網頁,自動更新囉,非常快速方便吧!

Image

Streamlit 欄位排版

三欄式切版

可以在一行中指定要切成幾欄,例如我要切成三欄

python
col1, col2,col3 = st.columns(3)

每欄中有多行指令要設定的話,可以配上with ,還進行欄位的設定

官網範例

Image

metric

實作中使用metric 這個函式,當欄位是要強調「某個值或訊息」的時候,很適合使用,其中參數包含:

  • label:指標的名稱,用於描述數值的內容。
  • value:主顯示數值。
  • delta(可選):和之前數值的變化,用於顯示增加或減少的趨勢(通常是數字或百分比)。
  • delta_color(可選):控制變化值的顏色。
    • "normal":預設,增加顯示綠色,減少顯示紅色。
    • "inverse":反轉顏色,增加顯示紅色,減少顯示綠色。
  • help(可選):一段說明文字,當使用者將滑鼠懸停在指標名稱上時會顯示。

實作:

python
col1, col2,col3, col4 = st.columns(4)
col1.metric('112年度電乙本校參檢人數', 84, delta=-20, delta_color="inverse", help=None)
col2.metric('112年度電乙全國參檢人數', 6874, delta=7651-6874, delta_color="inverse", help=None)
col3.metric('112年度電乙本校通過率', f'{48/84*100:.2f}%', delta='-13.1%', delta_color="inverse", help=None)
col4.metric('112年度電乙全國通過率', f'{3370/6874*100:.2f}%', delta=f'{((3370/6874)-(3654/7651))*100:.2f}%', delta_color="inverse", help=None)
Image

和台灣不同,預設「漲是綠色、跌是紅色」,所以在delta_color 要使用inverse

不同比例切版

除了可以在一行中指定要切成幾欄,也可以利用list 的方式設定比例,如官網範例:

Image

Plotly製圖I - 繪折線圖

實作前幾節的資料,用plotly來呈現,幾個plotly的用法可以注意一下:

  • 載入Plotly中可以互動的元件
python
import plotly.graph_objects as go
  • 建立空的圖表
python
fig = go.Figure()
  • 將線型加入,將我要加入的地區用list 呈現
    • add_trace() :在fig中加入軌跡(線圖)
    • Scatter() :散布圖/折線圖,根據後面的mode 決定
    • name :這個參數呈現在圖例(legend)當中
python
for area in ['臺灣地區-元', '新北市-元', '臺北市-元', '桃園市-元']:
	fig.add_trace(x=df['年'],y=df[area],mode='lines+markers',name=area)
  • 圖的額外設定,其中hovermode 可以設定x, y, closet ,互動時會依據方式來呈現資料
python
fig.update_layout(
    #title='各地區收入變化',
    xaxis_title='年份',
    yaxis_title='收入 (元)',
    hovermode='x',
    width=700  # 設定圖表寬度
)
  • 在streamlit呈現出來,use_container_width=True 能讓圖形根據網頁大小而做變化,更符合RWD建議加入
python
st.plotly_chart(fig,use_container_width=True)

此區段程式碼:

python
import plotly.graph_objects as go
data = {
    '年': [1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022],
    '臺灣地區-元': [1070268, 1089575, 1091478, 1064136, 1064153, 1064825, 1074665, 1082168, 1099739, 1108674, 1099994, 1074180, 1071938, 1104265, 1122379, 1140271, 1157926, 1167284, 1194572, 1231112, 1249031, 1274196, 1293719, 1314023, 1340848],
    '新北市-元': [1087242, 1102563, 1095192, 1098408, 1100003, 1093053, 1109639, 1135398, 1125605, 1099472, 1159279, 1110774, 1071131, 1116342, 1101389, 1129598, 1146991, 1171978, 1223867, 1265798, 1292753, 1319841, 1352548, 1381603, 1421385],
    '臺北市-元': [1442675, 1472979, 1530735, 1505506, 1514440, 1501916, 1488180, 1514069, 1526228, 1550134, 1538257, 1515793, 1564298, 1537890, 1570778, 1545415, 1575819, 1581899, 1568945, 1648122, 1649348, 1723021, 1716591, 1732126, 1752411],
    '桃園市-元': [1163181, 1174654, 1209363, 1143050, 1192731, 1163092, 1203215, 1192419, 1179444, 1210511, 1182721, 1146080, 1122238, 1183732, 1238698, 1257146, 1327963, 1307158, 1317790, 1337361, 1378732, 1392199, 1424027, 1448909, 1449549],
    '臺中市-元': [1102701, 1137007, 1038480, 1055944, 1015972, 1023452, 990633, 988496, 1048174, 1105239, 1026631, 977731, 987259, 1100346, 1067060, 1126875, 1152523, 1169183, 1140325, 1245350, 1279865, 1298497, 1289700, 1304508, 1338826],
    '臺南市-元': [911488, 896601, 933736, 872189, 898270, 936057, 943051, 881412, 950332, 972699, 976116, 924950, 886924, 948383, 927231, 996434, 991990, 1007093, 1063495, 1079199, 1086077, 1079174, 1086475, 1120580, 1143699],
    '高雄市-元': [1029317, 1051762, 1031569, 1003108, 974592, 972280, 1011408, 1046729, 1030212, 1081799, 1068765, 1052162, 1052260, 1043941, 1085971, 1107383, 1112287, 1145895, 1166824, 1186204, 1219246, 1224668, 1224100, 1231562, 1263068],
    '宜蘭縣-元': [976550, 1022506, 972996, 877199, 846963, 943028, 874576, 994967, 931588, 909097, 1006018, 904809, 971775, 877016, 1075706, 979013, 1071335, 1160320, 1085846, 1098807, 1069997, 1093475, 1138365, 1136419, 1165558],
    '新竹縣-元': [1113042, 1167454, 1232173, 1106603, 1127123, 1133617, 1196515, 1304360, 1164274, 1098234, 1289463, 1281933, 1300116, 1372358, 1367712, 1346768, 1389453, 1283995, 1365150, 1616327, 1519478, 1539555, 1619782, 1689337, 1702134],
    '苗栗縣-元': [943047, 945975, 935836, 944066, 921405, 949888, 895950, 931906, 1030851, 1000447, 919930, 926267, 964594, 1014144, 1012306, 1020185, 1100084, 1008241, 1166196, 1029485, 1045881, 1073028, 1161999, 1214424, 1273250],
    '彰化縣-元': [951047, 891310, 897000, 845708, 943182, 962720, 945506, 906328, 961572, 922001, 902838, 890929, 887707, 920937, 953701, 936595, 940572, 926717, 994353, 970491, 997162, 1026792, 996066, 1086809, 1108221],
    '南投縣-元': [875980, 1005079, 874344, 836262, 830936, 819394, 879780, 897891, 964953, 860836, 851992, 978331, 932725, 881743, 986881, 896557, 919551, 878760, 916199, 894368, 967369, 940893, 997605, 985686, 1048879],
    '雲林縣-元': [782106, 889682, 810634, 873803, 817942, 809617, 849594, 769997, 906358, 794205, 767431, 744181, 748256, 817778, 824211, 838094, 865131, 876670, 896101, 868663, 860237, 933883, 988062, 1024191, 1016913],
    '嘉義縣-元': [707349, 766905, 857615, 783819, 730084, 738385, 778574, 797895, 810323, 750236, 764933, 741766, 775017, 804668, 880625, 858253, 789406, 890742, 896217, 901022, 940780, 850597, 906554, 916631, 891498],
    '屏東縣-元': [919710, 879362, 913848, 838027, 927042, 919906, 847872, 894772, 899657, 953471, 842372, 877498, 850116, 866795, 889560, 861063, 832681, 869818, 911258, 958999, 985681, 964547, 1064508, 1046302, 1083933],
    '臺東縣-元': [702299, 725065, 780967, 760989, 774329, 737947, 755711, 695369, 753191, 860701, 699334, 805395, 674899, 733168, 796622, 799026, 820549, 746981, 797395, 889108, 884510, 874386, 891340, 911136, 916928],
    '花蓮縣-元': [887914, 883537, 933625, 933484, 845923, 880361, 858345, 795376, 862688, 878356, 843646, 808632, 904472, 920602, 927400, 966607, 860613, 924706, 948501, 910090, 937898, 956973, 1032499, 1026488, 1010041],
    '澎湖縣-元': [634053, 723916, 831230, 732306, 760406, 739670, 874714, 778659, 853872, 789752, 902952, 851835, 986954, 828441, 887077, 922916, 932694, 792696, 1069545, 857821, 959761, 1037554, 1091011, 796484, 919357],
    '基隆市-元': [999088, 1058154, 1052113, 964170, 893884, 881716, 1048399, 1052699, 948817, 999562, 976639, 1026166, 1040931, 1018118, 955197, 1002341, 957360, 1072433, 1074314, 1096361, 1070748, 1140481, 1106963, 1111556, 1099526],
    '新竹市-元': [1207558, 1268541, 1395702, 1419946, 1320717, 1344434, 1479816, 1361016, 1478303, 1554456, 1462204, 1426854, 1448209, 1479675, 1439066, 1535411, 1576797, 1427572, 1537317, 1572296, 1426379, 1602826, 1618903, 1602415, 1722889],
    '嘉義市-元': [1059622, 958462, 1005451, 975877, 1015957, 927635, 832402, 984171, 966863, 899387, 898229, 836551, 842337, 925444, 1095203, 1241161, 1157962, 1106004, 1154411, 1090947, 1152499, 1208298, 1225219, 1217617, 1282798]
}


df = pd.DataFrame(data) #各地區年收資料
col_rowB_1, col_rowB_2 = st.columns((7,3))
with col_rowB_1:
    st.write('### 桃園區與臺灣地區收入變化')
    # 使用 plotly 建立折線圖
    fig = go.Figure()

    # 添加每個選取的地區到圖表中
    for area in ['臺灣地區-元', '新北市-元', '臺北市-元', '桃園市-元']:
        fig.add_trace(go.Scatter(x=df['年'], y=df[area], mode='lines+markers', name=area))

    # 設定圖表標題和軸標籤
    fig.update_layout(
        #title='各地區收入變化',
        xaxis_title='年份',
        yaxis_title='收入 (元)',
        hovermode='x',
        width=700  # 設定圖表寬度
        
    )
    # 在 Streamlit 中顯示圖表
    st.plotly_chart(fig,use_container_width=True)
    
    
with col_rowB_2:
    st.write('### 原始資料')
    st.write(df)
Image

側邊欄設定

側邊欄是Streamlit一個有趣的設計,可以讓使用者自行篩選資料,讓圖表視覺化的呈現更為彈性,以上面的地區收入變化為例:

側邊欄的標題

分為主標題st.sidebar.header 和次標題st.sidebar.subheader 非常直觀

python
st.sidebar.subheader('收入-選擇地區')

多選條件

可以選定不同地區做即時的比較,所以使用multiselect ,其中

  • option :代表所有可選擇的標籤
  • default :代表網頁一開始預設有的標籤
python
selected_areas = st.sidebar.multiselect(
        '選擇地區',
        options=df.columns[1:],  # 不包含 '年' 欄位
        default=['臺灣地區-元', '新北市-元', '臺北市-元', '桃園市-元']  # 預設選擇
        
    )

側邊欄就被叫出來拉!

Image

因為要讓圖可以跟著調整,所以對原本圖的呈現進行修改:

要加入軌跡add_trace() 的線,改為由selected_areas 這個變數所控制,就這麼簡單!

python
for area in selected_areas:
        fig.add_trace(go.Scatter(x=df['年'], y=df[area], mode='lines+markers', name=area))

修改後就可以及時控制圖囉

Image

側邊欄風格調整

另外因為Streamlit預設主視覺顏色很鮮豔,一樣可以透過Setting來做修改,點擊Setting後選擇「Edit active theme」即可變換主題顏色

Image

繪製原餅圖

這裡使用之前廟宇的資料,先透過sidebar進行設定,這樣在算「主祀神祇的比例」的時候,可以篩選,要計入的神明。

這邊要注意的是option_list ,因為原本df_count_ratio.index 得到的是一個Series,要轉成文字串列才能被選擇。

python
st.sidebar.subheader('比例-選擇主祀神祇')
    options_list = df_count_ratio.index.astype(str).tolist()
    selected_labels = st.sidebar.multiselect(
        '選擇主祀神祇',
        options=options_list,
        default=options_list  # 預設顯示全部
    )

再來就差不多了,label 代表圓餅圖要出現的標籤,values 代表各個值,跟前面畫圖是一樣的

hole 則是用來設計中間的空心,改稱「甜甜圈圖」,可有可無。

python
# 使用 Plotly 繪製互動式圓餅圖
    fig = go.Figure(
        go.Pie(
            labels=filtered_data.index,
            values=filtered_data,
            hole=0.3,  # 设置为甜甜圈图表
            textinfo='percent+label'  # 显示百分比和标签
        )
    )

    # 在 Streamlit 中顯示圖表
    st.plotly_chart(fig)

此段完整程式碼:

python
st.write('## 桃園區廟宇登記數量分析')
col_rowC_1, col_rowC_2 = st.columns((5,5))
with col_rowC_1:
    st.write('### 桃園市各廟宇主祀神祇比例')

    # 從 API 讀取 JSON 數據
    data = pd.read_json('http://data.tycg.gov.tw/api/v1/rest/datastore/b2247404-3d92-4829-9855-0cd5e71b92b3?format=json&limit=500')
    df = pd.DataFrame(data['result']['records'])

    # 計算各主祀神祇的數量和比例
    df_count = df['主祀神祇'].value_counts(normalize=True)
    df_count_ratio = df_count / df_count.sum()
    # 計算 "其他" 的比例
    df_count_ratio['其他'] = df_count_ratio[df_count_ratio < 0.03].sum()

    # 保留比例大於等於 3% 的項目,並包含 "其他"
    df_count_ratio = df_count_ratio[df_count_ratio >= 0.03]
    # 在 Streamlit sidebar 中創建多選篩選
    # 轉換為純字串列表
    st.sidebar.subheader('比例-選擇主祀神祇')
    options_list = df_count_ratio.index.astype(str).tolist()
    selected_labels = st.sidebar.multiselect(
        '選擇主祀神祇',
        options=options_list,
        default=options_list  # 預設顯示全部
    )
   

    # 根據選擇篩選數據
    filtered_data = df_count_ratio[selected_labels]

    # 使用 Plotly 繪製互動式圓餅圖
    fig = go.Figure(
        go.Pie(
            labels=filtered_data.index,
            values=filtered_data,
            hole=0.3,  # 设置为甜甜圈图表
            textinfo='percent+label'  # 显示百分比和标签
        )
    )

    # 在 Streamlit 中顯示圖表
    st.plotly_chart(fig)

繪製長條圖 和 滑桿側邊欄

樞紐分析表Pivot_table

在前面我們使用pivot_table 來做神明與教別的交叉數量分析,將pivot_table 顯示出來更方便了解:

python
    # 建立交叉表,顯示不同主祀神祇所對應的教別數量
    pivot_table = pd.pivot_table(
        df, 
        index='主祀神祇', 
        columns='教別', 
        values='寺廟名稱',
        aggfunc='count', 
        fill_value=0
    ).reset_index()
Image

因為等等要建置的滑桿最小值和最大值和「總廟數」有關,於是又加了一個欄位,將所有列,第一欄以外(iloc[:,1:])進行橫向加總(axis=1)

python

    # 添加 "總寺廟數量" 列
    pivot_table['總寺廟數量'] = pivot_table.iloc[:, 1:].sum(axis=1)

滑桿Slider

設定滑桿,找到最大值和最小值即可

python
    min_count, max_count = st.sidebar.slider(
        '選擇寺廟數量篩選範圍',
        min_value=int(pivot_table['總寺廟數量'].min()),
        max_value=int(pivot_table['總寺廟數量'].max()),
        value=(int(pivot_table['總寺廟數量'].min()), int(pivot_table['總寺廟數量'].max()))
    )
Image

長條圖繪製前,講一下長表格melt

因為要透過「教別」來進行分組的長條圖,可以想見,x軸是依教別分好組的神祇,y軸是寺廟數量。

因此我想讓表格如下圖,每個神祇對應到四種教別的寺廟數量

Image

但原本pivot_table中,教別在欄,這時候就適合用melt將欄融化成列!

melt 的三個參數:

  • id_vars - 代表主要不轉換的鍵
  • var_name - 代表要融化的欄
  • value_name - 代表表中還要保留的值
python
    # 根據數量範圍篩選神祇
    filtered_table = pivot_table[(pivot_table['總寺廟數量'] >= min_count) & (pivot_table['總寺廟數量'] <= max_count)]

    # 去除 "總寺廟數量" 列,轉換為長格式
    filtered_table_long = filtered_table.drop(columns='總寺廟數量').melt(id_vars='主祀神祇', var_name='教別', value_name='寺廟數量')
    st.write(filtered_table_long)
    # 去除寺廟數量為 0 的項目
    filtered_table_long = filtered_table_long[filtered_table_long['寺廟數量'] > 0]
Image

繪製長條圖

轉換為長表格後,就可以用Plotly來進行Bar Chart的繪製,其中barmode 代表長條圖的顯示模式,也可以試著調整看看其他效果。

  • barmode='group':並排顯示條形,用於對比不同類別。
  • barmode='stack':堆疊顯示條形,用於展示類別總和及其貢獻。
  • barmode='overlay':覆蓋顯示條形,容易遮擋信息,不常用。
  • barmode='relative':相對堆疊顯示,展示各類別在 x 軸標籤下的比例。
python
# 使用 Plotly 繪製長條圖
    fig = px.bar(
        filtered_table_long,
        x='主祀神祇',
        y='寺廟數量',
        color='教別',
        barmode='group',
        title='',
        labels={'寺廟數量': '寺廟數量', '主祀神祇': '主祀神祇', '教別': '教別'}
    )

    # 在 Streamlit 中顯示長條圖
    st.plotly_chart(fig)

以上為將之前資料放上Streamlit的過程教學,在實作時應該很夠大家消化一下了。

我把地圖folium 的功能放在下一篇分開講!順便記錄一下,十月的颱風,放了兩天!

最後放上完整程式碼連結:

https://github.com/alu1006/dashboard_streamlit

版權聲明

文章標題:資料科學第六週-期中了!用Streamlit展示文字資訊與圖表

文章作者:阿盧老師

文章連結:https://codinglu.tw/blog/streamlit-dashboard

授權條款:本文採用 CC BY-NC 4.0 授權。轉載請標明出處。