Home
探索 Uedu
學生控制台
註冊會員/登入
研究知情同意中心
教師控制台
課程設定
支援與訊息
Uptime 數據

UeduGPTs

--

Jupyters

4

UG26 CISOSE26
臺北 AQI 46 · 臺中 AQI 28 · 臺南 AQI 24 · 高雄 AQI 33

AI 回覆桌面通知

AI 助教回覆完成時顯示桌面通知

聊天訊息通知

同學在討論區發送訊息時通知

聲音通知

每當有新通知時播放提示音

用 Python 做資料分析

用 Python 做資料分析:numpy、pandas 與 matplotlib 實戰

從一份成績 CSV 出發,用向量化陣列、DataFrame 與視覺化串起完整工作流,並接回優統計與優智慧 AI。

從一份成績 CSV 開始:你的第一場資料偵探任務

假設你手上有一份匯出的課堂資料:三百位同學、五次小考、出席率、還有一欄「最後總成績」。老師問你一個看似簡單的問題:「出席率高的同學,成績真的比較好嗎?」

你可以打開試算表,一格一格地框選、排序、目測。但當資料變成三千筆、三萬筆,或是每天自動更新時,手動的方法就崩潰了。這時候,Python 的資料分析三劍客——numpypandasmatplotlib——會把這份偵探任務變成幾行可重複執行的程式。更棒的是,這條工作流不會停在「畫一張圖」,它會自然接到「優統計」的敘述統計與迴歸,以及「優智慧 AI」的資料前處理。

這篇文章帶你一步一步動手,從原始的數字陣列,走到一個能回答真實問題的分析腳本。

用 Python 做資料分析概念示意圖

numpy:把「一堆數字」變成會算術的陣列

先從最底層說起。如果你只用 Python 內建的串列(list)來存一萬個分數,要把每個分數乘以 1.1(加分),你得寫一個迴圈:

scores = [62, 75, 88, 91, 54]
adjusted = []
for s in scores:
    adjusted.append(s * 1.1)
print(adjusted)
# 輸出:[68.2, 82.5, 96.8, 100.10000000000001, 59.400000000000006]

這能跑,但既冗長又慢。numpy 提供了 ndarray(N 維陣列),讓你把整個運算「一次套用到所有元素」,這叫做向量化(vectorization)

import numpy as np

scores = np.array([62, 75, 88, 91, 54])
adjusted = scores * 1.1          # 一行,整個陣列一起算
print(adjusted)
# 輸出:[68.2 82.5 96.8 100.1 59.4]

注意 scores * 1.1 並沒有迴圈,卻把每個元素都乘上了 1.1。這種「純量對整個陣列」的運算稱為廣播(broadcasting)。numpy 還內建大量的統計函式,直接對陣列操作:

print(scores.mean())   # 平均
# 輸出:74.0
print(scores.std())    # 母體標準差
# 輸出:14.177446878757825
print(scores.max(), scores.min())
# 輸出:91 54
print((scores >= 60).sum())   # 及格人數:布林陣列加總
# 輸出:4

最後一行特別值得玩味:scores >= 60 會回傳一個布林陣列 [True True True True False],而 True 在求和時當作 $1$、False 當作 $0$,所以 .sum() 直接數出及格人數。這種「用布林陣列當遮罩」的思路,是整個資料分析的核心慣用法。

我們也可以用布林遮罩篩選元素:

passing = scores[scores >= 60]   # 只留下及格的
print(passing)
# 輸出:[62 75 88 91]

向量化的好處不只是程式變短,更重要的是——這點我們留到最後一節從底層解釋。現在先記住一個原則:在 numpy / pandas 裡,能不寫 Python 迴圈就不寫

pandas:給資料一個「表格」的形狀

numpy 的陣列是純數字的方陣,但真實資料有欄位名稱、有不同型別(姓名是字串、分數是數字、出席是日期)。這時候就輪到 pandasDataFrame 登場——你可以把它想成「程式版的試算表」。

讀進一份 CSV

假設我們有一個 grades.csv

name,attendance,quiz_avg,final
Amy,0.95,82,88
Ben,0.60,55,51
Cathy,0.88,79,84
Dan,0.45,48,40
Eva,0.92,90,93

讀進來只要一行:

import pandas as pd

df = pd.read_csv("grades.csv")
print(df.head())     # 看前幾列
print(df.shape)      # 輸出:(5, 4)  → 5 列、4 欄
print(df.dtypes)     # 每欄的資料型別

df.head() 預設顯示前 5 列,是你拿到任何資料的第一個動作。df.describe() 則一次給你所有數值欄的敘述統計——這正是「優統計」敘述統計的起點:

print(df.describe())
# 輸出(節錄):
#        attendance   quiz_avg      final
# count    5.000000   5.000000   5.000000
# mean     0.760000  70.800000  71.200000
# std      0.218861  17.633774  23.491488
# min      0.450000  48.000000  40.000000
# ...

篩選:用條件挑出你要的列

延續開頭的問題,我們想看「出席率高於 0.8」的同學:

high_attend = df[df["attendance"] > 0.8]
print(high_attend[["name", "attendance", "final"]])
# 輸出:
#     name  attendance  final
# 0    Amy        0.95     88
# 2  Cathy        0.88     84
# 4    Eva        0.92     93

df["attendance"] > 0.8 跟 numpy 一樣回傳一個布林序列,放進 df[...] 就會把為 True 的列留下。多個條件要用 &(且)、|(或)連接,而且每個條件都要用括號包起來(這是初學者最常踩的雷):

# 出席率高「且」小考平均及格
mask = (df["attendance"] > 0.8) & (df["quiz_avg"] >= 60)
print(df[mask]["name"].tolist())
# 輸出:['Amy', 'Cathy', 'Eva']

groupby:分組後再彙總

groupby 是 pandas 最有威力的工具。先幫每位同學貼上「出席等級」標籤,再分組算平均:

# 用 numpy 的條件函式產生分組欄位
df["attend_level"] = np.where(df["attendance"] >= 0.8, "高出席", "低出席")

summary = df.groupby("attend_level")["final"].agg(["mean", "count"])
print(summary)
# 輸出:
#                    mean  count
# attend_level
# 低出席         45.500000      2
# 高出席         88.333333      3

短短三行,我們就回答了開頭的問題:高出席組的總成績平均 88.3,低出席組只有 45.5。groupby(欄位)[目標欄].agg([...]) 是一個你會用一輩子的句型——「依某欄分組,對另一欄做彙總」。

matplotlib:讓數字說話

數字再清楚,也不如一張圖直觀。matplotlib 是 Python 最基礎的繪圖庫,pandas 與它無縫整合。畫一張「出席率 vs 總成績」的散佈圖:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(df["attendance"], df["final"], s=80, color="#3C938A")
ax.set_xlabel("出席率")
ax.set_ylabel("總成績")
ax.set_title("出席率與總成績的關係")
plt.tight_layout()
plt.savefig("scatter.png", dpi=120)   # 存檔,不一定要 show()

幾個慣例值得記住:用 fig, ax = plt.subplots() 取得「畫布」與「座標軸」物件,之後所有設定都掛在 ax 上,這是比舊式 plt.plot() 更清楚、更好維護的物件導向寫法。畫長條圖呈現分組結果也很直接:

fig, ax = plt.subplots(figsize=(5, 4))
summary["mean"].plot(kind="bar", ax=ax, color=["#3C6F9A", "#00b297"])
ax.set_ylabel("平均總成績")
ax.set_title("不同出席等級的平均成績")
plt.tight_layout()
plt.savefig("bar.png", dpi=120)

summary["mean"].plot(kind="bar") 直接把 pandas 的彙總結果畫成長條圖——這就是三劍客協作的縮影:numpy 算、pandas 整理、matplotlib 呈現。

動手寫一段:一個完整的迷你分析腳本

把上面所有片段串成一個能獨立執行的腳本。它建立資料、做敘述統計、分組彙總、並計算一個簡單的相關係數(這一步就踏進了「優統計」的領域):

import numpy as np
import pandas as pd

# 1. 建立資料(實務上改成 pd.read_csv("grades.csv"))
data = {
    "name":       ["Amy", "Ben", "Cathy", "Dan", "Eva"],
    "attendance": [0.95, 0.60, 0.88, 0.45, 0.92],
    "quiz_avg":   [82, 55, 79, 48, 90],
    "final":      [88, 51, 84, 40, 93],
}
df = pd.DataFrame(data)

# 2. 敘述統計(接「優統計」敘述統計)
print("總成績平均:", round(df["final"].mean(), 1))
print("總成績標準差:", round(df["final"].std(), 1))

# 3. 分組彙總
df["attend_level"] = np.where(df["attendance"] >= 0.8, "高出席", "低出席")
print(df.groupby("attend_level")["final"].mean())

# 4. 出席率與成績的相關係數(接「優統計」迴歸的前奏)
corr = df["attendance"].corr(df["final"])
print("出席率與總成績的相關係數:", round(corr, 3))
# 輸出:
總成績平均: 71.2
總成績標準差: 23.5
attend_level
低出席    45.5
高出席    88.3
Name: final, dtype: float64
出席率與總成績的相關係數: 0.974

相關係數 $0.974$ 接近 $1$,量化地確認了我們的直覺:出席率與成績高度正相關。下一步如果要建立預測模型(例如「給定出席率,預測成績」),就會用到「優統計」的線性迴歸——而它收的輸入,正是這份整理好的 DataFrame。

接回優統計與優智慧 AI

這條工作流的價值在於它是各模組共同的「資料底座」:

  • 接「優統計」(敘述統計、迴歸)df.describe() 給你平均、標準差、四分位數;df["x"].corr(df["y"]) 給你相關係數。要做線性迴歸時,常見的 scikit-learn 模型直接吃 numpy 陣列或 DataFrame:model.fit(df[["attendance"]], df["final"])。換句話說,pandas 整理好的資料就是迴歸的原料。
  • 接「優智慧 AI」(資料前處理):機器學習模型不能直接吃髒資料。處理缺失值(df.fillna(df.mean()))、特徵縮放、把類別欄轉成數字(pd.get_dummies(df["attend_level"]))——這些前處理幾乎全在 pandas 裡完成。可以說,任何 AI 專案的第一個小時,幾乎都花在 pandas 上。
# 前處理示範:補缺失值 + 類別轉 one-hot
df_clean = df.copy()
df_clean["quiz_avg"] = df_clean["quiz_avg"].fillna(df_clean["quiz_avg"].mean())
features = pd.get_dummies(df_clean[["attendance", "attend_level"]])
print(features.columns.tolist())
# 輸出:['attendance', 'attend_level_低出席', 'attend_level_高出席']

常見錯誤

  • 多條件篩選忘了加括號df["a"] > 1 & df["b"] < 2 會因運算子優先序而報錯。& 的優先序高於比較運算子,務必寫成 (df["a"] > 1) & (df["b"] < 2),而且用 & / | 而不是 Python 的 and / or
  • 在 DataFrame 上手寫 for 迴圈逐列計算for i in range(len(df)): df.loc[i, "x"] = ... 又慢又容易出錯。先想想能不能用向量化或 groupby 取代——九成情況可以。
  • 鏈式賦值的 SettingWithCopyWarningdf[df["a"] > 1]["b"] = 0 可能改不到原資料。要修改請用 df.loc[df["a"] > 1, "b"] = 0 一次定位列與欄。
  • 混淆母體與樣本標準差numpy.std() 預設是母體標準差(分母 $N$),pandas.std() 預設是樣本標準差(分母 $N-1$)。做統計推論時這個差別會影響結果,要看清楚你用的是哪一個。
  • 以為讀進來的型別都對:CSV 全是文字,read_csv 會猜型別但不一定準。拿到資料先 df.dtypes 檢查,數字欄若變成 object,多半是有非數字的髒值混在裡面。

深入探討(研究所視角)

向量化為什麼比 Python 迴圈快?

關鍵在於 Python 是直譯式、動態型別的語言。當你寫 for s in scores: s * 1.1,每一圈直譯器都要做一連串昂貴的工作:檢查 s 是什麼型別、找出 * 對這個型別該怎麼做、把結果裝箱成一個新的 Python 物件。對一萬個元素,這些「解譯開銷」就重複一萬次。

numpy 的陣列則完全不同。一個 ndarray 在記憶體裡是一塊連續(contiguous)、同質(homogeneous)型別的緩衝區——一萬個 float64 就是緊密排列的 $10000 \times 8$ 位元組,中間沒有 Python 物件的指標與標頭。當你寫 scores * 1.1,numpy 把整個運算交給底層用 C 寫好的迴圈執行:型別只檢查一次、迴圈在編譯後的機器碼裡跑、完全不碰 Python 直譯器。這通常帶來數十到上百倍的加速。

連續記憶體還帶來第二層好處:CPU 快取友善。現代處理器一次會把鄰近的一整段記憶體載入快取(cache line),連續存放的陣列正好讓每次載入都「物盡其用」;而 Python 串列存的是一堆指向四散物件的指標,存取時不斷在記憶體裡跳躍,快取命中率低。再加上 numpy 的 C 迴圈能讓編譯器啟用 SIMD(單指令多資料)向量指令,一道指令同時處理多個浮點數——這就是「向量化」一詞的硬體根源。

所以「能不寫迴圈就不寫」不只是風格偏好,而是把計算從「慢速的 Python 解譯層」推到「快速的 C/硬體層」的具體策略。

pandas 背後其實是 numpy

理解 pandas 的效能與行為,最好的角度是:DataFrame 的每一欄(Series),底層多半就是一個 numpy 陣列。你可以親眼驗證:

print(type(df["final"].to_numpy()))
# 輸出:<class 'numpy.ndarray'>

這解釋了許多現象。為什麼 df["final"] * 2 這麼快?因為它直接調用 numpy 的向量化乘法。為什麼數值欄要型別一致才有效率?因為背後是同質型別的 numpy 緩衝區;一旦混入字串,整欄退化成 object 型別(一堆 Python 物件指標),就失去了向量化優勢,這也是 dtype 變成 object 通常是效能警訊的原因。

更廣地看,這正是整個 Python 資料科學生態的設計哲學:以 numpy 的 ndarray 作為共通的資料交換格式。pandas 在它之上加了標籤與彙總語意;matplotlib 直接畫 numpy 陣列;scikit-learn 的模型輸入輸出也是 numpy 陣列。於是一條典型工作流長這樣:

pandas 讀檔與清理轉成 numpy 陣列scikit-learn 訓練模型numpy 算出結果pandas 整理 / matplotlib 視覺化

當你之後接觸到 PyTorch 的 tensor、或處理大到放不進記憶體的資料而改用 Dask、Polars 時,會發現它們大量沿用同一套「陣列與向量化」的心智模型。換句話說,這篇文章裡那個小小的 np.array([62, 75, 88, 91, 54]),其實是通往整個現代資料科學與 AI 工程的第一塊基石。把向量化、DataFrame、視覺化這三件事練熟,你就握住了之後所有進階主題的鑰匙。

AI 共讀助教正在陪你讀:用 Python 做資料分析:numpy、pandas 與 matplotlib 實戰
嗨!我是這篇文章的共讀助教,只根據〈用 Python 做資料分析:numpy、pandas 與 matplotlib 實戰〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。