iT邦幫忙

0

Day 25:整合核心功能——完成健康習慣追蹤系統初版

  • 分享至 

  • xImage
  •  

一、前言

在前二十四天的學習中,我們逐步認識了 Python、Pandas、CSV 資料儲存,以及 Streamlit 網頁介面的開發方式,也陸續完成健康紀錄輸入、歷史資料查詢、統計分析和圖表呈現等功能。

前面的學習主要是針對個別功能進行練習,而今天的任務,是將這些功能整合在同一個程式中,建立一個可以實際操作的「個人健康習慣追蹤系統初版」。

本次不追求一次完成所有進階功能,而是先讓系統具備基本的操作流程:使用者可以新增每日健康紀錄、查看過去的資料,並透過儀表板了解自己的健康習慣變化。

二、今日學習目標

整合 Python、Pandas 與 Streamlit 的基本功能。

建立統一的 CSV 資料讀取與儲存機制。

完成每日健康紀錄的輸入與新增功能。

建立歷史紀錄查詢頁面。

整合平均值統計與健康趨勢圖表。

測試各個功能之間是否能正常運作。

三、系統功能規劃

初版系統將分成三個主要頁面:

https://ithelp.ithome.com.tw/upload/images/20261030/201789037i00AToqQy.png

所有資料統一儲存在 health_records.csv,避免不同功能各自使用不同的資料來源。

四、整合系統程式碼

首先,建立一個名為 app.py 的 Python 檔案,並確認已經安裝需要使用的套件:

pip install streamlit pandas

接著,將以下程式碼放入 app.py。

  1. 匯入套件與設定資料欄位

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 統一管理健康紀錄的欄位,讓新增資料、讀取資料與統計分析都能使用相同的資料格式。

  1. 建立資料讀取與儲存功能

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 檔案。

透過這兩個函式,可以讓不同頁面共用相同的資料管理方式。

  1. 建立新增健康紀錄功能

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。

另外,系統會先檢查日期是否已經存在,避免同一天重複新增紀錄。

  1. 建立歷史紀錄查詢功能

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)} 筆紀錄。")

這個頁面會讀取已儲存的資料,並讓使用者設定開始日期與結束日期。

系統會依照選擇的日期範圍篩選資料,再以日期由新到舊的順序呈現。

  1. 建立健康儀表板

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 圖表功能。

儀表板會計算平均喝水量、平均睡眠時間與平均運動時間,並透過折線圖和長條圖呈現健康紀錄的變化。

需要注意的是,這些平均值是根據目前有效的紀錄計算,並不代表每天都有填寫資料。

  1. 建立系統導覽功能

最後,將三個主要功能整合到同一個網頁中:

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 儲存、歷史查詢與健康儀表板,讓原本分散的功能開始形成一個完整的操作流程。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言