Pandas 作為 Python 資料科學領域的核心函式庫,提供高效的資料結構和運算功能。理解 Series 與 DataFrame 的運算機制對於資料分析至關重要。本文從 Series 的基本算術運算出發,逐步深入 DataFrame 的向量化運算、索引對齊及缺失值處理。此外,更涵蓋了聚合運算的應用,包含內建方法與自定義函式,並以程式碼範例佐證,讓讀者能快速掌握 Pandas 的運算技巧,提升資料處理效率。透過索引對齊和廣播機制,Pandas 簡化了複雜的資料操作,讓開發者能更專注於資料分析本身。

基本 pd.Series 算術運算

在探索 pandas 演算法時,最簡單的起點是使用 pd.Series,因為它也是 pandas 函式庫提供的最基本結構。基本的算術運算涵蓋了加法、減法、乘法和除法,正如您將在本文中看到的,pandas 提供了兩種執行這些運算的方法。第一種方法是利用 Python 語言內建的 +-*/ 運算元,這對於新使用者來說是一種直觀的方式。然而,為了涵蓋 Python 語言未涵蓋的資料分析特定功能,並支援本章稍後將介紹的與 .pipe 連結的方法,pandas 也分別提供了 pd.Series.addpd.Series.subpd.Series.mulpd.Series.div 方法。

如何進行基本 pd.Series 算術運算

首先,讓我們從一個簡單的 Python range 表示式建立一個 pd.Series

ser = pd.Series(range(3), dtype=pd.Int64Dtype())
ser

輸出結果:

0    0
1    1
2    2
dtype: Int64

讓我們簡要考慮一個表示式,如 a + b。在這樣的表示式中,我們使用了一個二元運算元 (+)。二元指的是需要將兩個東西相加才能使這個表示式有意義,也就是說,只有 a + 這樣的表示式是沒有意義的。這兩個「東西」在技術上被視為運算元;因此,在 a + b 中,我們有左邊的運算元 a 和右邊的運算元 b

當其中一個運算元是 pd.Series 時,pandas 中最基本的演算法表示式將包含另一個運算元是純量(即單一值)。發生這種情況時,純量值會被廣播到 pd.Series 的每個元素以應用演算法。

例如,如果我們想將數字 42 加到 pd.Series 的每個元素上,我們可以簡單地表達為:

ser + 42

輸出結果:

0    42
1    43
2    44
dtype: Int64

內容解密:

這段程式碼展示瞭如何使用 pandas 的 pd.Series 進行基本的算術運算。首先,建立了一個包含三個元素的 pd.Series,然後使用加法運算元 (+) 將數字 42 加到每個元素上。pandas 能夠以向量化的方式應用加法運算,即將數字 42 一次性應用於所有值,而無需使用者使用 Python 的 for 迴圈。

同樣地,可以使用 - 運算元進行減法運算:

ser - 42

輸出結果:

0   -42
1   -41
2   -40
dtype: Int64

使用 * 運算元進行乘法運算:

ser * 2

輸出結果:

0    0
1    2
2    4
dtype: Int64

使用 / 運算元進行除法運算:

ser / 2

輸出結果:

0    0.0
1    0.5
2    1.0
dtype: Float64

兩個運算元也可以都是 pd.Series

ser2 = pd.Series(range(10, 13), dtype=pd.Int64Dtype())
ser + ser2

輸出結果:

0    10
1    12
2    14
dtype: Int64

內容解密:

這段程式碼展示了當兩個運算元都是 pd.Series 時,如何進行加法運算。pandas 將根據索引標籤對齊兩個 pd.Series 的元素,然後進行加法運算。

雖然內建的 Python 運算元在大多數情況下是常用且可行的,但 pandas 仍然提供了專門的方法,如 pd.Series.addpd.Series.subpd.Series.mulpd.Series.div

ser1 = pd.Series([1., 2., 3.], dtype=pd.Float64Dtype())
ser2 = pd.Series([4., pd.NA, 6.], dtype=pd.Float64Dtype())
ser1.add(ser2)

輸出結果:

0     5.0
1    <NA>
2     9.0
dtype: Float64

使用 fill_value= 引數可以處理缺失資料:

ser1.add(ser2, fill_value=0.)

輸出結果:

0     5.0
1     2.0
2     9.0
dtype: Float64

內容解密:

這段程式碼展示瞭如何使用 pd.Series.add 方法進行加法運算,並使用 fill_value= 引數處理缺失資料。當第二個 pd.Series 中有缺失值時,預設情況下結果將是缺失值。使用 fill_value=0. 可以將缺失值視為 0,從而避免結果中的缺失值。

更多內容…

當表示式中的兩個運算元都是 pd.Series 物件時,pandas 將根據行標籤對齊。這種對齊行為被視為一種功能,但也可能令新手感到驚訝。

讓我們考慮兩個具有相同行索引的 pd.Series 物件。當我們將它們相加時,會得到一個相當不令人驚訝的結果:

ser1 = pd.Series(range(3), dtype=pd.Int64Dtype())
ser2 = pd.Series(range(3), dtype=pd.Int64Dtype())
ser1 + ser2

輸出結果:

0    0
1    2
2    4
dtype: Int64

但是,當行索引值不相同時會發生什麼?一種簡單的情況可能涉及將兩個 pd.Series 物件相加,其中一個 pd.Series 使用的行索引是另一個的子集。可以透過下面的程式碼看到這一點,其中 ser3 只有 2 個值,並使用預設的 pd.RangeIndex,其值為 [0, 1]。當與 ser1 相加時,我們仍然得到一個包含 3 個元素的 pd.Series,但只有在兩個 pd.Series 物件中都存在行索引標籤時,才會進行加法運算:

ser3 = pd.Series(range(2), dtype=pd.Int64Dtype())
ser1 + ser3

輸出結果取決於具體的 ser1 和 ser3。

圖表翻譯:

此處可加入 Mermaid 圖表來說明 pandas 如何根據索引標籤對齊兩個 Series 的元素,然後進行加法運算。例如:

  graph LR;
    A[ser1] -->|索引對齊|> B[相加結果];
    C[ser3] -->|索引對齊|> B;

圖表翻譯: 上圖展示了 pandas 如何根據索引標籤對齊兩個 Series 的元素,然後進行加法運算。只有具有相同索引標籤的元素才會被相加。

基本的 pandas 序列運算

在進行資料分析時,pandas 提供了一系列強大的資料結構和運算功能。其中,pd.Seriespd.DataFrame 是最常用的兩種資料結構。本章節將重點介紹 pd.Seriespd.DataFrame 的基本運算。

pd.Series 運算

首先,我們來探討 pd.Series 的基本運算。假設我們有兩個 pd.Series 物件,ser1ser3,其定義如下:

ser1 = pd.Series([0, 1, 2], dtype=pd.Int64Dtype())
ser3 = pd.Series([2, 4], dtype=pd.Int64Dtype())

當我們將 ser1ser3 相加時,pandas 會根據索引標籤對齊資料。如果某個索引標籤在其中一個序列中不存在,則結果中對應的值將為 <NA>

ser1 + ser3

輸出結果為:

0       2
1       5
2    <NA>
dtype: Int64

內容解密:

  • ser1ser3 相加時,索引 01 對齊成功,因此結果分別為 25
  • 索引 2 只存在於 ser1 中,因此結果為 <NA>

索引標籤不同的情況

接下來,我們探討當兩個 pd.Series 物件的索引標籤不同時的情況。

ser4 = pd.Series([2, 4, 8], index=[1, 2, 3], dtype=pd.Int64Dtype())
ser1 + ser4

輸出結果為:

0    <NA>
1       3
2       6
3    <NA>
dtype: Int64

內容解密:

  • 索引 12 同時存在於 ser1ser4 中,因此結果分別為 36
  • 索引 03 只存在於其中一個序列中,因此結果為 <NA>

索引標籤非唯一情況

如果某個 pd.Series 的索引標籤非唯一,則會導致結果中出現重複的索引標籤。

ser5 = pd.Series([2, 4, 8], index=[0, 1, 1], dtype=pd.Int64Dtype())
ser1 + ser5

輸出結果為:

0       2
1       5
1       9
2    <NA>
dtype: Int64

內容解密:

  • 索引 0 對齊成功,結果為 2
  • 索引 1 出現兩次,因此結果中有兩個值,分別為 59
  • 索引 2 只存在於 ser1 中,因此結果為 <NA>

這種行為類別似於 SQL 中的 FULL OUTER JOIN 操作。

SQL 中的 FULL OUTER JOIN

以下是一個在 PostgreSQL 中實作類別似操作的例子:

WITH ser1 AS (
    SELECT * FROM (
        VALUES
            (0, 0),
            (1, 1),
            (2, 2)
    ) AS t(index, val1)
),
ser5 AS (
    SELECT * FROM (
        VALUES
            (0, 2),
            (1, 4),
            (1, 8)
    ) AS t(index, val2)
)
SELECT index, val1 + val2 AS value 
FROM ser1 
FULL OUTER JOIN ser5 USING(index);

輸出結果為:

index | value
------+-------
    0 |     2
    1 |     5
    1 |     9
    2 |       
(4 rows)

圖表翻譯:

此圖示呈現了 FULL OUTER JOIN 的操作過程。兩個表格根據索引標籤進行連線,如果某個索引標籤在其中一個表格中不存在,則結果中對應的值將為空。

  graph LR;
    A[ser1] -->|FULL OUTER JOIN|> C[結果];
    B[ser5] -->|FULL OUTER JOIN|> C;

基本的 pd.DataFrame 運算

接下來,我們探討 pd.DataFrame 的基本運算。假設我們有一個 $3 \times 3$ 的 pd.DataFrame,其定義如下:

np.random.seed(42)
df = pd.DataFrame(
    np.random.randn(3, 3),
    columns=["col1", "col2", "col3"],
    index=["row1", "row2", "row3"],
).convert_dtypes(dtype_backend="numpy_nullable")

當我們對 df 進行基本運算時,例如加法或乘法,pandas 將根據標籤對齊資料。

df + 1
df * 2

輸出結果分別為:

       col1      col2      col3
row1   1.496714   0.861736   1.647689
row2   2.523030   0.765847   0.765863
row3   2.579213   1.767435   0.530526

       col1      col2      col3
row1   0.993428  -0.276529   1.295377
row2   3.046060  -0.468307  -0.468274
row3   3.158426   1.534869  -0.938949

與 pd.Series 的運算

當我們將 df 與一個 pd.Series 相加時,pandas 將根據列標籤對齊資料。

ser = pd.Series([20, 10, 0], index=["col1", "col2", "col3"], dtype=pd.Int64Dtype())
df + ser

輸出結果為:

       col1      col2      col3
row1   20.496714   9.861736   0.647689
row2   21.523030   9.765847  -0.234137
row3   21.579213  10.767435  -0.469474

圖表翻譯:

此圖示呈現了 DataFrame 與 Series 相加的過程。Series 的索引標籤與 DataFrame 的列標籤對齊,然後進行相加操作。

  graph LR;
    A[DataFrame] -->|與 Series 相加|> C[結果];
    B[Series] -->|與 DataFrame 相加|> C;

控制對齊方式

我們可以使用 axis= 引數控制對齊方式。

ser = pd.Series([20, 10, 0, 42], index=["row1", "row2", "row3", "row4"], dtype=pd.Int64Dtype())
df.add(ser, axis=0)

輸出結果為:

       col1      col2      col3
row1   20.496714   19.861736   20.647689
row2   11.523030   9.765847    9.765863
row3   1.579213    0.767435   -0.469474
row4    <NA>       <NA>       <NA>

DataFrame 之間的運算

兩個 DataFrame 之間的運算也遵循相同的對齊規則。

df * df

輸出結果為:

       col1      col2      col3
row1   0.246725   0.019117   0.419500
row2   2.319620   0.054828   0.054820
row3   2.493913   0.588956   0.220406

綜上所述,pandas 提供了一系列強大的資料結構和運算功能,能夠滿足各種資料分析需求。瞭解其基本運算規則和對齊方式,有助於更好地利用 pandas 處理資料。

資料加總與聚合運算

在資料分析過程中,聚合運算(Aggregations)扮演著至關重要的角色。它能夠將一連串的數值簡化為單一數值,使得複雜的資料變得更容易理解和運用。本篇將探討pandas函式庫中的聚合運算功能,並展示如何有效地應用這些運算來處理資料。

建立範例資料

首先,我們建立一個包含隨機數值的pd.Series,以便示範基本的聚合運算:

np.random.seed(42)
ser = pd.Series(np.random.rand(10_000), dtype=pd.Float64Dtype())

內容解密:

  • np.random.seed(42):設定亂數種子,以確保每次產生的隨機數都相同,便於重現結果。
  • pd.Series(np.random.rand(10_000), dtype=pd.Float64Dtype()):生成一個包含10,000個隨機浮點數的序列,並指定資料型別為Float64

基本聚合運算

pandas提供了一系列內建的聚合方法,如countmeanstdminmaxsum等,可以直接對pd.Series進行操作:

print(f"Count is: {ser.count()}")
print(f"Mean value is: {ser.mean()}")
print(f"Standard deviation is: {ser.std()}")
print(f"Minimum value is: {ser.min()}")
print(f"Maximum value is: {ser.max()}")
print(f"Summation is: {ser.sum()}")

內容解密:

  • ser.count():計算序列中的非缺失值數量。
  • ser.mean():計算序列的平均值。
  • ser.std():計算序列的標準差。
  • ser.min()ser.max():找出序列中的最小值和最大值。
  • ser.sum():計算序列中所有數值的總和。

使用.agg()進行聚合運算

除了直接呼叫聚合方法外,pandas還提供了.agg()方法,可以用來執行多種聚合運算:

ser.agg(["min", "max"])

內容解密:

  • .agg()方法接受一個列表,列表中包含要執行的聚合運算名稱,如minmax

pd.DataFrame進行聚合運算

對於pd.DataFrame,聚合運算預設是沿著列(axis=0)進行的:

np.random.seed(42)
df = pd.DataFrame(
    np.random.randn(10_000, 6),
    columns=list("abcdef"),
).convert_dtypes(dtype_backend="numpy_nullable")
df.sum()

內容解密:

  • df.sum():對DataFrame的每一列進行求和運算,傳回一個包含每列總和的Series。

若要沿著行(axis=1)進行聚合運算,可以指定axis=1引數:

df.sum(axis=1)

內容解密:

  • df.sum(axis=1):對DataFrame的每一行進行求和運算,傳回一個包含每行總和的Series。

自定義聚合函式

除了使用內建的聚合函式外,還可以自定義函式並傳遞給.agg()方法:

def mean_and_add_42(ser: pd.Series):
    return ser.mean() + 42

def mean_and_sub_42(ser: pd.Series):
    return ser.mean() - 42

ser.agg([mean_and_add_42, mean_and_sub_42])

內容解密:

  • 自定義函式mean_and_add_42mean_and_sub_42分別計算序列的平均值並加上或減去42。
  • 將這些自定義函式傳遞給.agg()方法,以執行自定義的聚合運算。