|
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 |
|
|
|
|
|
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 |
|
|
|
|
|
sheet_id = "1Wc15DZWq48MxL7nXAsROJ6sRvH5njSa1ea0aaOGUOVk" |
|
gid = "1168424766" |
|
|
|
@dataclass |
|
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 |
|
|
|
|
|
birth_years = pd.to_numeric(birth_year_column, errors='coerce') |
|
|
|
|
|
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], |
|
) |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
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() |