Pandas 作為 Python 資料科學領域的核心函式庫,其提供的資料選擇與指定機制是資料處理的根本。理解如何有效地運用這些機制對於資料分析、清洗和轉換至關重要。常見的操作包含使用 [] 運算元、.loc.iloc.filter 等方法,搭配切片、列表和標籤等引數,實作精確的資料定位和修改。然而,這些方法之間存在著微妙的差異,特別是標籤式選取和位置式選取,需要特別注意索引型別和資料結構,避免混淆和錯誤。熟練掌握這些技巧能大幅提升資料處理效率,是 Pandas 使用者必備的技能。

資料選擇與指定

在前一章中,我們學習瞭如何建立 pd.Seriespd.DataFrame,並探討了它們與 pd.Index 的關係。在掌握了建構函式的基礎之後,我們現在將重點轉移到至關重要的選擇和指定過程。選擇(也被稱為索引)被視為 getter,用於從 pandas 物件中檢索值。相反,指定是一種 setter,用於更新值。

本章的配方首先展示如何從 pd.Seriespd.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.DataFramepd.Series 中的資料進行選擇。
  • pd.DataFrame.ilocpd.Series.iloc 可用於按位置進行選擇,避免標籤混淆。
  • 可使用列表和切片物件自定義選擇結果。

標籤式選取(Label-based Selection)

在 pandas 中,pd.Series.locpd.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.locpd.DataFrame.ilocpd.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 指定篩選行,預設為篩選列。