Pandas 作為 Python 資料科學領域的核心函式庫,其提供的資料選擇與指定機制是資料處理的根本。理解如何有效地運用這些機制對於資料分析、清洗和轉換至關重要。常見的操作包含使用 [] 運算元、.loc、.iloc 和 .filter 等方法,搭配切片、列表和標籤等引數,實作精確的資料定位和修改。然而,這些方法之間存在著微妙的差異,特別是標籤式選取和位置式選取,需要特別注意索引型別和資料結構,避免混淆和錯誤。熟練掌握這些技巧能大幅提升資料處理效率,是 Pandas 使用者必備的技能。
資料選擇與指定
在前一章中,我們學習瞭如何建立 pd.Series 和 pd.DataFrame,並探討了它們與 pd.Index 的關係。在掌握了建構函式的基礎之後,我們現在將重點轉移到至關重要的選擇和指定過程。選擇(也被稱為索引)被視為 getter,用於從 pandas 物件中檢索值。相反,指定是一種 setter,用於更新值。
本章的配方首先展示如何從 pd.Series 和 pd.DataFrame 物件中檢索值,隨著複雜度的增加,我們最終將介紹 pd.MultiIndex,可用於分層選擇資料。最後,我們將介紹指定運算子。pandas API 盡可能重用相同的選擇和指定方法,這使您能夠以更靈活的方式與資料互動。
從 Series 中進行基本選擇
從 pd.Series 中進行選擇涉及透過位置或標籤存取元素。這類別似於透過索引存取列表中的元素或透過鍵存取字典中的元素。pd.Series 物件的多樣性使其成為資料操作的基本工具。
如何實作
首先,讓我們從一個簡單的 pd.Series 開始:
ser = pd.Series(list("abc") * 3)
ser
輸出:
0 a
1 b
2 c
3 a
4 b
5 c
6 a
7 b
8 c
dtype: object
在 Python 中,您已經知道可以使用 [] 運算子從容器中選擇元素。例如,some_dictionary[0] 將傳回與鍵 0 相關聯的值。使用 pd.Series 時,基本選擇的行為類別似:
ser[3]
輸出:
'a'
使用表示式 ser[3] 時,pandas 將嘗試在 pd.Series 的索引中查詢標籤 3,並假設只有一個匹配項,傳回與該標籤相關聯的值。
使用列表引數進行選擇
您可以使用列表引數來選擇多個值:
ser[[0, 2]]
輸出:
0 a
2 c
dtype: object
使用切片進行選擇
假設您使用預設索引,您可以使用切片引數,其工作方式與切片 Python 列表非常相似。例如,要取得直到(但不包括)位置 3 的元素,您可以使用:
ser[:3]
輸出:
0 a
1 b
2 c
dtype: object
負切片索引對 pandas 來說不是問題。以下程式碼將選擇 pd.Series 的最後四個元素:
ser[-4:]
輸出:
5 c
6 a
7 b
8 c
dtype: object
您甚至可以提供帶有起始和停止引數的切片。以下程式碼將檢索 pd.Series 的所有元素,從位置 2 開始,直到(但不包括)位置 6:
ser[2:6]
輸出:
2 c
3 a
4 b
5 c
dtype: object
最後一個關於切片的例子使用起始、停止和步長引數來抓取每第三個元素,從位置 1 開始,並在遇到位置 8 時停止:
ser[1:8:3]
輸出:
1 b
4 b
7 b
dtype: object
更多內容…
使用 [] 運算子時,一個常見的陷阱是假設使用整數引數進行選擇的工作方式與從 Python 列表中選擇相同。只有在使用預設 pd.Index 時,這才是正確的。
當不使用 pd.RangeIndex 時,必須特別注意行為。為了說明,讓我們從一個小的 pd.Series 開始,它仍然在其 pd.Index 中使用整數,但不使用從 0 開始的自動遞增序列:
ser = pd.Series(list("abc"), index=[2, 42, 21])
ser
輸出:
2 a
42 b
21 c
dtype: object
需要注意的是,整數引數按標籤選擇,而不是按位置選擇。也就是說,以下程式碼將傳回與標籤 2 相關聯的值,而不是位置 2 的值:
ser[2]
輸出:
'a'
雖然整數引數按標籤匹配,而不是按位置匹配,但切片仍然按位置工作。以下示例不會在遇到數字 2 時停止,而是傳回前兩個元素:
ser[:2]
輸出:
2 a
42 b
dtype: object
使用者還應該熟悉使用非唯一 pd.Index 時的選擇行為。讓我們建立一個小的 pd.Series,其中數字 1 在我們的行索引中出現兩次:
ser = pd.Series(["apple", "banana", "orange"], index=[0, 1, 1])
ser
輸出:
0 apple
1 banana
1 orange
dtype: object
使用此 pd.Series 時,嘗試選擇數字 1 將不會傳回單個值,而是傳回另一個 pd.Series:
ser[1]
輸出:
1 banana
1 orange
dtype: object
程式碼詳解:
上述程式碼展示瞭如何從 pd.Series 中進行基本選擇,包括使用整數索引、列表引數和切片等不同方法。
ser = pd.Series(list("abc") * 3):建立了一個包含 ‘a’, ‘b’, ‘c’ 重複三次的pd.Series。ser[3]:展示瞭如何透過標籤選擇單個元素。ser[[0, 2]]:展示瞭如何使用列表引數選擇多個元素。ser[:3]:展示瞭如何使用切片選擇直到某個位置的元素。ser[-4:]:展示瞭如何使用負切片索引選擇最後幾個元素。ser[2:6]:展示瞭如何使用切片選擇特定範圍內的元素。ser[1:8:3]:展示瞭如何使用帶步長的切片選擇元素。
資料選擇與指定
從 DataFrame 進行基本選擇
使用 [] 運算元對 pd.DataFrame 進行簡單選擇時,通常是根據欄位索引而非列索引進行資料選擇。瞭解這種基本的選擇行為差異是有效操作和分析資料的關鍵。
操作步驟
首先,建立一個簡單的 3x3 pd.DataFrame,並為欄位指定自定義標籤:
import pandas as pd
import numpy as np
df = pd.DataFrame(np.arange(9).reshape(3, -1), columns=["a", "b", "c"])
print(df)
輸出:
a b c
0 0 1 2
1 3 4 5
2 6 7 8
選擇單一欄位時,使用帶有純量引數的 [] 運算元:
print(df["a"])
輸出:
0 0
1 3
2 6
Name: a, dtype: int64
若要選擇單一欄位但仍傳回 pd.DataFrame 而非 pd.Series,可傳遞單元素列表:
print(df[["a"]])
輸出:
a
0 0
1 3
2 6
使用列表可選擇多個欄位:
print(df[["a", "b"]])
輸出:
a b
0 0 1
1 3 4
2 6 7
詳細解析
- 使用列表作為
[]運算元的引數時,可以自定義輸出欄位的順序。 - 當使用切片引數時,
[]運算元會根據列進行選擇。
使用位置索引從 Series 中選擇資料
為了明確表示按位置而非按標籤進行選擇,應使用 pd.Series.iloc。
操作步驟
建立一個具有非唯一整數標籤索引的 pd.Series:
ser = pd.Series(["apple", "banana", "orange"], index=[0, 1, 1])
print(ser)
輸出:
0 apple
1 banana
1 orange
dtype: object
使用 pd.Series.iloc 和整數引數選擇純量值:
print(ser.iloc[1])
輸出:
banana
詳細解析
pd.Series.iloc可接受整數、整數列表和切片物件作為引數。- 使用列表可選擇多個元素。
使用位置索引從 DataFrame 中選擇資料
與 pd.Series 類別似,pd.DataFrame.iloc 可接受整數、整數列表和切片物件作為引數,但需要提供兩個引數,分別用於選擇列和欄位。
操作步驟
建立一個具有五列四欄的 pd.DataFrame:
df = pd.DataFrame(np.arange(20).reshape(5, -1), columns=list("abcd"))
print(df)
輸出:
a b c d
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
使用 pd.DataFrame.iloc 和兩個整數引數傳回特定列和欄位位置的純量值:
print(df.iloc[2, 2])
輸出:
10
詳細解析
- 使用空切片物件
:可選擇特定軸上的所有元素。 - 可使用列表選擇多個列和欄位。
重點整理
- 使用
[]、iloc等方法可對pd.DataFrame和pd.Series中的資料進行選擇。 pd.DataFrame.iloc和pd.Series.iloc可用於按位置進行選擇,避免標籤混淆。- 可使用列表和切片物件自定義選擇結果。
標籤式選取(Label-based Selection)
在 pandas 中,pd.Series.loc 和 pd.DataFrame.loc 用於執行標籤式選取,而非位置式選取。這種方法在處理 pd.Index 時非常有用,因為它允許將索引視為查詢值,就像 Python 字典中的鍵一樣,而非僅關注資料的位置或順序。
從 Series 中進行標籤式選取
首先,建立一個具有整數標籤且非唯一索引的 pd.Series:
ser = pd.Series(["蘋果", "香蕉", "橙子"], index=[0, 1, 1])
print(ser)
輸出:
0 蘋果
1 香蕉
1 橙子
dtype: object
使用 pd.Series.loc 選取索引標籤為 1 的所有列:
print(ser.loc[1])
輸出:
1 香蕉
1 橙子
dtype: object
內容解密:
ser.loc[1]的作用是根據索引標籤選取資料,而不是根據位置。- 由於索引中有多個標籤為 1 的列,因此該操作傳回所有匹配的列。
接下來,建立一個具有字串索引的 pd.Series:
ser = pd.Series([2, 2, 4], index=["狗", "貓", "人類"], name="腿數")
print(ser)
輸出:
狗 2
貓 2
人類 4
Name: 腿數, dtype: int64
使用 pd.Series.loc 選取索引標籤為 “狗” 的列:
print(ser.loc["狗"])
輸出:
2
內容解密:
ser.loc["狗"]直接根據標籤 “狗” 傳回對應的值。- 這種操作適用於具有明確標籤的資料查詢。
要選取多個標籤,可以傳入一個列表:
print(ser.loc[["狗", "貓"]])
輸出:
狗 2
貓 2
Name: 腿數, dtype: int64
內容解密:
ser.loc[["狗", "貓"]]同時選取標籤為 “狗” 和 “貓” 的列。- 傳回結果保留了原有的索引和對應的值。
切片操作與標籤式選取的差異
在 Python 中,切片操作是根據位置的,而 pd.Series.loc 是根據標籤的。這導致了兩者在行為上的差異。
舉例來說,建立一個 Python 列表和一個具有相同資料的 pd.Series:
values = ["Jack", "Jill", "Jayne"]
ser = pd.Series(values)
print(ser)
輸出:
0 Jack
1 Jill
2 Jayne
dtype: object
在 Python 中,切片操作傳回到但不包括指定位置的元素:
print(values[:2])
輸出:
['Jack', 'Jill']
使用 pd.Series.iloc 時,行為與 Python 列表相同:
print(ser.iloc[:2])
輸出:
0 Jack
1 Jill
dtype: object
然而,使用 pd.Series.loc 時,結果可能會不同:
print(ser.loc[:2])
輸出:
0 Jack
1 Jill
2 Jayne
dtype: object
內容解密:
ser.loc[:2]是根據標籤的切片,因此它會查詢索引中值為 2 的標籤,並傳回到該標籤的所有資料。- 由於索引中有標籤 2,因此傳回了包含該標籤的所有行。
這種行為在索引值不唯一時尤為明顯:
repeats_2 = pd.Series(range(5), index=[0, 1, 2, 2, 0])
print(repeats_2.loc[:2])
輸出:
0 0
1 1
2 2
2 3
dtype: int64
內容解密:
- 當索引中有多個值為 2 的標籤時,
repeats_2.loc[:2]傳回所有匹配的行。 - 這種行為表明
loc是根據標籤匹配,而非位置。
從 DataFrame 中進行標籤式選取
對於 pd.DataFrame,可以使用 pd.DataFrame.loc 同時對行和列進行標籤式選取。
首先,建立一個具有字串索引的 pd.DataFrame:
df = pd.DataFrame([
[24, 180, "藍色"],
[42, 166, "棕色"],
[22, 160, "綠色"],
], columns=["年齡", "身高_cm", "眼睛顏色"], index=["Jack", "Jill", "Jayne"])
print(df)
輸出:
年齡 身高_cm 眼睛顏色
Jack 24 180 藍色
Jill 42 166 棕色
Jayne 22 160 綠色
使用 pd.DataFrame.loc 選取特定行和列:
print(df.loc["Jayne", "眼睛顏色"])
輸出:
綠色
內容解密:
df.loc["Jayne", "眼睛顏色"]同時指定了行標籤 “Jayne” 和列標籤 “眼睛顏色”,傳回對應的值。
要選取某列的所有行,可以使用如下語法:
print(df.loc[:, "年齡"])
輸出:
Jack 24
Jill 42
Jayne 22
Name: 年齡, dtype: int64
內容解密:
df.loc[:, "年齡"]表示選取所有行(使用:)和 “年齡” 列。- 傳回結果是一個 Series,包含該列的所有資料。
使用 pandas 進行資料選擇與指定
在 pandas 中,資料選擇與指定是非常重要的操作。本章節將介紹如何使用 pd.DataFrame.loc、pd.DataFrame.iloc 和 pd.DataFrame.filter 進行資料選擇,以及如何進行混合選擇和指定。
使用 pd.DataFrame.loc 進行標籤式選擇
pd.DataFrame.loc 是用於標籤式選擇的主要方法。它允許你根據行和列的標籤進行選擇。
import pandas as pd
# 建立一個 DataFrame
df = pd.DataFrame([
[24, 180, "blue"],
[42, 166, "brown"],
[22, 160, "green"],
], columns=["age", "height_cm", "eye_color"], index=["Jack", "Jill", "Jayne"])
# 選擇特定行的所有列
print(df.loc["Jack", :])
#### 內容解密:
# df.loc["Jack", :] 會選擇索引為 "Jack" 的行,並傳回該行的所有列。
# 輸出結果包括該行的 age、height_cm 和 eye_color。
# 選擇特定列的所有行
print(df.loc[:, ["age"]])
#### 內容解密:
# df.loc[:, ["age"]] 會選擇 "age" 列,並傳回該列的所有行。
# 輸出結果是一個 DataFrame,包含 "age" 列的所有行。
# 選擇多行和多列
print(df.loc[["Jack", "Jill"], ["age", "eye_color"]])
#### 內容解密:
# df.loc[["Jack", "Jill"], ["age", "eye_color"]] 會選擇 "Jack" 和 "Jill" 行,以及 "age" 和 "eye_color" 列。
# 輸出結果是一個 DataFrame,包含所選行和列的資料。
混合使用位置式和標籤式選擇
在某些情況下,你可能需要混合使用位置式和標籤式選擇。這時,你可以使用 pd.Index.get_indexer 方法將標籤轉換為位置索引。
# 建立一個 DataFrame
df = pd.DataFrame([
[24, 180, "blue"],
[42, 166, "brown"],
[22, 160, "green"],
], columns=["age", "height_cm", "eye_color"])
# 將標籤轉換為位置索引
col_idxer = df.columns.get_indexer(["age", "eye_color"])
# 使用位置式選擇
print(df.iloc[[0, 1], col_idxer])
#### 內容解密:
# df.columns.get_indexer(["age", "eye_color"]) 將 "age" 和 "eye_color" 的標籤轉換為位置索引。
# df.iloc[[0, 1], col_idxer] 使用位置式選擇,選擇第 0 和第 1 行,以及 "age" 和 "eye_color" 列。
使用 pd.DataFrame.filter 進行篩選
pd.DataFrame.filter 是一個專門用於篩選行或列的方法。
# 建立一個 DataFrame
df = pd.DataFrame([
[24, 180, "blue"],
[42, 166, "brown"],
[22, 160, "green"],
], columns=["age", "height_cm", "eye_color"], index=["Jack", "Jill", "Jayne"])
# 篩選列
print(df.filter(["age", "eye_color"]))
#### 內容解密:
# df.filter(["age", "eye_color"]) 會篩選出 "age" 和 "eye_color" 列。
# 輸出結果是一個 DataFrame,包含所選列的資料。
# 篩選行
print(df.filter(["Jack", "Jill"], axis=0))
#### 內容解密:
# df.filter(["Jack", "Jill"], axis=0) 會篩選出 "Jack" 和 "Jill" 行。
# axis=0 指定篩選行,預設為篩選列。