Spaces:
Sleeping
Sleeping
import streamlit as st | |
import pandas as pd | |
import plotly.express as px | |
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 | |
# 📊 Google Sheets ID | |
sheet_id = "1Wc15DZWq48MxL7nXAsROJ6sRvH5njSa1ea0aaOGUOVk" | |
gid = "1168424766" | |
class SurveyMappings: | |
"""📋 問卷數據對應""" | |
gender: Dict[str, int] = field(default_factory=lambda: {'男性': 1, '女性': 2}) | |
education: Dict[str, int] = field(default_factory=lambda: { | |
'國小(含)以下': 1, '國/初中': 2, '高中/職': 3, '專科': 4, '大學': 5, '研究所(含)以上': 6}) | |
frequency: Dict[str, int] = field(default_factory=lambda: { | |
'第1次': 1, '2-3次': 2, '4-6次': 3, '6次以上': 4, '經常來學習,忘記次數了': 5}) | |
class SurveyAnalyzer: | |
"""📊 問卷分析類""" | |
def __init__(self): | |
self.mappings = SurveyMappings() | |
self.satisfaction_columns = [ | |
'1. 示範場域提供多元的數位課程與活動', | |
'2.示範場域的數位課程與活動對我的生活應用有幫助', | |
'3. 示範場域的服務人員親切有禮貌', | |
'4.示範場域的服務空間與數位設備友善方便', | |
'5.在示範場域可以獲得需要的協助', | |
'6.對於示範場域的服務感到滿意' | |
] | |
self.satisfaction_short_names = [ | |
'多元課程與活動', | |
'生活應用有幫助', | |
'服務人員親切', | |
'空間設備友善', | |
'獲得需要協助', | |
'整體服務滿意' | |
] | |
def calculate_age(self, birth_year_column): | |
"""🔢 計算年齡(從民國年到實際年齡)""" | |
# 獲取當前年份(西元年) | |
current_year = datetime.now().year | |
# 將 NaN 或無效值處理為 NaN | |
birth_years = pd.to_numeric(birth_year_column, errors='coerce') | |
# 民國年份轉西元年份 (民國年+1911=西元年) | |
western_years = birth_years + 1911 | |
# 計算年齡 | |
ages = current_year - western_years | |
return ages | |
def generate_report(self, df: pd.DataFrame) -> Dict[str, Any]: | |
"""📝 生成問卷調查報告""" | |
# 計算年齡 | |
ages = self.calculate_age(df['2.出生年(民國__年)']) | |
# 取得教育程度分布(帶計數單位) | |
education_counts = df['3.教育程度'].value_counts().to_dict() | |
education_with_counts = {k: f"{v}人" for k, v in education_counts.items()} | |
# 性別分布(帶計數單位) | |
gender_counts = df['1. 性別'].value_counts().to_dict() | |
gender_with_counts = {k: f"{v}人" for k, v in gender_counts.items()} | |
# 計算每個滿意度項目的平均分數和標準差 | |
satisfaction_stats = {} | |
for i, col in enumerate(self.satisfaction_columns): | |
mean_score = df[col].mean() | |
std_dev = df[col].std() | |
satisfaction_stats[self.satisfaction_short_names[i]] = { | |
'平均分數': f"{mean_score:.2f}", | |
'標準差': f"{std_dev:.2f}" | |
} | |
return { | |
'基本統計': { | |
'總受訪人數': len(df), | |
'性別分布': gender_with_counts, | |
'教育程度分布': education_with_counts, | |
'平均年齡': f"{ages.mean():.1f}歲" | |
}, | |
'滿意度統計': { | |
'整體平均滿意度': f"{df[self.satisfaction_columns].mean().mean():.2f}", | |
'各項滿意度': satisfaction_stats | |
} | |
} | |
def plot_satisfaction_scores(self, df: pd.DataFrame): | |
"""📊 各項滿意度平均分數圖表""" | |
# 準備數據 | |
satisfaction_means = [df[col].mean() for col in self.satisfaction_columns] | |
satisfaction_stds = [df[col].std() for col in self.satisfaction_columns] | |
# 創建數據框 | |
satisfaction_df = pd.DataFrame({ | |
'滿意度項目': self.satisfaction_short_names, | |
'平均分數': satisfaction_means, | |
'標準差': satisfaction_stds | |
}) | |
# 繪製條形圖 | |
fig = px.bar( | |
satisfaction_df, | |
x='滿意度項目', | |
y='平均分數', | |
error_y='標準差', | |
title='📊 各項滿意度平均分數與標準差', | |
color='平均分數', | |
color_continuous_scale='Viridis', | |
text='平均分數' | |
) | |
# 調整圖表佈局 | |
fig.update_layout( | |
font=dict(size=16), | |
title_font=dict(size=24), | |
xaxis_title="滿意度項目", | |
yaxis_title="平均分數", | |
yaxis_range=[1, 5], # 假設評分範圍是 1-5 | |
) | |
# 調整文字格式 | |
fig.update_traces( | |
texttemplate='%{y:.2f}', | |
textposition='outside' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
def plot_gender_distribution(self, df: pd.DataFrame, venues=None, month=None): | |
"""🟠 性別分佈圓餅圖(使用藍色和紅色)""" | |
# 過濾數據 | |
filtered_df = df.copy() | |
if venues and '全部' not in venues: | |
filtered_df = filtered_df[filtered_df['場域名稱'].isin(venues)] | |
if month and month != '全部': | |
# 假設有一個月份欄位,如果沒有請調整 | |
filtered_df = filtered_df[filtered_df['月份'] == month] | |
gender_counts = filtered_df['1. 性別'].value_counts().reset_index() | |
gender_counts.columns = ['性別', '人數'] | |
# 計算百分比 | |
total = gender_counts['人數'].sum() | |
gender_counts['百分比'] = (gender_counts['人數'] / total * 100).round(1) | |
gender_counts['標籤'] = gender_counts.apply(lambda x: f"{x['性別']}: {x['人數']}人 ({x['百分比']}%)", axis=1) | |
# 設定顏色映射 - 男性藍色,女性紅色 | |
color_map = {'男性': '#2171b5', '女性': '#cb181d'} | |
fig = px.pie( | |
gender_counts, | |
names='性別', | |
values='人數', | |
title='🟠 受訪者性別分布', | |
color='性別', | |
color_discrete_map=color_map, | |
hover_data=['人數', '百分比'], | |
labels={'人數': '人數', '百分比': '百分比'}, | |
custom_data=['標籤'] | |
) | |
# 更新悬停信息 | |
fig.update_traces( | |
textinfo='percent+label', | |
hovertemplate='%{customdata[0]}' | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# 🎨 Streamlit UI | |
def main(): | |
st.set_page_config( | |
page_title="樂齡學習數位示範體驗場域 服務滿意度調查", | |
page_icon="📊", | |
layout="wide" | |
) | |
st.markdown(""" | |
# 📊 114年度樂齡學習數位示範體驗場域 | |
## 服務滿意度調查分析報告 | |
*國立中正大學高齡教育研究中心專案管理團隊製作* | |
本報告提供全面的問卷調查分析與視覺化圖表,深入剖析樂齡學習者參與數位示範場域服務的滿意情形。 | |
""", unsafe_allow_html=True) | |
# 讀取數據 | |
df = read_google_sheet(sheet_id, gid) | |
if df is not None: | |
analyzer = SurveyAnalyzer() | |
# 新增場域和月份篩選器 | |
st.sidebar.header("🔍 數據篩選") | |
# 假設數據有「場域名稱」欄位,如果名稱不同請調整 | |
if '場域名稱' in df.columns: | |
venues = ['全部'] + sorted(df['場域名稱'].unique().tolist()) | |
selected_venues = st.sidebar.multiselect("選擇場域", venues, default=['全部']) | |
else: | |
# 如果沒有場域欄位,創建10個虛擬場域供選擇 | |
venues = ['全部'] + [f'場域{i+1}' for i in range(10)] | |
selected_venues = st.sidebar.multiselect("選擇場域", venues, default=['全部']) | |
# 假設數據有「月份」欄位,如果沒有請調整 | |
if '月份' in df.columns: | |
months = ['全部'] + sorted(df['月份'].unique().tolist()) | |
selected_month = st.sidebar.selectbox("選擇月份", months) | |
else: | |
# 如果沒有月份欄位,可以創建虛擬月份選項 | |
months = ['全部'] + [f'{i+1}月' for i in range(12)] | |
selected_month = st.sidebar.selectbox("選擇月份", months) | |
# 📌 基本統計數據 | |
st.sidebar.header("📌 選擇數據分析") | |
selected_analysis = st.sidebar.radio("選擇要查看的分析", | |
["📋 問卷統計報告", "📊 滿意度統計", "🟠 性別分佈"]) | |
if selected_analysis == "📋 問卷統計報告": | |
st.header("📋 問卷統計報告") | |
report = analyzer.generate_report(df) | |
for category, stats in report.items(): | |
with st.expander(f"🔍 {category}", expanded=True): | |
for key, value in stats.items(): | |
if key == '各項滿意度': | |
st.write(f"**{key}:**") | |
for item, item_stats in value.items(): | |
st.write(f" - **{item}**: {', '.join([f'{k}: {v}' for k, v in item_stats.items()])}") | |
else: | |
st.write(f"**{key}**: {value}") | |
elif selected_analysis == "📊 滿意度統計": | |
st.header("📊 滿意度統計") | |
analyzer.plot_satisfaction_scores(df) | |
elif selected_analysis == "🟠 性別分佈": | |
st.header("🟠 性別分佈") | |
analyzer.plot_gender_distribution(df, selected_venues, selected_month) | |
if __name__ == "__main__": | |
main() |