跳到主要內容
Lab Grimoire
TW
請喝咖啡
動手實作

Python 與 pandas 讀碼:看懂程式片段在做什麼

以讀碼角度整理 pandas 的常用寫法:loc 與 iloc、多條件篩選、groupby、merge、melt、Int64、分塊讀取大檔與 seaborn 常用圖。

作者
CY
發表
AI 的資料基礎:從統計到生成式 AI · 第 8/19 篇

本系列第 8 篇|這篇回答:拿到一段 pandas 程式,怎麼看懂它對資料做了什麼? 讀完這篇,你應該能:

  • 看懂常見 pandas 片段每一步在做什麼,並預測輸出的形狀
  • 分辨 loc/iloc、寬表/長表等易錯點
  • 知道大檔案與缺失值整數的處理法

一、原理:資料表的兩個物件

多數 pandas 操作回傳新物件,要重新指定才會保留。

詳細說明

拿到一段已經寫好的程式時,要能判斷它跑完手上是什麼。先固定兩個物件。

Series 是一維的一欄,自帶索引(index)。DataFrame 是二維表,多個 Series 並排,共用同一組列索引。shape 的順序是(列數, 欄數)。

import pandas as pd

s = pd.Series([10, 5, 3], index=['a', 'b', 'c'])
df = pd.DataFrame({'a': [10, 5, 3], 'b': [1, 2, 0]})
print(s.shape)
print(df.shape)

輸出長什麼樣:s.shape 是 (3,),只有長度。df.shape 是 (3, 2),3 列、2 欄。

大多數 pandas 操作回傳新物件,原表保持原樣。變數要指到新結果時,必須重新指定,例如 df = df.dropna()。

import pandas as pd

df = pd.DataFrame({'a': [10, None, 3]})
df2 = df.dropna()
print(df.shape)
print(df2.shape)

輸出長什麼樣:df.shape 仍是 (3, 1),缺失還在。df2 是新表,3 列去掉 1 列缺失,df2.shape 是 (2, 1)。

方法串接(method chaining) 從左讀到右。每一步的輸出是下一步的輸入。讀的時候先寫下「現在是什麼型別、什麼形狀」,再看下一個點。型別若從 DataFrame 變成 Series,後面接得上的方法也跟著變。

import pandas as pd

df = pd.DataFrame({'a': [1, 5, 10, 3, 2]})
out = df[['a']].head(2)
print(out.shape)

輸出長什麼樣:df 是 (5, 1)。[['a']] 仍是 DataFrame,形狀還是 (5, 1)。head(2) 留下前 2 列,out.shape 是 (2, 1)。

二、方法(每點附程式)

上一節的讀法這裡照用。每個方法只補一件事:它回傳的型別與形狀。程式不用在考場上重打,要會把它拆開。

瀏覽

describe 只對數值欄給八個統計量,其中沒有眾數。

詳細說明

head 不寫列數時取前 5 列;表不到 5 列就全部留下。shape 回傳(列數, 欄數)。info 把每一欄的型別與非空數印出來。describe 只對數值欄給 count、mean、std、min、25%、50%、75%、max;std 是標準差(standard deviation),50% 那一列是中位數(median)。這八列裡沒有眾數(mode)。include='all' 才把類別欄也放進結果。

import pandas as pd

df = pd.DataFrame({'sales': [10, 5, 3], 'city': ['N', 'S', 'N']})
print(df.shape)
print(df.head(2).shape)
df.info()
print(df.describe().index.tolist())
print(df.describe(include='all').columns.tolist())

輸出長什麼樣:shape 是 (3, 2)。head(2) 是 (2, 2) 的 DataFrame。info 印出 sales、city 的型別,非空數都是 3。describe() 的列名是那八個統計量,欄只有 sales。include='all' 的欄才同時有 sales 與 city。

選取

loc 依標籤且切片含終點,iloc 依位置且切片不含終點。

詳細說明

df['a'] 回傳 Series。df[['a', 'b']] 多一層方括號,回傳 DataFrame,哪怕只點一欄也一樣。

loc 依標籤選,切片含終點。iloc 依位置選,切片不含終點。預設整數索引時,標籤剛好就是 0、1、2、…,所以兩種切片的差只剩「終點算不算」。標籤 0 到 2 含終點的個數是 2 - 0 + 1 = 3;位置 0 到 2 不含終點的個數是 2 - 0 = 2。

import pandas as pd

df = pd.DataFrame({'a': range(10), 'b': range(10)})
print(df['a'].shape)
print(df[['a']].shape)
print(df.loc[0:2].shape)
print(df.iloc[0:2].shape)

輸出長什麼樣:df['a'] 是 (10,) 的 Series。df[['a']] 是 (10, 1) 的 DataFrame。loc[0:2] 是 (3, 2),3 列。iloc[0:2] 是 (2, 2),2 列。

篩選

多條件用 &、|、~ 且各自加括號,不能用 and 或 or。

詳細說明

多條件用 &(且)、|(或)、~(非),每個條件各自加括號。Python 的 and/or 不能用來連接 Series:它們要求整條 Series 收成一個真假值,做不到,這一行會報錯。

另一個寫法 df['a'] > 5 & df['b'] == 'x' 也會錯。& 的優先順序高於比較運算,這行不會先做成兩個比較再取交集。

import pandas as pd

df = pd.DataFrame({'a': [10, 10, 3, 1], 'b': ['x', 'y', 'x', 'x']})
both = df[(df['a'] > 5) & (df['b'] == 'x')]
either = df[(df['a'] > 5) | (df['b'] == 'y')]
small = df[~(df['a'] > 5)]
print(both.shape, either.shape, small.shape)

輸出長什麼樣:a > 5 的是前兩列(值都是 10);其中 b == 'x' 的只有第 1 列,所以 both 是 (1, 2)。either 含前兩列,是 (2, 2)。small 是後兩列(3 與 1),也是 (2, 2)。三個結果都是 DataFrame。

缺失與型別

int64 不能存 NaN,大寫 Int64 才能把缺失留在整數欄。

詳細說明

isna().sum() 給出每欄的缺失數,是一條以欄名為索引的 Series。dropna() 與 fillna(值) 都回傳新表。

astype('int64') 與 astype(int) 不能存 NaN,欄裡有缺失就會報錯。Int64(大寫 I)是可空整數(nullable integer),缺失留得住,其餘格子仍是整數。先 fillna(0) 再轉整數,缺失會變成 0;年齡欄裡就是 0 歲,缺失已經不在了。astype('category') 用來降低重複字串的記憶體,列數不變。

import pandas as pd

df = pd.DataFrame({'age': [10, None, 5], 'city': ['N', 'S', 'N']})
print(df.isna().sum())
print(df.dropna().shape)
filled = df.fillna(0)
age_ok = df['age'].astype('Int64')
df['city'] = df['city'].astype('category')
print(filled.shape)
print(age_ok.dtype)
print(df['city'].dtype)

輸出長什麼樣:isna().sum() 裡 age 的缺失數是 1,city 是 0。dropna() 是 (2, 2),3 列去掉 1 列。fillna(0) 仍是 (3, 2),缺的那一格變成 0,原表 df 沒被這兩行改掉。age_ok 的型別是 Int64,中間那一格仍是缺失。city 的型別是 category,還是 3 列。

日期與分組

時間用 .dt 取星期與小時,groupby 先分組再取欄再加總。

詳細說明

pd.to_datetime 把時間字串收成時間型別。之後用 .dt.dayofweek 取星期,星期一為 0;用 .dt.hour 取小時。兩條都是與列數相同的整數 Series。

import pandas as pd

df = pd.DataFrame({'ts': ['2010-03-01 10:00:00', '2010-03-02 05:00:00']})
df['ts'] = pd.to_datetime(df['ts'])
print(df['ts'].dt.dayofweek.shape)
print(df['ts'].dt.hour.shape)

輸出長什麼樣:兩者都是 (2,)。列數與原表相同。

groupby 要拆開讀。df.groupby('region')['sales'].sum() 先分組、再取一欄、再加總,回傳以 region 為索引的 Series。.agg(['mean', 'count']) 點了兩個彙總,回傳 DataFrame,欄數等於函數個數。.sort_values(ascending=False) 讓數值大的排前面。.head(3) 切的是列,不是欄。nlargest(3, 'sales') 不先加總,直接從原表取該欄最大的 3 列,所以仍是 DataFrame。value_counts() 算各值次數,預設由多到少,回傳 Series。

import pandas as pd

df = pd.DataFrame({
    'region': ['N', 'N', 'S', 'E', 'W'],
    'sales': [10, 5, 3, 1, 2],
})
s = df.groupby('region')['sales'].sum()
t = df.groupby('region')['sales'].agg(['mean', 'count'])
top = s.sort_values(ascending=False).head(3)
big = df.nlargest(3, 'sales')
print(s.shape, t.shape, top.shape, big.shape)
print(top.index.tolist())
print(pd.Series(['x', 'x', 'x', 'y']).value_counts().index.tolist())

輸出長什麼樣:5 列裡 N 出現兩次,分組後是 4 個區域,所以 s.shape 是 (4,),t.shape 是 (4, 2)。N 的總額是 10 + 5 = 15,其後是 S 的 3、W 的 2、E 的 1;top 取前 3 個,索引是 N、S、W,top.shape 是 (3,)。big 是原表裡 10、5、3 那 3 列,shape 是 (3, 2),裡面會有兩列 N。value_counts 的索引先是 x(3 次)再是 y(1 次)。

合併、寬表與長表

left 保留左表全部列,melt 把寬表欄名收成一欄的值。

詳細說明

merge 的 how='left' 保留左表全部列,右表對不到的欄是 NaN。how='inner' 只留兩邊都有的鍵。pd.concat([df1, df2]) 上下堆疊,列數相加,欄仍是原來的欄。

import pandas as pd

orders = pd.DataFrame({'user_id': [1, 2, 3, 5, 10], 'amt': [10, 5, 3, 1, 2]})
users = pd.DataFrame({'user_id': [1, 2], 'city': ['N', 'S']})
left = pd.merge(orders, users, on='user_id', how='left')
inner = pd.merge(orders, users, on='user_id', how='inner')
stacked = pd.concat([
    pd.DataFrame({'a': [1, 2]}),
    pd.DataFrame({'a': [3, 5]}),
])
print(left.shape, inner.shape, stacked.shape)

輸出長什麼樣:訂單 5 列,其中 user_id 為 3、5、10 的 3 列不在 users。left.shape 是 (5, 3),這 3 列的 city 是 NaN。inner 只留對得到的 2 列,5 - 3 = 2,inner.shape 是 (2, 3)。stacked 是 2 + 2 = 4 列、1 欄,(4, 1)。merge 與 concat 都回傳新表,users 的列數不會被改寫。

melt 把寬表轉成長表:欄名收成一欄的值。pivot_table 反向,長表轉寬表,並依 aggfunc 聚合。seaborn 多數繪圖函式偏好長表,也就是一列一筆、類別放在同一欄。

import pandas as pd

wide = pd.DataFrame({
    'store': ['A', 'B'],
    'Jan': [10, 5],
    'Feb': [3, 1],
    'Mar': [2, 0],
})
long = pd.melt(
    wide,
    id_vars=['store'],
    value_vars=['Jan', 'Feb', 'Mar'],
    var_name='month',
    value_name='sales',
)
back = long.pivot_table(index='store', columns='month', values='sales', aggfunc='sum')
print(long.shape, back.shape)

輸出長什麼樣:2 家店 × 3 個月 = 6 列,加上 store、month、sales 三欄,long.shape 是 (6, 3)。轉回去後一列一家店、一個月一欄,back.shape 是 (2, 3)。

逐元素、大檔與 One-Hot

map 逐元素換值,大檔分塊讀,One-Hot 一值一個 0/1 欄。

詳細說明

map 用字典逐元素換值;指定回 df['b'] 時,這一次會改到 df,因為寫了指定。np.where(條件, 真值, 假值) 對整欄做二選一。apply(func, axis=1) 逐列跑,比較慢;同一件事能寫成整欄運算時,就用整欄運算。

import numpy as np
import pandas as pd

df = pd.DataFrame({'a': ['M', 'F', 'M']})
df['b'] = df['a'].map({'M': 0, 'F': 1})
num = pd.DataFrame({'a': [1, -1, 0]})
num['sign'] = np.where(num['a'] > 0, '正', '負')
num['by_apply'] = num.apply(lambda r: r['a'] + 1, axis=1)
print(df['b'].tolist())
print(num['sign'].tolist())
print(num['by_apply'].tolist())

輸出長什麼樣:b 是 [0, 1, 0],df 變成 (3, 2)。sign 是 ['正', '負', '負'],0 不大於 0,所以落在「負」。by_apply 是 [2, 0, 1],也就是 1 + 1、-1 + 1、0 + 1;同一結果可寫成 num['a'] + 1。

檔案大於記憶體時,pd.read_csv(..., chunksize=1_000_000) 回傳可迭代的分塊讀取器,一次交一塊。chunksize 要配 usecols(只讀需要的欄)與 dtype(指定較小的型別)。每一塊自己是 DataFrame。

import pandas as pd

reader = pd.read_csv(
    'big.csv',
    chunksize=1_000_000,
    usecols=['region', 'sales'],
    dtype={'region': 'category'},
)
for chunk in reader:
    block_total = chunk['sales'].sum()

輸出長什麼樣:reader 不是一張已裝好的大表。迴圈裡的 chunk 最多 1,000,000 列、2 欄;block_total 是這一塊的銷售合計,一個數。

pd.get_dummies(df, columns=['city']) 把指定欄拆成 One-Hot 欄位,每個不同的值一個 0/1 欄。

import pandas as pd

df = pd.DataFrame({'city': ['N', 'S', 'N']})
out = pd.get_dummies(df, columns=['city'])
print(out.shape)

輸出長什麼樣:3 列不變,city 換成 2 個 0/1 欄,shape 是 (3, 2)。N 所在的列,對應欄為 1、另一欄為 0。

seaborn 與切分後的縮放

seaborn 各圖對應不同資料形狀;縮放在切分後只用訓練集 fit。

詳細說明

seaborn 這幾個函式看的是圖種與資料形狀。histplot 看一個數值欄的分佈。boxplot 看各組的分佈,x 是組別、y 是數值。scatterplot 一列一個點。countplot 畫類別次數,高度對應次數,回傳的是圖。heatmap 吃的是矩陣:df.corr(numeric_only=True) 只留數值欄,做成相關矩陣,annot=True 把係數寫進格子,vmin=-1、vmax=1 把顏色範圍定在 -1 到 1。

import pandas as pd
import seaborn as sns

df = pd.DataFrame({
    'a': [1, 2, 3, 5, 10],
    'b': [10, 5, 3, 2, 1],
    'group': ['x', 'x', 'y', 'y', 'x'],
    'value': [1, 3, 5, 2, 10],
    'cat': ['x', 'y', 'x', 'x', 'y'],
})
sns.histplot(df, x='a')
sns.boxplot(data=df, x='group', y='value')
sns.scatterplot(data=df, x='a', y='b')
sns.countplot(data=df, x='cat')
sns.heatmap(df.corr(numeric_only=True), annot=True, cmap='coolwarm', vmin=-1, vmax=1)

輸出長什麼樣:每個呼叫畫一種圖;像這樣連續寫在同一段時,它們會疊在同一張畫布上,實務上會各自開一張圖(例如每次先 plt.figure())。散佈圖有 5 個點。cat 裡 x 有 3 列、y 有 2 列,次數圖就是這兩根長條。corr 排除 group 與 cat,剩下 a、b、value,熱圖是 3×3。

scikit-learn 的片段先看左邊接到哪四個變數。train_test_split 的順序是訓練集的 X、測試集的 X、訓練集的 y、測試集的 y。test_size=0.2 是測試集所佔的列比例,stratify=y 讓兩邊的類別比例跟原資料一樣,random_state=42 把抽取固定下來。

import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.DataFrame({
    'a': [1, 2, 3, 5, 10, 1, 2, 3, 5, 10],
    'y': [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
})
X = df[['a']]
y = df['y']
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)
print(len(X_train), len(X_test))

輸出長什麼樣:10 列 × 0.2 = 2 列進測試集,訓練集是 10 - 2 = 8 列。y 兩類各 10 / 2 = 5 列;比例要保持,測試集 2 列就是一類 1 列,訓練集每類剩 5 - 1 = 4 列。X 用雙層方括號,所以 X_train 仍是 DataFrame。

縮放器接在切分之後。fit_transform 用訓練集計算參數並轉換訓練集;transform 用同一組參數轉換測試集,不再重算。

X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)

輸出長什麼樣:兩行都是縮放後的訓練特徵與測試特徵,列數分別跟 X_train、X_test 相同。參數來自訓練集那一次 fit。測試集若改呼叫 fit_transform,參數會改用測試集重算,兩邊的尺度基準就不同了。

三、容易混淆的地方

下表左欄是成對出現的寫法,中欄是輸出差在哪,右欄是實際讀程式時可觀察的線索。

容易混淆 差別在哪 實際遇到時的線索
loc 與 iloc loc 依標籤、切片含終點;iloc 依位置、切片不含終點 預設整數索引時,程式碼中的 loc[0:2] 取 3 列,iloc[0:2] 取 2 列
df['a'] 與 df[['a']] 一層方括號是 Series;兩層是 DataFrame,即使只有一欄 下游方法要求表格輸入,或報表要確認輸出的維度與欄數時,需分清兩者
& 與 and &、|、~ 是元素級,每個條件要括號;and/or 要單一真假,Series 會報錯 程式碼審查看到篩選式用了 and,或 & 緊貼比較運算卻沒有括號時,應檢查條件寫法
int64 與 Int64 小寫的 int64 與 astype(int) 不能存缺失;大寫 I 的 Int64 可以 資料同時需要整數型態與保留缺失值時,需使用 Int64
melt 與 pivot_table melt 寬轉長,欄名變成值;pivot_table 長轉寬,並做聚合 圖表流程要把每個月各一欄的寬表交給 seaborn 時,先轉為長表
merge 的 left 與 inner left 留左表全部列,對不到的欄是 NaN;inner 只留兩邊都有的 合併後需要保留對不到鍵的列時用 left;只保留兩表都匹配的列時用 inner
fit_transform 與 transform fit_transform 在訓練集上計算參數並轉換;transform 沿用該參數 資料流程用同一個 scaler 先 fit_transform 訓練集,再 transform 測試集

四、基礎練習

第 1 題

某電商的訂單表 df 有 10 列,使用預設整數索引。下面兩行各回傳幾列?

df.loc[0:2]
df.iloc[0:2]
  • (A) 兩行都回傳 2 列
  • (B) loc 回傳 2 列,iloc 回傳 3 列
  • (C) loc 回傳 3 列,iloc 回傳 2 列
  • (D) 兩行都回傳 3 列
看答案與解析

答案:C

預設整數索引的標籤就是 0、1、2、…。loc 照標籤選,含終點,標籤 0、1、2 都在,個數是 2 - 0 + 1 = 3,所以 3 列。iloc 照位置選,不含終點,位置 0 與 1,個數是 2 - 0 = 2,所以 2 列。

  • (A) 把 loc 也當成不含終點,少算了標籤 2。
  • (B) 把含終點與不含終點對調了。
  • (D) 把 iloc 也當成含終點,多算了位置 2。

第 2 題

某電商的表有 region 與 sales。下面這一行做完,得到的是什麼?

df.groupby('region')['sales'].sum().sort_values(ascending=False).head(3)
  • (A) 原始資料中銷售額最高的前 3 筆交易
  • (B) 各區銷售總額最高的前 3 區,型別是以 region 為索引的 Series
  • (C) 各區銷售總額最低的 3 區
  • (D) 一個有 3 個欄位的 DataFrame
看答案與解析

答案:B

groupby('region')['sales'].sum() 先把每區的銷售加總,得到以 region 為索引的 Series。sort_values(ascending=False) 讓總額大的排前面。head(3) 留下這個 Series 的前 3 個區域,也就是總額最高的 3 區。

  • (A) 分組加總之後已經不是單筆交易;原表上直接取 3 列是 nlargest 那一種寫法。
  • (C) ascending=False 是由大到小,會把總額最低的區域排到後面。
  • (D) 選一欄再 sum 得到的是 Series;head(3) 限制的是列數。欄數變成 2 的是 .agg(['mean', 'count']) 這種列出多個函數的寫法。

第 3 題

某電商的會員年齡有缺失。想轉成整數,同時把缺失留在原位。應使用哪一行?

age = pd.Series([10, None, 5])
  • (A) age.astype(int)
  • (B) age.astype(float)
  • (C) age.fillna(0).astype(int)
  • (D) age.astype('Int64')
看答案與解析

答案:D

'Int64' 的 I 是大寫,型別是可空整數。10 與 5 變成整數,中間的缺失仍留在原位。

  • (A) astype(int) 不能存放 NaN,這條 Series 有缺失,執行會報錯。
  • (B) astype(float) 可以留下缺失,但型別是浮點數,不是整數。
  • (C) fillna(0) 先把缺失寫成 0,再轉整數後那一格是 0 歲,缺失沒有被保留。

第 4 題

某連鎖的門市表是寬表:一列一家店,Jan、Feb、Mar 各是一欄銷售額。想得到 store、month、sales 三欄,供 seaborn 畫各月的線。應使用哪一段?

# wide 的欄位是 store、Jan、Feb、Mar
  • (A) pd.melt(wide, id_vars=['store'], value_vars=['Jan', 'Feb', 'Mar'], var_name='month', value_name='sales')
  • (B) wide.pivot_table(index='store', columns='month', values='sales', aggfunc='sum')
  • (C) wide.T
  • (D) pd.concat([wide, wide])
看答案與解析

答案:A

melt 把 Jan、Feb、Mar 的欄名收進 month,格子裡的數字收進 sales,store 留在 id_vars。一家店一個月一列,得到長表三欄。seaborn 多數繪圖函式吃這種長表。

  • (B) pivot_table 是長表轉寬表,方向相反;這張寬表上也還沒有 month、sales 這兩欄可以放進參數。
  • (C) T 是轉置(transpose),行列對調,不會產生 store、month、sales 三欄。
  • (D) concat 把同一張表上下接兩次,列數加倍,Jan、Feb、Mar 仍是三個欄,欄名不會變成 month 裡的值。

第 5 題

某工廠有一份 50GB 的 CSV,記憶體放不下整份檔。不改用分散式框架,單機要能處理。應使用哪一種讀法?

# 檔名是 big.csv,不要一次把整份檔放進記憶體
  • (A) pd.read_csv('big.csv', low_memory=False)
  • (B) 先 pd.read_csv('big.csv') 讀成 df,再做 df.T
  • (C) pd.read_csv('big.csv', chunksize=1_000_000, usecols=['sales', 'region'], dtype={'region': 'category'}),再逐塊處理
  • (D) 把 CSV 改存成 Excel 再讀入
看答案與解析

答案:C

chunksize=1_000_000 回傳分塊讀取器,每一塊最多 1,000,000 列,整份 50GB 不必同時放進記憶體。usecols 只讀點名的欄,dtype 把重複的 region 定成 category,少佔記憶體。處理寫在逐塊的迴圈裡。

  • (A) low_memory=False 仍把整份 CSV 讀成一張表,只改變型別推斷的方式,檔案還是一次進記憶體。
  • (B) df.T 得先有整張表才能轉置,轉置也不會把 50GB 變小。
  • (D) 改存 Excel 仍是整檔讀寫,沒有分塊讀取器,放不進記憶體的問題還在。

第 6 題

某電商想留下 a 大於 5、而且 b 為 'x' 的列。有人這樣寫。這一行會怎樣?

df[df['a'] > 5 and df['b'] == 'x']
  • (A) 與 df[(df['a'] > 5) & (df['b'] == 'x')] 等價,會篩出相同的列
  • (B) 會報錯;應改成 df[(df['a'] > 5) & (df['b'] == 'x')]
  • (C) 回傳一個布林值
  • (D) 只檢查第一列,就決定哪些列留下
看答案與解析

答案:B

and 要求左右兩邊各自變成一個真假值。兩邊都是 Series,不能收成單一真假,所以這一行報錯,篩選不會開始。元素級的「且」要寫 &,兩個比較各自加括號。

  • (A) and 與 & 不是同一種運算,不能互換,也不會篩出列。
  • (C) 這一行在判定真假時就失敗,不會回傳布林值,也不會回傳子表。
  • (D) 它沒有去讀第一列再決定去留;錯誤來自整條 Series 不能交給 and。

第 7 題

某電商把訂單與會員合併,寫法如下。orders 裡有 3 筆的 user_id 在 users 找不到。結果會怎樣?

pd.merge(orders, users, on='user_id', how='left')
  • (A) 這 3 筆訂單被刪除
  • (B) 因為對不到鍵而報錯
  • (C) users 會多出 3 列
  • (D) 這 3 筆訂單仍保留,來自 users 的欄位為 NaN
看答案與解析

答案:D

how='left' 以 orders 為左表,左表的列都留著。這 3 筆對不到會員,列還在結果裡,從 users 帶過來的欄是 NaN。merge 回傳新表。

  • (A) 刪掉對不到的列是 how='inner' 的結果。
  • (B) 對不到鍵不會因此報錯,左合併會用 NaN 補上右表欄位。
  • (C) 這次呼叫不會往 users 寫入列;多出來的是合併結果裡那 3 筆訂單,不是 users 變長。

五、重點回顧

  • 讀串接從左到右,每一步先記下型別與形狀;沒有重新指定時,原表不被改寫。
  • shape 是(列數, 欄數)。loc 依標籤且含終點,iloc 依位置且不含終點;預設整數索引下 loc[0:2] 是 3 列、iloc[0:2] 是 2 列。
  • 一層方括號是 Series,兩層方括號是 DataFrame。多條件用 &、|、~ 並加括號;and/or 連接 Series 會報錯。
  • 要整數又要留缺失,用 astype('Int64')。astype(int) 與 astype('int64') 遇到 NaN 會錯;先 fillna(0) 會把缺失變成 0。
  • 寬表轉長表用 melt,長表轉寬表用 pivot_table。how='left' 保留左表全部列,對不到的欄是 NaN;how='inner' 只留兩邊都有的列。
  • 大於記憶體的 CSV 用 chunksize 分塊,並配 usecols 與 dtype。縮放時訓練集用 fit_transform,測試集只用 transform。

覺得有幫助? RSS · X · 請我喝杯咖啡

比較兩種切片在列 0、列 1、列 2 上各自留下哪些位置。

loc 含終點,iloc 不含終點
1 / 2
loc[0:2] 的三格是列 0、列 1、列 2
loc[0:2] 取 0、1、2 共 3 列;iloc[0:2] 只取 0、1 共 2 列。
  1. loc 依標籤選,切片含終點,0 到 2 的個數是 2 - 0 + 1 = 3。
  2. iloc 依位置選,切片不含終點,個數是 2 - 0 = 2,列 2 不在結果裡。

四區的 sales 總額並排,由高到低是 N、S、W、E。

groupby 加總後再取前三名
1 / 4
切到 sales 加總,四區為 N、S、W、E
區域 N 的銷售總額為 15,接著是 S 的 3、W 的 2;依文章範例加總。
  1. 5 列依 region 分組後剩 4 個區域,N 出現兩次,這張是加總而不是原表。
  2. groupby 先分組、再取 sales、再加總,同一區的兩列合成這一格。
  3. 這順序來自 sort_values(ascending=False),數值大的排前面。
  4. head(3) 切列不切欄,所以 top.shape 是 (3,),E 的 1 不在這 3 列。

沿 user_id 由上往下看,前兩列對上 city,後三列填的是 NaN。

left merge 保留左表全部列
1 / 3
表頭是 user_id、amt、city 三欄
左表的 3、5、10 在右表找不到,city 會填 NaN;數值取自文章範例。
  1. 結果比訂單多一欄 city,這欄來自 users,列數仍沿用左表。
  2. 這兩個 user_id 在 users 找得到,所以 city 寫入 N 與 S,不是 NaN。
  3. 這 3 列的 user_id 不在 users,how='left' 仍保留它們,不會像 inner 被刪掉。

看型別那一欄往下,缺的那一格在 int64 與 Int64 各會怎樣。

Int64 可保留整數欄的缺失
1 / 3
表頭分成型別、數值與缺失值
一般 int64 不能儲存 NaN;可空整數 Int64 以 <NA> 保留缺失。
  1. 型別欄用來對照 int64 與 Int64,另外兩欄看數值與缺失。
  2. astype('int64') 與 astype(int) 碰到缺失都會報錯,缺失格留不下來。
  3. 大寫 I 的 Int64 是可空整數,缺失留得住,其餘格子仍是整數。

動手試試不寫程式也能試:貼上寬表 CSV,工具偵測到寬表後會建議轉成長表。

資料淨化