一、前言
在前二十四天的學習中,我們逐步認識了 Python、Pandas、CSV 資料儲存,以及 Streamlit 網頁介面的開發方式,也陸續完成健康紀錄輸入、歷史資料查詢、統計分析和圖表呈現等功能。
前面的學習主要是針對個別功能進行練習,而今天的任務,是將這些功能整合在同一個程式中,建立一個可以實際操作的「個人健康習慣追蹤系統初版」。
本次不追求一次完成所有進階功能,而是先讓系統具備基本的操作流程:使用者可以新增每日健康紀錄、查看過去的資料,並透過儀表板了解自己的健康習慣變化。
二、今日學習目標
整合 Python、Pandas 與 Streamlit 的基本功能。
建立統一的 CSV 資料讀取與儲存機制。
完成每日健康紀錄的輸入與新增功能。
建立歷史紀錄查詢頁面。
整合平均值統計與健康趨勢圖表。
測試各個功能之間是否能正常運作。
三、系統功能規劃
初版系統將分成三個主要頁面:

所有資料統一儲存在 health_records.csv,避免不同功能各自使用不同的資料來源。
四、整合系統程式碼
首先,建立一個名為 app.py 的 Python 檔案,並確認已經安裝需要使用的套件:
pip install streamlit pandas
接著,將以下程式碼放入 app.py。
import streamlit as st
import pandas as pd
from pathlib import Path
FILE_PATH = Path("health_records.csv")
COLUMNS = [
"date",
"water_ml",
"sleep_hours",
"exercise_minutes",
"vegetables_fruits",
"notes"
]
st.set_page_config(
page_title="個人健康習慣追蹤系統",
page_icon="💚",
layout="wide"
)
這裡使用 Path 管理資料檔案位置,並透過 COLUMNS 統一管理健康紀錄的欄位,讓新增資料、讀取資料與統計分析都能使用相同的資料格式。
def load_records():
if not FILE_PATH.exists():
return pd.DataFrame(columns=COLUMNS)
try:
records = pd.read_csv(FILE_PATH)
missing_columns = [
col for col in COLUMNS
if col not in records.columns
]
if missing_columns:
raise ValueError(
f"資料缺少欄位:{missing_columns}"
)
return records[COLUMNS]
except (OSError, ValueError, pd.errors.ParserError) as e:
st.error(f"讀取資料失敗:{e}")
return pd.DataFrame(columns=COLUMNS)
def save_records(records):
try:
records.to_csv(
FILE_PATH,
index=False,
encoding="utf-8-sig"
)
return True
except OSError as e:
st.error(f"儲存資料失敗:{e}")
return False
load_records() 負責讀取 CSV 檔案。如果檔案不存在,就先建立空的資料表;如果讀取時發生錯誤,則顯示錯誤訊息。
save_records() 則負責將更新後的資料寫回 CSV 檔案。
透過這兩個函式,可以讓不同頁面共用相同的資料管理方式。
def add_record():
st.header("新增健康紀錄")
with st.form("health_form"):
record_date = st.date_input("紀錄日期")
water_ml = st.number_input(
"今日喝水量(ml)",
min_value=0,
max_value=10000,
value=2000
)
sleep_hours = st.number_input(
"今日睡眠時間(小時)",
min_value=0.0,
max_value=24.0,
value=7.0,
step=0.5
)
exercise_minutes = st.number_input(
"今日運動時間(分鐘)",
min_value=0,
max_value=1440,
value=30
)
vegetables_fruits = st.checkbox(
"今日有攝取蔬菜或水果"
)
notes = st.text_area("其他備註")
submitted = st.form_submit_button("儲存紀錄")
if submitted:
records = load_records()
date_text = record_date.isoformat()
if not records.empty and records["date"].astype(str).eq(date_text).any():
st.warning("這一天已經有紀錄,請先確認資料,避免重複新增。")
return
new_record = {
"date": date_text,
"water_ml": water_ml,
"sleep_hours": sleep_hours,
"exercise_minutes": exercise_minutes,
"vegetables_fruits": vegetables_fruits,
"notes": notes.strip()
}
updated_records = pd.concat(
[records, pd.DataFrame([new_record])],
ignore_index=True
)
if save_records(updated_records):
st.success("健康紀錄新增成功!")
這個功能將使用者輸入的內容整理成 Dictionary,再轉換成 DataFrame,最後透過 save_records() 儲存至 CSV。
另外,系統會先檢查日期是否已經存在,避免同一天重複新增紀錄。
def show_history():
st.header("歷史紀錄查詢")
records = load_records()
if records.empty:
st.info("目前尚無健康紀錄,請先新增資料。")
return
records["date"] = pd.to_datetime(
records["date"],
errors="coerce"
)
records = records.dropna(subset=["date"])
if records.empty:
st.info("目前沒有有效的日期紀錄。")
return
min_date = records["date"].min().date()
max_date = records["date"].max().date()
start_date = st.date_input(
"開始日期",
value=min_date,
key="history_start"
)
end_date = st.date_input(
"結束日期",
value=max_date,
key="history_end"
)
if start_date > end_date:
st.warning("開始日期不能晚於結束日期。")
return
filtered = records[
records["date"].dt.date.between(
start_date,
end_date
)
]
filtered = filtered.sort_values(
"date",
ascending=False
)
st.subheader("查詢結果")
st.dataframe(
filtered,
use_container_width=True,
hide_index=True
)
st.write(f"共找到 {len(filtered)} 筆紀錄。")
這個頁面會讀取已儲存的資料,並讓使用者設定開始日期與結束日期。
系統會依照選擇的日期範圍篩選資料,再以日期由新到舊的順序呈現。
def show_dashboard():
st.header("個人健康儀表板")
records = load_records()
if records.empty:
st.info("目前尚無資料,請先新增健康紀錄。")
return
records["date"] = pd.to_datetime(
records["date"],
errors="coerce"
)
numeric_columns = [
"water_ml",
"sleep_hours",
"exercise_minutes"
]
for col in numeric_columns:
records[col] = pd.to_numeric(
records[col],
errors="coerce"
)
valid_records = records.dropna(subset=["date"])
if valid_records.empty:
st.info("目前沒有可供分析的有效資料。")
return
avg_water = valid_records["water_ml"].mean()
avg_sleep = valid_records["sleep_hours"].mean()
avg_exercise = valid_records["exercise_minutes"].mean()
col1, col2, col3 = st.columns(3)
col1.metric(
"平均喝水量",
f"{avg_water:.0f} ml"
if pd.notna(avg_water) else "無資料"
)
col2.metric(
"平均睡眠時間",
f"{avg_sleep:.1f} 小時"
if pd.notna(avg_sleep) else "無資料"
)
col3.metric(
"平均運動時間",
f"{avg_exercise:.0f} 分鐘"
if pd.notna(avg_exercise) else "無資料"
)
st.subheader("每日喝水量趨勢")
water_data = valid_records[
["date", "water_ml"]
].dropna(subset=["water_ml"])
water_data = water_data.sort_values("date")
if not water_data.empty:
st.line_chart(
water_data.set_index("date")["water_ml"]
)
else:
st.info("目前沒有可呈現的喝水量資料。")
st.subheader("每日睡眠時間趨勢")
sleep_data = valid_records[
["date", "sleep_hours"]
].dropna(subset=["sleep_hours"])
sleep_data = sleep_data.sort_values("date")
if not sleep_data.empty:
st.line_chart(
sleep_data.set_index("date")["sleep_hours"]
)
else:
st.info("目前沒有可呈現的睡眠資料。")
st.subheader("每週運動時間統計")
exercise_data = valid_records[
["date", "exercise_minutes"]
].dropna(subset=["exercise_minutes"])
if not exercise_data.empty:
weekly = (
exercise_data
.set_index("date")["exercise_minutes"]
.resample("W")
.sum()
)
st.bar_chart(weekly)
else:
st.info("目前沒有可呈現的運動資料。")
這個頁面整合了前幾天學習的 Pandas 統計與 Streamlit 圖表功能。
儀表板會計算平均喝水量、平均睡眠時間與平均運動時間,並透過折線圖和長條圖呈現健康紀錄的變化。
需要注意的是,這些平均值是根據目前有效的紀錄計算,並不代表每天都有填寫資料。
最後,將三個主要功能整合到同一個網頁中:
def main():
st.title("💚 個人健康習慣追蹤系統")
st.sidebar.title("系統功能")
page = st.sidebar.radio(
"請選擇操作頁面",
[
"新增健康紀錄",
"歷史紀錄查詢",
"健康儀表板"
]
)
if page == "新增健康紀錄":
add_record()
elif page == "歷史紀錄查詢":
show_history()
elif page == "健康儀表板":
show_dashboard()
if name == "main":
main()
這裡使用 st.sidebar.radio() 建立側邊導覽列,使用者可以自由切換不同頁面。
透過 main() 函式,讓系統能夠依照使用者選擇執行對應功能。
五、實際執行系統
完成程式碼後,先確認 app.py 已經儲存,再開啟終端機,輸入:
python -m streamlit run app.py
執行成功後,瀏覽器會開啟健康習慣追蹤系統。
接著可以依照以下順序測試:
進入「新增健康紀錄」,輸入一筆測試資料。
按下儲存紀錄,確認畫面出現成功訊息。
開啟 health_records.csv,確認資料已經寫入。
進入「歷史紀錄查詢」,確認剛才新增的資料有出現。
嘗試調整開始日期與結束日期,確認篩選功能正常。
進入「健康儀表板」,確認平均數值與圖表能夠正常顯示。
回到新增紀錄頁面,嘗試再次輸入相同日期,確認系統會阻止重複新增。
六、今日成果與學習心得
今天最重要的成果,是將過去分開練習的功能整合成一個具有基本操作流程的系統。
從最初規劃健康紀錄需要哪些欄位,到使用 Python 建立資料結構,再透過 Pandas 整理資料、CSV 儲存資料,最後使用 Streamlit 建立網頁介面,逐步完成了健康習慣追蹤系統的初版。
這次整合也讓我更清楚理解不同工具在專案中的角色:
Python: 負責程式邏輯與功能執行。
Pandas: 負責資料整理、篩選與統計。
CSV: 負責保存使用者輸入的健康紀錄。
Streamlit: 負責建立使用者可以操作的網頁介面。
不過,目前的初版仍有一些限制,例如尚未加入資料修改與刪除介面、使用者帳號管理,以及更完整的錯誤處理機制。因此,這個版本主要是用來確認核心流程能否正常運作,還不是正式上線的完整產品。
七、下一步學習方向
明天將進入 Day 26,透過模擬實際使用情境,建立不同的測試資料,檢查系統在新增紀錄、查詢資料與統計分析時是否能正常運作。
除了測試正常操作,也會嘗試輸入空白資料、重複日期及不同範圍的數值,觀察系統是否能正確處理,進一步找出需要改善的地方。
今日總結:完成健康習慣追蹤系統初版,成功整合資料輸入、CSV 儲存、歷史查詢與健康儀表板,讓原本分散的功能開始形成一個完整的操作流程。