iT邦幫忙

1

Day 17:資料修改與刪除——完善基本紀錄管理功能

  • 分享至 

  • xImage
  •  

一、前言

在第十六天的學習中,我已經完成歷史紀錄查詢功能,能夠讀取 CSV 檔案中的健康資料,並透過 Streamlit 將紀錄呈現在網頁上。此外,也學習了依照日期排序,以及使用日期範圍篩選資料,讓使用者可以更方便地查找過去的健康紀錄。

然而,實際使用系統時,仍然可能遇到資料輸入錯誤的情況。例如,使用者可能將喝水量輸入成 1,800 毫升,事後才發現實際紀錄應該是 1,500 毫升;也可能不小心重複新增同一天的資料,或是希望移除不再需要的測試紀錄。

如果系統只能新增與查詢資料,就無法直接修正這些問題。因此,今天將學習如何建立資料修改與刪除功能,讓使用者能夠管理已經保存的健康紀錄。

今天的目標是完成健康習慣追蹤系統的基本資料管理流程,讓使用者不只能新增與查看資料,也能在需要時修正或移除紀錄。

二、資料修改與刪除的設計需求

在開始撰寫程式之前,需要先確認修改與刪除功能應該如何運作。

https://ithelp.ithome.com.tw/upload/images/20261027/20178903rKlhrbLMpr.png

其中最重要的是,修改與刪除都必須針對正確的紀錄進行。如果系統沒有清楚辨識資料,就可能修改錯誤的內容,或意外刪除其他紀錄。

因此,本次練習會使用資料表中的列索引作為暫時的識別方式,讓使用者先選擇目標紀錄,再進行操作。這種方式適合目前的單機練習專案;若未來要建立更完整的系統,則建議為每筆紀錄增加唯一識別碼。

三、讀取並整理現有健康紀錄

在修改或刪除資料之前,必須先讀取原本保存的 CSV 檔案。

以下程式可以檢查檔案是否存在,並將資料讀取成 Pandas DataFrame。

import pandas as pd
from pathlib import Path

FILE_PATH = Path("health_records.csv")

def load_health_records():
if FILE_PATH.exists():
return pd.read_csv(FILE_PATH)

return pd.DataFrame(
    columns=[
        "date",
        "water_ml",
        "sleep_hours",
        "exercise_minutes",
        "vegetables_fruits",
        "notes"
    ]
)

這段程式建立了 load_health_records() 函式,負責讀取健康紀錄。

如果 CSV 檔案存在,就讀取其中的資料;如果檔案尚未建立,就回傳一個具有固定欄位的空資料表。

透過這個函式,後續的修改與刪除功能就不必重複撰寫讀取檔案的程式碼。

四、建立資料保存函式

當資料被修改或刪除後,需要將更新結果寫回 CSV,才能讓變更持續保存。

def save_health_records(records):
records.to_csv(
FILE_PATH,
index=False,
encoding="utf-8-sig"
)

這個函式會將目前的 DataFrame 寫入 health_records.csv。

其中,index=False 可以避免將 Pandas 的列索引額外寫入檔案,而 encoding="utf-8-sig" 則有助於讓中文內容在常見的試算表軟體中正確顯示。

需要注意的是,這個函式會以目前的資料覆寫原有 CSV,因此呼叫之前必須先確認 DataFrame 內容正確。

五、建立資料修改功能

修改功能的主要流程是:先選擇一筆紀錄,再將原有內容放入輸入元件,讓使用者修改後重新保存。

以下使用 Streamlit 建立一個簡單的修改介面:

import streamlit as st
import pandas as pd
from pathlib import Path

FILE_PATH = Path("health_records.csv")

def load_health_records():
if FILE_PATH.exists():
return pd.read_csv(FILE_PATH)

return pd.DataFrame(
    columns=[
        "date",
        "water_ml",
        "sleep_hours",
        "exercise_minutes",
        "vegetables_fruits",
        "notes"
    ]
)

def save_health_records(records):
records.to_csv(
FILE_PATH,
index=False,
encoding="utf-8-sig"
)

st.title("個人健康習慣追蹤系統")
st.header("修改健康紀錄")

records = load_health_records()

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

else:
selected_index = st.selectbox(
"請選擇要修改的紀錄",
options=records.index.tolist(),
format_func=lambda i: (
f"第 {i + 1} 筆|"
f"{records.loc[i, 'date']}"
)
)

selected_record = records.loc[selected_index]

with st.form("edit_record_form"):
    record_date = st.text_input(
        "紀錄日期(YYYY-MM-DD)",
        value=str(selected_record["date"])
    )

    water = st.number_input(
        "喝水量(毫升)",
        min_value=0,
        max_value=10000,
        value=int(selected_record["water_ml"])
    )

    sleep = st.number_input(
        "睡眠時間(小時)",
        min_value=0.0,
        max_value=24.0,
        value=float(selected_record["sleep_hours"]),
        step=0.5
    )

    exercise = st.number_input(
        "運動時間(分鐘)",
        min_value=0,
        max_value=1440,
        value=int(selected_record["exercise_minutes"])
    )

    vegetables_fruits = st.checkbox(
        "今天有攝取蔬菜水果",
        value=str(
            selected_record["vegetables_fruits"]
        ).lower() == "true"
    )

    notes = st.text_area(
        "備註",
        value=str(selected_record["notes"])
        if pd.notna(selected_record["notes"])
        else ""
    )

    submitted = st.form_submit_button("保存修改")

if submitted:
    try:
        from datetime import date

        date.fromisoformat(record_date)

        records.loc[selected_index, "date"] = record_date
        records.loc[selected_index, "water_ml"] = water
        records.loc[selected_index, "sleep_hours"] = sleep
        records.loc[selected_index, "exercise_minutes"] = exercise
        records.loc[
            selected_index,
            "vegetables_fruits"
        ] = vegetables_fruits
        records.loc[selected_index, "notes"] = notes.strip()

        save_health_records(records)

        st.success("健康紀錄已成功修改!")
        st.dataframe(
            records,
            use_container_width=True
        )

    except ValueError:
        st.error("日期格式錯誤,請輸入有效的 YYYY-MM-DD 日期。")

    except OSError:
        st.error("資料保存失敗,請檢查檔案與執行權限。")

這段程式示範了幾個重要概念:

st.selectbox():讓使用者選擇要修改的紀錄。

records.loc[]:根據指定的列索引存取或修改資料。

st.form():將多個輸入欄位組成一份表單。

date.fromisoformat():檢查日期是否符合有效的 ISO 日期格式。

save_health_records():將修改後的資料重新保存至 CSV。

本例的日期欄位使用文字輸入框,方便示範日期驗證;未來可以改用 st.date_input(),讓使用者透過日曆選擇日期。

另外,這段程式假設 CSV 的欄位名稱與資料格式都符合先前的設計。若檔案中存在空白數字、格式錯誤或其他不完整資料,還需要增加更完整的資料驗證處理。

六、建立資料刪除功能

除了修改資料,系統也需要讓使用者可以刪除不再需要的紀錄。

刪除資料的基本流程如下:

讀取現有健康紀錄。

選擇要刪除的紀錄。

顯示刪除確認按鈕。

使用者確認後,從 DataFrame 移除指定資料。

將剩餘資料重新保存至 CSV。

顯示操作結果。

以下是簡單的刪除功能範例:

import streamlit as st
import pandas as pd
from pathlib import Path

FILE_PATH = Path("health_records.csv")

def load_health_records():
if FILE_PATH.exists():
return pd.read_csv(FILE_PATH)

return pd.DataFrame()

def save_health_records(records):
records.to_csv(
FILE_PATH,
index=False,
encoding="utf-8-sig"
)

st.title("個人健康習慣追蹤系統")
st.header("刪除健康紀錄")

records = load_health_records()

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

else:
selected_index = st.selectbox(
"請選擇要刪除的紀錄",
options=records.index.tolist(),
format_func=lambda i: (
f"第 {i + 1} 筆|"
f"{records.loc[i, 'date']}"
),
key="delete_record"
)

st.warning("刪除後,該筆紀錄將從 CSV 檔案中移除。")

if st.button("確認刪除"):
    updated_records = records.drop(
        index=selected_index
    ).reset_index(drop=True)

    try:
        save_health_records(updated_records)

        st.success("健康紀錄已成功刪除!")

        if updated_records.empty:
            st.info("目前已沒有健康紀錄。")
        else:
            st.dataframe(
                updated_records,
                use_container_width=True
            )

    except OSError:
        st.error("資料刪除失敗,請檢查檔案與執行權限。")

其中,drop() 可以移除指定索引的資料列,而 reset_index(drop=True) 則會重新整理索引,讓剩餘資料從零開始排列。

本例以按鈕作為簡化的刪除確認機制。若未來要提供更安全的操作體驗,可以加入額外的二次確認流程,降低誤刪資料的風險。

七、測試資料修改與刪除功能

完成程式後,需要透過實際操作確認資料是否能正確更新。

建議進行以下測試:
https://ithelp.ithome.com.tw/upload/images/20261027/201789036tGH3Kj4zn.png
測試時,除了觀察網頁上的訊息,也應該重新讀取 health_records.csv,確認資料確實被修改或刪除。

此外,今天的修改與刪除範例是分開展示的功能片段。如果要整合進同一個 app.py,應將它們合併成同一份程式,並避免重複建立相同的函式或標題。

八、今日實作需要注意的地方

今天使用 DataFrame 的列索引來選擇紀錄,主要是為了讓初學者理解資料修改與刪除的基本原理。

不過,列索引並不是穩定的資料識別方式。當資料重新排序、篩選或重新載入時,索引可能改變,未來也可能造成選錯紀錄的問題。

因此,較完整的系統可以在每筆健康紀錄中加入唯一識別碼,例如 record_id,並使用這個識別碼來定位資料。

另外,直接覆寫 CSV 雖然容易實作,但如果寫入過程發生問題,可能影響原有檔案。因此,正式系統還需要考慮備份、錯誤處理與資料復原機制。

這些限制也說明了,資料管理功能不只是讓按鈕能夠運作,還需要確保操作對象正確、資料能持續保存,以及使用者不會因為誤操作而失去重要紀錄。

九、今日成果與學習心得

透過今天的學習,我進一步完成健康習慣追蹤系統的資料管理概念,理解如何使用 Pandas 修改指定資料列、移除不需要的紀錄,並將更新後的結果重新寫入 CSV。

目前系統已經逐步具備新增、查詢、修改與刪除四項基本資料操作能力,讓使用者可以管理自己的健康紀錄,而不必每次都直接開啟 CSV 檔案修改內容。

同時,我也發現資料操作必須搭配輸入驗證、操作確認與錯誤處理,才能降低錯誤資料與誤刪紀錄造成的影響。

十、下一步學習方向

Day 18 將學習「個人健康儀表板——呈現每日健康概況」。

接下來會把目前已經保存的健康資料轉換成更容易理解的資訊,運用 Streamlit 的數值指標、摘要文字與資料整理功能,呈現喝水量、睡眠時間、運動時間與其他健康習慣的概況,讓使用者能夠更快速地了解自己的生活紀錄,逐步完成健康習慣追蹤系統的第一階段展示功能。


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

尚未有邦友留言

立即登入留言