跳到主要內容

DE Day 4 SQL 進階(二):排名、累計與移動平均

DE Day 4 SQL 進階(二):排名、累計與移動平均

執行需求:CPU 可跑。昨天我們學會了 OVER() 與 PARTITION BY、ORDER BY、frame 子句這四個視窗函式的核心觀念。今天把排名函式(ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE())與前後列取值(LAG()、LEAD())一次收進來,並接著昨天的主題,把累計與移動平均的實戰寫法拉成可以照抄的範本。這一篇做完,你就具備了商業分析 80% 的查詢能力。

引言

排名是分析報表裡最常見的需求之一:每位使用者前 3 名消費月份、各地區貢獻前 5 名業務員、產品類別前 10 大營收來源。這些問題用 ORDER BY ... LIMIT 看似可行,但每群都要重做一次;而排名函式讓我們可以在同一個 SELECT 裡標出每列在群內的位次,並能用 ROW_NUMBER() = 1 過濾掉「每群的第一列」。同樣地,LAG() 與 LEAD() 直接讀前一列或後一列,免去 self-join 的麻煩;移動平均與移動總和則靠昨天學的 frame 子句組合而成。

今天的目標有四個:第一,看懂 ROW_NUMBER()、RANK()、DENSE_RANK() 三者的差異,知道什麼情境該用哪個;第二,會用 LAG()、LEAD() 與 frame 子句計算「上個月到這個月的成長率」與「過去 3 個月的移動平均」;第三,會寫一個能整段執行的 Python + DuckDB 範例,從建立消費事實表開始,到同一個查詢同時輸出排名與累計;第四,學會用 QUALIFY 子句過濾視窗函式結果,這是 DuckDB 與新世代資料庫(Snowflake、BigQuery)的便利寫法。

三種排名的差異

排名函式有四個主要的函式,差別在「同分時」的處理。先把它們的行為用一張示意圖展現:假設四位選手成績分別是 95、90、90、85:

選手 分數 ROW_NUMBER RANK DENSE_RANK
A 95 1 1 1
B 90 2 2 2
C 90 3 2 2
D 85 4 4 3

從這張表可以讀到三件事:第一,ROW_NUMBER() 不管分數是否相同都給連續編號(即使 B 與 C 同分,B 拿到 2、C 拿到 3);第二,RANK() 在同分時跳號(同分都是 2,下一個就是 4);第三,DENSE_RANK() 在同分時不跳號(同分都是 2,下一個就是 3)。實務上:要做「唯一排序」、要分頁、要拿第 N 筆,用 ROW_NUMBER();要呈現「同分同名次」的競賽榜,用 RANK();要做「層級篩選(如前 10%)」,用 DENSE_RANK() 比較直觀。把它記成口訣:「連號用 ROW、跳號用 RANK、不跳用 DENSE」。

完整實作:用 DuckDB 同時計算排名與累計

我們沿用 Day 3 的 demo_orders 消費事實表,加上每位使用者每月的排名,並把累計寫法與本月相對上個月的成長率一起帶進來:

"""DE Day 4:排名、累計與移動平均。"""
import duckdb

con = duckdb.connect()
con.execute("""
    CREATE OR REPLACE TABLE demo_orders AS
    SELECT * FROM (VALUES
        ('u01', '2025-09-01', 1200),
        ('u01', '2025-10-01',  800),
        ('u01', '2025-11-01', 1500),
        ('u02', '2025-09-15',  600),
        ('u02', '2025-10-20', 2200),
        ('u02', '2025-11-05', 1100),
        ('u03', '2025-09-10', 4500),
        ('u03', '2025-10-12',  300),
        ('u03', '2025-11-18',  900),
        ('u04', '2025-09-22', 1900),
        ('u04', '2025-10-30', 2400),
        ('u04', '2025-11-25', 1300)
    ) AS t(user_id, order_date, amount)
""")
print("資料筆數:", con.execute("SELECT COUNT(*) FROM demo_orders").fetchone()[0])
# 輸出:資料筆數:12

這段把昨天的資料集重建起來。我們故意不放索引,直接用 CREATE OR REPLACE TABLE 確保腳本可以重跑。Day 7 會示範在同個欄位加索引後,查詢計畫怎麼變。這份資料的人物只有 4 位、日期只有 3 個月,是討論排名與累計的最小模型。

接下來同個查詢內計算:每位使用者內、依金額排序的排名(三種版本:ROW_NUMBER、RANK、DENSE_RANK),以及本月相對上月金額的變化:

result = con.execute("""
    SELECT
      user_id,
      order_date,
      amount,
      -- 在每位使用者內,依金額由大到小排名
      ROW_NUMBER() OVER (
          PARTITION BY user_id ORDER BY amount DESC
      ) AS rn_by_amount,
      RANK() OVER (
          PARTITION BY user_id ORDER BY amount DESC
      ) AS rk_by_amount,
      DENSE_RANK() OVER (
          PARTITION BY user_id ORDER BY amount DESC
      ) AS drk_by_amount,
      -- 在每位使用者內,依時間由早到晚計算累計
      SUM(amount) OVER (
          PARTITION BY user_id ORDER BY order_date
      ) AS running_total,
      -- 取前一列的金額
      LAG(amount) OVER (
          PARTITION BY user_id ORDER BY order_date
      ) AS prev_amount,
      -- 計算本期金額相對前一期金額的差異
      amount - LAG(amount) OVER (
          PARTITION BY user_id ORDER BY order_date
      ) AS diff_vs_prev
    FROM demo_orders
    ORDER BY user_id, order_date
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} rn={r[3]} rk={r[4]} drk={r[5]} rt={r[6]} prev={r[7]} diff={r[8]}")
# 輸出:user=u01 date=2025-09-01 amount=1200 rn=2 rk=2 drk=2 rt=1200 prev=None diff=None
# 輸出:user=u01 date=2025-10-01 amount=800 rn=3 rk=3 drk=3 rt=2000 prev=1200 diff=-400
# 輸出:user=u01 date=2025-11-01 amount=1500 rn=1 rk=1 drk=1 rt=3500 prev=800 diff=700
# 輸出:(依此類推)

這段一次展示六個視窗函式:我們有 rn_by_amount(ROW_NUMBER())、rk_by_amount(RANK())、drk_by_amount(DENSE_RANK())、running_total(依日期累計)、prev_amount(LAG(amount) 取前一列)、diff_vs_prev(本期減前期)。最後一個 diff_vs_prev 是把 LAG() 嵌入算術表達式,這是寫「與上一期差異」最簡潔的寫法。在 u01 的第一筆可以看到 prev=None 與 diff=None,因為 LAG() 在沒有前一期時會回 NULL。

注意 rn_by_amount、rk_by_amount、drk_by_amount 三者在這份資料裡看不出差異,因為每個使用者內的金額都不同。如果把 u01 的 800 元改成另一個 1200 元(與九月同分),ROW_NUMBER 給連號(2、3),RANK 同分同名(2、2,下一筆變 4),DENSE_RANK 同分同名(2、2,下一筆變 3)就會出現差異。建議你動手改一份輸入試試看,這是把排名函式學扎實的最快方式。

移動平均與 frame 子句組合技

移動平均(moving average)是把時間序列上「最近 N 個時間點」做平均,這在畫趨勢線與偵測異常時是基本款。我們把 frame 子句、累計函式、與排名函式混搭,做一份 u01 的「過去 2 期平均與全期排名」:

result = con.execute("""
    SELECT
      user_id,
      order_date,
      amount,
      -- 過去 2 列 + 目前列的移動平均(包含本期)
      AVG(amount) OVER (
          PARTITION BY user_id
          ORDER BY order_date
          ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
      ) AS ma_3,
      -- 對稱視窗:過去 1 列 + 本期 + 未來 1 列,做移動平均
      AVG(amount) OVER (
          PARTITION BY user_id
          ORDER BY order_date
          ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
      ) AS ma_3_sym,
      -- 過去 2 列的累計和
      SUM(amount) OVER (
          PARTITION BY user_id
          ORDER BY order_date
          ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
      ) AS sum_3
    FROM demo_orders
    WHERE user_id = 'u01'
    ORDER BY order_date
""").fetchall()

for r in result:
    print(f"date={r[1]} amount={r[2]} ma_3={r[3]:.2f} ma_3_sym={r[4]:.2f} sum_3={r[5]}")
# 輸出:date=2025-09-01 amount=1200 ma_3=1200.00 ma_3_sym=1000.00 sum_3=1200
# 輸出:date=2025-10-01 amount=800 ma_3=1000.00 ma_3_sym=1166.67 sum_3=2000
# 輸出:date=2025-11-01 amount=1500 ma_3=1166.67 ma_3_sym=1150.00 sum_3=3500

這段展示了 frame 子句的三種寫法:ma_3 是不對稱視窗,只看過去 2 期加本期;ma_3_sym 是對稱視窗,看前後各 1 期加本期,常用於去雜訊;sum_3 則是只算總和,這在計算「過去 7 天收入」時會用到。可以看到第一筆 ma_3 只有 1200(樣本不足),ma_3_sym 則是把 800(只有未來 1 列可看)拉進來算平均,這就是 frame 邊界條件的差異,務必在寫報表時親自驗算一次。

用 QUALIFY 過濾視窗結果

昨天我們提過:視窗函式不能直接放在 WHERE 內。DBA 與程式設計師常用的兩種變通手段是「CTE 過濾」與 QUALIFY 子句。後者是 DuckDB 1.4 與新世代資料庫(Snowflake、BigQuery、Trino)原生支援的語法,比 CTE 更簡潔:

result = con.execute("""
    SELECT
      user_id,
      order_date,
      amount,
      ROW_NUMBER() OVER (
          PARTITION BY user_id ORDER BY amount DESC
      ) AS rn
    FROM demo_orders
    QUALIFY rn = 1
    ORDER BY user_id
""").fetchall()

print("每位使用者單筆最高金額:")
for r in result:
    print(f"  user={r[0]} date={r[1]} amount={r[2]} rn={r[3]}")
# 輸出:user=u01 date=2025-11-01 amount=1500 rn=1
# 輸出:user=u02 date=2025-10-20 amount=2200 rn=1
# 輸出:user=u03 date=2025-09-10 amount=4500 rn=1
# 輸出:user=u04 date=2025-10-30 amount=2400 rn=1

這個查詢找出每位使用者金額最高的一筆訂單。QUALIFY rn = 1 直接對 ROW_NUMBER() 的結果做過濾,不需先寫 CTE 再過濾,可以少寫兩層 SQL。注意這在 MySQL 與傳統 PostgreSQL 13 之前不支援,本系列以 DuckDB 1.4 為主,QUALIFY 是 DuckDB 原生語法,請善用這個特性。

最後一個常見的視窗函式與排名組合技:把每筆訂單算出「這是這位使用者第幾筆」。這個 ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date) 在使用者行為分析裡很常用,可以用來計算「這位使用者的第 N 次購買」。

result = con.execute("""
    SELECT user_id, order_date, amount,
           ROW_NUMBER() OVER (
               PARTITION BY user_id ORDER BY order_date
           ) AS purchase_seq
    FROM demo_orders
    ORDER BY user_id, order_date
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} 序號={r[3]}")
# 輸出:user=u01 date=2025-09-01 amount=1200 序號=1
# 輸出:user=u01 date=2025-10-01 amount=800 序號=2
# 輸出:user=u01 date=2025-11-01 amount=1500 序號=3
# 輸出:(其餘使用者依此累加)

這個查詢把所有訂單依使用者 + 日期順序編上序號,可以一眼看出每位使用者的購買時間軸。如果再配合 CASE WHEN purchase_seq = 1 THEN '首次' ... END,就可以為每筆訂單加上「新客/回客」等業務標籤,這在 CRM 資料分析裡非常實用。Day 24 做訂單分析維度建模時會用到。

用 NTILE 做分群切塊

NTILE(n) 是把資料切成 n 個等量的桶(bucket),並把每列標出所屬桶號。這在分層抽樣、客戶分群(如前 20%、中間 60%、後 20%)時很常見。我們依金額把 12 筆訂單切成 4 桶:

result = con.execute("""
    SELECT user_id, order_date, amount,
           NTILE(4) OVER (ORDER BY amount) AS quartile
    FROM demo_orders
    ORDER BY amount
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} quartile={r[3]}")
# 輸出:user=u03 date=2025-10-12 amount=300 quartile=1
# 輸出:user=u02 date=2025-09-15 amount=600 quartile=1
# 輸出:user=u01 date=2025-10-01 amount=800 quartile=1
# 輸出:user=u03 date=2025-11-18 amount=900 quartile=2
# 輸出:user=u02 date=2025-11-05 amount=1100 quartile=2
# 輸出:user=u01 date=2025-09-01 amount=1200 quartile=2
# 輸出:user=u04 date=2025-11-25 amount=1300 quartile=3
# 輸出:user=u01 date=2025-11-01 amount=1500 quartile=3
# 輸出:user=u04 date=2025-09-22 amount=1900 quartile=3
# 輸出:user=u04 date=2025-10-30 amount=2400 quartile=4
# 輸出:user=u02 date=2025-10-20 amount=2200 quartile=4
# 輸出:user=u03 date=2025-09-10 amount=4500 quartile=4

把 12 筆切成 4 桶時每桶剛好 3 筆,可以看到 12 筆按金額由小到大被均分到 1-4 桶。NTILE() 在邊界的處理是「前幾桶可能多 1 筆」,而不是「最後一桶多 N 筆」。實務上,NTILE(4) 常被用於「客戶分層」:前 1 桶是高價值、中間 2 桶是常客、最後 1 桶是流失或低價值。整個 DAG 建模與消費洞察常見這類四分位切法。

另一個 NTILE() 的變體:把每位使用者「內部」切成兩組(高消費、低消費)。同一份資料集,你可以這麼寫:

result = con.execute("""
    SELECT user_id, order_date, amount,
        NTILE(2) OVER (
            PARTITION BY user_id
            ORDER BY amount DESC
        ) AS bucket
    FROM demo_orders
    ORDER BY user_id, order_date
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} bucket={r[3]}")
# 輸出:user=u01 date=2025-09-01 amount=1200 bucket=1
# 輸出:user=u01 date=2025-10-01 amount=800 bucket=2
# 輸出:user=u01 date=2025-11-01 amount=1500 bucket=1
# 輸出:(依此類推)

這個變體的關鍵是 PARTITION BY user_id 先切每位使用者,ORDER BY amount DESC 再依金額由大到小排序,NTILE(2) 把每位使用者內部的訂單分成兩群。可以看到 u01 的 1500、1200 是 bucket=1(高消費),800 是 bucket=2(低消費)。在做 RFM 客戶分群時,這個寫法可以幫助快速標出每位使用者內部的高額訂單,是 Day 24 維度建模的常見技巧。

常見錯誤與踩雷

第一個雷:ROW_NUMBER 與 RANK 同分時的解讀錯誤。許多新手以為 RANK() 「比較好用」,結果在「分頁拿第 10 筆」時出錯。請牢記:要做分頁或限額抓取,永遠用 ROW_NUMBER();要做排名榜或呈現名次,用 RANK()。第二個雷:LAG()、LEAD() 的偏移量(offset)忘了寫,預設是 1,但實務上常常要跳兩列或三列。請寫成 LAG(amount, 2)、LEAD(amount, 3) 等具體偏移量。

第三個雷:frame 子句的 ROWS 與 RANGE 沒有預期效果。ROWS 是按列數計算,當資料有重複 ORDER BY 值時,RANGE 會把同值的列一起算;當你想嚴格限定「過去 3 列」就用 ROWS,若想限定「過去 30 天內」就用 RANGE BETWEEN INTERVAL 30 DAYS PRECEDING AND CURRENT ROW。這個差異是 Day 7 探討查詢計畫時的重要觀念。

第四個雷:QUALIFY 子句用在不支援的引擎上。DuckDB、PostgreSQL 18、Trino、Snowflake、BigQuery 支援,但舊版 MySQL 與 SQLite 不支援。如果你的下游系統不支援,請改用 CTE。先寫出 WITH ranked AS (...),再 SELECT ... WHERE rn = 1,這在所有 SQL 引擎都通用。

第五個雷:NTILE() 桶數量超過資料筆數。例如 5 筆資料切成 10 桶,會出現桶號超過桶數的情況,DuckDB 會限制桶號不超過實際桶數,但仍可能讓分析師困惑。請把桶數寫成 ceil(N / 每桶筆數) 的合理值。

當我們要做「過去 12 個月移動平均」這類報表時,LAG() 的偏移量參數就派上用場了。底下這段示範怎麼用 LAG(amount, 2) 一次跳兩列:

result = con.execute("""
    SELECT user_id, order_date, amount,
           LAG(amount, 1) OVER (
               PARTITION BY user_id ORDER BY order_date
           ) AS prev_1,
           LAG(amount, 2) OVER (
               PARTITION BY user_id ORDER BY order_date
           ) AS prev_2,
           amount - LAG(amount, 1) OVER (
               PARTITION BY user_id ORDER BY order_date
           ) AS diff_prev_1
    FROM demo_orders
    WHERE user_id IN ('u01', 'u02')
    ORDER BY user_id, order_date
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} prev_1={r[3]} prev_2={r[4]} diff_1={r[5]}")
# 輸出:user=u01 date=2025-09-01 amount=1200 prev_1=None prev_2=None diff_1=None
# 輸出:user=u01 date=2025-10-01 amount=800 prev_1=1200 prev_2=None diff_1=-400
# 輸出:user=u01 date=2025-11-01 amount=1500 prev_1=800 prev_2=1200 diff_1=700
# 輸出:user=u02 date=2025-09-15 amount=600 prev_1=None prev_2=None diff_1=None
# 輸出:(其餘省略)

這段展示了 LAG(amount, n) 的兩個變化:prev_1 是前 1 列、prev_2 是前 2 列,並且 diff_1 用算術表達式直接減去前者做差。在 u01 的第三筆可以看到 prev_2=1200(跳過 800 元那筆,直接讀兩列前),這對「跟上上個月比」特別有用。當你想要做「同期比去年同期」、「對齊雙月資料比對」這類需要跨期讀取的分析時,LAG() 的偏移量參數會非常方便。

另一個常見寫法是把 LAG() 與 CASE WHEN 配合,根據讀取到的前期值分類。例如下面這段把每筆訂單分成「高於前期」、「等於前期」、「低於前期」三類:

result = con.execute("""
    SELECT user_id, order_date, amount,
           CASE
               WHEN amount > LAG(amount) OVER (PARTITION BY user_id ORDER BY order_date)
                   THEN '高於前期'
               WHEN amount = LAG(amount) OVER (PARTITION BY user_id ORDER BY order_date)
                   THEN '等於前期'
               ELSE '低於前期'
           END AS trend
    FROM demo_orders
    ORDER BY user_id, order_date
""").fetchall()

for r in result:
    print(f"user={r[0]} date={r[1]} amount={r[2]} trend={r[3]}")
# 輸出:user=u01 date=2025-09-01 amount=1200 trend=低於前期
# 輸出:user=u01 date=2025-10-01 amount=800 trend=低於前期
# 輸出:user=u01 date=2025-11-01 amount=1500 trend=高於前期
# 輸出:(其餘類似)

這個寫法在第一筆雖然 LAG() 為 NULL,但 CASE WHEN 邏輯自動落到 ELSE,所以歸類為「低於前期」。實務上你可以把它改成更有意義的標籤,例如 WHEN ... THEN '回購成長',這樣每筆訂單就有一個業務意義的分類欄位,可以直接餵給儀表板做甜甜圈圖。整個 RFM 客戶分層、新客 vs 回客分析,都是這個模式的延伸。

效能與實務提醒

排名函式的效能議題主要在「排序成本」。當 PARTITION BY 切的群數量小、每群內列數大時(例如 100 群 × 10 萬列),DuckDB 需要對每群內部做排序,這是 O(N log N) 成本。如果你的查詢主要在做「取每群前 N 名」,可以考慮改用 ORDER BY ... LIMIT 與 subquery 搭配,這在大資料量時有時比視窗函式快。請用 EXPLAIN 比較兩條路的成本再決定。

實務上:LAG()、LEAD() 搭配 frame 子句是時間序列分析的最常見組合,但請務必把 ORDER BY 內的欄位加上索引(Day 7 會談)。當資料量到數百萬列時,這個索引能讓 LAG() 從全表掃描降到快速游標掃描,把查詢時間從數分鐘降到秒級。

另一個提醒:當你的同事或主管抱怨「為什麼這份報表這麼慢?」,第一個檢查點就是:是否有重複的視窗函式計算。例如一個查詢內有三個不同排序方向的 RANK(),每個都得單獨排序一次,這時把結果拆成多個 CTE 反而會更快。建議你跑一份 EXPLAIN ANALYZE 看每個視窗的成本,調優時比較有依據。

這裡有個小建議:把今天常用的視窗函式寫成一個查詢範本(例如 queries/rank_template.sql),未來做新報表時複製貼上改欄位即可。DuckDB CLI 對多行檔案的支援也很好,可以用 duckdb warehouse/de-journey.duckdb < queries/rank_template.sql 直接跑管線輸出。視窗函式與排名函式結合起來,可以應付 Day 24 訂單分析 80% 以上的需求,剩下的 20% 才是複雜的維度建模與 CTE 技巧,這會交給 Day 5 與 Day 23 處理。接著來回顧今天學過的關鍵寫法。

小結

今天把排名函式(ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE())與前後取值(LAG()、LEAD())一次到位,並把 frame 子句結合 SUM() 與 AVG() 寫成移動平均的標準範本。我們用消費事實表展示了「同查詢多排名」、「本期減前期」、「過去 N 期平均」這三個最常見的實戰需求,並用 QUALIFY 子句把視窗結果直接過濾,這比傳統 CTE 更簡潔。NTILE() 則是客戶分層切塊的核心,會在 Day 23、Day 24 維度建模被反覆提到。

把今天學到的關鍵詞抄進筆記本:排名函式、ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE()、LAG()、LEAD()、QUALIFY、移動平均、frame 子句。明天會把這兩天學的視窗函式與「資料表達力」的主題:CTE(Common Table Expression)做結合,把多層巢狀的查詢改寫為可讀性更高的鏈式結構,並且開始談「遞迴 CTE」這個 SQL 中最具威力的寫法。

結語

排名與移動分析是商業數據的兩大軸:前者告訴你「誰在前面」、後者告訴你「現在在走的方向」。這兩種分析都可以用純 SQL 寫出來,不需要進 Python 也不需要寫 ETL。我們今天的寫法是業界最常用的「視窗函式為主、輔助 CTE 與 QUALIFY」,在 DuckDB 與 Snowflake、BigQuery 都可以直接對應。資料工程與資料分析的差別就在這裡:分析師拿到現成的事實表就可以寫查詢,資料工程師要把事實表建好、索引做好、模型取捨做好——這也是 Day 6、Day 7、Day 23 會陸續展開的主題。

明天,我們會進入「CTE 與遞迴查詢」的主題:把過去兩天寫的多層視窗查詢改寫成更易讀的鏈式 WITH ... AS (... 結構,並開始談「遞迴 CTE」這個 SQL 標準中最具威力的進階寫法。我們會用階層式組織圖(員工找直屬主管)當範例,示範 WITH RECURSIVE 的標準寫法,並把它改成可整段執行的 Python + DuckDB 範例。

延伸資源

  • DuckDB 視窗函式完整指南:https://duckdb.org/docs/sql/window_functions.html。本篇所用 QUALIFY、NTILE、ROWS BETWEEN 等語法以這份為主。
  • LeetCode SQL Top 50 練習題:https://leetcode.com/studyplan/top-sql-50/。視窗函式題目密集,是練功好去處。
  • Mode Analytics SQL 視窗函式教學:https://mode.com/sql-tutorial/sql-window-functions/。包含排名函式差異圖解。
  • PostgreSQL 18 視窗函式文件:https://www.postgresql.org/docs/current/tutorial-window.html。查詢計畫與索引策略的權威來源。
  • 《SQL 視窗函式解析》(書籍):Antoni Olszowski 等,深入探討 frame 邊界與實務效能。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...