iT邦幫忙

0

Day 20:每週運動分析——統計運動時間與次數

  • 分享至 

  • xImage
  •  

一、前言

在前幾天的學習中,我已經完成健康紀錄的新增、查詢、修改與刪除,也學會使用 Pandas 計算平均喝水量與平均睡眠時間。

不過,除了喝水量與睡眠時間之外,運動也是個人健康習慣追蹤系統的重要指標。

假設我每天都有記錄運動時間,單純查看每筆資料,只能知道某一天運動了多久。如果將這些紀錄整理成每週統計,就可以進一步了解自己一週總共運動了多少分鐘、運動了幾次,以及不同週之間是否出現變化。

因此,今天的學習目標是使用 Python 的 Pandas 套件,將每日運動紀錄依照週次分組,計算每週的運動總時間與運動次數,並將結果呈現在 Streamlit 網頁中。

二、學習目標

認識時間序列資料與每週統計的概念。

使用 Pandas 將日期轉換成日期格式。

使用 groupby() 依照每週進行資料分組。

使用 sum() 計算每週運動總時間。

使用 count() 計算每週具有有效運動紀錄的天數。

將每週運動統計整合至健康習慣追蹤系統。

三、認識每週運動統計

在目前的系統中,每筆健康紀錄都包含日期與運動時間兩個重要欄位:

https://ithelp.ithome.com.tw/upload/images/20261028/20178903ZumoTIeFEU.png
其中,date 表示紀錄日期,exercise_minutes 表示當天記錄的運動分鐘數。

假設以上五筆資料屬於同一週,則:

運動總時間:30 + 45 + 0 + 20 + 60 = 155 分鐘。

有運動的天數:4 天。

有效紀錄天數:5 天。

這裡需要區分「有運動的天數」與「有效紀錄天數」。如果當天填寫 0 分鐘,代表有完成紀錄,只是沒有運動;如果當天沒有留下任何紀錄,就不能直接認定當天完全沒有運動。

因此,本次實作會以有效紀錄作為統計基礎,避免將沒有資料的日期誤認為零分鐘。

四、使用 Pandas 整理日期資料

在進行每週統計之前,必須先確保日期欄位可以正確識別。

可以使用 pd.to_datetime() 將日期文字轉換成 Pandas 的日期格式:

import pandas as pd

records = pd.DataFrame({
"date": [
"2026-10-19",
"2026-10-20",
"2026-10-21",
"2026-10-22",
"2026-10-23"
],
"exercise_minutes": [30, 45, 0, 20, 60]
})

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

print(records)

執行後,date 欄位會轉換成日期時間格式。如果遇到無法辨識的日期,errors="coerce" 會將它轉成缺失值,方便後續排除。

接著,將運動時間轉換成數值:

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

這樣可以降低資料格式不一致對統計造成的影響。

五、使用 groupby() 進行每週分組

Pandas 的 groupby() 可以將符合相同條件的資料分成不同群組,再對各群組進行計算。

例如,想依照每週統計運動時間,可以先建立一個代表每週的欄位。

records["week"] = (
records["date"]
.dt.to_period("W-SUN")
.astype(str)
)

weekly_summary = records.groupby("week").agg(
total_exercise_minutes=("exercise_minutes", "sum"),
exercise_record_count=("exercise_minutes", "count")
)

print(weekly_summary)

這段程式使用 W-SUN,將星期一至星期日視為一週,並以星期日作為週期結束日。

agg() 可以一次執行多種統計:

sum:計算運動時間總和。

count:計算具有有效運動時間數值的紀錄筆數。

在這個例子中,exercise_record_count 代表有效運動紀錄筆數,不一定等於有運動的天數。若同一天有多筆紀錄,也會被計算多次。

如果希望計算有運動的天數,就必須先將同一天的紀錄整理成單日資料,再計算運動時間大於零的日期數量。

六、實作:建立每週運動分析網頁

接下來,將每週運動分析整合至 Streamlit。

請先備份原本的 app.py,再將以下程式碼放入檔案中。這個版本會讀取既有的 health_records.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("每週運動分析")

讀取健康紀錄

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", "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["exercise_minutes"] = pd.to_numeric(
records["exercise_minutes"],
errors="coerce"
)

排除日期或運動時間無效的資料

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

排除負數運動時間

records = records[
records["exercise_minutes"] >= 0
].copy()

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

同一天可能存在多筆紀錄,因此先彙整成每日資料

daily_summary = records.groupby(
records["date"].dt.normalize()
).agg(
daily_exercise_minutes=("exercise_minutes", "sum")
).reset_index(names="date")

以星期一至星期日為一週

daily_summary["week"] = (
daily_summary["date"]
.dt.to_period("W-SUN")
.astype(str)
)

計算每週運動總時間、有效紀錄天數與運動天數

weekly_summary = daily_summary.groupby("week").agg(
total_exercise_minutes=(
"daily_exercise_minutes",
"sum"
),
recorded_days=(
"date",
"count"
),
exercise_days=(
"daily_exercise_minutes",
lambda values: (values > 0).sum()
)
).reset_index()

顯示最新一週的統計

latest_week = weekly_summary.iloc[-1]

col1, col2, col3 = st.columns(3)

with col1:
st.metric(
"最新一週運動總時間",
f"{latest_week['total_exercise_minutes']:.0f} 分鐘"
)

with col2:
st.metric(
"最新一週有運動的天數",
f"{latest_week['exercise_days']:.0f} 天"
)

with col3:
st.metric(
"最新一週有紀錄的天數",
f"{latest_week['recorded_days']:.0f} 天"
)

st.divider()

st.subheader("每週運動統計表")

st.dataframe(
weekly_summary.sort_values(
"week",
ascending=False
),
use_container_width=True,
hide_index=True
)

st.caption(
"每週以星期一至星期日計算;"
"未記錄的日期不會自動視為零分鐘。"
)

程式碼重點說明

  1. 先彙整每日紀錄

daily_summary = records.groupby(
records["date"].dt.normalize()
).agg(
daily_exercise_minutes=("exercise_minutes", "sum")
).reset_index(names="date")

如果同一天有兩筆運動紀錄,例如上午運動 20 分鐘、晚上運動 30 分鐘,系統會先加總成當日 50 分鐘,再進行每週分析。

這可以避免同一天被重複計算成多個運動日。

  1. 計算每週運動總時間

total_exercise_minutes=(
"daily_exercise_minutes",
"sum"
)

這會將同一週內每天的運動時間加總,得到該週的運動總分鐘數。

  1. 計算有運動的天數

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

這段程式會判斷每日運動時間是否大於零。如果大於零,就將該日計算為有運動的一天。

  1. 顯示最新一週的數據

latest_week = weekly_summary.iloc[-1]

由於資料在這個版本中依照週期由早到晚建立,因此最後一筆是最新一週。若未來需要更完整的日期篩選功能,也可以再增加週次選擇器。

七、實際執行與測試

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

python -m streamlit run app.py

接著,可以利用以下測試案例確認統計是否正確。

https://ithelp.ithome.com.tw/upload/images/20261028/201789034hymWd0eT6.png

測試時,建議先使用備份的 CSV 或另外建立測試檔案,避免修改原本的健康紀錄。

另外要注意,目前系統只統計有保存的日期。若某週只有兩天留下紀錄,系統就只能確認這兩天的情況,不能將其餘五天直接判定為沒有運動。

八、今日成果與學習心得

今天完成了每週運動分析的基本功能,學會使用 Pandas 的日期處理、groupby()、sum() 與 count() 等方法,將每日健康紀錄整理成每週統計結果。

透過這次實作,我了解到資料分析不只是進行加總,也需要先思考資料應該如何分組,以及每個統計數字實際代表什麼。

例如,「運動紀錄筆數」、「有紀錄的天數」與「有運動的天數」看起來相似,但實際上具有不同意義。如果沒有先定義清楚,就可能得到容易誤解的結果。

此外,每週運動總時間也可以作為後續趨勢分析的基礎。當系統持續累積資料,就能比較不同週的運動量,觀察自己的生活習慣是否有所變化。

九、下一步學習方向

第二十一天的主題是「健康資料視覺化——使用圖表呈現紀錄趨勢」。

下一階段將學習如何使用圖表呈現喝水量、睡眠時間與運動時間的變化,讓使用者不只看到統計數字,也能透過圖形更直觀地理解自己的健康習慣。


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

尚未有邦友留言

立即登入留言