先用 query 建一張活躍客戶名單,再讓訂單闖兩道關卡:關卡一金額 >= 1000(實跑 9 筆剩 7 筆——999 差一元出局、1000 整整好過關);關卡二 how='inner' 對名單(7 筆剩 4 筆——5000 元大單也死,因為客戶 inactive;還有一筆 3000 元的孤兒單,客戶表裡根本查無此人)。活著出來的 4 筆,每筆多了一欄 region——merge 從客戶表帶進來的通行章。
閱讀下列程式,result 最終保留哪些資料?
active = customers.query("status == 'active'") # 名單:只留活躍客戶 result = (orders[orders['amount'] >= 1000] # 關卡一:金額 >= 1000 的訂單 .merge(active[['customer_id', 'region']], # 只帶 key 與 region 兩欄來併 on='customer_id', how='inner')) # 關卡二:兩邊都有的 customer_id 才留
想像 result 是夜店裡的人。門口站兩個保鑣:保鑣一看消費力——低消 1000,帶 999 的擋在門外(差一元也不行,但剛好 1000 放行——「至少」含等號);保鑣二對會員名單——名單是先用 query 印好的「活躍會員」清冊,不在名單上就出局,你消費力再高也一樣(實跑:5000 元的 O3 就死在這裡)。
兩關都過的人,手背蓋一個章——region:這個章原本不在訂單上,是保鑣二從會員清冊上抄過來的。這就是 merge 的雙重身分:既是過濾器(inner 刷人),又是搬運工(帶欄位進來)。
query("status == 'active'") 與 customers[customers['status']=='active'] 是同一件事的兩種寫法(實跑逐列相同)——題目故意一行用 query、一行用布林遮罩。② how='inner' = 交集:customer_id 兩邊都有的列才留。③ active[['customer_id','region']] 只挑兩欄來併——key 用來對名單、region 用來帶進 result,其他欄(status)不跟來。點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
四行,一行一行走完。右上角的「看位置」可以把這一行放回完整程式裡看。
active = customers.query("status == 'active'") # 6 位客戶裡挑出活躍的——名單存進 active
query 用一串文字描述篩選條件——引號裡的 status == 'active' 讀作「status 欄等於 'active' 的列」。實跑:6 位客戶剩 4 位(C1、C3、C4、C6),C2、C5 因 inactive 落選。它與 customers[customers['status']=='active'] 是同一件事的兩種寫法(實跑逐列相同)——題目故意兩種都用,考你認不認得出來。
result = (orders[orders['amount'] >= 1000] # 布林遮罩:金額 >= 1000 的訂單才往下走
這行用的是布林遮罩寫法:orders['amount'] >= 1000 對每筆訂單打 True/False,外層的 orders[...] 只留 True 的列。實跑:9 筆剩 7 筆——O2(800)與 O5(999)出局。注意邊界:>= 含等號,O4 的 1000 整整好過關;選項 A 說「至少 1000」,就是在講這個等號。開頭的 ( 是為了讓整條鏈可以優雅換行——跟結尾的 ) 成對。
.merge(active[['customer_id', 'region']], # 從名單只挑 key 與 region 兩欄
active[['customer_id', 'region']] 用雙中括號從名單挑兩欄:customer_id 是待會對名單用的 key、region 是要帶進 result 的贈品——status 等其他欄不跟來,結果表才乾淨。這是實務的好習慣:merge 前先把右表瘦身成「key + 要帶的欄」,省記憶體也防止欄位大爆炸。
on='customer_id', how='inner')) # 兩邊都有這個 customer_id 的列才留
on='customer_id' 指定用哪個欄對名單;how='inner' 指定對不到的怎麼辦:刷掉——只留兩邊都有的 customer_id(交集)。實跑:過了關卡一的 7 筆,再被刷掉 3 筆——O3(5000 元,但 C2 是 inactive)、O7(1500 元,C5 inactive)、O8(3000 元,C7 根本不在客戶表)——金額再大,名單對不到就出局。最終 4 筆存活:O1、O4、O6、O9,每筆帶上 region。
本站實跑的客戶表 6 位。第 1 行的 query 印出活躍名單——跟第 2 行用的布林遮罩其實是同一招的兩種寫法:
| customer_id | status | region | 下場 |
|---|---|---|---|
| C1 | active | 北區 | 入選名單 |
| C2 | inactive | 中區 | 落選 |
| C3 | active | 南區 | 入選名單 |
| C4 | active | 北區 | 入選名單 |
| C5 | inactive | 南區 | 落選 |
| C6 | active | 東區 | 入選(但它沒有訂單——記住它) |
.equals() 比對——逐列相同(True)。active 名單= C1、C3、C4、C6 共 4 位。題目一行用 query、一行用遮罩不是手滑,是考你認得出「它們是同一件事」——考場上兩種寫法都要能秒讀。每一筆訂單都要闖兩道關卡。點任一張卡,看它的完整死因或生還理由:
order_id / customer_id / amount / region,region 是 merge 從客戶名單帶進來的。兩個亮點:O4 的 1000 元整整好過關(>= 含等號);O3 的 5000 元死在第二關——金額是全場第二高,但 C2 不在活躍名單上。選項 B 說「所有訂單都保留;對不到客戶者的 region 填入 active」。把 how 真的轉一圈(全部實跑),看它錯在哪:
剩下兩個選項描述的集合,我們也真的算出來了。開庭,兩件展品:
這題就是 SQL 的老朋友換 pandas 皮。對照表一次備齊:
-- 同一件事的 SQL 寫法 SELECT o.order_id, o.customer_id, o.amount, c.region # 訂單欄位+帶入 region FROM orders o # 左表:訂單 INNER JOIN customers c ON o.customer_id = c.customer_id # 關卡二:對名單取交集 WHERE o.amount >= 1000 AND c.status = 'active' # 關卡一+名單條件
| how | 語意 | 本題實跑筆數 | 對不到的下場 |
|---|---|---|---|
| inner(題目) | 交集:兩邊都有 key 才留 | 4 | 整列刷掉 |
| left | 左表全留 | 7 | 右欄補 NaN(O3/O7/O8) |
| right | 右表全留 | — | 左欄補 NaN(C6 會以空訂單現身) |
| outer | 聯集:兩邊都全留 | 8 | 缺的那側補 NaN |
一對多陷阱(實跑):merge 不是 VLOOKUP——key 在右表出現幾次,左表那列就複製幾份。我們故意把名單裡的 C1 重複一次再併:result 從 4 筆膨脹成 6 筆(O1、O9 各複製兩份)。實務上 merge 前先確認右表 key 唯一(active['customer_id'].is_unique),報表金額才不會默默翻倍——「不報錯的錯」家族又一員。效率備忘:題目「先篩選、再 merge」的順序是好習慣——先把 9 筆砍成 7 筆再對名單,資料量大時省時省記憶體。
三段敘述對三段程式,逐字命中:「至少 1000」= >= 1000(含等號——實跑 O4 的 1000 過關、O5 的 999 出局);「對到 active 客戶」= merge(active[...], how='inner')(實跑刷掉 inactive 的 O3、O7 與孤兒單 O8);「帶入 region」= merge 的搬運工身分(result 四欄的最後一欄)。實跑 result:O1、O4、O6、O9 共 4 筆。
inner 對不到就整列刷掉——「所有訂單都保留」直接與 how='inner' 矛盾(實跑 9 筆只剩 4 筆)。就算改成 how='left' 也只有 7 筆(關卡一刷掉的回不來),且對不到的 region 是 NaN(實跑 O3、O7、O8 三格全是 NaN)——pandas 不會把缺格填成 'active':那是 status 欄的值,跟 region 欄無關。B 把 how、填值、欄位三件事全講錯。
兩個條件都跟程式相反(< 對 >=、inactive 對 active)。更妙的是把 C 描述的集合真的算出來:小額單只有 O2(800)與 O5(999),主人 C1、C3 都是 active——inactive 名單對不到任何一筆,實跑 0 筆、空集合。連反例都懶得存在的選項。
「沒有訂單的客戶」要用反向合併找:active[~active['customer_id'].isin(orders['customer_id'])]——實跑只有 C6 一位。而題目的 inner merge 是「訂單 × 名單」的交集:每一列 result 都必然掛著一筆訂單,沒有訂單的客戶在數學上就進不來(實跑 result 查無 C6)。D 描述的正好是 inner 的補集方向——考場常拿來釣「把 join 想成名單管理」的人。
再看一次同四行程式。這次你手上有口訣了:篩金額(含等號)、對名單(inner 取交集)、region 是帶進來的章。
active = customers.query("status == 'active'") # 名單:C1/C3/C4/C6 result = (orders[orders['amount'] >= 1000] # 關卡一:9 → 7 .merge(active[['customer_id', 'region']], # 帶 key 與 region on='customer_id', how='inner')) # 關卡二:7 → 4
八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。
query 印活躍名單(6 位 → 4 位);第 2–4 行讓訂單過兩道關卡——金額篩選、inner 對名單——存進 result。query("status == 'active'") ≡ customers[customers['status']=='active']——題目一行一種寫法,考你認出它們是同一件事。order_id / customer_id / amount / region——region 從客戶名單帶進來;status 沒跟來(第 3 行只挑了兩欄)。is_unique,金額才不會默默翻倍。「條件篩選 + merge」是中級科目二大數據處理分析與應用的高頻考點,pandas 版的 SQL 題。最常見的六種問法:① 給 query/布林遮罩+merge 的程式問最終保留誰(本題——認出兩道關卡與帶欄);② 問 how 四兄弟的差別(inner 交集/left 保左/right 保右/outer 聯集);③ 問對不到 key 的下場(inner 刷掉、其他補 NaN——絕不會「填入某個字串」);④ 問 query 與布林遮罩是否等價(是——兩種寫法都要會讀);⑤ 問 >= 與 > 的邊界(「至少」含等號);⑥ 問 merge 鍵重複的後果(一對多展開、列數膨脹)。一句口訣:篩金額、對名單、inner 取交集——對不到:inner 刷掉、left 補 NaN。
兩種都要會讀,因為它們是同一件事:customers.query("status == 'active'") 把條件寫成一串文字(引號裡的欄名不用再加 df 名),customers[customers['status']=='active'] 用布林遮罩——對每列打 True/False 再取 True。本站實跑兩者 .equals() 比對逐列相同。query 的優點是條件複雜時好讀("amount >= 1000 and status == 'active'" 一氣呵成)、字串裡的字串用不同引號包;遮罩的優點是不怕欄名有空格、變數帶入直覺。本題故意一行一種,就是在考「你認不認得出它們等價」。
對 key 的值(本題 customer_id)取交集:一列訂單要留下,它的 customer_id 必須「在右表也出現」。實跑七筆過了金額關的訂單裡:C1、C3、C4 在 active 名單 → O1/O4/O6/O9 留下;C2、C5 不在名單(inactive)、C7 連 customers 表都沒有 → O3/O7/O8 整列消失。注意兩個細節:① 交集是對「值」不是對「列數」——C1 有兩筆訂單,兩筆都留;② inner 之後右表的欄(region)跟著貼上來——merge 同時是過濾器與搬運工。
看 how。inner:對不到的列整列刷掉——根本輪不到 region 出場(本題)。left/outer:列保留、對不到的右表欄位補 NaN(缺值)——實跑 how='left' 之下 O3、O7、O8 的 region 全是 NaN。pandas 絕不會自動填入某個字串——選項 B 的「region 填入 active」是把 status 欄的值幻想成填充物;真要填值得自己來:result['region'].fillna('未知')。考場判法:看到「對不到就填入某某」的選項,先想「那要 fillna 才辦得到」。
三個理由。①乾淨:不挑欄的話 status 也會跟著併進來,result 多出無用欄位;②防撞名:右表欄位一多,跟左表同名的欄會被加上 _x/_y 後綴,讀表變災難;③省資源:實務上右表可能幾十欄、幾百萬列——先瘦身成「key + 要帶的欄」再併,記憶體與速度差很多。原則:merge 的右表=key + 贈品欄,其他都別帶。同一個精神也在「先篩選再 merge」:題目先把 9 筆砍成 7 筆才對名單——資料量大時順序就是效能。
那叫反向合併(anti-join),pandas 沒有 how='anti',慣用兩招:① active[~active['customer_id'].isin(orders['customer_id'])]——名單裡「不在訂單客戶集合」的人(實跑:只有 C6);② merge(..., how='left', indicator=True) 之後取 _merge == 'left_only' 的列。而題目的 inner merge 是「訂單 × 名單」的交集——result 每一列都必然掛著一筆訂單,沒訂單的 C6 在數學上進不來(實跑 result 查無 C6)。D 描述的正是 inner 的「補集方向」——搞混 join 與 anti-join 是考場經典陷阱。
一對多展開:右表 key 出現幾次,左表對到的那列就複製幾份——merge 是關聯代數的 join,不是 Excel 的 VLOOKUP(只取第一筆)。本站實跑:故意把名單裡的 C1 重複一次再併,result 從 4 筆膨脹成 6 筆(O1、O9 各出現兩次)——如果接著 result['amount'].sum(),C1 的金額就被默默算了兩倍,不報錯。防身三招:merge 前驗 right['key'].is_unique;merge 時加 validate='m:1'(違反就報錯);事後對筆數(合併後列數不該超過左表,若是 m:1)。
逐句對應:orders[orders['amount'] >= 1000] = WHERE amount >= 1000;merge(..., on='customer_id', how='inner') = INNER JOIN ... ON;query 的條件=另一段 WHERE(或先建 CTE)。會 SQL 的人:把 merge 讀成 join 就通了;不會 SQL 的人:這頁學完你已經會讀 INNER JOIN 了。系列串接:本頁與第 16 頁(melt+barplot)同屬科目二「資料整形三部曲」的前兩部——melt 管「表的形狀」、query+merge 管「表的內容與關聯」;「一對多展開不報錯」則加入第 9、10、12、14、15 頁的「不報錯的錯」家族——pandas 篇的代表選手。