本日主題:選一個題目,動手做出自動化小作品 預計時間:2 小時(動手日) 對應主教材:第 6.3 節


一、今日學習目標

  • [ ] 選定一個自動化題目
  • [ ] 完成程式主要功能
  • [ ] 學會完整的報表處理流程(含 merge、pivot_table)

二、選一個題目(擇一)

題目 A:每日報表彙總(推薦,最實用)

讀取一份(或多份)Excel/CSV,依某欄位彙總,輸出報表。

import pandas as pd
from datetime import datetime

# ---- 設定區 ----
INPUT_FILE = "daily_data.xlsx"
OUTPUT_DIR = "./"
GROUP_COL = "部門"

# ---- 主程式 ----
df = pd.read_excel(INPUT_FILE)

# 基本彙總
summary = df.groupby(GROUP_COL).agg(
    總數量=("數量", "sum"),
    總金額=("金額", "sum"),
    平均金額=("金額", "mean"),
    筆數=("金額", "count"),
).reset_index()

# 加上佔比欄位
summary["金額佔比(%)"] = (summary["總金額"] / summary["總金額"].sum() * 100).round(1)

# 排序
summary = summary.sort_values("總金額", ascending=False)

# 輸出
stamp = datetime.now().strftime("%Y%m%d")
output_path = f"{OUTPUT_DIR}彙總報表_{stamp}.xlsx"
summary.to_excel(output_path, index=False)
print(f"報表已產生:{output_path},共 {len(summary)} 個{GROUP_COL}")

進階版:多欄位交叉分析

# 用 pivot_table 做「部門 × 月份」交叉分析
df["月份"] = pd.to_datetime(df["日期"]).dt.strftime("%Y-%m")

pivot = df.pivot_table(
    values="金額",
    index="部門",
    columns="月份",
    aggfunc="sum",
    fill_value=0,
    margins=True,          # 加總計列
    margins_name="合計"
)

pivot.to_excel(f"交叉分析_{stamp}.xlsx")
print("交叉分析表已產生")

進階版:合併員工資料再彙總

# 報表只有員工編號,需要 merge 員工姓名
employees = pd.read_excel("employees.xlsx")  # 有:員工編號, 姓名, 部門
sales = pd.read_excel("sales.xlsx")          # 有:員工編號, 日期, 金額

# 合併
merged = pd.merge(sales, employees, on="員工編號", how="left")

# 處理缺值(有些員工可能已離職,查不到)
merged["姓名"] = merged["姓名"].fillna("未知員工")
merged["部門"] = merged["部門"].fillna("未分類")

# 依部門彙總
dept_summary = merged.groupby("部門").agg(
    總金額=("金額", "sum"),
    人數=("員工編號", "nunique"),
).reset_index()

dept_summary.to_excel("部門業績.xlsx", index=False)

題目 B:批次整理檔案

把一堆檔案依規則重新命名/分類。

import os
import shutil

folder = "./scans"
for i, fn in enumerate(sorted(os.listdir(folder)), start=1):
    if fn.lower().endswith(".pdf"):
        new = f"維修單_{i:03d}.pdf"
        os.rename(os.path.join(folder, fn), os.path.join(folder, new))
print("整理完成")

進階版:依檔案類型分資料夾

import os
import shutil

source = "./downloads"
type_map = {
    ".pdf": "PDF文件",
    ".xlsx": "Excel檔案",
    ".xls": "Excel檔案",
    ".docx": "Word文件",
    ".pptx": "簡報檔案",
    ".jpg": "圖片",
    ".png": "圖片",
    ".csv": "資料檔",
}

for fn in os.listdir(source):
    filepath = os.path.join(source, fn)
    if not os.path.isfile(filepath):
        continue

    ext = os.path.splitext(fn)[1].lower()
    folder_name = type_map.get(ext, "其他")
    dest_folder = os.path.join(source, folder_name)
    os.makedirs(dest_folder, exist_ok=True)
    shutil.move(filepath, os.path.join(dest_folder, fn))

print("檔案分類完成!")

題目 C:多檔合併

把資料夾內多個 Excel 合併成一個。

import pandas as pd
import glob

files = glob.glob("./reports/*.xlsx")
dfs = [pd.read_excel(f) for f in files]
all_df = pd.concat(dfs, ignore_index=True)

# 去重複
all_df = all_df.drop_duplicates()

# 排序
all_df = all_df.sort_values("日期")

all_df.to_excel("合併結果.xlsx", index=False)
print(f"合併了 {len(files)} 個檔案,共 {len(all_df)} 筆資料")

進階版:合併時記錄來源檔名

import pandas as pd
import glob
import os

files = glob.glob("./reports/*.xlsx")
dfs = []
for f in files:
    df = pd.read_excel(f)
    df["來源檔案"] = os.path.basename(f)
    dfs.append(df)

all_df = pd.concat(dfs, ignore_index=True)
all_df.to_excel("合併結果_含來源.xlsx", index=False)
print(f"合併了 {len(files)} 個檔案")

題目 D:自動寄送報表(進階挑戰)

結合題目 A + smtplib,產生報表後自動寄信通知。

import pandas as pd
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from datetime import datetime

# 1. 產生報表
df = pd.read_excel("daily_data.xlsx")
summary = df.groupby("部門").agg(
    總金額=("金額", "sum"),
).reset_index()

stamp = datetime.now().strftime("%Y%m%d")
report_file = f"彙總報表_{stamp}.xlsx"
summary.to_excel(report_file, index=False)
print(f"報表產生完成:{report_file}")

# 2. 寄信
msg = MIMEMultipart()
msg["From"] = "it@company.com"
msg["To"] = "manager@company.com"
msg["Subject"] = f"每日彙總報表 — {stamp}"

body = f"主管您好,\n\n今日彙總報表已產生,共 {len(summary)} 個部門。\n請參閱附件。\n\nIT 部門"
msg.attach(MIMEText(body, "plain", "utf-8"))

with open(report_file, "rb") as f:
    part = MIMEBase("application", "octet-stream")
    part.set_payload(f.read())
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", f"attachment; filename={report_file}")
    msg.attach(part)

with smtplib.SMTP("smtp.company.com", 587) as server:
    server.starttls()
    server.login("it@company.com", "password")
    server.send_message(msg)

print("報表已寄出!")

三、今日實作任務(產出)

  • [ ] 選定題目(建議 A)
  • [ ] 準備測試資料(自己做幾個 Excel/檔案)
  • [ ] 把程式跑通,能產出正確結果
  • [ ] 記錄:你解決的問題是什麼、原本要花多久
  • [ ] (進階)嘗試加入 merge 或 pivot_table

遇到錯誤是正常的:把錯誤訊息看懂、上網查、修正——這正是工作中寫程式的日常。


四、今日練習

Q1. pandas 的 pd.merge() 中,how="left" 代表什麼意思?

Q2.pivot_table 做「產品 × 月份」的金額加總,怎麼寫?

Q3. 用 os 模組建立一個不存在的資料夾(如果已存在也不報錯),怎麼寫?

參考解答 **A1.** `how="left"` 代表 LEFT JOIN,保留左邊 DataFrame 的所有資料列,右邊沒有匹配到的欄位填 NaN。等同 SQL 的 `LEFT JOIN`。 **A2.**
pivot = df.pivot_table(
    values="金額",
    index="產品",
    columns="月份",
    aggfunc="sum",
    fill_value=0
)
**A3.**
import os
os.makedirs("./output/2024", exist_ok=True)
`exist_ok=True` 代表資料夾已存在時不會報錯。

五、常見誤解

誤解 正確觀念
「程式第一次就該跑對」 除錯是常態,工程師大量時間花在 debug
「作品要很複雜才能拿出來講」 一個 20 行的腳本只要真的解決問題、有量化成果,就是好作品
「測試資料隨便做就好」 測試資料要包含邊界狀況(空值、重複、格式不一致)才能驗證程式的穩健性

六、今日檢核

  • [ ] 我選定了題目並備好測試資料
  • [ ] 我的程式能正確跑出結果
  • [ ] 我清楚這個作品解決什麼問題
  • [ ] 我記錄了「原本手動花多久 vs 自動化後花多久」

七、延伸閱讀


⬅️ 上一天:Day 3 | 🏠 本週總覽 | ➡️ 下一天:Day 5 — 自動化作品(下)