iT邦幫忙

0

Day 22:習慣完成率計算——追蹤個人目標的達成情況

  • 分享至 

  • xImage
  •  

一、前言

在前幾天的學習中,我已經完成健康紀錄的新增、查詢、修改與刪除,也學會計算平均喝水量、平均睡眠時間、每週運動總時間,並透過折線圖與長條圖呈現健康資料的變化。

然而,知道自己喝了多少水、睡了多久,並不一定能直接了解個人習慣的執行情況。

例如,假設我設定每日喝水量目標為 2000 毫升,而今天實際喝了 1600 毫升,便可以計算出今天完成了目標的 80%。如果每週設定運動三天,也可以根據實際有運動的天數,計算每週的運動目標完成率。

因此,今天的目標是學習如何設定個人健康習慣目標,利用 Python 計算完成率,並將結果整合至健康習慣追蹤系統,讓使用者更容易掌握自己的目標達成情況。

二、學習目標

了解習慣完成率的計算概念。

設定每日喝水量與每週運動天數目標。

使用 Python 計算目標完成率。

使用 Pandas 整理每日與每週健康資料。

使用 Streamlit 顯示完成率與進度條。

處理缺少紀錄、無效資料與目標設定錯誤等情況。

三、認識習慣完成率

習慣完成率是用來衡量實際表現與設定目標之間關係的指標。

例如,若每日喝水量目標是 2000 毫升,而實際喝水量是 1500 毫升,則完成率為:

[
\text{完成率}=\frac{\text{實際喝水量}}{\text{目標喝水量}}\times100%
]

代入數值:

[
\frac{1500}{2000}\times100%=75%
]

這代表當天記錄的喝水量達到設定目標的 75%。

不過,完成率不一定要限制在 100% 以內。如果實際喝水量超過目標,原始完成率可能高於 100%。為了讓儀表板的進度條容易閱讀,可以將顯示進度限制在 100%,但保留原始計算結果,避免遺失實際表現資訊。

另外,完成率的意義取決於目標如何定義。例如,每週運動目標是三天,實際運動四天,完成率為 (4/3\times100%),即約 133.3%。這代表達成了設定的次數目標,不代表運動越多就一定越健康。

四、使用 Python 計算完成率

首先,可以使用簡單的 Python 程式計算每日喝水量的完成率。

water_goal = 2000
actual_water = 1500

if water_goal > 0:
completion_rate = actual_water / water_goal * 100
print(f"喝水量完成率:{completion_rate:.1f}%")
else:
print("目標喝水量必須大於零。")

執行結果:

喝水量完成率:75.0%

其中:

water_goal:使用者設定的目標喝水量。

actual_water:當日實際記錄的喝水量。

completion_rate:計算後的目標完成率。

:.1f:將數字格式化為小數點後一位。

接著,將同樣的概念應用到每週運動目標。

exercise_goal_days = 3
actual_exercise_days = 2

if exercise_goal_days > 0:
completion_rate = (
actual_exercise_days / exercise_goal_days * 100
)
print(f"運動目標完成率:{completion_rate:.1f}%")

執行結果:

運動目標完成率:66.7%

這代表當週有運動的天數達到每週三天目標的約 66.7%。

五、使用 Pandas 整理健康紀錄

在實際系統中,完成率應該根據 health_records.csv 中已保存的資料計算,而不是每次手動輸入數字。

目前系統主要使用以下欄位:
https://ithelp.ithome.com.tw/upload/images/20261029/201789034mO2ffWFw8.png
在計算完成率之前,需要先將日期與數值整理成適當格式,並排除無效資料。

import pandas as pd
from pathlib import Path

FILE_PATH = Path("health_records.csv")

if FILE_PATH.exists():
records = pd.read_csv(FILE_PATH)

records["date"] = pd.to_datetime(
    records["date"],
    errors="coerce"
)

for column in [
    "water_ml",
    "sleep_hours",
    "exercise_minutes"
]:
    records[column] = pd.to_numeric(
        records[column],
        errors="coerce"
    )

records = records.dropna(subset=["date"])

print(records)

else:
print("目前尚無健康紀錄。")

這段程式會讀取健康資料,並將日期與數值轉換成統一格式,讓後續的完成率計算更加穩定。

需要注意的是,轉換格式只能處理部分資料問題。若喝水量出現負數,或運動時間不合理,仍然需要進一步驗證。

六、實作:建立健康習慣完成率儀表板

接下來,將完成率功能整合至 Streamlit 網頁。

本次先設定兩個可調整的目標:

每日喝水量目標,預設為 2000 毫升。

每週運動天數目標,預設為 3 天。

使用者可以在網頁中自行調整目標,系統則根據已保存的健康紀錄計算結果。

請先備份原本的 app.py,再將以下程式碼放入檔案中。這個版本會讀取既有的 CSV,不會覆寫原本的健康紀錄。

from pathlib import Path

import pandas as pd
import streamlit as st

FILE_PATH = Path("health_records.csv")

st.set_page_config(
page_title="個人健康習慣追蹤系統",
page_icon="🌱",
layout="wide"
)

st.title("🌱 個人健康習慣追蹤系統")
st.subheader("健康習慣目標完成率")

設定個人目標

st.sidebar.header("個人目標設定")

water_goal = st.sidebar.number_input(
"每日喝水量目標(毫升)",
min_value=1,
max_value=10000,
value=2000,
step=100
)

exercise_goal_days = st.sidebar.number_input(
"每週運動天數目標",
min_value=1,
max_value=7,
value=3,
step=1
)

讀取資料

if not FILE_PATH.exists():
st.info("目前尚無健康紀錄,請先新增每日健康資料。")
st.stop()

try:
records = pd.read_csv(FILE_PATH)
except (OSError, pd.errors.ParserError, UnicodeDecodeError):
st.error("讀取健康紀錄失敗,請檢查 CSV 檔案。")
st.stop()

if records.empty:
st.info("目前沒有可供分析的健康紀錄。")
st.stop()

檢查必要欄位

required_columns = [
"date",
"water_ml",
"exercise_minutes"
]

missing_columns = [
column
for column in required_columns
if column not in records.columns
]

if missing_columns:
st.error(
"健康紀錄缺少必要欄位:" + ", ".join(missing_columns)
)
st.stop()

整理日期與數值

records["date"] = pd.to_datetime(
records["date"],
errors="coerce"
)

records["water_ml"] = pd.to_numeric(
records["water_ml"],
errors="coerce"
)

records["exercise_minutes"] = pd.to_numeric(
records["exercise_minutes"],
errors="coerce"
)

records = records.dropna(subset=["date"]).copy()

排除負數資料

records.loc[
records["water_ml"] < 0,
"water_ml"
] = float("nan")

records.loc[
records["exercise_minutes"] < 0,
"exercise_minutes"
] = float("nan")

if records.empty:
st.info("目前沒有有效的日期資料。")
st.stop()

彙整同一天的紀錄

daily = records.groupby(
records["date"].dt.normalize()
).agg(
water_ml=("water_ml", "mean"),
exercise_minutes=("exercise_minutes", "sum")
).sort_index()

以最新一筆有效喝水量紀錄計算完成率

valid_water = daily["water_ml"].dropna()

st.subheader("每日喝水量目標")

if valid_water.empty:
st.info("目前沒有有效的喝水量紀錄。")
else:
latest_water = valid_water.iloc[-1]
water_rate = latest_water / water_goal * 100

col1, col2 = st.columns(2)

with col1:
    st.metric(
        "最新一筆喝水量",
        f"{latest_water:.0f} 毫升"
    )

with col2:
    st.metric(
        "喝水量目標完成率",
        f"{water_rate:.1f}%"
    )

st.progress(
    min(water_rate / 100, 1.0)
)

if water_rate >= 100:
    st.success("已達到設定的喝水量目標。")
else:
    st.info("目前尚未達到設定的喝水量目標。")

計算最新一個有有效運動資料的週次

st.divider()
st.subheader("每週運動目標")

valid_exercise = daily["exercise_minutes"].dropna()

if valid_exercise.empty:
st.info("目前沒有有效的運動時間紀錄。")
else:
exercise_data = valid_exercise.to_frame()

exercise_data["week"] = (
    exercise_data.index
    .to_period("W-SUN")
    .astype(str)
)

weekly = exercise_data.groupby("week").agg(
    total_exercise_minutes=("exercise_minutes", "sum"),
    exercise_days=("exercise_minutes",
                   lambda values: (values > 0).sum())
)

latest_week = weekly.iloc[-1]

actual_days = int(latest_week["exercise_days"])
exercise_rate = (
    actual_days / exercise_goal_days * 100
)

col1, col2 = st.columns(2)

with col1:
    st.metric(
        "最新一週有運動的天數",
        f"{actual_days} 天"
    )

with col2:
    st.metric(
        "運動目標完成率",
        f"{exercise_rate:.1f}%"
    )

st.progress(
    min(exercise_rate / 100, 1.0)
)

if exercise_rate >= 100:
    st.success("已達到設定的每週運動天數目標。")
else:
    st.info("目前尚未達到設定的每週運動天數目標。")

st.divider()

st.subheader("每日健康紀錄")
st.dataframe(
daily.reset_index(names="date").sort_values(
"date",
ascending=False
),
use_container_width=True,
hide_index=True
)

st.caption(
"完成率根據有效紀錄計算。最新一筆喝水量紀錄不一定是今天;"
"每週運動統計則以最新一個有有效運動資料的週次為準。"
)

程式碼重點說明

  1. 讓使用者自行設定目標

water_goal = st.sidebar.number_input(
"每日喝水量目標(毫升)",
min_value=1,
max_value=10000,
value=2000,
step=100
)

st.sidebar.number_input() 可以在網頁側邊欄建立數字輸入框,讓使用者調整每日喝水量目標。

這次設定的預設值只是程式實作的示範,不代表每個人都適用相同的健康目標。

  1. 計算喝水量完成率

water_rate = latest_water / water_goal * 100

系統會將最新一筆有效喝水量紀錄除以設定目標,再乘以 100,得到百分比。

這裡使用的是最新一筆有效紀錄,而不一定是今天的紀錄。如果想精確追蹤當天目標,還需要額外確認日期是否為今天。

  1. 使用進度條顯示完成率

st.progress(
min(water_rate / 100, 1.0)
)

st.progress() 的進度值介於 0 到 1 之間,因此將百分比除以 100,再利用 min() 將顯示進度限制在 100%。

即使實際完成率達到 120%,進度條仍然會顯示為滿格,而上方的數字會保留 120% 的原始完成率。

  1. 計算每週有運動的天數

exercise_days=("exercise_minutes",
lambda values: (values > 0).sum())

這段程式會計算每週運動時間大於零的日期數量。由於前面已經先將同一天的紀錄彙整,因此同一天有多筆運動紀錄時,不會被重複計算成多個運動日。

七、實際執行與測試

完成程式碼後,開啟終端機,進入專案資料夾,執行:

python -m streamlit run app.py

接著,透過網頁調整目標,確認完成率是否正確。
https://ithelp.ithome.com.tw/upload/images/20261029/20178903g722IUx36X.png

測試時,可以先使用幾筆已知數值的資料,手動計算一次完成率,再與網頁顯示的結果比較。

此外,也要留意資料缺失的情況。例如,某天沒有紀錄,不代表那天一定沒有喝水或運動。因此,完成率只能反映目前已記錄的資料,不能直接當成完整的生活習慣評分。

八、今日成果與學習心得

今天完成了健康習慣目標完成率的基本功能,學會讓使用者設定個人目標,並利用 Python 與 Pandas 計算喝水量及每週運動天數的完成率。

透過這次實作,我了解到健康習慣追蹤系統除了記錄資料與呈現統計結果之外,也可以將實際表現與個人設定的目標進行比較,讓使用者更清楚知道目前的進度。

不過,完成率並不是唯一的評估方式。若使用者沒有填寫資料,系統就無法確認其實際表現;若目標設定不符合個人需求,即使完成率很高,也不一定代表目標合理。因此,系統應該提供清楚的資料說明,並讓使用者依照自己的情況調整目標。

未來也可以增加目標保存功能,讓使用者重新開啟網頁時,仍然能保留先前設定的目標,而不需要每次重新輸入。

九、下一步學習方向

第二十三天的主題是「系統測試與除錯——檢查輸入資料是否正確」。

下一階段將針對目前已完成的健康紀錄、統計、圖表與完成率功能進行測試,檢查空白欄位、不合理數值、日期格式錯誤及重複紀錄等情況,並逐步改善系統的穩定性與可靠性。


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

尚未有邦友留言

立即登入留言