Pandas 是資料科學領域不可或缺的工具,其提供的時間序列處理能力尤其重要。然而,在面對更複雜的資料結構和精確度要求時,單純依靠 Pandas 內建功能可能不足。本文將探討如何結合 PyArrow 的高效能資料結構,提升 Pandas 在時間序列和高精確度數值處理方面的能力。PyArrow 的日期型別提供更精確且廣泛的日期表示,而 List 型別則能有效處理巢狀資料結構,例如儲存員工及其直屬下屬的關係。Decimal 型別則解決了浮點數精確度不足的問題,適用於金融等對數值精確度要求極高的場景。這些進階資料型別與 Pandas 的整合,讓開發者能更有效率地處理複雜資料,並確保資料的完整性和計算的準確性。
pandas 中的時間資料型別處理
在資料分析過程中,正確處理時間資料是至關重要的。pandas 提供了豐富的時間相關資料型別和操作方法,能夠滿足大多數場景下的時間資料處理需求。本文將深入探討 pandas 中 datetime、timedelta 和 PyArrow 日期型別的主要特點及應用方法。
datetime 資料型別的時區處理
在處理時間資料時,時區是一個非常重要的概念。pandas 的 datetime 資料型別支援時區設定,這使得不同時區之間的時間轉換變得更加容易。
import pandas as pd
# 建立包含時區資訊的時間序列
ser_tz = pd.Series([
"2024-01-01 00:00:01",
"2024-01-02 00:00:01",
"2024-01-03 00:00:01"
], dtype=pd.DatetimeTZDtype(tz="America/New_York"))
print(ser_tz)
程式碼解析:
此範例展示瞭如何建立一個帶有時區資訊的時間序列。透過使用 pd.DatetimeTZDtype(tz="America/New_York")
,我們明確指定了時區為美國東部時間。輸出結果中可以看到每個時間戳都帶有對應的時區偏移量。
時區轉換與本地化
當處理沒有明確時區資訊的時間資料時,可以使用 tz_localize
方法來指定時區。同時,tz_convert
方法允許我們將時間資料轉換到其他時區。
# 建立沒有時區資訊的時間序列
ser_no_tz = pd.Series([
"2024-01-01 00:00:00",
"2024-01-01 00:01:10",
"2024-01-01 00:02:42"
], dtype="datetime64[ns]")
# 設定時區為美國東部時間
ser_et = ser_no_tz.dt.tz_localize("America/New_York")
print(ser_et)
# 轉換時區到美國太平洋時間
ser_pt = ser_et.dt.tz_convert("America/Los_Angeles")
print(ser_pt)
程式碼解析:
首先建立一個沒有時區資訊的時間序列,然後使用 dt.tz_localize
方法將其時區設定為美國東部時間。接著,透過 dt.tz_convert
方法將時間轉換到美國太平洋時間。可以觀察到時間的時區偏移和實際時間的變化。
時間資料的規範化處理
在某些場景下,我們可能只關心日期而忽略具體的時間。這時可以使用 dt.normalize
方法將時間資料規範化到當天的零點。
# 將時間規範化到零點
ser_normalized = ser_pt.dt.normalize()
print(ser_normalized)
程式碼解析:
透過 dt.normalize
方法,我們將時間資料統一設定為當天的零點,消除了具體時間的差異,只保留日期資訊。
缺失值處理
時間資料型別同樣需要處理缺失值。在 pandas 中,時間型別的缺失值使用 NaT
(Not a Time) 表示。
# 建立包含缺失值的時間序列
ser_with_na = pd.Series([
"2024-01-01",
None,
"2024-01-03"
], dtype="datetime64[ns]")
print(ser_with_na)
print(pd.isna(ser_with_na))
程式碼解析:
範例中建立了一個包含缺失值的時間序列。None
值被自動轉換為 NaT
。使用 pd.isna
函式可以正確識別出缺失值。
timedelta 資料型別
timedelta 用於表示兩個時間點之間的差值,可以用來進行時間的加減運算。
# 建立時間序列並計算與特定時間的差值
ser = pd.Series([
"2024-01-01",
"2024-01-02",
"2024-01-03"
], dtype="datetime64[ns]")
delta = ser - pd.Timestamp("2023-12-31 12:00:00")
print(delta)
程式碼解析:
此範例計算了一個時間序列與特定時間點之間的時間差。結果是一個 timedelta 型別的序列,表示每個時間點與參考時間之間的差值。
PyArrow 日期型別
PyArrow 提供了真正的日期型別,可以用於擴充 pandas 的日期表示範圍。
import pyarrow as pa
# 使用 PyArrow 的 date32 型別建立日期序列
ser_date = pd.Series([
"2024-01-01",
"2024-01-02",
"2024-01-03"
], dtype=pd.ArrowDtype(pa.date32()))
print(ser_date)
程式碼解析:
透過使用 PyArrow 的 date32
型別,我們可以建立一個純日期的序列,而不需要考慮具體的時間。這種表示方法在某些資料函式庫操作中特別有用,因為資料函式庫通常有獨立的 DATE 型別。
圖表解析:
此流程圖展示了處理時間資料的主要步驟。首先需要根據資料特性選擇適當的資料型別。針對 datetime 型別,可以進行時區轉換和規範化操作。timedelta 型別用於計算時間差值,而 PyArrow 的 date32 型別則適用於處理純日期資料。最終完成時間資料的處理。
PyArrow 資料型別在 Pandas 中的應用
Pandas 結合 PyArrow 函式庫提供了多種進階資料型別,能夠有效處理複雜的資料結構與精確的數值計算需求。以下將深入探討 PyArrow 的 List 型別與 Decimal 型別在 Pandas 中的應用。
PyArrow List 型別:處理巢狀資料結構
在實際應用中,資料往往不會簡單地對應到單一的 DataFrame 結構。PyArrow 的 pa.list_()
型別提供了一種自然的方式來表達巢狀資料結構。以下範例展示如何使用 List 型別來表示員工與其直屬下屬之間的關係:
import pandas as pd
import pyarrow as pa
# 建立員工基本資料 DataFrame
df = pd.DataFrame({
"name": ["Alice", "Bob", "Janice", "Jim", "Michael"],
"years_exp": [10, 2, 4, 8, 6],
})
# 建立直屬下屬清單 Series,使用 PyArrow List 型別
ser = pd.Series([
["Bob", "Michael"],
None,
None,
["Janice"],
None,
], dtype=pd.ArrowDtype(pa.list_(pa.string())))
# 將直屬下屬資訊加入 DataFrame
df["direct_reports"] = ser
print(df)
內容解密:
此範例展示瞭如何使用 PyArrow 的 List 型別來處理巢狀資料結構。我們首先建立了一個包含員工基本資料的 DataFrame,接著建立了一個 Series 來儲存每個員工的直屬下屬清單。使用 pa.list_(pa.string())
定義了 List 型別,其中包含的字串元素代表員工姓名。最後,將這個 Series 加入到原始 DataFrame 中,形成了一個包含巢狀資料結構的表格。
使用 List 存取器進行資料操作
當 Series 具有 PyArrow List 型別時,可以使用 .list
存取器來解鎖更多功能:
# 取得 List 中元素的數量
print(ser.list.len())
# 存取 List 中的第一個元素
print(ser.list[0])
# 展開 List 成為單一的 Series
print(ser.list.flatten())
內容解密:
.list.len()
方法用於取得每個 List 中的元素數量,對於不是 List 的元素(例如 None)會傳回 NA。.list[0]
用於存取每個 List 中的第一個元素,同樣對於非 List 元素傳回 NA。.list.flatten()
則用於將所有 List 中的元素展開成一個新的 Series,方便進行進一步的分析。
PyArrow Decimal 型別:實作精確數值計算
在金融或高精確度計算場景中,浮點數的精確度問題可能導致嚴重的錯誤。PyArrow 的 Decimal 型別提供瞭解決方案:
# 建立精確數值 Series,使用 Decimal 型別
decimal_ser = pd.Series([
"123456789.123456789",
"-987654321.987654321",
"99999999.9999999999",
], dtype=pd.ArrowDtype(pa.decimal128(19, 10)))
print(decimal_ser)
內容解密:
Decimal 型別使用 pa.decimal128(precision, scale)
定義,其中 precision 表示總共可儲存的十進位數字位數,scale 表示小數點後的位數。範例中使用了 (19, 10) 的設定,能夠精確儲存具有 19 位有效數字且其中 10 位是小數的數值。需要注意的是,輸入資料必須以字串形式提供,以避免浮點數解析帶來的精確度損失。
浮點數精確度問題示範
# 直接使用浮點數建立 Series
float_ser = pd.Series([
123456789.123456789,
-987654321.987654321,
99999999.9999999999,
])
print(float_ser)
內容解密:
當直接使用浮點數進行計算時,由於浮點數儲存的本質,會導致精確度損失。例如,99999999.9999999999
可能被儲存為 100000000.0
。這種誤差在大多數科學計算中可能不明顯,但在金融計算中卻是不可接受的。
使用 Python decimal 模組避免精確度問題
import decimal
# 使用 decimal 模組進行精確計算
d1 = decimal.Decimal("99999999.9999999999")
d2 = decimal.Decimal("100000000.0")
print(d1 == d2) # 比較結果
print(d1 + d2) # 精確加法運算
內容解密:
Python 的 decimal 模組提供了 Decimal
類別,能夠精確表示十進位數,避免了浮點數的精確度問題。範例中展示瞭如何使用 Decimal
物件進行精確的數值比較和算術運算。
Mermaid 圖表:Decimal 與浮點數儲存比較
flowchart TD A[數值儲存] --> B{儲存方式} B -->|浮點數| C[精確度損失] B -->|Decimal| D[精確儲存] C --> E[可能導致計算錯誤] D --> F[確保計算準確性]
圖表翻譯:
此圖表比較了浮點數與 Decimal 型別在數值儲存上的差異。浮點數儲存方式可能導致精確度損失,進而影響計算結果的正確性。相對地,Decimal 型別能夠精確儲存數值,確保計算的準確性。圖表清晰地展示了兩種儲存方式的差異及其可能帶來的後果。
資料型別的最佳實踐與效能最佳化
在資料分析領域中,資料型別的選擇對於效能和準確性有著重要影響。Pandas 在 2.x 和 3.x 版本中預設使用 NumPy 資料型別,但這些型別在某些情況下可能導致效能問題或資料完整性問題。本章將深入探討 Pandas 中的資料型別選擇、缺失值處理以及如何最佳化資料儲存和運算效能。
支援高精確度小數的資料型別
Pandas 提供了多種資料型別來處理不同精確度的數值資料。其中,pa.decimal128
和 pa.decimal256
是用於處理高精確度小數的資料型別。
使用 pa.decimal256 處理高精確度小數
import pandas as pd
import pyarrow as pa
# 建立一個包含高精確度小數的 Series
ser = pd.Series([
"123456789123456789123456789123456789.123456789"
], dtype=pd.ArrowDtype(pa.decimal256(76, 10)))
print(ser)
圖表翻譯:
此範例展示瞭如何使用 pa.decimal256
資料型別處理高精確度小數資料。該資料型別支援最多 76 位有效數字和小數點後 10 位的精確度。
flowchart TD A[建立高精確度小數 Series] --> B{檢查資料型別} B -->|使用 pa.decimal256| C[處理高精確度小數資料] C --> D[輸出結果]
程式碼解說
此範例程式碼建立了一個包含高精確度小數的 Pandas Series,並使用 pa.decimal256
資料型別進行儲存。該資料型別能夠支援高達 76 位有效數字,確保了資料的精確性。
NumPy 資料型別系統的限制
NumPy 資料型別系統是 Pandas 在 2.x 和 3.x 版本中的預設選擇,但它存在一些限制,特別是在處理缺失值時。
缺失值對整數型別的影響
# 建立一個包含缺失值的整數 Series
ser = pd.Series([0, None, 2])
print(ser)
輸出結果:
0 0.0 1 NaN 2 2.0 dtype: float64
程式碼解說
當 Series 中包含缺失值(None)時,Pandas 會自動將整數型別轉換為浮點數型別(float64)。這種轉換可能會導致資料型別的意外變更。
使用 Pandas 擴充套件型別的最佳實踐
Pandas 提供了多種擴充套件型別(如 Int64Dtype
、StringDtype
等)來克服 NumPy 資料型別的限制。
使用 Int64Dtype 處理整數資料
# 使用 Int64Dtype 建立包含缺失值的整數 Series
ser = pd.Series([0, None, 2], dtype=pd.Int64Dtype())
print(ser)
輸出結果:
0 0 1 <NA> 2 2 dtype: Int64
圖表翻譯:
此範例展示瞭如何使用 Int64Dtype
資料型別處理包含缺失值的整數資料。
flowchart TD A[建立整數 Series] --> B{選擇資料型別} B -->|使用 Int64Dtype| C[支援缺失值] B -->|使用 int| D[不支援缺失值] C --> E[正確處理缺失值] D --> F[引發 TypeError]
程式碼解說
使用 Int64Dtype
可以正確處理包含缺失值的整數資料,避免了 NumPy 整數型別無法儲存缺失值的問題。
物件型別的應用場景與限制
Pandas 中的 object
型別是一種通用型別,可以儲存任意 Python 物件。然而,這種靈活性也帶來了一些問題。
物件型別的限制
# 建立一個使用物件型別的 Series
ser = pd.Series([True, False, None, "one of these things", ["is not like"], ["the other"]])
print(ser)
輸出結果:
0 True 1 False 2 None 3 one of these things 4 [is not like] 5 [the other] dtype: object
圖表翻譯:
此範例展示了 object
型別的靈活性,但也說明瞭其缺乏型別檢查的問題。
flowchart TD A[建立 Series] --> B{選擇資料型別} B -->|使用 object| C[靈活但無型別檢查] B -->|使用特定型別| D[有型別檢查] C --> E[可能儲存任意資料] D --> F[確保資料一致性]
程式碼解說
object
型別可以儲存任意 Python 物件,但這也意味著失去了型別檢查的保障,可能導致資料不一致的問題。
資料框的資料型別控制
在建立 DataFrame 時,可以透過 astype
方法來控制各欄位的資料型別。
控制 DataFrame 的資料型別
# 建立 DataFrame 並指定資料型別
df = pd.DataFrame([
["foo", 1, 123.45],
["bar", 2, 333.33],
["baz", 3, 999.99],
], columns=list("abc"))
# 使用 astype 方法轉換資料型別
df = df.astype({
"a": pd.StringDtype(),
"b": pd.Int64Dtype(),
"c": pd.Float64Dtype(),
})
print(df.dtypes)
輸出結果:
a string b Int64 c Float64 dtype: object
圖表翻譯:
此範例展示瞭如何使用 astype
方法來控制 DataFrame 中各欄位的資料型別。
flowchart TD A[建立 DataFrame] --> B{控制資料型別} B -->|使用 astype| C[轉換為期望型別] C --> D[輸出結果]
程式碼解說
透過 astype
方法,可以將 DataFrame 中的各欄位轉換為所需的資料型別,確保資料的一致性和正確性。