跳到主要內容

DS 102 · 模組 1

資料整理:從原始表格到分析資料

把「可以讀取的資料」整理成「可被信任、可重現、可追溯的分析資料」的第一步。

開始第一節
已完成 0 / 5
同步我的進度加入梯次後,進度可跨裝置保存,講師也能看到

資訊

加入後,你已完成的模組編號會連同「學員代碼」傳送給講師,講師可以看到你的進度。請使用講師提供的代碼,不要輸入姓名、公司機密或其他個資。進度資料與該梯次的其他資料一起保存。

學習目標

  • 定義資料粒度;區分格式整理與語意品質;建立 raw/staging/clean 三層觀念
  • 安全轉換型態、文字正規化、日期解析,並保留轉換失敗供人工檢查
  • 區分缺失與零值;辨識重複列與重複實體;定義候選鍵
  • 使用 melt/pivot 概念;理解 tidy data;避免 reshape 時意外聚合
  • 以可重現步驟完成混亂資料的 staging-to-clean 轉換

粒度與整理層次

25 分鐘

資料整理不是「修漂亮」

學習目標:定義資料粒度;區分格式整理與語意品質;建立 raw/staging/clean 三層觀念。

很多人把「資料整理」想成把欄位排整齊、把顏色改一改。但真正決定分析能不能被信任的,是兩個更根本的問題:這份資料的「粒度」是什麼?每一步轉換是不是可以重來一次、得到一樣的結果?

只做格式整理

把欄位名稱改成看起來一致、把數字加上千分位逗號、把空白補成看起來完整;資料看起來乾淨,但沒有人確認過每一列真正代表什麼。

同時檢查語意品質

粒度
先確認一列代表什麼:一家公司一年一列,還是一個據點一個月一列?
層次
原始檔案保持不動(raw),轉換過程留在 staging,只有驗證過的結果才進 clean。
可重現
每一步轉換都寫成程式碼或紀錄,重新執行一次應該得到相同結果。
Raw、Staging、Clean 三層的分工
層次內容規則
Raw(原始)直接從來源取得的檔案,完全不修改永遠保留一份,作為追溯的起點
Staging(處理中)型態轉換、清理、reshape 的中間結果可以重新產生;不當作最終結論使用
Clean(乾淨)已通過驗證規則的分析資料只有這一層可以直接拿來計算指標
  • 欄位名稱出現「total」或「avg」,卻沒有人說明是跨哪個維度加總的
  • 同一個 company_id 在原始檔案中的列數忽多忽少,卻沒有人解釋原因
  • 合併兩個來源之後列數改變了,卻沒有人回頭核對預期列數
本課程使用的資料(先讀入,不做任何修改)
import pandas as pd

# raw:完全不修改,先看清楚長什麼樣子
raw = pd.read_csv("DS102_Company_Year_Practice.csv")
raw.shape
raw.head()
一個常見的粒度錯誤(模擬情境)

某分析者把一家公司四個季度的用電量加總成一個年度數字,用來跟另一份「年度」資料比較。兩份資料看起來都是「一年一個數字」,但其中一份其實是四個季度加總而成,另一份是直接量測的年度數據,計算方式不同,直接比較會得出錯誤結論,而程式本身不會報錯。

注意

資料安全提醒:本課程所有練習一律使用課程提供的模擬資料檔。未經授權,請勿把機構或公司的機密資料、客戶資料或個人資料貼到 AI 工具或上傳到練習環境。

資訊

Human、AI、Human:人決定粒度與驗證規則,AI 可以協助寫轉換程式碼,人負責檢查每一步是否可重現、結果是否可信。
自我檢測

一份資料的每一列代表「一家公司一個年度」的紀錄。下列哪一種做法最能保護這個粒度?

一份資料的每一列代表「一家公司一個年度」的紀錄。下列哪一種做法最能保護這個粒度?

重點: 資料整理的第一步不是把它變好看,而是先確定粒度、分好層次,讓每一步都可以重來一次。

型態、文字與日期

35 分鐘

資料型態、文字與日期清理

學習目標:安全轉換型態、文字正規化、日期解析,並保留轉換失敗供人工檢查。

原始資料常常把數字存成文字(例如含千分位逗號的 "18,900")、把名稱多打了一個空白、或是日期格式不一致。這些問題如果直接忽略,程式可能不會報錯,但計算出來的結果會悄悄地錯。

三種常見問題與正確處理方式
問題錯誤處理正確處理
數字被存成文字(例如 "18,900")直接當成字串比較大小,或忽略錯誤先移除千分位逗號等格式符號,再轉成數值型態
文字前後有空白(例如 "Riverbank Foods Co. ")直接拿來分組或比對,結果悄悄變成兩個不同的群組用 .strip() 去除空白後再使用
日期格式不一致或無法解析猜一個日期填進去,或整列刪除保留原始值並加上錯誤旗標,交由人工檢查
安全的型態轉換:先看問題,再處理
# 千分位逗號會讓 pandas 把整欄讀成文字(dtype = object)
raw["revenue_m_ntd"].dtype

# 移除逗號後才能轉成數值
clean_revenue = raw["revenue_m_ntd"].astype(str).str.replace(",", "", regex=False).astype(float)
日期解析失敗時保留線索,不猜測填值
parsed = pd.to_datetime(raw["reported_at"], format="%Y-%m-%d", errors="coerce")
raw["reported_at_error"] = parsed.isna() & raw["reported_at"].notna()
raw[raw["reported_at_error"]][["company_id", "year", "reported_at"]]
  • 先用 df.dtypes 看每一欄目前是什麼型態,再決定要不要轉換
  • 轉換數字前,先確認唯一可能的格式符號(例如千分位逗號),不要用「盡量轉成功」的方式硬轉
  • 文字欄位一律先 .strip(),再拿來分組、篩選或當作 join 鍵
  • 無法解析的日期要保留原始值與錯誤旗標,絕不能猜一個日期填入
空白字元造成的隱藏分裂(模擬情境)

同一家公司在不同月份的檔案中,有時被打成 "Riverbank Foods Co.",有時被打成 "Riverbank Foods Co. "(尾端多一個空白)。如果直接用公司名稱分組,程式會把它們當成兩個不同的公司,年度加總因此被悄悄拆成兩筆比較小的數字,而不會有任何錯誤訊息。

注意

程式能執行,不代表資料是對的。errors="coerce" 會把無法解析的日期悄悄變成缺失值;如果不另外檢查,你可能完全不知道有一列被靜默地清空了。
自我檢測

某一列的 reported_at 值完全無法解析成日期。下列哪一種做法最正確?

某一列的 reported_at 值完全無法解析成日期。下列哪一種做法最正確?

重點: 型態轉換要先看清楚問題,處理失敗要留下痕跡,而不是讓它悄悄消失。

Missing、Duplicate 與鍵

40 分鐘

Missing、Duplicate 與唯一鍵

學習目標:區分缺失與零值;辨識重複列與重複實體;定義候選鍵。

「空白」與「0」是兩種完全不同的資訊:0 代表「已經確認、數值就是零」,空白代表「不知道」,可能是未揭露、未蒐集,或系統本身就沒有記到。把兩者混在一起處理,會讓後續的平均、加總全部失真。

Missing(缺失)

  • 代表未知或未蒐集
  • 不能直接當作 0
  • 應先確認產生機制,再決定是否填補

Zero(實際為零)

  • 是已確認的真實數值
  • 可以直接用於計算
  • 如果被誤填為缺失,反而會低估真實狀況
0 與空白的真實差異(模擬情境)

某據點 2023 年的 water_usage_m3 是 0,代表該年度確實沒有用水記錄且已核實;另一個據點同一欄位是空白,代表尚未回報。如果把空白也當成 0 計算平均用水量,會把「不知道」錯當成「確定沒有用」,讓平均值被低估。

找出缺失值,並確認候選鍵是否唯一
raw.isna().sum()

raw["company_year_key"] = raw["company_id"].astype(str) + "-" + raw["year"].astype(str)
raw["company_year_key"].duplicated().sum()  # 大於 0 代表有重複的候選鍵
重複列 vs 重複實體
情況意思處理方向
完全相同的兩列很可能是同一筆紀錄被輸入或匯入了兩次先確認粒度規則,若真的重複才刪除,並記錄理由
候選鍵相同但其他欄位不同可能是同一個實體被記錄了兩個不同版本(例如修正前後)查來源與時間戳記,判斷哪一筆正確,不能只憑順序刪除

資訊

候選鍵(candidate key)是「能唯一識別一列」的最小欄位組合。company_id 加 year 能唯一識別一列,只有 company_id 不能,因為同一家公司會有多個年度。

注意

「刪除重複列」不是安全的預設動作。先確認資料的粒度是否本來就允許多列(例如同一份月資料每天更新一次),再判斷是否真的重複。
自我檢測

water_usage_m3 這一欄有一列是空白。下列哪一種做法最正確?

water_usage_m3 這一欄有一列是空白。下列哪一種做法最正確?

重點: 缺失不是零;重複列不能不檢查就刪除;候選鍵要能唯一識別每一列。

Reshape

35 分鐘

Wide / Long 與 reshape

學習目標:使用 melt/pivot 概念;理解 tidy data;避免 reshape 時意外聚合。

同一份資料可以用「寬格式」或「長格式」儲存。寬格式(wide)把每個月份分別放在一欄,人眼看起來很直覺;長格式(tidy / long)把「月份」本身變成一欄資料,程式處理與繪圖通常更方便。

寬格式 vs 長格式(示意,非課程實際數值)
格式結構適合
Wide(寬)每個月份各佔一欄:Jan, Feb, Mar, ...人工閱讀、報表呈現
Long / Tidy(長)一欄 month、一欄 value,一個月一列程式篩選、分組、繪圖與時間序列分析
melt:把寬格式的月份欄位收成兩欄
site_wide = pd.read_csv("DS102_Site_Monthly_Wide.csv")
month_cols = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

site_long = site_wide.melt(
    id_vars=["company_id", "site_id", "year", "metric_name"],
    value_vars=month_cols,
    var_name="month",
    value_name="value"
)
site_long.shape  # 4 列 x 12 個月 = 48 列
melt 時漏掉 site_id 的後果(模擬情境)

如果在 melt 時只保留 company_id,漏掉 site_id,兩個據點的月資料會被攤在同一個 company_id 底下,看起來像同一個據點重複出現兩次。後續如果用 groupby("company_id", "month") 加總,兩個據點的用電量會被加在一起,卻沒有任何錯誤訊息提醒你資料被混用了。

注意

reshape 最常見的錯誤是「意外聚合」:如果 melt 或 pivot 時漏掉某個識別欄位(例如漏掉 site_id,只留 company_id),原本不同據點的資料會被悄悄混在一起,之後的加總或平均就會失真,卻不會有任何錯誤訊息。
  • melt 之前,先列出所有應該保留的識別欄位(id_vars),不要只憑記憶
  • melt 之後,檢查列數是否符合預期(原始列數 × 被收起來的欄位數)
  • pivot 方向相反:把長格式的一欄值展開成多欄,通常用在做報表或交叉分析時
  • 看到列數比預期少很多,先懷疑是不是識別欄位沒填齊,而不是急著解讀成「資料變少了」
自我檢測

一份資料把 Jan 到 Dec 十二個月分別放在十二個欄位。要轉成 month 與 value 兩欄,最接近哪一種操作?

一份資料把 Jan 到 Dec 十二個月分別放在十二個欄位。要轉成 month 與 value 兩欄,最接近哪一種操作?

重點: Reshape 改變的是資料的「形狀」,不是內容;漏掉識別欄位會讓資料在你沒注意到的情況下被混在一起。

Guided Lab

45 分鐘

Guided Lab:整理一份混亂的永續資料

學習目標:在教師引導下,以可重現步驟完成一份混亂資料的 staging-to-clean 轉換。

資訊

Lab 問題:據點層級的每月用電量,與公司層級的年度用水、廢棄物與營收資料,要怎麼放在一起看?直接合併有什麼地方需要特別小心?
  1. 載入三個檔案
    載入公司主檔、公司年度資料與據點月資料,先各自檢查 shape 與 columns。
  2. 建立並檢查候選鍵
    為公司年度資料建立 company_id + year 的候選鍵,確認沒有重複。
  3. melt 據點月資料
    把 Jan..Dec 十二欄轉成 month 與 value 兩欄,保留 company_id 與 site_id。
  4. 發現 join 陷阱
    檢查 site_id 是否在不同公司之間重複出現;如果重複,單獨用 site_id 做 join 會有風險。
  5. 建立正確的 join 鍵
    用 company_id + site_id 建立唯一的 site key,再與公司主檔做 1:N join。
  6. 檢查列數
    確認 join 後的列數符合預期(不多也不少)。
  7. 記錄三則觀察
    用自己的話寫下三則從輸出看到的觀察。
  8. 標示證據類型
    為每個觀察標示:Fact from data(資料事實)、Interpretation(解讀)或 Needs verification(待查證)。
  9. 寫一句限制
    寫下這份分析目前做不到什麼。
為每個觀察標示證據類型(句型範例,請填入你自己的輸出)
標籤意思句型範例
Fact from data(資料事實)直接從輸出讀到、可以被別人重現的數字在本模擬資料中,[據點] 在 [月份] 的用電量為 [數值] kWh。
Interpretation(解讀)你根據事實提出的看法,要說明依據與不確定性[公司] 的用電量在下半年較高,可能與產季或空調負載有關,但資料本身無法確認原因。
Needs verification(待查證)資料本身無法確認,需要其他資料或詢問資料提供者兩家公司都有一個叫 S01 的據點;如果不加公司代碼直接合併,會混到不同公司的用電資料,需要向資料提供單位確認命名規則。
  • 確認最終合併後的列數是否等於你事先手算的預期值
  • 確認 site_id 是否已經與 company_id 一起組成唯一的據點代碼
  • 確認每一筆標示為 Needs verification 的觀察,都寫出了需要向誰確認什麼

注意

這份資料只能描述「發生了什麼」,不能單獨解釋「為什麼」。避免寫出因果說法,也不要把據點層級的用電量直接當成公司層級指標使用,除非已經完成正確的彙整與驗證。

完成

如果使用 AI 協助寫 melt 或 join 的程式碼,請逐項確認:識別欄位是否列齊、join 鍵是否唯一、列數是否符合預期。AI 可以加快撰寫,驗證與結論仍由人負責。

重點: 一份可信的整合,不只是把檔案合併起來,還要能說明為什麼這樣合併是對的、列數為什麼是這個數字。

接下來做什麼?