本日主題:用 Python 處理 Excel/CSV 資料 預計時間:2 小時(動手日) 對應主教材:第 6.3 節


一、今日學習目標

  • [ ] 安裝 Python 與 pandas
  • [ ] 了解 Python 虛擬環境(venv)
  • [ ] 會用 pandas 讀寫 Excel/CSV
  • [ ] 會做基本的彙總(groupby、pivot_table)
  • [ ] 會用 merge 合併資料、fillna 處理缺值、apply 自訂運算
  • [ ] 認識 os/shutil 基本檔案操作

二、教材內容

2.1 安裝

  • 到 python.org 安裝 Python(勾選 Add to PATH)。
  • 安裝 pandas 與 Excel 支援:
pip install pandas openpyxl

2.2 Python 虛擬環境(venv)

為什麼需要虛擬環境?

不同專案可能用到不同版本的套件。虛擬環境讓每個專案有自己獨立的套件空間,不會互相干擾。

建立與使用

# 建立虛擬環境(在專案資料夾內執行)
python -m venv myenv

# 啟動虛擬環境
# Windows:
myenv\Scripts\activate
# macOS/Linux:
source myenv/bin/activate

# 啟動後,終端機會出現 (myenv) 前綴

# 安裝套件(只裝在這個虛擬環境裡)
pip install pandas openpyxl

# 匯出套件清單(方便別人重建環境)
pip freeze > requirements.txt

# 別人要重建環境時
pip install -r requirements.txt

# 離開虛擬環境
deactivate

面試加分小知識:提到你會用 venv 管理專案環境,代表你有「工程化」的觀念,而不只是會寫腳本。

2.3 Python 最基礎語法(夠用就好)

# 變數與印出
name = "王小明"
print("你好", name)

# 字串格式化(f-string,Python 3.6+)
age = 25
print(f"{name} 今年 {age} 歲")

# 清單(list)
fruits = ["蘋果", "香蕉", "橘子"]
fruits.append("葡萄")
print(len(fruits))  # 4

# 字典(dict)
server = {"name": "DC01", "ip": "192.168.1.10", "role": "AD"}
print(server["ip"])

# 迴圈
for i in range(3):
    print(i)         # 0 1 2

for fruit in fruits:
    print(fruit)

# 條件
salary = 52000
if salary > 50000:
    print("高於五萬")
elif salary > 40000:
    print("四萬到五萬之間")
else:
    print("四萬以下")

# 函式
def add(a, b):
    return a + b
print(add(2, 3))     # 5

# 例外處理
try:
    result = 10 / 0
except ZeroDivisionError:
    print("不能除以零!")

2.4 pandas 讀寫資料

import pandas as pd

# 讀檔
df = pd.read_excel("data.xlsx")    # 或 pd.read_csv("data.csv")

# 讀取特定工作表
df = pd.read_excel("data.xlsx", sheet_name="Sheet2")

# 看資料
print(df.head())       # 前 5 列
print(df.tail(3))      # 後 3 列
print(df.columns)      # 欄位名
print(df.shape)        # (列數, 欄數)
print(df.dtypes)       # 每個欄位的資料型別
print(df.describe())   # 數值欄位的統計摘要
print(df.info())       # 資料表整體資訊

# 篩選
it = df[df["部門"] == "IT"]
high_salary = df[df["月薪"] > 50000]

# 多條件篩選
senior_it = df[(df["部門"] == "IT") & (df["年資"] > 3)]

# 新增欄位
df["年薪"] = df["月薪"] * 12

# 排序
df_sorted = df.sort_values("月薪", ascending=False)

# 去除重複
df_unique = df.drop_duplicates(subset=["員工編號"])

# 寫出
df.to_excel("output.xlsx", index=False)
df.to_csv("output.csv", index=False, encoding="utf-8-sig")

2.5 彙總(groupby)

# 依部門加總數量
summary = df.groupby("部門")["數量"].sum().reset_index()

# 多個統計
summary = df.groupby("部門").agg(
    總數量=("數量", "sum"),
    平均金額=("金額", "mean"),
    最高金額=("金額", "max"),
    筆數=("金額", "count"),
).reset_index()

pandas 的 groupby 概念跟 SQL 的 GROUP BY 一樣,上週學過會很好懂。

2.6 pivot_table(樞紐分析表)

# 類似 Excel 的樞紐分析表
pivot = df.pivot_table(
    values="金額",           # 要彙總的數值
    index="部門",            # 列標籤
    columns="月份",          # 欄標籤
    aggfunc="sum",           # 彙總方式
    fill_value=0             # 空值填 0
)

print(pivot)
pivot.to_excel("樞紐分析.xlsx")

常見誤解:「pivot_table 跟 groupby 一樣」—— 不完全一樣。pivot_table 可以產生二維的交叉表(像 Excel 的樞紐分析),groupby 產生的是一維的分組彙總。要做「部門 × 月份」的交叉分析,pivot_table 更直覺。

2.7 merge 合併資料(類似 SQL JOIN)

# 假設有兩張表
# employees: 員工編號, 姓名, 部門代碼
# departments: 部門代碼, 部門名稱

merged = pd.merge(
    employees,
    departments,
    on="部門代碼",       # 共同的 key 欄位
    how="left"           # left / right / inner / outer
)

# 如果 key 欄位名稱不同
merged = pd.merge(
    employees,
    departments,
    left_on="dept_id",
    right_on="department_id",
    how="inner"
)
how 參數 對應 SQL 說明
inner INNER JOIN 只保留兩邊都有的
left LEFT JOIN 保留左表全部
right RIGHT JOIN 保留右表全部
outer FULL OUTER JOIN 兩邊都保留

2.8 fillna 處理缺值

# 查看缺值
print(df.isnull().sum())

# 用固定值填補
df["備註"] = df["備註"].fillna("無")

# 用前一個值填補(適合時間序列)
df["溫度"] = df["溫度"].fillna(method="ffill")

# 用平均值填補
df["薪水"] = df["薪水"].fillna(df["薪水"].mean())

# 直接刪除有缺值的列
df_clean = df.dropna()

# 刪除特定欄位有缺值的列
df_clean = df.dropna(subset=["姓名", "部門"])

2.9 apply 自訂運算

# 對單一欄位套用函式
df["薪資等級"] = df["月薪"].apply(lambda x: "高" if x > 60000 else "一般")

# 自訂函式(複雜邏輯用)
def classify_salary(salary):
    if salary >= 80000:
        return "A級"
    elif salary >= 60000:
        return "B級"
    elif salary >= 40000:
        return "C級"
    else:
        return "D級"

df["薪資等級"] = df["月薪"].apply(classify_salary)

# 對整列(row)操作
df["全名"] = df.apply(lambda row: row["姓"] + row["名"], axis=1)

2.10 os / shutil 檔案操作

import os
import shutil

# ---- os 模組 ----
# 列出資料夾內容
files = os.listdir("./reports")
print(files)

# 檢查檔案/資料夾是否存在
print(os.path.exists("data.xlsx"))     # True/False
print(os.path.isfile("data.xlsx"))     # 是否為檔案
print(os.path.isdir("./reports"))      # 是否為資料夾

# 取得檔案資訊
size = os.path.getsize("data.xlsx")    # 檔案大小(bytes)
print(f"檔案大小:{size / 1024:.1f} KB")

# 建立資料夾
os.makedirs("./output/2024", exist_ok=True)

# 重新命名
os.rename("old_name.txt", "new_name.txt")

# 刪除檔案
os.remove("temp.txt")

# 取得當前工作目錄
print(os.getcwd())

# 組合路徑(跨平台安全)
path = os.path.join("reports", "2024", "jan.xlsx")

# ---- shutil 模組 ----
# 複製檔案
shutil.copy2("source.xlsx", "backup/source_backup.xlsx")

# 複製整個資料夾
shutil.copytree("./reports", "./reports_backup")

# 搬移檔案
shutil.move("temp.xlsx", "./archive/temp.xlsx")

# 刪除整個資料夾(小心使用!)
shutil.rmtree("./old_reports")

2.11 實用範例:自動依日期整理檔案

import os
import shutil
from datetime import datetime

source = "./downloads"
for filename in os.listdir(source):
    filepath = os.path.join(source, filename)
    if os.path.isfile(filepath):
        # 取得檔案修改日期
        mtime = os.path.getmtime(filepath)
        date_folder = datetime.fromtimestamp(mtime).strftime("%Y-%m")

        # 建立日期資料夾
        dest_folder = os.path.join("./archive", date_folder)
        os.makedirs(dest_folder, exist_ok=True)

        # 搬移檔案
        shutil.move(filepath, os.path.join(dest_folder, filename))

print("檔案整理完成!")

三、今日實作任務(動手)

  1. 建立一個 Python 虛擬環境,安裝 pandas 與 openpyxl。
  2. 自己用 Excel 做一個小檔(如:日期、產品、數量、金額,20 列)。
  3. 用 pandas 讀進來。
  4. 依「產品」彙總總數量與總金額。
  5. 用 pivot_table 做「產品 × 月份」的交叉分析。
  6. 輸出成新的 Excel。

四、今日練習

Q1. 用 pandas 讀一個 CSV 檔的指令?

Q2. 依「部門」算每組「薪水」平均,怎麼寫?

Q3. pandas 的 groupby 對應 SQL 的什麼語法?

Q4. 如何用 pandas 合併兩張表(類似 SQL 的 LEFT JOIN)?

Q5. 資料中有缺值(NaN),想用 0 填補,怎麼寫?

Q6. 想對「月薪」欄位做自訂分類(>60000 標「高」,否則標「一般」),怎麼寫?

參考解答
# A1
df = pd.read_csv("data.csv")

# A2
df.groupby("部門")["薪水"].mean()
# 或完整版本:
avg_salary = df.groupby("部門")["薪水"].mean().reset_index()
avg_salary.columns = ["部門", "平均薪水"]

# A4
merged = pd.merge(left_df, right_df, on="共同欄位", how="left")

# A5
df["欄位名"] = df["欄位名"].fillna(0)
# 或全部欄位一起填:
df = df.fillna(0)

# A6
df["薪資等級"] = df["月薪"].apply(lambda x: "高" if x > 60000 else "一般")
**A3**:對應 SQL 的 `GROUP BY` + 聚合函數。例如 `df.groupby("部門")["薪水"].mean()` 等同於 `SELECT 部門, AVG(薪水) FROM table GROUP BY 部門`。

五、常見誤解

誤解 正確觀念
「pandas 只能處理小資料」 pandas 處理幾十萬列通常沒問題;超過百萬列才需考慮 Dask 等工具
「merge 跟 concat 一樣」 merge 是依 key 欄位合併(類似 JOIN);concat 是上下堆疊或左右拼接
「fillna 會改變原始資料」 預設不會(回傳新的 DataFrame),除非加 inplace=True
「venv 很麻煩可以不用」 養成好習慣很重要,避免套件版本衝突導致專案無法執行

六、今日檢核

  • [ ] 我裝好了 Python 與 pandas
  • [ ] 我建立過 Python 虛擬環境
  • [ ] 我會用 pandas 讀寫 Excel/CSV
  • [ ] 我會用 groupby 做彙總
  • [ ] 我會用 pivot_table 做交叉分析
  • [ ] 我會用 merge 合併兩張表
  • [ ] 我知道 os/shutil 的基本檔案操作

七、延伸閱讀


⬅️ 上一天:Day 1 | 🏠 本週總覽 | ➡️ 下一天:Day 3 — 自動化判斷思維