跳到主要內容

DS 101 · 模組 2

在 AI 協助下完成可信的 ESG 分析

程式跑得出結果之後,怎麼確認這個結果值得相信?

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

資訊

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

學習目標

  • 辨識缺失值、重複紀錄、資料型態、單位與異常值等資料品質問題,並用決策紀錄說明每一個清理決定
  • 使用 derived columns、groupby() 與 aggregation 計算 Scope 1+2、排放強度與年增減
  • 選擇適合比較與趨勢的圖表,並用 Fact / Analysis / Unknown 區分證據、解讀與未知
  • 使用 AI 協助撰寫與除錯 pandas 程式碼,並以五步驟檢查程式、公式、單位、結果與結論
  • 在 Independent Lab 中獨立完成一份可重現、可驗證、可解釋的 ESG 分析

資料品質

40 分鐘

ESG 資料品質與清理

資訊

學習目標:辨識並處理常見的資料品質問題,而且不把不確定的資料任意「修成看起來正常」。

本課使用 DS101_ESG_Company_Data_Practice.csv。這是一份模擬的教學資料,刻意保留了一些真實工作中常見的資料品質問題。請先不要急著計算,而是像審閱一份別人交來的報表一樣,先檢查:有沒有空白?有沒有重複?數字欄位真的是數字嗎?單位一致嗎?有沒有超出合理範圍或特別突出的值?公司名稱寫法一致嗎?

三個基本檢查指令
import pandas as pd

df = pd.read_csv("DS101_ESG_Company_Data_Practice.csv")

# 每個欄位有幾個缺失值
df.isna().sum()

# 有幾列與前面某一列完全相同
df.duplicated().sum()

# 每個欄位被讀成什麼資料型態
df.dtypes
  • 缺失值不等於零:空白代表「不知道」,0 代表「確定沒有」,兩者意義完全不同。
  • 重複紀錄不一定是真的重複:先檢查 company_id 與 year 這組鍵值,再判斷是重複輸入還是兩筆不同的紀錄。
  • 數字欄位可能被讀成文字:例如數字裡有千分位逗號,整欄就會變成 object,無法直接計算。
  • 單位不一致會讓比較失效:同一欄若混用不同單位,數字大小就不能直接比較。
  • 異常值是需要調查的訊號,不是自動刪除的對象:它可能是錯誤,也可能是真實發生的變化。
  • 看起來合理的數值仍可能是錯的:落在正常範圍內,不代表已經和來源核對過。

注意

必要範例:如果 scope2_tco2e 是空白,不可以自動填入 0,除非來源文件明確證明 0 才是正確的值。把空白當成 0,會讓排放量看起來比實際少,後面的 Scope 1+2 與排放強度也會跟著錯。
進一步檢查(只檢查,不改動原始欄位)
# 同一個 company_id 與 year 是否出現超過一次
print(df[df.duplicated(subset=["company_id", "year"], keep=False)])

# 公司名稱有沒有寫法不一致(例如多了空白)
print(df["company_name"].unique())

# 哪些值無法被讀成數字(另存成新變數,不覆寫原始欄位)
revenue_num = pd.to_numeric(df["revenue_m_twd"], errors="coerce")
print(df.loc[revenue_num.isna() & df["revenue_m_twd"].notna(), ["company_id", "year", "revenue_m_twd"]])

# 百分比欄位是否超出 0 到 100
print(df[(df["renewable_energy_pct"] < 0) | (df["renewable_energy_pct"] > 100)])

# 用摘要統計找出特別突出的值,再回頭看同一家公司的前後年度
df.describe()
資料品質決策紀錄:Issue / Evidence / Impact / Treatment decision / Justification / What must remain unknown(範例為模擬情境,不是練習檔的內容)
Issue(問題)Evidence(證據)Impact(若不處理的影響)Treatment decision(處理決定)Justification(為什麼這樣處理合理)What must remain unknown(必須維持未知/不變的部分)
某公司某一年的 waste_tonnes 約為其他年度的 1000 倍describe() 顯示最大值特別突出;來源附註寫的單位是 kg,而資料字典規定是 tonnes若不處理,該年度的廢棄物量會被高估約 1000 倍,連帶影響任何加總或比較與資料提供者確認後,在新欄位中換算為 tonnes,保留原始欄位同一欄必須使用同一單位,否則年度之間無法比較;換算前先與來源核對,而不是自行猜測其他年度是否也曾誤用 kg 尚未逐一確認,仍是未知
某公司某一年的 employees 為空白isna().sum() 顯示該欄有 1 個缺失值若補成 0 或平均值,該年度的人均指標會被錯誤地拉低或拉高維持缺失,不補值;計算每位員工指標時排除該年,並在限制中說明沒有來源可以確認員工人數,補值等於自行創造資料該年度真實的員工人數維持未知;該年的人均指標趨勢中斷,不予估計
某公司名稱在不同列的寫法不完全一致(例如多了結尾空白)df["company_name"].unique() 列出的值看起來重複,但字串比較結果不相等若不處理,groupby("company_name") 會把同一家公司拆成兩組,彙總結果被低估去除頭尾空白並統一寫法後存成新欄位,再用新欄位分組,保留原始欄位分組與彙總要依賴名稱完全相符;統一寫法前要先確認這些列確實是同一家公司,而不是直接改寫是否還有其他尚未發現的拼法差異,仍需要在後續檢查中留意

好的清理習慣是:保留原始欄位,把清理後的結果放在新欄位或新的 DataFrame;每一個修改都寫進決策紀錄;無法確認的地方就維持缺失,並在結論的限制中說出來。

  • 先看整體:df.shape 與 df.dtypes,確認資料大小與型態符合預期
  • 再看缺失:df.isna().sum(),知道哪些欄位有空白
  • 再看重複:df.duplicated(subset=["company_id","year"]).sum(),確認鍵值沒有重複
  • 最後看範圍與極端值:df.describe() 與欄位層級的範圍檢查,找出需要查證的數字
  • 建議依照這個順序,而不是看到哪個問題就先處理哪個,否則容易漏掉前面的問題
自我檢測

你發現某一列的 scope2_tco2e 是空白。接下來第一步應該怎麼做?

你發現某一列的 scope2_tco2e 是空白。接下來第一步應該怎麼做?

重點: 清理資料不是讓資料變漂亮,而是讓每一個修改都可解釋、可追溯。

指標與視覺化

40 分鐘

ESG 指標計算與 groupby

資訊

學習目標:能使用 derived columns、groupby() 與 aggregation,回答公司層級與年度層級的問題。

Derived column 是用既有欄位算出來的新欄位。本課用三個簡化的教學公式:Scope 1+2 排放量(絕對量)、排放強度(每新臺幣百萬元營收的 tCO2e),以及年增減百分比。絕對排放量回答「排了多少」,排放強度回答「相對於營收規模排了多少」,兩者回答的是不同的問題。以下範例使用已清理的 DS101_ESG_Company_Data_Clean.csv。

建立 derived columns
import pandas as pd

df = pd.read_csv("DS101_ESG_Company_Data_Clean.csv")

# Scope 1+2 排放量(tCO2e)
df["scope12_tco2e"] = df["scope1_tco2e"] + df["scope2_tco2e"]
# 排放強度(tCO2e / 新臺幣百萬元營收)
df["scope12_intensity"] = df["scope12_tco2e"] / df["revenue_m_twd"]
依公司分組彙總(named aggregation)
summary = (
    df.groupby("company_name", as_index=False)
      .agg(
          avg_scope12=("scope12_tco2e", "mean"),
          avg_intensity=("scope12_intensity", "mean")
      )
)

注意:上面的 summary 每家公司只剩一列,年度這個維度已經被平均掉了。它可以回答「2020 到 2025 年的平均水準」,但不能回答「逐年怎麼變化」。要看逐年變化,就要保留公司與年度的粒度,並先依公司與年度排序,再計算年增減。

計算年增減百分比
df = df.sort_values(["company_name", "year"])
df["scope12_yoy_pct"] = (
    df.groupby("company_name")["scope12_tco2e"]
      .pct_change() * 100
)
  • 分母不是 0,而且單位符合預期嗎?(revenue_m_twd 是新臺幣百萬元)
  • 缺失值是不是被悄悄排除了?(mean() 預設會略過 NaN,平均可能只來自部分年度)
  • 分組方式有保留你想要的分析粒度嗎?(依公司、依年度,還是公司加年度?)
  • 跨年度的平均值,對這個問題真的有意義嗎?
  • 前一年的數值很小時,百分比變化是不是會變得不穩定、看起來特別誇張?

注意

排放強度也不一定能直接跨公司比較:不同產業、不同的盤查邊界、方法與資料完整度,都會影響數字。本課的公式是簡化的教學範例,不能用來評比或排名公司,也不能取代正式的 ESG 盤查與報告指引。
示範:年增減百分比怎麼算(示意數值,非課程檔案中的實際數值)

假設某公司的 scope12_tco2e 在 2023 年是 4000、2024 年是 4600。年增減百分比 = (4600 - 4000) / 4000 x 100 ≈ 15%,也就是 pct_change() 回傳的值乘以 100 後的結果。df.groupby("company_name")["scope12_tco2e"].pct_change() 是依公司分別計算「這一列和同一家公司前一列」的變化,而不是和全部資料的前一列比較。

資訊

容易忽略的細節:每家公司時間序列中的第一個年度,沒有「前一年」可以比較,pct_change() 會回傳 NaN,這是正常現象,不是程式錯誤。計算平均年增減時,要知道這些 NaN 是被排除還是被當成缺失處理。
自我檢測

問題是「每家公司的 Scope 1+2 排放量逐年如何變化?」下列哪一種分組方式能保留回答這個問題所需的粒度?

問題是「每家公司的 Scope 1+2 排放量逐年如何變化?」下列哪一種分組方式能保留回答這個問題所需的粒度?

重點: 公式正確只是第一步;還要確認分母、分組、缺值與分析粒度。

35 分鐘

視覺化:從數字到證據

資訊

學習目標:能選擇簡單、可解讀而且不誤導的圖表,呈現比較與趨勢。
依問題選擇圖表
想回答的問題建議圖表注意事項
某個指標隨時間怎麼變化?折線圖(line chart)x 軸放年度;每條線代表一家公司,並加上標記點
不同類別之間如何比較?長條圖(bar chart)數值軸通常應從 0 開始,否則高低差距會被放大
兩個數值變數之間有沒有關係?散佈圖(scatter plot)只能探索關係,不能證明因果;也要注意點數很少時的偶然性
想同時比較很多家公司或很多個指標?小型多圖(small multiples):每家公司或每個指標各畫一張結構相同的小圖統一座標軸範圍與圖表大小,才能公平比較;圖太多時考慮只呈現與問題最相關的幾個
  • 座標軸要有名稱與單位,例如「tCO2e per NT$ million revenue」。
  • 長條圖通常應從 0 開始;截斷的座標軸會讓小差距看起來很大。
  • 即使是折線圖,也要留意座標軸範圍是否讓變化被誇大或被壓平。
  • 類別或顏色太多會讓圖難以解讀;必要時拆成多張圖或只呈現與問題相關的類別。
  • 標題描述圖上呈現了什麼,而不是宣稱它代表什麼。
各公司排放強度趨勢(延續 Lesson 7 已建立 scope12_intensity 的 df)
import matplotlib.pyplot as plt

for company, g in df.groupby("company_name"):
    plt.plot(g["year"], g["scope12_intensity"], marker="o", label=company)

plt.xlabel("Year")
plt.ylabel("tCO2e per NT$ million revenue")
plt.title("Scope 1+2 emissions intensity by company")
plt.legend()
plt.show()

資訊

無障礙小技巧:不要只靠顏色區分公司。每條線可以使用不同的標記形狀(例如 marker="o"、"s"、"^"),加上圖例或在線尾直接標註公司名稱,讓色覺差異者與黑白列印時也能讀懂。
  1. Fact(事實)
    圖上直接看得到的內容。例如:某家公司的排放強度在 2020 到 2025 年間大致下降。
  2. Analysis(分析)
    合理但需要說明依據的解讀。例如:下降可能與營收成長較快有關,因為排放強度的分母是營收。
  3. Unknown(未知)
    這份資料無法確立的事情。例如:下降是否來自減碳措施、盤查邊界是否改變,這份資料都無法回答。
標題怎麼寫

過度宣稱:「減碳最成功的公司」。描述所見:「各公司 Scope 1+2 排放強度,2020 至 2025 年(模擬教學資料)」。前者把解讀包裝成事實,後者只說出圖上呈現的內容。

容易誤導的標題

  • 「Evergreen Components Co. 是減碳贏家」把解讀包裝成事實
  • 沒有說明時間範圍或資料來源
  • 隱含了跨公司排名,但資料邊界未必可比

只描述圖上內容的標題

  • 「Evergreen Components Co. 的 Scope 1+2 排放強度,2020 至 2025 年(模擬教學資料)」
  • 清楚標示時間範圍與資料性質
  • 留給讀者自己判斷,而不是先下結論
自我檢測

你畫了一張散佈圖:x 軸是 renewable_energy_pct,y 軸是 scope12_intensity,點大致呈現向右下的趨勢。下列哪一個說法最恰當?

你畫了一張散佈圖:x 軸是 renewable_energy_pct,y 軸是 scope12_intensity,點大致呈現向右下的趨勢。下列哪一個說法最恰當?

重點: 圖表是證據的表達方式,不是把推測變成事實的工具。

AI 輔助與驗證

30 分鐘

AI 輔助分析與驗證

資訊

學習目標:使用 AI 協助 Python 分析,同時建立不可省略的驗證流程。
AI 輔助分析流程
  1. Human:提出分析問題
  2. AI:提出程式碼
  3. Python:執行程式碼
  4. Human:驗證資料、方法與輸出
  5. Human:溝通結論

文字說明:流程從人提出分析問題開始;AI 根據問題提出程式碼;Python 實際執行並產生結果;接著由人驗證資料、方法與輸出是否正確;最後由人撰寫並溝通結論。AI 只負責中間「提出程式碼」這一步,問題的定義、驗證與結論都由人負責。

建議的 AI 請求範本(可改寫成中文,但保留結構)
Task:
Use pandas to calculate Scope 1+2 emissions intensity by company and year.

Schema:
company_name: text
year: integer
revenue_m_twd: NT$ million
scope1_tco2e: tCO2e
scope2_tco2e: tCO2e

Rules:
- Do not invent columns.
- Do not replace missing emissions with zero.
- Show the formula explicitly.
- Preserve company-year granularity.
- Explain each transformation.
- Include verification checks for units, missing values, and row counts.
  1. Schema check(欄位檢查)
    程式碼引用的每一個欄位都存在於資料中。
  2. Formula check(公式檢查)
    計算方式符合你想要的指標定義。
  3. Unit check(單位檢查)
    單位彼此相容,而且有清楚標示。
  4. Output check(輸出檢查)
    至少手動驗算一列結果。
  5. Claim check(結論檢查)
    文字敘述沒有超出證據能支持的範圍。
AI 回覆的程式碼範例(含錯誤,用來練習找錯,請勿直接使用)
# AI 產生的程式碼:有問題
df = df.fillna(0)
df["intensity"] = df["total_emissions"] / df["revenue_m_twd"]
print("The company with the lowest intensity is the best company.")

注意

核心觀念:「程式跑得出來、也印出一個數字」不代表答案是對的。AI 產生的分析程式碼經常語法正確、可以執行、甚至輸出看起來合理的數字,但背後的邏輯是錯的。以下十種風險,每一種都要能自己找出問題、並知道怎麼驗證。
AI 產生的分析程式碼:十種常見風險與驗證方式
風險錯誤程式或輸出的樣子Learner 怎麼找出問題/驗證
程式能執行,答案卻是錯的上面的範例沒有任何錯誤訊息,直接印出結論;但 total_emissions 是捏造的欄位,intensity 其實是 NaN / 0 的結果。不要因為「沒有紅字」就相信結果。先做 Schema check:印出 df.columns,確認程式引用的每個欄位都存在。
分母錯誤(wrong denominator)AI 把 2021–2025 五年加總的排放量,除以「2025 年」單一年度的營收,得到一個虛高的排放強度。寫下公式的分子與分母各代表哪個時間範圍;分子分母的時間粒度必須一致,才手動用一列資料驗算。
分組錯誤(wrong groupby)df.groupby("company_name")["scope12_tco2e"].sum():只依公司分組,年度維度被加總消失,卻拿來回答「哪一年排放最高」。對照分析問題需要的粒度(公司?年度?公司 × 年度?),確認 groupby() 的欄位有留住這個維度。
單位不一致(unit mismatch)同一段程式裡把新臺幣元(NT$)與新臺幣百萬元(NT$ million)的營收欄位直接相加或相除。對照資料字典,確認每個數值欄位的單位;換算後另存新欄位,並在欄位名稱或註解中標明單位。
缺失值被悄悄捨棄(missing values silently dropped)df["scope1_tco2e"].mean() 預設會忽略 NaN;如果某公司有兩年缺值,平均其實只用了另外三年的資料,但程式不會提示你。計算平均或加總前,先用 df["col"].isna().sum() 確認有多少缺值;在結論中說明平均是根據哪幾年計算的。
重複列(duplicate rows)合併兩份表格後,某公司的某一年出現兩列一模一樣的紀錄,加總後排放量被算成兩倍。合併或彙總前先用 df.duplicated(subset=["company_id","year"]).sum() 檢查;合併後確認列數符合預期。
資料型態/日期解析錯誤(wrong datatype / date parsing)欄位裡有一格日期寫成「2021/13/05」,pd.to_datetime() 預設會把它悄悄轉成 NaT(不是日期),之後的年度篩選會漏掉這一列,但不會報錯。轉換型態或日期後,用 isna().sum() 檢查有沒有新增的缺失值;用 errors="raise" 先找出無法解析的原始值。
把相關性說成因果(correlation described as causation)文字結論寫「因為……所以」或「造成」,但證據只是「兩者同時變化」的觀察資料。檢查文字裡是否出現「導致」「造成」「因為」;若只有觀察到同時變化,改寫成「觀察到相關,但無法確立因果」。
過時或錯誤的資料來源(stale / wrong source)AI 在回答時引用了看起來合理的排放係數或基準年數字,但沒有列出來源,或來源其實是舊版、已被取代的版本。任何外部係數、標準值都要能回答「這個數字的來源是什麼、是哪一年的版本」;找不到來源就不能使用。
圖表誇大差異(chart that visually overstates difference)長條圖的 y 軸從 90 開始而不是從 0,讓兩家公司原本只差 3% 的排放強度,視覺上看起來差了好幾倍。檢查座標軸是否從 0 開始(長條圖通常應該);同一組圖表用相同的座標範圍比較。
重複列與資料型態範例(延伸練習:找出下面程式的問題)
# 合併兩個資料來源後
merged = pd.merge(emissions_2024, emissions_2024_restated, on="company_id", how="outer")
merged["scope1_tco2e"] = merged["scope1_tco2e"].sum()  # 有問題:同一家公司出現兩次

# year 欄位被讀成文字(例如原始資料裡有一格寫成 "2021 年")
merged["year"] = pd.to_numeric(merged["year"], errors="coerce")  # 有問題:無法轉換的值會悄悄變成缺失
print(merged["year"].isna().sum())  # 驗證:轉換後有幾筆變成缺失,而不是假設全部成功
  • 捏造不存在的欄位,例如引用資料中沒有的 total_emissions。
  • 沒有任何理由就把 NaN 填成 0。
  • 混用不同的營收單位,例如把新臺幣元與新臺幣百萬元放在同一個計算中。
  • 計算彙總比率時用錯分母,例如用某一年的營收去除多個年度加總的排放量。
  • 從相關性寫出「因為……所以」或「造成」。
  • 在沒有定義標準、邊界也不可比的情況下,宣稱某家公司「最好」。
示範:五步驟檢查套用在同一個問題上

問題:「2025 年哪家示範公司的排放強度最低?」Schema check:確認程式只用到 scope1_tco2e、scope2_tco2e、revenue_m_twd,沒有引用不存在的欄位。Formula check:確認強度 = (scope1_tco2e + scope2_tco2e) / revenue_m_twd,而不是只看其中一個 Scope。Unit check:確認 revenue_m_twd 真的是百萬元,而不是和其他年度的元混用。Output check:手動用計算機核對結果中最低的那一家,重新算一次。Claim check:結論只寫「在本模擬資料中,2025 年該公司的排放強度最低」,不寫成「這家公司的環保表現最好」。

注意

隱私與資料安全:未經授權,不要把機構或公司的機密資料上傳到 AI 工具;不要在公開或共用的 AI 對話中放入個人資料或敏感資料。練習請使用課程提供的模擬資料;要使用真實的 ESG 或內部營運資料前,先確認所屬組織的政策。
自我檢測

AI 提供的程式碼寫著 df["total_emissions"] / df["revenue_m_twd"],執行後出現 KeyError。最恰當的下一步是什麼?

AI 提供的程式碼寫著 df["total_emissions"] / df["revenue_m_twd"],執行後出現 KeyError。最恰當的下一步是什麼?

重點: AI 可以加速程式撰寫,但不能替你負責資料定義、公式與結論。

Independent Lab

35 分鐘

Independent Lab:可信分析整合

資訊

學習目標:獨立完成一份從資料品質檢查到結論溝通的 ESG 分析,並讓每一個步驟都可重現、可驗證、可解釋。
你的任務

使用 DS101_ESG_Company_Data_Practice.csv,獨立完成:找出一項值得報告的 ESG 趨勢或異常,完成資料品質檢查、必要清理、指標計算、視覺化與驗證,並清楚區分證據、解讀與未知事項。

  1. 1. Question(分析問題)
    用一句話寫出你要回答的問題。
  2. 2. Data source(資料來源)
    列出檔案名稱與來源說明。
  3. 3. Grain(資料粒度)
    寫出一列資料代表什麼。
  4. 4. Units(單位)
    列出實際用到的每個欄位的單位。
  5. 5. Cleaning decisions(清理決策)
    列出發現的資料品質問題,並用決策紀錄說明每個決定、理由與必須維持未知的部分。
  6. 6. Analysis method(分析方法)
    說明你用什麼方法、依什麼步驟完成分析。
  7. 7. Derived metrics(衍生指標)
    寫出公式與單位,例如 scope12_intensity 的分子與分母。
  8. 8. Visualization(視覺化)
    至少一張圖表,有座標軸名稱、單位與描述性的標題。
  9. 9. Verification checks(驗證)
    選一列,用計算機或手算核對程式的結果,並記錄你做了哪些檢查。
  10. 10. AI assistance record(AI 協助紀錄)
    如果使用了 AI,說明它協助了什麼,以及你自己驗證了什麼。
  11. 11. Human review(人工審閱)
    說明你(或同儕/教師)對這份分析做了什麼審閱。
  12. 12. Limitations(限制)
    三個證據陳述(Fact)、一個解讀陳述(Analysis),以及至少兩項限制或未知(Unknown)。
  13. 13. Reproduction instructions(重現步驟)
    寫出另一個人依這些步驟重現你分析所需的資訊。
完成標準
結果條件
Complete(完成)13 項必要內容都有,而且結論與證據一致。
Needs revision(需要修改)程式可以執行,但單位、公式或資料品質決定沒有記錄,或結論超出資料能支持的範圍。
  • 只算出一個指標的數字,卻沒有寫出公式的分子與分母與單位
  • 圖表畫出來了,卻沒有座標軸標籤或標題只是檔名
  • 找到一個異常值就直接刪除,沒有先查證也沒有寫進決策紀錄
  • 結論裡出現「因為……所以」,但證據只到「觀察到同時發生」
  • 忘記寫限制或未知事項,讓結論看起來比資料能支持的更確定

注意

練習資料是模擬的教學資料,不代表任何真實公司。請勿把機密或個人資料放進 AI 工具。AI 可以協助你寫程式,但分析與結論由你負責。

重點: 可重現、可驗證、可解釋,才是一份可以帶進工作情境的資料分析。

接下來做什麼?