Spaces:
Sleeping
Sleeping
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() |