Rooobert's picture
Update app.py
da1dc34 verified
raw
history blame
8.92 kB
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go
import numpy as np
from datetime import datetime
from dataclasses import dataclass, field
from typing import Dict, List, Tuple, Any
# 📥 讀取 Google 試算表函數
def read_google_sheet(sheet_id, sheet_number=0):
"""📥 從 Google Sheets 讀取數據"""
url = f'https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={sheet_number}'
try:
df = pd.read_csv(url)
return df
except Exception as e:
st.error(f"❌ 讀取失敗:{str(e)}")
return None
class SurveyAnalyzer:
"""📊 問卷分析類"""
def __init__(self):
# 滿意度欄位名稱
self.satisfaction_columns = [
'1.示範場域提供多元的數位課程與活動',
'2.示範場域的數位課程與活動對我的生活應用有幫助',
'3.示範場域的服務人員親切有禮貌',
'4.示範場域的服務空間與數位設備友善方便',
'5.在示範場域可以獲得需要的協助',
'6.對於示範場域的服務感到滿意'
]
# 對應的簡短名稱
self.satisfaction_short_names = [
'多元課程與活動',
'生活應用幫助',
'服務人員親切',
'空間設備友善',
'獲得需要協助',
'整體服務滿意'
]
def plot_satisfaction_scores(self, df: pd.DataFrame):
"""📊 示範場域滿意度平均分數圖表"""
# 確保所有滿意度欄位都存在
existing_columns = [col for col in self.satisfaction_columns if col in df.columns]
# 計算平均分數和標準差
satisfaction_means = [df[col].mean() for col in existing_columns]
satisfaction_stds = [df[col].std() for col in existing_columns]
# 創建數據框
satisfaction_df = pd.DataFrame({
'滿意度項目': [self.satisfaction_short_names[self.satisfaction_columns.index(col)] for col in existing_columns],
'平均分數': satisfaction_means,
'標準差': satisfaction_stds
})
# 排序結果(由高到低)
satisfaction_df = satisfaction_df.sort_values(by='平均分數', ascending=False)
# 建立顏色漸變映射
color_scale = [
[0, '#90CAF9'], # 淺藍色
[0.5, '#2196F3'], # 中藍色
[1, '#1565C0'] # 深藍色
]
# 繪製條形圖
fig = px.bar(
satisfaction_df,
x='滿意度項目',
y='平均分數',
error_y='標準差',
title='📊 示範場域各項滿意度分析',
color='平均分數',
color_continuous_scale=color_scale,
text='平均分數',
hover_data={
'滿意度項目': True,
'平均分數': ':.2f',
'標準差': ':.2f'
}
)
# 調整圖表佈局
fig.update_layout(
font=dict(family="Arial", size=16),
title_font=dict(family="Arial Black", size=24),
title_x=0.5, # 標題置中
xaxis_title="滿意度項目",
yaxis_title="平均分數",
yaxis_range=[0, 5], # 評分範圍從0開始,視覺上更明顯
plot_bgcolor='rgba(240,240,240,0.8)', # 淺灰色背景
paper_bgcolor='white',
xaxis_tickangle=-25, # 斜角標籤,避免重疊
margin=dict(l=40, r=40, t=80, b=60),
legend_title_text="平均分數",
shapes=[
# 添加參考線 - 4分線
dict(
type='line',
yref='y', y0=4, y1=4,
xref='paper', x0=0, x1=1,
line=dict(color='rgba(220,20,60,0.5)', width=2, dash='dash')
)
],
annotations=[
# 參考線標籤
dict(
x=0.02, y=4.1,
xref='paper', yref='y',
text='優良標準 (4分)',
showarrow=False,
font=dict(size=14, color='rgba(220,20,60,0.8)')
)
]
)
# 調整文字格式
fig.update_traces(
texttemplate='%{y:.2f}',
textposition='outside',
marker_line_color='rgb(8,48,107)',
marker_line_width=1.5,
opacity=0.85
)
# 計算整體平均滿意度(只計算存在的欄位)
overall_satisfaction = df[existing_columns].mean().mean()
# 返回圖表和整體滿意度
return fig, overall_satisfaction, len(df)
def analyze_demographic_data(self, df: pd.DataFrame):
"""分析性別和教育程度"""
# 性別分佈
if '性別' in df.columns:
gender_counts = df['性別'].value_counts()
gender_pie = go.Figure(data=[go.Pie(
labels=gender_counts.index,
values=gender_counts.values,
hole=.3,
title='性別分佈'
)])
gender_pie.update_layout(title='📊 性別分佈')
else:
gender_pie = None
st.warning("資料中缺少性別欄位")
# 教育程度分佈
if '教育程度' in df.columns:
education_counts = df['教育程度'].value_counts()
education_bar = go.Figure(data=[go.Bar(
x=education_counts.index,
y=education_counts.values,
text=education_counts.values,
textposition='auto'
)])
education_bar.update_layout(
title='📊 教育程度分佈',
xaxis_title='教育程度',
yaxis_title='人數'
)
else:
education_bar = None
st.warning("資料中缺少教育程度欄位")
return gender_pie, education_bar
def main():
st.set_page_config(page_title="示範場域滿意度調查", layout="wide")
# 讀取 Google Sheet 數據
sheet_id = "1Wc15DZWq48MxL7nXAsROJ6sRvH5njSa1ea0aaOGUOVk"
gid = "1168424766"
df = read_google_sheet(sheet_id, gid)
if df is not None:
# 創建分析器
analyzer = SurveyAnalyzer()
# 顯示標題
st.title("📊 示範場域滿意度調查分析")
# 提示缺少的滿意度欄位
missing_columns = [col for col in analyzer.satisfaction_columns if col not in df.columns]
if missing_columns:
st.warning(f"⚠️ 缺少以下滿意度欄位: {missing_columns}")
# 繪製滿意度圖表
satisfaction_fig, overall_satisfaction, num_respondents = analyzer.plot_satisfaction_scores(df)
# 顯示滿意度圖表
st.plotly_chart(satisfaction_fig, use_container_width=True)
# 顯示整體滿意度
st.markdown(f"""
### 📈 整體滿意度分析
- **受訪人數**: {num_respondents}
- **整體平均滿意度**: {overall_satisfaction:.2f}
#### 🔍 滿意度解讀
- 0-1分: 非常不滿意
- 1-2分: 不滿意
- 2-3分: 普通
- 3-4分: 滿意
- 4-5分: 非常滿意
根據調查結果,整體滿意度為 {overall_satisfaction:.2f} 分,
""", unsafe_allow_html=True)
# 根據整體滿意度提供文字解讀
if overall_satisfaction < 2:
st.warning("⚠️ 整體滿意度較低,建議深入檢討服務品質")
elif overall_satisfaction < 3:
st.info("ℹ️ 整體滿意度處於普通水平,可以進一步改善服務")
elif overall_satisfaction < 4:
st.success("✅ 整體滿意度良好,但仍有提升空間")
else:
st.balloons()
st.success("🎉 整體滿意度非常高,表現優異!")
# 人口統計分析
st.header("👥 人口統計分析")
# 創建兩列顯示
col1, col2 = st.columns(2)
# 性別分佈
with col1:
gender_pie, _ = analyzer.analyze_demographic_data(df)
if gender_pie:
st.plotly_chart(gender_pie, use_container_width=True)
# 教育程度分佈
with col2:
_, education_bar = analyzer.analyze_demographic_data(df)
if education_bar:
st.plotly_chart(education_bar, use_container_width=True)
if __name__ == "__main__":
main()